feat: 官网 + 管理端 + Gin/GORM 后端首个完整版本
- web/: 产品官网(Hero 事件流终端、功能矩阵、架构、快速开始、下载、博客 + Markdown 详情页),青瓷绿双主题 - admin/: 内容管理(JWT 登录、文章分页/搜索/CRUD、草稿与发布),embed 挂 /admin - server/: Gin + GORM,MySQL(DSN 走 .env,SQLite 兜底);规范落地:sundynix_ 表前缀、字符串雪花主键、snake_case 列名、统一响应信封、公共分页参数 - 双 SPA embed 单二进制部署,make build 一键出包 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
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.<id> # token 流\nsundynix.tools.go.* # Go 工具调用\nsundynix.tools.py.* # Python 工具调用\n```" + `
|
||||
|
||||
网关订阅流主题直接转 SSE/WS,中间零拷贝。
|
||||
|
||||
## 演进
|
||||
|
||||
单体先行(Monolith First):现在所有模块跑在一个进程里,但彼此只通过总线说话——拆微服务时只需要把订阅者搬走(Morph B)。`,
|
||||
},
|
||||
}
|
||||
return db.Create(&posts).Error
|
||||
}
|
||||
Reference in New Issue
Block a user