Files
Cyrene/backend/gateway/cmd/main.go
T
AskaEth bcf4d4e621 feat: 第五轮开发 - 14项未来路线图功能完整实现
W1-W14 全部完成:
- W1: 消息搜索 (ILIKE全文检索 + SearchModal)
- W2: 对话导出 (JSON/Markdown/TXT三格式)
- W3: 记忆时间线 DevTools 可视化
- W4: 通知推送系统 (WebSocket + Browser Notification API)
- W5: 定时提醒 (30s轮询 + 重复提醒 + WebSocket推送)
- W6: 每日简报 (08:00自动生成: 天气+新闻+提醒+AI摘要)
- W7: IoT场景自动化 (规则引擎 10s轮询 + 条件评估 + 场景执行)
- W8: 语音输入 (浏览器 Speech Recognition API)
- W9: STT服务 (voice-service + whisper.cpp)
- W10: TTS服务 (浏览器 Speech Synthesis + edge-tts三档回退)
- W11: 文件管理 (上传/下载/缩略图/纯Go bilinear缩放)
- W12: 知识库RAG (PostgreSQL tsvector + 文档分块 + 检索)
- W13: 多模态 (图片上传+分析: Vision API + 本地Go分析回退)
- W14: PWA (Service Worker + 离线页 + install prompt)

总计: 6个Go微服务 + 10+前端组件 + 10+ PostgreSQL表 + 4个后台调度器
2026-05-19 12:01:09 +08:00

173 lines
5.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/gin-gonic/gin"
"github.com/joho/godotenv"
"github.com/yourname/cyrene-ai/gateway/internal/config"
"github.com/yourname/cyrene-ai/gateway/internal/engine"
"github.com/yourname/cyrene-ai/gateway/internal/handler"
"github.com/yourname/cyrene-ai/gateway/internal/middleware"
"github.com/yourname/cyrene-ai/gateway/internal/router"
"github.com/yourname/cyrene-ai/gateway/internal/store"
"github.com/yourname/cyrene-ai/gateway/internal/ws"
)
func main() {
// 自动加载 .env 文件(来自 backend/.env
if err := godotenv.Load("../.env"); err != nil {
log.Println("ℹ 未找到 .env 文件,将使用环境变量或默认值")
}
// 加载配置
cfg := config.Load()
// 确保上传目录存在
if err := os.MkdirAll("./uploads", 0755); err != nil {
log.Printf("⚠ 创建上传目录失败: %v", err)
}
// 初始化数据库持久化存储 (降级:连接失败不崩溃)
var sessionStore *store.SessionStore
var reminderStore *store.ReminderStore
var briefingStore *store.BriefingStore
var automationStore *store.AutomationStore
var fileStore *store.FileStore
var knowledgeStore *store.KnowledgeStore
var ruleEngine *engine.RuleEngine
databaseURL := cfg.DatabaseURL()
if s, err := store.NewSessionStore(databaseURL); err != nil {
log.Printf("⚠ 会话持久化存储初始化失败 (数据库不可用): %v", err)
log.Println("⚠ Gateway 将以仅内存模式运行 — 会话数据在重启后丢失")
} else {
sessionStore = s
log.Println("✅ 会话持久化存储已启用 (PostgreSQL)")
// 初始化提醒存储(复用同一数据库连接)
if rs, err := store.NewReminderStore(s.DB()); err != nil {
log.Printf("⚠ 提醒存储初始化失败: %v", err)
} else {
reminderStore = rs
log.Println("✅ 提醒持久化存储已启用 (PostgreSQL)")
}
// 初始化简报存储(复用同一数据库连接)
if bs, err := store.NewBriefingStore(s.DB()); err != nil {
log.Printf("⚠ 简报存储初始化失败: %v", err)
} else {
briefingStore = bs
log.Println("✅ 简报持久化存储已启用 (PostgreSQL)")
}
// 初始化自动化存储(复用同一数据库连接)
if as, err := store.NewAutomationStore(s.DB()); err != nil {
log.Printf("⚠ 自动化存储初始化失败: %v", err)
} else {
automationStore = as
log.Println("✅ 自动化持久化存储已启用 (PostgreSQL)")
}
// 初始化文件存储(复用同一数据库连接)
if fs, err := store.NewFileStore(s.DB()); err != nil {
log.Printf("⚠ 文件存储初始化失败: %v", err)
} else {
fileStore = fs
log.Println("✅ 文件持久化存储已启用 (PostgreSQL)")
}
// 初始化知识库存储(复用同一数据库连接)
if ks, err := store.NewKnowledgeStore(s.DB()); err != nil {
log.Printf("⚠ 知识库存储初始化失败: %v", err)
} else {
knowledgeStore = ks
log.Println("✅ 知识库持久化存储已启用 (PostgreSQL)")
}
}
// 初始化 WebSocket Hub
hub := ws.NewHub()
hub.SetStore(sessionStore)
hub.SetIdleTimeout(cfg.SessionIdleTimeoutMin)
// 初始化规则引擎 (需要 Hub)
if automationStore != nil {
ruleEngine = engine.NewRuleEngine(automationStore, hub)
ruleEngine.Start()
log.Println("✅ 规则引擎已启动")
}
// 初始化Gin
if cfg.Env == "production" {
gin.SetMode(gin.ReleaseMode)
}
r := gin.New()
// 中间件
r.Use(middleware.CORS())
r.Use(middleware.RequestLogging())
r.Use(gin.Recovery())
// 启动 WebSocket Hub
go hub.Run()
// 启动闲置会话清理 (标记超时会话为 idle,不删除)
hub.StartIdleCleanup()
// 启动 IoT 设备状态广播(每10秒向所有WebSocket客户端推送设备状态)
hub.StartIoTBroadcast(cfg.IoTDebugServiceURL)
// 注册路由
router.Setup(r, hub, cfg, sessionStore, reminderStore, briefingStore, automationStore, fileStore, ruleEngine, knowledgeStore, nil)
// 启动提醒调度器
if reminderStore != nil {
handler.StartReminderScheduler(reminderStore, hub)
}
// 启动简报调度器
if briefingStore != nil && reminderStore != nil {
briefingHandler := handler.NewBriefingHandler(cfg, hub, briefingStore, reminderStore)
handler.StartBriefingScheduler(briefingHandler, briefingStore, cfg.BriefingTime)
}
// 启动服务
srv := &http.Server{
Addr: ":" + cfg.Port,
Handler: r,
}
go func() {
log.Printf("🚀 Gateway 启动在端口 %s", cfg.Port)
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("服务启动失败: %v", err)
}
}()
// 优雅关闭
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("正在关闭服务...")
hub.StopIoTBroadcast()
// 关闭数据库连接
if sessionStore != nil {
if err := sessionStore.Close(); err != nil {
log.Printf("⚠ 关闭数据库连接失败: %v", err)
}
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
srv.Shutdown(ctx)
log.Println("服务已关闭")
}