专栏首页RendaSpringMVC:SSM 整合

SpringMVC:SSM 整合

需求和步骤分析

需求:使用 SSM 框架完成对 account 表的增删改查操作。

步骤分析:

  1. 准备数据库和表记录
  2. 创建 web 项目
  3. 编写 MyBatis 在 SSM 环境中可以单独使用
  4. 编写 Spring 在 SSM 环境中可以单独使用
  5. Spring 整合 MyBatis
  6. 编写 SpringMVC 在 SSM 环境中可以单独使用
  7. Spring 整合 SpringMVC

环境搭建

准备数据库和表记录

CREATE TABLE `account` (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `name` varchar(32) DEFAULT NULL,
    `money` double DEFAULT NULL,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
insert into `account`(`id`,`name`,`money`) values (1,'tom',1000),(2,'jerry',1000);

创建 web 项目

使用 Maven 创建名为 ssm 的 web 项目

编写 MyBatis 在 SSM 环境中可以单独使用

需求:基于 MyBatis 先来实现对 account 表的查询

相关坐标

...
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.15</version>
</dependency>
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.1</version>
</dependency>
...

`Account` 实体

public class Account {

    private Integer id;
    private String name;
    private Double money;
     // getter and setter ...   
}

`AccountDao` 接口

public interface AccountDao {
    public List<Account> findAll();
}

`AccountDao.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.renda.dao.AccountDao">
    <select id="findAll" resultType="com.renda.domain.Account">
        select * from account
    </select>
</mapper>

MyBatis 核心配置文件

jdbc.properties

jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///spring_db?characterEncoding=utf8&useSSL=false
jdbc.username=root
jdbc.password=password

SqlMapConfig.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>

    <!-- 加载 properties -->
    <properties resource="jdbc.properties"/>

    <!-- 类型别名配置 -->
    <typeAliases>
        <package name="com.renda.domain"/>
    </typeAliases>

    <!-- 环境配置 -->
    <environments default="mysql">
        <environment id="mysql">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driverClassName}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>

    <!-- 加载映射 -->
    <mappers>
        <package name="com.renda.dao"/>
    </mappers>

</configuration>

测试代码

public class MyBatisTest {
    @Test
    public void  testMybatis() throws IOException {
        InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");

        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);

        SqlSession sqlSession = sqlSessionFactory.openSession();

        AccountDao mapper = sqlSession.getMapper(AccountDao.class);

        List<Account> all = mapper.findAll();
        for (Account account : all) {
            System.out.println(account);
        }

        sqlSession.close();
    }
}

编写 Spring 在 SSM 环境中可以单独使用

相关坐标

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.1.5.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.13</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.1.5.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
    <version>5.1.5.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.1.5.RELEASE</version>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>

`AccountService` 接口

public interface AccountService {
    List<Account> findAll();
}

`AccountServiceImpl` 实现

@Service
public class AccountServiceImpl implements AccountService {
    @Override
    public List<Account> findAll() {
        System.out.println("findAll 执行了...");
        return null;
    }
}

Spring 核心配置文件

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:conext="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
           http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 配置 IOC 相关操作: 开启注解扫描 -->
    <conext:component-scan base-package="com.renda.service"/>

</beans>

测试代码

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringTest {
    @Autowired
    private AccountService accountService;

    @Test
    public void testSpring() throws Exception {
        List<Account> accountList = accountService.findAll();
        System.out.println(accountList);
    }
}

Spring 整合 MyBatis

整合思想

将 MyBatis 接口代理对象的创建权交给 Spring 管理,我们就可以把 Dao 的代理对象注入到 Service 中,此时也就完成了 Spring 与 MyBatis 的整合了。

导入整合包

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>1.3.1</version>
</dependency>

Spring 配置文件管理 MyBatis

此时可以将 MyBatis 主配置文件 SqlMapConfig.xml 删除,在 applicationContext.xml 配置文件中加入 MyBatis:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:conext="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
           http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 配置 IOC 相关操作: 开启注解扫描 -->
    <conext:component-scan base-package="com.renda.service"/>

    <!-- spring 整合 mybatis 开始 -->
    <!-- 引入 jdbc.properties -->
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <!-- 创建数据源 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driverClassName}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
    <!-- sqlSessionFactory 的创建权交给了 spring,生产 sqlSession -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="typeAliasesPackage" value="com.renda.domain"/>
        <!-- 引入加载 mybatis 的核心配置文件,可以不用去加载 -->
<!--        <property name="configLocation" value="classpath:SqlMapConfig.xml"/>-->
    </bean>
    <!-- mapper 映射扫描 MapperScannerConfigurer 扫描该包下所有接口,生成代理对象存到 IOC 容器中 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.renda.dao"/>
    </bean>
    <!-- spring 整合 mybatis 结束 -->

</beans>

修改 `AccountServiceImpl`

@Service
public class AccountServiceImpl implements AccountService {
    @Autowired
    AccountDao accountDao;

    @Override
    public List<Account> findAll() {
        return accountDao.findAll();
    }
}

测试代码

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringTest {

    @Autowired
    private AccountService accountService;

    @Test
    public void testSpring() {
        System.out.println(accountService.findAll());
    }

}

编写 SpringMVC 在 SSM 环境中可以单独使用

需求:访问到 Controller 里面的方法查询所有账户,并跳转到 list.jsp 页面进行列表展示

相关坐标

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.1.5.RELEASE</version>
</dependency>
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>javax.servlet.jsp</groupId>
    <artifactId>jsp-api</artifactId>
    <version>2.2</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>jstl</groupId>
    <artifactId>jstl</artifactId>
    <version>1.2</version>
</dependency>
</dependencies>

导入页面资源

webapp 目录下导入 JSP 页面相关的资源:add.jspindex.jsplist.jspupdate.jsp

index.jsp

...
<div align="center">
    <a href="account/findAll" style="text-decoration:none;font-size:33px">
        查询账户信息列表
    </a>
</div>
...

前端控制器 `DispatcherServlet`

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <!-- 前端控制器 -->
    <servlet>
        <servlet-name>DispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-mvc.xml</param-value>
        </init-param>
        <load-on-startup>2</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>DispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <!-- 中文乱码过滤器:解决 post 方式提交的乱码 -->
    <filter>
        <filter-name>CharacterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CharacterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

</web-app>

`AccountController` 和 `list.jsp`

@Controller
@RequestMapping("/account")
public class AccountController {

    @RequestMapping("/findAll")
    public String findAll(Model model) {
        ArrayList<Object> arrayList = new ArrayList<>();
        arrayList.add(new Account(, "张人大", d));
        arrayList.add(new Account(, "布莱尔", d));
        model.addAttribute("list", arrayList);
        return "list";
    }

}
...
<c:forEach items="${list}" var="account">
    <tr>
        <td>
            <input type="checkbox" name="ids" value="${account.id}">
        </td>
        <td>${account.id}</td>
        <td>${account.name}</td>
        <td>${account.money}</td>
        <td>
            <a class="btn btn-default btn-sm" href="${pageContext.request.contextPath}/update.jsp">修改</a>&nbsp;
            <a class="btn btn-default btn-sm" href="">删除</a>
        </td>
    </tr>
</c:forEach>
...

SpringMVC 核心配置文件

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/mvc
    http://www.springframework.org/schema/mvc/spring-mvc.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 1. 组件扫描:只扫描 controller -->
    <context:component-scan base-package="com.renda.controller"/>

    <!-- 2. mvc 注解增强:处理器映射器及处理器适配器 -->
    <mvc:annotation-driven/>

    <!-- 3. 视图解析器 -->
    <bean id="resourceViewResolve" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/"/>
        <property name="suffix" value=".jsp"/>
    </bean>

    <!-- 4. 放行静态资源 -->
    <mvc:default-servlet-handler/>

</beans>

Spring 整合 SpringMVC

整合思想

Spring 和 SpringMVC 本来就已经整合好了,都是属于 Spring 全家桶一部分。

但是需要做到 Spring 和 web 容器整合,让 web 容器启动的时候自动加载 Spring 配置文件,web 容 器销毁的时候 Spring 的 IOC 容器也销毁。

Spring 和 Web 容器整合

ContextLoaderListener 加载

可以使用 spring-web 包中的 ContextLoaderListener 监听器,来监听 servletContext 容器的创建和销毁,来同时创建或销毁 IOC 容器。

web.xml

<!-- 配置 spring 的监听器 -->
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
</context-param>

修改 `AccountController`

@Controller
@RequestMapping("/account")
public class AccountController {
    @Autowired
    private AccountService accountService;

    @RequestMapping("/findAll")
    public String findAll(Model model) {
        model.addAttribute("list", accountService.findAll());
        return "list";
    }
}

Spring 配置声明式事务

Spring 配置文件加入声明式事务

applicationContext.xml

<!-- 1. 事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>
<!-- 2. 开始事务注解的支持 -->
<tx:annotation-driven/>
@Service
@Transactional
public class AccountServiceImpl implements AccountService {
    ...
}

`add.jsp`

...
<form action="${pageContext.request.contextPath}/account/save" method="post">
    <div class="form-group">
        <label for="name">姓名:</label>
        <input type="text" class="form-control" id="name" name="name" placeholder="请输入姓名">
    </div>
    <div class="form-group">
        <label for="money">余额:</label>
        <input type="text" class="form-control" id="money" name="money" placeholder="请输入余额">
    </div>
    <div class="form-group" style="text-align: center">
        <input class="btn btn-primary" type="submit" value="提交"/>
        <input class="btn btn-default" type="reset" value="重置"/>
        <input class="btn btn-default" type="button" onclick="history.go(-1)" value="返回"/>
    </div>
</form>
...

`AccountDao`

void save(Account account);

`AccountDao.xml` 映射

<insert id="save" parameterType="account">
    insert into account (`name`, `money`) values (#{name}, #{money});
</insert>

`AccountService` 接口和实现类

void save(Account account);
@Override
public void save(Account account) {
    accountDao.save(account);
}

`AccountController`

@RequestMapping("/save")
public String save(Account account) {
    accountService.save(account);
    // 跳转到 findAll 方法重新查询一次数据库进行数据的遍历展示
    return "redirect:/account/findAll";
}

修改操作

数据回显

`AccountController`
@RequestMapping("/findById")
public String findById(Integer id,Model model){
    // 存到 model 中
    model.addAttribute("account", accountService.findById(id));
    // 视图跳转
    return  "update";
}
`AccountService` 接口和实现类
Account findById(Integer id);
@Override
public Account findById(Integer id) {
    return accountDao.findById(id);
}
`AccountDao` 接口和 `AccountDao.xml` 映射文件
Account findById(Integer id);
<select id="findById" parameterType="int" resultType="account">
    select * from account where id = #{id}
</select>
`update.jsp`
...
<form action="${pageContext.request.contextPath}/account/update" method="post">
    <input type="hidden" name="id" value="${account.id}">
    <div class="form-group">
        <label for="name">姓名:</label>
        <input type="text" class="form-control" id="name" name="name" value="${account.name}" placeholder="请输入姓名">
    </div>
    <div class="form-group">
        <label for="money">余额:</label>
        <input type="text" class="form-control" id="money" name="money" value="${account.money}"placeholder="请输入余额">
    </div>
    <div class="form-group" style="text-align: center">
        <input class="btn btn-primary" type="submit" value="提交"/>
        <input class="btn btn-default" type="reset" value="重置"/>
        <input class="btn btn-default" type="button" onclick="history.go(-1)" value="返回"/>
    </div>
</form>
...
`list.jsp`
...
<td>
    <a class="btn btn-default btn-sm" href="${pageContext.request.contextPath}/account/findById?id=${account.id}">修改</a>&nbsp;
    <a class="btn btn-default btn-sm" href="">删除</a>
</td>
...

账户更新

`AccountController`
@RequestMapping("/update")
public String update(Account account) {
    accountService.update(account);
    return "redirect:/account/findAll";
}
`AccountService` 接口和实现类
void update(Account account);
@Override
public void update(Account account) {
    accountDao.update(account);
}
`AccountDao` 接口和 `AccountDao.xml` 映射文件
void update(Account account);
<update id="update" parameterType="account">
    update account set name = #{name}, money = #{money} where id = #{id}
</update>

批量删除

`list.jsp`

<form action="${pageContext.request.contextPath}/account/deleteBatch" method="post" id="deleteBatchForm">
    <table border="1" class="table table-bordered table-hover">
        <tr class="success">
            <th>
                <input type="checkbox" id="checkAll">
            </th>
            <th>编号</th>
            <th>姓名</th>
            <th>余额</th>
            <th>操作</th>
        </tr>

        <c:forEach items="${list}" var="account">
            <tr>
                <td>
                    <input type="checkbox" name="ids" value="${account.id}">
                </td>
                <td>${account.id}</td>
                <td>${account.name}</td>
                <td>${account.money}</td>
                <td>
                    <a class="btn btn-default btn-sm" href="${pageContext.request.contextPath}/account/findById?id=${account.id}">
                        修改
                    </a>&nbsp;
                    <a class="btn btn-default btn-sm" href="">
                        删除
                    </a>
                </td>
            </tr>
        </c:forEach>

        <tr>
            <td colspan="9" align="center">
                <a class="btn btn-primary" href="${pageContext.request.contextPath}/add.jsp">添加账户</a>
                <input class="btn btn-primary" type="button" value="删除选中" id="deleteBatchBtn">
            </td>
        </tr>
    </table>
</form>

...

<script>
    /* 实现全选全不选效果 */
    $('#checkAll').click(function () {
        $('input[name="ids"]').prop('checked', $(this).prop('checked'));
    });

    /* 给删除选中按钮绑定点击事件 */
    $('#deleteBatchBtn').click(function () {
        if (confirm('确定要删除吗')) {
            if ($('input[name=ids]:checked').length > ) {
                /* 提交表单 */
                $('#deleteBatchForm').submit();
            }
        } else {
            alert('操作被取消')
        }
    });
</script>
...

`AccountController`

@RequestMapping("/deleteBatch")
public String deleteBatch(Integer[] ids) {
    accountService.deleteBatch(ids);
    return "redirect:/account/findAll";
}

`AccountService` 接口和实现类

void deleteBatch(Integer[] ids);
@Override
public void deleteBatch(Integer[] ids) {
    accountDao.deleteBatch(ids);
}

`AccountDao` 接口和 `AccountDao.xml` 映射文件

void deleteBatch(Integer[] ids);
<delete id="deleteBatch" parameterType="int">
    delete from account
    <where>
        <foreach collection="array" open="id in(" close=")" separator="," item="id">
            #{id}
        </foreach>
    </where>
</delete>

本文分享自微信公众号 - Renda(Renda_Zhang),作者:Renda Zhang

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-09-14

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Spring:AOP 面向切面编程

    上面的代码事务在 Dao 层,转出转入操作都是一个独立的事务,但实际开发,应该把业务逻辑控制在一个事务中,所以应该将事务挪到 Service 层。

    RendaZhang
  • Spring:JDBC Template,声明式事务

    JdbcTemplate 是 spring 框架中提供的一个模板对象,是对原始繁琐的 JDBC API 对象的简单封装。

    RendaZhang
  • 前端基础:ECMAScript 6

    ECMAScript 6.0(以下简称 ES6)是 JavaScript 语言的下一代标准。

    RendaZhang
  • 《Spring实战》摘录 - 21

    问题: #12.3.1 | Spring Data Redis为四种Redis客户端实现提供了连接工厂

    用户1335799
  • 拦截器的使用

    用户2193479
  • SSH框架之旅-spring(3)

    Spring 框架是一站式的框架,针对 JavaEE 的三层结构,每一层都有解决的技术,在 DAO(数据操作层)使用 jdbcTempalte。并且 Sprin...

    Wizey
  • zuul动态配置路由规则,从DB读取

    前面已经讲过zuul在application.yml里配置路由规则,将用户请求分发至不同微服务的例子。

    天涯泪小武
  • SpringBoot---(15)Spring Boot创建定时任务

    摘要:项目中经常会需要做一些定时的跑的事情,比如每间隔多久做个统计,发个邮件,清理个数据。这时候就要用到定时任务,SpringBoot中,创建定时任务非常简单,...

    IT云清
  • IntelliJ IDEA 中的 Maven 项目初体验及搭建 Spring MVC 框架

    在「详述 IntelliJ IDEA 创建 Maven 项目及设置 java 源目录的方法」一文中,我们已经将 IntelliJ IDEA 中的 Maven 项...

    CG国斌
  • IntelliJ IDEA 中的 Maven 项目初体验及搭建 Spring MVC 框架

    在「详述 IntelliJ IDEA 创建 Maven 项目及设置 java 源目录的方法」一文中,我们已经将 IntelliJ IDEA 中的 Maven 项...

    CG国斌

作者介绍

精选专题

活动推荐

扫码关注云+社区

领取腾讯云代金券