| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232 |
- package main
-
- import (
- "fmt"
- "log"
- "net"
- "net/http"
- "sync"
- "time"
-
- "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-svc-code/internal/opencode"
- "git.x2erp.com/qdy/go-svc-code/internal/routes"
- )
-
- var (
- appName = "svc-code"
- appVersion = "1"
- )
-
- // 全局变量存储 opencode 进程信息
- var (
- opencodeProcess *opencode.Process
- opencodePort int
- opencodeMutex sync.Mutex
- )
-
- 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. 启用运行日志(需要在启动 opencode 服务之前)
- container.Create(ctr, logger.InitRuntimeLogger)
-
- // 4. 启动 opencode 服务
- port, err := startOpenCodeService()
- if err != nil {
- log.Fatalf("启动 opencode 服务失败: %v", err)
- }
- opencodePort = port
- logger.Info(fmt.Sprintf("opencode 服务已启动,端口: %d", port))
-
- // 注册 opencode 进程到关闭容器,确保主程序退出时关闭子进程
- resource := &opencodeResource{
- process: opencodeProcess,
- name: "opencode",
- }
- container.Reg(ctr, resource)
-
- // 4. 创建 opencode 客户端
- client, err := opencode.NewClient(port)
- if err != nil {
- log.Fatalf("创建 opencode 客户端失败: %v", err)
- }
-
- // 5. 得到 webservice 服务工厂
- webxFactory := webx.GetWebServiceFactory()
-
- // 6. 建立 httpService 服务
- webService, err := webxFactory.CreateService(cfg.GetServiceConfig())
- if err != nil {
- log.Fatalf("创建 HTTP 服务失败: %v", err)
- }
-
- // 7. 建立路由-api
- routerService := router.NewWebService(webService.GetRouter())
-
- // 8. 注册路由--api
- registerRoutes(routerService, client, webService)
-
- // 9. 注册前端静态文件服务
- registerStaticFiles(webService)
-
- // 10. 启动服务
- webService.Run()
-
- // 11. 等待关闭
- graceful.WaitForShutdown(cfg.GetServiceConfig().ServiceName, ctr, webService.GetServer())
- }
-
- // startOpenCodeService 启动 opencode 服务并返回端口
- func startOpenCodeService() (int, error) {
- port, err := getAvailablePort()
- if err != nil {
- return 0, fmt.Errorf("获取可用端口失败: %w", err)
- }
-
- process, err := opencode.Start(port)
- if err != nil {
- return 0, fmt.Errorf("启动 opencode 进程失败: %w", err)
- }
-
- opencodeMutex.Lock()
- opencodeProcess = process
- opencodeMutex.Unlock()
-
- // 等待 opencode 服务就绪
- if err := waitForOpenCodeReady(port); err != nil {
- process.Stop()
- return 0, fmt.Errorf("opencode 服务未就绪: %w", err)
- }
-
- return port, nil
- }
-
- // getAvailablePort 获取可用端口
- func getAvailablePort() (int, error) {
- addr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:0")
- if err != nil {
- return 0, err
- }
-
- l, err := net.ListenTCP("tcp", addr)
- if err != nil {
- return 0, err
- }
- defer l.Close()
-
- return l.Addr().(*net.TCPAddr).Port, nil
- }
-
- // waitForOpenCodeReady 等待 opencode 服务就绪
- func waitForOpenCodeReady(port int) error {
- url := fmt.Sprintf("http://127.0.0.1:%d/global/health", port)
- client := &http.Client{Timeout: 5 * time.Second}
-
- for i := 0; i < 30; i++ {
- resp, err := client.Get(url)
- if err == nil && resp.StatusCode == 200 {
- resp.Body.Close()
- return nil
- }
- if resp != nil {
- resp.Body.Close()
- }
- time.Sleep(100 * time.Millisecond)
- }
-
- return fmt.Errorf("opencode 服务在 3 秒内未就绪")
- }
-
- // registerRoutes 注册所有 API 路由
- func registerRoutes(ws *router.RouterService, client opencode.OpenCodeClient, webService *webx.WebService) {
- // 会话管理路由
- routes.RegisterSessionRoutes(ws, client)
-
- // 同步对话路由
- routes.RegisterPromptSyncRoutes(ws, client)
-
- // 流式对话路由 - 注册原始 HTTP 处理器
- webService.GetRouter().Handle("/api/prompt/stream", routes.StreamPromptHandler(client))
-
- // 日志流路由 - 注册原始 HTTP 处理器
- if opencodeProcess != nil {
- webService.GetRouter().Handle("/api/logs/stream", routes.LogStreamHandler(opencodeProcess))
- }
-
- // 健康检查路由
- ws.GET("/api/health", func(reqCtx *ctx.RequestContext) (*response.QueryResult[map[string]interface{}], error) {
- result := map[string]interface{}{
- "status": "healthy",
- "opencode_port": opencodePort,
- "service": appName,
- "version": appVersion,
- }
- return &response.QueryResult[map[string]interface{}]{
- Success: true,
- Data: result,
- }, nil
- }).Register()
- }
-
- // registerStaticFiles 注册前端静态文件服务
- func registerStaticFiles(webService *webx.WebService) {
- // 简单的静态文件服务
- fs := http.FileServer(http.Dir("web"))
- webService.GetRouter().Handle("/", fs)
- webService.GetRouter().Handle("/static/", http.StripPrefix("/static/", fs))
- }
-
- // opencodeResource 包装 opencode 进程以实现 container.Resource 接口
- type opencodeResource struct {
- process *opencode.Process
- name string
- }
-
- func (r *opencodeResource) GetName() string {
- return r.name
- }
-
- func (r *opencodeResource) Close() {
- if r.process != nil {
- r.process.Stop()
- }
- }
-
- // 日志处理器
- type logHandler struct {
- mu sync.Mutex
- logs []string
- }
-
- func (h *logHandler) Write(p []byte) (n int, err error) {
- line := string(p)
- h.mu.Lock()
- h.logs = append(h.logs, line)
- if len(h.logs) > 1000 {
- h.logs = h.logs[1:]
- }
- h.mu.Unlock()
- logger.Debug(fmt.Sprintf("opencode: %s", line))
- return len(p), nil
- }
|