refactor(server): 拆分 cmd/mini 与 cmd/web 两个部署入口
小程序接口(lunar.neatcn.com)与商业版 Web(kepo.net)此前共用一个 二进制,导致小程序域名暴露商业后台与 Stripe Webhook,商业站点也 暴露了许愿树等小程序接口。现拆为两个入口: - cmd/mini:微信登录、生辰档案、许愿树、百科、版本号 - cmd/web:邮箱登录、commerce、报告/订单、/admin 后台、商业前端 静态托管与 SPA 回退 - internal/app 承载共用启动与路由注册;图片资源移至 internal/assets CI:server-deploy.yml 改构建 cmd/mini 并随包移除后台静态资源; 新增 web-deploy.yml 构建 cmd/web + web-commercial 前端,发布 web-v* Release;deploy-watch.sh 支持 TAG_PREFIX/ASSET_NAME 变量; 新增 deploy-web.sh(systemd 版)与 kepo 一次性配置文档 .gitea/docs/PULL_DEPLOY_WEB.md。 Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
parent
28309e0257
commit
12fa2b0d19
Binary file not shown.
|
Before Width: | Height: | Size: 332 KiB |
@@ -1,199 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"io/fs"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gouki/lunar-server/internal/config"
|
||||
"github.com/gouki/lunar-server/internal/handler"
|
||||
"github.com/gouki/lunar-server/internal/middleware"
|
||||
"github.com/gouki/lunar-server/internal/service"
|
||||
"github.com/joho/godotenv"
|
||||
)
|
||||
|
||||
// 版本信息由 CI 通过 ldflags 注入:
|
||||
// -X main.Version=... -X main.BuildTime=... -X main.CommitSha=...
|
||||
var (
|
||||
Version = "dev"
|
||||
BuildTime = "unknown"
|
||||
CommitSha = "unknown"
|
||||
)
|
||||
|
||||
// 内嵌图片资源(许愿树背景图等),随二进制一起部署
|
||||
//
|
||||
//go:embed assets/images
|
||||
var assetsFS embed.FS
|
||||
|
||||
func main() {
|
||||
// 加载本地环境变量文件(生产环境由容器注入,忽略缺失;../.env.local 兼容从 server/ 目录启动)
|
||||
_ = godotenv.Load(".env.local", "../.env.local", ".env")
|
||||
|
||||
// 将构建时注入的版本信息传递给 handler
|
||||
handler.Version = Version
|
||||
handler.BuildTime = BuildTime
|
||||
handler.CommitSha = CommitSha
|
||||
log.Printf("lunar-server %s (commit %s, built at %s)", Version, CommitSha, BuildTime)
|
||||
|
||||
// 加载配置
|
||||
cfg := config.Load()
|
||||
|
||||
// 初始化数据库
|
||||
if err := config.InitDB(cfg); err != nil {
|
||||
log.Fatalf("Failed to initialize database: %v", err)
|
||||
}
|
||||
|
||||
// 启动机器人许愿定时任务
|
||||
wishService := service.NewWishService()
|
||||
wishService.StartRobotWishJob()
|
||||
|
||||
// 启动维基百科同步定时任务(历史上的今天;首次启动数据为空时自动全量抓取)
|
||||
handler.WikiSync = service.NewWikiSyncService(config.GetDB())
|
||||
handler.WikiSync.StartScheduler(cfg.Wiki.SyncIntervalDays, cfg.Wiki.SyncHour)
|
||||
|
||||
// 启动节气详情同步定时任务(百度百科;独立于维基同步,首次启动缺详情时自动补齐)
|
||||
handler.BaikeTermSync = service.NewBaikeTermSyncService(config.GetDB())
|
||||
handler.BaikeTermSync.StartScheduler(cfg.Wiki.SyncIntervalDays, cfg.Wiki.SyncHour)
|
||||
|
||||
// 设置运行模式
|
||||
if cfg.Server.Env == "production" {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
}
|
||||
|
||||
// 创建路由(gin.Default 自带访问日志与 Recovery)
|
||||
r := gin.Default()
|
||||
|
||||
// 中间件
|
||||
r.Use(middleware.CORS())
|
||||
|
||||
// 商业版前端静态文件、旧管理后台静态资源与模板
|
||||
// 商业站点部署在根路径;后台仍通过 /admin 访问。
|
||||
r.Static("/assets", "./frontend/assets")
|
||||
r.GET("/index.html", func(c *gin.Context) { c.File("./frontend/index.html") })
|
||||
r.GET("/", func(c *gin.Context) { c.File("./frontend/index.html") })
|
||||
r.Static("/static", "./web/static")
|
||||
r.LoadHTMLFiles("./web/index.html")
|
||||
|
||||
// 内嵌图片资源(许愿树背景图等)
|
||||
if imagesFS, err := fs.Sub(assetsFS, "assets/images"); err == nil {
|
||||
r.GET("/images/*path", gin.WrapH(http.StripPrefix("/images", http.FileServer(http.FS(imagesFS)))))
|
||||
}
|
||||
|
||||
// API 路由
|
||||
api := r.Group("/api")
|
||||
{
|
||||
// 版本信息(无需认证)
|
||||
api.GET("/version", handler.GetVersion)
|
||||
|
||||
auth := api.Group("/auth")
|
||||
{
|
||||
auth.POST("/email/request", middleware.RateLimit(), handler.RequestEmailLoginCode)
|
||||
auth.POST("/email/verify", middleware.RateLimit(), handler.VerifyEmailLoginCode)
|
||||
}
|
||||
|
||||
commerce := api.Group("/commerce")
|
||||
{
|
||||
commerce.POST("/charts", middleware.RateLimit(), handler.SaveChartRecord)
|
||||
commerce.POST("/checkout", middleware.Auth(), handler.CreateReportCheckout)
|
||||
commerce.GET("/orders/:orderNo", middleware.Auth(), handler.GetCommerceOrder)
|
||||
commerce.GET("/reports/:reportId/access", middleware.Auth(), handler.GetReportAccess)
|
||||
commerce.GET("/reports", middleware.Auth(), handler.ListPurchasedReports)
|
||||
commerce.GET("/reports/recover/:accessCode", middleware.Auth(), handler.GetReportByAccessCode)
|
||||
commerce.POST("/webhooks/stripe", handler.StripeWebhook)
|
||||
commerce.POST("/webhooks/binance-pay", handler.BinancePayWebhook)
|
||||
}
|
||||
|
||||
// 用户相关
|
||||
user := api.Group("/user")
|
||||
{
|
||||
user.POST("/login", handler.UserLogin)
|
||||
user.POST("/logout", handler.UserLogout)
|
||||
user.GET("/profile", middleware.Auth(), handler.GetUserProfile)
|
||||
user.PUT("/profile", middleware.Auth(), handler.UpdateUserProfile)
|
||||
user.POST("/auth", handler.WechatAuth)
|
||||
// 生辰档案(八字绑定,每用户免费 3 个)
|
||||
user.GET("/birth-profiles", middleware.Auth(), handler.GetBirthProfiles)
|
||||
user.POST("/birth-profiles", middleware.Auth(), handler.CreateBirthProfile)
|
||||
user.DELETE("/birth-profiles/:id", middleware.Auth(), handler.DeleteBirthProfile)
|
||||
}
|
||||
|
||||
// 支付相关
|
||||
pay := api.Group("/pay")
|
||||
{
|
||||
pay.POST("/create", middleware.Auth(), handler.CreateOrder)
|
||||
pay.POST("/notify", handler.PayNotify)
|
||||
pay.GET("/status/:orderId", middleware.Auth(), handler.GetPayStatus)
|
||||
}
|
||||
|
||||
// 订单相关
|
||||
order := api.Group("/order")
|
||||
{
|
||||
order.GET("/list", middleware.Auth(), handler.GetOrderList)
|
||||
order.GET("/:id", middleware.Auth(), handler.GetOrderDetail)
|
||||
order.POST("/cancel/:id", middleware.Auth(), handler.CancelOrder)
|
||||
}
|
||||
|
||||
// 许愿相关
|
||||
wish := api.Group("/wish")
|
||||
{
|
||||
wish.GET("/trees", handler.GetWishTrees)
|
||||
wish.GET("/tree", handler.GetWishTree)
|
||||
wish.GET("/tree/:id/wishes", handler.GetTreeWishes)
|
||||
wish.POST("/create", middleware.Auth(), handler.CreateWish)
|
||||
wish.GET("/list", handler.GetWishList)
|
||||
wish.GET("/:id", handler.GetWishDetail)
|
||||
wish.DELETE("/:id", middleware.Auth(), handler.DeleteWish)
|
||||
wish.GET("/products", handler.GetWishProducts)
|
||||
}
|
||||
|
||||
// 百科/历史上的今天(无需认证)
|
||||
wiki := api.Group("/wiki")
|
||||
{
|
||||
wiki.GET("/entries", handler.GetWikiEntries)
|
||||
wiki.GET("/on-this-day", handler.GetOnThisDay)
|
||||
}
|
||||
}
|
||||
|
||||
// 管理后台路由(Inertia.js)
|
||||
admin := r.Group("/admin")
|
||||
{
|
||||
// 登录/登出接口无需认证;未配置 ADMIN_PASSWORD 时登录返回 503
|
||||
admin.POST("/login", handler.AdminLogin)
|
||||
admin.POST("/logout", handler.AdminLogout)
|
||||
|
||||
authed := admin.Group("")
|
||||
authed.Use(middleware.AdminAuth())
|
||||
{
|
||||
authed.GET("/", handler.AdminDashboard)
|
||||
authed.GET("/users", handler.AdminUsers)
|
||||
authed.GET("/orders", handler.AdminOrders)
|
||||
authed.GET("/charts", handler.AdminCharts)
|
||||
authed.GET("/wishes", handler.AdminWishes)
|
||||
authed.GET("/wiki", handler.AdminWiki)
|
||||
authed.GET("/settings", handler.AdminSettings)
|
||||
|
||||
// 百科管理 API
|
||||
authed.GET("/api/wiki/entries", handler.AdminWikiEntries)
|
||||
authed.POST("/api/wiki/entries", handler.AdminWikiEntryCreate)
|
||||
authed.PUT("/api/wiki/entries/:id", handler.AdminWikiEntryUpdate)
|
||||
authed.DELETE("/api/wiki/entries/:id", handler.AdminWikiEntryDelete)
|
||||
authed.GET("/api/wiki/sync-status", handler.AdminWikiSyncStatus)
|
||||
authed.POST("/api/wiki/sync", handler.AdminWikiSyncTrigger)
|
||||
authed.GET("/api/orders", handler.AdminCommerceOrders)
|
||||
authed.GET("/api/orders/stats", handler.AdminCommerceStats)
|
||||
authed.GET("/api/charts", handler.AdminChartRecords)
|
||||
// 节气详情同步(百度百科,独立于维基)
|
||||
authed.GET("/api/wiki/term-sync-status", handler.AdminTermSyncStatus)
|
||||
authed.POST("/api/wiki/term-sync", handler.AdminTermSyncTrigger)
|
||||
}
|
||||
}
|
||||
|
||||
// 启动服务器
|
||||
addr := ":" + cfg.Server.Port
|
||||
log.Printf("Server starting on %s", addr)
|
||||
if err := r.Run(addr); err != nil {
|
||||
log.Fatalf("Failed to start server: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// 小程序接口服务(lunar.neatcn.com):微信登录、许愿树、百科等。
|
||||
// 不包含商业版支付、邮箱验证码登录与管理后台。
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/gouki/lunar-server/internal/app"
|
||||
"github.com/gouki/lunar-server/internal/handler"
|
||||
)
|
||||
|
||||
// 版本信息由 CI 通过 ldflags 注入:
|
||||
// -X main.Version=... -X main.BuildTime=... -X main.CommitSha=...
|
||||
var (
|
||||
Version = "dev"
|
||||
BuildTime = "unknown"
|
||||
CommitSha = "unknown"
|
||||
)
|
||||
|
||||
func main() {
|
||||
handler.Version = Version
|
||||
handler.BuildTime = BuildTime
|
||||
handler.CommitSha = CommitSha
|
||||
log.Printf("lunar-mini-server %s (commit %s, built at %s)", Version, CommitSha, BuildTime)
|
||||
|
||||
cfg := app.Bootstrap()
|
||||
r := app.NewEngine()
|
||||
app.SetupMini(r, cfg)
|
||||
app.Run(r, cfg.Server.Port)
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// 商业版 Web 服务(kepo.net):邮箱验证码登录、Stripe / Binance Pay
|
||||
// 支付、订单权益、报告快照与恢复码、管理后台、商业前端静态站点。
|
||||
// 不包含小程序接口(微信登录、许愿树、百科同步等)。
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/gouki/lunar-server/internal/app"
|
||||
"github.com/gouki/lunar-server/internal/handler"
|
||||
)
|
||||
|
||||
// 版本信息由 CI 通过 ldflags 注入:
|
||||
// -X main.Version=... -X main.BuildTime=... -X main.CommitSha=...
|
||||
var (
|
||||
Version = "dev"
|
||||
BuildTime = "unknown"
|
||||
CommitSha = "unknown"
|
||||
)
|
||||
|
||||
func main() {
|
||||
handler.Version = Version
|
||||
handler.BuildTime = BuildTime
|
||||
handler.CommitSha = CommitSha
|
||||
log.Printf("lunar-web-server %s (commit %s, built at %s)", Version, CommitSha, BuildTime)
|
||||
|
||||
cfg := app.Bootstrap()
|
||||
r := app.NewEngine()
|
||||
app.SetupWeb(r, cfg)
|
||||
app.Run(r, cfg.Server.Port)
|
||||
}
|
||||
Reference in New Issue
Block a user