package store import ( "strings" "time" "github.com/glebarez/sqlite" "gorm.io/driver/mysql" "gorm.io/gorm" "gorm.io/gorm/logger" "gorm.io/gorm/schema" "git.sundynix.cn/Blizzard/sundynix-site/server/internal/model" ) // Open 打开数据库并完成迁移与种子数据。 // DSN 含 "@tcp(" 走 MySQL,否则按 SQLite 文件路径处理(本地开发兜底)。 // 命名规范:表前缀 sundynix_、单数表名、snake_case 列名。 func Open(dsn string) (*gorm.DB, error) { var dialector gorm.Dialector if strings.Contains(dsn, "@tcp(") { dialector = mysql.Open(dsn) } else { dialector = sqlite.Open(dsn) } db, err := gorm.Open(dialector, &gorm.Config{ Logger: logger.Default.LogMode(logger.Warn), TranslateError: true, // 唯一索引冲突 → gorm.ErrDuplicatedKey NamingStrategy: schema.NamingStrategy{ TablePrefix: "sundynix_", SingularTable: true, }, }) if err != nil { return nil, err } if err := db.AutoMigrate(&model.Post{}); err != nil { return nil, err } if err := seed(db); err != nil { return nil, err } return db, nil } // seed 空库时写入示例文章,方便前端联调。 func seed(db *gorm.DB) error { var count int64 if err := db.Model(&model.Post{}).Count(&count).Error; err != nil { return err } if count > 0 { return nil } at := func(s string) *time.Time { t, _ := time.Parse("2006-01-02", s) return &t } posts := []model.Post{ { Slug: "v0-1-2-release", Title: "sundynix agentix v0.1.2 发布:应用内更新与团队视图", Category: "RELEASE", Summary: "v0.1.2 带来应用内更新横幅、卡通办公室团队视图,以及发布流水线自动出包。", PublishedAt: at("2026-07-02"), Content: `## 亮点 - **应用内更新**:新版本发布后,工作台顶部出现更新横幅,一键升级。 - **团队视图**:多智能体协作时,AI 角色会在卡通办公室里走动干活。 - **发布流水线**:GitHub Actions 自动产出 macOS universal .app 与 Windows .exe。 ## 升级方式 桌面版直接点更新横幅;自托管用户: ` + "```bash\ndocker compose pull && docker compose up -d\n```" + ` 完整变更见 Releases 页面。`, }, { Slug: "hybrid-retrieval", Title: "三路混合检索是怎么工作的:vector + fulltext + graph", Category: "ENGINEERING", Summary: "向量召回语义、全文召回关键词、图谱召回关系,RRF 把三路结果融成一路。", PublishedAt: at("2026-06-18"), Content: `## 为什么一路不够 单靠向量检索,专有名词和精确匹配经常翻车;单靠全文检索,又抓不到语义近邻。 我们的做法是三路并发: | 通路 | 引擎 | 擅长 | |------|------|------| | 向量 | Milvus | 语义相似 | | 全文 | Bleve | 关键词精确匹配 | | 图谱 | Neo4j | 实体关系跳跃 | 三路结果用 **RRF(Reciprocal Rank Fusion)** 融合,再过一遍 rerank。 ## 调试 检索控制台会展示每一路的召回与得分,坏 case 一眼定位。`, }, { Slug: "why-event-driven", Title: "为什么我们选择事件驱动:NATS 零拷贝骨干网设计记", Category: "ARCHITECTURE", Summary: "Agent 的一切都是流:token、执行轨迹、工具调用。事件总线是最自然的骨架。", PublishedAt: at("2026-05-30"), Content: `## 流式优先 LLM 的输出天生是 token 流,Agent 的执行天生是事件序列。与其在 HTTP 请求-响应模型上硬凑,不如让整个系统跑在消息总线上。 ## 主题设计 ` + "```\nsundynix.tasks.* # 任务派发\nsundynix.streams. # token 流\nsundynix.tools.go.* # Go 工具调用\nsundynix.tools.py.* # Python 工具调用\n```" + ` 网关订阅流主题直接转 SSE/WS,中间零拷贝。 ## 演进 单体先行(Monolith First):现在所有模块跑在一个进程里,但彼此只通过总线说话——拆微服务时只需要把订阅者搬走(Morph B)。`, }, } return db.Create(&posts).Error }