Rust 错误处理:从 Result 到 thiserror,生产级方案怎么选?

Rust错误处理:thiserror与anyhow实战指南 错误处理是系统编程中的核心概念,Rust通过Result类型实现了显式错误传播机制。不同于传统的异常处理,这种设计需要开发者明确处理所有潜在错误,从而提升代码可靠性。在工程实践中,错误处理库如thiserror和anyhow通过提供类型安全的错误抽象和上下文增强能力,大幅降低了开发复杂度。thiserror适用于需要精确错误类型的库开发场景,而anyhow则擅长快速原型开发和应用层错误处理。两者的组合使用能有效覆盖从底层基础设施到上层业务逻辑的全栈开发需求,特别适合网络服务、文件解析器等需要严格错误分类的场 阅读详情

Rust 错误处理:从 Result 到 thiserror,生产级方案怎么选?

cover

一、当编译器不再帮你兜底:Rust 错误处理的现实困境

写 Rust 的人大概都经历过这种时刻:代码逻辑明明没问题,编译器却死活不让过,原因只是你忘了处理一个 Result。这种体验在刚开始学 Rust 的时候,简直让人怀疑人生。但冷静下来想,这恰恰是 Rust 的核心设计哲学——错误不是异常,不是你可以假装看不见的东西,而是必须被显式处理的返回值。

在 C 语言里,错误码被随意忽略是家常便饭。在 Java 里,try-catch 包裹一切,异常在调用栈里层层上抛,你永远不知道哪一层会吞掉它。Rust 选择了第三条路:用类型系统强制你面对错误。这条路的好处是可靠性,代价是心智负担。

生产环境里,错误处理远不止"unwrap 还是 match"这么简单。一个 CLI 工具需要给用户友好的错误提示,一个库需要给调用方结构化的错误类型,一个异步服务需要把错误正确地传播到 tokio 运行时。这些场景对错误处理的要求各不相同,选错方案,要么代码到处是样板,要么运行时信息丢失。

二、Result、Option 与错误传播:类型层面的安全网

Rust 的错误处理建立在两个核心类型之上:Option<T> 表示可能缺席的值,Result<T, E> 表示可能失败的计算。它们不是语法糖,而是类型系统的一等公民。

graph TD
    A[函数执行] --> B{计算结果}
    B -->|成功| C[Ok T]
    B -->|可恢复错误| D[Err E]
    B -->|逻辑缺席| E[None]
    C --> F[继续处理]
    D --> G[match 显式处理]
    D --> H[? 运算符传播]
    E --> I[unwrap_or 提供默认]
    E --> J[ok_or 转为 Result]

? 运算符是错误传播的核心机制。它不是简单的语法糖,而是一个涉及 From trait 的类型转换过程。当 ? 执行时,如果遇到 Err(e),它会尝试将 e 通过 From::from() 转换成当前函数的返回错误类型,然后提前返回。这意味着你可以用 ? 把不同模块的错误类型无缝地传播到同一个错误链里。

但这里有个坑:? 只能在返回 Result 的函数里使用。如果你在 main 函数里直接用 ?,Rust 2021 edition 之前是不行的。2021 edition 之后,main 可以返回 Result<(), Box<dyn Error>>,这给了 CLI 工具一个简洁的入口。

三、从手写 Error 到 thiserror:生产级错误类型的演进

先看一个最基础的自定义错误类型:

use std::fmt;
use std::error::Error;

/// 数据库连接错误类型
#[derive(Debug)]
enum DbError {
    /// 连接超时,携带超时时长(毫秒)
    ConnectionTimeout(u64),
    /// 认证失败,携带原因描述
    AuthFailed(String),
    /// 查询执行失败,携带 SQL 语句和底层错误
    QueryFailed { sql: String, source: io::Error },
}

impl fmt::Display for DbError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            DbError::ConnectionTimeout(ms) => {
                write!(f, "数据库连接超时,等待了 {}ms", ms)
            }
            DbError::AuthFailed(reason) => {
                write!(f, "数据库认证失败:{}", reason)
            }
            DbError::QueryFailed { sql, source } => {
                write!(f, "查询执行失败 [{}],原因:{}", sql, source)
            }
        }
    }
}

impl Error for DbError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        match self {
            DbError::QueryFailed { source, .. } => Some(source),
            _ => None,
        }
    }
}

手写 Display 和 Error 实现很繁琐,尤其是错误变体多的时候。thiserror 就是来解决这个问题的:

use thiserror::Error;
use std::io;

/// 使用 thiserror 派生宏简化错误类型定义
#[derive(Error, Debug)]
enum DbError {
    /// 连接超时错误,自动生成 Display 实现
    #[error("数据库连接超时,等待了 {0}ms")]
    ConnectionTimeout(u64),

    /// 认证失败错误
    #[error("数据库认证失败:{0}")]
    AuthFailed(String),

    /// 查询执行失败,通过 source 属性自动实现 Error::source
    #[error("查询执行失败 [{sql}],原因:{source}")]
    QueryFailed {
        sql: String,
        #[source]
        source: io::Error,
    },
}

thiserror 的核心优势在于:用声明式的方式替代手写样板代码,#[error(...)] 属性同时处理了 Display 实现,#[source] 属性自动处理了错误链。在库的开发中,thiserror 几乎是标配。

对于应用层,anyhow 提供了另一种思路。它不关心错误类型的具体结构,只关心错误信息的可读性和上下文附加:

use anyhow::{Context, Result};

/// 从配置文件加载数据库配置
fn load_db_config(path: &str) -> Result<DbConfig> {
    let content = std::fs::read_to_string(path)
        .context(format!("无法读取配置文件:{}", path))?;
    let config: DbConfig = toml::from_str(&content)
        .context("配置文件格式解析失败")?;
    Ok(config)
}

context() 方法在不改变错误类型的前提下,给错误链追加上下文信息。这在应用层非常实用——你不需要为每个操作定义专门的错误类型,只需要在关键节点加上"当时在做什么"的说明。

四、thiserror 与 anyhow 的适用边界与架构取舍

选择错误处理方案,本质上是在"类型安全"和"开发效率"之间做权衡。

thiserror 适合库的开发。库的消费者需要根据错误类型做不同的处理逻辑,match 一个具体的枚举比 downcast 一个 anyhow::Error 要自然得多。如果你的 API 需要向调用方暴露错误的结构,thiserror 是正确选择。

anyhow 适合应用层。CLI 工具、Web 服务、后台任务——这些场景下,错误的最终消费者是人,不是代码。你需要的是清晰的错误信息和完整的上下文链,而不是精确的类型匹配。

但两者不是互斥的。一个常见的架构模式是:库内部用 thiserror 定义精确的错误类型,应用层用 anyhow 收集和传播这些错误,同时附加业务上下文。thiserror 定义的错误类型天然实现了 Into<anyhow::Error>,所以这个组合是无缝的。

需要注意的坑:anyhow 的 Error 类型不是 Send + Sync 的(在旧版本中),这在某些异步场景下会出问题。1.0 版本之后已经修复,但如果你在维护老代码,这个点值得排查。另外,anyhow 的错误信息在跨线程传播时可能会丢失 backtrace,这在调试并发问题时是个痛点。

另一个常被忽略的问题是错误信息的国际化。thiserror 的 #[error(...)] 里写的字符串会直接成为 Display 输出,如果你需要支持多语言,就需要在 Display 实现里做翻译映射,而不是依赖派生宏。这种场景下,手写 Display 反而更灵活。

五、总结

Rust 的错误处理体系建立在类型系统之上,Result 和 Option 是编译器强制你面对错误的工具。? 运算符通过 From trait 实现了跨类型的错误传播,这是整个机制的粘合层。thiserror 适合库开发,提供类型精确、可 match 的错误枚举;anyhow 适合应用层,提供上下文附加和简洁的错误传播。两者可以组合使用:库用 thiserror,应用用 anyhow。选择方案时,核心判断标准是错误的消费者是代码还是人。生产环境中,错误处理不只是"让编译通过",更是给调试和运维留足信息。

【Rust错误处理】Rust错误处理机制详解与应用实战 在 Rust 中,错误处理是一个核心特性,它通过 Result 和 Option 类型以及 panic! 机制来管理错误和异常情况。Rust 的错误处理强调显式性和安全性,避免了传统异常机制的隐式控制流问题。 Rust 主要有两种错误处理方式:可恢复错误和不可恢复错误。 阅读详情

相关推荐

Ollama+FAISS+Embedding+Rerank+LLM 详细部署指南

下载安装Ollama 双击运行安装程序 安装完成后会自动启动服务 修改默认安装路径(避免C盘爆满) 或使用PowerShell: 验证安装 Linux系统部署: (二)准备模型 1. 拉取Embedding模型 2. 拉取LLM模型 注意:原文中提到的可能需要从自定义仓库拉取: 3. 下载Rerank模型 (三)安装依赖库 创建项目目录和虚拟环境: 二、主要功能实现 项目结构 (一)配置文件(config/config.yaml) (二)文本分块策略实现 创建 : (三)文档读取

AI技术改变世界 1580

Rust 错误处理全景:从 thiserror 到生产级代码组织的进阶之路

Rust 的错误处理体系以和?操作符为核心,通过Fromtrait 实现错误的自动类型转换。生产级代码应避免unwrap(),转而使用thiserror构建领域语义的错误类型体系。每个错误变体都应携带诊断上下文,而非仅传递底层错误消息。统一枚举方案适合中小型项目,大型项目应采用分层错误设计以保持模块边界。库代码必须使用具体错误类型,应用代码可以使用anyhow简化开发。错误信息的设计需要平衡诊断价值与安全约束,避免在错误消息中泄露敏感数据。

no1coder的博客 5457

WPS Office 2019 For Linux二次开发全流程:从源码下载到Demo运行

本文详细介绍了在Linux环境下进行WPS Office 2019 C++二次开发的全流程,涵盖从开发环境准备、源码获取、核心依赖(Qt与WPS)安装配置,到最终编译运行官方Demo的完整步骤。文章重点解析了环境搭建中的常见问题与解决方案,旨在帮助开发者快速上手WPS二次开发。

weixin_29001423的博客 388

Crate 级错误类型与 Result 别名:用 thiserror 打造生产级 Rust 错误处理(Rust for C Programmers)

本文是 RustTraining 仓库《Rust for C# Programmers》([csharp-book](https://link.gitcode.com/i/d9ef08e718d26557254402f4ac2a0678))第九章进阶篇的深度展开,核心讲解生产级 Rust 项目中最关键的错误处理模式:用 `thiserror` 定义一个 crate 级的错误枚举、通过 `Resul

gitblog_00431的博客 689

[比赛]新冠期间饿了么骑士行为预估-数据理解

写在前面 饿了么的比赛的赛题数据理解。 一、骑士行为数据 courier_id 骑士id wave_index 波次index tracking_id 订单id courier_wave_start_lng 波次起始时刻骑士位置 courier_wave_...

Helslie的博客 2943

【运筹优化】VRPTW建模实战:Python+Gurobi求解带时间窗的车辆路径规划

本文详细介绍了如何使用Python和Gurobi求解带时间窗的车辆路径问题(VRPTW)。通过将物流调度难题抽象为混合整数规划模型,并逐步讲解建模、编码与求解过程,为读者提供了从理论到实践的完整指南,帮助解决物流配送中的路径优化挑战。

peace的博客 201

Rust 错误处理模式:从 Result 到 thiserror 的生产级代码组织

// 应用级错误类型:用 thiserror 派生 Error trait/// IO 错误:自动实现 From<io::Error>#[error("IO 错误: {0}")]/// 解析错误:自动实现 From<ParseIntError>#[error("配置解析失败: {0}")]/// 数据库错误#[error("数据库错误: {message}")]Database {#[source]},/// 业务逻辑错误#[error("用户 {user_id} 不存在")]

no1coder的博客 4794

Stata实战:5步搞定PSM倾向得分匹配(附完整代码与常见报错解决)

本文详细介绍了使用Stata进行PSM(倾向得分匹配)的5步实战流程,包括数据预处理、模型构建、匹配算法选择、匹配质量诊断和结果解释。提供完整代码示例和常见报错解决方案,帮助研究者高效完成实证分析,提升匹配成功率。

weixin_29214559的博客 590

Rust 错误处理模式:从 Result 到 thiserror/anyhow 的生产级代码组织

/ 使用 thiserror 为库定义精确的错误类型// 调用方可以 match 具体变体,做出不同处理/// 数据库操作错误类型/// 每个变体对应一种具体的失败场景/// 连接失败:网络不可达或认证失败#[error("数据库连接失败: {source}")]#[source]},/// 查询超时:SQL 执行超过时间限制#[error("查询超时: {sql} 执行超过 {timeout_ms}ms")]},/// 数据未找到:查询条件无匹配记录。

no1coder的博客 3251

delphi fmx android 自动更新(一)

所以,如果是http 则,manifest.xml application 下,https无需添加。增加android:usesCleartextTraffic="true",不然无法使用http下载东西。1,REQUEST_INSTALL_PACKAGES 权限。4,WRITE_EXTERNAL_STORAGE 权限。3,READ_EXTERNAL_STORAGE 权限。一般是从http下载安装包 apk。2,INTERNET 权限。sFile为apk包路径。

全粘工程师 550

SAP_SD模块更改销售订单定价简介

方式优点缺点适用场景MASS简单,无需开发不能触发重新定价逻辑修改特定字段BDC (VA02)可模拟人工操作维护复杂,速度慢少量订单批改稳定、安全需ABAP支持推荐用于批量重新定价VBOF标准重算返利仅适返利类条件返利更新通过本文,我们了解了 SAP 的[SAP_SD模块更改销售订单定价]的基本概念和应用方法。希望这些信息能帮助您在实际工作中更好地使用 SAP 系统,提高工作效率和管理水平。更多SAP文章请点击更多SAP文章更新,大家一起学习进步!!!

我小时候很黑的博客 712

Rust 错误处理模式与生产级代码组织:从 Result/Option 到 `thiserror` 与 `anyhow` 最佳实践

一个成熟的技术人,体现在对错误和异常情况的优雅掌控上。理清ResultOption无成本抽象的原理,掌握?操作符在编译期自动展开与Fromtrait 转换的机制,坚持在库代码用thiserror、应用代码用anyhow,才能告别粗暴的unwrap()崩溃,写出符合大厂生产标准的高质量 Rust 代码。

no1coder的博客 5155

基于STM32F103单片机的节能型自适应风扇项目.zip

基于单片机的系统

Rust 错误处理的艺术:从 Result、? 到 anyhow 与 thiserror

Rust采用Result<T,E>枚举处理可恢复错误,通过?操作符简化错误传播。对比thiserror(库开发)和anyhow(应用开发)两种实践:thiserror创建具体错误类型便于调用方匹配;anyhow提供动态错误包装和上下文信息。性能方面,Rust的错误处理是零成本抽象,比传统异常机制更高效。文章还包含代码示例和性能对比分析,为开发者提供Rust错误处理的最佳实践方案。

m0_37613794的博客 539

三相三线制20kw光伏并网逆变器程序.zip_mass并网逆变器_光伏 并网_光伏逆变 20kw_源程序_逆变器程序

20Kw的大功率并网逆变器,丰富的源程序,经过量产的成熟产品,直接就可以使用。

labview程序,电梯,串口助手,自动售货机,计算器.zip源码Labview个人项目资料程序资源下载

labview程序,电梯,串口助手,自动售货机,计算器.zip源码Labview个人项目资料程序资源下载labview程序,电梯,串口助手,自动售货机,计算器.zip源码Labview个人项目资料程序资源下载labview程序,电梯,串口助手,自动售货机,计算器.zip源码Labview个人项目资料程序资源下载labview程序,电梯,串口助手,自动售货机,计算器.zip源码Labview个人项目资料程序资源下载1.合个人学习技术做项目参考合个人学习技术做项目参考2.适合学生做毕业设计项目参考适合学生做毕业设计项目参考 3.适合小团队开发项目模型参考适合小团队开发项目模型参考

Rust专项——错误处理实战:Result、`?`、thiserror 与 anyhow

本文系统梳理 Rust 错误处理的最佳实践。从基础的 Result 和 ? 运算符开始,介绍了库级自定义错误类型(使用 thiserror)和应用层快速处理(使用 anyhow)。重点阐述了错误处理的分层策略:库应当使用可枚举的自定义错误类型,而应用层适合使用 anyhow 收集上下文信息。文章还提供了实战示例和常见陷阱,强调通过合理分层实现"接口清晰、边界合理、信息友好、可追踪可定位"的错误处理目标,并展示了 thiserror 和 anyhow 如何减少样板代码,提高开发效率。

红目香薰 1006

基于单片机的太阳能电池板自动跟踪系统的研究

本文是对单片机的太阳能电池板跟踪系统的研究, 将采用光电检测跟踪方式和太阳高度角跟踪方式相结合, 晴天时采用的是光电检测跟踪方式, 阴天时采用的是太阳高度角跟踪方式, 这两种方法结合起来, 使跟踪系统可以在不同天气状况下进行实时跟踪。 文中光电跟踪模式采用光敏二极管和四象限探测器组成的光电传感器, 检测晴天时的太阳光线强度, 使光敏二极管首先进行粗略的跟踪, 然后再由四象限探测器进行精确跟踪, 最终实现跟踪的精确定位。 太 阳高度角跟踪方式利用太阳方位计算器计算出某时刻太阳所在的位置, 单片机把计算出来的太阳方位值, 转化为能够识别的控制信号, 从而驱动步进电机执行相应的动作, 最终达到跟踪太阳的目的。论文在最后分别对系统进行了侧试, 结果表明: 自动跟踪系统的太阳能接收率比固定不变的太阳能电池板的接收效率提高33.5%, 太阳方位计算器的结果显示出计算精度较高。 系统基本满足了跟踪的要求, 提高了太阳能电池板的最大转换效率。

【Rust每周一库】anyhow和thiserror - 错误处理库

前言之前我们借助讲解failure库,详细说明的Rust错误处理的哲学,这有助于我们理解今天的主题,anyhow和thiserrorAnyhow提供了一个anyhow::Error t...

Rust语言学习交流 5316

Rust 错误传播模式:从 Result 到 thiserror,让编译器帮你排错

Result 是值不是异常、?是传播不是忽略、库精确应用统一。基础用法:所有可能失败的操作返回Result,用?传播错误,不用unwrap()。库层:用 thiserror 定义精确的错误枚举,#[from]自动实现类型转换,让调用者可以 match。应用层:用 anyhow 统一收集错误,context()添加上下文,main 返回。错误信息:包含"什么操作、什么上下文、什么原因"三要素,让排障者能快速定位。Rust 的错误处理初看繁琐,但它让每个可能的错误都在编译期被标记。

no1coder的博客 4836

Rust错误处理:anyhow与thiserror对比与实践

错误处理是系统编程中的核心概念,Rust通过Result类型实现了显式错误处理机制。不同于传统的异常处理,这种范式在编译期强制错误检查,显著提升了代码可靠性。anyhow和thiserror作为Rust生态中的热门错误处理库,分别针对应用层和库层提供了优化解决方案。anyhow采用动态错误类型实现快速原型开发,而thiserror通过过程宏生成类型安全的错误定义。在Web服务、命令行工具等应用场景中,合理选择错误处理策略能有效平衡开发效率与代码质量。本文深入解析这两个库的设计哲学,并分享实际项目中的最佳实践

weixin_30938149的博客 399

Rust 错误处理分层策略:从 thiserror 到 anyhow 的工程化实践

// 数据库访问层错误#[error("连接池耗尽: 等待超时 {timeout_ms}ms")]#[error("查询执行失败: {query}")]#[source]},#[error("记录未找到: {table} WHERE {condition}")]#[error("事务冲突: {reason}")]/// 解析层错误#[error("JSON 解析失败: {path}")]#[source]},

no1coder的博客 2593

别再纠结该返回啥:Result/Option 到 thiserror/anyhow 的落地套路

面向“库 × 应用”分层,给出一套能直接复用的错误返回与传播策略:底层库用 `Result` 搭配 `thiserror` 精准建模、应用层用 `anyhow` 汇聚并补充上下文;结合 `Backtrace`、结构化日志与指标,把错误变成可定位、可观测、可回归的信号。文中以真实工程片段为主,涵盖同步/异步场景、跨线程任务、错误码对外暴露与边界收敛,最终形成一套可复制的错误处理底盘。

努力分享一些人工智能、计算机视觉、影像等相关的知识干货! 961

从 Result、? 到 anyhow 与 thiserror

Rust 抛弃了传统(如 C++/Java)的异常(Exceptions)机制,转而使用基于枚举(Enums)的类型来处理可恢复的错误。这种方式使得错误在类型系统中变得可见和可控。本文将深入探讨 Rust 的错误处理哲学、?(问号操作符)的精妙脱糖(Desugar)过程,并对比thiserror(用于库)和anyhow(用于应用)两大主流错误处理库的最佳实践,帮助您构建健壮且易于维护的 Rust 程序。错误处理是健壮软件的灵魂。C 语言:依赖错误码(Error Codes,如-1)和全局errno。

weixin_75201252的博客 1015

RustTraining 之 C/C++ 开发者错误处理最佳实践:Option、Result、`?` 运算符与 thiserror 生产级模式

> **本文定位**:面向从 C/C++ 迁移到 Rust 的开发者,基于 RustTraining 项目中《Rust for C/C++ Programmers》一书的第 9 章配套深度篇([c-cpp-book/src/ch09-1-error-handling-best-practices.md](https://link.gitcode.com/i/8c7f72d209b6075f48c1

gitblog_00298的博客 267

Rust错误处理模式与生产级代码组织:让每一步失败都有迹可循

// 配置解析模块的错误类型/// 为什么用thiserror而不是手动impl Display/From?/// 因为样板代码太多,thiserror用宏自动生成,/// 减少手写错误,也更容易维护#[error("配置文件不存在: {path}")]#[source]},#[error("配置格式错误: {message}")]#[source]},#[error("缺少必要配置项: {key}")]

no1coder的博客 4284

Rust错误处理:anyhow与thiserror实战指南

错误处理是编程语言的核心机制之一,Rust通过Result类型实现了显式错误处理范式。不同于传统的异常机制,这种设计使错误成为API的显式部分,提高了代码可靠性和可维护性。anyhow和thiserror作为Rust生态中的明星错误处理库,分别针对应用程序和库开发场景优化:thiserror通过过程宏简化了符合Rust惯用法的自定义错误类型定义,支持完整的错误信息链;anyhow则提供了通用的Error类型和上下文添加功能,大幅简化应用程序的错误处理逻辑。这两个库都深度集成了Rust的trait系统和?操作

weixin_30896825的博客 403

Rust 错误处理的 7 月总结:从 panic 到优雅降级的完整进化路径回顾

/ 等级四:用 thiserror 派生宏自动生成样板代码// 只需定义错误变体和错误消息,Display/Error/From 全部自动实现/// IO 操作失败#[error("IO 错误:{0}")]/// JSON 解析失败#[error("JSON 解析错误:{0}")]/// HTTP 请求错误#[error("HTTP 请求失败:{0}")]/// 业务逻辑错误#[error("业务错误 [{code}]:{message}")]Business {},/// 用。

no1coder的博客 4478

Rust 中的错误处理模式:从 Result 到 anyhow 的工程实践

Rust通过类型系统构建了一套独特的错误处理机制。Result类型将错误转化为显式值传递,?运算符简化错误传播,thiserror减少样板代码,而anyhow则在应用层提供简化处理。这种设计实现了零成本抽象,在保持高性能的同时强制开发者处理所有可能的错误路径。最佳实践是:库代码使用thiserror定义精确错误类型,应用层采用anyhow简化处理。Rust的错误处理体系虽然需要更多前期设计,但能显著提升程序的可靠性和失败模式清晰度,在性能与健壮性之间取得完美平衡。

Zjm94888的博客 896
上一篇: 安全并发不是梦:Rust 并发编程从线程模型到 Tokio 异步实战
下一篇: AI 编译技术:从 MLIR 到 TVM,AI 模型如何变成高效机器码?
第一程序员
博客等级 码龄1年 1万+粉丝 1807原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值