18、Web API与客户端集成开发指南

Web API与客户端集成开发指南

在现代软件开发中,Web API与客户端的集成是实现高效、交互性强的应用程序的关键环节。本文将详细介绍如何创建API控制器、SignalR中心,如何发布Web API,以及如何构建客户端应用程序以实现与Web API的集成。

创建API控制器

为了快速创建用于处理订单和批次数据的控制器,我们可以使用脚手架来生成DbContext和一组REST操作,实现数据的创建、读取、更新、删除和列表展示。以下是创建 OrderController 的具体步骤:
1. 添加项目引用 :在添加控制器之前,需要向 ProductionModel 项目添加引用,以便脚手架能够识别我们要使用的实体。具体操作是右键单击“References”文件夹,选择“Add Reference”,在“Solution | Projects”选项卡中勾选 ProductionModel ,然后点击“OK”。
2. 打开添加脚手架对话框 :右键单击“Controllers”文件夹,选择“Add | Controller”,打开“Add Scaffold”对话框。
3. 选择控制器类型 :在对话框中,选择“Web API 2 Controller with actions, using Entity Framework”,然后点击“Add”,此时会弹出“Add Controller”对话框。
4. 选择模型和数据上下文 :从“Model class”选择器中选择订单模型,然后点击“Data context class”旁边的“+”按钮创建新的 DbContext (如果是创建 BatchController ,则从选择器中选择现有的 DbContext )。
5. 配置数据上下文 :可以在此处更改数据上下文的名称,也可以使用默认名称,然后点击“Add”查看详细信息。
6. 启用异步控制器操作 :勾选“Use async controller actions”,这将标记异步操作,允许我们在 DbContext 中使用异步方法。点击“Add”生成控制器。
7. 查看生成的文件 :查看 OrdersController ManagementWebApiDataContext 以及 Web.config 中的额外EF部分,然后开始进行修改以集成现有的数据库。
8. 更改数据库连接字符串 :在 Web.config 文件中,找到数据库连接字符串,将其替换为指向现有SQL Express数据库的连接字符串,以便与订单处理器共享数据。示例代码如下:

<connectionStrings>
  <add name="ManagementWebApiContext"  
    connectionString="Data Source=localhost;Initial  
      Catalog= AzureBakeryProduction;Integrated  
        Security=True"  
          providerName="System.Data.SqlClient" />
</connectionStrings>
  1. 禁用EF代理创建 :在 ManageMentWebApiDataContext 构造函数中添加以下代码,以禁用EF代理创建:
public ManagementWebApiContext() :  
  base("name=ManagementWebApiContext")
{
    base.Configuration.ProxyCreationEnabled = false;
}
  1. 修改 GetOrders 方法 :修改 OrdersController GetOrders 方法,使其仅返回状态为“Open”的订单:
public IQueryable<Order> GetOrders()
{
    return db.Orders
        .Where(o => o.Status == OrderStatus.Open)
}
  1. 测试控制器 :由于控制器目前未使用 Authorize 属性,因此可以进行快速测试。调试Web API项目,然后在浏览器中访问 /api/orders (根据实际的IIS Express端口进行调整),例如: https://localhost:44303/api/orders
  2. 添加授权属性 :在确认控制器正常工作后,为控制器类添加 Authorize 属性,以确保安全性:
[Authorize]
public class OrdersController : ApiController
  1. 创建 BatchController :使用相同的步骤创建 BatchController ,但使用之前创建的 DbContext

为了进一步测试控制器,可以使用浏览器进行大多数HTTP GET操作,也可以使用Fiddler(http://www.telerik.com/fiddler)或cURL(http://curl.haxx.se/)来构造其他操作,如POST或PUT,这些操作需要在请求体中插入数据。

创建SignalR中心

为了允许客户端在用户更改订单和批次状态时进行更新,并接收系统中其他客户端的更新,我们将实现一个SignalR中心。具体步骤如下:
1. 安装NuGet包 :在NuGet包管理器控制台中依次输入以下命令,安装所需的包:

Install-Package Microsoft.AspNet.SignalR
Install-Package Microsoft.AspNet.SignalR.ServiceBus
install-package windowsazure.servicebus
  1. 修改服务总线连接字符串 :修改由 Microsoft.AspNet.SignalR.ServiceBus 包添加的 Microsoft.ServiceBus.ConnectionString 应用设置,并添加服务总线命名空间的ACS密钥(从门户获取)。连接字符串示例如下:
<add key="Microsoft.ServiceBus.ConnectionString"  
  value="Endpoint=sb://azurebakery.servicebus.windows.net/; 
    SharedSecretIssuer=owner;SharedSecretValue= 
      vxaQFgh8zGFtsqnAemCcv/NTCtLNM2qhYslQq7TIQsI=" />
  1. 修改 Startup :在 App_Start/Startup.Auth.cs 文件中修改 Startup 类,示例代码如下:
using Microsoft.AspNet.SignalR;
using Microsoft.Owin;
using Microsoft.Owin.Security.ActiveDirectory;
using Owin;
using System.Configuration;

[assembly: OwinStartup(typeof(ManagementWebApi.Startup))]
namespace ManagementWebApi
{
    public partial class Startup
    {
        public void ConfigureAuth(IAppBuilder app)
        {
            app.UseWindowsAzureActiveDirectoryBearerAuthentication(
          new WindowsAzureActiveDirectoryBearerAuthenticationOptions
                {
                    Audience = ConfigurationManager. 
                    AppSettings["ida:Audience"],
                    Tenant = ConfigurationManager. 
                    AppSettings["ida:Tenant"]
                });
            var connectionString = ConfigurationManager. 
            AppSettings["Microsoft.ServiceBus.ConnectionString"];
            GlobalHost.DependencyResolver. 
            UseServiceBus(connectionString, "ManagementApi");
            app.MapSignalR();
        }
    }
}
  1. 添加中心类 :在项目中添加一个中心类,例如将其放在名为“SignalR”的解决方案文件夹下。右键单击该文件夹,选择“Add | New Item”,从“SignalR”选项卡中选择“SignalR Hub Class (v2)”,命名后点击“OK”。
  2. 添加更新方法 :在中心类中添加 UpdateOrder UpdateBatch 方法,用于客户端调用并更新所有连接的客户端。示例代码如下:
[Authorize]
public class ManagementHub : Hub
{
    private readonly DataService _dataService = new  
      DataService();
    private readonly MessagingService _messagingService =  
      new MessagingService();
    public void UpdateOrder(Order order)
    {
        this._dataService.UpdateOrder(order);
        this._messagingService.UpdateOrder(order);
        Clients.All.updateOrder(order);
    }
    public void UpdateBatch(Batch batch)
    {
        this._dataService.UpdateBatch(batch);
        Clients.All.updateBatch(batch);
    }
}
发布Web API

可以使用常规的网站发布过程将Web API直接发布到创建时预配的网站,但需要特别注意发布设置。具体步骤如下:
1. 配置发布设置 :勾选“Enable Organizational Authentication”,输入AD租户域名,确保设置Azure数据库的数据库连接字符串,并勾选“Use this connection string at runtime (update destination web.config)”。
2. 输入登录凭据 :发布时,需要输入AD租户的登录凭据。
3. 查看发布结果 :发布完成后,会看到网站已创建,同时在AD租户工作区的“APPLICATIONS”选项卡中会出现一个新的应用程序。 Web.config 文件中的 ida:Audience 设置也会更新为新Azure Web API应用程序的ID。

修改Web API AD清单

在为客户端创建AD应用程序之前,需要修改本地和Azure ManagementWebApi 应用程序的清单,以便其他应用程序可以使用AD授权获得访问权限。具体步骤如下:
1. 下载清单文件 :在门户中,点击AD应用程序工具栏上的“MANAGE MANIFEST | Download Manifest”,下载JSON清单文件。
2. 修改清单文件 :打开下载的JSON清单文件,将空的 "appPermissions": [] 部分替换为以下代码,然后保存文件:

"appPermissions": [
    {
      "claimValue": "user_impersonation",
      "description": "Allow the application full access to  
      the service on behalf of the signed-in user",
      "directAccessGrantTypes": [],
      "displayName": "Have full access to the service",
      "impersonationAccessGrantTypes": [
        {
          "impersonated": "User",
          "impersonator": "Application"
        }
      ],
      "isDisabled": false,
      "origin": "Application",
      "permissionId":  
        "B4B3BA55-0770-47D0-A447-C55BB6A371DF",
      "resourceScopeType": "Personal",
      "userConsentDescription": "Allow the application full  
       access to the service on your behalf",
      "userConsentDisplayName": "Have full access to the  
       service"
    }
  ]
  1. 上传清单文件 :在门户中,点击“MANAGE MANIFEST | Upload Manifest”,上传保存后的清单文件。
  2. 重复操作 :对第二个清单重复上述步骤。
添加客户端应用程序到AD

在客户端应用程序连接到Web API之前,需要将其添加到Azure AD并授予访问Web API应用程序的权限。具体步骤如下:
1. 导航到AD工作区 :在Azure门户中导航到AD工作区。
2. 添加应用程序 :点击“APPLICATIONS”工具栏上的“ADD”,选择“Add an application my organization is developing”。
3. 配置应用程序信息 :输入应用程序名称,选择“NATIVE CLIENT APPLICATION”,点击“下一步”箭头。
4. 输入重定向URI :输入一个有效的重定向URI(用于OAuth2请求后的重定向,此实现中未使用),然后点击“勾选”按钮完成。
5. 添加权限 :滚动到“CONFIGURATION”选项卡底部的“permissions to other applications”部分,为 ManagementWebApi 添加新的权限,选择“Have full access to the service”作为委托权限。
6. 更改默认权限 :将默认的“Windows Azure Active Directory”权限的委托权限更改为包括“Access your organization’s directory (preview)”。
7. 添加Azure管理API权限 :添加Azure管理API的权限。
8. 保存设置 :点击工具栏上的“SAVE”完成设置。

构建客户端应用程序

为了显示批次和订单并允许更改其状态,我们将创建一个WPF客户端应用程序。使用MVVM Light帮助实现简洁的MVVM模式,并创建多个数据服务,使用Azure AD身份验证从API获取数据。

准备WPF项目

创建WPF应用程序并安装所需的NuGet包,具体步骤如下:
1. 添加WPF项目 :在解决方案中添加一个名为 ManagementApplication 的WPF项目。
2. 安装MVVM Light :在NuGet包管理器控制台中输入以下命令安装MVVM Light:

install-package mvvmlight
  1. 安装AD身份验证包 :输入以下命令安装 Microsoft.IdentityModel.Clients.ActiveDirectory 包:
install-package Microsoft.IdentityModel.Clients.ActiveDirectory
  1. 安装JSON.NET :输入以下命令安装JSON.NET:
install-package newtonsoft.json
  1. 安装SignalR客户端包 :输入以下命令安装SignalR客户端包:
Install-package Microsoft.AspNet.SignalR.Client
  1. 添加项目引用 :右键单击“References”文件夹,选择“Add Reference”,在“Solution | Projects”选项卡中勾选 ProductionModel ,然后点击“OK”。同样的方法添加 System.Configuraton System.Net.Http 的引用。
  2. 添加Token设置 :在项目的 Settings.settings 文件中,添加一个名为 Token 的字符串设置,用于存储用户的身份验证令牌。
  3. 添加 appSettings :在 App.config 中添加以下 appSettings 块,并添加注释以帮助理解和记忆:
<appSettings>
  <!-- AD Tenant -->
  <add key="ida:Tenant" value="azurebakery.onmicrosoft.com" />

  <!-- The target api AD application APP ID (get it from  
    config tab in portal) -->
  <!-- Local -->
  <add key="ida:Audience"  
    value="https://azurebakery.onmicrosoft.com/ManagementWebApi" 
/>
  <!-- Azure -->
  <!-- <add key="ida:Audience"  
    value="https://azurebakery.onmicrosoft.com/ 
      WebApp-azurebakeryproduction.azurewebsites.net" /> -->

  <!-- The client id of THIS application (get it from  
    config tab in portal) -->
  <add key="ida:ClientID" value= 
    "1a1867d4-9972-45bb-a9b8-486f03ad77e9" />

  <!-- Callback URI for OAuth workflow -->
  <add key="ida:CallbackUri"  
    value="https://azurebakery.com" />

  <!-- The URI of the Web API -->
  <!-- Local -->
  <add key="serviceUri" value="https://localhost:44303/" />
  <!-- Azure -->
  <!-- <add key="serviceUri" value="https://azurebakeryproduction.
azurewebsites.net/" />  
  -->
</appSettings>
  1. 添加ViewModelLocator :在 App.xaml Application.Resources 中添加MVVM Light的 ViewModelLocator
<Application.Resources>
    <vm:ViewModelLocator x:Key="Locator"  
      d:IsDataSource="True" xmlns:vm= 
        "clr-namespace:AzureBakery.Production. 
           ManagementApplication. 
             ViewModel" />
  </Application.Resources>
  1. 绑定数据上下文 :在 MainWindow.xaml 中,将 DataContext 绑定到 ViewModelLocator Main 属性:
<Window x:Class="AzureBakery.Production.ManagementApplication. 
  MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/ 
          presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        DataContext="{Binding Source={StaticResource  
          Locator}, Path=Main}"
        Title="Production Management Application"  
          Height="350" Width="525">
创建身份验证基类

由于Web API和SignalR中心使用Azure AD身份验证,我们将创建一个服务来与两者进行交互,并创建一个公共基类以确保所有请求都经过身份验证。以下是 AzureAdAuthBase 类的代码:

using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System;
using System.Configuration;
using System.Diagnostics;
using System.Net;

namespace AzureBakery.Production.ManagementApplication.Services
{
    public abstract class AzureAdAuthBase
    {
        protected AuthenticationResult Token = null;
        protected readonly string ServiceUri = null;

        protected AzureAdAuthBase()
        {
            this.ServiceUri =  
              ConfigurationManager.AppSettings["serviceUri"];
#if DEBUG
            ServicePointManager.ServerCertificateValidationCallback +=  
            (se, cert, chain, sslerror) => true;
#endif
        }

        protected bool Login()
        {
            var tenantId =  
              ConfigurationManager.AppSettings["ida:Tenant"];
            var resourceId =  
              ConfigurationManager.AppSettings["ida:Audience"];
            var clientId =  
              ConfigurationManager.AppSettings["ida:ClientID"]; 
            var callback = new  
              Uri(ConfigurationManager.AppSettings["ida:CallbackU 
              ri"]);
            var authContext = new  
              AuthenticationContext(string.Format("https://login. 
              windows.net/{0}", tenantId));

            if(this.Token == null)
            {
                var token = Properties.Settings.Default.Token;
                if (!string.IsNullOrWhiteSpace(token))
                    this.Token = AuthenticationResult. 
                    Deserialize(token);
            }

            if (this.Token == null)
            {
                try
                {
                    this.Token =  
                      authContext.AcquireToken(resourceId,  
                        clientId, callback);
                }
                catch(Exception ex)
                {
                    Debug.WriteLine(ex.ToString());
                    return false;
                }
            }
            else if(this.Token.ExpiresOn < DateTime.UtcNow)
            {
                this.Token =  
                  authContext.AcquireTokenByRefreshToken(this.Token. 
                  RefreshToken, clientId);
            }

            if (this.Token != null && this.Token.ExpiresOn >  
              DateTime.UtcNow)
            {
                Properties.Settings.Default.Token =  
                  this.Token.Serialize(); 
                Properties.Settings.Default.Save();
                return true;
            }

            this.Token = null;
            Properties.Settings.Default.Token = null;
            Properties.Settings.Default.Save();
            return false;
        }
    }
}

该类使用 AuthenticationContext.AquireToken 方法启动内置的登录对话框,处理OAuth2工作流,并在登录成功时返回身份验证令牌。令牌存储在用户设置中,并在必要时进行刷新,因此用户不必每次使用应用程序时都登录。派生服务类每次调用服务时可以调用 Login 方法,以检查用户是否已登录以及是否有有效的令牌可供使用。

创建数据服务

我们将创建一个 DataService 类,该类继承自刚刚创建的 AzureAdAuthBase 类,并使用AD身份验证从Web API服务获取数据。首先,创建一个通用的辅助方法 GetData<T> ,该方法使用 HttpClient 类调用API的GET操作,并将返回的JSON对象反序列化为.NET类型的对象 T

private async Task<T> GetData<T>(string action)
{
    if (!base.Login())
        return default(T);

    var authHeader = this.Token.CreateAuthorizationHeader();
    var client = new HttpClient();
    var uri = string.Format("{0}{1}", this.ServiceUri,  
      string.Format("api/{0}", action));
    var request = new HttpRequestMessage(HttpMethod.Get, uri);
    request.Headers.TryAddWithoutValidation("Authorization",  
      authHeader);

    var response = await client.SendAsync(request);
    var responseString = await response.Content.ReadAsStringAsync();
    var data = await Task.Factory.StartNew(() =>  
      JsonConvert.DeserializeObject<T>(responseString));

    return data;
}

有了这个方法后,我们可以快速创建获取订单和批次数据的方法:

public async Task<IEnumerable<Order>> GetOrders()
{
    return await this.GetData<IEnumerable<Order>>("orders");
}

public async Task<IEnumerable<Batch>> GetBatches()
{
    return await this.GetData<IEnumerable<Batch>>("batches");
}

该服务实现了 IDataService 接口,并在 ViewModelLocator 类中注册,以便注入到视图模型中:

SimpleIoc.Default.Register<IDataService, DataService>();
创建SignalR服务

我们将创建另一个继承自 AzureAdAuthBase 类的服务 ManagementService ,该服务将更新后的订单发送到SignalR中心,并接收来自其他客户端的更新,以实时更新UI。以下是 ManagementService 类的代码:

private IHubProxy _proxy = null;
public event EventHandler<Order> OrderUpdated;
public event EventHandler<Batch> BatchUpdated;

public ManagementService()
{
}

public async Task Register()
{
    if (!this.Login())
        return;

    var authHeader = this.Token.CreateAuthorizationHeader();
    var cnString = string.Format("{0}signalr", base.ServiceUri);
    var hubConnection = new HubConnection(cnString, useDefaultUrl:  
      false);
    this._proxy = hubConnection.CreateHubProxy("managementHub");
    hubConnection.Headers.Add("Authorization", authHeader);

    this._proxy.On<Order>("updateOrder", order =>
    {
        this.OnOrderUpdated(order);
    });

    this._proxy.On<Batch>("updateBatch", batch =>
    {
        this.OnBatchUpdated(batch);
    });

    await hubConnection.Start();
}

通过以上步骤,我们完成了Web API与客户端的集成开发,实现了订单和批次数据的管理、实时更新以及安全访问。

Web API与客户端集成开发指南

总结与最佳实践

在完成Web API与客户端的集成开发后,我们对整个过程进行总结,并分享一些最佳实践,以帮助开发者更好地应对类似的开发任务。

开发流程总结
开发步骤 关键操作
创建API控制器 使用脚手架生成控制器和REST操作,配置数据库连接,添加授权属性
创建SignalR中心 安装相关NuGet包,配置服务总线连接,添加中心类和更新方法
发布Web API 配置发布设置,输入AD租户登录凭据
修改Web API AD清单 下载、修改并上传清单文件,授予其他应用程序访问权限
添加客户端应用程序到AD 配置应用程序信息,添加权限
构建客户端应用程序 创建WPF项目,安装所需NuGet包,实现MVVM模式,创建数据服务和SignalR服务
最佳实践建议
  • 安全性 :始终使用授权属性保护API和SignalR中心,确保只有经过身份验证的用户可以访问。同时,对敏感数据进行加密处理,如存储在用户设置中的身份验证令牌。
  • 性能优化 :使用异步方法和任务并行处理,提高应用程序的响应性能。例如,在数据服务中使用 async await 关键字,避免阻塞线程。
  • 代码可维护性 :将数据库和消息传递逻辑抽象到服务类中,使代码更易于阅读和维护。同时,遵循MVVM模式,将视图和业务逻辑分离。
  • 错误处理 :在关键代码中添加适当的错误处理机制,如在登录方法中捕获异常并记录错误信息,确保应用程序的稳定性。
未来拓展方向

随着技术的不断发展,Web API与客户端的集成开发也有许多可以拓展的方向。以下是一些可能的拓展方向,供开发者参考。

多平台支持

目前我们开发的是WPF客户端应用程序,可以考虑拓展到其他平台,如移动应用(iOS和Android)、Web应用等。可以使用跨平台开发框架,如Xamarin、React Native等,实现一次开发,多平台部署。

微服务架构

将现有的Web API拆分为多个微服务,每个微服务负责一个特定的业务功能。这样可以提高系统的可扩展性和可维护性,同时便于团队协作开发。

实时数据分析

结合实时数据分析技术,对订单和批次数据进行实时监控和分析。例如,使用Azure Stream Analytics对数据进行实时处理,生成实时报表和预警信息。

人工智能与机器学习

引入人工智能和机器学习技术,对订单和批次数据进行预测和优化。例如,使用机器学习算法预测订单需求,优化生产计划。

常见问题解答

在开发过程中,开发者可能会遇到一些常见的问题。以下是一些常见问题的解答,希望能帮助开发者快速解决问题。

问题1:API请求返回401 Unauthorized错误
  • 原因 :可能是授权属性配置不正确,或者身份验证令牌过期。
  • 解决方法 :检查API控制器和SignalR中心是否添加了 Authorize 属性,确保身份验证令牌有效。可以在登录方法中添加日志记录,查看令牌获取和刷新的情况。
问题2:SignalR连接失败
  • 原因 :可能是服务总线连接配置不正确,或者网络问题。
  • 解决方法 :检查 Microsoft.ServiceBus.ConnectionString 配置是否正确,确保服务总线命名空间和ACS密钥有效。同时,检查网络连接是否正常,尝试重启服务。
问题3:数据服务返回空数据
  • 原因 :可能是数据库连接配置不正确,或者API接口返回的数据格式不匹配。
  • 解决方法 :检查数据库连接字符串是否正确,确保数据库服务正常运行。同时,检查API接口返回的数据格式,确保与客户端代码中的反序列化方法匹配。
总结

通过本文的介绍,我们详细了解了Web API与客户端集成开发的整个流程,包括创建API控制器、SignalR中心,发布Web API,构建客户端应用程序等。同时,我们分享了开发过程中的最佳实践、未来拓展方向和常见问题解答。希望这些内容能帮助开发者更好地完成Web API与客户端的集成开发任务,实现高效、安全、稳定的应用程序。

在实际开发中,开发者可以根据具体的业务需求和技术栈,对本文介绍的方法和步骤进行适当的调整和优化。同时,不断学习和掌握新的技术和工具,提高自己的开发能力和水平。

最后,祝愿开发者在Web API与客户端集成开发的道路上取得成功!

基于径向基函数神经网络RBFNN的自适应滑模控制学习(Matlab代码实现)内容概要:本文介绍了基于径向基函数神经网络(RBFNN)的自适应滑模控制方法,并提供了相应的Matlab代码实现。该方法结合了RBF神经网络的非线性逼近能力和滑模控制的强鲁棒性,用于解决复杂系统的控制问题,尤其适用于存在不确定性和外部干扰的动态系统。文中详细阐述了控制算法的设计思路、RBFNN的结构权重更新机制、滑模面的构建以及自适应律的推导过程,并通过Matlab仿真验证了所提方法的有效性和稳定性。此外,文档还列举了大量相关的科研方向和技术应用,涵盖智能优化算法、机器学习、电力系统、路径规划等多个领域,展示了该技术的广泛应用前景。; 适合人群:具备一定自动控制理论基础和Matlab编程能力的研究生、科研人员及工程技术人员,特别是从事智能控制、非线性系统控制及相关领域的研究人员; 使用场景及目标:①学习和掌握RBF神经网络滑模控制相结合的自适应控制策略设计方法;②应用于电机控制、机器人轨迹跟踪、电力电子系统等存在模型不确定性或外界扰动的实际控制系统中,提升控制精度鲁棒性; 阅读建议:建议读者结合提供的Matlab代码进行仿真实践,深入理解算法实现细节,同时可参考文中提及的相关技术方向拓展研究思路,注重理论分析仿真验证相结合。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值