5d0bb96abe
**DevTools 新增功能 (Tasks 13-14):** - 首页仪表盘添加数据库实时监看卡片 (5端口状态 + 记忆数) - 侧边栏新增数据库面板,支持自动 5 秒刷新 - 数据库面板显示 PostgreSQL/Redis/Qdrant/MinIO/NATS 端口状态 - 隧道控制按钮 (启动/停止/重启/查看状态) - 新增 API 端点: GET /api/database/status, POST /api/tunnel/:action - 更新 docs/api-reference/ API 文档 **Bug 修复 (Task 15):** - 修复 pgrep -f 自匹配导致隧道状态误判 (添加 ^ssh 锚点) - devtools/src/index.js (dashboard + database/status) - scripts/tunnel.sh (is_tunnel_running + show_status) - 修复数据库面板缺少自动刷新定时器 - 修复侧边栏数据库徽章永远 display:none - 修复僵尸进程场景下按钮死锁问题 **其他改进:** - .gitignore 添加 backend/cmd, backend/iot-debug-service/main - 前端多项改进 (登录/注册/会话/流式动画等)
194 lines
4.5 KiB
Go
194 lines
4.5 KiB
Go
package handler
|
|
|
|
import (
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"github.com/yourname/cyrene-ai/gateway/internal/middleware"
|
|
"github.com/yourname/cyrene-ai/gateway/internal/ws"
|
|
)
|
|
|
|
// SessionHandler 会话管理处理器
|
|
type SessionHandler struct {
|
|
// MVP阶段使用内存存储,后续迁移到PostgreSQL
|
|
sessions map[string][]SessionInfo // userID -> sessions
|
|
hub *ws.Hub
|
|
}
|
|
|
|
// SessionInfo 会话信息
|
|
type SessionInfo struct {
|
|
ID string `json:"id"`
|
|
UserID string `json:"user_id"`
|
|
Title string `json:"title"`
|
|
CreatedAt int64 `json:"created_at"`
|
|
UpdatedAt int64 `json:"updated_at"`
|
|
MessageCount int `json:"message_count"`
|
|
IsActive bool `json:"is_active"`
|
|
}
|
|
|
|
// NewSessionHandler 创建会话处理器
|
|
func NewSessionHandler(hub *ws.Hub) *SessionHandler {
|
|
return &SessionHandler{
|
|
sessions: make(map[string][]SessionInfo),
|
|
hub: hub,
|
|
}
|
|
}
|
|
|
|
// Create 创建新会话
|
|
func (h *SessionHandler) Create(c *gin.Context) {
|
|
userID := middleware.GetUserID(c)
|
|
|
|
var req struct {
|
|
Title string `json:"title"`
|
|
}
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
// 允许空body
|
|
req.Title = "新的对话"
|
|
}
|
|
if req.Title == "" {
|
|
req.Title = "新的对话"
|
|
}
|
|
|
|
session := SessionInfo{
|
|
ID: "session_" + randomID(12),
|
|
UserID: userID,
|
|
Title: req.Title,
|
|
CreatedAt: time.Now().UnixMilli(),
|
|
UpdatedAt: time.Now().UnixMilli(),
|
|
}
|
|
|
|
h.sessions[userID] = append([]SessionInfo{session}, h.sessions[userID]...)
|
|
|
|
c.JSON(http.StatusCreated, session)
|
|
}
|
|
|
|
// List 获取会话列表
|
|
func (h *SessionHandler) List(c *gin.Context) {
|
|
userID := middleware.GetUserID(c)
|
|
|
|
sessions, ok := h.sessions[userID]
|
|
if !ok {
|
|
sessions = []SessionInfo{}
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"sessions": sessions,
|
|
})
|
|
}
|
|
|
|
// Delete 删除会话
|
|
func (h *SessionHandler) Delete(c *gin.Context) {
|
|
userID := middleware.GetUserID(c)
|
|
sessionID := c.Param("id")
|
|
|
|
sessions := h.sessions[userID]
|
|
for i, s := range sessions {
|
|
if s.ID == sessionID {
|
|
h.sessions[userID] = append(sessions[:i], sessions[i+1:]...)
|
|
c.JSON(http.StatusOK, gin.H{"status": "deleted"})
|
|
return
|
|
}
|
|
}
|
|
|
|
c.JSON(http.StatusNotFound, gin.H{
|
|
"error": "会话不存在",
|
|
"errorType": "session_not_found",
|
|
"hint": "会话可能已被删除,或 Gateway 重启后内存数据已清空",
|
|
})
|
|
}
|
|
|
|
// Get 获取单个会话信息
|
|
func (h *SessionHandler) Get(c *gin.Context) {
|
|
userID := middleware.GetUserID(c)
|
|
sessionID := c.Param("id")
|
|
|
|
for _, s := range h.sessions[userID] {
|
|
if s.ID == sessionID {
|
|
c.JSON(http.StatusOK, s)
|
|
return
|
|
}
|
|
}
|
|
|
|
c.JSON(http.StatusNotFound, gin.H{
|
|
"error": "会话不存在",
|
|
"errorType": "session_not_found",
|
|
"hint": "会话可能已被删除,或 Gateway 重启后内存数据已清空",
|
|
})
|
|
}
|
|
|
|
// GetMessages 获取会话的完整消息列表
|
|
func (h *SessionHandler) GetMessages(c *gin.Context) {
|
|
userID := middleware.GetUserID(c)
|
|
sessionID := c.Param("id")
|
|
|
|
messages := h.hub.GetConversation(userID, sessionID)
|
|
if messages == nil {
|
|
messages = []ws.Message{}
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"messages": messages,
|
|
})
|
|
}
|
|
|
|
// ========== Admin 端点 ==========
|
|
|
|
// ListActiveSessions 获取当前所有活跃 WebSocket 会话列表 (管理员)
|
|
func (h *SessionHandler) ListActiveSessions(c *gin.Context) {
|
|
sessions := h.hub.GetActiveSessions()
|
|
if sessions == nil {
|
|
sessions = []*ws.SessionState{}
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"sessions": sessions,
|
|
"total": len(sessions),
|
|
})
|
|
}
|
|
|
|
// GetActiveSessions 返回按用户分组的活跃会话列表
|
|
func (h *SessionHandler) GetActiveSessions(c *gin.Context) {
|
|
sessionsByUser := h.hub.GetActiveSessionsByUser()
|
|
if sessionsByUser == nil {
|
|
sessionsByUser = make(map[string][]*ws.SessionState)
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"users": sessionsByUser,
|
|
})
|
|
}
|
|
|
|
// GetSession 获取指定会话的详细信息 (管理员)
|
|
func (h *SessionHandler) GetSession(c *gin.Context) {
|
|
sessionID := c.Param("id")
|
|
if sessionID == "" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "缺少会话ID"})
|
|
return
|
|
}
|
|
|
|
session := h.hub.GetSession(sessionID)
|
|
if session == nil {
|
|
c.JSON(http.StatusNotFound, gin.H{
|
|
"error": "会话不存在",
|
|
"errorType": "session_not_found",
|
|
"hint": "该会话可能已断开,或 Gateway 重启后内存数据已清空",
|
|
})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, session)
|
|
}
|
|
|
|
// 简单的工具函数
|
|
func randomID(n int) string {
|
|
const letters = "abcdefghijklmnopqrstuvwxyz0123456789"
|
|
b := make([]byte, n)
|
|
for i := range b {
|
|
b[i] = letters[i%len(letters)]
|
|
}
|
|
// 使用纳秒时间戳增加唯一性
|
|
return string(b)
|
|
}
|