説明なし
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

main.go 6.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. package main
  2. import (
  3. "fmt"
  4. "log"
  5. "net"
  6. "net/http"
  7. "sync"
  8. "time"
  9. "git.x2erp.com/qdy/go-base/config"
  10. "git.x2erp.com/qdy/go-base/container"
  11. "git.x2erp.com/qdy/go-base/ctx"
  12. "git.x2erp.com/qdy/go-base/graceful"
  13. "git.x2erp.com/qdy/go-base/logger"
  14. "git.x2erp.com/qdy/go-base/model/response"
  15. "git.x2erp.com/qdy/go-base/sdk/configure"
  16. "git.x2erp.com/qdy/go-base/webx"
  17. "git.x2erp.com/qdy/go-base/webx/router"
  18. "git.x2erp.com/qdy/go-svc-code/internal/opencode"
  19. "git.x2erp.com/qdy/go-svc-code/internal/routes"
  20. )
  21. var (
  22. appName = "svc-code"
  23. appVersion = "1"
  24. )
  25. // 全局变量存储 opencode 进程信息
  26. var (
  27. opencodeProcess *opencode.Process
  28. opencodePort int
  29. opencodeMutex sync.Mutex
  30. )
  31. func main() {
  32. // 0. 初始化启动日志
  33. logBootFactory := logger.InitBootLog()
  34. // 1. 获取配置文件
  35. cfg := config.GetConfig()
  36. cfg.SetAppName(appName)
  37. cfg.SetAppVersion(appVersion)
  38. // 2. 创建关闭容器
  39. ctr := container.NewContainer(cfg)
  40. // 注册日志,实现自动关闭
  41. container.Reg(ctr, logBootFactory)
  42. // 3. 启用运行日志(需要在启动 opencode 服务之前)
  43. container.Create(ctr, logger.InitRuntimeLogger)
  44. // 4. 启动 opencode 服务
  45. port, err := startOpenCodeService()
  46. if err != nil {
  47. log.Fatalf("启动 opencode 服务失败: %v", err)
  48. }
  49. opencodePort = port
  50. logger.Info(fmt.Sprintf("opencode 服务已启动,端口: %d", port))
  51. // 注册 opencode 进程到关闭容器,确保主程序退出时关闭子进程
  52. resource := &opencodeResource{
  53. process: opencodeProcess,
  54. name: "opencode",
  55. }
  56. container.Reg(ctr, resource)
  57. // 4. 创建 opencode 客户端
  58. client, err := opencode.NewClient(port)
  59. if err != nil {
  60. log.Fatalf("创建 opencode 客户端失败: %v", err)
  61. }
  62. // 4.1 创建配置中心客户端
  63. var configClient *configure.Client
  64. configClient, err = configure.NewClient()
  65. if err != nil {
  66. log.Printf("警告: 创建配置中心客户端失败,登录功能将不可用: %v", err)
  67. configClient = nil
  68. } else {
  69. log.Printf("配置中心客户端创建成功,BaseURL: %s", configClient.GetConfig().BaseURL)
  70. }
  71. // 5. 得到 webservice 服务工厂
  72. webxFactory := webx.GetWebServiceFactory()
  73. // 6. 建立 httpService 服务
  74. webService, err := webxFactory.CreateService(cfg.GetServiceConfig())
  75. if err != nil {
  76. log.Fatalf("创建 HTTP 服务失败: %v", err)
  77. }
  78. // 7. 建立路由-api
  79. routerService := router.NewWebService(webService.GetRouter())
  80. // 8. 注册路由--api
  81. registerRoutes(routerService, client, configClient, webService)
  82. // 9. 注册前端静态文件服务
  83. registerStaticFiles(webService)
  84. // 10. 启动服务
  85. webService.Run()
  86. // 11. 等待关闭
  87. graceful.WaitForShutdown(cfg.GetServiceConfig().ServiceName, ctr, webService.GetServer())
  88. }
  89. // startOpenCodeService 启动 opencode 服务并返回端口
  90. func startOpenCodeService() (int, error) {
  91. port, err := getAvailablePort()
  92. if err != nil {
  93. return 0, fmt.Errorf("获取可用端口失败: %w", err)
  94. }
  95. process, err := opencode.Start(port)
  96. if err != nil {
  97. return 0, fmt.Errorf("启动 opencode 进程失败: %w", err)
  98. }
  99. opencodeMutex.Lock()
  100. opencodeProcess = process
  101. opencodeMutex.Unlock()
  102. // 等待 opencode 服务就绪
  103. if err := waitForOpenCodeReady(port); err != nil {
  104. process.Stop()
  105. return 0, fmt.Errorf("opencode 服务未就绪: %w", err)
  106. }
  107. return port, nil
  108. }
  109. // getAvailablePort 获取可用端口
  110. func getAvailablePort() (int, error) {
  111. addr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:0")
  112. if err != nil {
  113. return 0, err
  114. }
  115. l, err := net.ListenTCP("tcp", addr)
  116. if err != nil {
  117. return 0, err
  118. }
  119. defer l.Close()
  120. return l.Addr().(*net.TCPAddr).Port, nil
  121. }
  122. // waitForOpenCodeReady 等待 opencode 服务就绪
  123. func waitForOpenCodeReady(port int) error {
  124. url := fmt.Sprintf("http://127.0.0.1:%d/global/health", port)
  125. client := &http.Client{Timeout: 5 * time.Second}
  126. for i := 0; i < 30; i++ {
  127. resp, err := client.Get(url)
  128. if err == nil && resp.StatusCode == 200 {
  129. resp.Body.Close()
  130. return nil
  131. }
  132. if resp != nil {
  133. resp.Body.Close()
  134. }
  135. time.Sleep(100 * time.Millisecond)
  136. }
  137. return fmt.Errorf("opencode 服务在 3 秒内未就绪")
  138. }
  139. // registerRoutes 注册所有 API 路由
  140. func registerRoutes(ws *router.RouterService, client opencode.OpenCodeClient, configClient *configure.Client, webService *webx.WebService) {
  141. // 会话管理路由
  142. routes.RegisterSessionRoutes(ws, client)
  143. // 同步对话路由
  144. routes.RegisterPromptSyncRoutes(ws, client)
  145. // 流式对话路由 - 注册原始 HTTP 处理器
  146. webService.GetRouter().Handle("/api/prompt/stream", routes.StreamPromptHandler(client))
  147. // 日志流路由 - 注册原始 HTTP 处理器
  148. if opencodeProcess != nil {
  149. webService.GetRouter().Handle("/api/logs/stream", routes.LogStreamHandler(opencodeProcess))
  150. }
  151. // 认证路由
  152. if configClient != nil {
  153. routes.RegisterAuthRoutes(ws, configClient)
  154. }
  155. // 健康检查路由
  156. ws.GET("/api/health", func(reqCtx *ctx.RequestContext) (*response.QueryResult[map[string]interface{}], error) {
  157. result := map[string]interface{}{
  158. "status": "healthy",
  159. "opencode_port": opencodePort,
  160. "service": appName,
  161. "version": appVersion,
  162. }
  163. return &response.QueryResult[map[string]interface{}]{
  164. Success: true,
  165. Data: result,
  166. }, nil
  167. }).Register()
  168. }
  169. // registerStaticFiles 注册前端静态文件服务
  170. func registerStaticFiles(webService *webx.WebService) {
  171. // 简单的静态文件服务
  172. fs := http.FileServer(http.Dir("web"))
  173. webService.GetRouter().Handle("/", fs)
  174. webService.GetRouter().Handle("/static/", http.StripPrefix("/static/", fs))
  175. }
  176. // opencodeResource 包装 opencode 进程以实现 container.Resource 接口
  177. type opencodeResource struct {
  178. process *opencode.Process
  179. name string
  180. }
  181. func (r *opencodeResource) GetName() string {
  182. return r.name
  183. }
  184. func (r *opencodeResource) Close() {
  185. if r.process != nil {
  186. r.process.Stop()
  187. }
  188. }
  189. // 日志处理器
  190. type logHandler struct {
  191. mu sync.Mutex
  192. logs []string
  193. }
  194. func (h *logHandler) Write(p []byte) (n int, err error) {
  195. line := string(p)
  196. h.mu.Lock()
  197. h.logs = append(h.logs, line)
  198. if len(h.logs) > 1000 {
  199. h.logs = h.logs[1:]
  200. }
  201. h.mu.Unlock()
  202. logger.Debug(fmt.Sprintf("opencode: %s", line))
  203. return len(p), nil
  204. }