RustForge 主程序开发文档
基于 Rust + Axum 的企业级网站核心架构详解
📖 概述
RustForge 是一个使用 Rust 编写的企业级网站系统,后端采用 Axum 框架,数据库使用 PostgreSQL + SQLx,集成 Tera 模板引擎和基于 Wasmtime 的插件系统。
核心设计理念:
- 分层架构:清晰分离 presentation / core / infrastructure
- 依赖注入:通过
AppState共享资源 - 可扩展:主题和插件系统支持动态扩展,插件通过 Wasm 安全隔离
- RBAC 权限:细粒度的用户-角色-权限管理
- 完整的产品管理:支持产品、变体、属性、尺码表、多语言导出等
- 钩子系统:插件可通过钩子在主题任意位置注入内容(导航栏、侧边栏、页脚等),并支持多语言切换
📁 项目结构
rustforge/
├── Cargo.toml
├── config.toml # 配置文件
├── migrations/ # 数据库迁移文件
├── src/
│ ├── main.rs # 入口,启动服务器
│ ├── presentation/ # 表现层
│ │ ├── mod.rs # 路由组装、AppState 定义
│ │ ├── handlers/ # 请求处理器
│ │ │ ├── product.rs # 产品 CRUD
│ │ │ ├── product_export.rs # 产品导出
│ │ │ ├── product_admin.rs # 产品管理后台
│ │ │ ├── attribute.rs # 属性模板管理
│ │ │ └── plugin_hook.rs # 钩子管理 API
│ │ ├── middleware/ # 中间件
│ │ └── types.rs # 请求/响应类型
│ ├── core/ # 核心业务接口(Repository trait)
│ │ ├── mod.rs # 所有 Repository trait 定义
│ │ └── models.rs # 数据模型(含 PluginHook)
│ ├── infrastructure/ # 基础设施层
│ │ ├── db.rs # 数据库连接池
│ │ ├── db/ # Repository 实现
│ │ │ ├── product_repo.rs
│ │ │ ├── attribute_repo.rs
│ │ │ ├── plugin_hook_repo.rs # 钩子数据仓库
│ │ │ └── amatemplate_repo.rs
│ │ ├── auth.rs # JWT 生成与验证
│ │ ├── plugin.rs # Wasm 插件运行时
│ │ └── theme/ # 主题管理器(含钩子注入)
│ └── config.rs # 配置加载
├── plugins/ # 插件存放目录
├── themes/ # 主题存放目录
└── frontend/dist/ # 管理后台静态文件
采用三层架构,表现层依赖核心接口,基础设施层实现具体逻辑。
🛒 产品模块
(产品模块内容与先前版本一致,此处省略以聚焦钩子系统更新)
⚙️ 配置系统
配置文件 config.toml:
[server]
host = "0.0.0.0"
port = 3000
[theme]
default_theme = "Default"
themes_dir = "themes"
[database]
url = "" # 可通过环境变量 DATABASE_URL 覆盖
max_connections = 10
系统启动时加载 config.toml 并存入 AppState。数据库连接通常通过环境变量设置,配置文件中可留空。
🧭 路由与处理器
所有路由在 presentation/mod.rs 的 run() 函数中定义,使用 Router 构建。
公开路由
| 方法 | 路径 | 处理器 | 说明 |
|---|---|---|---|
| GET | / | home_handler | 首页 |
| GET | /health | health_handler | 健康检查 |
| POST | /api/register | register_handler | 用户注册 |
| POST | /api/login | login_handler | 登录并设置 Cookie |
| POST | /api/logout | logout_handler | 退出登录 |
| GET | /api/contents | list_published_contents_handler | 已发布内容列表 |
| GET | /content/:slug | content_detail_handler | 内容详情页 |
| GET | /:slug | category_page_handler | 分类页(动态路由) |
| GET | /sitemap.xml | sitemap_handler | 站点地图 |
| GET | /install | 安装页面 | 首次安装向导 |
| POST | /api/install | install_handler | 执行安装 |
| GET | /api/public/products | get_public_products_handler | 前台产品列表 |
| GET | /api/public/products/:id | get_public_product_handler | 前台产品详情 |
| GET | /api/public/products/:id/size-table | get_product_size_table_handler | 尺码表 |
| GET | /api/public/products/:id/attribute-values | get_public_product_attributes | 产品属性值 |
受保护路由(需认证)
所有 /api/admin/* 和 /api/plugin/* 等路由被封装在 protected_api 路由组中,应用了 auth_middleware。部分示例:
| 路径 | 说明 |
|---|---|
/api/me | 当前用户信息 |
/api/me/permissions | 当前用户权限 |
/api/admin/contents | 内容管理 CRUD |
/api/admin/plugins | 插件管理(含扫描可用插件) |
/api/admin/themes | 主题管理 |
/api/admin/products | 产品管理 CRUD |
/api/admin/products/export | 产品导出 |
/api/admin/attribute-templates | 属性模板管理 |
/api/admin/attribute-groups | 属性分组管理 |
/api/admin/amatemplates | 导出模板管理 |
/api/admin/plugins/:plugin_name/hooks | 插件钩子列表(可过滤语言) |
/api/admin/plugins/:plugin_name/hooks/:id | 钩子 CRUD |
插件与静态资源路由
| 路径 | 处理器 | 说明 |
|---|---|---|
/plugins/:plugin_name/static/*file | plugin_static_handler | 插件静态文件 |
/plugins/:plugin_name/:page | plugin_page_handler | 插件页面渲染 |
/plugins/:plugin_name/settings | plugin_settings_view_handler | 插件设置界面(后台 iframe 加载) |
/admin/ | 受保护的静态文件服务 | 管理后台 |
⛓️ 中间件
系统采用多个中间件实现横切关注点。
全局中间件
check_installed:检查系统是否已安装,未安装时重定向至/install。CookieManagerLayer:解析和设置 Cookie。inject_user_info:从 Cookie 中提取 JWT 并查询用户信息,注入到Request::Extensions,供所有处理器使用。
认证中间件
auth_middleware:优先从 Cookie 获取 token,其次从Authorization头获取,用于 API 保护。admin_auth_middleware:基于 Cookie 保护/admin路由,未登录重定向到登录页。
用户信息注入(inject_user_info)
处理器可通过 Extension<UserInfo> 直接获取当前用户状态,无需重复解析 Cookie。
pub async fn home_handler(
Extension(user_info): Extension<UserInfo>,
State(state): State<Arc<AppState>>,
) -> impl IntoResponse {
// user_info.is_logged_in, user_info.user_name 等
}
🗄️ 数据库与 Repository
使用 SQLx 连接 PostgreSQL,通过 create_pool 创建连接池并放入 AppState。
核心层定义了 Repository trait:
pub trait UserRepository {
async fn get_user_by_email(&self, email: &str) -> Result<Option<User>>;
async fn get_user_by_id(&self, id: Uuid) -> Result<Option<User>>;
// ...
}
基础设施层提供 PostgresUserRepo、PostgresContentRepo、PostgresProductRepo、PostgresAttributeRepo、PostgresPluginHookRepo 等实现。
数据库迁移使用 sqlx migrate run 或安装向导自动执行,迁移文件位于 migrations/ 目录。
钩子数据表
CREATE TABLE plugin_hooks (
id SERIAL PRIMARY KEY,
plugin_name VARCHAR(255) NOT NULL,
hook_name VARCHAR(255) NOT NULL,
content TEXT NOT NULL,
sort_order INTEGER DEFAULT 0,
lang VARCHAR(10) DEFAULT '',
enabled BOOLEAN DEFAULT true,
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now()
);
钩子存储所有插件注入的内容。lang 字段用于多语言:空字符串表示通用钩子(所有语言显示),特定语言代码(如 zh)仅在该语言下显示。系统加载钩子时优先匹配当前语言,若不存在则回退到通用钩子。
plugin_name 与 plugins 表的 name 关联。当插件被禁用时,其所有钩子将不会在前台渲染(通过 list_enabled_by_lang 方法过滤)。这确保了禁用插件后其内容不会意外出现在主题中。
🔐 认证与授权
JWT 认证
- 登录接口生成 JWT,包含
sub(用户 ID)和过期时间。 - JWT 设置为 HttpOnly Cookie,增强安全性。
- 全局中间件
inject_user_info解析 Cookie 中的 token 并验证。
RBAC 权限模型
用户通过 user_roles 关联角色,角色通过 role_permissions 关联权限。处理器中可使用 CurrentUser 提取当前用户 ID,并结合权限检查。
权限检查通常在后端 API 层进行(通过查询用户权限表),未来可抽象为中间件或 guard。
退出登录
POST /api/logout 调用 logout_handler,使用 cookies.add() 设置过期 Cookie 以清除 auth_token。
🔌 插件系统
插件系统基于 Wasmtime 运行时,支持动态加载 .wasm 模块。插件可拥有独立的前端页面、API 接口和设置界面。
核心结构
pub struct WasmPlugin {
engine: Engine,
store: Store<()>,
instance: Instance,
memory: Memory,
}
提供方法:
call_execute(input_json) -> Result<String>call_is_page_protected(page) -> Result<String>call_metadata() -> Result<String>(用于扫描安装)
插件安装与扫描
系统支持两种安装方式:
- 上传 .wasm 文件:通过后台手动上传。
- 扫描目录自动发现:将插件文件夹放入
plugins/,在后台“扫描可用插件”即可一键安装。安装时系统会复制一份 wasm 到plugins/根目录,原始文件保留以便重新扫描。
插件设置
每个插件可在 views/settings.html 中提供自定义设置界面,通过 /api/admin/plugins/{name}/settings 读写 JSON 配置。管理员在后台点击“设置”按钮时,通过 iframe 加载该页面。
页面保护
plugin_page_handler 加载插件并调用 is_page_protected,根据结果和用户登录状态决定是否重定向到插件登录页。
plugin_hooks API 向主题注入内容,无需修改主题模板。钩子内容支持 {{ key }} 多语言占位符,由主程序自动翻译。当插件被禁用时,其钩子将不会在前台显示(通过数据库查询过滤已启用插件)。插件开发细节请参考《插件开发完全指南》。
🔌 钩子系统
钩子系统是 RustForge 的核心扩展机制之一,允许插件在主题的任意位置注入内容,无需修改主题模板。钩子支持多语言、多插件协作,并且与插件启用状态联动。
工作原理
- 主题定义钩子:在主题模板中放置
{{ hooks.nav | safe }}占位符。 - 插件创建钩子:通过
/api/admin/plugins/{name}/hooksAPI 创建钩子记录,指定hook_name、content、lang(语言代码)和enabled。 - 主程序渲染:
TeraThemeManager在渲染时调用load_hooks(lang),从数据库加载所有启用且语言匹配的钩子(同时检查所属插件是否启用)。 - 合并与翻译:相同
hook_name的钩子按sort_order排序后合并,并替换{{ key }}为当前语言的翻译。 - 输出:合并后的内容注入到
hooks对象,主题模板使用{{ hooks.nav | safe }}输出。
钩子数据模型
| 字段 | 类型 | 说明 |
|---|---|---|
id | i32 | 主键 |
plugin_name | String | 来源插件名称(与插件表关联) |
hook_name | String | 钩子名称(如 nav, sidebar) |
content | String | HTML 内容,支持 {{ key }} 多语言占位符 |
sort_order | i32 | 排序(多个钩子按此合并) |
lang | String | 语言代码(空字符串表示通用,zh 表示中文等) |
enabled | bool | 是否启用 |
多语言策略与优先级
系统在加载钩子时遵循以下规则:
- 语言匹配:优先加载当前语言(如
lang='zh')的钩子。 - 回退机制:若当前语言钩子不存在,则加载通用钩子(
lang='')。 - 多插件合并:同一钩子名(如
nav)可由多个插件分别注入内容,按sort_order排序后合并为一个 HTML 字符串。 - 插件状态过滤:仅加载所属插件处于启用状态的钩子(通过
INNER JOIN plugins ON plugins.name = plugin_hooks.plugin_name AND plugins.enabled = true)。 - 翻译:钩子内容中的
{{ key }}会被自动替换为I18n::t(lang, key)的结果。
zh 和 en 创建不同内容的版本,这样切换语言时导航文字自动适配,无需修改主题模板。
钩子生命周期
- 创建:管理员通过插件设置界面或直接调用 API 创建钩子。
- 存储:数据存入
plugin_hooks表,enabled默认为true。 - 启用/禁用:管理员可通过钩子管理页面或插件启停间接控制。
- 渲染:
TeraThemeManager::load_hooks执行查询,过滤已启用插件和匹配语言的钩子。 - 合并与翻译:同一
hook_name的内容合并,并进行占位符替换。 - 输出:注入到 Tera 上下文,供主题模板使用。
- 删除:管理员可通过 API 或钩子管理页面删除。
钩子 API
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/admin/plugins/{name}/hooks | 获取插件的所有钩子(支持 ?lang=zh 过滤) |
| POST | /api/admin/plugins/{name}/hooks | 创建钩子(需指定 hook_name, content, lang, sort_order) |
| PUT | /api/admin/plugins/{name}/hooks/:id | 更新钩子(可修改 content, lang, enabled 等) |
| DELETE | /api/admin/plugins/{name}/hooks/:id | 删除钩子 |
核心实现(代码片段)
// TeraThemeManager::load_hooks(简化版)
async fn load_hooks(&self, lang: &str) -> HashMap<String, String> {
let mut hooks = HashMap::new();
if let Some(repo) = &self.hook_repo {
// 获取通用钩子和当前语言钩子(仅已启用插件)
let generic = repo.list_enabled_by_lang("").await?;
let specific = repo.list_enabled_by_lang(lang).await?;
let all = generic.into_iter().chain(specific).collect::>();
// 按 hook_name 分组
let mut groups: HashMap> = HashMap::new();
for hook in all {
groups.entry(hook.hook_name).or_default().push(hook);
}
for (hook_name, mut hooks_vec) in groups {
hooks_vec.sort_by_key(|h| h.sort_order);
// 合并内容并翻译
let combined = hooks_vec.iter().map(|h| h.content.clone()).collect::>().join("\n");
let translated = TEMPLATE_REGEX.replace_all(&combined, |caps| {
let key = caps.get(1).unwrap().as_str().trim();
self.i18n.t(lang, key)
});
hooks.insert(hook_name, translated.to_string());
}
}
hooks
}
- 解耦:主题无需知道插件存在,插件无需修改主题。
- 多语言原生支持:
{{ key }}自动翻译,且钩子可分别管理不同语言版本。 - 多插件协作:多个插件可向同一钩子注入内容,按序排列。
- 灵活扩展:主题开发者可自定义任意钩子名。
- 安全控制:禁用插件后其钩子自动消失,避免残留内容。
🎨 主题系统
使用 Tera 模板引擎,支持多主题和动态切换。
主题管理器
pub struct TeraThemeManager {
themes: RwLock<HashMap<String, Box<dyn Theme>>>,
active: RwLock<String>,
hook_repo: Option<Arc<dyn PluginHookRepository>>,
i18n: Arc<I18n>,
}
启动时扫描 themes/ 目录下的所有 theme.toml,加载对应模板,并注入钩子内容。
所有返回 HTML 的处理器在渲染前将必要变量(nav_categories, site_config, user_info, hooks 等)注入上下文,然后调用 render()。
主题开发细节请参考《主题开发指南》。
🚢 部署
Docker 部署(推荐)
# 构建镜像
docker build -t rustforge .
# 运行容器
docker run -d --name rustforge -p 3000:3000 \
-e DATABASE_URL="postgresql://..." \
-v /host/path/config.toml:/app/config.toml \
-v /host/path/uploads:/app/uploads \
-v /host/path/plugins:/app/plugins \
-v /host/path/themes:/app/themes \
rustforge
Docker Compose
version: '3.8'
services:
rustforge:
image: rustforge
ports:
- "3000:3000"
environment:
DATABASE_URL: postgresql://postgres:password@postgres/rustforge
volumes:
- ./config.toml:/app/config.toml
- ./uploads:/app/uploads
- ./plugins:/app/plugins
- ./themes:/app/themes
postgres:
image: postgres:15
environment:
POSTGRES_PASSWORD: password
POSTGRES_DB: rustforge
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
推荐使用反向代理(Nginx/Caddy)处理 HTTPS 和静态资源加速。可通过挂载目录方式热更新配置和主题。