# 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/ # 业务逻辑 (领域服务) │ ├── _service/ # 服务实现 │ ├── interface/ # 服务接口 │ └── mq_service/ # 消息队列抽象 ├── repository/ # 数据访问层 │ ├── _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 # 在仓库根目录 bash deploy/script/prepare.sh ``` **格式:** TOML,支持环境变量替换 (`${ENV_VAR}`)。 **环境变量:** 在 `deploy/.env` 中定义 (模板:`deploy/.env.example`)。 **访问方式:** 通过 `config.GlobalConfig.*` 单例。 ### 关键配置部分 ```toml [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 # 生成配置文件 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. 独立运行后端 (可选) 如果基础设施已在外部运行: ```bash 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.Login` - `AuthService.Register` - `SystemService.Health` - `SystemService.SystemInfo` 所有其他端点需要在 `Authorization: Bearer ` 头中提供有效的 JWT。 ## 后台工作器 ### 快照工作器 **用途:** 将 Yjs 文档快照持久化到数据库。 **队列:** 消费 RabbitMQ 主题中的脏文档通知。 **流程:** 1. 协作服务在编辑后将文档标记为脏 2. 快照工作器接收通知 3. 从协作服务获取当前 Yjs 状态 4. 将快照持久化到 `document_yjs_snapshots` 表 ### 通知工作器 **用途:** 处理和投递通知。 **队列:** 消费 RabbitMQ 通知主题。 ### 搜索同步工作器 **用途:** 保持 Elasticsearch 索引与数据库同步。 **队列:** 消费 RabbitMQ 中的文档/知识库变更。 **流程:** 1. 文档/知识库被创建/更新/删除 2. 工作器接收事件 3. 相应更新 Elasticsearch 索引 ## 数据库 ### 模式管理 **迁移:** 基于模型结构体定义的 GORM `AutoMigrate`。 **位置:** `cmd/migrate/migration.go` **流程:** 1. 创建 PostgreSQL `ltree` 扩展 (用于层级组织结构) 2. 自动迁移所有 16 个模型 3. 创建自定义索引 (最近访问项的唯一约束) 4. 应用手动迁移 (例如,删除已弃用的列) **模型:** - User (用户) - Document, DocumentVersion, DocumentYjsSnapshot, DocumentComment (文档相关) - KnowledgeBase (知识库) - Template (模板) - Attachment (附件) - Invitation, InvitationRecord (邀请相关) - Notification (通知) - UserGroupMeta, MembershipRelation (用户组和成员关系) - OrgNodeMeta (组织节点) - RecentDocument, RecentKnowledgeBase, RecentTemplate (最近访问) ### 种子数据 **位置:** `cmd/db_init/` **流程:** 1. 创建管理员用户 (用户名:`admin`,密码来自配置) 2. 创建默认权限 3. 创建默认用户组 4. 创建默认模板 ## 开发 ### 代码结构模式 **服务层:** ```go // 包级单例 var AuthSvc *AuthService // 在 service.Init() 中初始化 func Init(cfg *config.Config, repos *repository.Repositories) error { auth_service.AuthSvc = auth_service.NewAuthService(repos) // ... } ``` **仓储层:** ```go // 通过 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), // ... } } ``` **错误处理:** ```go // 带状态码的类型化错误 return status.GenErrWithCustomMsg( status.StatusServiceInternalError, "operation failed", ) ``` ### Protobuf 代码生成 **源文件:** `proto/yoresee_doc/v1/yoresee_doc.proto` **生成的 Go 代码:** `pkg/gen/` (gitignored) **重新生成:** ```bash # 在仓库根目录 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 或测试套件。使用以下命令验证更改: ```bash go build ./... go vet ./... ``` ## 部署 ### Docker 镜像 | Dockerfile | 用途 | |------------|------| | `Dockerfile` | 开发镜像 (含 Delve 调试器) | | `Dockerfile.prod` | 生产 API 服务器 | | `Dockerfile.snapshot.prod` | 生产快照工作器 | | `Dockerfile.notification.prod` | 生产通知工作器 | | `Dockerfile.search-sync.prod` | 生产搜索同步工作器 | ### 启动顺序 生产环境容器 CMD: ```bash ./migrate.out && ./db_init.out && ./es_init.out && ./main.out ``` 工作器是独立的容器,有自己的入口点。 ### 健康检查 Docker Compose 使用 `wget` 检查 `/health` 端点: ```yaml 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 签名密钥 ## 许可证 [在此填写您的许可证] ## 贡献 [在此填写您的贡献指南]