Bez popisu
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

log_stream_routes.go 4.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. package routes
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "net/http"
  6. "strings"
  7. "time"
  8. "git.x2erp.com/qdy/go-base/authbase"
  9. "git.x2erp.com/qdy/go-base/webx"
  10. "git.x2erp.com/qdy/go-base/webx/router"
  11. "git.x2erp.com/qdy/go-svc-code/internal/opencode"
  12. )
  13. // RegisterLogStreamRoutes 注册日志流路由
  14. func RegisterLogStreamRoutes(ws *router.RouterService, webService *webx.WebService, process *opencode.Process, opencodePort int) {
  15. // 日志流需要直接处理 HTTP 流式响应,不能使用标准的路由包装
  16. // 我们直接注册到 webService 的底层路由器
  17. webService.GetRouter().Handle("/api/logs/stream", LogStreamHandler(process, opencodePort))
  18. }
  19. // LogStreamHandler 日志流的 HTTP 处理器(已包含TokenAuth认证)
  20. func LogStreamHandler(process *opencode.Process, opencodePort int) http.HandlerFunc {
  21. // 创建内部处理器
  22. handler := func(w http.ResponseWriter, r *http.Request) {
  23. fmt.Printf("🔍 [LogStreamHandler] 新日志流连接: %s %s\n", r.Method, r.URL.String())
  24. fmt.Printf("🔍 [LogStreamHandler] 查询参数: %v\n", r.URL.Query())
  25. // 设置 SSE 头
  26. w.Header().Set("Content-Type", "text/event-stream")
  27. w.Header().Set("Cache-Control", "no-cache")
  28. w.Header().Set("Connection", "keep-alive")
  29. w.Header().Set("Access-Control-Allow-Origin", "*")
  30. flusher, ok := w.(http.Flusher)
  31. if !ok {
  32. http.Error(w, "Streaming unsupported", http.StatusInternalServerError)
  33. return
  34. }
  35. // 获取sessionId过滤参数
  36. sessionId := r.URL.Query().Get("sessionId")
  37. if sessionId != "" {
  38. // 记录日志过滤设置
  39. fmt.Fprintf(w, "data: 过滤日志,仅显示会话 %s 的日志\n\n", sessionId)
  40. flusher.Flush()
  41. }
  42. // 检查是否有可用的opencode进程
  43. if process == nil {
  44. // 外部opencode模式,提供模拟日志流
  45. fmt.Printf("🔍 [LogStreamHandler] 使用模拟日志流(外部opencode模式)\n")
  46. // 发送初始消息
  47. fmt.Fprintf(w, "data: 外部 opencode 模式,模拟日志流已连接\n\n")
  48. flusher.Flush()
  49. fmt.Fprintf(w, "data: opencode 服务运行在端口 %d\n\n", opencodePort)
  50. flusher.Flush()
  51. fmt.Fprintf(w, "data: 当前时间: %s\n\n", time.Now().Format("2006-01-02 15:04:05"))
  52. flusher.Flush()
  53. // 监听客户端断开连接
  54. for {
  55. select {
  56. case <-r.Context().Done():
  57. // 客户端断开连接
  58. return
  59. case <-time.After(30 * time.Second):
  60. // 发送JSON格式的心跳事件保持连接
  61. heartbeatEvent := map[string]interface{}{
  62. "payload": map[string]interface{}{
  63. "type": "server.heartbeat",
  64. "properties": map[string]interface{}{
  65. "timestamp": time.Now().Format(time.RFC3339),
  66. },
  67. },
  68. }
  69. jsonData, _ := json.Marshal(heartbeatEvent)
  70. fmt.Fprintf(w, "data: %s\n\n", string(jsonData))
  71. flusher.Flush()
  72. }
  73. }
  74. }
  75. // 获取日志通道
  76. logChan := process.GetLogs()
  77. if logChan == nil {
  78. http.Error(w, "日志通道不可用", http.StatusInternalServerError)
  79. return
  80. }
  81. // 发送初始消息
  82. fmt.Fprintf(w, "data: 连接到 opencode 日志流\n\n")
  83. flusher.Flush()
  84. // 监听日志通道
  85. for {
  86. select {
  87. case log, ok := <-logChan:
  88. if !ok {
  89. // 通道关闭
  90. fmt.Fprintf(w, "data: 日志流已结束\n\n")
  91. flusher.Flush()
  92. return
  93. }
  94. // 根据sessionId过滤日志
  95. if sessionId != "" && !strings.Contains(log, sessionId) {
  96. // 日志不包含sessionId,跳过
  97. if strings.Contains(log, "session") {
  98. fmt.Printf("🔍 [LogStreamHandler] 跳过不匹配的日志 (sessionId=%s): %s\n", sessionId, log)
  99. }
  100. continue
  101. }
  102. // 发送日志
  103. fmt.Printf("🔍 [LogStreamHandler] 发送日志: %s\n", log)
  104. fmt.Fprintf(w, "data: %s\n\n", log)
  105. flusher.Flush()
  106. case <-r.Context().Done():
  107. // 客户端断开连接
  108. return
  109. case <-time.After(30 * time.Second):
  110. // 发送JSON格式的心跳事件保持连接
  111. heartbeatEvent := map[string]interface{}{
  112. "payload": map[string]interface{}{
  113. "type": "server.heartbeat",
  114. "properties": map[string]interface{}{
  115. "timestamp": time.Now().Format(time.RFC3339),
  116. },
  117. },
  118. }
  119. jsonData, _ := json.Marshal(heartbeatEvent)
  120. fmt.Fprintf(w, "data: %s\n\n", string(jsonData))
  121. flusher.Flush()
  122. }
  123. }
  124. }
  125. // 包装TokenAuth中间件
  126. return authbase.TokenAuth(http.HandlerFunc(handler)).ServeHTTP
  127. }