用SpringCloud Alibaba搭建属于自己的微服务(三十四)~业务开发~下订单核心接口开发

一.概述

之前章节开发的接口都是为了下订单接口的开发做了铺垫,下订单的接口内部将调用这些接口,是一个涉及到4-5个微服务的重量级接口.

二.下订单接口伪代码

可以看到该接口涉及的微服务有:server-user、server-goods、server-pay和server-order服务.
在这里插入图片描述

三.新建server-order微服务

1.创建maven工程.

在这里插入图片描述

2.server-order.pom中引入maven依赖.

<dependencies>
        <dependency>
            <groupId>com.ccm</groupId>
            <artifactId>assembly-mysql</artifactId>
            <version>1.0.0</version>
        </dependency>

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

3.编写服务启动类.

package com.ccm.server.order;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.ComponentScan;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

/**
 * @Description server-order服务启动类
 * @Author ccm
 * @CreateTime 2020/8/14 9:47
 */
@EnableFeignClients
@EnableDiscoveryClient //注册中心客户端
@ComponentScan(basePackages = "com.ccm")
@EnableSwagger2
@SpringBootApplication //声明为一个启动类
@MapperScan(basePackages = "com.ccm.server.order.dao.mysql.mapper")
public class ServerOrderApplication {
    public static void main(String[] args) {
        SpringApplication.run(ServerOrderApplication.class,args);
    }
}

4.编写bootstrap.yml配置文件.

server:
  port: 7677  #服务端口
spring:
  application:
    name: server-order #服务名称
  cloud:
    nacos:
      discovery:
        server-addr: 47.96.131.185:8849
      config:
        server-addr: 47.96.131.185:8849  #nacos config配置中心ip和端口
        file-extension: yaml  #文件扩展名格式,针对于默认的{spring.application.name}-${profile}.${file-extension:properties}配置
        enabled: true #开启或关闭配置中心
  datasource:
    username: root
    password: Test2016@
    url: jdbc:mysql://47.96.131.185:3306/order?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=GMT%2B8&autoReconnect=true
    type: com.alibaba.druid.pool.DruidDataSource
mybatis-plus:
  typeAliasesPackage: com.ccm.server.order.dao.mysql.domain  #数据库实体类包
  mapper-locations: classpath:mappering/*.xml #xml文件扫描

#自定义配置
server-order:
  serverNumber: 12

5.自定义配置的实体映射.

package com.ccm.server.order.constants;

import com.alibaba.fastjson.JSONObject;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

/**
 * @Description 自定义配置映射实体
 * @Author ccm
 * @CreateTime 2020/08/18 15:01
 */
@Slf4j
@Data
@Component
@ConfigurationProperties(prefix = "server-order")
public class ServerOrderProperties {

    private String serverNumber;

    @PostConstruct
    public void init() {
        log.info("ServerOrderProperties初始化完成......{}", JSONObject.toJSONString(this));
    }

}

6.swagger配置.

package com.ccm.server.order.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Parameter;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;

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

/**
 * @Description swagger配置
 * @Author ccm
 * @CreateTime 2020/08/14 17:38
 */
@Configuration
public class SwaggerConfig {

    @Bean
    public Docket createRestApi() {
        List<Parameter> pars = new ArrayList<Parameter>();

        ParameterBuilder ticketPar = new ParameterBuilder();
        ticketPar.name("ccm-token").description("必要参数(白名单接口无需传递)")
                .modelRef(new ModelRef("string")).parameterType("header")
                .required(false).build(); //header中的ticket参数非必填,传空也可以
        pars.add(ticketPar.build());

        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.ccm.server.order.controller"))    //swagger接口扫描包
                .paths(PathSelectors.any()).build().globalOperationParameters(pars);
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder().version("1.0.0")
                .title("欢迎")
                .description("光临")
                .termsOfServiceUrl("www.baidu.com")
                .build();
    }
}

7.gateway网关加入server-order微服务的路由.

spring:
  cloud:
    gateway:
      routes: #路由配置
        - id: server-order #路由名称,不配默认为UUID
          uri: lb://server-order #满足断言的路由到此服务
          predicates: #为一个数组,每个规则为并且的关系
            - Path=/api-order/** #断言表达式,如果args不写key的,会自动生成一个id,如下会生成一个xxx0的key,值为/foo/*
          filters: #请求路由转发前执行的filter,为数组
            - StripPrefix=1  #缩写,和name=StripPrefix,args,参数=1是一个意思,该过滤器为路由转发过滤去
            - name: AuthFilter

四.业务代码编写

1.控制层

(1).OrderController
package com.ccm.server.order.controller;

import com.ccm.common.exception.result.ResultSet;
import com.ccm.server.order.controller.req.PayOrderReq;
import com.ccm.server.order.service.OrderService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import javax.validation.constraints.Size;
import java.util.List;

/**
 *  @Description 订单控制层
 *  @Author ccm
 *  @CreateTime 2020/08/14 14:46
 */
@Api(tags = "订单控制层")
@RestController
@RequestMapping(value = "order")
public class OrderController {

    @Autowired
    private OrderService orderService;


    @ApiOperation(value = "下单")
    @PostMapping
    public ResultSet order(@Valid @Size(min = 1) @RequestBody List<PayOrderReq> payOrderReqList,
                                @ApiParam(hidden = true) @RequestHeader(name = "ccm-userId") Long userId) {
        orderService.order(payOrderReqList,userId);
        return ResultSet.success();
    }
}
(2).PayOrderReq
package com.ccm.server.order.controller.req;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import javax.validation.constraints.NotNull;
import java.util.List;

/**
 *  @Description 下订单入参实体
 *  @Author ccm
 *  @CreateTime 2020/08/14 10:52
 */
@Data
@ApiModel(value = "下订单入参实体")
public class PayOrderReq {

    @ApiModelProperty(value = "商品id")
    private Long skuId;

    @ApiModelProperty(value = "下单商品数量")
    private Integer skuNumber;
}

2.业务层

(1).OrderService
package com.ccm.server.order.service;

import com.ccm.server.order.controller.req.PayOrderReq;

import java.util.List;

/**
 *  @Description 订单业务层
 *  @Author ccm
 *  @CreateTime 2020/08/14 10:10
 */
public interface OrderService {


    /**
     * @Description 下单
     * @Author ccm
     * @CreateTime 2020/8/20 13:54
     * @Params [waitingPayReq, userId]
     * @Return void
     */
    void order(List<PayOrderReq> payOrderReqList, Long userId);
}
(2).OrderServiceImpl
package com.ccm.server.order.service.impl;

import com.alibaba.nacos.client.naming.utils.CollectionUtils;
import com.ccm.common.exception.CustomerException;
import com.ccm.common.exception.result.ResultSet;
import com.ccm.server.order.config.OrderIdGenerator;
import com.ccm.server.order.controller.req.PayOrderReq;
import com.ccm.server.order.dao.mysql.domain.OrderInfo;
import com.ccm.server.order.dao.mysql.domain.OrderSku;
import com.ccm.server.order.dao.mysql.mapper.OrderInfoMapper;
import com.ccm.server.order.dao.mysql.mapper.OrderSkuMapper;
import com.ccm.server.order.feign.ServerGoodsFeign;
import com.ccm.server.order.feign.ServerPayFeign;
import com.ccm.server.order.feign.req.ReduceStockReq;
import com.ccm.server.order.feign.vo.GoodsSkuVO;
import com.ccm.server.order.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 *  @Description 订单业务层实现类
 *  @Author ccm
 *  @CreateTime 2020/08/14 10:11
 */
@Service
public class OrderServiceImpl implements OrderService {

    @Autowired
    private OrderSkuMapper orderSkuMapper;

    @Autowired
    private OrderInfoMapper orderInfoMapper;

    @Autowired
    private ServerGoodsFeign serverGoodsFeign;

    @Autowired
    private OrderIdGenerator orderIdGenerator;

    @Autowired
    private ServerPayFeign serverPayFeign;


    @Override
    @Transactional(rollbackFor = Exception.class)
    public void order(List<PayOrderReq> payOrderReqList, Long userId) {
        //生成订单号
        String orderId = orderIdGenerator.nextOrderId();

        //获取商品的详细信息
        ResultSet<List<GoodsSkuVO>> resultSet = serverGoodsFeign.selectByIdList(payOrderReqList.stream()
                .map(PayOrderReq::getSkuId)
                .collect(Collectors.toList()));
        List<GoodsSkuVO> goodsSkuVOList = ResultSet.getFeignData(resultSet);
        if(CollectionUtils.isEmpty(goodsSkuVOList) || goodsSkuVOList.size()!=payOrderReqList.size()) {
            throw new CustomerException("商品不存在");
        }

        //扣商品库存
        List<ReduceStockReq> reduceStockReqList = payOrderReqList.stream()
                                                    .map(t -> new ReduceStockReq(t.getSkuId(),t.getSkuNumber()))
                                                    .collect(Collectors.toList());
        ResultSet.getFeignData(serverGoodsFeign.reduceStock(reduceStockReqList));

        //支付
        BigDecimal totalMoney = new BigDecimal(0.0d);
        for(PayOrderReq payOrderReq:payOrderReqList) {
            for(GoodsSkuVO goodsSkuVO:goodsSkuVOList) {
                if(payOrderReq.getSkuId().equals(goodsSkuVO.getId())) {
                    BigDecimal skuNumber = new BigDecimal(payOrderReq.getSkuNumber());
                    totalMoney = totalMoney.add(goodsSkuVO.getPrice().multiply(skuNumber));
                    break;
                }
            }
        }
        String flowingWaterId = ResultSet.getFeignData(serverPayFeign.pay(userId, totalMoney));

        //主订单表插入数据
        OrderInfo orderInfo = new OrderInfo();
        orderInfo.setId(orderId);
        orderInfo.setAmountMoney(totalMoney);
        orderInfo.setStatus(1);
        orderInfo.setUserId(userId);
        orderInfo.setFlowingWaterId(flowingWaterId);
        orderInfoMapper.insert(orderInfo);

        //子订单表插入数据
        ArrayList<OrderSku> orderSkuList = new ArrayList<>();
        payOrderReqList.forEach(payOrderReq -> {
            OrderSku orderSku = new OrderSku();
            orderSku.setOrderId(orderId);
            orderSku.setSkuNumber(payOrderReq.getSkuNumber());
            orderSku.setSkuId(payOrderReq.getSkuId());
            for(GoodsSkuVO goodsSkuVO:goodsSkuVOList) {
                if(payOrderReq.getSkuId().equals(goodsSkuVO.getId())) {
                    orderSku.setSkuMoney(goodsSkuVO.getPrice());
                    break;
                }
            }
            orderSkuList.add(orderSku);
        });
        orderSkuMapper.insertList(orderSkuList);
    }
}
(3).OrderIdGenerator
package com.ccm.server.order.config;

import com.ccm.server.order.constants.ServerOrderProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.UUID;

/**
 * @Description 订单id生成器
 * @Author ccm
 * @CreateTime 2020/02/16 15:17
 */
@Component
public class OrderIdGenerator {

    private Long lastTimeStamp = 0L; //上次生成订单id的时间戳

    @Autowired
    private ServerOrderProperties serverOrderProperties;
    
    /**
     * @Description 生成订单号
     * @Author zhouzhiwu
     * @CreateTime 2020/2/16 15:17
     * @Params []
     * @Return java.lang.String
     */
    public String nextOrderId() {

        Long timeStamp;
        synchronized (this.getClass()) {
            while(true) {
                Long nowTimeStamp = System.currentTimeMillis();
                if(!nowTimeStamp.equals(lastTimeStamp)) {
                    timeStamp = nowTimeStamp;
                    lastTimeStamp = nowTimeStamp;
                    break;
                }
            }
        }

        String timeStampStr = new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date(timeStamp));
        StringBuffer orderIdStringBuffer = new StringBuffer();
        orderIdStringBuffer.append(timeStampStr)
                .append(serverOrderProperties.getServerNumber())
                .append(UUID.randomUUID().toString());
        return orderIdStringBuffer.toString().substring(0,32);
    }
}

3.调用外部服务的feign层.

(1).ServerGoodsFeign
package com.ccm.server.order.feign;

import com.ccm.common.exception.result.ResultSet;
import com.ccm.server.order.feign.req.ReduceStockReq;
import com.ccm.server.order.feign.vo.GoodsSkuVO;
import io.swagger.annotations.ApiOperation;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.List;

/**
 *  @Description 调用server-goods服务feign层
 *  @Author ccm
 *  @CreateTime 2020/08/18 15:23
 */
@FeignClient(name = "server-goods")
public interface ServerGoodsFeign {

    @ApiOperation(value = "减少库存入参")
    @PutMapping(value = "goods/reduceStock")
    ResultSet reduceStock(@RequestBody List<ReduceStockReq> reduceStockReqList);

    @ApiOperation(value = "根据id获取sku")
    @GetMapping(value = "goods/selectByIdList")
    ResultSet<List<GoodsSkuVO>> selectByIdList(@RequestParam List<Long> idList);
}
(2).ServerPayFeign
package com.ccm.server.order.feign;

import com.ccm.common.exception.result.ResultSet;
import com.ccm.server.order.feign.req.ReduceStockReq;
import com.ccm.server.order.feign.vo.GoodsSkuVO;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 *  @Description 调用server-pay服务feign层
 *  @Author ccm
 *  @CreateTime 2020/08/18 15:23
 */
@FeignClient(name = "server-pay")
public interface ServerPayFeign {

    @ApiOperation(value = "支付")
    @PostMapping(value = "pay/pay")
    ResultSet<String> pay(@RequestHeader(name = "ccm-userId") Long userId,@RequestParam BigDecimal payMoney);
}
(3).ReduceStockReq
package com.ccm.server.order.feign.req;

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

/**
 *  @Description feign调用减少库存入参
 *  @Author ccm
 *  @CreateTime 2020/08/17 14:49
 */
@NoArgsConstructor
@AllArgsConstructor
@Data
public class ReduceStockReq {

    private Long skuId;

    private Integer number;
}
(4).GoodsSkuVO
package com.ccm.server.order.feign.vo;

import com.alibaba.fastjson.annotation.JSONField;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;

import java.util.Date;

/**
 *  @Description feign调用server-goods商品的结果视图
 *  @Author ccm
 *  @CreateTime 2020/08/19 10:48
 */
@Data
public class GoodsSkuVO {
    private Long id;

    private String name;

    private BigDecimal price;

    private Integer stock;

    private Date createTime;

    private Date updateTime;
}

3.持久层.

(1).OrderInfo
package com.ccm.server.order.dao.mysql.domain;

import lombok.Data;

import java.util.Date;

/**
 *  @Description order_info表实体类
 *  @Author ccm
 *  @CreateTime 2020/08/17 10:44
 */
@Data
public class OrderInfo {
    private String id;
    private BigDecimal amountMoney;
    private Integer status;
    private Long userId;
    private String flowingWaterId;
    private Date updateTime;
    private Date createTime;
}
(2).OrderSku
package com.ccm.server.order.dao.mysql.domain;

import lombok.Data;

import java.util.Date;

/**
 *  @Description order_sku表实体类
 *  @Author ccm
 *  @CreateTime 2020/08/17 10:46
 */
@Data
public class OrderSku {
    private Long id;
    private Long skuId;
    private Integer skuNumber;
    private BigDecimal skuMoney;
    private String orderId;
    private Date createTime;
}
(3).OrderInfoMapper
package com.ccm.server.order.dao.mysql.mapper;

import com.ccm.server.order.dao.mysql.domain.OrderInfo;

/**
 *  @Description order_info表持久层
 *  @Author ccm
 *  @CreateTime 2020/08/17 10:49
 */
public interface OrderInfoMapper {

    int insert(OrderInfo orderInfo);
}
(4).OrderSkuMapper
package com.ccm.server.order.dao.mysql.mapper;

import com.ccm.server.order.dao.mysql.domain.OrderSku;
import org.apache.ibatis.annotations.Param;

import java.util.List;

/**
 *  @Description order_sku表持久层
 *  @Author ccm
 *  @CreateTime 2020/08/17 10:49
 */
public interface OrderSkuMapper {
    int insertList(@Param("list") List<OrderSku> orderSkuList);
}
(5).OrderInfoMapper.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.ccm.server.order.dao.mysql.mapper.OrderInfoMapper" >
    <insert id="insert" parameterType="com.ccm.server.order.dao.mysql.domain.OrderInfo" >
        insert into order_info
        <trim prefix="(" suffix=")" suffixOverrides="," >
            <if test="id != null and id != ''" >
                id,
            </if>
            <if test="amountMoney != null and amountMoney != ''" >
                amount_money,
            </if>
            <if test="status != null and status != ''" >
                `status`,
            </if>
            <if test="userId != null and userId != ''" >
                user_id,
            </if>
            <if test="flowingWaterId != null and flowingWaterId != ''" >
                flowing_water_id,
            </if>
            update_time,
            create_time,
        </trim>
        <trim prefix="values (" suffix=")" suffixOverrides="," >
            <if test="id != null and id != ''" >
                #{id},
            </if>
            <if test="amountMoney != null and amountMoney != ''" >
                #{amountMoney},
            </if>
            <if test="status != null and status != ''" >
                #{status},
            </if>
            <if test="userId != null and userId != ''" >
                #{userId},
            </if>
            <if test="flowingWaterId != null and flowingWaterId != ''" >
                #{flowingWaterId},
            </if>
            now(),
            now(),
        </trim>
    </insert>
</mapper>
(6).OrderSkuMapper.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.ccm.server.order.dao.mysql.mapper.OrderSkuMapper" >
    <insert id="insertList" parameterType="java.util.List" >
        insert into order_sku
        (
        sku_id,
        sku_number,
        sku_money,
        order_id,
        create_time
        )
        values
        <foreach collection="list" item="item" index="index" separator=",">
            (
            #{item.skuId},
            #{item.skuNumber},
            #{item.skuMoney},
            #{item.orderId},
            now()
            )
        </foreach>
    </insert>
</mapper>
(7).order.order_info表结构

在这里插入图片描述

(8).order.order_sku表结构

在这里插入图片描述

五.测试

1.数据库基础数据准备

(1).goods.goods_sku增加两个商品

在这里插入图片描述

(2).user.user_info为ccm用户的账户余额到10万

在这里插入图片描述

2.启动gateway、server-user、server-goods、server-pay和server-order服务.在这里插入图片描述

3.打开gateway的swagger界面.

在这里插入图片描述

4.调用下单接口,买一台苹果11和一台华为P30.

在这里插入图片描述

在这里插入图片描述

5.查看数据库的数据变化.

(1).user.user_info表,账户余额被扣除

在这里插入图片描述

(2).pay.pay_flowing_water支付流水表,生成了流水记录

在这里插入图片描述

(3).goods.goods_sku商品表,库存减少了

在这里插入图片描述

(4).order.order_info主订单表,有了一笔主订单

在这里插入图片描述

(5).order.order_sku子订单表,有了两笔子订单

在这里插入图片描述
至此,完事!

您的点赞、收藏、转发和关注是我持续创作的动力!

源码地址:https://gitee.com/chouchimoo/ccm-mall.git(本章节代码分支:zj-34)

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
容错机制值得是服务容忍错误的能力,当系统出现网络延迟、网络中断、服务异常等原因,造成当前服务暂时不可用,Dubbo提供了容错机制来优雅地帮助服务调用者处理这类错误。 Dubbo默认提供了6种容错模式 Failover Cluster(默认):失败自动切换。当服务调用失败后,会切换到集群中的其他机器进行重试,默认重试次数为2,通过属性retries=2可以修改次数,但是重试次数增加会带来更长的响应延迟。(这种容错模式通常用于读操作) Failfast Cluster:快速失败。当服务调用失败后,立即报错,也就是指发起一次调用。(这种模式通常用于写操作,这种模式可以防止网络等问题导致数据重复新增,它等同于Failover Cluster retries=0) Failsafe Cluster:失败安全,出现异常时,直接忽略异常。(这种模式处理的数据相对而言不太重要) Failback Cluster:失败后自动回复。服务调用出现异常时,在后台记录这条失败的请求定时重发。(这种模式适合用于消息通知操作,保证这个请求一定发送成功,可以解决短期网络拥塞导致请求的丢失) Forking Cluster:并行调用集群中的多个服务,只要其中一个成功就返回。可以通过forks=2来设置最大并行数。(这种模式要保证请求的幂等性) Broadcast Cluster:广播调用所有的服务提供者,任意一个服务报错则表示服务调用失败。(这种模式需要所有节点都是正常的才能被调用) 服务调用者容错机制的配置方式 容错机制既可以在服务调用者中配置或在服务提供者中配置 在服务调用者中配置只对该调用者起作用(其他调用节点采用默认的) 在服务服务者中配置对所有调用者起作用 调用者的配置优先级高于服务者

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值