RustForge 主程序开发文档

基于 Rust + Axum 的企业级网站核心架构详解

Rust Axum SQLx Wasmtime 产品管理 钩子系统

📖 概述

RustForge 是一个使用 Rust 编写的企业级网站系统,后端采用 Axum 框架,数据库使用 PostgreSQL + SQLx,集成 Tera 模板引擎和基于 Wasmtime 的插件系统。

核心设计理念:

📁 项目结构

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.rsrun() 函数中定义,使用 Router 构建。

公开路由

方法路径处理器说明
GET/home_handler首页
GET/healthhealth_handler健康检查
POST/api/registerregister_handler用户注册
POST/api/loginlogin_handler登录并设置 Cookie
POST/api/logoutlogout_handler退出登录
GET/api/contentslist_published_contents_handler已发布内容列表
GET/content/:slugcontent_detail_handler内容详情页
GET/:slugcategory_page_handler分类页(动态路由)
GET/sitemap.xmlsitemap_handler站点地图
GET/install安装页面首次安装向导
POST/api/installinstall_handler执行安装
GET/api/public/productsget_public_products_handler前台产品列表
GET/api/public/products/:idget_public_product_handler前台产品详情
GET/api/public/products/:id/size-tableget_product_size_table_handler尺码表
GET/api/public/products/:id/attribute-valuesget_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/*fileplugin_static_handler插件静态文件
/plugins/:plugin_name/:pageplugin_page_handler插件页面渲染
/plugins/:plugin_name/settingsplugin_settings_view_handler插件设置界面(后台 iframe 加载)
/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>>;
    // ...
}

基础设施层提供 PostgresUserRepoPostgresContentRepoPostgresProductRepoPostgresAttributeRepoPostgresPluginHookRepo 等实现。

数据库迁移使用 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_nameplugins 表的 name 关联。当插件被禁用时,其所有钩子将不会在前台渲染(通过 list_enabled_by_lang 方法过滤)。这确保了禁用插件后其内容不会意外出现在主题中。

🔐 认证与授权

JWT 认证

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,
}

提供方法:

插件安装与扫描

系统支持两种安装方式:

插件设置

每个插件可在 views/settings.html 中提供自定义设置界面,通过 /api/admin/plugins/{name}/settings 读写 JSON 配置。管理员在后台点击“设置”按钮时,通过 iframe 加载该页面。

页面保护

plugin_page_handler 加载插件并调用 is_page_protected,根据结果和用户登录状态决定是否重定向到插件登录页。

💡 插件与钩子的关系:插件通过 plugin_hooks API 向主题注入内容,无需修改主题模板。钩子内容支持 {{ key }} 多语言占位符,由主程序自动翻译。当插件被禁用时,其钩子将不会在前台显示(通过数据库查询过滤已启用插件)。

插件开发细节请参考《插件开发完全指南》。

🔌 钩子系统

钩子系统是 RustForge 的核心扩展机制之一,允许插件在主题的任意位置注入内容,无需修改主题模板。钩子支持多语言、多插件协作,并且与插件启用状态联动。

工作原理

  1. 主题定义钩子:在主题模板中放置 {{ hooks.nav | safe }} 占位符。
  2. 插件创建钩子:通过 /api/admin/plugins/{name}/hooks API 创建钩子记录,指定 hook_namecontentlang(语言代码)和 enabled
  3. 主程序渲染TeraThemeManager 在渲染时调用 load_hooks(lang),从数据库加载所有启用且语言匹配的钩子(同时检查所属插件是否启用)。
  4. 合并与翻译:相同 hook_name 的钩子按 sort_order 排序后合并,并替换 {{ key }} 为当前语言的翻译。
  5. 输出:合并后的内容注入到 hooks 对象,主题模板使用 {{ hooks.nav | safe }} 输出。

钩子数据模型

字段类型说明
idi32主键
plugin_nameString来源插件名称(与插件表关联)
hook_nameString钩子名称(如 nav, sidebar
contentStringHTML 内容,支持 {{ key }} 多语言占位符
sort_orderi32排序(多个钩子按此合并)
langString语言代码(空字符串表示通用,zh 表示中文等)
enabledbool是否启用

多语言策略与优先级

系统在加载钩子时遵循以下规则:

✅ 最佳实践:为每种语言单独创建钩子,同时保留一个通用版本作为 fallback。例如,导航栏钩子可分别为 zhen 创建不同内容的版本,这样切换语言时导航文字自动适配,无需修改主题模板。

钩子生命周期

钩子 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 和静态资源加速。可通过挂载目录方式热更新配置和主题。