fix: 第二轮修复 — 数据库启动检查、会话持久化、URL路由、设备排序等
1. DevTools 启动前检查数据库状态,失败时自动尝试启动 2. ai-core 添加数据库断线重连机制 (30秒间隔) 3. Dashboard 添加数据库状态卡片 (启动/停止/重启) 4. Gateway 会话空闲超时管理 (30分钟标记空闲) 5. 会话/消息 PostgreSQL 持久化 (SessionStore + REST API) 6. 前端服务端会话持久化 + URL hash 路由 + 侧边栏管理 7. 管理员回到主对话按钮 8. IoT 设备卡片固定排序 9. 更新相关文档
This commit is contained in:
@@ -8,6 +8,8 @@ import (
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/yourname/cyrene-ai/gateway/internal/store"
|
||||
)
|
||||
|
||||
// SessionState 会话状态
|
||||
@@ -60,6 +62,22 @@ type Hub struct {
|
||||
iotServiceURL string
|
||||
iotStopCh chan struct{}
|
||||
iotPollRunning bool
|
||||
|
||||
// 持久化存储 (可选,数据库连接失败时为 nil)
|
||||
store *store.SessionStore
|
||||
|
||||
// 闲置超时时间
|
||||
idleTimeout time.Duration
|
||||
}
|
||||
|
||||
// SetStore 设置持久化存储 (可选)
|
||||
func (h *Hub) SetStore(s *store.SessionStore) {
|
||||
h.store = s
|
||||
}
|
||||
|
||||
// SetIdleTimeout 设置闲置超时时间
|
||||
func (h *Hub) SetIdleTimeout(minutes int) {
|
||||
h.idleTimeout = time.Duration(minutes) * time.Minute
|
||||
}
|
||||
|
||||
// NewHub 创建WebSocket Hub
|
||||
@@ -72,9 +90,79 @@ func NewHub() *Hub {
|
||||
userClients: make(map[string]map[*Client]bool),
|
||||
sessions: make(map[string]*SessionState),
|
||||
iotStopCh: make(chan struct{}),
|
||||
idleTimeout: 30 * time.Minute, // 默认30分钟
|
||||
}
|
||||
}
|
||||
|
||||
// StartIdleCleanup 启动闲置会话清理 goroutine
|
||||
// 每5分钟检查一次,将超过 idleTimeout 无活动的会话标记为 idle
|
||||
func (h *Hub) StartIdleCleanup() {
|
||||
go func() {
|
||||
ticker := time.NewTicker(5 * time.Minute)
|
||||
defer ticker.Stop()
|
||||
|
||||
for range ticker.C {
|
||||
h.cleanupIdleSessions()
|
||||
}
|
||||
}()
|
||||
log.Printf("[WS] 闲置会话清理已启动 (超时: %v)", h.idleTimeout)
|
||||
}
|
||||
|
||||
// cleanupIdleSessions 标记超时会话为 idle(不删除状态)
|
||||
func (h *Hub) cleanupIdleSessions() {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
|
||||
now := time.Now()
|
||||
idleCount := 0
|
||||
|
||||
for sessionID, s := range h.sessions {
|
||||
// 检查该 session 是否还有活跃连接
|
||||
hasActiveConn := false
|
||||
for _, clients := range h.userClients {
|
||||
for c := range clients {
|
||||
if c.SessionID == sessionID {
|
||||
hasActiveConn = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if hasActiveConn {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有活跃连接且超过闲置超时,标记为 idle
|
||||
if !hasActiveConn && now.Sub(s.LastActivity) > h.idleTimeout {
|
||||
if s.State != "idle" {
|
||||
s.State = "idle"
|
||||
idleCount++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if idleCount > 0 {
|
||||
log.Printf("[WS] 闲置清理: %d 个会话标记为 idle", idleCount)
|
||||
}
|
||||
}
|
||||
|
||||
// GetAllActiveSessions 返回所有会话状态(包括 idle),供 DevTools 监看使用
|
||||
func (h *Hub) GetAllActiveSessions() []*SessionState {
|
||||
h.mu.RLock()
|
||||
defer h.mu.RUnlock()
|
||||
|
||||
if h.sessions == nil || len(h.sessions) == 0 {
|
||||
return []*SessionState{}
|
||||
}
|
||||
|
||||
result := make([]*SessionState, 0, len(h.sessions))
|
||||
for _, s := range h.sessions {
|
||||
cp := *s
|
||||
cp.RecentMessages = nil
|
||||
result = append(result, &cp)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Run 启动Hub主循环
|
||||
func (h *Hub) Run() {
|
||||
for {
|
||||
@@ -119,7 +207,7 @@ func (h *Hub) Run() {
|
||||
}
|
||||
}
|
||||
|
||||
// 检查该session是否还有其他连接,没有则移除会话状态
|
||||
// 检查该session是否还有其他连接,没有则标记为 idle 而非删除
|
||||
hasOtherConn := false
|
||||
if clients, ok := h.userClients[client.UserID]; ok {
|
||||
for c := range clients {
|
||||
@@ -130,7 +218,10 @@ func (h *Hub) Run() {
|
||||
}
|
||||
}
|
||||
if !hasOtherConn {
|
||||
delete(h.sessions, client.SessionID)
|
||||
// 不再删除 session 状态,而是标记为 idle 保留在内存中
|
||||
if s, ok := h.sessions[client.SessionID]; ok {
|
||||
s.State = "idle"
|
||||
}
|
||||
}
|
||||
}
|
||||
h.mu.Unlock()
|
||||
|
||||
Reference in New Issue
Block a user