商品详情和缓存穿透、击穿、雪崩

1.商品详情的方案

1.1.网页静态化方案

1、创建商品详情的thymeleaf模板

2、创建服务接收消息,收到消息后创建静态页面

3、搭建nginx服务器,返回静态页面

1.2.redis缓存商品信息方案

使用redis做缓存,业务逻辑:

1、根据商品id到redis中查询

​ 查得到,直接返回

2、查不到,查询mysql,

​ 数据放到redis中

​ 设置缓存的有效期一天的时间,可以根据实际情况调整。

需要使用String类型来保存商品数据,可以加前缀对redis中的key进行归类:

ITEM_INFO:123456:BASE:{}

ITEM_INFO:123456:DESC:{}

ITEM_INFO:123456:PARAM:{}

2.查询商品详情

2.1.创建power_shop_detail

2.1.1.创建工程

2.1.2.pom.xml

<?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">
    <parent>
        <artifactId>power_shop_parent</artifactId>
        <groupId>com.bjpowernode</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>power_shop_detail</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>com.bjpowernode</groupId>
            <artifactId>power_shop_item_feign</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
</project>

2.1.3.application.yml

server:
  port: 8094
spring:
  application:
    name: power-shop-detail
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.204.129:8848

2.1.4.logback.xml

<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
    <!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径-->
    <property name="LOG_HOME" value="${catalina.base:-.}/logs/" />
    <!-- 控制台输出 -->
    <appender name="Stdout" class="ch.qos.logback.core.ConsoleAppender">
        <!-- 日志输出编码 -->
        <layout class="ch.qos.logback.classic.PatternLayout">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
            </pattern>
        </layout>
    </appender>
    <!-- 按照每天生成日志文件 -->
    <appender name="RollingFile"  class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--日志文件输出的文件名-->
            <FileNamePattern>${LOG_HOME}/server.%d{yyyy-MM-dd}.log</FileNamePattern>
            <MaxHistory>30</MaxHistory>
        </rollingPolicy>
        <layout class="ch.qos.logback.classic.PatternLayout">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
            </pattern>
        </layout>
        <!--日志文件最大的大小-->
        <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
            <MaxFileSize>10MB</MaxFileSize>
        </triggeringPolicy>
    </appender>

    <!--myibatis log configure-->

    <!-- 日志输出级别 -->
    <root level="DEBUG">
        <appender-ref ref="Stdout" />
        <appender-ref ref="RollingFile" />
    </root>



    <!--日志异步到数据库 -->
    <!--     <appender name="DB" class="ch.qos.logback.classic.db.DBAppender">
            日志异步到数据库
            <connectionSource class="ch.qos.logback.core.db.DriverManagerConnectionSource">
               连接池
               <dataSource class="com.mchange.v2.c3p0.ComboPooledDataSource">
                  <driverClass>com.mysql.jdbc.Driver</driverClass>
                  <url>jdbc:mysql://127.0.0.1:3306/databaseName</url>
                  <user>root</user>
                  <password>root</password>
                </dataSource>
            </connectionSource>
      </appender> -->

</configuration>

2.1.5.创建启动类

package com.bjpowernode;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class PowerShopDetailApp {
    public static void main(String[] args) {
        SpringApplication.run(PowerShopDetailApp.class, args);
    }
}

2.2.查询商品信息

2.2.1.power_shop_item

2.2.1.1.application.yml
#商品详情
ITEM_INFO: ITEM_INFO
BASE: BASE
DESC: DESC
PARAM: PARAM
ITEM_INFO_EXPIRE: 86400
2.2.1.2.service
  • 修改ItemServiceImpl
	@Value("${ITEM_INFO}")
	private String ITEM_INFO;

	@Value("${BASE}")
	private String BASE;

	@Value("${DESC}")
	private String DESC;

	@Value("${ITEM_INFO_EXPIRE}")
	private Integer ITEM_INFO_EXPIRE;

	@Autowired
	private RedisClient redisClient;
	/**
	 * 查询商品信息
	 * @param itemId
	 * @return
	 */
	@Override
	public TbItem selectItemInfo(Long itemId) {
		//查询缓存
		TbItem tbItem = (TbItem) redisClient.get(ITEM_INFO + ":" + itemId + ":"+ BASE);
		if(tbItem!=null){
			return tbItem;
		}

		tbItem = tbItemMapper.selectByPrimaryKey(itemId);
		//把数据保存到缓存
		redisClient.set(ITEM_INFO + ":" + itemId + ":"+ BASE,tbItem);
		//设置缓存的有效期
		redisClient.expire(ITEM_INFO + ":" + itemId + ":"+ BASE,ITEM_INFO_EXPIRE);

		return tbItem;
	}
	/**
	 * 根据商品 ID 查询商品描述
	 * @param itemId
	 * @return
	 */
	@Override
	public TbItemDesc selectItemDescByItemId(Long itemId) {
		//查询缓存
		TbItemDesc tbItemDesc = (TbItemDesc) redisClient.get(
            			ITEM_INFO + ":" + itemId + ":"+ DESC);
		if(tbItemDesc!=null){
			return tbItemDesc;
		}

		TbItemDescExample example = new TbItemDescExample();
		TbItemDescExample.Criteria criteria = example.createCriteria();
		criteria.andItemIdEqualTo(itemId);
		List<TbItemDesc> itemDescList =
				this.tbItemDescMapper.selectByExampleWithBLOBs(example);
		if(itemDescList!=null && itemDescList.size()>0){
			//把数据保存到缓存
			redisClient.set(ITEM_INFO + ":" + itemId + ":"+ DESC,itemDescList.get(0));
			//设置缓存的有效期
			redisClient.expire(ITEM_INFO + ":" + itemId + ":"+ DESC,ITEM_INFO_EXPIRE);
			return itemDescList.get(0);
		}
		return null;
	}
  • 修改ItemParamServiceImpl
    @Value("${ITEM_INFO}")
    private String ITEM_INFO;

    @Value("${PARAM}")
    private String PARAM;

    @Value("${ITEM_INFO_EXPIRE}")
    private Integer ITEM_INFO_EXPIRE;

    @Autowired
    private RedisClient redisClient;
     /**
     * 根据商品id查询商品规格
     * @param itemId
     * @return
     */
    @Override
    public TbItemParamItem selectTbItemParamItemByItemId(Long itemId) {
        //查询缓存
        TbItemParamItem tbItemParamItem = (TbItemParamItem) redisClient.get(
            						ITEM_INFO + ":" + itemId + ":"+ PARAM);
        if(tbItemParamItem!=null){
            return tbItemParamItem;
        }

        TbItemParamItemExample example = new TbItemParamItemExample();
        TbItemParamItemExample.Criteria criteria = example.createCriteria();
        criteria.andItemIdEqualTo(itemId);
        List<TbItemParamItem> tbItemParamItemList = 
            	tbItemParamItemMapper.selectByExampleWithBLOBs(example);
        if(tbItemParamItemList!=null && tbItemParamItemList.size()>0){
            //把数据保存到缓存
            redisClient.set(ITEM_INFO + ":" + itemId + ":"+ 
                            PARAM,tbItemParamItemList.get(0));
            //设置缓存的有效期
            redisClient.expire(ITEM_INFO + ":" + itemId + ":"+ PARAM,ITEM_INFO_EXPIRE);
            return tbItemParamItemList.get(0);
        }
        return null;
    }
2.2.1.3.controller
  • 修改ItemController
    /**查询商品信息
     * 根据商品id
     * @param itemId
     * @return
     */
    @RequestMapping("/selectItemInfo")
    public TbItem selectItemInfo(Long itemId){
        return this.itemService.selectItemInfo(itemId);
    }

    /**
     * 根据商品 ID 查询商品描述
     */
    @RequestMapping("/selectItemDescByItemId")
    public TbItemDesc selectItemDescByItemId(Long itemId){
        return this.itemService.selectItemDescByItemId(itemId);
    }

  • 修改ItemParamController
    /**
     * 根据商品 ID 查询商品规格
     */
    @RequestMapping("/selectTbItemParamItemByItemId")
    public TbItemParamItem selectTbItemParamItemByItemId(@RequestParam Long itemId){
        return itemParamService.selectTbItemParamItemByItemId(itemId);
    }

2.2.2.power_shop_item_feign

2.2.2.1.pom.xml
        <dependency>
            <groupId>com.bjpowernode</groupId>
            <artifactId>common_pojo</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
2.2.2.2.feign
    @RequestMapping("/service/item/selectItemInfo")
    TbItem selectItemInfo(@RequestParam("itemId") Long itemId);

    @RequestMapping("/service/item/selectItemDescByItemId")
    TbItemDesc selectItemDescByItemId(@RequestParam("itemId") Long itemId);

    @RequestMapping("/service/itemParam/selectTbItemParamItemByItemId")
    TbItemParamItem selectTbItemParamItemByItemId(@RequestParam("itemId") Long itemId);

2.2.3.power_shop_detail

2.2.3.1.controller
package com.bjpowernode.controller;

import com.bjpowershop.feign.ItemServiceFeign;
import com.bjpowershop.pojo.TbItem;
import com.bjpowershop.pojo.TbItemDesc;
import com.bjpowershop.pojo.TbItemParamItem;
import com.bjpowershop.utils.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/frontend/detail")
public class DetailController {
    @Autowired
    private ItemServiceFeign itemServiceFeign;

    /**
     * 查询商品基本信息
     */
    @RequestMapping("/selectItemInfo")
    public Result selectItemInfo(Long itemId) {
        TbItem tbItem = itemServiceFeign.selectItemInfo(itemId);
        if (tbItem != null) {
            return Result.ok(tbItem);
        }
        return Result.error("查无结果");
    }

    /**
     * 查询商品介绍
     */
    @RequestMapping("/selectItemDescByItemId")
    public Result selectItemDescByItemId(Long itemId){
        TbItemDesc tbItemDesc = itemServiceFeign.selectItemDescByItemId(itemId);
        if(tbItemDesc != null){
            return Result. ok (tbItemDesc);
        }
        return Result.error ("查无结果");
    }

    /**
     * 根据商品 ID 查询商品规格参数
     */
    @RequestMapping("/selectTbItemParamItemByItemId")
    public Result selectTbItemParamItemByItemId(Long itemId){
        TbItemParamItem tbItemParamItem =
                itemServiceFeign.selectTbItemParamItemByItemId(itemId);
        if(tbItemParamItem != null){
            return Result.ok(tbItemParamItem);
        }
        return Result.error ("查无结果");
    }
}

2.2.4.测试

在这里插入图片描述

3.缓存同步

后台修改或删除商品时直接删除redis中的商品

4.缓存穿透

4.1.描述

   缓存穿透是指<font color=red>缓存和数据库中都没有数据</font>,而用户不断发起请求则这些<font color=red>请求会穿过缓存直接访问数据库</font>,如发起为id为“-1”的数据或id为特别大不存在的数据。假如有恶意攻击,就可以利用这个漏洞,对数据库造成压力,甚至压垮数据库。

在这里插入图片描述

4.2.解决方案

**缓存空对象:**当存储层不命中时,创建空对象并将其缓存起来,同时会设置一个过期时间(避免控制占用更多的存储空间),之后再访问这个数据将会从缓存中获取,保护了后端数据源;

4.3.power_shop_item

4.3.1.service

  • 修改ItemServiceImpl
	/**
	 * 查询商品信息
	 * @param itemId
	 * @return
	 */
	@Override
	public TbItem selectItemInfo(Long itemId) {
		//查询缓存
		TbItem tbItem = (TbItem) redisClient.get(ITEM_INFO + ":" + itemId + ":"+ BASE);
		if(tbItem!=null){
			return tbItem;
		}

		tbItem = tbItemMapper.selectByPrimaryKey(itemId);
		/********************解决缓存穿透************************/
		if(tbItem == null){
			//把空对象保存到缓存
			redisClient.set(ITEM_INFO + ":" + itemId + ":"+ BASE,new TbItem());
			//设置缓存的有效期
			redisClient.expire(ITEM_INFO + ":" + itemId + ":"+ BASE,30);
			return tbItem;
		}
		//把数据保存到缓存
		redisClient.set(ITEM_INFO + ":" + itemId + ":"+ BASE,tbItem);
		//设置缓存的有效期
		redisClient.expire(ITEM_INFO + ":" + itemId + ":"+ BASE,ITEM_INFO_EXPIRE);
		return tbItem;
	}

	/**
	 * 根据商品 ID 查询商品介绍
	 * @param itemId
	 * @return
	 */
	@Override
	public TbItemDesc selectItemDescByItemId(Long itemId) {
		//查询缓存
		TbItemDesc tbItemDesc = (TbItemDesc) redisClient.get(
            	ITEM_INFO + ":" + itemId + ":"+ DESC);
		if(tbItemDesc!=null){
			return tbItemDesc;
		}

		TbItemDescExample example = new TbItemDescExample();
		TbItemDescExample.Criteria criteria = example.createCriteria();
		criteria.andItemIdEqualTo(itemId);
		List<TbItemDesc> itemDescList =
				this.tbItemDescMapper.selectByExampleWithBLOBs(example);
		if(itemDescList!=null && itemDescList.size()>0){
			//把数据保存到缓存
			redisClient.set(ITEM_INFO + ":" + itemId + ":"+ DESC,itemDescList.get(0));
			//设置缓存的有效期
			redisClient.expire(ITEM_INFO + ":" + itemId + ":"+ DESC,ITEM_INFO_EXPIRE);
			return itemDescList.get(0);
		}
		/********************解决缓存穿透************************/
		//把空对象保存到缓存
		redisClient.set(ITEM_INFO + ":" + itemId + ":"+ DESC,new TbItemDesc());
		//设置缓存的有效期
		redisClient.expire(ITEM_INFO + ":" + itemId + ":"+ DESC,30);
		return null;
	}
  • 修改ItemParamServiceImpl
	/**
     * 根据商品id查询商品规格
     * @param itemId
     * @return
     */
    @Override
    public TbItemParamItem selectTbItemParamItemByItemId(Long itemId) {
        //查询缓存
        TbItemParamItem tbItemParamItem = (TbItemParamItem) redisClient.get(
            						ITEM_INFO + ":" + itemId + ":"+ PARAM);
        if(tbItemParamItem!=null){
            return tbItemParamItem;
        }

        TbItemParamItemExample example = new TbItemParamItemExample();
        TbItemParamItemExample.Criteria criteria = example.createCriteria();
        criteria.andItemIdEqualTo(itemId);
        List<TbItemParamItem> tbItemParamItemList = 
            	tbItemParamItemMapper.selectByExampleWithBLOBs(example);
        if(tbItemParamItemList!=null && tbItemParamItemList.size()>0){
            //把数据保存到缓存
            redisClient.set(ITEM_INFO + ":" + itemId + ":"+ 
                            PARAM,tbItemParamItemList.get(0));
            //设置缓存的有效期
            redisClient.expire(ITEM_INFO + ":" + itemId + ":"+ PARAM,ITEM_INFO_EXPIRE);
            return tbItemParamItemList.get(0);
        }
        /********************解决缓存穿透************************/
        //把空对象保存到缓存
        redisClient.set(ITEM_INFO + ":" + itemId + ":"+ PARAM,new TbItemParamItem());
        //设置缓存的有效期
        redisClient.expire(ITEM_INFO + ":" + itemId + ":"+ PARAM,30);
        return null;
    }

5.缓存击穿

5.1.描述

缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对一个key不停进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。

在这里插入图片描述

5.2.解决方案

分布式锁:

  1. 设置热点数据永远不过期

  2. 加分布式锁

在这里插入图片描述

1、如何释放锁?del

2、业务处理失败?expire

5.3.common_redis

5.3.1.RedisClient

    /**
     * 分布式锁
     * @param key
     * @param count
     * @param value
     * @return
     */
    public Boolean setnx(String key, Object value, long time) {
        try {
            return redisTemplate.opsForValue().setIfAbsent(key, value, time,
                                                           TimeUnit.SECONDS);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

5.4.power_shop_item

5.4.1.application.yml

#分布式锁
SETNX_LOCK_BASC: SETNX_LOCK_BASC
SETNX_LOCK_DESC: SETNX_LOCK_DESC
SETNX_LOCK_PARAM: SETNX_LOCK_PARAM

5.4.2.service

  • 修改ItemServiceImpl
	/**
	 * 查询商品信息
	 * @param itemId
	 * @return
	 */
    @Override
    public TbItem selectItemInfo(Long itemId){
        //1、先查询redis,如果有直接返回
        TbItem tbItem = (TbItem) redisClient.get(ITEM_INFO+":"+itemId+":"+BASE);
        if(tbItem!=null){
            return tbItem;
        }
        /*****************解决缓存击穿***************/
        if(redisClient.setnx(SETNX_LOCK_BASC+":"+itemId,itemId,5L)){
            //2、再查询mysql,并把查询结果缓存到redis,并设置失效时间
            tbItem = tbItemMapper.selectByPrimaryKey(itemId);

            /*****************解决缓存穿透*****************/
            if(tbItem!=null){
                redisClient.set(ITEM_INFO+":"+itemId+":"+BASE,tbItem);
                redisClient.expire(ITEM_INFO+":"+itemId+":"+BASE,ITEM_INFO_EXPIRE);
            }else{
                redisClient.set(ITEM_INFO+":"+itemId+":"+BASE,new TbItem());
                redisClient.expire(ITEM_INFO+":"+itemId+":"+BASE,30L);
            }
            redisClient.del(SETNX_LOCK_BASC+":"+itemId);
            return tbItem;
        }else{
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
           return selectItemInfo(itemId);
        }
    }
	/**
	 * 根据商品 ID 查询商品介绍
	 * @param itemId
	 * @return
	 */
    @Override
    public TbItemDesc selectItemDescByItemId(Long itemId) {
        //1、先查询redis,如果有直接返回
        TbItemDesc tbItemDesc = (TbItemDesc) redisClient.get(ITEM_INFO + ":" + itemId + 
                                                             ":" + DESC);
        if(tbItemDesc!=null){
            return tbItemDesc;
        }
        if(redisClient.setnx(SETNX_DESC_LOCK_KEY+":"+itemId,itemId,30L)){
            //2、再查询mysql,并把查询结果缓存到redis,并设置失效时间
            tbItemDesc = tbItemDescMapper.selectByPrimaryKey(itemId);

            if(tbItemDesc!=null){
                redisClient.set(ITEM_INFO + ":" + itemId + ":" + DESC,tbItemDesc);
                redisClient.expire(ITEM_INFO + ":" + itemId + ":" + 
                                   DESC,ITEM_INFO_EXPIRE);

            }else{
                redisClient.set(ITEM_INFO + ":" + itemId + ":" + DESC,new TbItemDesc());
                redisClient.expire(ITEM_INFO + ":" + itemId + ":" + DESC,30L);
            }
            redisClient.del(SETNX_DESC_LOCK_KEY+":"+itemId);
            return tbItemDesc;
        }else{
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return selectItemDescByItemId(itemId);
        }
    }
  • 修改ItemParamServiceImpl
    /**
     * 根据商品id查询商品规格
     * @param itemId
     * @return
     */
    @Override
    public TbItemParamItem selectTbItemParamItemByItemId(Long itemId) {
        //1、先查询redis,如果有直接返回
        TbItemParamItem tbItemParamItem = (TbItemParamItem) redisClient.get(ITEM_INFO + 
                                                          d":" + itemId + ":" + PARAM);
        if(tbItemParamItem!=null){
            return tbItemParamItem;
        }
        if(redisClient.setnx(SETNX_LOCK_PARAM+":"+itemId,itemId,30L)){
            //2、再查询mysql,并把查询结果缓存到redis,并设置失效时间
            TbItemParamItemExample tbItemParamItemExample = new TbItemParamItemExample();
            TbItemParamItemExample.Criteria criteria = 
                tbItemParamItemExample.createCriteria();
            criteria.andItemIdEqualTo(itemId);
            List<TbItemParamItem> tbItemParamItems = 
                tbItemParamItemMapper.selectByExampleWithBLOBs(tbItemParamItemExample);
            if(tbItemParamItems!=null && tbItemParamItems.size()>0){
                tbItemParamItem = tbItemParamItems.get(0);
                redisClient.set(ITEM_INFO + ":" + itemId + ":" + PARAM,tbItemParamItem);
                redisClient.expire(ITEM_INFO + ":" + itemId + ":" + 
                                   PARAM,ITEM_INFO_EXPIRE);

            }else{
                redisClient.set(ITEM_INFO + ":" + itemId + ":" + PARAM,
                                							new TbItemParamItem());
                redisClient.expire(ITEM_INFO + ":" + itemId + ":" + PARAM,30L);
            }
            redisClient.del(SETNX_LOCK_PARAM+":"+itemId);
            return  tbItemParamItem;
        }else{
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return selectTbItemParamItemByItemId(itemId);
        }
    }

6.缓存雪崩

6.1.描述:

缓存雪崩,是指在某一个时间段,缓存集中过期失效。

在这里插入图片描述

6.2.解决方案:

缓存数据的过期时间设置随机或不同分类商品缓存不同周期或热门类目的商品缓存永不过期

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值