This commit is contained in:
2026-08-26 18:15:54 +08:00
parent 54d343b739
commit a4568d8a55
79 changed files with 11264 additions and 560 deletions
+101 -3
View File
@@ -9,29 +9,35 @@ import (
"github.com/gogf/gf/v2/util/gconv"
"observer-server/biz/controller"
"observer-server/biz/service"
"observer-server/common"
)
func main() {
ctx := context.Background()
initDatabase(ctx)
// 封面存量迁移:历史固定命名 cover* → UUID jpg(幂等,新库空跑)
if err := service.Dataset.MigrateLegacyCovers(ctx); err != nil {
g.Log().Errorf(ctx, "封面存量迁移失败: %+v", err)
}
s := g.Server()
// Android APK 下载静态托管:app.apkDir 目录下固定文件 observer-latest.apk
// URL 固定 /download/observer-latest.apk(绕过统一响应包装,纯二进制流)
// URL 固定 /download/observer-latest.apk(绕过统一响应包装,纯二进制流)
// 模型热更新文件同根 /download/models/<数据集名>/latest.tflitedatasetDir 下 models 目录)
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
// 账号组公开(注册/登录),业务组(订单/授权/模型目录)需登录态(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)
common.BindController(group, controller.Order, controller.License, controller.ModelCatalog)
})
// 支付回调组:不做统一包装,按渠道应答格式直接返回(微信 SUCCESS/FAIL JSON、支付宝 success/failure 文本)
s.Group("/api/v1/payment", func(group *ghttp.RouterGroup) {
@@ -56,6 +62,8 @@ func main() {
} else {
g.Log().Warningf(ctx, "admin_dist 不存在,跳过管理端静态托管(cd server_admin && npm run build")
}
// 后台协程:训练进度轮询 + 孤儿预标注任务恢复
service.Training.StartBackgroundJobs(ctx)
s.Run()
}
@@ -104,4 +112,94 @@ func initDatabase(ctx context.Context) {
}
g.Log().Infof(ctx, "数据库初始化完成(version=3)")
}
if version < 8 {
// v8dataset 加训练配置/展示列 + label_task 加 filenames(多选批量标注)。
// PRAGMA table_info 逐列检测缺失才 ADD COLUMN,全新库建表自带全列直接跳过
addCols := []struct{ table, col, ddl string }{
{"dataset", "cover", "TEXT"},
{"dataset", "description", "TEXT"},
{"dataset", "ai_endpoint", "TEXT"},
{"dataset", "ai_model", "TEXT"},
{"dataset", "train_host", "TEXT"},
{"dataset", "train_user", "TEXT"},
{"dataset", "train_password", "TEXT"},
{"dataset", "train_key", "TEXT"},
{"label_task", "filenames", "TEXT"},
}
for _, c := range addCols {
cols, err := g.DB().GetAll(ctx, "PRAGMA table_info("+c.table+")")
if err != nil {
g.Log().Fatalf(ctx, "读取 %s 表结构失败: %+v", c.table, err)
}
exists := false
for _, col := range cols {
if gconv.String(col["name"]) == c.col {
exists = true
break
}
}
if exists {
continue
}
if _, err := g.DB().Exec(ctx, "ALTER TABLE "+c.table+" ADD COLUMN "+c.col+" "+c.ddl); err != nil {
g.Log().Fatalf(ctx, "给 %s 加列 %s 失败: %+v", c.table, c.col, err)
}
}
if _, err := g.DB().Exec(ctx, "PRAGMA user_version = 8"); err != nil {
g.Log().Fatalf(ctx, "库版本写入失败: %+v", err)
}
g.Log().Infof(ctx, "数据库初始化完成(version=8)")
}
if version < 10 {
// v10:标注流程简化,撤销候选确认两阶段 —— dataset_image 删 candidates_json 列
//(存量候选数据为空直接删;全新库建表已无此列、PRAGMA table_info 检测后跳过)
cols, err := g.DB().GetAll(ctx, "PRAGMA table_info(dataset_image)")
if err != nil {
g.Log().Fatalf(ctx, "读取 dataset_image 表结构失败: %+v", err)
}
hasCandidates := false
for _, col := range cols {
if gconv.String(col["name"]) == "candidates_json" {
hasCandidates = true
break
}
}
if hasCandidates {
if _, err := g.DB().Exec(ctx, "ALTER TABLE dataset_image DROP COLUMN candidates_json"); err != nil {
g.Log().Fatalf(ctx, "删除 candidates_json 列失败: %+v", err)
}
}
if _, err := g.DB().Exec(ctx, "PRAGMA user_version = 10"); err != nil {
g.Log().Fatalf(ctx, "库版本写入失败: %+v", err)
}
g.Log().Infof(ctx, "数据库初始化完成(version=10)")
}
if version < 11 {
// v11:移除模型存档回退机制 —— model_version 删 model_file 列
//(模型文件不落表:发布即写 latest.tflite,客户端固定下载;存量库删列、新库建表已无此列自动跳过)
cols, err := g.DB().GetAll(ctx, "PRAGMA table_info(model_version)")
if err != nil {
g.Log().Fatalf(ctx, "读取 model_version 表结构失败: %+v", err)
}
hasModelFile := false
for _, col := range cols {
if gconv.String(col["name"]) == "model_file" {
hasModelFile = true
break
}
}
if hasModelFile {
if _, err := g.DB().Exec(ctx, "ALTER TABLE model_version DROP COLUMN model_file"); err != nil {
g.Log().Fatalf(ctx, "删除 model_file 列失败: %+v", err)
}
}
if _, err := g.DB().Exec(ctx, "PRAGMA user_version = 11"); err != nil {
g.Log().Fatalf(ctx, "库版本写入失败: %+v", err)
}
g.Log().Infof(ctx, "数据库初始化完成(version=11)")
}
// 死表清理:app_config 全局训练配置表已撤销(配置走 config.yml),存量库残留表启动即删
if _, err := g.DB().Exec(ctx, "DROP TABLE IF EXISTS app_config"); err != nil {
g.Log().Fatalf(ctx, "删除残留 app_config 表失败: %+v", err)
}
}