本文定义 GCFeed 的目录职责、代码风格、接口设计、数据模型和测试约定。新增功能时优先遵循本文,再查看对应模块文档。
| 区域 | 技术 |
|---|---|
| API | Go、Gin、GORM |
| 数据库 | MySQL |
| 缓存 | Redis |
| 消息队列 | RabbitMQ |
| 鉴权 | JWT |
| Web | React、Vite |
| 规格驱动 | OpenSpec |
后端位于 apps/api,采用四层结构:
apps/api/
cmd/feed/main.go
cmd/worker/main.go
configs/
internal/
domain/{module}/
application/{module}/
infra/
infra/persistence/{module}/
interfaces/http/{module}/
interfaces/http/router/router.go
test/
| 层 | 职责 | 依赖方向 |
|---|---|---|
| Domain | 实体、领域错误、业务不变量、仓储接口 | 只依赖标准库 |
| Application | 用例编排、分页游标、幂等、跨实体流程 | 依赖 Domain 接口 |
| Infrastructure | GORM、Redis、RabbitMQ、JWT、配置 | 实现 Domain/Application 所需接口 |
| Interfaces | HTTP Handler、DTO、路由、中间件 | 调用 Application Service |
模块接入顺序:
- 定义 Domain 实体、错误和仓储接口。
- 编写 Application Service。
- 实现 GORM 模型和 Repository。
- 编写 HTTP DTO 和 Handler。
- 在
router.Register装配Repository -> Service -> Handler -> Route。 - 补充 API 流程测试和模块文档。
apps/api/internal/domain/{module}/entity.go
apps/api/internal/domain/{module}/errors.go
apps/api/internal/domain/{module}/repository.go
apps/api/internal/application/{module}/service.go
apps/api/internal/infra/persistence/{module}/model.go
apps/api/internal/infra/persistence/{module}/gorm.go
apps/api/internal/interfaces/http/{module}/dto.go
apps/api/internal/interfaces/http/{module}/handler.go
apps/api/test/{module}_api_test.go
docs/modules/{module}.md
当前模块体量继续增长时,按职责拆出 cursor.go、worker.go、event.go、errors.go 等文件。
包名使用层级前缀 + 模块名:
package domainvideo
package applicationvideo
package infravideo
package interfaceshttpvideo导入时使用同名别名:
import (
applicationvideo "GCFeed/internal/application/video"
domainvideo "GCFeed/internal/domain/video"
)常用类型命名:
| 类型 | 命名 |
|---|---|
| 应用服务 | Service,构造函数 New |
| HTTP 入口 | Handler,构造函数 New |
| 仓储接口 | Repository |
| 仓储实现 | Repository,通过包名区分 |
| GORM 模型 | {Entity}Model |
| 响应转换 | {xxx}ResponseFromDomain、{xxx}ResponseFromResult |
| 参数解析 | parse{Field}、parsePositiveInt64、parseLimit |
| 错误写入 | write{Module}Error |
常量位置:
| 常量类型 | 位置 |
|---|---|
| 领域枚举、最大长度 | domain/{module}/entity.go |
| 应用默认值 | application/{module}/service.go |
| HTTP query 默认值 | interfaces/http/{module}/handler.go |
Domain 层负责业务不变量和领域表达。
放在 Domain 的内容:
- 实体结构体。
- 状态常量和业务限制。
- 领域错误。
- 领域构造函数,例如
NewPublished、NewComment、NewFollow。 - 数据恢复函数,例如
RestoreVideo、RestoreUserWithStats。 - 实体方法,例如
Authenticate、UpdateProfile、DeleteBy、Active。 - 仓储接口。
领域构造函数负责清理字符串输入、校验 ID、校验必填字段、校验长度限制、设置默认状态和业务时间。读取路径使用 Restore* 保留数据库状态并做展示字段清洗。
Application 层负责用例编排。
放在 Application 的内容:
Service。- 用例入参结构,例如
FeedRequest。 - 用例返回结构,例如
LoginResult、CreateResult、CommentListResult。 - 跨实体流程,例如发布视频时处理幂等键。
- 游标解析和编码。
- 默认分页大小和最大分页裁剪。
- 基础设施能力的最小接口,例如
TokenSigner。
Service 依赖 Domain 的 Repository 接口,构造函数注入依赖。Redis、RabbitMQ、JWT 这类能力通过小接口注入,便于测试。
Infrastructure 层负责外部资源和技术实现。
主要目录:
internal/infra/config/
internal/infra/database/
internal/infra/cache/
internal/infra/mq/
internal/infra/jwt/
internal/infra/persistence/{module}/
internal/infra/persistence/migration/
GORM Repository 规则:
- 每个模块独立
model.go和gorm.go。 model.go只定义数据库模型和TableName。gorm.go实现 Domain Repository 接口。- 写操作尽量保持事务边界清晰。
- 列表查询使用稳定排序字段和游标。
- 返回 Domain 实体,避免把 GORM 模型泄漏到 Application。
Interfaces 层负责 HTTP 入口。
Handler 职责:
- 解析 path、query、body 和 header。
- 从鉴权上下文读取用户 ID。
- 调用 Application Service。
- 将结果转换为响应 DTO。
- 将业务错误映射成 HTTP 状态码。
Handler 避免承载业务规则。业务判断放在 Domain 或 Application。
路径使用资源名,方法表达动作:
| 方法 | 用途 |
|---|---|
GET |
查询资源 |
POST |
创建资源或提交复杂查询 |
PUT |
设置确定状态 |
PATCH |
部分更新 |
DELETE |
删除或取消 |
路径约定:
POST /api/users
POST /api/sessions
DELETE /api/sessions/current
GET /api/users/me
PATCH /api/users/me
POST /api/videos
GET /api/videos/{videoId}
GET /api/feed-items
POST /api/feed-queries
PUT /api/videos/{videoId}/like
DELETE /api/videos/{videoId}/like
状态码约定:
| 状态码 | 场景 |
|---|---|
200 |
查询、更新、删除成功 |
201 |
创建成功 |
400 |
参数格式或业务输入错误 |
401 |
登录态缺失或 Token 异常 |
403 |
已登录但权限不足 |
404 |
资源缺失 |
409 |
幂等冲突或唯一性冲突 |
500 |
服务内部错误 |
写接口支持 Idempotency-Key 时,客户端可传最长 128 字符的幂等键。重复请求返回同一业务结果。
列表接口优先使用游标分页。游标内容使用排序字段,编码为 URL-safe 字符串。
常见排序:
| 列表 | 排序 |
|---|---|
| Timeline Feed | published_at DESC, id DESC |
| 评论列表 | created_at DESC, id DESC |
| 关注列表 | updated_at DESC, target_user_id DESC |
| 粉丝列表 | updated_at DESC, user_id DESC |
返回结构:
{
"items": [],
"next_cursor": "",
"has_more": false
}表名使用小写蛇形命名。领域主表使用单数名,例如 user、video。关系和行为表使用业务事实名,例如 user_follow、interaction_action。
通用字段:
| 字段 | 说明 |
|---|---|
id |
BIGINT 主键 |
created_at |
创建时间 |
updated_at |
更新时间 |
status |
软状态字段 |
idempotency_key |
写操作幂等键 |
高频计数独立成统计表,例如 video_stat、user_relation_stat。计数更新与事实写入放在同一事务中完成。缓存计数允许短暂偏差,持久化表保存最终事实。
Domain 定义明确业务错误:
var ErrInvalidVideoID = errors.New("invalid video id")Application 可以包装跨资源错误,但保留可判断性:
return nil, fmt.Errorf("%w: %d", domainvideo.ErrVideoNotFound, videoID)HTTP 层使用 errors.Is 映射状态码。响应保持简洁:
{"error":"invalid request"}前端位于 apps/web,当前保持轻量结构:
apps/web/src/App.jsx
apps/web/src/main.jsx
apps/web/src/styles.css
规则:
- 组件使用函数组件。
- API 调用集中使用
apiRequest。 - 服务端错误显示为用户可理解文案。
- 页面状态保持清楚:loading、error、empty、success。
- CSS class 使用语义命名。
- 页面继续增长时拆为
src/pages/、src/components/、src/lib/api.js。
后端测试位于 apps/api/test。新增接口至少覆盖:
- 成功路径。
- 参数错误。
- 鉴权错误。
- 幂等重复请求。
- 游标分页稳定性。
- 关键状态变化和计数变化。
常用命令:
cd apps/api
go test ./...Web 构建命令:
cd apps/web
npm run buildOpenSpec 校验命令:
openspec validate --all --strict改动以下内容时同步文档:
| 改动 | 需要同步 |
|---|---|
| 新增接口 | docs/product.md、docs/modules/{module}.md |
| 新增模块 | docs/modules/README.md、模块文档、OpenSpec |
| 改目录或分层 | 本文、docs/quickread.md |
| 改核心链路 | docs/architecture.md |
| 改前端页面 | docs/uiux.md |
| 改性能策略 | docs/optimization.md |
新增后端能力检查清单:
- Domain 实体、错误和仓储接口完整。
- Application Service 覆盖核心用例。
- Infrastructure Repository 实现接口。
- Handler 完成参数解析和错误映射。
- Router 完成依赖装配。
- API 测试覆盖成功和失败路径。
- 模块文档和产品状态更新。