微服务项目实战技术点汇总:“尚硅谷的谷粒在线教育” 四、课程分类模块实现,课程管理模块,多级分类,多级联动,微服务复用

一、课程分类模块

1、修改路由(前端)

 /* 课程分类模块*/
  {
    path: '/subject',
    component: Layout,
    redirect: '/subject/list',
    name: '课程分类管理',
    meta: { title: '课程分类管理', icon: 'example' },
    children: [
      {
        path: 'list',
        name: '课程分类列表',
        component: () => import('@/views/subject/list'),
        meta: { title: '课程分类列表', icon: 'table' }
      },
      {
        path: 'save',
        name: '添加课程分类',
        component: () => import('@/views/subject/save'),
        meta: { title: '添加课程分类', icon: 'tree' }
      },
    ]
  },

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2、设计使用Excel表格添加分类课程页面(前端)

1、添加上传文件组件

在这里插入图片描述

2、编写页面

<template>
  <div class="app-container">
    <el-form label-width="120px">
      <el-form-item label="信息描述">
        <el-tag type="info">excel模版说明</el-tag>
        <el-tag>
          <i class="el-icon-download"/>
          <a :href="OSS_PATH + '/excel/%E8%AF%BE%E7%A8%8B%E5%88%86%E7%B1%BB%E5%88%97%E8%A1%A8%E6%A8%A1%E6%9D%BF.xls'">点击下载模版</a>
        </el-tag>
      </el-form-item>
      <el-form-item label="选择Excel">
        <el-upload
          ref="upload"
          :auto-upload="false"
          :on-success="fileUploadSuccess"
          :on-error="fileUploadError"
          :disabled="importBtnDisabled"
          :limit="1"
          :action="BASE_API+'/eduservice/edu-subject/insertExcel'"
          name="file"
          accept="application/vnd.ms-excel">
          <el-button slot="trigger" size="small" type="primary">选取文件</el-button>
          <el-button
            :loading="loading"
            style="margin-left: 10px;"
            size="small"
            type="success"
            @click="submitUpload">{{ fileUploadBtnText }}</el-button>
        </el-upload>
      </el-form-item>
    </el-form>
  </div>
</template>

<script>
	export default{
		data(){
			return{
				BASE_API: process.env.VUE_APP_BASE_API, // 接口API地址
			    // OSS_PATH: process.env.OSS_PATH, // 阿里云OSS地址
			    fileUploadBtnText: '上传到服务器', // 按钮文字
			    importBtnDisabled: false, // 按钮是否禁用,
			    loading: false,
			}
		},
		created() {

		},
		methods: {
		    submitUpload() {
		      this.fileUploadBtnText = '正在上传'
		      this.importBtnDisabled = true
		      this.loading = true
		      this.$refs.upload.submit()
		    },
        /* 上传成功*/
		    fileUploadSuccess(response) {
          /* 提示*/
          this.fileUploadBtnText = '导入成功'
          this.loading = false
              this.$message({
                  type: 'success',
                  message: response.message
              })
              /* 跳转到课程分类列表*/
               this.$router.push({name:'课程分类列表'})
		    },
        /* 上传失败*/
		    fileUploadError(response) {
          this.fileUploadBtnText = '导入失败'
              this.loading = false
              this.$message({
              type: 'error',
              message: '导入失败'
              })
		    }
		},
	}
</script>

<style>
</style>

在这里插入图片描述
在这里插入图片描述

3、设计课程分类展示(后端)

先确定前端需要的数据格式
在这里插入图片描述在这里插入图片描述

我们的课程分两级,所以为了方便和效率
我们建立两个实体类,一级分类一个,二级分类一个
然后根据两个实体类查询

1、编写两个实体类,分别代表两个级别

package com.yzpnb.eduservice.entity.subject;

import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

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

/**
 * 一级分类
 * */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class OneSubject {
    @ApiModelProperty("一级分类id")
    private String id;
    @ApiModelProperty("一级课程名")
    private String title;
    @ApiModelProperty("多个二级课程包含再一个一级分类")
    private List<TwoSubject> children=new ArrayList<>();
}

package com.yzpnb.eduservice.entity.subject;

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

/**
 * 二级分类
 */
@Data
public class TwoSubject {
    @ApiModelProperty("二级分类id")
    private String id;
    @ApiModelProperty("二级课程名")
    private String title;
}

在这里插入图片描述

2、编写Controller接口

 /**
     * 查询
     */
    //1、查询课程,根据id和parend_id决定树形结构
    @GetMapping("selectTree")
    @ApiOperation("查询树形结构课程目录")
    public Result selectTree(){
        List<OneSubject> list=eduSubjectService.selectAllOneSubject();
        return Result.ok().data("allSubject",list);
    }

在这里插入图片描述

3、service接口实体类

    /**查询所有课程,返回树形结构*/
    @Override
    public List<OneSubject> selectAllOneSubject() {
        //1、查询所有一级分类 parent_id 等于0
        QueryWrapper<EduSubject> queryOneWrapper=new QueryWrapper<>();
        queryOneWrapper.eq("parent_id","0");
        List<EduSubject> oneSubjects = baseMapper.selectList(queryOneWrapper);//baseMapper 是我们的Mapper继承的接口封装好的对象
        //2、查询所有二级分类 parent_id 不等于0
        QueryWrapper<EduSubject> queryTwoWrapper=new QueryWrapper<>();
        queryTwoWrapper.ne("parent_id","0");
        List<EduSubject> twoSubjects = baseMapper.selectList(queryTwoWrapper);//baseMapper 是我们的Mapper继承的接口封装好的对象
        //3、封装一级分类
        List<OneSubject> list=new ArrayList<>();//list用来保存最终返回结果集
        for (EduSubject eduOneSubject:oneSubjects) {//遍历所有一级分类
            /**1、创建一级分类对象,将所有一级分类集合中的每个对象的数据,赋值给一级分类对象*/
            OneSubject oneSubject=new OneSubject();
//            oneSubject.setId(eduSubject.getId());
//            oneSubject.setTitle(eduSubject.getTitle());
            BeanUtils.copyProperties(eduOneSubject,oneSubject);//这个是spring boot提供的工具类中的方法,封装了我上面注释的两条代码的实现逻辑
            /**2、遍历二级分类*/
            for (EduSubject eduTwoSubject:twoSubjects) {
                TwoSubject twoSubject=new TwoSubject();
                BeanUtils.copyProperties(eduTwoSubject,twoSubject);//将当前遍历对象的相应属性封装到二级分类对象中
                /**如果二级分类的parent_id等于当前一级分类的id就添加到此一级分类*/
                if(eduTwoSubject.getParentId().equals(eduOneSubject.getId())){//使用equals比较,因为==号比较的是内存地址,而我们要比的仅仅是字面值
                    oneSubject.getChildren().add(twoSubject);//将这个二级分类添加到当前一级目录的集合中
                }
            }
            /**3、将每个一级分类对象添加到集合中*/
            list.add(oneSubject);
        }
        return list;//将集合返回
    }

在这里插入图片描述

4、测试接口

在这里插入图片描述

4、课程分类展示(前端)

1、编写API接口

在这里插入图片描述

2、修改list页面

在这里插入图片描述

二、课程管理模块(添加课程)

需要用到的表
在这里插入图片描述

1、(后端)编写添加课程信息接口(涉及两张表,一次性添加课程基本信息和课程简介信息)

1、使用代码生成器直接生成java对应数据库中各个表的MVC结构

在这里插入图片描述

2、实体类

实体类:需要单独创建一个实体类用于保存用户提交的表单,因为,它有两张表的数据,其它两张表对应的实体类需要完善

package com.yzpnb.eduservice.entity.vo;

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

import java.math.BigDecimal;

/**
 * 课程基本信息实体类
 * */
@ApiModel(value = "课程基本信息", description = "编辑课程基本信息的表单对象")
@Data
public class CourseInfoVo {
    private static final long serialVersionUID = 1L;
    @ApiModelProperty(value = "课程ID")
    private String id;

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

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

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

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

    @ApiModelProperty(value = "课程销售价格,设置为0则可免费观看")
    private BigDecimal price;//这里使用BigDecimal是math类提供的比float和double精度更准确的一个类型,因为我们的价格是.00的形式

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

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

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

在这里插入图片描述
在这里插入图片描述

3、controller接口

package com.yzpnb.eduservice.controller;


import com.yzpnb.common_utils.Result;
import com.yzpnb.eduservice.entity.vo.CourseInfoVo;
import com.yzpnb.eduservice.service.EduCourseService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * <p>
 * 课程 前端控制器
 * </p>
 *
 * @author testjava
 * @since 2020-05-24
 * 课程基本信息
 */
@RestController
@RequestMapping("/eduservice/edu-course")
@CrossOrigin
public class EduCourseController {

    @Autowired
    private EduCourseService eduCourseService;

    /**
     * 添加
     */
    //1、添加课程基本信息
    @PostMapping("insertCourseInfo")
    public Result insertCourseInfo(@RequestBody CourseInfoVo courseInfoVo)//将用户提交的表单保存到此对象中
    {
        //调用接口
        String id=eduCourseService.insertCourseInfo(courseInfoVo);
        return Result.ok().data("courseId",id);//将添加完成后的id值返回
    }
}


在这里插入图片描述

4、service接口实现类

package com.yzpnb.eduservice.service.impl;

import com.yzpnb.eduservice.entity.EduCourse;
import com.yzpnb.eduservice.entity.EduCourseDescription;
import com.yzpnb.eduservice.entity.vo.CourseInfoVo;
import com.yzpnb.eduservice.mapper.EduCourseMapper;
import com.yzpnb.eduservice.service.EduCourseDescriptionService;
import com.yzpnb.eduservice.service.EduCourseService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yzpnb.service_base_handler.CustomExceptionHandler;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * <p>
 * 课程 服务实现类
 * </p>
 *
 * @author testjava
 * @since 2020-05-24
 */
@Service
public class EduCourseServiceImpl extends ServiceImpl<EduCourseMapper, EduCourse> implements EduCourseService {

    @Autowired
    EduCourseDescriptionService eduCourseDescriptionService;//注入课程简介信息的添加接口,我们添加课程简介的时候需要使用

    /**
     *添加课程基本信息
     * @return
     */
    @Override
    public String insertCourseInfo(CourseInfoVo courseInfoVo) {
        /**1、课程表中添加课程基本信息*/
        //1.1将courseInfoVo中保存的所有信息中挑出课程基本信息添加到基本信息表
        EduCourse eduCourse=new EduCourse();
        BeanUtils.copyProperties(courseInfoVo,eduCourse);
        //1.2调用baseMapper中添加方法将课程添加
        int index=baseMapper.insert(eduCourse);
        //1.3判断是否添加成功
        if(index<=0){
            throw new CustomExceptionHandler(20001,"添加课程信息失败");//抛出我们的自定义异常
        }
        /**2、课程简介表中添加课程简介*/
        //2.1将courseInfoVo中保存的信息中挑出简介信息添加到简介表中
        EduCourseDescription eduCourseDescription=new EduCourseDescription();
        //BeanUtils.copyProperties(eduCourseDescription,courseInfoVo);与下面的这行代码效果相同,但会自动生成id所以依然需要为其人为设置id
        eduCourseDescription.setDescription(courseInfoVo.getDescription());
        //**这两个表因为是1对1的关系,id值应该相同,将自动为Course表生成的id直接赋值给简介表**
        eduCourseDescription.setId(eduCourse.getId());
        //2.2调用接口添加信息
        eduCourseDescriptionService.save(eduCourseDescription);
        return eduCourse.getId();//返回id值
    }
}

在这里插入图片描述
在这里插入图片描述

5、测试

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2、(前端)课程管理模块

1、设计路由

/* 课程管理模块*/
  {
    path: '/course',
    component: Layout,
    redirect: '/course/list',
    name: '课程管理模块',
    meta: { title: '课程管理模块', icon: 'nested' },
    children: [
      {
        path: 'list',
        name: '课程展示',
        component: () => import('@/views/course/list'),
        meta: { title: '课程展示', icon: 'table' }
      },
      {
        path: 'save1',
        name: '课程添加',
        component: () => import('@/views/course/save1'),
        meta: { title: '课程添加', icon: 'tree' },
      },
      {
        path: 'save1/:id',
        name: '课程添加1',
        component: () => import('@/views/course/save1'),
        meta: { title: '编辑课程基本信息', icon: 'tree' },
        hidden:true
      },
      {
        path: 'save2/:id',
        name: '课程添加2',
        component: () => import('@/views/course/save2'),
        meta: { title: '编辑课程大纲', icon: 'tree' },
        hidden:true
      },
      {
        path: 'save3/:id',
        name: '课程添加3',
        component: () => import('@/views/course/save3'),
        meta: { title: '发布课程', icon: 'tree' },
        hidden:true
      },

在这里插入图片描述
在这里插入图片描述

2、编写对应api接口

import request from '@/utils/request'//引入request.js,它封装了axios请求处理

export default{
    /*保存课程信息 */
    saveCourseInfo(courseInfo) {
        return request({
          url: `/eduservice/edu-course/insertCourseInfo`,
          method: 'post',
          data:courseInfo
        })

     }
}

在这里插入图片描述

3、编写save1页面

<template>
  <div class="app-container">
    <!-- 导航-->
    <h2 style="text-align: center;">发布新课程</h2>
    <el-steps :active="1" process-status="wait" align-center style="margin-bottom: 40px;">
      <el-step title="填写课程基本信息"/>
      <el-step title="创建课程大纲"/>
      <el-step title="提交审核"/>
    </el-steps>
    <el-form label-width="120px">
    </el-form>
    <!-- 内容-->
    <el-form label-width="120px">
      <el-form-item label="课程标题">
        <el-input v-model="courseInfo.title" placeholder=" 示例:机器学习项目课:从基础到搭建项目视频课程。专业名称注意大小写"/>
      </el-form-item>
      <!-- 所属分类 TODO -->
      <!-- 课程讲师 TODO -->
      <el-form-item label="总课时">
        <el-input-number :min="0" v-model="courseInfo.lessonNum" controls-position="right" placeholder="请填写课程的总课时数"/>
      </el-form-item>
      <!-- 课程简介 TODO -->
      <el-form-item label="课程简介">
        <el-input v-model="courseInfo.description" placeholder=" 简介信息"/>
      </el-form-item>
      <!-- 课程封面 TODO -->
      <el-form-item label="课程价格">
        <el-input-number :min="0" v-model="courseInfo.price" controls-position="right" placeholder="免费课程请设置为0元"/></el-form-item>
      <el-form-item>
        <el-button :disabled="saveBtnDisabled" type="primary" @click="next">保存并下一步</el-button>
      </el-form-item>
    </el-form>
  </div>
</template>
<script>
import eduCourse from '@/api/course/eduCourse.js'//引入api接口
const defaultForm = {//创建默认表单内容对象
  title: '',        //课程标题
  teacherId: '',    //讲师id
  subjectId: '',    //课程分类id
  subjectParentId:'',//课程分类父id
  lessonNum: 0,     //总课时
  description: '',  //课程简介
  cover: '',
  price: 0          //课程价格
}
export default {
  data() {
    return {
      courseInfo: defaultForm,//为数据赋初始值
      saveBtnDisabled: false // 保存按钮是否禁用
    }
  },
  watch: {
    $route(to, from) {//监听路由是否发生变化
      console.log('watch $route')
      this.init()//如果路由改变,调用初始化函数
    }
  },
  created() {//页面加载时,调用初始化函数
    console.log('info created')
    this.init()
  },
  methods: {
    /* 初始化函数*/
    init() {
      if (this.$route.params && this.$route.params.id) {//如果当前路由的params有值并且传过来的值是id就执行
        const id = this.$route.params.id//将当前的id值拿出来
        console.log(id)
      } else {//若没有值,就直接赋值为默认的初始值
        this.courseInfo = { ...defaultForm }
      }
    },
    /* 下一步函数*/
    next() {//当点击下一步时
      console.log('next')
      this.saveBtnDisabled = true//让保存按钮可用
      if (!this.courseInfo.id) {//如果现在数据中没有id值
        this.saveData()//执行保存函数
      } else {
        this.updateData()
      }
    },
    // 保存
    saveData() {
      //调用api接口
      eduCourse.saveCourseInfo(this.courseInfo).then(response => {
        this.$message({//提示
          type: 'success',
          message: '保存成功!'
        })
        return response// 将响应结果传递给then
      }).then(response => {
        this.$router.push({name:"课程添加2",params:{id:response.data.courseId}})
      }).catch((response) => {
        this.$message({
          type: 'error',
          message: response.message
        })
      })
    },

    updateData() {//跳转路由,id为1
      this.$router.push({name:"课程添加2",params:{id:1}})
    }
  }
}
</script>

4、save2页面(用于测试,代码都是复制save1的,之后会改)

<template>
  <div class="app-container">
    <h2 style="text-align: center;">发布新课程</h2>
    <el-steps :active="2" process-status="wait" align-center style="margin-bottom: 40px;">
      <el-step title="填写课程基本信息"/>
      <el-step title="创建课程大纲"/>
      <el-step title="提交审核"/>
    </el-steps>
    <el-form label-width="120px">
      <el-form-item>
        <el-button @click="previous">上一步</el-button>
        <el-button :disabled="saveBtnDisabled" type="primary" @click="next">下一步</el-button>
      </el-form-item>
    </el-form>
  </div>
</template>
<script>
export default {
  data() {
    return {
      saveBtnDisabled: false // 保存按钮是否禁用
    }
  },
  created() {
    console.log('chapter created')
  },
  methods: {
    previous() {
      console.log('previous')
      this.$router.push({name:"课程添加",params:{id:1}})
    },
    next() {
      console.log('next')
      this.$router.push({name:"课程添加3",params:{id:1}})
    }
  }
}
</script>

5、测试

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3、完善讲师选择

1、添加页面组件

<!-- 课程讲师 -->
      <el-form-item label="课程讲师">
        <el-select
          v-model="courseInfo.teacherId"
          placeholder="请选择">
          <el-option
            v-for="teacher in teacherList"
            :key="teacher.id"
            :label="teacher.name"
            :value="teacher.id"/>
        </el-select>
      </el-form-item>

在这里插入图片描述

2、api接口

在这里插入图片描述

3、编写逻辑代码

在这里插入图片描述

4、完善课程分类选择(二级联动分类)

1、添加组件

<!-- 所属分类:级联下拉列表 -->

       <el-form-item label="课程类别">
         <!-- 一级分类 -->
         <el-select
           v-model="courseInfo.subjectParentId"
           placeholder="请选择"
           @change="oneSubjectChange(courseInfo.subjectParentId)">
           <el-option
             v-for="subject in oneSubject"
             :key="subject.id"
             :label="subject.title"
             :value="subject.id"
             />
         </el-select>

         <!-- 二级分类 -->
         <el-select v-model="courseInfo.subjectId" placeholder="请选择">
           <el-option
             v-for="subject in twoSubject"
             :key="subject.value"
             :label="subject.title"
             :value="subject.id"/>
         </el-select>
       </el-form-item>

在这里插入图片描述

2、api接口

在这里插入图片描述

3、编写逻辑代码

在这里插入图片描述
在这里插入图片描述

4、测试

在这里插入图片描述
在这里插入图片描述

5、复用阿里云OSS头像上传的微服务,实现课程封面上传(分模快微服务,一个服务到处使用)

1、添加组件

<el-form-item label="课程封面">
        <el-upload
          :show-file-list="false"
          :on-success="handleAvatarSuccess"
          :before-upload="beforeAvatarUpload"
          :action="BASE_API+'/ossservice/uploadFile'"
          class="avatar-uploader">
          <img :src="courseInfo.cover">
        </el-upload>
      </el-form-item>

2、代码实现

/* 上传成功调用*/
    handleAvatarSuccess(res, file) {
      console.log(res)// 上传响应
      console.log(URL.createObjectURL(file.raw))// base64编码
      this.courseInfo.cover = res.data.url
    },
    /* 上传之后调用*/
    beforeAvatarUpload(file) {
      const isJPG = file.type === 'image/jpeg'
      const isLt2M = file.size / 1024 / 1024 < 2
      if (!isJPG) {
        this.$message.error('上传头像图片只能是 JPG 格式!')
      }
      if (!isLt2M) {
        this.$message.error('上传头像图片大小不能超过 2MB!')
      }
      return isJPG && isLt2M
    },

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3、测试(测试前记住先将oss上传的后端微服务开启)

在这里插入图片描述

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

殷丿grd_志鹏

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

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

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

打赏作者

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

抵扣说明:

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

余额充值