simple-starter-web
simple-starter-web是 simple-starter 的Web 插件模块:集成 Axum 框架,提供路由自动收集、REST 控制器、统一 JSON 响应与监听器扩展,并重导出全部 Web 宏。
一、基本原理
1. 分布式路由自动收集
路由定义分散在各模块的 Controller 中,启动时自动收集聚合,无需在main集中挂载:
- 路由宏(
#[get]、#[rest_controller]等)在编译期把 handler 包装为RouteFactory(fn(&ComponentContainer) -> Router,路由构建时查询State组件),通过inventory静态收集。 WebPlugin::finalize阶段消费WebExtensionRegistry,注册延迟构建的后台任务。- 服务启动时(
build_and_serve)遍历 inventory 中全部RouteFactory,router.merge(...)合并为完整 Router。
2. 分层构建
最终 Router 按固定顺序分层构建(build_and_serve):
- 合并自动收集与手动注册的路由
- 应用路由修改器(扩展点)
- 挂载
base_path(如/api) - 应用外部中间件(业务层,扩展点)
- 应用框架自带
TraceLayer(日志追踪,logger.level控制级别) - 构建监听器(
TcpListenerFactory,可自定义 TLS/UDS) - 启动
axum::serve,优雅退出监听CancellationToken
3. 插件生命周期
| 周期 | 行为 |
|---|---|
assemble | 创建WebExtensionRegistry并移入扩展存储(Extensions),供其他插件注册中间件、路由修改器 |
finalize | 所有组件就绪后:加载web配置、取出注册表、从组件仓库获取TcpListenerFactory、注册服务后台任务 |
4. Controller 参数重写原理
#[rest_controller]把方法改写为两段:原方法保留(Axum 提取器参数重写为裸类型,如Path(id): Path<i64>→id: i64),另生成一个路由 handler(保留提取器参数形式)负责从State<Arc<Controller>>取组件并调用原方法,返回值自动用Json<T>包裹。
二、导出的用户可用组件与宏
1. WebPlugin(插件入口)
usesimple_starter_core::Application;usesimple_starter_web::{WebPlugin,axum};fnmain(){Application::new().register_plugin(WebPlugin::new().add_manual_router_factory(||axum::Router::new().route("/manual",axum::routing::get(manual_handler))).add_middleware(|router|router.layer(CompressionLayer::new())).add_router_modifier(|router|router.fallback(fallback_handler)).set_server_scheme("https")).run();}| 方法 | 说明 |
|---|---|
new() | 创建插件 |
add_manual_router_factory(f) | 手动挂载动态构建的路由(自动收集之外的补充) |
add_router_modifier(f) | 注册路由修改器(所有路由合并后、base_path前调用) |
add_middleware(f) | 注册中间件(base_path后、框架TraceLayer前执行) |
set_server_scheme(s) | 设置协议前缀(如"https",影响启动日志) |
2. 自由函数路由宏:#[get]/#[post]/#[put]/#[delete]
参数支持简写#[get("/path")]与键值#[get(path = "/path", state = expr)]:
#[get( path ="/student/{id}", state = simple_starter_core::app_container() .expect("global container snapshot must be installed before route registration") .get_component::<StudentService>() .expect("StudentService component must be registered") )]#[json_response]// 自动将返回值包装为 Jsonasyncfnget_student_name(axum::extract::Path(id):axum::extract::Path<i64>,State(student_service):State<Arc<StudentService>>,)->JsonResponse{json_response_wrap!(function_name="根据学生id获取学生姓名",{ifid==0{returnErr(SimpleAppWebError::new(400,"无效的学生id"));}Ok(student_service.get_student_name(id).await.ok_or_else(||SimpleAppWebError::new(404,"未找到该id相关的学生姓名"))?)})}3. REST 控制器宏:#[rest_controller]+*_mapping
#[rest_controller("/api")]声明基础路径;方法级#[get_mapping]/#[post_mapping]/#[put_mapping]/#[delete_mapping]标记路由(路径参数简写或键值均可)。Controller 本身是组件,可注入依赖:
#[component]pubstructTestController{#[inject]student_service:Arc<StudentService>,}#[rest_controller("/test")]implTestController{#[post_mapping("/student/add")]pubasyncfnadd_student(&self,extract::Json(student):extract::Json<StudentDto>,)->JsonResponse{json_response_wrap!(function_name="添加学生",{self.student_service.add(student).await?;Ok(())})}}4.#[json_response]宏
作用于async fn,把返回类型T自动包装为axum::Json<T>,省去手动包裹。
5.JsonResponse与json_response_wrap!宏
JsonResponse是标准响应结构({ code, message, service_name, function_name, data },camelCase 序列化)。json_response_wrap!执行异步代码块并把Result<T, SimpleAppWebError>转换为JsonResponse:
- 成功:
code/message使用宏参数(默认 200 / “操作成功”),data序列化业务返回值 - 失败:使用
SimpleAppWebError自带的code/message/data,自动记录错误链日志
支持模式:json_response_wrap!(code = ..., message = ..., function_name = ..., { ... })(任意组合)。
6.SimpleAppWebError(业务错误)
SimpleAppWebError::new(400,"无效的学生id").with_data(json!({"field":"id"})).with_source(io_error);new(code, message):创建基础错误with_data(serializable):附加业务数据(进入响应data字段)with_source(err):关联底层错误(仅服务端日志,不返回前端)- 任意
std::error::Error经From自动转换为 500"服务器内部错误"
7. 扩展点
TcpListenerFactory(监听器扩展)
默认实现直连 TCP 绑定。实现该 trait 并注册组件即可覆盖(默认实现带条件注册,用户提供实现时自动退位):
#[simple_starter_core::component]pubstructTlsListenerFactory;#[simple_starter_core::injectable]#[async_trait::async_trait]implTcpListenerFactoryforTlsListenerFactory{asyncfnbind(&self,host:&str,port:u16)->simple_starter_core::anyhow::Result<TcpListener>{// 在此构建 TLS / UDS 监听器todo!()}}WebExtensionRegistry(路由/中间件扩展)
供其他插件在assemble阶段经扩展存储(assemble参数)获取并注册扩展:
asyncfnassemble(&mutself,extensions:&mutsimple_starter_core::Extensions)->anyhow::Result<()>{extensions.get_mut::<WebExtensionRegistry>()?.add_middleware(|router|router.layer(CompressionLayer::new()));Ok(())}三、组合使用示例
以下示例串联组件、REST 控制器与统一响应(UserContext由 security 中间件注入):
usesimple_starter_core::{component,inject,Application};usesimple_starter_web::{json_response_wrap,post_mapping,rest_controller,JsonResponse,WebPlugin};usesimple_starter_web::axum::extract;usestd::sync::Arc;#[component]structStudentService;implStudentService{asyncfnadd(&self,name:&str)->anyhow::Result<()>{Ok(())}}#[component]structStudentController{#[inject]student_service:Arc<StudentService>,}#[rest_controller("/api")]implStudentController{#[post_mapping("/student/add")]asyncfnadd_student(&self,extract::Json(student):extract::Json<StudentDto>)->JsonResponse{json_response_wrap!(function_name="添加学生",{self.student_service.add(&student.name).await?;Ok(())})}}fnmain(){Application::new().register_plugin(WebPlugin::new()).add_default_config(toml::toml!{[web]base_path="/api"}).run();}四、配置项([web]节点)
[web] port = 8080 # 监听端口 binding = "0.0.0.0" # 绑定地址 base_path = "/api" # 全局路径前缀(可选,所有路由挂载其下) log_include_headers = false # 是否在 Trace 日志中记录请求/响应头