- 微信登录改为真实 jscode2session,session_key 不再下发客户端 - 支付回调增加 HMAC 验签(X-Pay-Sign)与幂等处理,未配置密钥时拒绝回调 - 订单金额一律以服务端商品表定价,禁止客户端传入金额 - 付费许愿改为支付成功后创建,不再先许愿后付款 - 管理后台增加登录认证(ADMIN_PASSWORD + role=admin JWT + HttpOnly Cookie) - 订单详情/取消增加本人归属校验,修复越权访问 - 版本信息改为 ldflags 注入单一链路,GoVersion 用 runtime.Version() - 恢复 gin 默认访问日志(原 Logger 中间件输出为空) - 加载 HTML 模板修复后台页面 500;godotenv 加载 .env.local - CORS 支持 CORS_ORIGINS 白名单配置
131 lines
3.6 KiB
Go
131 lines
3.6 KiB
Go
package main
|
||
|
||
import (
|
||
"log"
|
||
|
||
"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"
|
||
)
|
||
|
||
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()
|
||
|
||
// 设置运行模式
|
||
if cfg.Server.Env == "production" {
|
||
gin.SetMode(gin.ReleaseMode)
|
||
}
|
||
|
||
// 创建路由(gin.Default 自带访问日志与 Recovery)
|
||
r := gin.Default()
|
||
|
||
// 中间件
|
||
r.Use(middleware.CORS())
|
||
|
||
// 静态文件与管理后台模板
|
||
r.Static("/static", "./web/static")
|
||
r.StaticFile("/", "./web/index.html")
|
||
r.LoadHTMLFiles("./web/index.html")
|
||
|
||
// API 路由
|
||
api := r.Group("/api")
|
||
{
|
||
// 版本信息(无需认证)
|
||
api.GET("/version", handler.GetVersion)
|
||
|
||
// 用户相关
|
||
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)
|
||
}
|
||
|
||
// 支付相关
|
||
pay := api.Group("/pay")
|
||
{
|
||
pay.POST("/create", middleware.Auth(), handler.CreateOrder)
|
||
pay.POST("/notify", handler.PayNotify)
|
||
pay.GET("/status/:orderId", 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)
|
||
}
|
||
}
|
||
|
||
// 管理后台路由(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("/wishes", handler.AdminWishes)
|
||
authed.GET("/settings", handler.AdminSettings)
|
||
}
|
||
}
|
||
|
||
// 启动服务器
|
||
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)
|
||
}
|
||
}
|