RustLoom 开发手册
本手册是 RustLoom 开发规范的单一事实来源,面向参与设计、开发、测试和 发布的贡献者。README 只提供项目入口,具体规则应在本手册中维护。
RustLoom 当前已经具备:
- Axum HTTP 服务和 Tokio 运行时;
- PostgreSQL 连接池;
- 由 SQLx 管理的可逆数据库迁移;
- 进程存活与数据库就绪检查;
- 邮箱注册、密码登录、当前设备退出和只读 Profile;
- PostgreSQL 服务端 Session 与安全 Cookie;
- 自动化代码检查和文档构建。
身份能力仍是安全 MVP,不包含邮箱验证、密码找回、限流、权限或公网生产加固。
设计原则
- 先交付可验证的最小能力,再增加抽象。
- 所有数据库结构变化必须进入版本化 migration。
- 对外接口和错误码必须稳定、可测试、可记录。
- 文档与代码在同一 PR 中同步更新。
- 开发环境可以便利,生产默认必须安全。
快速开始
环境要求
- Rust stable,支持 Rust 2024 Edition
- Docker 和 Docker Compose
初始化环境变量并安装锁定版本的开发依赖:
make init
make install
启动服务
make db
make db-migrate
make dev
应用在宿主机运行,PostgreSQL 在 Compose 中运行。默认地址是
127.0.0.1:3000,默认数据库连接为
postgres://rustloom:rustloom@127.0.0.1:5432/rustloom。
make init 只在 .env 不存在时从 .env.example 创建文件,不覆盖已有配置。
make db 启动并等待当前数据服务健康;默认服务列表为 postgres,以后新增 Redis
等 Compose 数据服务时同步扩展 DATA_SERVICES。make db-migrate 会先确保数据
服务已启动,再执行 forward-only migration。make dev 包含前述数据库准备步骤,
并以 Rust dev profile 启动应用。
验证服务:
curl -i http://127.0.0.1:3000/health
curl -i http://127.0.0.1:3000/ready
注册并读取 Profile:
curl -i -c .cookies \
-H 'content-type: application/json' \
-d '{"email":"user@example.com","password":"correct horse 电池 staple","display_name":"Shawn"}' \
http://127.0.0.1:3000/api/v1/auth/register
curl -i -b .cookies http://127.0.0.1:3000/api/v1/profile
停止数据库但保留数据:
docker compose stop postgres
删除本地数据库数据是破坏性操作,只能在明确需要重建开发环境时执行:
docker compose down --volumes
环境变量
| 变量 | 必填 | 默认值 | 用途 |
|---|---|---|---|
DATABASE_URL | 是 | 无 | PostgreSQL 连接字符串 |
DATABASE_MAX_CONNECTIONS | 否 | 10 | 连接池最大连接数 |
DATABASE_ACQUIRE_TIMEOUT_SECONDS | 否 | 2 | 获取数据库连接的最大等待秒数 |
SESSION_COOKIE_SECURE | 否 | false | 是否给 Session Cookie 设置 Secure;生产必须为 true |
SERVER_ADDRESS | 否 | 127.0.0.1:3000 | HTTP 监听地址 |
应用启动时会读取仓库根目录的 .env。.env 不得提交,新增变量时必须同步
更新 .env.example 和本页。
应用架构
RustLoom 保持单个 Cargo package 和单体二进制,不建立 workspace 或微服务。
模块职责
main:加载环境、建立数据库连接、启动 Session 清理任务、绑定端口并服务。config:解析和校验环境变量,不包含业务逻辑。database:创建 PostgreSQL 连接池。app:组合 Router、共享状态和 Session middleware。identity:输入校验、身份 handler、认证提取器、用户查询和密码服务。error:把内部错误转换为稳定的对外 JSON 错误。
identity 内部按真实职责分为 handler/validation、store 和 password,但没有
repository trait 或通用 service 抽象。只有出现第二个使用者时才提取接口。
身份请求路径
HTTP request
-> Session middleware
-> Axum identity handler / AuthenticatedUser extractor
-> input validation
-> password blocking task and/or SQLx user store
-> PostgreSQL users + tower_sessions.session
-> stable JSON response and optional Set-Cookie
注册在用户写入成功后建立 Session;登录在密码校验成功后轮换 Session ID; Profile 的认证提取器同时检查闲置有效性、7 天绝对期限和用户是否仍然存在。
数据库与进程边界
应用启动必须成功连接 PostgreSQL;连接失败时进程以非零状态退出。/ready
在运行时检查数据库查询能力。
Migration 是部署前显式步骤。应用启动不调用 sqlx migrate run,也不允许
Session Store 自行创建表。过期 Session 清理只删除数据,不改变 schema。
数据库设计
首个数据库目标是 PostgreSQL 18。开发环境固定使用 postgres:18.4-alpine,
生产环境应持续安装 PostgreSQL 18 的最新补丁版本。
users
users 是首版全局登录用户表:
| 字段 | 类型 | 约束 |
|---|---|---|
id | uuid | 主键,由应用生成 |
email | varchar(254) | 去除首尾空白并转小写,全局唯一 |
display_name | varchar(80) | 去除首尾空白,长度 1–80 |
password_hash | text | 只保存密码散列,不允许空值 |
created_at | timestamptz | 创建时间 |
updated_at | timestamptz | 最近更新时间 |
首版不建立角色、权限、Tenant、Profile 扩展表或软删除字段。查询 Profile 时
必须显式选择公开字段,不能使用 SELECT * 把 password_hash 带入响应模型。
tower_sessions.session
Session Store 使用独立 schema:
| 字段 | 类型 | 用途 |
|---|---|---|
id | text | 不透明 Session ID |
data | bytea | Session 序列化数据 |
expiry_date | timestamptz | 过期时间和清理索引 |
该结构与 tower-sessions SQLx Store 兼容,但表由仓库 migration 创建。应用代码
不得调用 Store 自带的 migrate()。应用每小时清理过期记录;Session 闲置
12 小时失效,并由应用层额外执行 7 天绝对期限。
时间与标识
- 数据库存储统一使用
timestamptz和 UTC。 - 对外时间使用 RFC 3339。
- 用户 ID 使用 UUID,不把自增序号暴露为公共身份。
数据库迁移
SQLx migration 是数据库结构版本的唯一来源。SQLx 在数据库的
_sqlx_migrations 表记录版本、执行时间和 checksum。
创建 migration
sqlx migrate add <short_description>
每个 migration 只生成一个 .sql 文件。名称使用简短的英文 snake_case,
SQL 必须能在空数据库上按版本顺序执行。
本地工作流
sqlx migrate info
make db-migrate
开发者提交前必须在空数据库执行 migration,并再次执行 sqlx migrate run
确认没有待执行项。
Forward-only 规则
- 已合并或已在共享环境执行的 migration 不得修改、重命名或删除。
- 发现历史 migration 有问题时新增前向修复 migration。
- 不创建
.down.sql,也不执行sqlx migrate revert。 - 应用版本回滚不得反向修改数据库结构;新旧版本必须通过兼容窗口共同支持当前 schema。
- 删除列、表或数据前必须在 PR 中明确数据保留和恢复方案。
- 应用启动、Session Store 初始化和测试辅助代码都不得绕过 migration 建表。
部署顺序
备份与兼容性检查
-> sqlx migrate info
-> sqlx migrate run
-> 启动新应用版本
-> 验证 /health 和 /ready
需要零停机时,migration 必须遵循 expand-and-contract:先增加兼容结构,完成 代码迁移和数据回填后,再在后续版本删除旧结构。
HTTP API 规范
业务 API 使用 /api/v1 前缀和 JSON。健康检查是运维接口,不加版本前缀。
身份 API
POST /api/v1/auth/register
请求:
{
"email": "user@example.com",
"password": "correct horse 电池 staple",
"display_name": "Shawn"
}
成功时返回 201 Created、Profile,并设置登录 Cookie:
{
"id": "019c9122-e715-7ea3-89d4-f1f046e365f4",
"email": "user@example.com",
"display_name": "Shawn",
"created_at": "2026-07-24T12:00:00Z"
}
邮箱保存前去除首尾空白并转为小写;显示名称去除首尾空白。注册成功即登录。
POST /api/v1/auth/login
请求:
{
"email": "user@example.com",
"password": "correct horse 电池 staple"
}
成功时返回 200 OK 和 Profile,并轮换当前 Session ID。邮箱不存在与密码错误
统一返回 401 INVALID_CREDENTIALS。
POST /api/v1/auth/logout
删除当前设备 Session、清除 Cookie,返回 204 No Content。其他设备的 Session
不受影响;未登录时重复退出仍返回 204。
GET /api/v1/profile
返回当前用户的只读 Profile。无有效 Session、Session 闲置过期或达到绝对期限
时返回 401 AUTHENTICATION_REQUIRED。
输入规则
- 邮箱去除首尾空白后转小写,全局唯一,最长 254 个字符。
- 显示名称去除首尾空白,长度 1–80 个 Unicode 字符,不允许控制字符。
- 密码长度 15–128 个 Unicode 字符,允许空格、符号和 Unicode,不静默截断。
- 不可解析的 JSON 返回
400 INVALID_REQUEST。 - 字段校验失败返回
422 VALIDATION_ERROR,具体字段位于error.fields。
Cookie
Session Cookie 名为 rustloom_session,属性为 HttpOnly、SameSite=Lax 和
Path=/,不设置 Domain。开发环境允许 Secure=false;HTTPS 生产环境必须
设置 SESSION_COOKIE_SECURE=true。
使用 curl 保留 Cookie:
curl -i -c .cookies \
-H 'content-type: application/json' \
-d '{"email":"user@example.com","password":"correct horse 电池 staple","display_name":"Shawn"}' \
http://127.0.0.1:3000/api/v1/auth/register
curl -i -b .cookies http://127.0.0.1:3000/api/v1/profile
.cookies 可能包含有效登录凭据,只能作为本地临时文件,不得提交。
运维接口
GET /health
只检查 HTTP 进程是否运行,不访问依赖:
HTTP/1.1 200 OK
content-type: text/plain; charset=utf-8
ok
GET /ready
执行轻量 PostgreSQL 查询。成功返回 200:
{"status":"ready"}
数据库不可用时返回 503 DATABASE_UNAVAILABLE。
错误格式
{
"error": {
"code": "STABLE_MACHINE_CODE",
"message": "safe public message"
}
}
| HTTP | code | 含义 |
|---|---|---|
400 | INVALID_REQUEST | 请求 JSON 无法解析 |
401 | INVALID_CREDENTIALS | 登录凭据无效 |
401 | AUTHENTICATION_REQUIRED | 缺少有效 Session |
409 | EMAIL_ALREADY_REGISTERED | 邮箱已注册 |
422 | VALIDATION_ERROR | 输入字段不符合规则 |
500 | INTERNAL_ERROR | 内部错误,响应不包含底层原因 |
503 | DATABASE_UNAVAILABLE | 就绪检查无法查询数据库 |
code 是稳定、可测试的机器标识。客户端应结合 HTTP 状态处理错误,不依赖
message 文本。
安全边界
当前身份功能是安全 MVP,不代表服务已经完成公网生产加固。
密码
- 密码只保存 Argon2id PHC 格式散列,每条凭据使用独立随机 salt。
- 参数固定为
m=19456 KiB、t=2、p=1,符合当前项目采用的 OWASP 最低配置。 - 散列和校验通过 Tokio blocking task 执行,不阻塞异步运行时线程。
- 密码长度按 Unicode 字符计算,为 15–128;允许空格、符号和 Unicode。
- 用户不存在时仍执行一次 Argon2id 运算;不存在与密码错误返回相同响应。
如果以后修改参数,登录校验必须继续兼容已有 PHC 字符串,并设计登录后升级 散列的路径。
Session
- 浏览器只保存不透明 Session ID,数据位于 PostgreSQL。
- Cookie 名为
rustloom_session,设置HttpOnly、SameSite=Lax、Path=/, 不设置Domain。 - 登录和注册会轮换 Session ID,防止 Session fixation。
- 每台设备使用独立 Session,退出只删除当前设备记录。
- Session 闲置 12 小时过期;每次有效认证请求刷新活动时间。
- 无论是否活跃,Session 创建 7 天后绝对失效。
- 应用每小时删除一次已过期的数据库记录。
- Session 表只能由 SQLx migration 创建,代码禁止调用 Store 的
migrate()。
本地 HTTP 开发使用 SESSION_COOKIE_SECURE=false。生产环境在 HTTPS 下必须使用:
SESSION_COOKIE_SECURE=true
数据和错误
.env、生产连接串、Cookie 文件和数据库密码不得提交。- SQL 使用绑定参数,公开 Profile 模型不含
password_hash。 - 数据库、Session 与密码服务的底层错误只写入服务端标准错误流;客户端仅收到 稳定错误码。
- 服务未开放 CORS,身份 API 按同源浏览器应用设计。
尚未覆盖
首版明确不包含:
- 注册和登录限流、安全审计与异常登录检测;
- 邮箱验证、密码找回和密码修改;
- CSRF Token、内容安全策略与可信代理配置;
- 角色、权限、管理员、Tenant 和第三方登录;
- 全设备 Session 列表与一键退出。
在补齐限流、审计、邮箱验证策略、CSRF 威胁评估、TLS 和部署安全审查之前, 不得把身份 API 描述为公网生产就绪。
测试与质量门禁
本地检查
先启动 PostgreSQL 并执行 migration:
make db-migrate
然后运行完整检查:
cargo fmt --check
cargo check --all-targets
cargo test --all-targets
cargo clippy --all-targets -- -D warnings
mdbook test docs
mdbook build docs
git diff --check
数据库测试
- 集成测试使用
DATABASE_URL指向专用测试数据库。 - CI 在 PostgreSQL 18 服务上先运行 migration,再执行测试。
- migration 必须验证空数据库首次应用和重复执行时没有待执行项。
- 测试不得依赖个人数据库中已有的数据。
HTTP 测试
Router 由 library crate 构建,测试直接发送 HTTP request 并断言状态码、 Content-Type 和响应体。只有验证真实监听、端口冲突或外部协议时才启动服务 进程。
身份集成测试必须使用真实 PostgreSQL,并至少覆盖:
- 注册、字段规范化、重复和并发重复邮箱;
- Argon2id 参数及数据库中不出现明文密码;
- 登录错误一致性和成功后的 Session ID 轮换;
- 注册后 Profile、多设备 Session 和当前设备退出;
- 闲置与绝对过期、未知或损坏 Cookie;
- Cookie 的
HttpOnly、SameSite、Path和环境化Secure。
CI
CI workflow 对 pull request 和 main push 执行 Rust、migration 和集成测试。
Developer Guide workflow 验证 mdBook;只有 main 的成功构建可以部署
GitHub Pages。
贡献流程
开始工作
- 更新远端引用并从最新
origin/main创建codex/<description>分支。 - 确认工作树没有混入其他任务的修改。
- 大功能先明确产品边界、非目标和验收标准。
- 数据库变化先设计 migration 的升级、前向修复和兼容路径。
实现要求
- 保持 PR 单一目的,不提前实现后续阶段。
- 只暂存本次任务的文件。
- 修改行为时同步测试和开发手册。
- 新增依赖必须说明用途,关闭不需要的默认 feature。
- 不提交
.env、数据库数据目录、构建产物或文档输出。
Pull request
PR 描述至少包括:
- 改了什么以及为什么;
- 对开发者或调用方的影响;
- migration、兼容窗口和前向修复方式;
- 安全边界与明确非目标;
- 实际运行的验证命令。
默认创建 Draft PR。只有本地检查和 GitHub CI 全部成功、PR 可合并且文档已同步 时,才能转为 Ready。