package main import ( "embed" "io/fs" "log" "net/http" "strings" "git.x2erp.com/qdy/go-base/config" "git.x2erp.com/qdy/go-base/container" "git.x2erp.com/qdy/go-base/ctx" "git.x2erp.com/qdy/go-base/graceful" "git.x2erp.com/qdy/go-base/logger" "git.x2erp.com/qdy/go-base/model/response" "git.x2erp.com/qdy/go-base/webx" "git.x2erp.com/qdy/go-base/webx/router" "git.x2erp.com/qdy/go-db/factory/database" "git.x2erp.com/qdy/go-db/factory/mongodb" "git.x2erp.com/qdy/go-svc-code/internal/api" opencodecontainer "git.x2erp.com/qdy/go-svc-code/internal/opencode/container" ) //go:embed web/dist var frontendFS embed.FS var ( appName = "svc-code" appVersion = "1" ) func main() { //0.日志 logBootFactory := logger.InitBootLog() //1.获取配置文件 cfg := config.GetConfig() cfg.SetAppName(appName) cfg.SetAppVersion(appVersion) //2.创建关闭容器 ctr := container.NewContainer(cfg) //注册日志,实现自动关闭 container.Reg(ctr, logBootFactory) //3.创建数据库工厂--如果需求 dbFactory := container.Create(ctr, database.CreateDBFactory) // 创建mongodb mongoDBFactory := container.Create(ctr, mongodb.CreateFactory) mongoDBFactory.TestConnection() // 测试数据库连接 dbFactory.TestConnection() // 初始化OpenCode实例管理器 serviceConfig := cfg.GetServiceConfig() basePort := serviceConfig.Port basePath := "./opencode_projects" globalConfigPath := "./.opencode/opencode.json" instanceManager, err := opencodecontainer.NewInstanceManager(basePort, basePath, globalConfigPath) if err != nil { log.Fatalf("创建OpenCode实例管理器失败: %v", err) } // 注册清理函数(TODO: 需要实现正确的注册方法) // ctr.Register(func() { // instanceManager.Cleanup() // }) //得到webservice服务工厂 webxFactory := webx.GetWebServiceFactory() //建立httpService服务 webService, _ := webxFactory.CreateService(serviceConfig) //建立路由-api routerService := router.NewWebService(webService.GetRouter()) //注册路由--api registerDefaultRouter(routerService, mongoDBFactory, instanceManager) // 注册OpenCode事件流路由 webService.GetRouter().HandleFunc("/api/opencode/projects/{id}/events", api.EventsStreamHandler(instanceManager)) // 静态文件服务(使用嵌入的前端资源) frontendHandler := ServeFrontend() webService.GetRouter().Handle("/", frontendHandler) // 注册健康检查-api // health.RegisterConsulHealthCheck(routerService) // 暂时注释,避免路由冲突 //启动服务 webService.Run() //启用运行日志 container.Create(ctr, logger.InitRuntimeLogger) //注册到注册中心 //container.Create(ctr, consul.Register) //等待关闭 //webService.WaitForServiceShutdown(ctr) graceful.WaitForShutdown(cfg.GetServiceConfig().ServiceName, ctr, webService.GetServer()) } func registerDefaultRouter(ws *router.RouterService, mongoDBFactory *mongodb.MongoDBFactory, instanceManager *opencodecontainer.InstanceManager) { log.Printf("注册路由...") // 健康检查 ws.GET("/api/health", func(reqCtx *ctx.RequestContext) (*response.QueryResult[interface{}], error) { return &response.QueryResult[interface{}]{ Data: map[string]interface{}{ "healthy": true, "version": "1.0", }, }, nil }, ).Register() // 模拟项目列表 (GET /api/projects) - 临时使用 ws.GET("/api/projects", api.MockProjectsHandler).Register() // OpenCode实例管理路由 // 启动项目OpenCode实例 ws.POST("/api/opencode/projects/{id}/start", api.StartInstanceHandler(instanceManager)).Register() // 停止项目OpenCode实例 ws.POST("/api/opencode/projects/{id}/stop", api.StopInstanceHandler(instanceManager)).Register() // 获取项目实例状态 ws.GET("/api/opencode/projects/{id}/status", api.InstanceStatusHandler(instanceManager)).Register() // 创建OpenCode会话 ws.POST("/api/opencode/projects/{id}/sessions", api.CreateSessionHandler(instanceManager)).Register() // 发送消息到OpenCode会话(同步) ws.POST("/api/opencode/projects/{id}/messages", api.SendMessageHandler(instanceManager)).Register() // 异步发送消息到OpenCode会话 ws.POST("/api/opencode/projects/{id}/messages/async", api.SendMessageAsyncHandler(instanceManager)).Register() // 准备项目目录 ws.POST("/api/projects/{id}/prepare-directory", api.PrepareDirectoryHandler(instanceManager)).Register() // 测试路由 ws.POST("/api/test", func(req map[string]interface{}, reqCtx *ctx.RequestContext) (*response.QueryResult[interface{}], error) { log.Printf("测试路由被调用") return &response.QueryResult[interface{}]{ Success: true, Data: map[string]interface{}{"message": "测试成功"}, }, nil }, ).Register() log.Printf("路由注册完成") } // ServeFrontend 返回处理前端静态文件的服务处理器 func ServeFrontend() http.Handler { // 从嵌入的文件系统中获取 dist 子目录 fsys, err := fs.Sub(frontendFS, "web/dist") if err != nil { // 如果失败,回退到空文件系统 return http.FileServer(http.FS(frontendFS)) } // 创建自定义文件服务器,处理 SPA 路由 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // 只处理 GET 和 HEAD 请求 if r.Method != "GET" && r.Method != "HEAD" { // 对于非 GET/HEAD 请求,返回 404 让 API 路由处理 http.NotFound(w, r) return } // API 路径不处理 if strings.HasPrefix(r.URL.Path, "/api/") { http.NotFound(w, r) return } // 尝试从文件系统读取请求的文件 filePath := strings.TrimPrefix(r.URL.Path, "/") if filePath == "" { filePath = "index.html" } // 检查文件是否存在 _, err := fs.Stat(fsys, filePath) if err != nil { // 文件不存在,返回 index.html 用于 SPA 路由 indexData, err := frontendFS.ReadFile("web/dist/index.html") if err != nil { http.Error(w, "Not Found", http.StatusNotFound) return } w.Header().Set("Content-Type", "text/html; charset=utf-8") w.Write(indexData) return } // 文件存在,正常提供 http.FileServer(http.FS(fsys)).ServeHTTP(w, r) }) }