SpringCloudAlibaba【六】微服务架构下的秒杀案例

背景

分布式微服务中的秒杀是怎么实现的呢?接着看下去吧

我们实现一个秒杀微服务,流程逻辑如下

在这里插入图片描述

项目搭建

MySQL

create database if not exists demo;

use demo;

drop table if exists skill_goods;

create table if not exists skill_goods (
    id bigint(20) key,
    name varchar(200),
    price decimal(10,2),
    cost_price decimal(10,2),
    status char,
    num int,
    stock_count int,
    introduction varchar(200)
);

drop table if exists skill_order;

create table if not exists skill_order (
    id bigint key,
    skill_id bigint,
    money decimal,
    user_id varchar(20),
    create_time datetime,
    pay_time datetime,
    status char
);

在这里插入图片描述

在这里插入图片描述

IDEA中构建Maven父项目,再建两个子项目Product和Skill

在这里插入图片描述

POM

<?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>

    <groupId>org.example</groupId>
    <artifactId>springcloud-nacos</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>skill</module>
        <module>product</module>
    </modules>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <!--add-->

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.2.RELEASE</version>
    </parent>

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

        <!--nacos-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

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

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

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

        <dependency>
            <groupId>cn.itlym.shoulder</groupId>
            <artifactId>lombok</artifactId>
            <version>0.1</version>
        </dependency>

        <!--fastjson-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.69</version>
        </dependency>

    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>2.2.5.RELEASE</version>
                <scope>import</scope>
                <type>pom</type>
            </dependency>
        </dependencies>
    </dependencyManagement>

</project>

Product模块

创建如下目录结构

在这里插入图片描述

YML

server:
  port: 9000
spring:
  cloud:
    nacos:
      discovery:
        service: prod-serv
      server-addr: localhost:8848
    sentinel:
      transport:
        dashboard: localhost:8080
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/demo?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
    username: root
    password: fengzhizi98!

  redis:
    host: localhost
    port: 6379

ProductApplication

package com.product;

import com.fasterxml.jackson.databind.ser.std.NumberSerializers;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableScheduling
public class ProductApplication {

    public static void main(String[] args) {
        SpringApplication.run(ProductApplication.class);
    }

    @Bean
    public RedisTemplate<Object, Object> redisTemplate (RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);

        template.setKeySerializer(new StringRedisSerializer());
        return template;
    }

}

ProductController

package com.product.controller;

import com.product.entity.Goods;
import com.product.service.GoodService;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;

@RestController
public class ProductController {

    @Resource
    private GoodService goodService;

    @GetMapping("/product/{productId}")
    public Goods product(@PathVariable Long productId) {
        System.out.println("调用商品服务");
        return goodService.queryGoods(productId);
    }

    @PostMapping("/product")
    public String update(@RequestBody Goods goods) {
        goodService.update(goods);
        return "更新库存成功";
    }

}

GoodService

package com.product.service;

import com.alibaba.fastjson.JSON;
import com.product.dao.GoodsDao;
import com.product.entity.Goods;
import com.product.utils.JSONUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

@Component
public class GoodService {

    public static final String SKILL_GOODS_PHONE = "SKILL_GOODS_PHONE";
    public static final String SKILL_GOODS_QUEUE = "SKILL_GOODS_QUEUE";

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private GoodsDao goodsDao;

    @Scheduled(fixedDelay = 5000)
    public void prepareGood() {
        System.out.println("开始加载商品");

        Set<Long> set = redisTemplate.boundHashOps(SKILL_GOODS_PHONE).keys();
        List<Long> ids = new ArrayList<>();
        for (Long id : set) {
            ids.add(id);
        }

        List<Goods> list = null;
        //只查询出不在内存当中的商品信息,并加载到内存
        if (CollectionUtils.isEmpty(ids)) {
            list = goodsDao.findAllGoods();
        } else {
            list = goodsDao.findGoods(ids);
        }

        if (!CollectionUtils.isEmpty(list)) {
            for (Goods goods : list) {
                redisTemplate.boundHashOps(SKILL_GOODS_PHONE).put(goods.getId(), JSON.toJSONString(goods));
                redisTemplate.boundListOps(SKILL_GOODS_QUEUE+goods.getId()).leftPush(convertToArray(goods.getStock_count(), goods.getId()));
            }
        }

        //查看当前缓存中所有的商品信息
        Set keys = redisTemplate.boundHashOps(SKILL_GOODS_PHONE).keys();
        for (Object s : keys) {
            Goods goods = JSONUtil.toEntity((String) redisTemplate.boundHashOps(SKILL_GOODS_PHONE).get(s), Goods.class);
            System.out.println(goods.getName() + " 库存剩余:" + goods.getStock_count());
        }
    }

    private Long[] convertToArray(Integer stock_count, Long id) {
        Long[] idlong = new Long[stock_count];
        for (int i = 0; i < stock_count; i++) {
            idlong[i] = id;
        }
        return idlong;
    }

    //查询商品信息
    public Goods queryGoods (Long productId) {
        return JSONUtil.toEntity((String) redisTemplate.boundHashOps(SKILL_GOODS_PHONE).get(productId), Goods.class);
    }

    //更新商品信息
    public void update(Goods goods) {
        goodsDao.save(goods);
    }

}

GoodsDao

package com.product.dao;

import com.product.entity.Goods;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface GoodsDao extends JpaRepository<Goods, Long> {

    @Query(value = "select * from skill_goods where status = 1 and num > 0 and stock_count > 0 and id not in (?1)", nativeQuery = true)
    List<Goods> findGoods(List<Long> ids);

    @Query(value = "select * from skill_goods where status = 1 and num > 0 and stock_count > 0", nativeQuery = true)
    List<Goods> findAllGoods();

}

Goods

package com.product.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.*;
import java.io.Serializable;
import java.math.BigDecimal;

@Entity
@Table(name = "skill_goods")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Goods implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "name")
    private String name;

    @Column(name = "price")
    private BigDecimal price;

    @Column(name = "cost_price")
    private BigDecimal cost_price;

    @Column(name = "status")
    private String status;

    @Column(name = "num")
    private Integer num;

    @Column(name = "stock_count")
    private Integer stock_count;

    @Column(name = "introduction")
    private String introduction;

}

JSONUtil

说明:为了使Redis的序列化简化,我们使用JSON来存储对象信息,该工具主要功能使JSON和实体的转换

package com.product.utils;

import com.alibaba.fastjson.JSON;

public class JSONUtil {

    /***
     * 将JSON文本反序列化到对象
     */
    public static <T> T toEntity(String jsonString, Class<T> bean) {
        T t = (T) JSON.parseObject(jsonString, bean); // fastjson
        return t;
    }

}

Skill模块

创建如下目录结构

在这里插入图片描述

YML

server:
  port: 13000
spring:
  cloud:
    nacos:
      discovery:
        service: skill-serv
      server-addr: localhost:8848
    sentinel:
      transport:
        dashboard: localhost:8080
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/demo?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
    username: root
    password: fengzhizi98!

  redis:
    host: localhost
    port: 6379

SkillApplication

package com.skill;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
@EnableAsync
public class SkillApplication {

    public static void main(String[] args) {
        SpringApplication.run(SkillApplication.class);
    }

    @Bean
    public RedisTemplate<Object, Object> redisTemplate (RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);

        template.setKeySerializer(new StringRedisSerializer());
        return template;
    }

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }


}

SkillApplication

package com.skill.controller;

import com.skill.service.SkillGoodsService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
public class SkillController {

    @Resource
    private SkillGoodsService skillGoodsService;

    @GetMapping("/skill")
    public String skill(String userId, Long productId) {
        try{
            skillGoodsService.add(productId, userId);
            return "秒杀成功";
        } catch (Exception e) {
            return e.getMessage();
        }
    }

}

SkillGoodsService

package com.skill.service;

import com.alibaba.fastjson.JSON;
import com.skill.entity.SkillEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;
import java.util.UUID;

@Service
public class SkillGoodsService {

    public static final String SKILL_GOODS_LIST = "SKILL_GOODS_LIST";

    public static final String SKILL_GOODS_ONLY = "SKILL_GOODS_ONLY";

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private MultiThreadOrder multiThreadOrder;

    @Transactional
    public void add(Long productId, String userId) {

        //模拟多用户
        userId = UUID.randomUUID().toString();

        //判断用户是否参加过抢单
        Long time = redisTemplate.boundHashOps(SKILL_GOODS_ONLY).increment(userId, 1L);
        try {
            if (time > 1) {
                throw new Exception("重复抢单,不要贪心");
            }
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }

        //封装对象放入Redis队列
        SkillEntity skill = new SkillEntity();
        skill.setProductId(productId);
        skill.setUserId(userId);

        redisTemplate.boundListOps(SKILL_GOODS_LIST).leftPush(JSON.toJSONString(skill));

        try {
            multiThreadOrder.createOrder();
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }

    }

}

ProductService

package com.skill.service;

import com.skill.entity.Goods;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

@Service
public class ProductService {

    @Autowired
    private RestTemplate restTemplate;

    public Goods queryByProductId(Long productId) {
        return restTemplate.getForObject("http://prod-serv/product/" + productId, Goods.class);
    }

    public void update(Goods goods) {
        ResponseEntity<String> result = restTemplate.postForEntity("http://prod-serv/product", goods, String.class);
        System.out.println(result.getBody());
    }

}

MultiThreadOrder

package com.skill.service;

import com.skill.dao.SkillOrderDao;
import com.skill.entity.Goods;
import com.skill.entity.SkillEntity;
import com.skill.entity.SkillOrder;
import com.skill.utils.JSONUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import java.util.Date;

@Component
public class MultiThreadOrder {

    public static final String SKILL_GOODS_PHONE = "SKILL_GOODS_PHONE";

    public static final String SKILL_GOODS_LIST = "SKILL_GOODS_LIST";

    public static final String SKILL_GOODS_QUEUE = "SKILL_GOODS_QUEUE";

    private static final String SKILL_GOODS_ONLY = "SKILL_GOODS_ONLY";

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private SkillOrderDao skillOrderDao;

    @Autowired
    private ProductService productService;

    @Async
    public void createOrder() {
        System.out.println("开始异步抢单");

        SkillEntity skill = JSONUtil.toEntity((String) redisTemplate.boundListOps(SKILL_GOODS_LIST).rightPop(), SkillEntity.class);
        if (skill == null) {
            return;
        }
        Long productId = skill.getProductId();
        String userId = skill.getUserId();

        Goods goods = productService.queryByProductId(productId);

        try {
            if (goods == null) {
                throw new Exception("商品被抢光");
            }
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }

        Long stockId = (Long) redisTemplate.boundListOps(SKILL_GOODS_QUEUE + productId).rightPop();
        if (stockId == null) {
            System.out.println("该商品已被秒杀完毕");
            redisTemplate.boundHashOps(SKILL_GOODS_ONLY).delete(userId);
            redisTemplate.boundHashOps(SKILL_GOODS_PHONE).delete(goods.getId());
            goods.setStock_count(0);
            productService.update(goods);
            return;
        }

        SkillOrder skillOrder = new SkillOrder();
        skillOrder.setMoney(goods.getCost_price());
        skillOrder.setPayTime(new Date());
        skillOrder.setStatus("0");
        skillOrder.setUserId(userId);
        skillOrder.setCreateTime(new Date());
        skillOrder.setSkillId(productId);

        skillOrderDao.save(skillOrder);

        System.out.println("结束异步抢单");
    }
}

SkillOrderDao

package com.skill.dao;

import com.skill.entity.SkillOrder;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface SkillOrderDao extends JpaRepository<SkillOrder, Long> {
}

Goods

package com.skill.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.*;
import java.io.Serializable;
import java.math.BigDecimal;

@Entity
@Table(name = "skill_goods")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Goods implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "name")
    private String name;

    @Column(name = "price")
    private BigDecimal price;

    @Column(name = "cost_price")
    private BigDecimal cost_price;

    @Column(name = "status")
    private String status;

    @Column(name = "num")
    private Integer num;

    @Column(name = "stock_count")
    private Integer stock_count;

    @Column(name = "introduction")
    private String introduction;

}

SkillEntitiy

package com.skill.entity;

import lombok.Data;

import java.io.Serializable;

@Data
public class SkillEntity implements Serializable {
    private Long productId;

    private String userId;
}

SkillOrder

package com.skill.entity;


import lombok.Data;

import javax.persistence.*;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;

@Data
@Entity
@Table(name = "skill_order")
public class SkillOrder implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private String id;

    @Column(name = "skill_id")
    private Long skillId;

    @Column(name = "money")
    private BigDecimal money;

    @Column(name = "user_id")
    private String userId;

    @Column(name = "create_time")
    private Date createTime;

    @Column(name = "pay_time")
    private Date payTime;

    @Column(name = "status")
    private String status;
}

JSONUtil

package com.skill.utils;

import com.alibaba.fastjson.JSON;

public class JSONUtil {

    /***
     * 将JSON文本反序列化到对象
     */
    public static <T> T toEntity(String jsonString, Class<T> bean) {
        T t = (T) JSON.parseObject(jsonString, bean); // fastjson
        return t;
    }

}

测试

先启动Redis,Sentinel,MySQL,Nacos

Nacos

在这里插入图片描述

Sentinel

在这里插入图片描述

Redis

在这里插入图片描述

MySQL

在这里插入图片描述

启动项目

在这里插入图片描述

Apifox高并发测试

在这里插入图片描述

结果

IPHONE12 已经被抢光了

在这里插入图片描述

数据库IPHONE12的库存也清零了

在这里插入图片描述

项目代码

项目代码

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
购买提醒:全程代码实战,本系列课程建议有Java开发经验2年以上的学员观看和购买。录制本套教程的初衷,通过从业10年接触过很多的技术开发人员,尤其在面试一些技术人员的时候,发现他们的技术知识更新较慢,很多人渴望接触到高并发系统和一些高级技术架构,为了帮助更多人能够提升自己和接触到这类技术架构,并满足企业的人才需求,利用业余时间我开始录制这套教程。通过录制教程有很多学员给我反馈信息,给了我很大的鼓舞,当然也有吐槽,我想说的是技术是没有边界的,脱离一线业务场景去谈技术,都是耍流氓的。如对我录制的教程内容有建议请及时交流。本套课程历经1年时间研发,案例来源于真实业务场景抽离,由从业10年企业一线架构师实录,没有基础不建议购买。购买后提供企业级多方位指导,通过本套案例可以让你学习目前主流的微服务技术架构和多种企业级高并发和海量数据、高可用、分布式、支付、多语言、前后端分离等技术的综合应用解决方案。在开始本课程前给大家科普几个概念: 高并发是指在比较短的时间内有大量的访问者访问目标系统,系统负载饱和或者过载宕机。 高并发的应用,我们应该都有用过或者见过,比如天猫、京东、拼多多、亚马逊的秒杀抢购还有12306的抢票。我们在体验应用的时候,可能并不会像到这种高并发系统背后的技术实现难度。高并发系统都存在这几种问题,高并发读、高并发写、访问高峰突发性、反馈结果的即时性。在抢购的时候,尤其是抢购火车票的时候,我们经常会疯狂的刷库存,几亿用户产生非常大的高并发读; 通过以上的科普相信大家对课程有一个基本的认知了,本套教程以应用最为广泛的电商系统为标本,给大家构建一个亿级微服务秒杀系统,让大家跟着我的步骤能学习行为背后的原理。本课程采用全新的微服务架构,运用了很多工业界企业解决方案和高级技术,带大家手把手实现一个高性能,高并发,高可用等的亿级微服务秒杀系统,本课程会包含很多高级的内容,比如微服务架构、分布式部署方案、多线程、支付、多语言、全链路性能压力测试等,让大家在实战中学习知识,在实战中不断进步。该课程是一个完整的微服务架构秒杀系统项目代码,案例具有很高的商业价值,大家可以根据自己的业务进行修改,便可以使用。本套课程可以满足世面上绝大多数企业级的业务场景,本课程全部代码可以直接部署企业,普通集群,支撑**并发;集群规模大,支撑亿级并发。本课程包含的技术: IDEA集成开发工具 SpringBoot2.0.2.RELEASE SpringCloudFinchley.RELEASE Thymeleaf(模板引擎技术) 微信支付 支付宝支付 银联支付 分布式数据库Mycat MySQL Druid RabbitMQ 分布式事务 分布式锁 事件驱动 多线程 MyBatis QuartzEhcache Redis Hystrix 单点登陆CAS Nginx Lua Restful AOP技术 性能压力测试Jemter VUE+jQuery+Ajax+NodeJS Python Go语言课程亮点: 1.与企业无缝对接、真实工业界产品 2.主流支付全覆盖(微信、支付宝、银联) 3.前后端分离(主流技术架构) 4.实现高并发请求和实现高可用架构解决方案 5.多语言(Java、Go、Python) 6.亿级微服务秒杀系统(支撑海量数据) 7.大型系统分布式部署方案 8.全链路性能压力测试  9.分布式事务解决方案 10.事件驱动设计解决方案 11.多线程技术的实战应用 12.高并发下的服务降级、限流实战 13.分布式架构师下实现分布式定时调度 14.集成MyBatis实现多数据源路由实战 15.集成Redis缓存实战 16.Eureka注册中心 17.OpenFeign声明式服务调用 18.Hystrix服务熔断降级方式 19.基于Hystrix实现接口降级实战 20.集成SpringCloud实现统一整合方案 21.全程代码实操,提供全部代码和资料 22.提供答疑和提供企业技术方案咨询购买提醒: 我本人在企业从业10年,因为热爱,所以坚持,下一个10年依然会在企业一线服务,因此对于课程中的技术点可以提供全方面的业务场景解决方案。我本人并非培训机构脱离一线业务场景的讲师,从业多年接触过大量的真实业务场景案例,后面会逐步通过教程案例分享我多年的实战经验,送给同行一句话:技术是服务于业务的,脱离一线业务场景就是耍流氓。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BirdMan98

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值