14 KiB
Yoresee Doc 后端
Yoresee Doc 协作文档管理平台的后端服务,基于 Go 和 Connect RPC 构建。
概述
Yoresee Doc 后端提供:
- 文档管理:创建、编辑、版本控制、组织文档,支持附件
- 知识库:层级化知识库组织
- 用户与权限控制:用户组、组织结构、成员管理、邀请
- 实时协作:Yjs 快照持久化和冲突解决
- 全文搜索:Elasticsearch 集成实现文档搜索
- 通知系统:基于 RabbitMQ 的事件驱动通知
架构
高层结构
┌─────────────────────────────────────────────────────────┐
│ API 层 │
│ Connect RPC 处理器 (HTTP/2 + gRPC-web) │
│ - 认证拦截器 (JWT) │
│ - CORS 支持 │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ 服务层 │
│ 业务逻辑,使用包级单例 │
│ - auth_service, document_service 等 │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ 仓储层 │
│ 使用 GORM (PostgreSQL) 的数据访问 │
│ - 通过 repository.NewRepositories() 构造 │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ 基础设施层 │
│ - PostgreSQL (主数据存储) │
│ - Redis (缓存、会话、锁) │
│ - MinIO (文件存储) │
│ - Elasticsearch (搜索索引) │
│ - RabbitMQ (消息队列) │
│ - Consul (服务发现/配置) │
└─────────────────────────────────────────────────────────┘
项目结构
cmd/
├── main.go # API 服务器入口
├── migrate/ # 数据库迁移 (GORM AutoMigrate)
├── db_init/ # 种子数据 (管理员用户、默认分组、模板)
├── es_init/ # Elasticsearch 索引初始化
├── notification-worker/ # 通知消费者
├── search-sync-worker/ # 搜索索引同步消费者
└── snapshot-worker/ # Yjs 快照持久化消费者
internal/
├── bootstrap/ # 链式初始化器 (配置 → 基础设施 → 服务)
├── config/ # Viper TOML 配置加载器
├── transport/
│ ├── connectserver/ # HTTP 多路复用、RPC 处理器、认证拦截器
│ └── grpcserver/ # 各服务的 RPC 实现
├── service/ # 业务逻辑 (领域服务)
│ ├── <domain>_service/ # 服务实现
│ ├── interface/ # 服务接口
│ └── mq_service/ # 消息队列抽象
├── repository/ # 数据访问层
│ ├── <domain>_repo/ # 仓储实现
│ └── init.go # 仓储构造
├── model/ # GORM 模型定义 (16 个模型)
├── dto/ # 数据传输对象
├── mapper/ # 模型 ↔ DTO 映射器
├── types/ # 领域类型
└── status/ # 类型化错误码
pkg/
├── storage/ # 基础设施客户端 (DB、Redis、MinIO、ES、Consul)
├── mq/ # 消息队列接口 + RabbitMQ 实现
├── cache/ # 缓存工具
├── lock/ # 分布式锁
├── key/ # 共享缓存键
├── errs/ # 共享错误
├── constant/ # 共享常量
└── gen/ # 生成的 protobuf 代码 (gitignored)
前置要求
- Go 1.24+
- PostgreSQL 15+ 支持
ltree扩展 - Redis 7+
- MinIO (或 S3 兼容对象存储)
- RabbitMQ 3+
- Consul 1.16+
- Elasticsearch 8+ (可选,用于搜索功能)
所有基础设施服务可通过项目的 Docker Compose 配置启动。
配置
配置文件
配置从 config.toml (gitignored) 加载,由 deploy/script/prepare.sh 从 config.toml.tmpl 生成。
生成:
# 在仓库根目录
bash deploy/script/prepare.sh
格式: TOML,支持环境变量替换 (${ENV_VAR})。
环境变量: 在 deploy/.env 中定义 (模板:deploy/.env.example)。
访问方式: 通过 config.GlobalConfig.* 单例。
关键配置部分
[server]
grpc_port = 9090 # 原生 gRPC 端口
grpc_web_port = 8080 # gRPC-web 端口 (浏览器客户端)
[database]
host = "localhost"
port = 5432
user = "root"
password = "your_password"
name = "yoresee_doc_db"
[redis]
host = "localhost"
port = 6379
password = "your_redis_password"
db = 0
[consul]
enabled = true
address = "localhost:8500"
scheme = "http"
token = "yoresee_doc_root_token"
prefix = "yoresee_doc"
[minio]
endpoint = "localhost:9000"
access_key = "minioadmin"
secret_key = "minioadmin"
bucket = "yoresee_doc"
use_ssl = false
[elasticsearch]
enabled = true
addresses = ["http://localhost:9200"]
username = "elastic"
password = "your_password"
index_prefix = "yoresee_doc"
[mq_config.rabbitmq]
url = "amqp://guest:guest@localhost:5672/"
[backend]
system_name = "yoresee_doc"
internal_rpc_key = "yoresee_doc_internal_key"
[backend.jwt]
secret = "yoresee_doc_jwt_secret_key"
expire = 86400 # 24 小时
refresh_expire = 604800 # 7 天
[backend.security]
password_hash_cost = 12
max_login_attempts = 5
login_lock_duration = 30 # 分钟
快速开始
1. 启动基础设施
在仓库根目录:
# 生成配置文件
bash deploy/script/configure.sh # 交互式设置
# 或
cp deploy/.env.example deploy/.env
bash deploy/script/prepare.sh # 非交互式
# 通过 Docker Compose 启动所有服务
bash deploy/script/start.sh dev up
这将启动:
- PostgreSQL、Redis、Consul、MinIO、RabbitMQ、Elasticsearch
- 后端 API 服务器 (自动迁移 + 种子数据)
- 后台工作器 (快照、通知、搜索同步)
- 前端、协作服务、nginx
2. 独立运行后端 (可选)
如果基础设施已在外部运行:
cd backend
# 运行迁移 + 种子数据
go run ./cmd/migrate
go run ./cmd/db_init
go run ./cmd/es_init
# 启动 API 服务器
go run ./cmd/main.go
3. 验证
健康检查端点:
GET /health— 存活检查GET /readyz— 就绪检查 (检查 DB + Redis)GET /livez— 存活检查别名
API 结构
Connect RPC
后端使用 Connect RPC (非原生 gRPC) 暴露两个 HTTP 监听器:
| 端口 | 用途 | 协议 |
|---|---|---|
| 9090 | 原生 gRPC | HTTP/2 |
| 8080 | gRPC-web | HTTP/1.1+ (浏览器客户端) |
两个监听器都使用 h2c (HTTP/2 明文)。
服务
API 组织为以下服务:
- AuthService:登录、注册、个人资料管理
- DocumentService:文档 CRUD、版本控制、附件、模板
- KnowledgeBaseService:知识库管理
- MembershipService:用户组、组织结构、成员关系
- InvitationService:邀请创建和管理
- NotificationService:通知 CRUD、已读状态
- CommentService:文档评论
- SettingService:系统设置
- SystemService:健康检查、系统信息
认证
基于 JWT 的认证,以下端点无需认证:
AuthService.LoginAuthService.RegisterSystemService.HealthSystemService.SystemInfo
所有其他端点需要在 Authorization: Bearer <token> 头中提供有效的 JWT。
后台工作器
快照工作器
用途: 将 Yjs 文档快照持久化到数据库。
队列: 消费 RabbitMQ 主题中的脏文档通知。
流程:
- 协作服务在编辑后将文档标记为脏
- 快照工作器接收通知
- 从协作服务获取当前 Yjs 状态
- 将快照持久化到
document_yjs_snapshots表
通知工作器
用途: 处理和投递通知。
队列: 消费 RabbitMQ 通知主题。
搜索同步工作器
用途: 保持 Elasticsearch 索引与数据库同步。
队列: 消费 RabbitMQ 中的文档/知识库变更。
流程:
- 文档/知识库被创建/更新/删除
- 工作器接收事件
- 相应更新 Elasticsearch 索引
数据库
模式管理
迁移: 基于模型结构体定义的 GORM AutoMigrate。
位置: cmd/migrate/migration.go
流程:
- 创建 PostgreSQL
ltree扩展 (用于层级组织结构) - 自动迁移所有 16 个模型
- 创建自定义索引 (最近访问项的唯一约束)
- 应用手动迁移 (例如,删除已弃用的列)
模型:
- User (用户)
- Document, DocumentVersion, DocumentYjsSnapshot, DocumentComment (文档相关)
- KnowledgeBase (知识库)
- Template (模板)
- Attachment (附件)
- Invitation, InvitationRecord (邀请相关)
- Notification (通知)
- UserGroupMeta, MembershipRelation (用户组和成员关系)
- OrgNodeMeta (组织节点)
- RecentDocument, RecentKnowledgeBase, RecentTemplate (最近访问)
种子数据
位置: cmd/db_init/
流程:
- 创建管理员用户 (用户名:
admin,密码来自配置) - 创建默认权限
- 创建默认用户组
- 创建默认模板
开发
代码结构模式
服务层:
// 包级单例
var AuthSvc *AuthService
// 在 service.Init() 中初始化
func Init(cfg *config.Config, repos *repository.Repositories) error {
auth_service.AuthSvc = auth_service.NewAuthService(repos)
// ...
}
仓储层:
// 通过 Repositories 结构体构造
type Repositories struct {
DB *gorm.DB
Redis *redis.Client
Document *document_repo.DocumentRepository
// ...
}
func NewRepositories(db *gorm.DB, redis *redis.Client) *Repositories {
return &Repositories{
Document: document_repo.NewDocumentRepository(db, redis),
// ...
}
}
错误处理:
// 带状态码的类型化错误
return status.GenErrWithCustomMsg(
status.StatusServiceInternalError,
"operation failed",
)
Protobuf 代码生成
源文件: proto/yoresee_doc/v1/yoresee_doc.proto
生成的 Go 代码: pkg/gen/ (gitignored)
重新生成:
# 在仓库根目录
bash deploy/script/gen_proto.sh
目标:
backend/pkg/gen— Go 代码collab-go/pkg/gen— Go 代码frontend/src/gen— TypeScript ES 模块collab/src/gen— Node gRPC 代码
代码验证
未配置 linter 或测试套件。使用以下命令验证更改:
go build ./...
go vet ./...
部署
Docker 镜像
| Dockerfile | 用途 |
|---|---|
Dockerfile |
开发镜像 (含 Delve 调试器) |
Dockerfile.prod |
生产 API 服务器 |
Dockerfile.snapshot.prod |
生产快照工作器 |
Dockerfile.notification.prod |
生产通知工作器 |
Dockerfile.search-sync.prod |
生产搜索同步工作器 |
启动顺序
生产环境容器 CMD:
./migrate.out && ./db_init.out && ./es_init.out && ./main.out
工作器是独立的容器,有自己的入口点。
健康检查
Docker Compose 使用 wget 检查 /health 端点:
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://backend:9090/health"]
interval: 5s
timeout: 3s
retries: 20
start_period: 30s
基础设施端口
| 服务 | 默认端口 |
|---|---|
| 后端 gRPC | 9090 |
| 后端 gRPC-web | 8080 |
| 后端调试 (Delve) | 2345 |
| PostgreSQL | 5432 |
| Redis | 6379 |
| Consul | 8500 |
| MinIO API | 9000 |
| MinIO 控制台 | 9001 |
| RabbitMQ AMQP | 5672 |
| RabbitMQ 管理界面 | 15672 |
| Elasticsearch | 9200 |
关键依赖
- Connect RPC (
connectrpc.com/connect) — 现代 RPC 框架 - GORM (
gorm.io/gorm) — PostgreSQL ORM - go-redis (
github.com/redis/go-redis/v9) — Redis 客户端 - MinIO Go (
github.com/minio/minio-go/v7) — S3 兼容存储 - Viper (
github.com/spf13/viper) — 配置管理 - Logrus (
github.com/sirupsen/logrus) — 结构化日志 - JWT (
github.com/golang-jwt/jwt/v5) — 认证 - amqp091-go (
github.com/rabbitmq/amqp091-go) — RabbitMQ 客户端 - improbable-eng/grpc-web — 浏览器客户端的 gRPC-web 代理
- Snowflake (
github.com/bwmarrin/snowflake) — 分布式 ID 生成
环境变量
所有环境变量在 deploy/.env 中定义 (参见 deploy/.env.example 获取模板)。
关键变量:
POSTGRES_*— 数据库连接REDIS_*— Redis 连接CONSUL_*— Consul 配置MINIO_*— MinIO/S3 配置ELASTICSEARCH_*— Elasticsearch 配置RABBITMQ_*— RabbitMQ 配置BACKEND_*— 后端服务配置JWT_SECRET— JWT 签名密钥
许可证
[在此填写您的许可证]
贡献
[在此填写您的贡献指南]