mybatis逆向工程(根据表结构生成POJO、dao、mapper.xml)

本篇文章讲解的Mybatis generator在Spring Boot上的实现

因为在一些小型企业,Mybatis相比Hibernate越来越流行,所以我觉得很有必要写一篇更加简单易懂的文章,来教会大家如何在Spring Boot上整合Mybatis并使用它的逆向工程

前提准备:
使用的工具:JDK1.8、IntelliJ IDEA、Mysql

首先:我们来看一下基本的目录结构:

数据库创建的表结构:

maven依赖的包与插件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>app</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>app</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Greenwich.SR1</spring-cloud.version>
        <mybatis-generator.version>1.3.7</mybatis-generator.version>
    </properties>

    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <!-- SpringBoot-MyBatis -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.0.1</version>
        </dependency>
        <!--mybatis分页插件:PageHelper插件-->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.0.0</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-json-org</artifactId>
        </dependency>

        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-envers</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpmime</artifactId>
        </dependency>

        <!-- SpringBoot-MyBatis逆向工程 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!--mybatis逆向工程-->
            <dependency>
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-core</artifactId>
                <version>${mybatis-generator.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <fork>true</fork>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-maven-plugin</artifactId>
                <version>${mybatis-generator.version}</version>
                <configuration>
                    <!--允许移动生成的文件 -->
                    <verbose>true</verbose>
                    <!-- 是否覆盖 -->
                    <overwrite>false</overwrite>
                    <!-- 自动生成的配置 -->
                    <configurationFile>src/main/resources/mybatis-generator.xml</configurationFile>
                </configuration>
                <dependencies>
                    <dependency>
                        <groupId>mysql</groupId>
                        <artifactId>mysql-connector-java</artifactId>
                        <version>5.1.47</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>


</project>

application.yml文件配置:

server:
  port: 8088
spring:
  profiles:
    active: native
  aop:
    auto: true
    proxy-target-class: true
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
    open-in-view: true
  http:
    multipart:
      enabled: true
      max-file-size: 20MB      #(这里是限制的文件大小)
      max-request-size: 20MB   #(这里是限制的文件大小)
---
spring:
  profiles: native
  cloud:
    config:
      server:
        native:
          search-locations: classpath:/shared
---
spring:
  profiles: test
  cloud:
    config:
      server:
        git:
          uri: https://github.com/trusause/spring-cloud-test
          search-paths: my-config-server/src/main/resources/test
          username: trusause
          password: ai181003
          default-label: develop

app.yml:

server:
  port: 8091
spring:
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    #    driver-class-name: com.mysql.jdbc.Driver
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8
    username: root
    password: db_password
    initialSize: 5    # 下面为连接池的补充设置,应用到上面所有数据源中
    minIdle: 5        # 初始化大小,最小,最大
    maxActive: 20
    maxWait: 60000     # 配置获取连接等待超时的时间
    timeBetweenEvictionRunsMillis: 60000    # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
    validationQuery: SELECT 1 FROM DUAL  # 配置一个连接在池中最小生存的时间,单位是毫秒
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true  # 打开PSCache,并且指定每个连接上PSCache的大小
    maxPoolPreparedStatementPerConnectionSiz: 20
    filters: stat,wall,log4j    # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
#     通过connectProperties属性来打开mergeSql功能;慢SQL记录
    useGlobalDataSourceStat: true # 合并多个DruidDataSource的监控数据

#showsql
logging:
  level:
    com:
      trusause:
        app:
          mapper: debug

mybatis:
  mapper-locations: classpath:mapping/*.xml
  type-aliases-package: com.trusause.app

#pagehelper
pagehelper:
  helperDialect: mysql
  reasonable: true
  supportMethodsArguments: true
  params: count=countSql
  returnPageInfo: check

generatorConfig.xml(逆向工程配置文件,如果使用maven插件启动,请保持文件名一致):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
    <!--mysql 连接数据库jar 这里选择自己本地位置-->
    <!--<classPathEntry-->
    <!--location="D:\apache-maven-3.3.9\maven-repository\mysql\mysql-connector-java\5.1.46\mysql-connector-java-5.1.46.jar"/>-->
    <context id="testTables" targetRuntime="MyBatis3">
        <!-- 生成的Java文件的编码-->
        <property name="javaFileEncoding" value="UTF-8"></property>
        <!-- 生成的Java文件的编码-->
        <property name="javaFileEncoding" value="UTF-8"></property>
        <!-- 格式化java代码-->
        <property name="javaFormatter" value="org.mybatis.generator.api.dom.DefaultJavaFormatter"></property>
        <!-- 格式化XML代码-->
        <property name="xmlFormatter" value="org.mybatis.generator.api.dom.DefaultXmlFormatter"></property>
        <!--beginningDelimiter和endingDelimiter:指明数据库的用于标记数据库对象名的符号,比如ORACLE就是双引号,MYSQL默认是`反引号; -->
        <property name="beginningDelimiter" value="`"></property>
        <property name="endingDelimiter" value="`"></property>

        <commentGenerator>
            <!-- 是否去除自动生成的注释 true:是 : false:否 -->
            <property name="suppressAllComments" value="true"/>
        </commentGenerator>
        <!--数据库连接的信息:驱动类、连接地址、用户名、密码 -->
        <jdbcConnection driverClass="com.mysql.jdbc.Driver"
                        connectionURL="jdbc:mysql://localhost:3306/test?useUnicode=true&amp;characterEncoding=utf-8&amp;useSSL=false"
                        userId="root"
                        password="db_password">
        </jdbcConnection>
        <!-- 默认false,把JDBC DECIMAL 和 NUMERIC 类型解析为 Integer,为 true时把JDBC DECIMAL 和
           NUMERIC 类型解析为java.math.BigDecimal -->
        <javaTypeResolver>
            <property name="forceBigDecimals" value="false"/>
        </javaTypeResolver>
        <!--指定javaBean生成的位置 javaBean生成的位置-->
        <javaModelGenerator targetPackage="com.trusause.app.domain" targetProject="./src/main/java">
            <!--  for MyBatis3/MyBatis3Simple
           自动为每一个生成的类创建一个构造方法,构造方法包含了所有的field;而不是使用setter;
            -->
            <property name="constructorBased" value="false"/>
            <!-- enableSubPackages:是否让schema作为包的后缀 -->
            <property name="enableSubPackages" value="false"/>
            <!-- 是否清理从数据库中查询出的字符串左右两边的空白字符 -->
            <property name="trimStrings" value="true"/>
            <!-- for MyBatis3 / MyBatis3Simple
            是否创建一个不可变的类,如果为true,
            那么MBG会创建一个没有setter方法的类,取而代之的是类似constructorBased的类
            -->
            <property name="immutable" value="false"/>
            <!-- 设置一个根对象,
                如果设置了这个根对象,那么生成的keyClass或者recordClass会继承这个类;在Table的rootClass属性中可以覆盖该选项
                注意:如果在key class或者record class中有root class相同的属性,MBG就不会重新生成这些属性了,包括:
                   1,属性名相同,类型相同,有相同的getter/setter方法;
                -->
            <!--<property name="rootClass" value="com.trusause.app.domain.GenericEntity"/>-->
        </javaModelGenerator>

        <!--sql映射文件生成的位置-->
        <sqlMapGenerator targetPackage="mapping" targetProject="./src/main/resources">
            <property name="enableSubPackages" value="true"/>
        </sqlMapGenerator>

        <!-- 对于mybatis来说,即生成Mapper接口,注意,如果没有配置该元素,那么默认不会生成Mapper接口
        targetPackage/targetProject:同javaModelGenerator
        type:选择怎么生成mapper接口(在MyBatis3/MyBatis3Simple下):
            1,ANNOTATEDMAPPER:会生成使用Mapper接口+Annotation的方式创建(SQL生成在annotation中),不会生成对应的XML;
            2,MIXEDMAPPER:使用混合配置,会生成Mapper接口,并适当添加合适的Annotation,但是XML会生成在XML中;
            3,XMLMAPPER:会生成Mapper接口,接口完全依赖XML;
        注意,如果context是MyBatis3Simple:只支持ANNOTATEDMAPPER和XMLMAPPER
        -->
        <javaClientGenerator type="XMLMAPPER" targetPackage="com.trusause.app.mapper" targetProject="./src/main/java">
            <!-- 在targetPackage的基础上,根据数据库的schema再生成一层package,最终生成的类放在这个package下,默认为false -->
            <property name="enableSubPackages" value="true"/>
            <!-- 可以为所有生成的接口添加一个父接口,但是MBG只负责生成,不负责检查
            <propertyname="rootInterface" value=""/>
             -->
        </javaClientGenerator>

        <!--table是指定每个表的生成策略-->
        <table tableName="t_test_course" domainObjectName="Course"
               enableCountByExample="false"
               enableUpdateByExample="false"
               enableDeleteByExample="false"
               enableSelectByExample="false"
               selectByExampleQueryId="false"></table>

    </context>
</generatorConfiguration>

三、生成逆向工程

方法一:直接使用maven插件自带的功能

方法二:自己配置maven插件

启动Edit Configuartions,在跳出界面中进行如下配置,-e更加方面我们阅读报错的原因

设置插件完毕之后,就可以启动插件了

四、生成相关逆向工程

运行之后,如果成功便会在指定位置(generatorConfig.xml中可以配置,具体看注释)生成响应的POJO类,dao接口,和mapper的.xml数据库文件.

五、生成的文件

POJO类

package com.trusause.app.domain;

import java.util.Date;

/**
 * 通过mybatis逆向工程生成的文件。
 */

public class Course {
    private Integer id;

    private String code;

    private String name;

    private String createPerson;

    private String createPersonName;

    private Boolean deleteFlag;

    private Date systemCreateTime;

    private Date systemUpdateTime;

    private String updatePerson;

    private String updatePersonName;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code == null ? null : code.trim();
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name == null ? null : name.trim();
    }

    public String getCreatePerson() {
        return createPerson;
    }

    public void setCreatePerson(String createPerson) {
        this.createPerson = createPerson == null ? null : createPerson.trim();
    }

    public String getCreatePersonName() {
        return createPersonName;
    }

    public void setCreatePersonName(String createPersonName) {
        this.createPersonName = createPersonName == null ? null : createPersonName.trim();
    }

    public Boolean getDeleteFlag() {
        return deleteFlag;
    }

    public void setDeleteFlag(Boolean deleteFlag) {
        this.deleteFlag = deleteFlag;
    }

    public Date getSystemCreateTime() {
        return systemCreateTime;
    }

    public void setSystemCreateTime(Date systemCreateTime) {
        this.systemCreateTime = systemCreateTime;
    }

    public Date getSystemUpdateTime() {
        return systemUpdateTime;
    }

    public void setSystemUpdateTime(Date systemUpdateTime) {
        this.systemUpdateTime = systemUpdateTime;
    }

    public String getUpdatePerson() {
        return updatePerson;
    }

    public void setUpdatePerson(String updatePerson) {
        this.updatePerson = updatePerson == null ? null : updatePerson.trim();
    }

    public String getUpdatePersonName() {
        return updatePersonName;
    }

    public void setUpdatePersonName(String updatePersonName) {
        this.updatePersonName = updatePersonName == null ? null : updatePersonName.trim();
    }
}

dao:

package com.trusause.app.mapper;

import com.trusause.app.domain.Course;

public interface CourseMapper {
    int deleteByPrimaryKey(Integer id);

    int insert(Course record);

    int insertSelective(Course record);

    Course selectByPrimaryKey(Integer id);

    int updateByPrimaryKeySelective(Course record);

    int updateByPrimaryKey(Course record);
}

xxxMapper.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.trusause.app.mapper.CourseMapper">
  <resultMap id="BaseResultMap" type="com.trusause.app.domain.Course">
    <id column="id" jdbcType="INTEGER" property="id" />
    <result column="code" jdbcType="VARCHAR" property="code" />
    <result column="name" jdbcType="VARCHAR" property="name" />
    <result column="create_person" jdbcType="VARCHAR" property="createPerson" />
    <result column="create_person_name" jdbcType="VARCHAR" property="createPersonName" />
    <result column="delete_flag" jdbcType="BIT" property="deleteFlag" />
    <result column="system_create_time" jdbcType="TIMESTAMP" property="systemCreateTime" />
    <result column="system_update_time" jdbcType="TIMESTAMP" property="systemUpdateTime" />
    <result column="update_person" jdbcType="VARCHAR" property="updatePerson" />
    <result column="update_person_name" jdbcType="VARCHAR" property="updatePersonName" />
  </resultMap>
  <sql id="Base_Column_List">
    id, code, name, create_person, create_person_name, delete_flag, system_create_time, 
    system_update_time, update_person, update_person_name
  </sql>
  <select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
    select 
    <include refid="Base_Column_List" />
    from t_test_course
    where id = #{id,jdbcType=INTEGER}
  </select>
  <delete id="deleteByPrimaryKey" parameterType="java.lang.Integer">
    delete from t_test_course
    where id = #{id,jdbcType=INTEGER}
  </delete>
  <insert id="insert" parameterType="com.trusause.app.domain.Course">
    insert into t_test_course (id, code, name, 
      create_person, create_person_name, delete_flag, 
      system_create_time, system_update_time, 
      update_person, update_person_name)
    values (#{id,jdbcType=INTEGER}, #{code,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR}, 
      #{createPerson,jdbcType=VARCHAR}, #{createPersonName,jdbcType=VARCHAR}, #{deleteFlag,jdbcType=BIT}, 
      #{systemCreateTime,jdbcType=TIMESTAMP}, #{systemUpdateTime,jdbcType=TIMESTAMP}, 
      #{updatePerson,jdbcType=VARCHAR}, #{updatePersonName,jdbcType=VARCHAR})
  </insert>
  <insert id="insertSelective" parameterType="com.trusause.app.domain.Course">
    insert into t_test_course
    <trim prefix="(" suffix=")" suffixOverrides=",">
      <if test="id != null">
        id,
      </if>
      <if test="code != null">
        code,
      </if>
      <if test="name != null">
        name,
      </if>
      <if test="createPerson != null">
        create_person,
      </if>
      <if test="createPersonName != null">
        create_person_name,
      </if>
      <if test="deleteFlag != null">
        delete_flag,
      </if>
      <if test="systemCreateTime != null">
        system_create_time,
      </if>
      <if test="systemUpdateTime != null">
        system_update_time,
      </if>
      <if test="updatePerson != null">
        update_person,
      </if>
      <if test="updatePersonName != null">
        update_person_name,
      </if>
    </trim>
    <trim prefix="values (" suffix=")" suffixOverrides=",">
      <if test="id != null">
        #{id,jdbcType=INTEGER},
      </if>
      <if test="code != null">
        #{code,jdbcType=VARCHAR},
      </if>
      <if test="name != null">
        #{name,jdbcType=VARCHAR},
      </if>
      <if test="createPerson != null">
        #{createPerson,jdbcType=VARCHAR},
      </if>
      <if test="createPersonName != null">
        #{createPersonName,jdbcType=VARCHAR},
      </if>
      <if test="deleteFlag != null">
        #{deleteFlag,jdbcType=BIT},
      </if>
      <if test="systemCreateTime != null">
        #{systemCreateTime,jdbcType=TIMESTAMP},
      </if>
      <if test="systemUpdateTime != null">
        #{systemUpdateTime,jdbcType=TIMESTAMP},
      </if>
      <if test="updatePerson != null">
        #{updatePerson,jdbcType=VARCHAR},
      </if>
      <if test="updatePersonName != null">
        #{updatePersonName,jdbcType=VARCHAR},
      </if>
    </trim>
  </insert>
  <update id="updateByPrimaryKeySelective" parameterType="com.trusause.app.domain.Course">
    update t_test_course
    <set>
      <if test="code != null">
        code = #{code,jdbcType=VARCHAR},
      </if>
      <if test="name != null">
        name = #{name,jdbcType=VARCHAR},
      </if>
      <if test="createPerson != null">
        create_person = #{createPerson,jdbcType=VARCHAR},
      </if>
      <if test="createPersonName != null">
        create_person_name = #{createPersonName,jdbcType=VARCHAR},
      </if>
      <if test="deleteFlag != null">
        delete_flag = #{deleteFlag,jdbcType=BIT},
      </if>
      <if test="systemCreateTime != null">
        system_create_time = #{systemCreateTime,jdbcType=TIMESTAMP},
      </if>
      <if test="systemUpdateTime != null">
        system_update_time = #{systemUpdateTime,jdbcType=TIMESTAMP},
      </if>
      <if test="updatePerson != null">
        update_person = #{updatePerson,jdbcType=VARCHAR},
      </if>
      <if test="updatePersonName != null">
        update_person_name = #{updatePersonName,jdbcType=VARCHAR},
      </if>
    </set>
    where id = #{id,jdbcType=INTEGER}
  </update>
  <update id="updateByPrimaryKey" parameterType="com.trusause.app.domain.Course">
    update t_test_course
    set code = #{code,jdbcType=VARCHAR},
      name = #{name,jdbcType=VARCHAR},
      create_person = #{createPerson,jdbcType=VARCHAR},
      create_person_name = #{createPersonName,jdbcType=VARCHAR},
      delete_flag = #{deleteFlag,jdbcType=BIT},
      system_create_time = #{systemCreateTime,jdbcType=TIMESTAMP},
      system_update_time = #{systemUpdateTime,jdbcType=TIMESTAMP},
      update_person = #{updatePerson,jdbcType=VARCHAR},
      update_person_name = #{updatePersonName,jdbcType=VARCHAR}
    where id = #{id,jdbcType=INTEGER}
  </update>
</mapper>

当然如果你的电脑上许许多多的问题(日常)导致无法使用maven插件,可以在src/main/java文件夹下创建一个test类,写下如下代码:
注意new file("")中写下自己的逆向配置文件路径!

public class GeneratorTest {
    public static void main(String[] args)throws Exception {
        List<String> warnings = new ArrayList<String>();
        boolean overwrite = true;
        File configFile = new File("/Users/apple/Documents/Spring-Boot/src/main/resources/generatorConfig.xml");
        ConfigurationParser cp = new ConfigurationParser(warnings);
        Configuration config = cp.parseConfiguration(configFile);
        DefaultShellCallback callback = new DefaultShellCallback(overwrite);
        MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
        myBatisGenerator.generate(null);
    }
}

然后单独运行这个类也能自动生成代码!
以上便是Mybatis基于Spring Boot的逆向工程实现
谢谢关注~记得点个赞再走哦

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值