苍穹外卖-SpringTask、订单状态定时处理、WebSocket来单提醒和催单提醒

目录

1. Spring Task

1.1 介绍

1.2 cron表达式

1.3 入门案例

1.3.1 Spring Task使用步骤

1.3.2 代码开发

1.3.3 功能测试

2.订单状态定时处理

2.1 需求分析

2.2 代码开发

2.3 功能测试

3. WebSocket

3.1 介绍

3.2 入门案例

3.2.1 案例分析

3.2.2 代码开发

3.2.3 功能测试

4. 来单提醒

4.1 需求分析和设计

4.2 代码开发

4.3 功能测试

4.4 代码提交

5. 客户催单

5.1 需求分析和设计

5.2 代码开发

5.2.1 Controller层

5.2.2 Service层接口

5.2.3 Service层实现类

5.2.4 Mapper层

5.3 功能测试

5.4 代码提交


  • Spring Task

  • 订单状态定时处理

  • WebSocket

  • 来单提醒

  • 客户催单

功能实现:订单状态定时处理来单提醒客户催单

订单状态定时处理:

来单提醒:

客户催单:

1. Spring Task

1.1 介绍

Spring Task 是Spring框架提供的任务调度工具,可以按照约定的时间自动执行某个代码逻辑。

定位:定时任务框架

作用:定时自动执行某段Java代码

为什么要在Java程序中使用Spring Task?

应用场景:

1). 信用卡每月还款提醒

 

2). 火车票售票系统处理未支付订单

3). 入职纪念日为用户发送通知

强调:只要是需要定时处理的场景都可以使用Spring Task

1.2 cron表达式

cron表达式其实就是一个字符串,通过cron表达式可以定义任务触发的时间

构成规则:分为6或7个域,由空格分隔开,每个域代表一个含义

每个域的含义分别为:秒、分钟、小时、日、月、周、年(可选)

举例:

2022年10月12日上午9点整 对应的cron表达式为:0 0 9 12 10 ? 2022

说明:一般的值不同时设置,其中一个设置,另一个用?表示。

比如:描述2月份的最后一天,最后一天具体是几号呢?可能是28号,也有可能是29号,所以就不能写具体数字。

为了描述这些信息,提供一些特殊的字符。这些具体的细节,我们就不用自己去手写,因为这个cron表达式,它其实有在线生成器。

cron表达式在线生成器:https://cron.qqe2.com/

可以直接在这个网站上面,只要根据自己的要求去生成corn表达式即可。所以一般就不用自己去编写这个表达式。

通配符:

* 表示所有值;

? 表示未说明的值,即不关心它为何值;

- 表示一个指定的范围;

, 表示附加一个可能值;

/ 符号前表示开始时间,符号后表示每次递增的值;

cron表达式案例:

*/5 * * * * ? 每隔5秒执行一次

0 */1 * * * ? 每隔1分钟执行一次

0 0 5-15 * * ? 每天5-15点整点触发

0 0/3 * * * ? 每三分钟触发一次

0 0-5 14 * * ? 在每天下午2点到下午2:05期间的每1分钟触发

0 0/5 14 * * ? 在每天下午2点到下午2:55期间的每5分钟触发

0 0/5 14,18 * * ? 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发

0 0/30 9-17 * * ? 朝九晚五工作时间内每半小时

0 0 10,14,16 * * ? 每天上午10点,下午2点,4点

1.3 入门案例

1.3.1 Spring Task使用步骤

1). 导入maven坐标 spring-context(已存在)

2). 启动类添加注解 @EnableScheduling 开启任务调度

3). 自定义定时任务类

4). 类中定义方法,方法上加上@Scheduled(cron = "cron表达式")

5). 启

1.3.2 代码开发

编写定时任务类:

进入sky-server模块中

package com.sky.task;

import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.util.Date;

/**
 * 自定义定时任务类
 */
@Component
@Slf4j
public class MyTask {

    /**
     * 定时任务 每隔5秒触发一次
     */
    @Scheduled(cron = "0/5 * * * * ?")
    public void executeTask(){
        log.info("定时任务开始执行:{}",new Date());
    }
}

开启任务调度:

启动类添加注解 @EnableScheduling

package com.sky;

import lombok.extern.slf4j.Slf4j;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@EnableScheduling             //开启任务调度功能
@EnableCaching                //开启缓存注解功能
@MapperScan("com.sky.mapper") //指定扫描mapper
@SpringBootApplication
@EnableTransactionManagement //开启注解方式的事务管理
@Slf4j
public class SkyApplication {
    public static void main(String[] args) {
        SpringApplication.run(SkyApplication.class, args);
        log.info("server started");
    }
}

1.3.3 功能测试

启动服务,查看日志

每隔5秒执行一次。

2.订单状态定时处理

2.1 需求分析

用户下单后可能存在的情况:

  • 下单后未支付,订单一直处于“待支付”状态

  • 用户收货后管理端未点击完成按钮,订单一直处于“派送中”状态

支付超时的订单如何处理? 派送中的订单一直不点击完成如何处理?

对于上面两种情况需要通过定时任务来修改订单状态,具体逻辑为:

  • 通过定时任务每分钟检查一次是否存在支付超时订单(下单后超过15分钟仍未支付则判定为支付超时订单),如果存在则修改订单状态为“已取消”

  • 通过定时任务每天凌晨1点检查一次是否存在“派送中”的订单,如果存在则修改订单状态为“已完成”

2.2 代码开发

1). 自定义定时任务类OrderTask:

package com.sky.task;

import com.sky.entity.Orders;
import com.sky.mapper.OrdersMapper;
import com.sky.service.OrdersService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.List;

@Slf4j
@Component
public class OrderTask {

    @Autowired
    private OrdersMapper ordersMapper;

    /**
     * 每分钟检查一次订单是否超时,如果超时则取消订单(下单超过15分钟未支付就代表超时,需要修改状态为已取消)
     */
    @Scheduled(cron = "0 * * * * ?") //每分钟检查一次
    public void processOutTimeOrder() {
        log.info("查看是否存在【超时未支付】的订单");
        //1.查询数据库orders表,条件:状态-待付款,下单时间 < 当前时间-15分钟
        //select * from orders where status = 1 and order_time < 当前时间-15分钟;
        LocalDateTime time = LocalDateTime.now().minusMinutes(15);

        List<Orders> orderList = ordersMapper.selectByStatusAndOrderTime(Orders.PENDING_PAYMENT, time);

        //2.如果查询到了数据,代表存在超时未支付的订单,需要修改订单的状态为"status = 6(已取消)"
        //update orders set status = 6 where status = 1 and order_time < 当前时间-15分钟;
        if (orderList != null && orderList.size() > 0) {
            for (Orders order : orderList) {
                order.setStatus(Orders.CANCELLED);
                order.setCancelReason("支付超时,取消订单");
                order.setCancelTime(LocalDateTime.now());
                ordersMapper.update(order);
            }
        }
    }

    /**
     * 每天凌晨1点检查一次订单表,查看是否存在"派送中"的订单,如果存在修改状态为"已完成"
     * 不查最近1小时的订单,以免发生冲突
     */
    @Scheduled(cron = "0 0 1 * * ?")
    //@Scheduled(cron = "0 8 19 * * ?")  //TODO: 测试用:19:08分触发
    public void processDeliveryOrder() {
        log.info("查看是否存在【派送中】的订单");
        //1.查询数据库orders表,条件:状态-派送中,下单时间 < 当前时间-1小时
        //select * from orders where status = 4 and order_time < 当前时间-1小时;
        LocalDateTime time = LocalDateTime.now().minusHours(1);

        List<Orders> orderList = ordersMapper.selectByStatusAndOrderTime(Orders.DELIVERY_IN_PROGRESS, time);

        //2.如果查询到了数据,代表存在一直派送中的订单,需要修改订单的状态为"status = 5(已完成)"
        //update orders set status = 5 where status = 4 and order_time < 当前时间-1小时;
        if (orderList != null && orderList.size() > 0) {
            for (Orders order : orderList) {
                order.setStatus(Orders.COMPLETED);
                order.setDeliveryTime(time);
                ordersMapper.update(order);
            }
        }
    }
}

2). 在OrderMapper接口中扩展方法:

/**
     * 根据状态和下单时间查询订单
     * @param status
     * @param time
     * @return
     */
    @Select("select * from orders where status = #{status} and order_time < #{time}")
    List<Orders> selectByStatusAndOrderTime(Integer status, LocalDateTime time);

2.3 功能测试

可以通过如下方式进行测试:

  • 查看控制台sql

  • 查看数据库中数据变化

处理“派送中”状态的订单任务测试自已完成,测试步骤和上述一致。可适当修改cron表达式,改变任务执行频率,方便测试。


3. WebSocket

3.1 介绍

WebSocket 是基于 TCP 的一种新的网络协议。它实现了浏览器与服务器全双工通信——浏览器和服务器只需要完成一次握手,两者之间就可以创建持久性的连接, 并进行双向数据传输。

HTTP协议和WebSocket协议对比:

  • HTTP是短连接

  • WebSocket是长连接

  • HTTP通信是单向的,基于请求响应模式

  • WebSocket支持双向通信

  • HTTP和WebSocket底层都是TCP连接

思考:既然WebSocket支持双向通信,功能看似比HTTP强大,那么我们是不是可以基于WebSocket开发所有的业务功能?

WebSocket缺点:

  • 服务器长期维护长连接需要一定的成本
  • 各个浏览器支持程度不一
  • WebSocket是长连接,受网络限制比较大,需要处理好重连

结论:WebSocket并不能完全取代HTTP,它只适合在特定的场景下使用

WebSocket应用场景:

1). 视频弹幕

2). 网页聊天

3). 体育实况更新

4). 股票基金报价实时更新

3.2 入门案例

3.2.1 案例分析

需求:实现浏览器与服务器全双工通信。浏览器既可以向服务器发送消息,服务器也可主动向浏览器推送消息。

效果展示:

实现步骤:

1). 直接使用websocket.html页面作为WebSocket客户端

2). 导入WebSocket的maven坐标

3). 导入WebSocket服务端组件WebSocketServer,用于和客户端通信

4). 导入配置类WebSocketConfiguration,注册WebSocket的服务端组件

5). 导入定时任务类WebSocketTask,定时向客户端推送数据

3.2.2 代码开发

1). 导入maven坐标

在sky-server模块pom.xml中已定义

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

2). 定义WebSocket服务端组件

直接导入到sky-server模块即可

package com.sky.websocket;

import org.springframework.stereotype.Component;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

/**
 * WebSocket服务
 */
@Component
@ServerEndpoint("/ws/{sid}")
public class WebSocketServer {

    //存放会话对象
    private static Map<String, Session> sessionMap = new HashMap();

    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("sid") String sid) {
        System.out.println("客户端:" + sid + "建立连接");
        sessionMap.put(sid, session);
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息
     */
    @OnMessage
    public void onMessage(String message, @PathParam("sid") String sid) {
        System.out.println("收到来自客户端:" + sid + "的信息:" + message);
    }

    /**
     * 连接关闭调用的方法
     *
     * @param sid
     */
    @OnClose
    public void onClose(@PathParam("sid") String sid) {
        System.out.println("连接断开:" + sid);
        sessionMap.remove(sid);
    }

    /**
     * 群发
     *
     * @param message
     */
    public void sendToAllClient(String message) {
        Collection<Session> sessions = sessionMap.values();
        for (Session session : sessions) {
            try {
                //服务器向客户端发送消息
                session.getBasicRemote().sendText(message);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

}

3). 定义配置类,注册WebSocket的服务端组件

package com.sky.websocket;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

/**
 * WebSocket配置类,用于注册WebSocket的Bean
 */
@Configuration
public class WebSocketConfiguration {

    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }

}

4). 定义定时任务类,定时向客户端推送数据

package com.sky.websocket;

import com.sky.websocket.WebSocketServer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

@Component
public class WebSocketTask {
    @Autowired
    private WebSocketServer webSocketServer;

    /**
     * 通过WebSocket每隔5秒向客户端发送消息
     */
//    @Scheduled(cron = "0/5 * * * * ?")
    public void sendMessageToClient() {
        webSocketServer.sendToAllClient("这是来自服务端的消息:" + DateTimeFormatter.ofPattern("HH:mm:ss").format(LocalDateTime.now()));
    }
}

3.2.3 功能测试

启动服务,打开websocket.html页面

浏览器向服务器发送数据:

服务器向浏览器间隔5秒推送数据:

4. 来单提醒

4.1 需求分析和设计

用户下单并且支付成功后,需要第一时间通知外卖商家。通知的形式有如下两种:

  • 语音播报

  • 弹出提示框

设计思路:

  • 通过WebSocket实现管理端页面和服务端保持长连接状态
  • 当客户支付后,调用WebSocket的相关API实现服务器向客户端推送消息
  • 客户端浏览器解析服务器推送的消息,判断是来单提醒还是客户催单(代码中用type表示),进行相应的语音播报
  • 约定服务端发送给客户端浏览器的数据格式为JSON,字段包括:type,orderId,content

  • type 为消息类型,1为来单提醒 2为客户催单

  • orderId 为订单id

  • content 为消息内容

4.2 代码开发

在OrderServiceImpl中注入WebSocketServer对象,修改paySuccess方法,但是由于我们这里并没有真正实现微信支付功能,只是模拟跳过了,所以我们最好把这段代码写在payment方法中,订单提交点击支付发送请求之后就会调用此方法,然后执行发送来单提醒的逻辑:

/**
     * 订单支付
     *
     * @param ordersPaymentDTO
     * @return
     */
    public OrderPaymentVO payment(OrdersPaymentDTO ordersPaymentDTO) throws Exception {
        // 当前登录用户id
        Long userId = BaseContext.getCurrentId();
        User user = userMapper.selectById(userId);

        //调用微信支付接口,生成预支付交易单
//        JSONObject jsonObject = weChatPayUtil.pay(
//                ordersPaymentDTO.getOrderNumber(), //商户订单号
//                new BigDecimal(0.01), //支付金额,单位 元
//                "苍穹外卖订单", //商品描述
//                user.getOpenid() //微信用户的openid
//        );
//
//        if (jsonObject.getString("code") != null && jsonObject.getString("code").equals("ORDERPAID")) {
//            throw new OrderBusinessException("该订单已支付");
//        }

        // 根据订单号查询当前用户的订单
        Orders ordersDB = ordersMapper.getByNumberAndUserId(ordersPaymentDTO.getOrderNumber(), userId);

        //通过Websocket向客户端浏览器推送消息 type orderId content
        Map map = new HashMap();
        map.put("type", 1); //1表示来单提醒 2表示客户催单
        map.put("orderId", ordersDB.getId());
        map.put("content", "订单号:" + ordersPaymentDTO.getOrderNumber());

        String json = JSON.toJSONString(map);
        webSocketServer.sendToAllClient(json);


        JSONObject jsonObject = new JSONObject();
        jsonObject.put("code","ORDERPAID");
        OrderPaymentVO vo = jsonObject.toJavaObject(OrderPaymentVO.class);
        vo.setPackageStr(jsonObject.getString("package"));
        Integer OrderPaidStatus = Orders.PAID;//支付状态,已支付
        Integer OrderStatus = Orders.TO_BE_CONFIRMED; //订单状态,待接单
        LocalDateTime check_out_time = LocalDateTime.now();//更新支付时间
        ordersMapper.updateStatus(OrderStatus, OrderPaidStatus, check_out_time, this.orders.getId());
        return vo;
    }

正常如果可以实现成功支付的功能是要写在paysuccess方法中的,因为支付成功之后会触发回调函数,并且调用paysuccess方法:

/**
     * 支付成功,修改订单状态
     *
     * @param outTradeNo
     */
    public void paySuccess(String outTradeNo) {
        // 当前登录用户id
        Long userId = BaseContext.getCurrentId();

        // 根据订单号查询当前用户的订单
        Orders ordersDB = ordersMapper.getByNumberAndUserId(outTradeNo, userId);

        // 根据订单id更新订单的状态、支付方式、支付状态、结账时间
        Orders orders = Orders.builder()
                .id(ordersDB.getId())
                .status(Orders.TO_BE_CONFIRMED)
                .payStatus(Orders.PAID)
                .checkoutTime(LocalDateTime.now())
                .build();

        ordersMapper.update(orders);

//        //通过Websocket向客户端浏览器推送消息 type orderId content
//        Map map = new HashMap();
//        map.put("type", 1); //1表示来单提醒 2表示客户催单
//        map.put("orderId", ordersDB.getId());
//        map.put("content", "订单号:" + outTradeNo);
//
//        String json = JSON.toJSONString(map);
//        webSocketServer.sendToAllClient(json);
    }

4.3 功能测试

可以通过如下方式进行测试:

  • 查看浏览器调试工具数据交互过程

  • 前后端联调

1). 登录管理端后台

登录成功后,浏览器与服务器建立长连接

注意:测试时重新编译Java服务器之后,也要重新登录客户端,让他们重新建立连接。

查看控制台日志

2). 小程序端下单支付

修改回调地址,利用内网穿透获取域名

每一次关闭后重新启动服务都要重新修改配置,用cpolar。

下单支付

3). 查看来单提醒

支付成功后,后台收到来单提醒,并有语音播报,如果出现一直响不停的情况,我们只需要把WebSocketTask中的定时任务注释掉就可以了。

package com.sky.websocket;

import com.sky.websocket.WebSocketServer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

@Component
public class WebSocketTask {
    @Autowired
    private WebSocketServer webSocketServer;

    /**
     * 通过WebSocket每隔5秒向客户端发送消息
     */
//    @Scheduled(cron = "0/5 * * * * ?")
    public void sendMessageToClient() {
        webSocketServer.sendToAllClient("这是来自服务端的消息:" + DateTimeFormatter.ofPattern("HH:mm:ss").format(LocalDateTime.now()));
    }
}

4.4 代码提交

5. 客户催单


5.1 需求分析和设计

用户在小程序中点击催单按钮后,需要第一时间通知外卖商家。通知的形式有如下两种:

  • 语音播报

  • 弹出提示框

设计思路:

  • 通过WebSocket实现管理端页面和服务端保持长连接状态

  • 当用户点击催单按钮后,调用WebSocket的相关API实现服务端向客户端推送消息

  • 客户端浏览器解析服务端推送的消息,判断是来单提醒还是客户催单,进行相应的消息提示和语音播报 约定服务端发送给客户端浏览器的数据格式为JSON,字段包括:type,orderId,content

    • type 为消息类型,1为来单提醒 2为客户催单

    • orderId 为订单id

    • content 为消息内容

当用户点击催单按钮时,向服务端发送请求。

接口设计(催单):

5.2 代码开发

5.2.1 Controller层

根据用户催单的接口定义,在user/OrderController中创建催单方法:

 /**
     * 客户催单
     *
     * @param id
     * @return
     */
    @GetMapping("/reminder/{id}")
    @ApiOperation("客户催单")
    public Result reminder(@PathVariable("id") Long id) {
        ordersService.reminder(id);
        return Result.success();
    }

5.2.2 Service层接口

在OrderService接口中声明reminder方法:

 /**
     * 催单
     * @param id
     */
    void reminder(Long id);

5.2.3 Service层实现类

在OrderServiceImpl中实现reminder方法:

/**
     * 客户催单
     * @param id
     */
    @Override
    public void reminder(Long id) {
        // 根据id查询订单
        Orders ordersDB = ordersMapper.getById(id);

        // 校验订单是否存在
        if (ordersDB == null) {
            throw new OrderBusinessException(MessageConstant.ORDER_STATUS_ERROR);
        }

        Map map = new HashMap();
        map.put("type",2);//1表示来单提醒,2表示客户催单
        map.put("orderId",id);
        map.put("content","订单号:"+ordersDB.getNumber());

        //通过webSocket推送催单消息给客户端
        webSocketServer.sendToAllClient(JSON.toJSONString(map));

    }

5.2.4 Mapper层

在OrderMapper中添加getById:

	/**
     * 根据id查询订单
     * @param id
     */
    @Select("select * from orders where id=#{id}")
    Orders getById(Long id);

5.3 功能测试

可以通过如下方式进行测试:

  • 查看浏览器调试工具数据交互过程

  • 前后端联调

1). 登录管理端后台

登录成功后,浏览器与服务器建立长连接

查看控制台日志

2). 用户进行催单

用户可在订单列表或者订单详情,进行催单

3). 查看催单提醒

既有催单弹窗,同时语音播报

5.4 代码提交

苍穹外卖项目day10来提醒实现方法如下: 1. **明确提醒关联信息**:来提醒对应一个订,需提交订id,不管是来提醒还是客户,此id用于明确当前提醒的具体订[^1]。 2. **通知形式与接口设计**:用户下且支付成功后,需及时通知外卖商家,通知形式有语音播报弹出提示框。通过WebSocket实现管理端页面服务端保持长连接状态,当客户支付后,调用WebSocket的相关API实现服务端向客户端推送消息。客户端浏览器解析服务端推送的消息,判断是来提醒还是客户,进行相应的消息提示语音播报。服务端发送给客户端浏览器的数据格式为JSON,字段包括:type(消息类型,1为来提醒,2为客户)、orderId(订id)、content(消息内容)[^2]。 3. **代码实现**:在`paySuccess`方法中,先获取当前登录用户id,根据订号查询当前用户的订,然后更新订的状态、支付方式、支付状态、结账时间。接着通过WebSocket实现提醒,构建包含消息类型、订id消息内容的Map,将其转换为JSON字符串,最后通过`webSocketServer.sendToAllClient`方法向客户端浏览器推送消息。示例代码如下: ```java public void paySuccess(String outTradeNo) { // 当前登录用户id Long userId = BaseContext.getCurrentId(); // 根据订号查询当前用户的订 Orders ordersDB = orderMapper.getByNumberAndUserId(outTradeNo, userId); // 根据订id更新订的状态、支付方式、支付状态、结账时间 Orders orders = Orders.builder() .id(ordersDB.getId()) .status(Orders.TO_BE_CONFIRMED) .payStatus(Orders.PAID) .checkoutTime(LocalDateTime.now()) .build(); orderMapper.update(orders); // 通过WebSocket实现提醒 type orderId content Map map = new HashMap<>(); map.put("type", 1); // 消息类型,1表示来提醒 2表示客户 map.put("orderID", ordersDB.getId()); // 订id map.put("content", "订号:" + outTradeNo); // 通过websocket向客户端浏览器推送消息 String json = JSON.toJSONString(map); webSocketServer.sendToAllClient(json); } ``` [^5]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值