Files
observer/server/main.go
T
2026-08-25 16:52:55 +08:00

102 lines
3.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package main
import (
"context"
"os"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/util/gconv"
"observer-server/biz/controller"
"observer-server/common"
)
func main() {
ctx := context.Background()
initDatabase(ctx)
s := g.Server()
// Android APK 下载静态托管:app.apkDir 目录下固定文件 observer-latest.apk
// URL 固定 /download/observer-latest.apk(绕过统一响应包装,纯二进制流)
if err := os.MkdirAll(common.ApkDir(ctx), 0o755); err != nil {
g.Log().Fatalf(ctx, "创建 APK 目录失败: %+v", err)
}
s.AddStaticPath("/download", common.ApkDir(ctx))
// 客户端接口组:统一响应包装 {"code":0,"message":"ok","data":...}
// 账号组公开(注册/登录),业务组(订单/授权)需登录态(Authorization: Bearer token
s.Group("/api/v1", func(group *ghttp.RouterGroup) {
group.Middleware(common.UnifiedResponse)
common.BindController(group, controller.Auth, controller.AppVersion)
})
s.Group("/api/v1", func(group *ghttp.RouterGroup) {
group.Middleware(common.UnifiedResponse, common.AuthRequired)
common.BindController(group, controller.Order, controller.License)
})
// 支付回调组:不做统一包装,按渠道应答格式直接返回(微信 SUCCESS/FAIL JSON、支付宝 success/failure 文本)
s.Group("/api/v1/payment", func(group *ghttp.RouterGroup) {
common.BindController(group, controller.Payment)
})
// 管理端接口组:统一响应包装 + 静态 token 鉴权
s.Group("/api/v1/admin", func(group *ghttp.RouterGroup) {
group.Middleware(common.UnifiedResponse, common.AdminAuth)
common.BindController(group, controller.Admin)
})
// 管理端静态页面托管:构建产物输出到 admin_distserver_admin/ 构建),SPA history 路由回退 index.html
// 目录不存在(尚未构建前端)时跳过,不影响 API 启动
if stat, err := os.Stat("./admin_dist"); err == nil && stat.IsDir() {
s.AddStaticPath("/admin", "./admin_dist")
s.BindMiddlewareDefault(common.SpaFallback("./admin_dist", "/admin"))
} else {
g.Log().Warningf(ctx, "admin_dist 不存在,跳过管理端静态托管(cd server_admin && npm run build")
}
s.Run()
}
// initDatabase 数据初始化:存量库以 PRAGMA user_version 版本化迁移;
// v2 = 删 license.plan_id 列(无业务语义);v3 = 删 plan 表(套餐已配置化,清残留表)。
func initDatabase(ctx context.Context) {
res, err := g.DB().GetAll(ctx, "PRAGMA user_version")
if err != nil {
g.Log().Fatalf(ctx, "读取库版本失败: %+v", err)
}
version := 0
if len(res) > 0 {
version = gconv.Int(res[0]["user_version"])
}
if version < 2 {
// v2:删除 license.plan_id(只留到期时间;PRAGMA table_info 检测列存在才 DROP
// 全新库建表已无此列、直接跳过,不丢账号数据)
cols, err := g.DB().GetAll(ctx, "PRAGMA table_info(license)")
if err != nil {
g.Log().Fatalf(ctx, "读取 license 表结构失败: %+v", err)
}
hasPlanId := false
for _, col := range cols {
if gconv.String(col["name"]) == "plan_id" {
hasPlanId = true
break
}
}
if hasPlanId {
if _, err := g.DB().Exec(ctx, "ALTER TABLE license DROP COLUMN plan_id"); err != nil {
g.Log().Fatalf(ctx, "删除 license.plan_id 列失败: %+v", err)
}
}
if _, err := g.DB().Exec(ctx, "PRAGMA user_version = 2"); err != nil {
g.Log().Fatalf(ctx, "库版本写入失败: %+v", err)
}
g.Log().Infof(ctx, "数据库初始化完成(version=2)")
}
if version < 3 {
// v3:套餐改 config.yml 配置后清理残留 plan 表(订单快照 payment_order.plan_id 不受影响)
if _, err := g.DB().Exec(ctx, "DROP TABLE IF EXISTS plan"); err != nil {
g.Log().Fatalf(ctx, "删除残留 plan 表失败: %+v", err)
}
if _, err := g.DB().Exec(ctx, "PRAGMA user_version = 3"); err != nil {
g.Log().Fatalf(ctx, "库版本写入失败: %+v", err)
}
g.Log().Infof(ctx, "数据库初始化完成(version=3)")
}
}