谷粒学苑项目实战(十三):课程管理模块搭建

17 篇文章 3 订阅

目录

一、课程相关表的关系

二、实现添加课程基本信息功能 

        1、使用代码生成器生成相关代码 

        2、创建vo类封装表单提交的数据 

        3、controller 

        4、service 

        5、serviceImpl 

        6、启动测试 

三、实现修改课程基本信息功能 

        1、controller 

        2、service 

        3、serviceImpl 

四、实现课程章节的大纲显示功能 

        1、controller

        2、service 

        3、serviceImpl 

五、实现课程章节的添加、修改功能 

        1、controller 

六、 实现课程章节的删除功能

        1、controller 

        2、service  

        3、serviceimpl 

七、实现小节的增加、删除和修改功能

八、实现课程信息确认功能

        1、controller

        2、service 

        3、mapper 

        4、servceImpl 

        5、swagger测试

        6、修改后重新测试 

九、课程最终确认 

十、实现课程大纲列表显示

十一、删除课程 

        1、controller 

        2、service 

        3、serviceImpl 

        4、小节中 

        5、章节中 


一、课程相关表的关系

二、实现添加课程基本信息功能 

        1、使用代码生成器生成相关代码 

        还是只需要改这里

        2、创建vo类封装表单提交的数据 

@Data
public class CourseInfoVo {
    @ApiModelProperty(value = "课程ID")
    private String id;

    @ApiModelProperty(value = "课程讲师ID")
    private String teacherId;

    @ApiModelProperty(value = "课程专业ID")
    private String subjectId;

    @ApiModelProperty(value = "课程标题")
    private String title;

    @ApiModelProperty(value = "课程销售价格,设置为0则可免费观看")
    // 0.01
    private BigDecimal price;

    @ApiModelProperty(value = "总课时")
    private Integer lessonNum;

    @ApiModelProperty(value = "课程封面图片路径")
    private String cover;

    @ApiModelProperty(value = "课程简介")
    private String description;
}

        3、controller 

@RestController
@RequestMapping("/eduservice/course")
@CrossOrigin
public class EduCourseController {

    @Autowired
    private EduCourseService eduCourseService;

    /**
     * 添加课程的基本信息
     * @param courseInfoVo 封装好的course信息
     * @return
     */
    @PostMapping("/addCourseInfo")
    public R addCourseInfo(@RequestBody CourseInfoVo courseInfoVo){
        eduCourseService.saveCourseInfo(courseInfoVo);
        return R.ok();
    }
}

        4、service 

public interface EduCourseService extends IService<EduCourse> {

    /**
     * 添加课程的基本信息
     * @param courseInfoVo
     */
    void saveCourseInfo(CourseInfoVo courseInfoVo);
}

        5、serviceImpl 

@Service
public class EduCourseServiceImpl extends ServiceImpl<EduCourseMapper, EduCourse> implements EduCourseService {

    @Autowired
    private EduCourseDescriptionService eduCourseDescriptionService;

    /**
     * 添加课程的基本信息
     * @param courseInfoVo
     */
    @Override
    public void saveCourseInfo(CourseInfoVo courseInfoVo) {


        //向课程表中添加课程基本信息

        //因为传入的courseInfoVo 和 eduCourse 不一样,所以要转换一下
        EduCourse eduCourse = new EduCourse();
        BeanUtils.copyProperties(courseInfoVo, eduCourse);
        int insert = baseMapper.insert(eduCourse);
        if (insert <= 0){
            throw new  GuliException(20001, "添加课程信息失败");
        }


        //获取课程id,用来和课程描述进行关联
        String cid = eduCourse.getId();

        //向课程描述表中加课程描述信息
        EduCourseDescription eduCourseDescription = new EduCourseDescription();
        eduCourseDescription.setDescription(courseInfoVo.getDescription());
        eduCourseDescription.setId(cid);
        eduCourseDescriptionService.save(eduCourseDescription);
    }
}

         这里需要注意的点是:由于课程和课程描述是一对一关系,所以我们直接把课程描述的id值设置为课程的id,所以需要把描述的主键生成策略改为INPUT(其实不改也行)

        6、启动测试 

        启动后,打开swagger-ui 

        再看数据库中,edu_course 和 edu_course_description中都插入了数据。 

三、实现修改课程基本信息功能 

        实现修改功能,首先要实现修改的时候信息回显(查询)功能;然后实现修改功能。

        

        1、controller 

/**
     * 根据课程id查询课程信息
     * @param courseId
     * @return
     */
    @GetMapping("/getCourseInfo/{courseId}")
    public R getCourseInfo(@PathVariable String courseId){
        CourseInfoVo courseInfoVo = eduCourseService.getCourseInfo(courseId);
        return R.ok().data("courseInfoVo", courseInfoVo);
    }

    /**
     * 修改课程信息
     * @param courseInfoVo
     * @return
     */
    @PostMapping("/updateCourseInfo")
    public R updateCourseInfo(@RequestBody CourseInfoVo courseInfoVo){
        eduCourseService.updateCourseInfo(courseInfoVo);
        return R.ok();
    }

        2、service 

/**
     * 根据课程id查询课程信息
     * @param courseId
     * @return
     */
    CourseInfoVo getCourseInfo(String courseId);

    /**
     * 修改课程信息
     * @param courseInfoVo
     */
    void updateCourseInfo(CourseInfoVo courseInfoVo);

        3、serviceImpl 

/**
     * 根据课程id查询课程信息
     * @param courseId
     * @return
     */
    @Override
    public CourseInfoVo getCourseInfo(String courseId) {

        //根据id查课程表
        EduCourse course = baseMapper.selectById(courseId);
        CourseInfoVo courseInfoVo = new CourseInfoVo();
        BeanUtils.copyProperties(course, courseInfoVo);

        //根据id查描述表
        EduCourseDescription description = eduCourseDescriptionService.getById(courseId);
        courseInfoVo.setDescription(description.getDescription());

        return courseInfoVo;
    }

    /**
     * 修改课程信息
     * @param courseInfoVo
     */
    @Override
    public void updateCourseInfo(CourseInfoVo courseInfoVo) {

        //根据id修改课程表
        EduCourse eduCourse = new EduCourse();
        BeanUtils.copyProperties(courseInfoVo, eduCourse);
        int update = baseMapper.updateById(eduCourse);
        if (update <= 0){
            throw new GuliException(20001, "修改课程信息失败");
        }

        //根据id修改描述表
        EduCourseDescription eduCourseDescription = new EduCourseDescription();
        eduCourseDescription.setId(courseInfoVo.getId());
        eduCourseDescription.setDescription(courseInfoVo.getDescription());
        eduCourseDescriptionService.updateById(eduCourseDescription);
    }

四、实现课程章节的大纲显示功能 

        逻辑上与课程分类的显示基本相同(都是树形显示)

        

        1、controller

@RestController
@RequestMapping("/eduservice/chapter")
@CrossOrigin
public class EduChapterController {

    @Autowired
    private EduChapterService chapterService;



    /**
     * 课程大纲列表,根据课程id进行查询
     * @param courseId
     * @return
     */
    @GetMapping("getChapterVideo/{courseId}")
    public R getChapterVideo(@PathVariable String courseId){
        List<ChapterVo> list = chapterService.getChapterVideoByCourseId(courseId);
        return R.ok().data("allChapterVideo", list);
    }

  
}

        2、service 

public interface EduChapterService extends IService<EduChapter> {

    /**
     * 课程大纲列表,根据课程id查询课程
     * @param courseId
     * @return
     */
    List<ChapterVo> getChapterVideoByCourseId(String courseId);

    
}

        3、serviceImpl 

@Service
public class EduChapterServiceImpl extends ServiceImpl<EduChapterMapper, EduChapter> implements EduChapterService {

    //注入小节的service,才能查询小节信息
    @Autowired
    private EduVideoService videoService;

    /**
     * 课程大纲列表,根据课程id查询课程
     * @param courseId
     * @return
     */
    @Override
    public List<ChapterVo> getChapterVideoByCourseId(String courseId) {

        //1 根据课程id查询课程里面所有的章节
        QueryWrapper<EduChapter> wrapperChapter = new QueryWrapper<>();
        wrapperChapter.eq("course_id",courseId);
        List<EduChapter> eduChapterList = baseMapper.selectList(wrapperChapter);

        //2 根据课程id查询课程里面所有的小节
        QueryWrapper<EduVideo> wrapperVideo = new QueryWrapper<>();
        wrapperVideo.eq("course_id",courseId);
        List<EduVideo> eduVideoList = videoService.list(wrapperVideo);

        //创建list集合,用于最终封装数据
        List<ChapterVo> finalList = new ArrayList<>();

        //3 遍历查询章节list集合进行封装
        //遍历查询章节list集合
        for (int i = 0; i < eduChapterList.size(); i++) {
            //每个章节
            EduChapter eduChapter = eduChapterList.get(i);
            //eduChapter对象值复制到ChapterVo里面
            ChapterVo chapterVo = new ChapterVo();
            BeanUtils.copyProperties(eduChapter,chapterVo);
            //把chapterVo放到最终list集合
            finalList.add(chapterVo);

            //创建集合,用于封装章节的小节
            List<VideoVo> videoList = new ArrayList<>();

            //4 遍历查询小节list集合,进行封装
            for (int m = 0; m < eduVideoList.size(); m++) {
                //得到每个小节
                EduVideo eduVideo = eduVideoList.get(m);
                //判断:小节里面chapterid和章节里面id是否一样
                if(eduVideo.getChapterId().equals(eduChapter.getId())) {
                    //进行封装
                    VideoVo videoVo = new VideoVo();
                    BeanUtils.copyProperties(eduVideo,videoVo);
                    //放到小节封装集合
                    videoList.add(videoVo);
                }
            }
            //把封装之后小节list集合,放到章节对象里面
            chapterVo.setChildren(videoList);
        }
        return finalList;

    }

}

五、实现课程章节的添加、修改功能 

        1、controller 

/**
     * 添加章节
     * @param eduChapter
     * @return
     */
    @PostMapping("/addChapter")
    public R addChapter(@RequestBody EduChapter eduChapter){
        chapterService.save(eduChapter);
        return R.ok();
    }

    /**
     * 根据id查询章节   用于修改时的回显
     * @param chapterId
     * @return
     */
    @GetMapping("getChapterInfo/{chapterId}")
    public R getChapterInfo(@PathVariable String chapterId){
        EduChapter chapter = chapterService.getById(chapterId);
        return R.ok().data("chapter", chapter);
    }

    /**
     * 修改章节信息
     * @param eduChapter
     * @return
     */
    @PostMapping("updateChapter")
    public R updateChapter(@RequestBody EduChapter eduChapter){
        chapterService.updateById(eduChapter);
        return R.ok();
    }

        因为没有什么特殊的业务要求,所以直接用MP里提供的方法就可以。

六、 实现课程章节的删除功能

        删除章节的时候需要注意:章节下还有小节,如果直接删除章节,小节还是会存在,并且成为无用的数据,所以不能直接用MP提供的删除方法。

        我们有两种解决思路:

        1、在删除章节的同时将下面的所有小节删掉;

        2、如果章节下面有小节,就不能删除(必须等小节删完了才能删章节) 

        这次我们采用第二种方法处理,在下面删除课程(第十步)中也会采用第一种方法。实际工作中具体用哪一种,取决于业务需求。

        1、controller 

/**
     * 删除章节
     * @param chapterId
     * @return
     */
    @DeleteMapping("/deleteChapter/{chapterId}")
    public R deleteChapter(@PathVariable String chapterId){
        //这里要注意的问题是,章节下有小节,如果直接删除章节,小节还是会存在,所以不能直接用MP提供的删除方法
        boolean delete = chapterService.deleteChapter(chapterId);
        if (delete){
            return R.ok();
        } else{
            return R.error();
        }
    }

        2、service  

/**
     * 删除章节
     * @param chapterId
     */
    boolean deleteChapter(String chapterId);

        3、serviceimpl 

/**
     * 删除章节
     * @param chapterId
     */
    @Override
    public boolean deleteChapter(String chapterId) {

        //如果章节下面有小节,就不能删除(必须等小节删完了才能删章节)
        QueryWrapper<EduVideo> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("chapter_id", chapterId);
        int count = videoService.count(queryWrapper);
        if (count > 0){ //章节下有小节
            throw new GuliException(20001, "不能删除");
        } else {
            int result = baseMapper.deleteById(chapterId);
            //result=1说明成功删除,result=0说明失败
            return result>0;
        }
    }

七、实现小节的增加、删除和修改功能

        每个课程对应的章节下有若干个小节。

        代码比较简单,就直接贴controller了。

@RestController
@RequestMapping("/eduservice/video")
@CrossOrigin
public class EduVideoController {

    @Autowired
    private EduVideoService eduVideoService;

    /**
     * 增加小节
     * @param eduVideo
     * @return
     */
    @PostMapping("/addVideo")
    public R addVideo(@RequestBody EduVideo eduVideo){
        eduVideoService.save(eduVideo);
        return R.ok();
    }

    /**
     * 删除小节(后续需要将小节中的视频也删掉)
     * @param id
     * @return
     */
    @DeleteMapping("{id}")
    public R deleteVideo(@PathVariable String id){
        eduVideoService.removeById(id);
        return R.ok();
    }

    /**
     * 根据id查找小节   用于修改小节时的回显
     * @param id
     * @return
     */
    @GetMapping("{id}")
    public R getVideoById(@PathVariable String id){
        EduVideo video = eduVideoService.getById(id);
        return R.ok().data("video", video);
    }

    /**
     * 修改小节
     * @param eduVideo
     * @return
     */
    @PostMapping("/updateVideo")
    public R updateVideo(@RequestBody EduVideo eduVideo){
        eduVideoService.updateById(eduVideo);
        return R.ok();
    }
}

八、实现课程信息确认功能

        在增加课程信息时,在发布课程之前,需要确认课程信息是否正确 。

        因为课程的所有信息需要查询多张表才能得到,所以需要我们自己编写SQL语句来实现。

             

        1、controller

/**
     * 根据课程id查询课程确认信息
     * @param id
     * @return
     */
    @GetMapping("/getPublishCourseInfo/{id}")
    public R getPublishCourseInfo(@PathVariable String id){
        CoursePublishVo coursePublishVo = eduCourseService.publishCourseInfo(id);
        return R.ok().data("publishCourse", coursePublishVo);
    }

        2、service 

/**
     * 根据课程id查询课程确认信息
     * @param id
     * @return
     */
    CoursePublishVo publishCourseInfo(String id);

        3、mapper 

public interface EduCourseMapper extends BaseMapper<EduCourse> {

    public CoursePublishVo getPublishCourseInfo(String courseId);
}

<?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.shang.eduservice.mapper.EduCourseMapper">

    <!-- 根据课程id查询课程确认信息 -->
    <select id="getPublishCourseInfo" resultType="com.shang.eduservice.entity.vo.CoursePublishVo">

        SELECT ec.id, ec.title, ec.price, ec.lesson_num as lessonNum,ec.cover,
               et.name as teacherName,
               es1.title AS subjectLevelOne,
               es2.title AS subjectLevelTwo
        FROM edu_course ec LEFT OUTER JOIN edu_course_description ecd ON ec.`id`=ecd.`id`
                           LEFT OUTER JOIN edu_teacher et ON ec.`teacher_id`=et.id
                           LEFT OUTER JOIN edu_subject es1 ON ec.`subject_parent_id`=es1.`id`
                           LEFT OUTER JOIN edu_subject es2 ON ec.`subject_id`=es2.`id`
        WHERE ec.`id`=#{couseId};

    </select>

</mapper>

        4、servceImpl 

@Override
    public CoursePublishVo publishCourseInfo(String id) {
        //调用自己写的mapper
        return baseMapper.getPublishCourseInfo(id);
    }

        5、swagger测试

        发现报错:Invalidbound statement (not found)

        具体原因和解决方法我已经写在另一篇博客里了,传送门:

        org.apache.ibatis.binding.BindingException: Invalidbound statement (not found)的解决方案和造成原因分析(超详细) 

        6、修改后重新测试 

        查到数据。 

九、课程最终确认 

        在course表中,有一个字段用来标记是否发布。

        只要修改一下这个字段的属性值就可以完成发布了。

/**
     * 课程最终发布
     * @param id
     * @return
     */
    @PostMapping("/publishCourse/{id}")
    public R publishCourse(@PathVariable String id){
        EduCourse eduCourse = new EduCourse();
        eduCourse.setId(id);
        //这里如果把状态定义为枚举类型  会更好一些
        eduCourse.setStatus("Normal");
        eduCourseService.updateById(eduCourse);
        return R.ok();
    }

十、实现课程大纲列表显示

        逻辑上和之前的讲师列表一样,就不多赘述了。

十一、删除课程 

        首先我们要知道,在处理表与表一对多的关系时,通常是将多的那一张表创建字段作为外键,指向为一的那张表的主键。其实,在实际工作中,一般是不将外键设置出来的,程序员只要心里清楚这是外键就可以了,因为设置外键之后必须保持数据一致性,会带来一些问题(比如想要删除课程,就必须先删除他下面的所有章节;想要删除章节,就必须先删除他下面的所有小节)

        这次我们删除采用的方法是,删除课程时,先把他下面的所有东西删掉。

        1、controller 

/**
     * 删除课程
     * @param courseId
     * @return
     */
    @DeleteMapping("{courseId}")
    public R deleteCourse(@PathVariable String courseId){
        eduCourseService.removeCourse(courseId);
        return R.ok();
    }

        2、service 

/**
     * 删除课程
     * @param courseId
     */
    void removeCourse(String courseId);

        3、serviceImpl 

@Override
    public void removeCourse(String courseId) {

        //根据课程id删除小节
        eduVideoService.removeVideoByCourseId(courseId);

        //根据课程id删除章节
        eduChapterService.removeChapterByCourseId(courseId);

        //根据课程id删除描述(课程和描述是一对一关系)
        eduCourseDescriptionService.removeById(courseId);

        //根据课程id删除课程
        int result = baseMapper.deleteById(courseId);
        if (result <= 0){
            throw new GuliException(20001, "删除课程失败");
        }
    }

        4、小节中 

public interface EduVideoService extends IService<EduVideo> {

    /**
     * 根据课程id删除小节
     * @param courseId
     */
    void removeVideoByCourseId(String courseId);
}
@Service
public class EduVideoServiceImpl extends ServiceImpl<EduVideoMapper, EduVideo> implements EduVideoService {

    /**
     * 根据课程id删除小节
     * @param courseId
     */
    @Override
    public void removeVideoByCourseId(String courseId) {

        // TODO 删除小节的时候,还要删除对应的视频

        QueryWrapper<EduVideo> queryWrapper = new QueryWrapper();
        queryWrapper.eq("course_id", courseId);
        baseMapper.delete(queryWrapper);
    }
}

        5、章节中 

/**
     * 根据课程id删除章节
     * @param courseId
     */
    void removeChapterByCourseId(String courseId);

/**
     * 根据课程id删除其下面的所有章节
     * @param courseId
     */
    @Override
    public void removeChapterByCourseId(String courseId) {
        QueryWrapper<EduChapter> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("course_id", courseId);
        baseMapper.delete(queryWrapper);
    }

 

十二、实现添加小节时上传视频的功能 

         上传视频使用阿里云视频点播功能,具体操作参考我的博客:

        谷粒学苑项目实战(十四):实现阿里云视频点播功能(java编码实现)

        1、引入依赖 

        2、创建application配置文件 

# 服务端口
server.port=8003
# 服务名
spring.application.name=service-vod

# 环境设置:dev、test、prod
spring.profiles.active=dev

#阿里云 vod
#不同的服务器,地址不同
aliyun.vod.file.keyid=xxxxxx
aliyun.vod.file.keysecret=xxxxx

# 最大上传单个文件大小:默认1M
spring.servlet.multipart.max-file-size=1024MB
# 最大置总上传的数据大小 :默认10M
spring.servlet.multipart.max-request-size=1024MB

# nacos服务地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848

#开启熔断机制
feign.hystrix.enabled=true
# 设置hystrix超时时间,默认1000ms
#hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=6000

        3、启动类 

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableDiscoveryClient
@ComponentScan(basePackages = {"com.shang"})
public class VodApplication {
    public static void main(String[] args) {
        SpringApplication.run(VodApplication.class);
    }
}

 

        4、controller 

/**
     * 上传视频到阿里云
     * @param file
     * @return
     */
    @PostMapping("uploadAlyivideo")
    public R uploadAlyivideo(MultipartFile file){
        //获取要上传的视频id
        String videoId = vodService.uploadVideoAly(file);
        return R.ok();
    }

 

        5、service 

 /**
     * 上传视频到阿里云
     * @param file
     * @return
     */
    String uploadVideoAly(MultipartFile file);

 

        6、serviceImpl 

/**
     * 上传文件到阿里云
     * @param file
     * @return
     */
    @Override
    public String uploadVideoAly(MultipartFile file) {

        try {
            String accessKeyId = ConstantVodUtils.ACCESS_KEY_ID;
            String accessKeySecret = ConstantVodUtils.ACCESS_KEY_SECRET;
            String title = "哈哈哈";  //上传后的名称
            String fileName = file.getOriginalFilename();   //文件原始名称
            InputStream inputStream = file.getInputStream();

            UploadStreamRequest request = new UploadStreamRequest(accessKeyId, accessKeySecret, title, fileName, inputStream);
            /* 是否使用默认水印(可选),指定模板组ID时,根据模板组配置确定是否使用默认水印*/
            //request.setShowWaterMark(true);
            /* 自定义消息回调设置,参数说明参考文档 https://help.aliyun.com/document_detail/86952.html#UserData */
            //request.setUserData(""{\"Extend\":{\"test\":\"www\",\"localId\":\"xxxx\"},\"MessageCallback\":{\"CallbackURL\":\"http://test.test.com\"}}"");
            /* 视频分类ID(可选) */
            //request.setCateId(0);
            /* 视频标签,多个用逗号分隔(可选) */
            //request.setTags("标签1,标签2");
            /* 视频描述(可选) */
            //request.setDescription("视频描述");
            /* 封面图片(可选) */
            //request.setCoverURL("http://cover.sample.com/sample.jpg");
            /* 模板组ID(可选) */
            //request.setTemplateGroupId("8c4792cbc8694e7084fd5330e56a33d");
            /* 工作流ID(可选) */
            //request.setWorkflowId("d4430d07361f0*be1339577859b0177b");
            /* 存储区域(可选) */
            //request.setStorageLocation("in-201703232118266-5sejdln9o.oss-cn-shanghai.aliyuncs.com");
            /* 开启默认上传进度回调 */
            // request.setPrintProgress(true);
            /* 设置自定义上传进度回调 (必须继承 VoDProgressListener) */
            // request.setProgressListener(new PutObjectProgressListener());
            /* 设置应用ID*/
            //request.setAppId("app-1000000");
            /* 点播服务接入点 */
            //request.setApiRegionId("cn-shanghai");
            /* ECS部署区域*/
            // request.setEcsRegionId("cn-shanghai");
            UploadVideoImpl uploader = new UploadVideoImpl();
            UploadStreamResponse response = uploader.uploadStream(request);
            System.out.print("RequestId=" + response.getRequestId() + "\n");  //请求视频点播服务的请求ID
            if (response.isSuccess()) {
                System.out.print("VideoId=" + response.getVideoId() + "\n");
            } else { //如果设置回调URL无效,不影响视频上传,可以返回VideoId同时会返回错误码。其他情况上传失败时,VideoId为空,此时需要根据返回错误码分析具体错误原因
                System.out.print("VideoId=" + response.getVideoId() + "\n");
                System.out.print("ErrorCode=" + response.getCode() + "\n");
                System.out.print("ErrorMessage=" + response.getMessage() + "\n");
            }
            return response.getVideoId();
        } catch (Exception e){
            e.printStackTrace();
            return null;
        }
    }

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值