本文件回答:技术栈、目录结构、组件关系、配置项清单、部署形态(占位)。 依赖:overview.md
| 层 | 选型 | 说明 |
|---|---|---|
| 后端语言 | Go | 版本 ≥ 1.22 |
| 后端 HTTP 框架 | Gin | 版本 ≥ 1.10,统一使用,不混用 chi / Echo |
| 数据库 | PostgreSQL | ≥ 15,启用 pg_trgm 扩展 |
| 数据库访问 | database/sql + pgx 驱动,手写 SQL |
不引入 ORM / sqlc |
| 前端框架 | Vue 3 + TypeScript | Composition API |
| 前端构建 | Vite | 不使用 Webpack / Nuxt |
| 前端路由 | Vue Router | History 模式 |
| 前端状态 | Pinia(仅需时用) | 简单状态优先用 composable |
| 前端 UI | 待定 | 不做组件库锁定,用原生 CSS 或轻量库 |
锁定含义:opencode 生成代码时不得替换上述选型,需要新增依赖时先改本节。
框架选择理由:本项目代码由 opencode + DeepSeek 生成,Gin 的训练语料丰富、写法惯例统一,生成质量比 chi 更稳定。代价是 Gin 自带 *gin.Context,需要通过约束把它限制在 handler 层。
Haven/
├── docs/ # 唯一事实来源(文档)
├── backend/ # Go 后端
│ ├── cmd/
│ │ └── haven/
│ │ └── main.go # 入口
│ ├── internal/
│ │ ├── config/ # 配置加载
│ │ ├── db/ # 数据库连接
│ │ ├── auth/ # 鉴权中间件、会话、Token
│ │ ├── notes/ # 笔记领域
│ │ ├── tags/ # 标签领域
│ │ ├── search/ # 搜索
│ │ ├── portfolio/ # 作品集
│ │ ├── audit/ # 审计
│ │ ├── llm/ # LLM 抽象层(本期空接口)
│ │ └── httpx/ # 通用 HTTP 辅助(错误响应、中间件注册)
│ ├── migrations/ # SQL migration 文件
│ ├── go.mod
│ └── go.sum
├── frontend/ # Vue 前端
│ ├── src/
│ │ ├── api/ # API 客户端封装
│ │ ├── views/ # 页面
│ │ ├── components/ # 组件
│ │ ├── router/ # 路由
│ │ ├── stores/ # Pinia(仅需时)
│ │ └── main.ts
│ ├── index.html
│ ├── package.json
│ ├── tsconfig.json
│ └── vite.config.ts
├── deploy/ # 部署脚本(systemd unit、构建脚本),如需要
└── README.md
约定:
notes、tags、portfolio…),不按技术层分包(不建 handlers/、services/、repositories/ 这种横切目录)。internal/httpx、internal/auth、internal/config。views,公共组件放 components,API 调用统一封装在 api/ 下。浏览器 (Vue SPA)
│ HTTPS / HTTP
▼
Go 后端 (Gin)
│
├── 鉴权中间件 ──→ sessions / pending_logins / tokens / trusted_devices
│
├── 领域 handler ──→ notes / tags / search / portfolio / audit
│
└── database/sql ──→ PostgreSQL(唯一真值源)
TUI / Neovim ──→ Go 后端(Authorization: Bearer <token>)
关键点:
所有配置通过环境变量提供(自用场景,不引入配置文件)。后端启动时读取并校验,缺失必填项则拒绝启动。
| 变量名 | 必填 | 说明 | 示例 |
|---|---|---|---|
HAVEN_LISTEN_ADDR |
是 | 后端监听地址 | 127.0.0.1:8080 |
HAVEN_DB_DSN |
是 | PostgreSQL 连接串 | postgres://haven:xxx@127.0.0.1:5432/haven?sslmode=disable |
HAVEN_FRONTEND_DIR |
是 | 前端静态文件目录 | /opt/haven/frontend/dist |
HAVEN_COOKIE_SECURE |
否 | 会话 cookie 是否加 Secure 属性,默认 true;HTTP 部署时设 false |
true |
HAVEN_GOGS_BASE_URL |
是 | Gogs 站点 base URL,用于拼作品集跳转链接 | https://gogs.example.com |
HAVEN_TOTP_ISSUER |
否 | TOTP 验证器里显示的发行者名,默认 Haven |
Haven |
HAVEN_LOG_LEVEL |
否 | 日志级别,默认 info |
info / debug |
HAVEN_COOKIE_DOMAIN |
是 | 会话与授信设备 cookie 的 Domain | app.example.com |
常量(不进配置):
以下值在 auth.md 中已定,直接写在代码里,不通过环境变量暴露,避免误配。
| 常量 | 值 | 来源 |
|---|---|---|
| 会话 cookie 名 | haven_session |
auth.md 第三节 |
| 授信设备 cookie 名 | haven_trusted_device |
auth.md 第四节 |
| 会话有效期 | 7 天 | auth.md 第三节 |
| 授信设备有效期 | 30 天 | auth.md 第四节 |
pending_logins 有效期 |
5 分钟 | auth.md 3.1 |
| bcrypt cost | 12 | auth.md 8.1 |
未来可能新增(本期不实现):
LLM_PROVIDER / LLM_API_KEY / LLM_BASE_URL / LLM_MODEL — 见 llm-extension.md 4.1TOTP_SECRET_ENCRYPTION_KEY — 见 auth.md 8.2 后期升级路径采用 database/sql + pgx 驱动,手写 SQL。理由:
pgx 提供 PostgreSQL 完整支持(jsonb、uuid、timestamptz)。不引入:GORM、Ent、sqlc、sqlboiler。
*gin.Context 只允许出现在 handler 层,不得传入业务逻辑层或数据访问层。
func(c *gin.Context);context.Context 和领域参数,不接受 *gin.Context;c.Request.Context()。原因:把 Gin 限制在 HTTP 层,业务层不感知框架,将来如需换框架只改 handler。
internal/auth 下实现,返回 gin.HandlerFunc;c.Set("user", ...) 注入身份,handler 用 c.MustGet("user") 取出;internal/httpx 或 main.go。error,handler 层统一映射为 api.md 1.4 定义的错误响应;500 只返回 internal_error,细节进日志);internal/httpx,所有 handler 调用同一套。ShouldBindJSON 绑定请求体;binding:"required")做基础校验,复杂校验在业务层;422 validation_failed,fields 从绑定错误中提取。backend/migrations/;0001_init.up.sql / 0001_init.down.sql;data-model.md 第九节的依赖顺序编写;log/slog;audit_events 表,不走日志。frontend/src/api/ 下按后端领域分文件(auth.ts、notes.ts、tags.ts…),每个文件导出函数,函数内部处理:
/api);ApiError 类型,含 error / message / fields);GET /api/me 判断登录态;npm run build 输出到 frontend/dist/;HAVEN_FRONTEND_DIR 指向的位置(部署时由运维决定是否拷贝)。后端同时托管前端静态文件,不单独部署 nginx。
GET /api/* 走 API handler;/、/notes/xxx 等)一律返回 index.html,由 Vue Router 接管;/assets/*.js、*.css)由后端直接返回文件,带长缓存头(文件名含 hash)。实现方式:Gin 的 r.NoRoute(...) 兜底到 index.html,r.Static("/assets", ...) 托管静态资源。
理由:自用项目、单机部署,多一个 nginx 就多一个组件要运维。Go 的静态文件服务足够。
当前状态:待定。已知约束:
待 Gogs 部署完成后补充:
pg_dump 定时任务)。conventions.md 第四节是完整禁令清单,本文件不重复。这里只强调与架构相关的四条:
*gin.Context 不得越出 handler 层。