学成在线day11选课 支付(集成新版沙箱环境,亲测可用)

选课:

1、执行流程

 2、接口定义

需要取用户信息,若为空说明没有登录,抛出异常要求登录。

/**
 * @author Mr.M
 * @version 1.0
 * @description 我的课程表接口
 * @date 2022/10/25 9:40
 */

@Api(value = "我的课程表接口", tags = "我的课程表接口")
@Slf4j
@RestController
public class MyCourseTablesController {


    @Autowired
    private MyCourseTables myCourseTables;

    @ApiOperation("添加选课")
    @PostMapping("/choosecourse/{courseId}")
    public XcChooseCourseDto addChooseCourse(@PathVariable("courseId") Long courseId) {
        String userId = null;
        try {
            SecurityUtil.XcUser user = SecurityUtil.getUser();
            userId = user.getId();
        } catch (Exception e) {
            XueChengPlusException.cast("请登录后再选课!");
        }
        return myCourseTables.addChooseCourse(courseId,userId);
    }

    @ApiOperation("查询学习资格")
    @PostMapping("/choosecourse/learnstatus/{courseId}")
    public XcCourseTablesDto getLearnstatus(@PathVariable("courseId") Long courseId) {
        SecurityUtil.XcUser user = SecurityUtil.getUser();
        if (ObjectUtils.isEmpty(user)){
            XueChengPlusException.cast("请登录后再查询!");
        }
        return myCourseTables.getLearnstatus(courseId);
    }

    @ApiOperation("我的课程表")
    @GetMapping("/mycoursetable")
    public PageResult<XcCourseTables> mycoursetable(MyCourseTableParams params) {
        return null;
    }

}

3、业务层定义

/**
 * @ClassName : MyCourseTables
 * @Description : 课程表业务处理层
 * @Author : abc123
 * @Date: 2024-02-17 10:13
 */
public interface MyCourseTables {

    /**
     * 添加选课
     * @param courseId 课程id
     * @param userId
     * @return 统一返回类
     */
    XcChooseCourseDto addChooseCourse(Long courseId, String userId);

    /**
     * 初始化选课信息表
     * @param coursepublish
     */
    XcChooseCourse initChooseCourseTableData(CoursePublish coursepublish, String orderType, String status, String userId);

    /**
     * 初始化选课信息表和课程表
     * @param coursepublish
     */
    XcChooseCourse initChooseCourseAndCourseTableData(CoursePublish coursepublish,String userId);

    /**
     * 查询学习资格
     * @param courseId
     * @return
     */
    XcCourseTablesDto getLearnstatus(Long courseId);
}

添加选课业务实现:

(1)定义feign接口远程调用xuecheng-plus-content服务,查询course_publish表中是否存在该课程。(只有发布的课程才能被添加)

(2)判断收费标准。如果免费就直接加入xc_choose_course和xc_course_tables表中。如果收费先加入xc_choose_course表,后续支付完成后再加入xc_course_tables表。

查询学习资格业务实现:

(1)判断该课程是否在xc_choose_course或xc_course_tables表中。

(2)如果该课程是收费课程,判断是否支付。

(3)判断当前日期是否超过课程结束日期。

/**
 * @ClassName : MyCourseTablesImpl
 * @Description : 课程表业务处理层实现类
 * @Author : abc123
 * @Date: 2024-02-17 10:14
 */
@Service
@Transactional
@Slf4j
public class MyCourseTablesImpl implements MyCourseTables {

    @Autowired
    private ContentServiceClient contentServiceClient;

    @Autowired
    private XcChooseCourseMapper chooseCourseMapper;

    @Autowired
    private XcCourseTablesMapper courseTablesMapper;

    @Autowired
    private MyCourseTables myCourseTables;

    /**
     * 添加选课
     *
     * @param courseId 课程id
     * @param userId
     * @return 统一返回类
     */
    @Override
    public XcChooseCourseDto addChooseCourse(Long courseId, String userId) {
        //远程调用内容管理服务查询课程信息
        CoursePublish coursepublish = contentServiceClient.getCoursepublish(courseId);
        //只有课程发布了才能被选择
        if (ObjectUtils.isEmpty(coursepublish)) {
            throw new XueChengPlusException("您添加的课程不存在,请检查!");
        }
        //判断收费标准
        String charge = coursepublish.getCharge();
        XcChooseCourse xcChooseCourse = null;
        switch (charge) {
            case "201000"://免费
                xcChooseCourse = myCourseTables.initChooseCourseAndCourseTableData(coursepublish, userId);
                break;
            case "201001"://收费
                xcChooseCourse = myCourseTables.initChooseCourseTableData(coursepublish, "700002", "701002", userId);
                break;
        }
        XcChooseCourseDto dto = new XcChooseCourseDto();
        BeanUtils.copyProperties(xcChooseCourse, dto);
        return dto;
    }


    public XcChooseCourse initChooseCourseAndCourseTableData(CoursePublish coursepublish, String userId) {
        XcChooseCourse xcChooseCourseRes = this.chooseCourseMapper.selectOne(new LambdaQueryWrapper<XcChooseCourse>().eq(XcChooseCourse::getCourseId, coursepublish.getId()));
        XcCourseTables xcCourseTablesRes = this.courseTablesMapper.selectOne(new LambdaQueryWrapper<XcCourseTables>().eq(XcCourseTables::getCourseId, coursepublish.getId()));
        if (!ObjectUtils.isEmpty(xcChooseCourseRes) || !ObjectUtils.isEmpty(xcCourseTablesRes)) {
            throw new XueChengPlusException("该课程已存在,请勿重复添加!");
        }
        //添加课程到选课记录表
        XcChooseCourse xcChooseCourse = myCourseTables.initChooseCourseTableData(coursepublish, "700001", "701001", userId);

        // 添加课程到课程表 先要判断是否过期
        if (LocalDateTime.now().isAfter(xcChooseCourse.getValidtimeEnd())) {
            throw new XueChengPlusException("该课程已过期,请勿添加!");
        }
        XcCourseTables xcCourseTables = new XcCourseTables();
        BeanUtils.copyProperties(xcChooseCourse, xcCourseTables);
        xcCourseTables.setChooseCourseId(xcChooseCourse.getId());
        this.courseTablesMapper.insert(xcCourseTables);
        return xcChooseCourse;
    }

    /**
     * 查询学习资格
     *
     * @param courseId
     * @return
     */
    @Override
    public XcCourseTablesDto getLearnstatus(Long courseId) {
        XcCourseTablesDto xcCourseTablesDto = new XcCourseTablesDto();
        //无论是免费还是收费都要是选课成功
        XcChooseCourse xcChooseCourse = null;
        XcCourseTables xcCourseTables = null;
        try {
            //选课记录表
            xcChooseCourse = chooseCourseMapper.selectOne(new LambdaQueryWrapper<XcChooseCourse>().eq(XcChooseCourse::getCourseId, courseId));
            //课程表
            xcCourseTables = courseTablesMapper.selectOne(new LambdaQueryWrapper<XcCourseTables>().eq(XcCourseTables::getCourseId, courseId));
        } catch (Exception e) {
            log.error("查询选课信息错误,课程ID:{}", courseId);
            XueChengPlusException.cast("查询选课信息错误!");
        }
        //判断是否在选课记录表或课程表中
        if (ObjectUtils.isEmpty(xcChooseCourse) && ObjectUtils.isEmpty(xcCourseTables)) {
            xcCourseTablesDto.setLearnStatus("702002");
            return xcCourseTablesDto;
        }
        switch (xcChooseCourse.getStatus()) {
            case "701001":
                xcCourseTablesDto.setLearnStatus("702001");
                return xcCourseTablesDto;
            case "701002":
                xcCourseTablesDto.setLearnStatus("702002");
                return xcCourseTablesDto;
        }
        //然后判断有效期是否过期
        if (xcChooseCourse.getValidtimeEnd().isBefore(LocalDateTime.now())) {
            xcCourseTablesDto.setLearnStatus("702003");
            return xcCourseTablesDto;
        }
        xcCourseTablesDto.setLearnStatus("702001");
        return xcCourseTablesDto;
    }

    /**
     * 添加课程到选课记录表
     *
     * @param coursepublish 课程发布实体
     * @param orderType     收费方式
     * @param status        选课状态
     * @param userId        用户id
     */
    public XcChooseCourse initChooseCourseTableData(CoursePublish coursepublish, String orderType, String status, String userId) {
        //添加前先查询,如果课程存在并且状态是未支付,就直接返回
        XcChooseCourse xcChooseCourseRes = this.chooseCourseMapper.selectOne(new LambdaQueryWrapper<XcChooseCourse>().eq(XcChooseCourse::getCourseId, coursepublish.getId()));
        if (!ObjectUtils.isEmpty(xcChooseCourseRes) && xcChooseCourseRes.getStatus().equals("701002")) {
            return xcChooseCourseRes;
        }
        //否则抛出异常
        if (!ObjectUtils.isEmpty(xcChooseCourseRes)) {
            throw new XueChengPlusException("该课程已存在,请勿重复添加!");
        }
        //不存在则添加
        XcChooseCourse xcChooseCourse = new XcChooseCourse();
        BeanUtils.copyProperties(coursepublish, xcChooseCourse);
        xcChooseCourse.setCourseId(coursepublish.getId());
        xcChooseCourse.setOrderType(orderType);
        xcChooseCourse.setStatus(status);
        xcChooseCourse.setUserId(userId);
        xcChooseCourse.setCoursePrice(coursepublish.getPrice() == null ? 0F : coursepublish.getPrice());
        LocalDateTime now = LocalDateTime.now();
        xcChooseCourse.setValidtimeStart(now);
        xcChooseCourse.setValidtimeEnd(DateUtil.getFutureDateTimePlusDays(now, coursepublish.getValidDays()));
        xcChooseCourse.setCourseName(coursepublish.getName());
        this.chooseCourseMapper.insert(xcChooseCourse);
        return xcChooseCourse;
    }


}

4、功能测试

先调用一次查询学习资格接口

### 查询学习资格
POST http://localhost:63020/learning/choosecourse/learnstatus/121
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsieHVlY2hlbmctcGx1cyJdLCJ1c2VyX25hbWUiOiJ7XCJjZWxscGhvbmVcIjpcIjE1NjUxODIwMDg4XCIsXCJjcmVhdGVUaW1lXCI6XCIyMDI0LTAyLTE2VDE5OjMyOjA4XCIsXCJlbWFpbFwiOlwiOTgwNjgwNTQ0QHFxLmNvbVwiLFwiaWRcIjpcImUzMzVjMTk4LTYyMzgtNDc2NS04ZTA3LTA5MDk1ZDUyODU2Y1wiLFwibmFtZVwiOlwiZWxkZXdpc3NcIixcIm5pY2tuYW1lXCI6XCJlbGRld2lzc1wiLFwicGVybWlzc2lvbnNcIjpbXSxcInN0YXR1c1wiOlwiMVwiLFwidXNlcm5hbWVcIjpcImVsZGV3aXNzXCIsXCJ1dHlwZVwiOlwiMTAxMDAxXCJ9Iiwic2NvcGUiOlsiYWxsIl0sImV4cCI6MTcwODE3ODMwMCwiYXV0aG9yaXRpZXMiOlsieGNfc3lzbWFuYWdlcl9kb2MiLCJ4Y190ZWFjaG1hbmFnZXJfY291cnNlX2xpc3QiLCJ4Y19zeXNtYW5hZ2VyIiwieGNfc3lzbWFuYWdlcl9jb21wYW55IiwieGNfc3lzbWFuYWdlcl9sb2ciXSwianRpIjoiOGNkYmQxZmUtOTI4Yi00NmZlLThiYmQtZTBlNTFjMmZmYzFiIiwiY2xpZW50X2lkIjoiWGNXZWJBcHAifQ.9JY_3fapNgsHfefSf9aejHRTgqLWpUFUIyEy0smaCGc

由于未选课,返回702002

{
  "id": null,
  "chooseCourseId": null,
  "userId": null,
  "courseId": null,
  "companyId": null,
  "courseName": null,
  "courseType": null,
  "createDate": null,
  "validtimeStart": null,
  "validtimeEnd": null,
  "updateDate": null,
  "remarks": null,
  "learnStatus": "702002"
}

再调用添加课程接口

## 添加课程
POST http://localhost:63020/learning/choosecourse/121
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsieHVlY2hlbmctcGx1cyJdLCJ1c2VyX25hbWUiOiJ7XCJjZWxscGhvbmVcIjpcIjE1NjUxODIwMDg4XCIsXCJjcmVhdGVUaW1lXCI6XCIyMDI0LTAyLTE2VDE5OjMyOjA4XCIsXCJlbWFpbFwiOlwiOTgwNjgwNTQ0QHFxLmNvbVwiLFwiaWRcIjpcImUzMzVjMTk4LTYyMzgtNDc2NS04ZTA3LTA5MDk1ZDUyODU2Y1wiLFwibmFtZVwiOlwiZWxkZXdpc3NcIixcIm5pY2tuYW1lXCI6XCJlbGRld2lzc1wiLFwicGVybWlzc2lvbnNcIjpbXSxcInN0YXR1c1wiOlwiMVwiLFwidXNlcm5hbWVcIjpcImVsZGV3aXNzXCIsXCJ1dHlwZVwiOlwiMTAxMDAxXCJ9Iiwic2NvcGUiOlsiYWxsIl0sImV4cCI6MTcwODE3ODMwMCwiYXV0aG9yaXRpZXMiOlsieGNfc3lzbWFuYWdlcl9kb2MiLCJ4Y190ZWFjaG1hbmFnZXJfY291cnNlX2xpc3QiLCJ4Y19zeXNtYW5hZ2VyIiwieGNfc3lzbWFuYWdlcl9jb21wYW55IiwieGNfc3lzbWFuYWdlcl9sb2ciXSwianRpIjoiOGNkYmQxZmUtOTI4Yi00NmZlLThiYmQtZTBlNTFjMmZmYzFiIiwiY2xpZW50X2lkIjoiWGNXZWJBcHAifQ.9JY_3fapNgsHfefSf9aejHRTgqLWpUFUIyEy0smaCGc

由于该课程是收费课程,暂时未支付,所以只在 xc_choose_course表中初始化一条记录。

因为没有支付,所以查询学习资格依旧是返回702002。

支付:

1、集成支付宝环境

    <!-- 支付宝SDK -->
        <dependency>
            <groupId>com.alipay.sdk</groupId>
            <artifactId>alipay-sdk-java</artifactId>
            <version>3.7.73.ALL</version>
        </dependency>

        <!-- 支付宝SDK依赖的日志 -->
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.2</version>
        </dependency>
        <dependency>
            <groupId>com.xuxueli</groupId>
            <artifactId>xxl-job-core</artifactId>
        </dependency>
        <!-- 二维码生成&识别组件 -->
        <dependency>
            <groupId>com.google.zxing</groupId>
            <artifactId>core</artifactId>
            <version>3.3.3</version>
        </dependency>

        <dependency>
            <groupId>com.google.zxing</groupId>
            <artifactId>javase</artifactId>
            <version>3.3.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>

public class AlipayConfig {
    // 商户appid
//	public static String APPID = "";
    // 私钥 pkcs8格式的
//	public static String RSA_PRIVATE_KEY = "";
    // 服务器异步通知页面路径 需http://或者https://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
    public static String notify_url = "http://商户网关地址/alipay.trade.wap.pay-JAVA-UTF-8/notify_url.jsp";
    // 页面跳转同步通知页面路径 需http://或者https://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问 商户可以自定义同步跳转地址
    public static String return_url = "http://商户网关地址/alipay.trade.wap.pay-JAVA-UTF-8/return_url.jsp";
    // 请求网关地址
    public static String URL = "https://openapi-sandbox.dl.alipaydev.com/gateway.do";
    // 编码
    public static String CHARSET = "UTF-8";
    // 返回格式
    public static String FORMAT = "json";
    // 支付宝公钥
//	public static String ALIPAY_PUBLIC_KEY = "";
    // 日志记录目录
    public static String log_path = "/log";
    // RSA2
    public static String SIGNTYPE = "RSA2";
}

下载秘钥工具

 


重要:如果使用的是新版支付宝沙箱,请求网关地址为"https://openapi-sandbox.dl.alipaydev.com/gateway.do"

APP_PRIVATE_KEY对应的是查看秘钥-应用私钥。

ALIPAY_PUBLIC_KEY对应的是查看秘钥-支付宝公钥,非应用公钥!

我这里采用的是下载手机版沙箱支付宝进行测试:

2、测试代码

2.1、测试支付宝接口:

官网文档:小程序文档 - 支付宝文档中心

/**
 * @author Mr.M
 * @version 1.0
 * @description 测试支付宝接口
 * @date 2022/10/20 22:19
 */
@Controller
public class PayTestController {

    @Value("${pay.alipay.APP_ID}")
    String APP_ID;
    @Value("${pay.alipay.APP_PRIVATE_KEY}")
    String APP_PRIVATE_KEY;

    @Value("${pay.alipay.ALIPAY_PUBLIC_KEY}")
    String ALIPAY_PUBLIC_KEY;


    @RequestMapping("/alipaytest")
    public void doPost(HttpServletRequest httpRequest,
                       HttpServletResponse httpResponse) throws ServletException, IOException, AlipayApiException {
        AlipayClient alipayClient = new DefaultAlipayClient(AlipayConfig.URL, APP_ID, APP_PRIVATE_KEY, AlipayConfig.FORMAT, AlipayConfig.CHARSET, ALIPAY_PUBLIC_KEY,AlipayConfig.SIGNTYPE);
        AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest();
//异步接收地址,仅支持http/https,公网可访问
        request.setNotifyUrl("");
//同步跳转地址,仅支持http/https
        request.setReturnUrl("");
/******必传参数******/
        JSONObject bizContent = new JSONObject();
//商户订单号,商家自定义,保持唯一性
        bizContent.put("out_trade_no", "20210817010101005");
//支付金额,最小值0.01元
        bizContent.put("total_amount", 0.01);
//订单标题,不可使用特殊符号
        bizContent.put("subject", "测试商品");
/******可选参数******/
//手机网站支付默认传值FAST_INSTANT_TRADE_PAY
        bizContent.put("product_code", "QUICK_WAP_WAY");
//bizContent.put("time_expire", "2022-08-01 22:00:00");
 商品明细信息,按需传入
//JSONArray goodsDetail = new JSONArray();
//JSONObject goods1 = new JSONObject();
//goods1.put("goods_id", "goodsNo1");
//goods1.put("goods_name", "子商品1");
//goods1.put("quantity", 1);
//goods1.put("price", 0.01);
//goodsDetail.add(goods1);
//bizContent.put("goods_detail", goodsDetail);
 扩展信息,按需传入
//JSONObject extendParams = new JSONObject();
//extendParams.put("sys_service_provider_id", "2088511833207846");
//bizContent.put("extend_params", extendParams);
        request.setBizContent(bizContent.toString());
        AlipayTradeWapPayResponse response = alipayClient.pageExecute(request, "POST");
// 如果需要返回GET请求,请使用
// AlipayTradeWapPayResponse response = alipayClient.pageExecute(request,"GET");
        String pageRedirectionData = response.getBody();
        System.out.println(pageRedirectionData);
        if (response.isSuccess()) {
            System.out.println("调用成功");
            String form = alipayClient.pageExecute(request).getBody(); //调用SDK生成表单
            httpResponse.setContentType("text/html;charset=" + AlipayConfig.CHARSET);
            httpResponse.getWriter().write(form);//直接将完整的表单html输出到页面
            httpResponse.getWriter().flush();
            return; // 添加此行,结束方法执行,确保只返回 HTML 表单代码而不会继续执行下面的代码
        } else {
            System.out.println("调用失败");
        }

    }



}

生成二维码:

/**
 * @author Mr.M
 * @version 1.0
 * @description 二维码生成工具
 * @date 2022/10/3 0:03
 */
public class QRCodeUtil {
    /**
     * 生成二维码
     *
     * @param content 二维码对应的URL
     * @param width   二维码图片宽度
     * @param height  二维码图片高度
     * @return
     */
    public String createQRCode(String content, int width, int height) throws IOException {
        String resultImage = "";
        //除了尺寸,传入内容不能为空
        if (!StringUtils.isEmpty(content)) {
            ServletOutputStream stream = null;
            ByteArrayOutputStream os = new ByteArrayOutputStream();
            //二维码参数
            @SuppressWarnings("rawtypes")
            HashMap<EncodeHintType, Comparable> hints = new HashMap<>();
            //指定字符编码为“utf-8”
            hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
            //L M Q H四个纠错等级从低到高,指定二维码的纠错等级为M
            //纠错级别越高,可以修正的错误就越多,需要的纠错码的数量也变多,相应的二维吗可储存的数据就会减少
            hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
            //设置图片的边距
            hints.put(EncodeHintType.MARGIN, 1);

            try {
                //zxing生成二维码核心类
                QRCodeWriter writer = new QRCodeWriter();
                //把输入文本按照指定规则转成二维吗
                BitMatrix bitMatrix = writer.encode(content, BarcodeFormat.QR_CODE, width, height, hints);
                //生成二维码图片流
                BufferedImage bufferedImage = MatrixToImageWriter.toBufferedImage(bitMatrix);
                //输出流
                ImageIO.write(bufferedImage, "png", os);
                /**
                 * 原生转码前面没有 data:image/png;base64 这些字段,返回给前端是无法被解析,所以加上前缀
                 */
                resultImage = new String("data:image/png;base64," + EncryptUtil.encodeBase64(os.toByteArray()));
                return resultImage;
            } catch (Exception e) {
                e.printStackTrace();
                throw new RuntimeException("生成二维码出错");
            } finally {
                if (stream != null) {
                    stream.flush();
                    stream.close();
                }
            }
        }
        return null;
    }

    public static void main(String[] args) throws IOException {
        QRCodeUtil qrCodeUtil = new QRCodeUtil();
        System.out.println(qrCodeUtil.createQRCode("http://"换成你自己的":63030/orders/alipaytest", 200, 200));

    }
}


重要:端口号非localhost,而是本地网卡分配的局域网ip地址。我这里填的是wifi地址,手机电脑连同一个wifi进行测试。

复制控制台的BASE64字符串至浏览器,用手机沙箱版支付宝扫描。

用浏览器打开

 选择沙箱版支付宝,输入密码支付即可。


注意:同一out_trade_no只能使用一次,相当于支付宝系统数据库已存该条记录!

2.2、测试交易结果查询接口:

官网文档:小程序文档 - 支付宝文档中心

关键入参:out_trade_no-订单号

/**
 * @ClassName : Test
 * @Description :
 * @Author : abc123
 * @Date: 2024-02-17 19:28
 */
public class Test {
    public static void main(String[] args) throws AlipayApiException {

        AlipayClient alipayClient = new DefaultAlipayClient(AlipayConfig.URL, "填自己的appid",
                "填自己的APP_PRIVATE_KEY",
                "json", AlipayConfig.CHARSET, "填自己的ALIPAY_PUBLIC_KEY", AlipayConfig.SIGNTYPE); //获得初始化的AlipayClient
        AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
        JSONObject bizContent = new JSONObject();
        bizContent.put("out_trade_no", "20210817010101005");
        //bizContent.put("trade_no", "2014112611001004680073956707");
        request.setBizContent(bizContent.toString());
        AlipayTradeQueryResponse response = alipayClient.execute(request);
        if (response.isSuccess()) {
            System.out.println("调用成功");
            String resultJson = response.getBody();
            //转map
            Map resultMap = JSON.parseObject(resultJson, Map.class);
            Map alipay_trade_query_response = (Map) resultMap.get("alipay_trade_query_response");
            //支付结果
            String trade_status = (String) alipay_trade_query_response.get("trade_status");
            System.out.println(trade_status);
        } else {
            System.out.println("调用失败");
        }

    }
}

 附录:

sign check fail: check Sign and Data Fail问题官方解决方案:小程序文档 - 支付宝文档中心

  • 17
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值