news 2026/9/14 20:31:34

simple-starter-web

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
simple-starter-web

simple-starter-web

simple-starter-web是 simple-starter 的Web 插件模块:集成 Axum 框架,提供路由自动收集、REST 控制器、统一 JSON 响应与监听器扩展,并重导出全部 Web 宏。

一、基本原理

1. 分布式路由自动收集

路由定义分散在各模块的 Controller 中,启动时自动收集聚合,无需在main集中挂载:

  1. 路由宏(#[get]#[rest_controller]等)在编译期把 handler 包装为RouteFactoryfn(&ComponentContainer) -> Router,路由构建时查询State组件),通过inventory静态收集。
  2. WebPlugin::finalize阶段消费WebExtensionRegistry,注册延迟构建的后台任务。
  3. 服务启动时(build_and_serve)遍历 inventory 中全部RouteFactoryrouter.merge(...)合并为完整 Router。

2. 分层构建

最终 Router 按固定顺序分层构建(build_and_serve):

  1. 合并自动收集与手动注册的路由
  2. 应用路由修改器(扩展点)
  3. 挂载base_path(如/api
  4. 应用外部中间件(业务层,扩展点)
  5. 应用框架自带TraceLayer(日志追踪,logger.level控制级别)
  6. 构建监听器(TcpListenerFactory,可自定义 TLS/UDS)
  7. 启动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.JsonResponsejson_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::ErrorFrom自动转换为 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 日志中记录请求/响应头
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/14 20:29:45

三菱PLC恒压供水系统设计与PID控制实现

1. 项目概述&#xff1a;三菱PLC恒压供水系统设计这套"一拖四"恒压供水系统采用三菱FX3U系列PLC作为主控制器&#xff0c;搭配昆仑通态MCGS触摸屏实现人机交互。系统核心是通过PID算法动态调节四台水泵的运行状态&#xff0c;确保管网压力恒定在设定值&#xff08;通…

作者头像 李华
网站建设 2026/9/14 20:28:45

Sqoop数据迁移中MySQL驱动版本兼容性问题解析

1. Sqoop任务报错问题概述最近在搭建Hadoop数据仓库时&#xff0c;使用Sqoop从MySQL导入数据到HDFS时遇到了一个"意想不到"的错误。这个错误信息并没有明确指向某个具体问题&#xff0c;经过一番排查才发现是驱动版本不兼容导致的。这类问题在实际工作中经常遇到&…

作者头像 李华
网站建设 2026/9/14 20:28:19

幼儿家庭科学消毒指南:春节流感防护方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/14 20:26:14

国产短距离低功耗红外测距模块选型实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/14 20:23:32

Word转PDF公式符号丢失的终极解决方案

1. 问题现象与根源分析当我们将包含数学公式的Word文档转换为PDF格式时&#xff0c;经常遇到公式中的部分符号神秘消失的情况。这种情况在学术论文、技术文档的提交过程中尤为常见&#xff0c;我最近处理的一份工程报告就出现了积分符号和希腊字母丢失的问题。经过多次实测和对…

作者头像 李华