package main import ( "cid/controller/app" "cid/controller/data" "cid/controller/dataengine" "cid/controller/mapping" "cid/controller/yidun" controllerYidun "cid/controller/yidun" serviceDataengine "cid/service/dataengine" serviceYidun "cid/service/yidun" "context" "fmt" "os" "path/filepath" "time" _ "gitea.com/red-future/common/consul" "gitea.com/red-future/common/http" "gitea.com/red-future/common/jaeger" _ "github.com/gogf/gf/contrib/drivers/pgsql/v2" _ "github.com/gogf/gf/contrib/nosql/redis/v2" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/net/ghttp" ) func main() { ctx := context.Background() defer jaeger.ShutDown(ctx) // 设置时区为东八区 loc, err := time.LoadLocation("Asia/Shanghai") if err == nil { time.Local = loc } // 关键:设置 PGTZ 环境变量,lib/pq 驱动在连接 pg 时会自动设置 session timezone // 确保从数据库读取 TIMESTAMPTZ 时返回的是东八区时间,gtime.Time 序列化输出北京时间 os.Setenv("PGTZ", "Asia/Shanghai") // 初始化易盾客户端 if err := serviceYidun.InitYidunClients(ctx); err != nil { panic(fmt.Sprintf("初始化易盾客户端失败: %v", err)) } g.Log().Info(ctx, "易盾客户端初始化成功") // 启动内容送检定时任务 startContentCheckService(ctx) // 获取前端目录 frontendDir := getFrontendDir() // 注册前端静态文件路由(在使用 http.Httpserver 之前) registerFrontendRoutes(frontendDir) // 注册 API 路由并启动服务器 http.RouteRegister([]interface{}{ data.Platform, data.ApiInterface, data.DataFetch, mapping.DataMapping, app.Application, controllerYidun.YidunController, controllerYidun.YidunCallback, yidun.ContentCheck, dataengine.MaterialVerify, }) // 打印前端访问地址 port := g.Cfg().MustGet(ctx, "server.address", ":3001").String() g.Log().Info(ctx, "============================================") g.Log().Infof(ctx, "🌐 前端访问地址: http://localhost%s", port) g.Log().Info(ctx, "============================================") select {} } // getFrontendDir 获取前端目录路径 func getFrontendDir() string { execPath, _ := os.Executable() execDir := filepath.Dir(execPath) frontendDir := filepath.Join(execDir, "resource", "frontend") if _, err := os.Stat(frontendDir); os.IsNotExist(err) { cwd, _ := os.Getwd() frontendDir = filepath.Join(cwd, "resource", "frontend") } return frontendDir } // registerFrontendRoutes 注册前端静态文件路由 func registerFrontendRoutes(frontendDir string) { if _, err := os.Stat(frontendDir); os.IsNotExist(err) { g.Log().Warningf(context.Background(), "前端目录不存在: %s", frontendDir) return } s := http.Httpserver // 静态资源路由 s.BindHandler("/frontend/{file}", func(r *ghttp.Request) { file := r.Get("file").String() filePath := filepath.Join(frontendDir, file) if _, err := os.Stat(filePath); err == nil { r.Response.ServeFile(filePath) } else { r.Response.WriteStatus(404) } }) // 首页/主入口 s.BindHandler("/", func(r *ghttp.Request) { indexFile := filepath.Join(frontendDir, "material-verify.html") if _, err := os.Stat(indexFile); err == nil { r.Response.ServeFile(indexFile) } else { r.Response.Write("
前端页面未找到
") } }) } // startContentCheckService 启动内容送检服务 func startContentCheckService(ctx context.Context) { // 检查是否启用定时送检任务 schedulerEnabled := g.Cfg().MustGet(ctx, "content_check.scheduler_enabled", true).Bool() if !schedulerEnabled { g.Log().Info(ctx, "定时送检任务已禁用(scheduler_enabled=false),仅支持API手动送检") return } // 配置送检服务参数 config := serviceDataengine.ContentCheckConfig{ BatchSize: g.Cfg().MustGet(ctx, "content_check.batch_size", 10).Int(), ImageEnabled: g.Cfg().MustGet(ctx, "content_check.image_enabled", true).Bool(), VideoEnabled: g.Cfg().MustGet(ctx, "content_check.video_enabled", true).Bool(), IntervalSeconds: g.Cfg().MustGet(ctx, "content_check.interval_seconds", 30).Int(), } serviceDataengine.TencentContentCheck.SetConfig(config) // 启动服务 if err := serviceDataengine.TencentContentCheck.Start(ctx); err != nil { g.Log().Errorf(ctx, "启动内容送检服务失败: %v", err) } else { g.Log().Info(ctx, "内容送检服务启动成功") } }