Taro 小程序开发大型实战(八):尝鲜 LeanCloud Serverless 云服务

后台回复“z”加群,一起交流技术!

欢迎继续阅读《Taro 小程序开发大型实战》系列,前情回顾:

在上两篇文章中,我们讲解了使用微信小程序云作为我们的小程序后台,然后我们跑通了我们的注册登录、创建帖子、获取帖子列表、获取帖子详情的全栈流程,如果只想了解微信小程序的全栈开发流程的话,之前的文章已经足够了,如果还想了解跨端开发全栈开发流程的话(当然用 Taro 的同学估计也比较期待跨端的全栈开发流程,手动滑稽)接下来这篇文章就是你的菜了????

首先我们先来看一下最终的运行效果:

如果你不熟悉 Redux,推荐阅读我们的《Redux 包教包会》系列教程:

如果你希望直接从这一步开始,请运行以下命令:

git clone -b leancloud-start https://github.com/tuture-dev/ultra-club.git
cd ultra-club

本文所涉及的源代码都放在了 Github[11] 上,如果您觉得我们写得还不错,希望您能给❤️这篇文章点个在看+Github仓库加星❤️哦~

此教程属于 React 前端工程师学习路线[12]的一部分,欢迎来 Star 一波,鼓励我们继续创作出更好的教程,持续更新中~

在这一篇文章中,我们将接入 LeanCloud Serverless 服务,它类似微信小程序云,只不过它没有平台属性,所有的端都可以便捷访问,相信你已经迫不及待了,让我们马上开始吧????!

小程序接入外网的流程

因为小程序是封装在一些巨型 App 应用里的沙盒环境之内,所以对于接入外站的服务需要一些特殊的流程,我们在这里总结一下:

  • 注册外站服务的账号

  • 找到对应的小程序开发接入指南

  • 获取对应的接入地址,将接入地址填入小程序后台的白名单列表

  • 在小程序实际接入,并进行测试

在这篇文章中,我们的外站特指 LeanCloud Serverless 云服务,小程序特指微信小程序和支付宝小程序。

好的,了解了流程之后,我们现在来走一遍流程来将我们的 LeanCloud 接入到微信/支付宝小程序。

注册外站服务的账号

访问 LeanCloud 网址:点我访问[13],完成注册登录流程。

找到对应的小程序开发接入指南

这里我们找到 LeanCloud 微信/QQ 小程序接入指南:点我访问[14]

注意

LeanCloud 没有提供支付宝的接入指南,但是小程序接入指南都基本类似,我们会一一讲解这一流程。

获取对应的接入地址

LeanCloud 已经有详细的链接提示如何接入:

对于支付宝小程序的白名单配置地址如下:点我访问[15]

在小程序实际接入,并进行测试

最后我们需要进行小程序的实际接入,因为 LeanCloud 并没有提供支付宝小程序的 SDK 包,这里对于支付宝小程序我们使用 LeanCloud 提供的 REST 接口进行访问,具体地址如下:点我访问[16]

定义 LeanCloud 相关的辅助函数

对于接入 LeanCloud,我们需要在应用中做一系列初始化环境的准备,在 src/api/ 文件夹下创建 utils.js 文件,并在其中编写内容如下:

const API_BASE_URL = ''

const LOGIN_URL = `${API_BASE_URL}/1.1/functions/login`
const CREATE_POST_URL = `${API_BASE_URL}/1.1/functions/createPost`
const GET_POST_URL = `${API_BASE_URL}/1.1/functions/getPost`
const GET_POSTS_URL = `${API_BASE_URL}/1.1/functions/getPosts`

const HEADER = {
  'X-LC-Prod': 1,
  'X-LC-Id': '',
  'X-LC-Key': '',
}

const convertUserFormat = user => {
  const _id = user.objectId
  delete user.objectId

  return { ...user, _id }
}

const convertPostFormat = post => {
  const _id = post.objectId
  const user = convertUserFormat(post.user)

  delete post.objectId
  delete post.user

  return { ...post, _id, user }
}

const convertPostsFormat = posts => {
  const convertedPosts = posts.map(post => convertPostFormat(post))

  return convertedPosts
}

export {
  LOGIN_URL,
  HEADER,
  CREATE_POST_URL,
  GET_POST_URL,
  GET_POSTS_URL,
  convertPostsFormat,
  convertPostFormat,
  convertUserFormat,
}

可以看到,上面的代码主要分为四个部分:

  • 定义云函数的 REST URL,LeanCloud 的云函数 REST URL 的格式类似这样:https://API_BASE_URL/1.1/functions/functionName,其中 API_BASE_URL 可以通过文档获取:点我访问[17];而 functionName 即为我们需要调用的云函数名字。这里我们定义了 API_BASE_URL ,我们给了空字符串,读者可以根据 LeanCloud 给与的 Base URL 替代空字符串;同样我们定义了四个云函数,分别代表登录、创帖、查询帖子列表、查询单个帖子,具体的云端云函数我们将在后面定义。

  • 第二个部分即为向 LeanCloud 服务器发送 REST 请求时需要携带的请求头部,这个也可以在文档里给出:点我访问[18];这里也需要用户用自己的内容来替换上面的空字符串。

  • 第三个部分则为两个辅助转换格式的函数,主要用于将 LeanCloud 数据库格式的数据与现有的微信小程序数据库格式的数据兼容。

  • 第四个部分为导出这些定义的内容,供其它模块使用。

提示

上面的 API_BASE_URLHEADER 都需要用户在登录的情况下访问给出的地址才能获取到。

在 LeanCloud 上面创建数据库表

登录 LeanCloud 控制台,在左边栏的 存储 > 结构化数据 可以看到创建 Class 的按钮,我们可以通过创建一个 Class 来创建一张数据库表:

可以看到我们创建了两张表:PostMyUser,一个存放和帖子相关的数据,一个存放和用户相关的数据。其中 MyUser 类似之前我们在微信小程序数据库表时的 user 表。

定义 MyUser 字段

如图之前在微信小程序数据库表创建时一样,我们同样为 MyUser 定义如下的字段:

  • avatar

  • nickName

至于读者看到的其它字段都是 LeanCloud 默认创建且自动更新的字段,用户不可以操作。

定义 Post 字段

同样和之前在微信小程序里面创建 post 一样,我们给 Post 定义如下字段:

  • content

  • title

  • user

眼尖的同学可能注意到了,这里的 user 字段是一个 Pointer 类型,它是 LeanCloud 数据表独有的引用类型,类似关系数据库里面的外键,即存一个指针,之后获取数据的时候可以便捷的获取对应的 user 数据。

关于默认 Class 的解释

这里有些读者可能有疑问,为什么还有一些多余的表了?这些以下划线开头的 Class 其实是 LeanCloud 默认创建的,不允许删除,用于 LeanCloud SDK 封装一系列常用且复杂的应用功能,供用户快速搭建 App/网站/小程序原型,比如类似微信的朋友圈功能,LeanCloud 提供开箱即用的逻辑,你可以直接调用。

并且,类似 _UserUser Class 其实是引用自同一个 Class,所以不能创建和 LeanCloud 默认的类具有同名且不带前缀下划线的类,比如 UserFile 类就不能创建,所以这里我们创建了 MyUser 类,这样不用去考虑 _User 类本身存在的一些细节限制。

在 LeanCloud 上创建云函数

在上一步里面,我们在小程序代码里创建了和 LeanCloud 有关的逻辑代码,其中我们创建了四个云函数,现在我们要创建对应这四个云函数的实际云函数。

注册并登陆 LeanCloud 之后,点击左边栏的 云引擎 > 部署 可以看到类似下面的界面:

LeanCloud 提供给我们在线创建和编写云函数的方便界面,使得我们不用自己创建本地服务器代码和配置部署和运维过程,大大加速了程序的开发过程。

接下来,我们将遵循以下三步走的方式来进行 LeanCloud 云函数的开发:

  • 创建云函数

  • 部署

  • 在小程序端进行调试

创建 User 逻辑 云函数

点击界面里面的创建按钮,会看到如下的界面:

可以看到上图分为如下几个部分:

  • 选择我们要创建的函数类型,主要有三类,这里我们选择 Function ,这也是默认选择的类型,其余两类读者有兴趣可以自行探索,这里我们不展开讲。

  • 接着就是定义你的函数名,这里我们填入 login

  • 接着就是编写函数体,它是一个 Node.js 函数,我们只需要编写对应的 Node.js 处理逻辑就可以了。

  • 最后我们可以对这个函数写一点注释,方便日后回顾,这里我们选择不填入。

好的,了解了创建函数的弹出层之后,我们填入我们需要创建的 login 函数体如下:

const { userInfo } = request.params

const query = new AV.Query('MyUser')
query.equalTo('nickName', userInfo.nickName)
const users = await query.find()

if (users.length > 0) {
    return users[0]
} else {
    const MyUser = AV.Object.extend('MyUser')
    const myUser = new MyUser()

    const { nickName, avatar } = userInfo
    myUser.set('nickName', nickName)
    myUser.set('avatar', avatar)

    const user = await myUser.save()
    return user
}

可以看到我们上面的内容主要改动有四处:

  • request.params 取到对应的请求体数据 userInfo,这决定了我们之后在小程序端调用 LeanCloud 云函数时,要使用 POST 的方式。

  • 接着我们使用了 LeanCloud 的查询 SDK 操作

    • 首先通过 new AV.Query 新建一个对 MyUser Class 的查询请求。注意到这里的 AV 接口是 LeanCloud 暴露给我们的默认接口,可以通过这个接口操作 LeanCloud 的各种资源。

    • 接着通过 equalTo 进行条件过滤,这里我们查询 nickNameuserInfo.nickName 的用户。

    • 通过 query.find() 来提交查询操作,注意到这里我们使用了 await 关键字,那是因为默认包裹云函数提的是一个 async 函数,允许我们方便的执行异步流程。

  • 接着我们对查询到的数据进行判断,如果 users.length > 0 表示存在用户,那么我们返回查询到的第一个用户;如果不存在,我们执行创建用户操作,再返回创建的用户。

  • 创建用户的操作主要是如下几个步骤:

    • AV.Object.extend 对应的 Class 来获取对应的类

    • 实例化这个类获取一个对象

    • 设置这个对象的属性,这里通过 set(key, value) 的方式设置

    • 通过对象的 save 方法进行保存,保存到 LeanCloud 数据库

注意

这里我们只用到了 LeanCloud 的一些简单操作,具体的详情可以查看官方文档,官方文档撰写了非常完备的操作指南:点我查看[19]

部署

按照上面的步骤编写完 login 云函数之后,点击保存,此时我们的云函数就编写好了,但是我们目前在小程序端还无法调用它,因为我们还需要一个部署的操作。

在 LeanCloud 上面进行云函数的部署也同样简单,只需要点击一个按钮:

点我之后,等待部署提示,过一会应该就会提示部署成功,这个时候我们就可以在小程序端通过 REST API 访问了。

在小程序端进行调试

我们这里使用 LeanCloud 主要是让支付宝小程序也可以成为全栈应用,对应我们之前提到的 H5,因为 Taro 目前对 H5 的支持还不完善,我们决定放弃对 H5 的讲解, 但是这并不代表 Taro 存在缺陷,只能说它是一个很有潜力的框架,成长还需要实践,并且跨端小程序是它诞生的重点,将精力放在主要的路径上是值得提倡的,Taro 在近期发布了 Taro Next,支持使用 Vue/React/Nerve 开发跨端小程序,笔者这里推荐读者可以尝试一波:点我跳转[20]

因为我们首先创建了 login 的云函数,所以我们需要改进一下我们的支付宝登录的按钮逻辑,打开 src/components/AlipayLoginButton/index.js 对其中的内容作出如下的修改:

import { useDispatch } from '@tarojs/redux'

import './index.scss'
import { LOGIN } from '../../constants'

export default function AlipayLoginButton(props) {
  const [isLogin, setIsLogin] = useState(false)...      userInfo = JSON.parse(userInfo.response).response
      const { avatar, nickName } = userInfo

      dispatch({
        type: LOGIN,
        payload: {
          userInfo: {
            avatar,
            nickName,
          },
        },
      })
    } catch (err) {

可以看到,上面的内容改动主要有三处:

  • 我们删掉了使用支付宝获取登录信息之后存缓存的逻辑

  • 接着,我们 dispatch 了一个 action.type 为 LOGIN 的异步 ACTION,并传递了 userInfo 数据

  • 最后我们导入了需要的 LOGIN 常量。

除了支付宝登录按钮的逻辑改进之外,我们还要改进我们的 api 逻辑,加上对支付宝环境的判断和调用对应的 LeanCloud 云函数。

打开 src/api/user.js 文件,对其中的内容作出对应的修改如下:

import Taro from '@tarojs/taro'

import { HEADER, LOGIN_URL, convertUserFormat } from './utils'

async function login(userInfo) {
  const isWeapp = Taro.getEnv() === Taro.ENV_TYPE.WEAPP
  const isAlipay = Taro.getEnv() === Taro.ENV_TYPE.ALIPAY
  const isH5 = Taro.getEnv() === Taro.ENV_TYPE.WEB

  // 针对微信小程序使用小程序云函数,其他使用小程序 RESTful API
  try {...      })

      return result.user
    } else if (isAlipay || isH5) {
      const { data } = await Taro.request({
        url: LOGIN_URL,
        method: 'POST',
        header: { ...HEADER },
        data: {
          userInfo,
        },
      })

      return convertUserFormat(data.result)
    }
  } catch (err) {
    console.error('login ERR: ', err)

可以看到上面主要做了四处修改:

  • 首先我们导入了之前定义的和 LeanCloud 有个的 utils 函数。

  • 接着我们加入了对 H5 环境的判断。

  • 最后我们增加了一个 else if 流程,用于判断在支付宝小程序或者 H5 环境下需要执行发起 REST 请求的逻辑,这里我们使用了 Taro.request 进行网络请求,并传入了对应的 urlheaderdata,以及将请求的类型设置为 POST,之前我们提到过,对 LeanCloud 云函数发起请求都需要使用 POST 方法。

  • 最后我们将从 LeanCloud 拿到的请求结果使用 convertUserFormat 做了一次格式的转换,以适应现有的微信小程序数据类型。

好了,通过以上三步流程,我们就跑通了小程序类请求 LeanCloud 的流程,保存修改的代码,让我们马上打开支付宝小程序试一下吧!

创建 Post 逻辑云函数

在上一节中,我们创建了 User 逻辑的 login 云函数,在这一节中,我们来收尾 Post 逻辑的三个云函数:

  • createPost

  • getPosts

  • getPost

因为创建的逻辑和方式和之前的 login 云函数类似,我们这里不再赘述,会简单的贴一下代码,但我们同样按照之前的三步流程来讲解。

创建云函数

首先创建我们的 createPost 云函数,其代码如下:

const { postData, userId } = request.params


const Post = AV.Object.extend('Post')
const post = new Post();

const myUser = AV.Object.createWithoutData('MyUser', userId)
const newPost = await post.save({
    ...postData,
    user: myUser
});

const query = new AV.Query('Post')
const postWithUser = await query.equalTo('objectId', newPost.get('objectId')).include('user').first()

return postWithUser

接着来创建我们的获取帖子列表的云函数 getPosts,其代码如下:

const query = new AV.Query('Post')
const posts = await query.include('user').find()

return posts

最后是我们的获取帖子详情的云函数 getPost

const { postId } = request.params

const query = new AV.Query('Post')
const post = await query.equalTo('objectId', postId).include('user').first()

return post
部署

好的,三个和 Post 逻辑有关的云函数创建好了,我们马上点击部署按钮来将它们部署上线。

在小程序端测试

当创建好云函数,并部署好之后,我们就可以在小程序端编写对应的代码进行测试了,打开 src/api/post.js 文件,对其中的代码做出对应的修改如下:

import Taro from '@tarojs/taro'

import {
  HEADER,
  CREATE_POST_URL,
  GET_POSTS_URL,
  GET_POST_URL,
  convertPostFormat,
  convertPostsFormat,
} from './utils'

async function createPost(postData, userId) {
  const isWeapp = Taro.getEnv() === Taro.ENV_TYPE.WEAPP
  const isAlipay = Taro.getEnv() === Taro.ENV_TYPE.ALIPAY
  const isH5 = Taro.getEnv() === Taro.ENV_TYPE.WEB

  // 针对微信小程序使用小程序云函数,其他使用小程序 RESTful API
  try {...      })

      return result.post
    } else if (isAlipay || isH5) {
      const { data } = await Taro.request({
        url: CREATE_POST_URL,
        method: 'POST',
        header: { ...HEADER },
        data: {
          postData,
          userId,
        },
      })

      return convertPostFormat(data.result)
    }
  } catch (err) {
    console.error('createPost ERR: ', err)...async function getPosts() {
  const isWeapp = Taro.getEnv() === Taro.ENV_TYPE.WEAPP
  const isAlipay = Taro.getEnv() === Taro.ENV_TYPE.ALIPAY
  const isH5 = Taro.getEnv() === Taro.ENV_TYPE.WEB

  // 针对微信小程序使用小程序云函数,其他使用小程序 RESTful API
  try {...      })

      return result.posts
    } else if (isAlipay || isH5) {
      const { data } = await Taro.request({
        url: GET_POSTS_URL,
        method: 'POST',
        header: { ...HEADER },
      })

      return convertPostsFormat(data.result)
    }
  } catch (err) {
    console.error('getPosts ERR: ', err)...async function getPost(postId) {
  const isWeapp = Taro.getEnv() === Taro.ENV_TYPE.WEAPP
  const isAlipay = Taro.getEnv() === Taro.ENV_TYPE.ALIPAY
  const isH5 = Taro.getEnv() === Taro.ENV_TYPE.WEB

  // 针对微信小程序使用小程序云函数,其他使用小程序 RESTful API
  try {...      })

      return result.post
    } else if (isAlipay || isH5) {
      const { data } = await Taro.request({
        url: GET_POST_URL,
        method: 'POST',
        header: { ...HEADER },
        data: {
          postId,
        },
      })

      return convertPostFormat(data.result)
    }
  } catch (err) {
    console.error('getPost ERR: ', err)

可以看到上面主要做了四处修改:

  • 首先我们导入了之前定义的和 LeanCloud 有关的 utils 函数。

  • 接着我们加入了对 H5 环境的判断。

  • 最后我们增加了一个 else if 流程,用于判断在支付宝小程序或者 H5 环境下需要执行发起 REST 请求的逻辑,这里我们使用了 Taro.request 进行网络请求,并传入了对应三个和 Post 逻辑有关的 url 、以及对应的 headerdata,以及将请求的类型设置为 POST,之前我们提到过,对 LeanCloud 云函数发起请求都需要使用 POST 方法。

  • 最后我们将从 LeanCloud 拿到的请求结果使用 convertPostFormat 做了一次格式的转换,以适应现有的微信小程序数据类型。

好了,通过以上三步流程,我们就跑通了小程序类请求 LeanCloud 的流程,保存修改的代码,让我们马上打开支付宝小程序试一下吧!

小结

在这篇文章中,我们讲解了支付宝小程序接入 LeanCloud Serverless 云服务的过程,我们再来复习一下整个流程:

  • 首先我们讲解了微信小程序云的不足,然后引出了 LeanCloud 来实现跨端小程序开发

  • 接着我们介绍了 LeanCloud 服务的配置过程,具体包含 1)注册登录 LeanCloud 2)配置对应的小程序后台的白名单。且因为 LeanCloud 没有支付宝小程序的 SDK,所以我们采用 REST 请求的方式来获取和修改对应的数据

  • 接着我们讲解了如何在 LeanCloud 上面创建数据表。

  • 接着,我们介绍了如何在 LeanCloud 创建云函数。

  • 最后我们通过三步走流程:1)创建云函数 2)部署 3)在小程序端测试,创建了我们需要的四个云函数。

我们再来看一下整体的接入效果:

到这里我们的 Taro 多端小程序开发大型实战 就基本告一段落了,整个教程内容想当长,涵盖的内容也相当多,这也是图雀社区最长的一个系列教程。最后希望 Taro 社区越来越好,也希望能帮到您!

One More Thing

我们在之前的教程中花了8篇文章的篇幅讲解了小程序从0到开发完成的过程,但是我们还没将如何将小程序上线,这里我们再额外花一点笔墨讲一下如何上线你的小程序,因为小程序的上线很容易,所以内容不会很长,有兴趣的读者可以继续读下去ღ( ´・ᴗ・` )比心。

微信小程序上线

首先点击小程序开发者工具的右上角的上传按钮:

接着去微信小程序网站后台:点我前往。

进行登录之后,在进来的第一个页面的第二步可以看见版本发布的信息,安装微信官方的流程进行即可。

支付宝小程序上线

首先点击支付宝小程序开发者工具的右上角上传按钮:

接着去支付宝小程序后台:点我前往[21]

进行登录之后,点击顶部的 开发中心,选择小程序应用,选择你的小程序应用,然后同样可以看到类似发布上线的栏目,安装支付宝官方的流程进行发布就可以了。

提示

微信/支付宝小程序对于有社交、社区性质的小程序是需要企业认证的,所以有类似需求的需要做一下准备。

好了,到这里我们的要说再见了 ????!希望你们学得开心!

想要学习更多精彩的实战技术教程?来图雀社区[22]逛逛吧。

本文所涉及的源代码都放在了 Github[23] 上,如果您觉得我们写得还不错,希望您能给❤️这篇文章点个在看+Github仓库加星❤️哦

参考资料

[1]

熟悉的 React,熟悉的 Hooks: https://juejin.im/post/5e046c4fe51d45584221e508

[2]

多页面跳转和 Taro UI 组件库: https://juejin.im/post/5e0891b66fb9a0165936fb0b

[3]

实现微信和支付宝多端登录: https://juejin.im/post/5e10118be51d454165777203

[4]

使用 Hooks 版的 Redux 实现大型应用状态管理(上篇): https://juejin.im/post/5e100f78e51d4541493621cd

[5]

使用 Hooks 版的 Redux 实现大型应用状态管理(下篇): https://juejin.im/post/5e1e63e8f265da3e4412b1f1

[6]

Taro 小程序开发大型实战(六):尝鲜微信小程序云(上篇): https://juejin.im/post/5e1dd614f265da3e12181ff3

[7]

Taro 小程序开发大型实战(七):尝鲜微信小程序云(下篇): https://juejin.im/user/5b33414351882574b9694d28/posts

[8]

Redux 包教包会(一):解救 React 状态危机: https://juejin.im/post/5df62cd8e51d4558270ef5ca

[9]

Redux 包教包会(二):趁热打铁,完全重构: https://juejin.im/post/5df7b11c51882512664b1068

[10]

Redux 包教包会(三):各司其职,重拾初心: https://juejin.im/post/5e0fe9705188253ab044c869

[11]

Github: https://github.com/tuture-dev/ultra-club

[12]

React 前端工程师学习路线: https://github.com/tuture-dev/react-roadmap

[13]

点我访问: https://www.leancloud.cn/

[14]

点我访问: https://leancloud.cn/docs/weapp.html

[15]

点我访问: https://mini.open.alipay.com/channel/miniIndex.htm

[16]

点我访问: https://leancloud.cn/docs/leanengine-rest-api.html#hash20005220

[17]

点我访问: https://leancloud.cn/docs/leanengine-rest-api.html#hash-1722650509

[18]

点我访问: https://leancloud.cn/docs/leanengine-rest-api.html#hash20005220

[19]

点我查看: https://leancloud.cn/docs/leanstorage_guide-js.html

[20]

点我跳转: https://taro.jd.com/

[21]

点我前往: https://mini.open.alipay.com/channel/miniIndex.htm

[22]

图雀社区: https://tuture.co?utm_source=juejin_zhuanlan

[23]

Github: https://github.com/tuture-dev/ultra-club

● Taro小程序开发大型实战(一):熟悉的React,熟悉的Hooks
● Taro小程序开发大型实战(二):多页面跳转和TaroUI组件库
● Taro 小程序开发大型实战(七):尝鲜微信小程序云(下篇)

·END·

图雀社区

汇聚精彩的免费实战教程

关注公众号回复 z 拉学习交流群

喜欢本文,点个“在看”告诉我

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值