布谷课堂1

布谷学院项目介绍

布谷课堂(谷粒学院)二

一、功能简介

谷粒学院,是一个B2C模式的职业技能在线教育系统,分为前台用户系统和后台运营平台。
在这里插入图片描述

二、技术架构

系统开发阶段使用了前后端分离架构,部署阶段使用了容器技术
在这里插入图片描述

day01

请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述

请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述

请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述

day02

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

开发讲师模块后端

准备工作
1、创建数据库bugu

2、数据表 guli_edu.sql
在这里插入图片描述

一、工程结构

在这里插入图片描述

guli-parent:根目录(父工程),管理子模块:

  • canal-client:canal数据库表同步模块

  • common:公共模块父节点

    • common-util:工具类模块,所有模块都可以依赖于它

    • service-base:service服务的base包,包含service服务的公共配置类,所有service模块依赖于它

    • spring-security:认证与授权模块,需要认证授权的service服务依赖于它

  • infrastructure:基础服务模块父节点

    • api-gateway:api网关服务
  • service:api接口服务父节点

    • service-edu:教学相关api接口服务

    • service-oss:阿里云oss api接口服务

    • service-cms:cms api接口服务

    • service-sms:短信api接口服务

    • service-trade:订单和支付相关api接口服务

    • service-statistics:统计报表api接口服务

    • service-ucenter:会员api接口服务

    • service-vod:视频点播api接口服务

二、创建父工程guli-parent

1、创建Spring Boot项目
使用 Spring Initializr 快速初始化一个 Spring Boot 项目

Group:com.yhn

Artifact:bugu-parent
2、删除src
3、版本改为2.2.1
在artifactId下面加一个pom标签表示是一个pom文件

  <artifactId>bugu_parent</artifactId>
    <packaging>pom</packaging>

4、pom的依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.yhn</groupId>
    <artifactId>bugu_parent</artifactId>
    <packaging>pom</packaging>
    <version>0.0.1-SNAPSHOT</version>
    <name>bugu_parent</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
        <cloud.version>Hoxton.RELEASE</cloud.version>
        <alibaba.version>2.2.0.RELEASE</alibaba.version>
        <mybatis-plus.version>3.3.1</mybatis-plus.version>
        <velocity.version>2.0</velocity.version>
        <swagger.version>2.7.0</swagger.version>
        <aliyun.oss.version>3.1.0</aliyun.oss.version>
        <jodatime.version>2.10.1</jodatime.version>
        <commons-fileupload.version>1.3.1</commons-fileupload.version>
        <commons-io.version>2.6</commons-io.version>
        <commons-lang.version>3.9</commons-lang.version>
        <httpclient.version>4.5.1</httpclient.version>
        <jwt.version>0.7.0</jwt.version>
        <aliyun-java-sdk-core.version>4.3.3</aliyun-java-sdk-core.version>
        <aliyun-java-sdk-vod.version>2.15.2</aliyun-java-sdk-vod.version>
        <aliyun-sdk-vod-upload.version>1.4.11</aliyun-sdk-vod-upload.version>
        <fastjson.version>1.2.28</fastjson.version>
        <gson.version>2.8.2</gson.version>
        <json.version>20170516</json.version>
        <commons-dbutils.version>1.7</commons-dbutils.version>
        <alibaba.easyexcel.version>2.1.1</alibaba.easyexcel.version>
        <apache.xmlbeans.version>3.1.0</apache.xmlbeans.version>
    </properties>
    <dependencyManagement>
        <dependencies>
            <!--Spring Cloud-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!--mybatis-plus 持久层-->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>${mybatis-plus.version}</version>
            </dependency>
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-generator</artifactId>
                <version>${mybatis-plus.version}</version>
            </dependency>

            <!-- velocity 模板引擎, Mybatis Plus 代码生成器需要 -->
            <dependency>
                <groupId>org.apache.velocity</groupId>
                <artifactId>velocity-engine-core</artifactId>
                <version>${velocity.version}</version>
            </dependency>

            <!--swagger-->
            <dependency>
                <groupId>io.springfox</groupId>
                <artifactId>springfox-swagger2</artifactId>
                <version>${swagger.version}</version>
            </dependency>
            <!--swagger ui-->
            <dependency>
                <groupId>io.springfox</groupId>
                <artifactId>springfox-swagger-ui</artifactId>
                <version>${swagger.version}</version>
            </dependency>

            <!--aliyunOSS-->
            <dependency>
                <groupId>com.aliyun.oss</groupId>
                <artifactId>aliyun-sdk-oss</artifactId>
                <version>${aliyun.oss.version}</version>
            </dependency>

            <!--日期时间工具-->
            <dependency>
                <groupId>joda-time</groupId>
                <artifactId>joda-time</artifactId>
                <version>${jodatime.version}</version>
            </dependency>

            <!--文件上传-->
            <dependency>
                <groupId>commons-fileupload</groupId>
                <artifactId>commons-fileupload</artifactId>
                <version>${commons-fileupload.version}</version>
            </dependency>

            <!--commons-io-->
            <dependency>
                <groupId>commons-io</groupId>
                <artifactId>commons-io</artifactId>
                <version>${commons-io.version}</version>
            </dependency>

            <!--commons-lang3-->
            <dependency>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons-lang3</artifactId>
                <version>${commons-lang.version}</version>
            </dependency>

            <!--httpclient-->
            <dependency>
                <groupId>org.apache.httpcomponents</groupId>
                <artifactId>httpclient</artifactId>
                <version>${httpclient.version}</version>
            </dependency>

            <!-- JWT -->
            <dependency>
                <groupId>io.jsonwebtoken</groupId>
                <artifactId>jjwt</artifactId>
                <version>${jwt.version}</version>
            </dependency>

            <!--aliyun-->
            <dependency>
                <groupId>com.aliyun</groupId>
                <artifactId>aliyun-java-sdk-core</artifactId>
                <version>${aliyun-java-sdk-core.version}</version>
            </dependency>
            <dependency>
                <groupId>com.aliyun</groupId>
                <artifactId>aliyun-java-sdk-vod</artifactId>
                <version>${aliyun-java-sdk-vod.version}</version>
            </dependency>
            <dependency>
                <groupId>com.aliyun</groupId>
                <artifactId>aliyun-sdk-vod-upload</artifactId>
                <version>${aliyun-sdk-vod-upload.version}</version>
            </dependency>

            <!--json-->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>${fastjson.version}</version>
            </dependency>
            <dependency>
                <groupId>org.json</groupId>
                <artifactId>json</artifactId>
                <version>${json.version}</version>
            </dependency>
            <dependency>
                <groupId>com.google.code.gson</groupId>
                <artifactId>gson</artifactId>
                <version>${gson.version}</version>
            </dependency>

            <dependency>
                <groupId>commons-dbutils</groupId>
                <artifactId>commons-dbutils</artifactId>
                <version>${commons-dbutils.version}</version>
            </dependency>

            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>easyexcel</artifactId>
                <version>${alibaba.easyexcel.version}</version>
            </dependency>

            <dependency>
                <groupId>org.apache.xmlbeans</groupId>
                <artifactId>xmlbeans</artifactId>
                <version>${apache.xmlbeans.version}</version>
            </dependency>

        </dependencies>
    </dependencyManagement>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

5、在bugu_parent下创建maven模块service
把src文件也可以删除
配置pom文件

 <artifactId>service</artifactId>
  <packaging>pom</packaging>
<dependencies>
    <dependency>
        <groupId>com.yhn</groupId>
        <artifactId>common-util</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </dependency>
 <!--<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
        </dependency>-->

        <!--hystrix依赖,主要是用  @HystrixCommand -->
        <!--<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>-->

        <!--服务注册-->
        <!-- <dependency>
             <groupId>org.springframework.cloud</groupId>
             <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
         </dependency>-->
        <!--服务调用-->
        <!-- <dependency>
             <groupId>org.springframework.cloud</groupId>
             <artifactId>spring-cloud-starter-openfeign</artifactId>
         </dependency>-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!--mybatis-plus-->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
         <version>3.0.5</version>
    </dependency>
    <!--mysql-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>

   
    <!-- velocity 模板引擎, Mybatis Plus 代码生成器需要 -->
    <dependency>
        <groupId>org.apache.velocity</groupId>
        <artifactId>velocity-engine-core</artifactId>
    </dependency>

    <!--swagger-->
    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger2</artifactId>
    </dependency>
    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger-ui</artifactId>
    </dependency>

    <!--lombok用来简化实体类:需要安装lombok插件-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>

    <!--日期时间工具-->
    <dependency>
        <groupId>joda-time</groupId>
        <artifactId>joda-time</artifactId>
    </dependency>

    <!--lang3-->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
    </dependency>

    <dependency>
        <groupId>commons-fileupload</groupId>
        <artifactId>commons-fileupload</artifactId>
    </dependency>
    <!--commons-io-->
    <dependency>
        <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
    </dependency>

    <!--json-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
    </dependency>
    <dependency>
        <groupId>org.json</groupId>
        <artifactId>json</artifactId>
    </dependency>
    <dependency>
        <groupId>com.google.code.gson</groupId>
        <artifactId>gson</artifactId>
    </dependency>

    <!--httpclient-->
    <dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpclient</artifactId>
    </dependency>
</dependencies>

7、在service中再创建一个子maven模块service_edu(讲师模块)

在这里插入图片描述
8、在bugu_parent下创建一个common的maven模块
然后删除src

 - 8.1 引入依赖
 <artifactId>common</artifactId>
    <packaging>pom</packaging>
<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <scope>provided </scope>
        </dependency>

        <!--mybatis-plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <scope>provided </scope>
        </dependency>

        <!--lombok用来简化实体类:需要安装lombok插件-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided </scope>
        </dependency>

        <!--swagger-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <scope>provided </scope>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <scope>provided </scope>
        </dependency>

        <!-- redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <!-- spring2.X集成redis所需common-pool2
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
            <version>2.6.0</version>
        </dependency>-->
    </dependencies>
</project>
 - 8.2 在common创建一个子maven工程service_base

在里面再创建一个com.yhn.servicebase包里面创建SwaggerConfig配置类

@Configuration//配置类
@EnableSwagger2 //swagger注解
public class SwaggerConfig {

    @Bean
    public Docket webApiConfig(){   //类型
        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("webApi")
                .apiInfo(webApiInfo())
                .select()
                .paths(Predicates.not(PathSelectors.regex("/admin/.*")))
                .paths(Predicates.not(PathSelectors.regex("/error.*")))
                .build();              //  表示当接口路径中有admin,error它就不进行显示

    }

    private ApiInfo webApiInfo(){ //设置在线文档中的信息

        return new ApiInfoBuilder()
                .title("网站-课程中心API文档")
                .description("本文档描述了课程中心微服务接口定义")
                .version("1.0")
                .contact(new Contact("java", "http://atguigu.com", "1123@qq.com"))
                .build();
    }
}


  • 8.3 要在service_edu中使用service_base里面的类,需要在service包的pom中引入service_base的依赖
     <dependency>
            <groupId>com.yhn</groupId>
            <artifactId>service_base</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
  • 8.4 在service_edu的启动类EduApplication中加一个注解,使得启动service_edu的时候能扫描加载到SwaggerConfig.java。就可以进行swagger的整合测试了,不设置的话项目启动只能扫描到自己当前项目下的
@SpringBootApplication
@ComponentScan(basePackages = {"com.yhn"}) //设置包扫描规则为了让扫描到同样在com.yhn包下的SwaggerConfig,并加载
public class EduApplication {

在这里插入图片描述

三、开发讲师管理模块

3.1、准备工作

1、创建application.properties配置文件

# 服务端口
server.port=8001
# 服务名
spring.application.name=service-edu

# 环境设置:dev开发环境、test、prod生产环境
spring.profiles.active=dev

# mysql数据库连接
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/bugu?serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=123456

#返回json的全局时间格式
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8

#mybatis日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

2、编写controller service mapper代码内容
mp提供代码生成器
在test包下

public class CodeGenerator {

    @Test
    public void run() {

        // 1、创建代码生成器
        AutoGenerator mpg = new AutoGenerator();

        // 2、全局配置
        GlobalConfig gc = new GlobalConfig();
        String projectPath = System.getProperty("user.dir");
        //需要改为绝对路径
        gc.setOutputDir("F:\\progrm_files\\MyProject\\bugu\\bugu_parent\\service\\service_edu" + "/src/main/java");

        gc.setAuthor("yhn");//作者名
        gc.setOpen(false); //生成后是否打开资源管理器
        gc.setFileOverride(false); //重新生成时文件是否覆盖

        //UserServie
        gc.setServiceName("%sService");	//去掉Service接口的首字母I

        gc.setIdType(IdType.ID_WORKER_STR); //主键策略,根据主键类型修改
        gc.setDateType(DateType.ONLY_DATE);//定义生成的实体类中日期类型
        gc.setSwagger2(true);//开启Swagger2模式

        mpg.setGlobalConfig(gc);

        // 3、数据源配置(需要改)
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setUrl("jdbc:mysql://localhost:3306/bugu?serverTimezone=GMT%2B8");
        dsc.setDriverName("com.mysql.cj.jdbc.Driver");
        dsc.setUsername("root");
        dsc.setPassword("123456");
        dsc.setDbType(DbType.MYSQL);
        mpg.setDataSource(dsc);

        // 4、包配置
        PackageConfig pc = new PackageConfig();
        pc.setModuleName("eduservice"); //模块名
        //包  com.yhn.eduservice
        pc.setParent("com.yhn");
        //包  com.yhn.eduservice.controller
        pc.setController("controller");
        pc.setEntity("entity");
        pc.setService("service");
        pc.setMapper("mapper");
        mpg.setPackageInfo(pc);

        // 5、策略配置
        StrategyConfig strategy = new StrategyConfig();

        strategy.setInclude("edu_teacher");//多张表时可以加逗号分开

        strategy.setNaming(NamingStrategy.underline_to_camel);//数据库表映射到实体的命名策略
        strategy.setTablePrefix(pc.getModuleName() + "_"); //生成实体时去掉表前缀

        strategy.setColumnNaming(NamingStrategy.underline_to_camel);//数据库表字段映射到实体的命名策略
        strategy.setEntityLombokModel(true); // lombok 模型 @Accessors(chain = true) setter链式操作

        strategy.setRestControllerStyle(true); //restful api风格控制器
        strategy.setControllerMappingHyphenStyle(true); //url中驼峰转连字符

        mpg.setStrategy(strategy);


        // 6、执行
        mpg.execute();
    }
}

在这里插入图片描述

3.2、开始编写

1、查询讲师列表

1、编写控制层

@RestController//里面的@Controller把类交给spring进行管理 @ResponseBody表示类里面的内容需要返回数据
@RequestMapping("/eduservice/teacher")
public class EduTeacherController {

    //把EduTeacherServiceImpl注入
    @Autowired
    private EduTeacherService teacherService;

    //1 查询讲师所有数据
    //rest风格
    @GetMapping("findAll")//这个里面的/可以加可以不加
    public List<EduTeacher> findAllTeacher() {
        //调用service的方法实现查询所有的操作
        List<EduTeacher> list = teacherService.list(null);//里面不需要其他条件
        return list;
    }

}

2、创建springboot的启动类

@SpringBootApplication
public class EduApplication {
    public static void main(String[] args) {
        SpringApplication.run(EduApplication.class, args);
    }

}

3、创建一个配置类,配置mapper扫描
新建一个配置包config
里面EduConfig.java

@Configuration
@MapperScan("com.yhn.eduservice.mapper")//这个也可以放在启动类中
public class EduConfig {

}

4、测试项目启动使用8001端口访问

在EduApplication.java启动类中中右键启动
访问地址是:http://localhost:8001/eduservice/teacher/findAll
返回一段json数据
在这里插入图片描述

2、讲师逻辑删除

1、在配置类EduConfig中配置逻辑删除的插件

  /**
     * ;逻辑删除插件
    */
    @Bean
    public ISqlInjector sqlInjector() {
        return new LogicSqlInjector();
    }

2、在EduTeacher类中给逻辑删除字段加一个注解 @TableLogic

	@ApiModelProperty(value = "逻辑删除 1(true)已删除, 0(false)未删除")
    @TableLogic
    private Boolean isDeleted;

3、编写controller中的方法

  //2 逻辑删除的方法
    @DeleteMapping("{id}")  //id值需要路径进行传递
    public boolean removeTeacher(@PathVariable String id) {//表示得到路径中参数
        boolean flag = teacherService.removeById(id);
        return flag;
    }

4、如何测试
借助一些工具进行测试
- swagger测试(重点)
- postman(了解)

  • 整合swagger进行测试

    • swagger2介绍
      在这里插入图片描述
  • 测试,在启动类EduApplication中先启动项目

    • http://localhost:8001/swagger-ui.html查看swagger是否启动成功
      在这里插入图片描述

在这里插入图片描述
id 输入要删除的id
Try it out进行测试

在这里插入图片描述
数据库中is_delete变为1
在这里插入图片描述

提供添加api的注解可以在测试的时候更加方便,对功能没有影响

 //2 逻辑删除的方法
    @ApiOperation(value = "逻辑删除讲师")
    @DeleteMapping("{id}")  //id值需要路径进行传递
    public boolean removeTeacher(@ApiParam(name = "id",value="讲师id",required = true) @PathVariable String id) {//表示得到路径中参数
        boolean flag = teacherService.removeById(id);
        return flag;
    }

在这里插入图片描述

四、统一返回结果对象

第一步:在common包下创建子maven模块common_utils
在这里插入图片描述
第二步:写一个interface定义数据返回状态码
成功:20000
失败:20001

package com.yhn.commonutils;

public interface ResultCode {
    public static Integer SUCCESS = 20000;
    
    public static Integer ERROR = 20001;
}

第三步:创建结果集


//统一返回结果的类
package com.yhn.commonutils;

@Data
public class R {

    @ApiModelProperty(value = "是否成功")//方便swagger测试使用
    private Boolean success;

    @ApiModelProperty(value = "返回码")
    private Integer code;

    @ApiModelProperty(value = "返回消息")
    private String message;

    @ApiModelProperty(value = "返回数据")
    private Map<String, Object> data = new HashMap<String, Object>();

    //把构造方法私有
    private R() {}

    //成功静态方法
    public static R ok() {
        R r = new R();
        r.setSuccess(true);
        r.setCode(ResultCode.SUCCESS);
        r.setMessage("成功");
        return r;
    }

    //失败静态方法
    public static R error() {
        R r = new R();
        r.setSuccess(false);
        r.setCode(ResultCode.ERROR);
        r.setMessage("失败");
        return r;
    }

    public R success(Boolean success){
        this.setSuccess(success);
        return this; //this就是当前类的对象r
    }

    public R message(String message){
        this.setMessage(message);
        return this;
    }

    public R code(Integer code){
        this.setCode(code);
        return this;
    }

    public R data(String key, Object value){
        this.data.put(key, value);
        return this;
    }

    public R data(Map<String, Object> map){
        this.setData(map);
        return this;
    }
}

第四步把com.yhn.commonutils包也引入到service模块下
在service的pom文件里

<dependency>
            <groupId>com.yhn</groupId>
            <artifactId>common_utils</artifactId>
            <version>0.0.1-SNAPSHOT</version>
</dependency>

第五步:修改控制器EduTeacherController,把接口方法返回结果都是R
注意:R的包不能导错

import com.yhn.commonutils.R;
@Api(description = "讲师管理")//为了在swagger测试的时候更加清晰
@RestController//里面的@Controller把类交给spring进行管理 @ResponseBody表示里面的内容需要返回数据
@RequestMapping("/eduservice/teacher")
public class EduTeacherController {

    //吧service注入
    @Autowired
    private EduTeacherService teacherService;

    //1 查询讲师所有数据
    //rest风格
    @ApiOperation(value = "所有讲师列表")
    @GetMapping("findAll")//这个里面的/可以加可以不加
    public R findAllTeacher() {
        //调用service的方法实现查询所有的操作
        List<EduTeacher> list = teacherService.list(null);//里面不需要其他条件
        return R.ok().data("items",list);
        //R.ok()会返回一个有数据的r.然后提供r调R里面的方法data把list数据存入map类型的data中
    }

    //2 逻辑删除的方法
    @ApiOperation(value = "逻辑删除讲师")
    @DeleteMapping("{id}")  //id值需要路径进行传递
    public R removeTeacher(@ApiParam(name = "id",value="讲师id",required = true) @PathVariable String id) {//表示得到路径中参数
        boolean flag = teacherService.removeById(id);
        if (flag) {
            return R.ok();
        } else {
            return R.error();
        }
    }

}


再次测试
查询的
在这里插入图片描述
删除的
在这里插入图片描述

五、讲师管理模块其他功能

5.1、分页功能

1、查询分页

1、在EduConfig类中配置分页插件

 /**
     * 分页插件
     */
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        return new PaginationInterceptor();
    }

2、在controller编写讲师分页查询的方法

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;//包不能导错
 // 3 分页查询讲师的方法
    @GetMapping("pageTeacher/{current}/{limit}")
    public R pageListTeacher(@PathVariable long current,@PathVariable long limit) {

        //创建page对象
        Page<EduTeacher> pageTeacher = new Page<>(current,limit);

        //调用方法的时候,底层封装,把所有分页所有数据封装到pageTeacher对象里面
        teacherService.page(pageTeacher, null);
        long total = pageTeacher.getTotal();//总记录数
        List<EduTeacher> records = pageTeacher.getRecords();//数据list集合

//        HashMap map = new HashMap();
//        map.put("total", total);
//        map.put("rows", records);
//        return R.ok().data(map);

        return R.ok().data("total", total).data("rows", records);//data是map集合可以这样put

    }

3、启动项目测试

2、条件查询分页

在这里插入图片描述

多条件组合查询带分页
第一步把条件值传递到接口里
1、 把条件值封装到对象中
在entity下创建一个vo包,在里面创建一个TeacherQuery类

@Data
public class TeacherQuery {

@ApiModelProperty(value = "教师名称,模糊查询")
private String name;

@ApiModelProperty(value = "头衔 1高级讲师 2首席讲师")
private Integer level;

@ApiModelProperty(value = "查询开始时间", example = "2019-01-01 10:10:10")
private String begin;//注意,这里使用的是String类型,前端传过来的数据无需进行类型转换

@ApiModelProperty(value = "查询结束时间", example = "2019-12-01 10:10:10")
private String end;

}

2、编写EduTeacherController.java

import org.springframework.util.StringUtils;
 //4 条件查询带分页的方法
    @GetMapping("pageTeacherCondition/{current}/{limit}")
    public R pageTeacherCondition(@PathVariable long current, @PathVariable long limit, TeacherQuery teacherQuery) {
        //创建一个page对象
        Page<EduTeacher> pageTeacher = new Page<>(current, limit);
        //构建条件
        QueryWrapper<EduTeacher> wrapper = new QueryWrapper<>();
        //多条件组合查询 类似动态sql
        String name = teacherQuery.getName();
        Integer level = teacherQuery.getLevel();
        String begin = teacherQuery.getBegin();
        String end = teacherQuery.getEnd();
        //判断条件值是否为空,如果不为空拼接条件
        if (!StringUtils.isEmpty(name)) {
            //构建条件
            wrapper.like("name", name);
        }
        if (!StringUtils.isEmpty(level)) {
            wrapper.eq("level", level);
        }
        if (!StringUtils.isEmpty(begin)) {
            wrapper.ge("gmt_create", begin);//gmt_create是表中的字段名,ge是大于等于
        }
        if (!StringUtils.isEmpty(end)) {
            wrapper.le("gmt_create", end);//le 是小于等于
        }
         //排序
        wrapper.orderByDesc("gmt_create");
        
        //调用方法实现条件查询分页
        teacherService.page(pageTeacher, wrapper);

        long total = pageTeacher.getTotal();//总记录数
        List<EduTeacher> records = pageTeacher.getRecords();//数据list集合

        return R.ok().data("total", total).data("rows", records);//data是map集合可以这样put

    }

3、启动测试
在这里插入图片描述
4、第二种获取参数的方法@RequestBody(required=false),加required=false表示这个值可以没有,然后需要改为post提交(用这种方式,不然前端的参数提交不了不能使用条件查询)

  //4 条件查询带分页的方法
    @PostMapping("pageTeacherCondition/{current}/{limit}")
    public R pageTeacherCondition(@PathVariable long current,
     @PathVariable long limit, @RequestBody(required=false) TeacherQuery teacherQuery) {
        //创建一个page对象

测试
在这里插入图片描述
条件什么都不写是查询所有

5.2、添加讲师

1、自动填充

1、在实体类添加自动填充注解

 @ApiModelProperty(value = "创建时间")
    @TableField(fill = FieldFill.INSERT)
    private Date gmtCreate;

    @ApiModelProperty(value = "更新时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date gmtModified;

2、创建自动填充类
在service_base模块下
com.yhn.servicebase.handler包下

//编写处理器来处理注解@TableField
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
                                          //传的类中的属性名称,不是字段名称
        this.setFieldValByName("gmtCreate", new Date(), metaObject);
        this.setFieldValByName("gmtModified", new Date(), metaObject);
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        this.setFieldValByName("gmtModified", new Date(), metaObject);
    }
}

这个注解之前已经在EduApplication主启动类中加过了

@ComponentScan(basePackages = {"com.yhn"}) //改变扫描规则为了让扫描到同样在com.yhn包下的SwaggerConfig
2、编写添加讲师的controller
     //添加讲师接口的方法
    @PostMapping("addTeacher")
    public R addTeacher(@RequestBody EduTeacher eduTeacher) {
        boolean save = teacherService.save(eduTeacher);
        if (save) {
            return R.ok();
        } else {
            return R.error();
        }
    }

时间和id需要去掉,时间是自动填充,id是自动生成
在这里插入图片描述

5.3、讲师修改功能

1、根据讲师id进行查询
   //根据讲师id进行查询
    @GetMapping("getTeacher/{id}")
    public R getTeacher(@PathVariable String id) {
        EduTeacher eduTeacher = teacherService.getById(id);
        return R.ok().data("teacher", eduTeacher);
    }
2、讲师修改
 //讲师修改
    @PostMapping("updateTeacher")
    public R updateTeacher(@RequestBody EduTeacher eduTeacher) {
        boolean flag = teacherService.updateById(eduTeacher);
        if (flag) {
            return R.ok();
        } else {
            return R.error();
        } 

    }

修改的时候必须有id值,是根据id值然后把其他值修改进去.时间要删除,不然会报错
在这里插入图片描述

5.4统一异常处理

5.4.1全局异常处理

1、在service_base中的com.yhn.servicebase下再建一个包exceptionhandler
里面建类GlobalExceptionHandler

@ControllerAdvice
public class GlobalExceptionHandler {
    //指定出现什么异常执行这个方法
    @ExceptionHandler(Exception.class)
    @ResponseBody  //为了能够返回数据
    public R error(Exception e) {
        e.printStackTrace();
        return R.error().message("执行了全局异常");
    }
}

2、然后发现R不是引入的不是我们创建的R类所以要
在service_base模块中引入common_utils模块

 <dependencies>
        <dependency>
            <groupId>com.yhn</groupId>
            <artifactId>common_utils</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
    </dependencies>

3、问题原来在service模块中引入了common_utils和service_base模块,现在又在service_base中引入了common_utils,所以在service模块中会造成common_utils引入多次,所以要把service模块中引入的common_utils部分删除。maven中有依赖传递在3里面只需要引入2 就行
在这里插入图片描述
4、在方法里加 int i=10 /0;进行异常测试

在这里插入图片描述

5.4.2、特定异常处理

在统一异常处理类GlobalExceptionHandler添加规则

  //特定异常
    @ExceptionHandler(ArithmeticException.class)
    @ResponseBody
    public R error(ArithmeticException e) {
        e.printStackTrace();
        return R.error().message("ArithmeticException");
    }
5.4.3、自定义异常处理

第一步、创建自定义异常类继承RuntimeExcept
servicebase.exceptionhandler包下

@Data
@AllArgsConstructor
@NoArgsConstructor
public class BuguException extends RuntimeException {

    private Integer code;//状态码

    private Integer msg;//异常信息
}

第二步、在统一异常处理类添加规则

 //自定义异常处理(自己写的异常)
    @ExceptionHandler(BuguException.class)
    @ResponseBody
    public R error(BuguException e) {
        e.printStackTrace();
        return R.error().code(e.getCode()).message(e.getMsg());
    }

第三步、执行自定义异常
这个异常是自己写的,系统不会自动抛出,需要手动抛出

	try {
            int i = 10 / 0;
        } catch (Exception e) {
            //执行自定义异常
            throw new BuguException(20001, "执行了自定义异常处理");
        }

5.5统一日志处理

在这里插入图片描述

# 设置日志级别 warn只显示警告信息
logging.level.root=warn
Logback日志工具

和log4j相似
把日志不仅仅可以输出的控制台还能输出到文件中
1、先把之前配置文件中的日志设置删掉

# 设置日志级别
#logging.level.root=info

#mybatis日志
#mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

2、resources中创建logback-spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration  scan="true" scanPeriod="10 seconds">
    <!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 -->
    <!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true -->
    <!-- scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
    <!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->

    <contextName>logback</contextName>
         <!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义变量后,可以使“${}”来使用变量。
                                         /要写对           bugu_1010/edu 不用提前创建,系统自己创建              -->
    <property name="log.path" value="F:/progrm_files/bugu_1010/edu" />
 
    <!-- 彩色日志 -->
    <!-- 配置格式变量:CONSOLE_LOG_PATTERN 彩色日志格式 -->
    <!-- magenta:洋红 -->
    <!-- boldMagenta:粗红-->
    <!-- cyan:青色 -->
    <!-- white:白色 -->
    <!-- magenta:洋红 -->
    <property name="CONSOLE_LOG_PATTERN"
              value="%yellow(%date{yyyy-MM-dd HH:mm:ss}) |%highlight(%-5level) |%blue(%thread) |%blue(%file:%line) |%green(%logger) |%cyan(%msg%n)"/>


    <!--输出到控制台-->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
        <!-- 例如:如果此处配置了INFO级别,则后面其他位置即使配置了DEBUG级别的日志,也不会被输出 -->
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>INFO</level>
        </filter>
        <encoder>
            <Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
            <!-- 设置字符集 -->
            <charset>UTF-8</charset>
        </encoder>
    </appender>


    <!--输出到文件-->

    <!-- 时间滚动输出 level为 INFO 日志 -->
    <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <file>${log.path}/log_info.log</file>
        <!--日志文件输出格式-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 每天日志归档路径以及格式 -->
            <fileNamePattern>${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文件保留天数-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日志文件只记录info级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>INFO</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- 时间滚动输出 level为 WARN 日志 -->
    <appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <file>${log.path}/log_warn.log</file>
        <!--日志文件输出格式-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset> <!-- 此处设置字符集 -->
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/warn/log-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文件保留天数-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日志文件只记录warn级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>warn</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>


    <!-- 时间滚动输出 level为 ERROR 日志 -->
    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <file>${log.path}/log_error.log</file>
        <!--日志文件输出格式-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset> <!-- 此处设置字符集 -->
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文件保留天数-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日志文件只记录ERROR级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!--
        <logger>用来设置某一个包或者具体的某一个类的日志打印级别、以及指定<appender>。
        <logger>仅有一个name属性,
        一个可选的level和一个可选的addtivity属性。
        name:用来指定受此logger约束的某一个包或者具体的某一个类。
        level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
              如果未设置此属性,那么当前logger将会继承上级的级别。
    -->
    <!--
        使用mybatis的时候,sql语句是debug下才会打印,而这里我们只配置了info,所以想要查看sql语句的话,有以下两种操作:
        第一种把<root level="INFO">改成<root level="DEBUG">这样就会打印sql,不过这样日志那边会出现很多其他消息
        第二种就是单独给mapper下目录配置DEBUG模式,代码如下,这样配置sql语句会打印,其他还是正常DEBUG级别:
     -->
    <!--开发环境:打印控制台-->
    <springProfile name="dev">
        <!--可以输出项目中的debug日志,包括mybatis的sql日志-->
        <logger name="com.yhn" level="INFO" />

        <!--
            root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性
            level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,默认是DEBUG
            可以包含零个或多个appender元素。
        -->
        <root level="INFO">
            <appender-ref ref="CONSOLE" />
            <appender-ref ref="INFO_FILE" />
            <appender-ref ref="WARN_FILE" />
            <appender-ref ref="ERROR_FILE" />
        </root>
    </springProfile>


    <!--生产环境:输出到文件-->
    <springProfile name="pro">

        <root level="INFO">
            <appender-ref ref="CONSOLE" />
            <appender-ref ref="DEBUG_FILE" />
            <appender-ref ref="INFO_FILE" />
            <appender-ref ref="ERROR_FILE" />
            <appender-ref ref="WARN_FILE" />
        </root>
    </springProfile>

</configuration>

在这里插入图片描述
3、如果程序出现异常把错误输出到文件
在GlobalExceptionHandler全局异常处理 加注解@Slf4j

@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

4、在异常处理里面加一行代码log.error(e.getMsg());//把错误信息写到文件中去

 //自定义异常处理(自己写的异常)
    @ExceptionHandler(BuguException.class)
    @ResponseBody
    public R error(BuguException e) {
        log.error(e.getMsg());//把错误信息写到文件中去
        e.printStackTrace();
        return R.error().code(e.getCode()).message(e.getMsg());
    }

day03-项目前端

vscode介绍

在这里插入图片描述
创建文件写代码并执行,使用open with Live server,执行。
在这里插入图片描述

vue.js是什么

vue介绍

使用!快捷生成html页面
2、在vscode中抽取vue的代码片段
在这里插入图片描述

day04-项目前端和前端环境搭建

搭建项目前端页面环境

1、把vue-admin-template-master的这个前端框架解压到自己的vscode工作目录下
2、用vscode把这个文件用终端打开
3、安装依赖,npm install下载package.json里面的依赖
在这里插入图片描述
下载完成后的依赖在node_modules文件夹下。如果失败就把这个文件删除再重新下载
4、启动下载好依赖的项目 命令

npm run dev

在这里插入图片描述

框架中目录介绍

在这里插入图片描述
在这里插入图片描述
框架可以自动帮我们把es6转为es5执行

day05-讲师管理前端开发

注意:每次改完前端都要 ctrl + s保存

后台系统登录功能改造

在这里插入图片描述
在这里插入图片描述
4、开发接口
在controller层中新建一个类


/**
 * 模拟登录的方法
 *
 * @author YHN
 * @create 2021-03-03 10:32
 */
@RestController
@RequestMapping("/eduservice/user")
@CrossOrigin  //解决跨域
public class EduLoginController {

    //login
    @PostMapping("login")
    public R login() {
        return R.ok().data("token", "admin");//和前端中user.js里面返回数据的名字token一样
    }

    //info
    @GetMapping("info")
    public R info() {                                                //头像
        return R.ok().data("roles","[admin]").data("name","admin").data("avatar","https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif");

    }
}

5、修改api文件夹login.js文件修改本地接口路径

 url: '/eduservice/user/login',
    method: 'post',

 url: '/eduservice/user/info',
    method: 'get',

6、最终测试。启动前端测试
然后点登录出现问题
在这里插入图片描述
7、跨域的解决方式
(1) 在后端接口controller加一个注解

@CrossOrigin  //解决跨域
public class EduLoginController {

(2)使用网关解决

测试成功
在这里插入图片描述
在这里插入图片描述
每次都是两次同样请求,第一次先做一个域请求看服务器是否能联通,能联通之后才发送请求

框架开发过程介绍

第一步添加路由,按照index.js里面实例代码改

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

第二步点击某个路由显示里面的内容
在这里插入图片描述
第三步在api文件中创建js文件,定义接口地址和参数

import request from '@/utils/request'

export function getList(params) {
  return request({ //表示用到引入的request
    url: '/table/list',
    method: 'get',
    params   //参数
  })
}

第四步 创建vue页面引入js文件,调用方法实现功能
在这里插入图片描述

讲师列表

第一步 在router/index.js中添加路由
这个路由修改后是在页面中显示
复制一份example路由然后修改

{
    path: '/teacher',
    component: Layout, //布局
    redirect: '/teacher/table',
    name: '讲师管理',
    meta: { title: '讲师管理', icon: 'example' },//icon是前面的图标
    children: [
      {
        path: 'table',
        name: '讲师列表',
        component: () => import('@/views/edu/teacher/list'),//点击之后跳转到这个页面
        meta: { title: '讲师列表', icon: 'table' }
      },
      {
        path: 'save',
        name: '添加讲师',
        component: () => import('@/views/edu/teacher/save'),
        meta: { title: '添加讲师', icon: 'tree' }
      }
    ]
  },

在这里插入图片描述

第二步 创建路由对应的页面
@/相当于./ 当前路径。
/views/edu/teacher/list.vue

<template>
  <div class="app-container">
      讲师列表
  </div>
</template>

/views/edu/teacher/save.vue

<template>
  <div class="app-container">
      添加讲师
  </div>
</template>

然后在路由中引入(第一步的时候已经引入)

第三步 在api文件夹中中创建edu/teaher.js定义访问的接口地址

import request from '@/utils/request'  

export default{
    //1 讲师列表(条件分页查询)
    getTeacherListPage(current,limit,teacherQuery){
        return request({  //表示用到引入的request
            // url: '/eduservice/teacher/pageTeacherCondition/'+current+"/"+limit',
            url: `/eduservice/teacher/pageTeacherCondition/${current}/${limit}`,//用的是飘号``
            method: 'post',
           //teacherQuery 条件对象,后端使用RequestBody获取数据
           data:teacherQuery //data表示把对象转换为json进行传递到接口里面
          })

    }
}

第四步在讲师列表页面list.vue调用定义的接口方法,得到接口返回的数据。

<template>
  <div class="app-container">
      讲师列表
  </div>
</template>
<script>
//引入调用teacher.js文件
import teacher from '@/api/edu/teacher'

export default { //表示可以被其他的调用
    //写核心代码的位置
    // data:{
    // },
    data(){   //定义变量和初始值
        return {
            list:null,  //查询之后接口返回集合
            page:1,//当前页
            limit:10,//每页记录数
            total:0,//总记录数
            teacherQuery:{}//条件封装对象
        }
    },
    created(){ //在页面渲染之前之前执行,调用methods定义的方法
      //调用
      this.getList()
    },methods:{//创建具体的方法,调用teacher.js定义的方法
        //讲师列表的方法
        getList(){
            teacher.getTeacherListPage(this.page,this.limit,this.teacherQuery)
               .then(response=>{ //请求成功
                   //response接口返回的数据
                  //console.log(response)
                  this.list = response.data.rows
                  this.total = response.data.total //total不用写错,写错分页用不了
                  
               })  
               .catch(error=>{
                   console.log(error)})  //请求失败
      }
    }
}
</script>

在这里插入图片描述
第五步 把数据在页面中显示需要使用到element插件
list.vue

<template>

  <div class="app-container">
  
 	 <!-- 表格 -->
    <el-table
    :data="list"
    border
    element-loading-text="数据加载中"
    stripe
    style="width: 100%">
     <el-table-column
        label="序号"
        width="70"
        align="center">
        <template slot-scope="scope">
          {{ (page - 1) * limit + scope.$index + 1 }}
        </template>
      </el-table-column>
      <el-table-column prop="name" label="名称" width="80" />

      <el-table-column label="头衔" width="80">
          <!-- 整个表是一个域scope,可以得到整个表中的内容 -->
        <template slot-scope="scope">  
            <!-- == 只判断大小,===判断大小和类型 -->
          {{ scope.row.level===1?'高级讲师':'首席讲师' }}
        </template>
     </el-table-column>

      <el-table-column prop="intro" label="简介" />
      <el-table-column prop="career" label="资历" />

      <el-table-column prop="gmtCreate" label="添加时间" width="160"/>

      <el-table-column prop="sort" label="排序" width="60" />

      <el-table-column label="操作" width="200" align="center">
        <template slot-scope="scope">
          <router-link :to="'/teacher/edit/'+scope.row.id">
            <el-button type="primary" size="mini" icon="el-icon-edit">修改</el-button>
          </router-link>
          <el-button type="danger" size="mini" icon="el-icon-delete" @click="removeDataById(scope.row.id)">删除</el-button>
        </template>
      </el-table-column>
    </el-table>
  </div>
</template>

在这里插入图片描述

讲师分页

list.vue

 </el-table>
 
 <!-- 分页 -->
 <el-pagination
    :current-page="1"
    :page-size="5"
    :total="7" 
    background
    style="text-align:center;"
    layout="total,prev, pager, next,jumper"
    @current-change="getList"
    >
    </el-pagination>

然后修改一下讲师列表的功能,实现页码的切换可以使分页查询不同页
list.vue

  //讲师列表的方法
        getList(page=1){
            this.page=page //为了做到分页的切换,页数会从分页的 @current-change="getList"中传过来

在这里插入图片描述

讲师条件查询

list.vue的

里面

 <!--查询表单 在一行显示 -->
    <el-form :inline="true" class="demo-form-inline">
      <el-form-item> 
      <!-- name可以不在data中定义 -->
        <el-input v-model="teacherQuery.name" placeholder="讲师名"/>
      </el-form-item>

      <el-form-item>
        <el-select v-model="teacherQuery.level" clearable placeholder="讲师头衔">
          <el-option :value="1" label="高级讲师"/>
          <el-option :value="2" label="首席讲师"/>
        </el-select>
      </el-form-item>

      <el-form-item label="添加时间">
        <el-date-picker
          v-model="teacherQuery.begin"
          type="datetime"
          placeholder="选择开始时间"
          value-format="yyyy-MM-dd HH:mm:ss"
          default-time="00:00:00"
        />
      </el-form-item>
      <el-form-item>
        <el-date-picker
          v-model="teacherQuery.end"
          type="datetime"
          placeholder="选择截止时间"
          value-format="yyyy-MM-dd HH:mm:ss"
          default-time="00:00:00"
        />
      </el-form-item>

      <el-button type="primary" icon="el-icon-search" @click="getList()">查询</el-button>
      <el-button type="default" @click="resetData()">清空</el-button>
    </el-form>

因为是双向绑定,输入值之后再次查询

 teacher.getTeacherListPage(this.page,this.limit,this.teacherQuery)

里面就有teacherQuery条件查询了

在这里插入图片描述

清空功能的实现:

清空表单中的条件数据如何查询所有数据
在methods中新增一个方法

   },
      resetData(){ //清空的方法
         //表单输入项数据清空,因为是双向绑定当teacherQuery为空,则表单中也变为空
         this.teacherQuery={}
         
         //查询所有讲师的数据
         this.getList()

绑定方法
在这里插入图片描述

讲师删除

在绑定事件的
在这里插入图片描述
4、测试一下有没有显示
list.vue新建一个删除的方法

,
     removeDataById(id){
         alert(id)
     }
      

在这里插入图片描述
5、在api/edu/teacher.js里面添加删除的方法
写在export default{ 里面

,
    //2 删除讲师
    deleteTeacherId(id){
        return request({  //表示用到引入的request
        
            url: `/eduservice/teacher/${id}`, //用的是飘号``
            method: 'delete'
          })
    }

6、页面调用方法实现删除
之前引入过teacher.js所以直接调用
在list.vue里面修改removeDataById方法

,
     removeDataById(id){

          this.$confirm('此操作将永久删除讲师记录, 是否继续?', '提示', {
            confirmButtonText: '确定',
            cancelButtonText: '取消',
            type: 'warning'
        }).then(() => {  //点击确定执行这个方法
        //调用删除方法
            teacher.deleteTeacherId(id)
            .then(response => { //删除成功
                //提示信息
                this.$message({
                type: 'success',
                message: '删除成功!'
                })
                //回到列表页面,刷新数据
                this.getList()               
          })//因为在request.js中给外面把错误信息做了封装,所以可以不用写catcath
        })
     }

在这里插入图片描述

讲师添加功能

一、路由中的代码

  {
        path: 'save',//在浏览器中的地址为/teacher/table
        name: '添加讲师',
        component: () => import('@/views/edu/teacher/save'),
        meta: { title: '添加讲师', icon: 'tree' }
      }

二、api中定义接口的地址
teacher.js

,
    //添加讲师
    addTeacher(teacher){
        return request({
            url:`/eduservice/teacher/addTeacher`,
            method:"post",
            data:teacher
        })
    }

三、在页面中实现调用
views/edu/teacher/save.vue

<template>
  <div class="app-container">
    <!-- 讲师表单 -->
     <el-form label-width="120px">
      <el-form-item label="讲师名称">
        <el-input v-model="teacher.name"/>
      </el-form-item>
      <el-form-item label="讲师排序">
        <el-input-number v-model="teacher.sort" controls-position="right" min="0"/>
      </el-form-item>
      <el-form-item label="讲师头衔">
        <el-select v-model="teacher.level" clearable placeholder="请选择">
          <el-option :value="1" label="高级讲师"/>
          <el-option :value="2" label="首席讲师"/>
        </el-select>
      </el-form-item>
      <el-form-item label="讲师资历">
        <el-input v-model="teacher.career"/>
      </el-form-item>
      <el-form-item label="讲师简介">
        <el-input v-model="teacher.intro" :rows="10" type="textarea"/>
      </el-form-item>

      <!-- 讲师头像:TODO -->

      <el-form-item>
        <el-button :disabled="saveBtnDisabled" type="primary" @click="saveTeacher">保存</el-button>
      </el-form-item>
    </el-form>

  </div>
</template>
<script>
import teacherApi from '@/api/edu/teacher'
export default {
  data() {
    return {
      teacher:{
       name: '',
        sort: 0,
        level: 1,
        career: '',
        intro: '',
        avatar: ''
      },
      saveBtnDisabled:false  // 保存按钮是否禁用,为了不多次添加
    }
  },
  methods:{
  
    //添加讲师的方法
    saveTeacher() {
      teacherApi.addTeacher(this.teacher)
        .then(response => {//添加成功
          //提示信息
          this.$message({ //封装好的提示插件
              type: 'success',
              message: '添加成功!'
          });
          //回到列表页面 路由跳转
          this.$router.push({path:'/teacher/table'})
        })
    }

  }
}
</script>

在这里插入图片描述

讲师修改功能

一、添加一个隐藏的路由
/router/intex.js,加在teacher的子路由路由,添加讲师路由的下面

,
  {
    path:'edit/:id',
    name:'EduTeacherEdit',
    component:() =>import("@/views/edu/teacher/save"),//组件,和添加讲师路由使用一个页面save
    meta:{title:'编辑讲师',noCache:true},
    hidden:true  //这个路由不显示

  },

二、页面的通过路由跳转
/edu/teacher/list.vue

 <!-- 通过路由方式跳转到回显页面-->
          <router-link :to="'/teacher/edit/'+scope.row.id">
            <el-button type="primary" size="mini" icon="el-icon-edit">修改</el-button>
          </router-link>  

现在点击修改就能跳转到修改表单页面
在这里插入图片描述
在这里插入图片描述

跳转到修改表单页面
在这里插入图片描述
三、在表单页面中实现页面回显
1、在teacher.js定义根据id查询和修改讲师的接口

 //根据id查询
    getTeacherInfo(id){
        return request({
            url:`/eduservice/teacher/getTeacher/${id}`,//idea里面接口的地址
            method:"get",
            //data:teacher 注意没有这个,参数只是一个id值,不需要转成json传递到接口中
        })
    },
    //修改讲师
    updateTeacher(teacher){
        return request({
            url:`/eduservice/teacher/updateTeacher`,//idea里面接口的地址
            method:"post",
            data:teacher
        })
    }

2、在表单页面中调用,因为添加和修改都要使用save页面,区别添加还是修改,只有修改的时候查询数据并且回显
save.vue

<template>...
<script>
import teacherApi from '@/api/edu/teacher'
export default {
  data() {...
  },
  created() { //页面渲染之前执行
    this.init()
  },
  watch: {  //监听
    $route(to, from) { //路由变化方式,路由发生变化,方法就会执行
      this.init()
    }
  },
  methods:{
    init() {
      //判断路径有id值,做修改  this.$route.params路由里面的参数值
      if(this.$route.params && this.$route.params.id) {
          //从路径获取id值
          const id = this.$route.params.id
          //调用根据id查询的方法
          this.getInfo(id) //查询出来的值和data里面的值对应,因为和文本框是双向绑定所以可以回显到文本框里
      } else { //路径没有id值,做添加
        //清空表单
        this.teacher = {}
      }
    },
    //根据讲师id查询的方法
    getInfo(id) {
      teacherApi.getTeacherInfo(id)
        .then(response => {
          this.teacher = response.data.teacher
        })
    },
    saveOrUpdate() {
      //判断修改还是添加
      //根据teacher是否有id,修改有之前查出来的id,添加时候id没有是自动生成的
      if(!this.teacher.id) {
        //添加
        this.saveTeacher()
      } else {
        //修改
        this.updateTeacher()
      }
    },
    //修改讲师的方法
    updateTeacher() {
      teacherApi.updateTeacher(this.teacher)
        .then(response => {
          //提示信息
          this.$message({
              type: 'success',
              message: '修改成功!'
          });
          //回到列表页面 路由跳转
          this.$router.push({path:'/teacher/table'})
        })
    },
    //添加讲师的方法
    saveTeacher() {
      teacherApi.addTeacher(this.teacher)
        .then(response => {//添加成功
          //提示信息
          this.$message({ //封装好的提示插件
              type: 'success',
              message: '添加成功!'
          });
          //回到列表页面 路由跳转
          this.$router.push({path:'/teacher/table'})
        })
    }

  }
}
</script>

问题:当点击修改后跳转到修改页面,但是左边路由是在讲师列表,点击添加讲师之后还是在有数据回显的页面。
在这里插入图片描述
在这里插入图片描述
解决方式:
第一步:添加讲师的时候表单数据清空

  if(this.$route.params && this.$route.params.id) {
     else { //路径没有id值,做添加
        //清空表单
        this.teacher = {}
      }

发现还是没有执行是因为先修改和添加都是跳转到同一个页面create只执行了一次,所以if执行了下次跳转过来就不执行else清空表单
第二步:加一个监听方法

 watch: {  //监听
    $route(to, from) { //路由变化方式(点击,或to:),路由发生变化,方法就会执行
      this.init() 
    }
  },

每次路由变化这个init方法都会执行要么if要么else

day06

添加讲师实现头像上传功能

一、阿里云oss存储服务介绍和注册

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

二、阿里云oss开发准备

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

三、后端环境搭建

1、在service模块下创建service_oss模块
在这里插入图片描述
2、在service_oss模块pom文件中引入依赖

<dependencies>
        <!-- 阿里云oss依赖 -->
        <dependency>
            <groupId>com.aliyun.oss</groupId>
            <artifactId>aliyun-sdk-oss</artifactId>
        </dependency>

        <!-- 日期工具栏依赖 -->
        <dependency>
            <groupId>joda-time</groupId>
            <artifactId>joda-time</artifactId>
        </dependency>
    </dependencies>

3、创建配置文件

#服务端口 和service_edu中的端口号要用区别
server.port=8002
#服务名
spring.application.name=service-oss

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

#阿里云 OSS
#不同的服务器,地址不同
aliyun.oss.file.endpoint=oss-cn-beijing.aliyuncs.com
aliyun.oss.file.keyid=LTAI4GJ8qrUvsDHAWoeL5QLR
aliyun.oss.file.keysecret=50UPe6R0lUhFTZolVNn4G5DctF2v0U
#bucket可以在控制台创建,也可以使用java代码创建
aliyun.oss.file.bucketname=bugu-1

4、创建一个springboot启动类测试
在这里插入图片描述
com.yhn.oss.OssApplication.java

@SpringBootApplication
@ComponentScan(basePackages = {"com.yhn"})
public class OssApplication {
    public static void main(String[] args) {
        SpringApplication.run(OssApplication.class, args);
    }
}

启动出现问题程序找数据库配置
在这里插入图片描述
解决方式

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

5、创建常量类加载读取文件内容
com.yhn.oss.utils.ConstantPropertiesUtils.java

//当项目启动,spring接口,spring加载之后,执行接口一个方法
@Component
public class ConstantPropertiesUtils implements InitializingBean {
    //读取配置文件内容
    @Value("${aliyun.oss.file.endpoint}")
    private String endpoint;

    @Value("${aliyun.oss.file.keyid}")
    private String keyId;

    @Value("${aliyun.oss.file.keysecret}")
    private String keySecret;

    @Value("${aliyun.oss.file.bucketname}")
    private String bucketName;

    //定义公开静态常量
    public static String END_POINT;
    public static String ACCESS_KEY_ID;
    public static String ACCESS_KEY_SECRET;
    public static String BUCKET_NAME;

    @Override
    public void afterPropertiesSet() throws Exception {
        END_POINT = endpoint;
        ACCESS_KEY_ID = keyId;
        ACCESS_KEY_SECRET = keySecret;
        BUCKET_NAME = bucketName;

    }
}

6、创建controller

@RestController
@RequestMapping("/eduoss/fileoss")
@CrossOrigin
public class OssController {
    @Autowired
    private OssService ossService;

    //上传头像的方法
    @PostMapping
    public R uploadOssFile(MultipartFile file) {
        //获取上传文件 MultipartFile
        //返回上传的oss路径
        String url=ossService.uploadFileAvatar(file);
        return R.ok().data("url", url);
    }
}

7、service 里实现上传文件过程
接口OssService

package com.yhn.oss.service;
public interface OssService {
    //上传头像到oss
    String uploadFileAvatar(MultipartFile file);
}

在这里插入图片描述
实现类OssServiceImpl

package com.yhn.oss.service.impl;


@Service
public class OssServiceImpl implements OssService {
    //上传头像到oss
    @Override
    public String uploadFileAvatar(MultipartFile file) {
        // 工具类获取值
        String endpoint = ConstantPropertiesUtils.END_POINT;
        String accessKeyId = ConstantPropertiesUtils.ACCESS_KEY_ID;
        String accessKeySecret = ConstantPropertiesUtils.ACCESS_KEY_SECRET;
        String bucketName = ConstantPropertiesUtils.BUCKET_NAME;

        try {
            // 创建OSS实例。
            OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

            //获取上传文件输入流
            InputStream inputStream = file.getInputStream();
            //获取文件真实名称
            String fileName = file.getOriginalFilename();

            //1 在文件名称里面添加随机唯一的值
            String uuid = UUID.randomUUID().toString().replaceAll("-","");
            // yuy76t5rew01.jpg
            fileName = uuid+fileName;

            //2 把文件按照日期进行分类
            //获取当前日期
            //   2019/11/12
            String datePath = new DateTime().toString("yyyy/MM/dd");
            //拼接
            //  2019/11/12/ewtqr313401.jpg
            fileName = datePath+"/"+fileName;

            //调用oss方法实现上传
            //第一个参数  Bucket名称
            //第二个参数  上传到oss文件路径和文件名称   aa/bb/1.jpg
            //第三个参数  上传文件输入流
            ossClient.putObject(bucketName,fileName , inputStream);

            // 关闭OSSClient。
            ossClient.shutdown();

            //把上传之后文件路径返回
            //需要把上传到阿里云oss路径手动拼接出来
            //  https://edu-guli-1010.oss-cn-beijing.aliyuncs.com/01.jpg
            String url = "https://"+bucketName+"."+endpoint+"/"+fileName;
            return url;
        }catch(Exception e) {
            e.printStackTrace();
            return null;
        }
     }

}

测试
http://localhost:8002/swagger-ui.html
在这里插入图片描述

在这里插入图片描述

nginx使用

1、反向代理服务器
2、功能:

  • 请求转发

    • 什么是请求转发?
      在这里插入图片描述
  • 负载均衡
    在这里插入图片描述

  • 动静分离

启动

解压之后,直接到文件里面启动nginx.exe
在这里插入图片描述

关闭cmd窗口这个不会停止,要输入命令
在这里插入图片描述
所以用的时候先停止在启动

nginx.exe -s stop

nginx.exe -s stop

配置nginx实现请求转发的功能

在这里插入图片描述

然后把前端dev.env.js请求地址改为nginx地址9001
在这里插入图片描述
在这里插入图片描述
测试的时候idea里面的两个服务都要启动

上传讲师头像-前端实现

在这里插入图片描述
2、在添加讲师页面save.vue中使用这个组件

(1)先引入两个组件

import ImageCropper from '@/components/ImageCropper'
import PanThumb from '@/components/PanThumb'

(2)声明组件

//export default {
  components: { ImageCropper, PanThumb },
// data() {

(3)从课件中复制要添加的组件代码

 //<div class="app-container"> 里面
 
 <!-- 讲师头像:TODO -->
      <!-- 讲师头像 -->
      <el-form-item label="讲师头像">

          <!-- 头衔缩略图 -->
          <pan-thumb :image="teacher.avatar"/>
          <!-- 文件上传按钮 -->
          <el-button type="primary" icon="el-icon-upload" @click="imagecropperShow=true">更换头像
          </el-button>

          <!--
      v-show:是否显示上传组件
      :key:类似于id,如果一个页面多个图片上传控件,可以做区分
      :url:后台上传的url地址
      @close:关闭上传组件
      @crop-upload-success:上传成功后的回调 
        <input type="file" name="file"/>
      -->
          <image-cropper
                        v-show="imagecropperShow"
                        :width="300"
                        :height="300"
                        :key="imagecropperKey"
                        :url="BASE_API+'/eduoss/fileoss'"
                        field="file"
                        @close="close"
                        @crop-upload-success="cropSuccess"/>
      </el-form-item>

(4)然后在data里面定义变量和初始值

//data() {
//    return {
     //上传弹框组件是否显示
      imagecropperShow:false,
      imagecropperKey:0,//上传组件key值
      BASE_API:process.env.BASE_API, //获取dev.env.js里面地址

(5)编写close方法和上传成功的方法

methods:{
    close(){//关闭弹窗的方法
        this.imagecropperShow=false
    },
    //上传成功的方法
    cropSuccess(data){
      //上传成功之后关闭弹窗
      this.imagecropperShow=false 
      //上传之后接口返回图片地址
     this.teacher.avatar = data.url

    },

(6)小bug在同一个页面需要再次修改已经提交成功的头像,点击发现显示的是上传成功,
所以我们每次提交之后要改变组件的:id,组件的唯一标识,让下次提交的时候是新的组件

 close(){//关闭弹窗的方法
        this.imagecropperShow=false
        //上传组件初始化
        this.imagecropperKey=this.imagecropperKey+1
    },
    //上传成功的方法
    cropSuccess(data){
      //上传成功之后关闭弹窗
      this.imagecropperShow=false 
      //上传之后接口返回图片地址
     this.teacher.avatar = data.url
     this.imagecropperKey=this.imagecropperKey+1

    },

课程分类管理

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

使用EasyExcel

在这里插入图片描述

一、实现EasyExcel对Excel写操作

1、引入EasyExcel依赖
依赖放在service_edu的pom文件中

 <dependencies>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>easyexcel</artifactId>
            <version>2.1.1</version>
        </dependency>
    </dependencies>

还需要poi依赖之前已经在service模块的pom中引入过了

  <!--        xls-->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <!--父pom文件中没有引入这个,所以要加版本号-->
            <version>3.17</version>
        </dependency>

2、创建实体类和excel数据对应

@Data
public class DemoData {

    //设置表头名称
    @ExcelProperty("学生编号")
    private Integer sno;
    @ExcelProperty("学生姓名")
    private Integer sname;
}

3、创建测试类进行写操作
在这里插入图片描述

public class TestEasyExcel {
    public static void main(String[] args) {
        //实现excel写的操作
        //1、设置写入文件夹地址和excel文件名称
                String filename = "C:\\Users\\17788\\Desktop\\code\\write1.xlsx";//必须要有具体的excle文件

        //调用easyexcel里面的方法实现写操作
        EasyExcel.write(filename,DemoData.class).sheet("学生列表").doWrite(getData());
    }
    //创建一个方法返回list集合
    private static List<DemoData> getData() {
        ArrayList<DemoData> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            DemoData data = new DemoData();
            data.setSno(i);
            data.setSname("lucy" + i);
            list.add(data);
        }
        return list;
    }
}


然后执行main方法进行测试

二、实现实现EasyExcel对Excel读操作

一、创建和excel对应实体类,标记对应列关系

@Data
public class DemoData {
    //设置表头名称
    @ExcelProperty(value = "学生编号",index = 0)//index=0表示第一列对应的值
    private Integer sno;
    @ExcelProperty(value = "学生姓名",index = 1)
    private String sname;
}

二、创建监听进行excel文件读取,因为要一行一行读取
com.yhn.demo.excel.ExcelListener .java

public class ExcelListener extends AnalysisEventListener<DemoData> {
    //一行一行读取excel内容
    @Override
    public void invoke(DemoData data, AnalysisContext analysisContext) {
        System.out.println("****"+data);
    }

    //读取表头的内容
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
        System.out.println("表头:"+ headMap);
    }


    //读取完成之后
    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {

    }
}

三、测试类进行读操作

public class TestEasyExcel {
    public static void main(String[] args) {
   
        //实现excel读的操作
        String filename = "C:\\Users\\17788\\Desktop\\code\\write1.xlsx";
        EasyExcel.read(filename,DemoData.class,new ExcelListener()).sheet().doRead();

    }
 }

在这里插入图片描述

添加课程分类后端实现—使用EasyExcel

在这里插入图片描述

一、引入依赖
二、使用代码生成器生成操作edu_subject的代码,只需要改表名就行
在这里插入图片描述
创建好之后给时间加注解自动填充
在这里插入图片描述

三、编写controller里面的内容
EduSubjectController.java

@RestController
@RequestMapping("/eduservice/subject")
@CrossOrigin //解决跨域问题 手动加
public class EduSubjectController {

    @Autowired
    private EduSubjectService subjectService;

    //提交课程分类
    //获取上传过来的文件,把文件内容读取出来
    @PostMapping("addSubject")
    public R addSubject(MultipartFile file) {
        //上传过来excel文件 传个subjectService是为了在监听器中通过它调用方法,
        subjectService.saveSubject(file,subjectService);
        return R.ok();
    }

}


四、创建和excel对应的实体类
com.yhn.eduservice.entity.excel.SubjectData.java

@Data
public class SubjectData {
    @ExcelProperty(index = 0)  //一级分类 也第一行
    private String oneSubjectName;
    @ExcelProperty(index = 1)
    private String twoSubjectName;
}

五、编写service
EduSubjectService.java接口

public interface EduSubjectService extends IService<EduSubject> {

    void saveSubject(MultipartFile file,EduSubjectService subjectService);
}

EduSubjectServiceImpl.java实现类

@Service
public class EduSubjectServiceImpl extends ServiceImpl<EduSubjectMapper, EduSubject> implements EduSubjectService {

    /**
     * 添加课程分类
     * @param file
     */
    @Override
    public void saveSubject(MultipartFile file,EduSubjectService subjectService) {
        try {
            //文件输入流
            InputStream in = file.getInputStream();
            //调用方法进行读取
            EasyExcel.read(in, SubjectData.class, new SubjectExcelListener()).sheet().doRead();

        } catch (IOException e) {
        }
    }
}

六、监听器里面写把课程添加到数据库的过程
创建一个listener包
SubjectExcelListener.java
在这里插入图片描述
parent_id为0表示是一级目录
SubjectExcelListener .java

package com.yhn.eduservice.listener;

public class SubjectExcelListener extends AnalysisEventListener<SubjectData> {
    //因为这个类不能交给spring管理,所以需要自己new,不能注入其他对象
    //不能实现数据库操作,所以只能手动把EduSubjectService传进来,在service和controller中也要手动传入EduSubjectService这个参数
    private EduSubjectService subjectService;//用这个为了做添加

    public SubjectExcelListener() {
    }

    public SubjectExcelListener(EduSubjectService subjectService) {
        this.subjectService = subjectService;
    }

    //读取excel内容,一行一行读取 SubjectData 是excel中的内容 一行是一个subjectData对象
    @Override
    public void invoke(SubjectData subjectData, AnalysisContext analysisContext) {
        if (subjectData == null) {
            //没有数据
            throw new BuguException(20001, "文件为空");
        }
        //一行一行读取,每次读取两个值,第一个值一级分类,第二个值二级分类
        //判断一级分类是否重复
        EduSubject existOneSubject = this.existOneSubject(subjectService, subjectData.getOneSubjectName());
        if (existOneSubject == null) {//没有相同的一级分类
            existOneSubject = new EduSubject();
            existOneSubject.setParentId("0");
            existOneSubject.setTitle(subjectData.getOneSubjectName());//设置一级分类名称
            subjectService.save(existOneSubject);
        }

        //获取一级分类的id值
        String pid = existOneSubject.getId();
        //添加二级分类
        //判断二级添加是否重复
        EduSubject existTwoSubject = this.existTwoSubject(subjectService, subjectData.getTwoSubjectName(), pid);
        if (existTwoSubject == null) {
            existTwoSubject = new EduSubject();
            existTwoSubject.setParentId(pid);
            existTwoSubject.setTitle(subjectData.getTwoSubjectName());//二级分类名称
            subjectService.save(existTwoSubject);
        }


    }

    //判断一级分类不能重复添加(通过查询看里面是否有相同的一级和二级分类)
    private EduSubject existOneSubject(EduSubjectService subjectService, String name) {
        QueryWrapper<EduSubject> wrapper = new QueryWrapper<>();
        wrapper.eq("title", name);
        wrapper.eq("parent_id", "0");//eq表示等于0
        EduSubject oneSubject = subjectService.getOne(wrapper);
        return oneSubject;
    }
    //判断二级分类不能重复添加
    private EduSubject existTwoSubject(EduSubjectService subjectService, String name,String pid) {
        QueryWrapper<EduSubject> wrapper = new QueryWrapper<>();
        wrapper.eq("title", name);
        wrapper.eq("parent_id", pid);//二级分类中的pid不能一样,所以可以通过名字和parent_id查到
        EduSubject twoSubject = subjectService.getOne(wrapper);
        return twoSubject;
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {

    }
}

启动EduApplication.java启动类。在swagger测试

day07 添加课程和课程管理

添加课程分类前端实现

第一步、添加课程分类路由
router/index.js

  {
    path: '/subject',
    component: Layout, //布局
    redirect: '/subject/list',
    name: '课程分类管理',
    meta: { title: '课程分类管理', icon: 'example' },//icon是前面的图标
    children: [
      {
        path: 'list',//在浏览器中的地址为/subject/list
        name: '课程分类列表',
        component: () => import('@/views/edu/subject/list'),//组件
        meta: { title: '课程分类列表', icon: 'table' }
      },
      {
        path: 'save',
        name: '添加课程分类',
        component: () => import('@/views/edu/subject/save'),
        meta: { title: '添加课程分类', icon: 'tree' }
      }
    ]
  }

第二步、创建课程相应页面修改路由对应的页面路径
在这里插入图片描述

第三步、在课程分类页面实现
views/edu/subject/save.vue

<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="'https://bugu-1.oss-cn-beijing.aliyuncs.com/2021/03/04/1.xlsx'">点击下载模版</a>
        </el-tag>

      </el-form-item>
      <!-- 
          ref: 组件唯一标识
          :auto-upload  是否自动上传
          :on-success 上传成功后调用的方法
          :disabled  让按钮不能点第二次
          :limit  限制文件数量
          :action点击上传之后服务之后访问地址
          accept 上传的格式 application/vnd.ms-excel

       -->

      <el-form-item label="选择Excel">
        <el-upload
          ref="upload"  
          drag
          :auto-upload="false"
          :on-success="fileUploadSuccess"
          :on-error="fileUploadError"
          :disabled="importBtnDisabled"
          :limit="1"
          :action="BASE_API+'/eduservice/subject/addSubject'"
          name="file"
          accept="application">
          <el-button slot="trigger" size="small" type="primary">选取文件</el-button>
          <el-button
            :loading="loading"
            style="margin-left: 10px;"
            size="small"
            type="success"
            @click="submitUpload">上传到服务器</el-button>
        </el-upload>
      </el-form-item>
    </el-form>
  </div>
</template>
<script>
export default {
    data() {
        return {
            BASE_API: process.env.BASE_API, // 接口API地址
            importBtnDisabled: false, // 按钮是否禁用,
            loading: false
        }
    },
    created() {

    },
    methods:{
        //点击按钮上传文件到接口里面
        submitUpload(){
          this.importBtnDisabled =true
          this.loading =true
          //js: document.getElementById("upload").submit()
          this.$refs.upload.submit() //官方给的提交表单的方法
        },
        //上传成功
        fileUploadSuccess(){
          //提示信息
            this.loading = false
            this.$message({
                type: 'success',
                message: '添加课程分类成功'
            })
            //跳转课程分类列表 
            //路由跳转
            this.$router.push({path:'/subject/list'})
        },
        fileUploadError(){
            this.loading = false
            this.$message({
                type: 'error',
                message: '添加课程分类失败'
            })
        }

    }
}
</script>

在这里插入图片描述

课程分类显示

在这里插入图片描述

课程分类显示接口

一、针对返回数据创建对应的实体类 。两个实体类 一级分类和二级分类
并且在实体类之间表示关系(一个一级分类有多个二级分类)

/**一级分类
 * @author YHN
 * @create 2021-03-05 14:47
 */
@Data
public class OneSubject {

    private String id;

    private String title;
    //一个一级分类中有多个二级分类
    private List<TwoSubject> children = new ArrayList<>();
}

/**二级分类
 * @author YHN
 * @create 2021-03-05 14:47
 */
@Data
public class TwoSubject {
    
    private String id;

    private String title;
}

二、编写具体封装代码
controller层
EduSubjectController

  //课程分类列表(树型)
    @GetMapping("getAllSubject")
    public R getAllSubject() {
        //list集合中泛型是一级分类,业务一级分类中有它本身和二级分类
        List<OneSubject> list = subjectService.getAllOneTwoSubject();
        return R.ok().data("list",list);
    }

EduSubjectService接口

List<OneSubject> getAllOneTwoSubject();

在这里插入图片描述
EduSubjectServiceImpl实现类

//课程分类列表(数形)
    @Override
    public List<OneSubject> getAllOneTwoSubject() {
        //1、查询所有的一级分类 parentid=0
        QueryWrapper<EduSubject> wrapperOne = new QueryWrapper<>();
        wrapperOne.eq("parent_id", "0");
        //baseMapper在EduSubjectServiceImpl继承的ServiceImpl中做了封装可以直接调用
        List<EduSubject> oneSubjectList = baseMapper.selectList(wrapperOne);


        //2、查询所有的二级分类parentid!=0
        QueryWrapper<EduSubject> wrapperTwo = new QueryWrapper<>();
        wrapperTwo.ne("parent_id", "0");
        List<EduSubject> twoSubjectList = baseMapper.selectList(wrapperTwo);

        //创建一个list集合存储最终封装的数据
        ArrayList<OneSubject> finalSubjectList = new ArrayList<>();

        //3、封装一级分类
        //把查询出来的所有一级分类list遍历集合,得到每一个一级分类对象,获取每一个一级对象|值
        //封装到要求的finalSubjectList集合中
        for (int i = 0; i < oneSubjectList.size(); i++) {
            //得到oneSubjectList每个eduSubject对象
            EduSubject eduSubject = oneSubjectList.get(i);
            //把eduSubject里面值获取出来放到OneSubject对象里面
            //多个OneSubject放到finalSubjectList里面
            OneSubject oneSubject = new OneSubject();

//            oneSubject.setId(eduSubject.getId());
//            oneSubject.setTitle(eduSubject.getTitle());

            //使用工具类添加值,把一个对象中的值封装到另外一个对象中.把上面的代码简化
            BeanUtils.copyProperties(eduSubject, oneSubject);
            //多个oneSubject放到finalSubjectList里面
            finalSubjectList.add(oneSubject);
            
            //4、封装二级分类:在一级分类循环遍历查询所有的二级分类
            //创建list集合封装每一个一级分类的二级分类
            ArrayList<TwoSubject> twoFinalSubjectList = new ArrayList<>();
            //遍历二级分类list集合
            for (int j = 0; j < twoSubjectList.size(); j++) {
                  //获取每个二级分类
                EduSubject tSubject = twoSubjectList.get(j);
                //判断二级分类parentId和一级分类id是否一样
                if (tSubject.getParentId().equals(eduSubject.getId())) {
                    //把tSubject值复制到TwoSubject里面,放到twoFinalSubjectList里面
                    TwoSubject twoSubject = new TwoSubject();
                    BeanUtils.copyProperties(tSubject, twoSubject);
                    twoFinalSubjectList.add(twoSubject);
                }
            }
            //把一级下面所有二级分类放到一级分类里面
            oneSubject.setChildren(twoFinalSubjectList);


        }


        return finalSubjectList;
    }

在这里插入图片描述

课程分类显示前端

一、在api/edu中新建一个subject.js

import request from '@/utils/request'  

export default{
    //1 课程分类列表
    getSubjectList(){
        return request({  //表示用到引入的request
            
            url: `/eduservice/subject/getAllSubject`,//没有参数写飘号``或者引号''都行
            method: 'get'
          })

    }
}

二、页面实现
复制一个树型控件
把原来的data2改为空,通过接口赋值,要想使用api/edu/subject中的方法先要导入’@/api/edu/subject’。然后让getAllSubjectList在页面加载前调用

<template>
  <div class="app-container">
      <!-- 检索功能 -->
    <el-input v-model="filterText" placeholder="Filter keyword" style="margin-bottom:30px;" />

    <el-tree
      
      ref="tree2"
      :data="data2"
      :props="defaultProps"
      :filter-node-method="filterNode"
      class="filter-tree"
      default-expand-all
    />

  </div>
</template>
<script>
import subject from '@/api/edu/subject' 
export default {

  data() {
    return {
      filterText: '',
      data2: [],//返回所有分类数据
      defaultProps: {
        children: 'children',
        label: 'title'  //表示取里面分类的字段名
      }
    }
  },
  created(){ //单词不用写错了
    this.getAllSubjectList()
  },
  watch: {
    filterText(val) {
      this.$refs.tree2.filter(val)
    }
  },

  methods: {
    getAllSubjectList(){
      subject.getSubjectList()
          .then(response=>{
          this.data2 = response.data.list
          })
    },
    filterNode(value, data) {
      if (!value) return true
      return data.title.toLowerCase().indexOf(value.toLowerCase()) !== -1
    }
  }
}
</script>

在这里插入图片描述

课程管理

课程添加分析

在这里插入图片描述

在这里插入图片描述
需要使用的数据表

表名表信息
edu_course课程表:课程的基本信息
edu_course_description课程简介表:存储课程简介信息
edu_chapter课程章节表:存储课程章节信息
edu_video课程小结表:存储章节里面小节的信息
edu_teacher讲师表
edu_subject课程分类表

表之间的对应关系
在这里插入图片描述

添加课程基本信息接口

一、使用代码生成器生成课程相关代码
只改表名

 strategy.setInclude("edu_course","edu_course_description","edu_chapter","edu_video");//多张表时可以加逗号分开

EduCourseDescriptionController课程简介这个controller不需要,删除。课程简介放到课程中操作。
controller中加@CrossOrigin

二、细节问题
在这里插入图片描述

三、创建vo类封装表单提交的数据
在这里插入图片描述
把EduCourseDescription课程描述类和EduCourse课程类中一些字段合并成为CourseInfoVo类

@Data
public class CourseInfoVo {
    @ApiModelProperty(value = "课程ID")
    @TableId(value = "id", type = IdType.ID_WORKER_STR)
    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;

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

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

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


}

四、编写controller
EduCourseController.java

@RestController
@RequestMapping("/eduservice/course")
@CrossOrigin
public class EduCourseController {
    @Autowired
    private EduCourseService courseService;

    //添加课程基本信息的方法
    @PostMapping("addCourseInfo")
    public R addCourseInfo(@RequestBody CourseInfoVo courseInfoVo) {
        courseService.saveCourseInfo(courseInfoVo);
        return R.ok();

    }

}


五、编写service层
EduCourseService.java接口

//* 课程 服务类
public interface EduCourseService extends IService<EduCourse> {
    //添加课程基本信息
    void saveCourseInfo(CourseInfoVo courseInfoVo);
}

// * 课程 服务实现类
 
@Service
public class EduCourseServiceImpl extends ServiceImpl<EduCourseMapper, EduCourse> implements EduCourseService {
    //课程描述的注入
    @Autowired
    private EduCourseDescriptionService courseDescriptionService;

    @Override
    public void saveCourseInfo(CourseInfoVo courseInfoVo) {
        //1 向课程表添加课程基本信息
        //CourseInfoVo对象转换为eduCourse对象
        EduCourse eduCourse = new EduCourse();
        BeanUtils.copyProperties(courseInfoVo, eduCourse);
        int insert = baseMapper.insert(eduCourse);

        if (insert <= 0) {
            //添加失败
            throw new BuguException(20001, "添加课程信息失败");
        }
        
       //获取添加之后课程id
        String cid = eduCourse.getId();


        //2 向课程简介表添加课程信息
        EduCourseDescription courseDescription = new EduCourseDescription();
        courseDescription.setDescription(courseInfoVo.getDescription());
        //设置描述id就是课程id
        courseDescription.setId(cid);
        courseDescriptionService.save(courseDescription);

    }
}

六、问题EduCourseDescription 和EduCourse 两个的id不一样所以在数据库中这两个表表示一一对应的关系
解决方法:设置描述id就是课程id
在这里插入图片描述
把EduCourseDescription .java中id的生成策略改为input 手动输入,而不是自动生成
在这里插入图片描述
测试:使用@RequestBody就是这样测试的窗口,要会把自动生成的id去掉
在这里插入图片描述

添加课程信息前端

第一步:添加路由
index.js

,
  {
    path: '/course',
    component: Layout, //布局
    redirect: '/course/list',
    name: '课程管理',
    meta: { title: '课程管理', icon: 'example' },//icon是前面的图标
    children: [
      {
        path: 'list',//在浏览器中的地址为/subject/list
        name: '课程列表',
        component: () => import('@/views/edu/course/list'),//组件
        meta: { title: '课程列表', icon: 'table' }
      },
      {
        path: 'info',
        name: '添加课程',
        component: () => import('@/views/edu/course/info'),
        meta: { title: '添加课程', icon: 'tree' }
      },
      {
        path: 'info/:id',
        name: 'EduCourseInfoEdit',
        component: () => import('@/views/edu/course/info'),
        meta: { title: '编辑课程基本信息', noCache: true },
        hidden: true  //隐藏路由做页面跳转
      },
      {
        path: 'chapter/:id',
        name: 'EduCourseChapterEdit',
        component: () => import('@/views/edu/course/chapter'),
        meta: { title: '编辑课程大纲', noCache: true },
        hidden: true
      },
      {
        path: 'publish/:id',
        name: 'EduCoursePublishEdit',
        component: () => import('@/views/edu/course/publish'),
        meta: { title: '发布课程', noCache: true },
        hidden: true
      }
    ]
  }

api中的这个还没有的时候要是就在info.vue中导入会出错
import course from '@/api/edu/course’

二、编写api把Java中的添加课程信息的方法引入

import request from '@/utils/request'  

export default{
    //1 添加课程信息
    addCourseInfo(courseInfo){
        return request({  //表示用到引入的request
            
            url: `/eduservice/course/addCourseInfo`,//没有参数写飘号``或者引号''都行
            method: 'post',
            data:courseInfo//因为Java中用了requestBody 使用json传递,所以要加这行表示使用json传递参数
          })

    }
}

二、编写表单页面,实现接口的调用
/edu/course/info.vue

<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-item label="课程标题">
            <el-input v-model="courseInfo.title" placeholder=" 示例:机器学习项目课:从基础到搭建项目视频课程。专业名称注意大小写"/>
        </el-form-item>

        <!-- 所属分类 TODO -->
        <el-form-item label="课程分类">
            <el-select
                v-model="courseInfo.subjectParentId"
                placeholder="一级分类" @change="subjectLevelOneChanged">

                <el-option
                    v-for="subject in subjectOneList"
                    :key="subject.id"
                    :label="subject.title"
                    :value="subject.id"/>

            </el-select>

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


        <!-- 课程讲师 TODO -->
        <!-- 课程讲师 -->
        <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>

        <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-upload
                :show-file-list="false"
                :on-success="handleAvatarSuccess"
                :before-upload="beforeAvatarUpload"
                :action="BASE_API+'/eduoss/fileoss'"
                class="avatar-uploader">
                <img :src="courseInfo.cover">
            </el-upload>

        </el-form-item>

        <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="saveOrUpdate">保存并下一步</el-button>
        </el-form-item>
        </el-form>
  </div>
</template>
<script>
import course from '@/api/edu/course'
import subject from '@/api/edu/subject'
export default {
    data() {
        return{
            saveBtnDisabled:false,
            courseInfo:{
                title: '',
                subjectId: '',//二级分类id
                subjectParentId:'',//一级分类id
                teacherId: '',
                lessonNum: 0,
                description: '',
                cover: '/static/01.jpg',
                price: 0
            },
            BASE_API: process.env.BASE_API, // 接口API地址
            teacherList:[],//封装所有的讲师
            subjectOneList:[],//一级分类
            subjectTwoList:[]//二级分类
            
        }

    },
    created(){

    },
    methods:{
        saveOrUpdate(){
          course.
          addCourseInfo(this.courseInfo)
          .then(response=>{
            //提示
             this.$message({
                type: 'success',
                message: '添加课程信息成功'
            })
          });
            // 跳转到第二步
            this.$router.push({path:'/course/chapter/1'})
        }
    }
}
</script>

/edu/course/chapter.vue

<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(){

    },
    methods:{
        previous(){
        this.$router.push({path:'/course/info/1'})
        },
        next(){
            // 跳转到第二步
            this.$router.push({path:'/course/publish/1'})
        }
    }
}
</script>

publish.vue

<template>

  <div class="app-container">

    <h2 style="text-align: center;">发布新课程</h2>

    <el-steps :active="3" 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(){

    },
    methods:{
        previous(){
        this.$router.push({path:'/course/chapter/1'})
        },
        next(){
            // 跳转到第二步
            this.$router.push({path:'/course/list/1'})
        }
    }
}
</script>

第三步、添加之后要返回课程id,需要完善Java中的接口
在service和controller中把方法返回值改为string类型
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在EduCourseServiceImpl返回cid
在这里插入图片描述
因为在controller中返回的是(“courseId”,id),所以在前端中通过response取到courseId
在info.vue中跳转的时候把课程id带过去
在这里插入图片描述
在这里插入图片描述

添加课程信息功能完善

选择课程讲师讲师用下拉列表
api中添加方法

   //2 查询所有的讲师
    getListTeacher(){
        return request({
            url:'eduservice/teacher/findAll',
            method:'get'
            
        })
    }

info.vue
在这里插入图片描述
创建一个查询所有讲师的方法把返回值赋值给teacherList,然后把这个方法在初始化中调用

  created(){
      //初始化所有讲师
       this.getListTeacher()
    },
    methods:{
      //查询所有讲师
      getListTeacher(){
          course.getListTeacher()
          .then(response=>{
          this.teacherList=  response.data.items
          })
      },

通过for循环遍历出讲师名字

  
        <!-- 课程讲师 -->
        <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>

在这里插入图片描述

显示分类

在这里插入图片描述
api中引用方法之前写过的
在这里插入图片描述

info.vue引入subject

import subject from '@/api/edu/subject'

然后创建查询一级分类的方法

 created(){
      //初始化所有讲师
       this.getListTeacher()
       //初始化一级分类
       this.getOneSubject()
    },
    methods:{
        //查询所有的一级分类
        getOneSubject(){
           subject.getSubjectList()
           .then(response=>{
               this.subjectOneList=response.data.list
           })
        },

在data中定义两个空列表

  subjectOneList:[],//一级分类
   subjectTwoList:[]//二级分类

一级分类前端显示

  <!-- 所属分类 TODO -->
        <el-form-item label="课程分类">
            <el-select
                v-model="courseInfo.subjectParentId"
                placeholder="一级分类" @change="subjectLevelOneChanged">

                <el-option
                    v-for="subject in subjectOneList"
                    :key="subject.id"
                    :label="subject.title"
                    :value="subject.id"/>

            </el-select>

在一级分类中绑定一个事件

  placeholder="一级分类" @change="subjectLevelOneChanged">

change事件和subjectLevelOneChanged这个方法绑定,点击一级分类的时候就能得到value即一级分类的id值,这些是前端框架帮我们封装的

 methods:{
        //当我们点击某个一级分类,触发change,显示对应的二级分类
        subjectLevelOneChanged(value){
          //value就是一级分类的id值
          alert(value)
        },

在这里插入图片描述
完善methids里面的subjectLevelOneChanged

  methods:{
        //当我们点击某个一级分类,触发change,显示对应的二级分类
        subjectLevelOneChanged(value){
          //value就是一级分类的id值
         //遍历所有的分类,包含一级二级  subjectOneList是在data中定义的,用的时候需要加this
         for(var i=0;i<this.subjectOneList.length;i++){
             //每个一级分类
             var oneSubject=this.subjectOneList[i]
             //判断:所有一级分类id和点击分类id是否一样
             if(value === oneSubject.id){
                 //从一级分类中获取里面所有的二级分类 children是二级分类列表
                 this.subjectTwoList=oneSubject.children
                  //把二级分类值清空
                 this.course.subjectId =''
             }
         }
        },

页面显示 组件

    <!-- 二级分类  v-model当里面选中之后把选中对象的二级id绑定到
            courseInfo对象的subjectId  
            :label显示出来的值
             value是实际里面的内容v-model是和value双向绑定的 
             :key表示标签的唯一标识里面一般写id
             -->
            <el-select v-model="courseInfo.subjectId" placeholder="二级分类">
                <el-option
                    v-for="subject in subjectTwoList"
                    :key="subject.id"
                    :label="subject.title"
                    :value="subject.id"/>
            </el-select>
        </el-form-item>

在这里插入图片描述
问题:再次点另外一个一级分类二级分类中原来的显示没有清空,但是下拉列表是对的
解决方法:每次点击一级分类时候给二级分类里面值清空
在这里插入图片描述

课程封面

1、定义接口地址

在这里插入图片描述
2、编写方法

 methods:{
        //上传封面成功调用的方法 res就是response
        handleAvatarSuccess(res,file){
            //把返回的地址封装到courseInfo的cover里面
          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、组件

 <!-- 课程封面-->
        <el-form-item label="课程封面">

            <el-upload
                :show-file-list="false"
                :on-success="handleAvatarSuccess"
                :before-upload="beforeAvatarUpload"
                :action="BASE_API+'/eduoss/fileoss'"
                class="avatar-uploader">
                <img :src="courseInfo.cover" :width="254" :height="210">
            </el-upload>

        </el-form-item>

day08课程大纲和课程发布

富文本整合

一、复制脚本库到对应的文件中
在这里插入图片描述

第二步配置html变量
在这里插入图片描述
在这里插入图片描述
三、在index.html引入js脚本
在这里插入图片描述

<body>
    <script src=<%= BASE_URL %>/tinymce4.7.5/tinymce.min.js></script>
    <script src=<%= BASE_URL %>/tinymce4.7.5/langs/zh_CN.js></script>
    
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>

四、在页面中使用文本编辑器组件,引入组件然后声明

info.vue

import Tinymce from '@/components/Tinymce'  //引入组件 前面的@/不能丢

export default {
    //因为是第三方组件必须声明再使用
    components: {Tinymce},

五、页面中替换原来的课程简介的组件
info.vue

  <!-- 课程简介 TODO -->
       <el-form-item label="课程简介">
            <tinymce :height="300" v-model="courseInfo.description"/>
        </el-form-item>

六、在页面的最后加一段样式

//</script> scoped表示在当前页面有效
<style scoped>
.tinymce-container {
  line-height: 29px;
}
</style>

在这里插入图片描述

课程大纲列表功能(后端)

第一步创建两个实体类,章节和小结
在这里插入图片描述
章节实体类ChapterVo

@Data
public class ChapterVo {

    private String id;

    private String title;

    //表示小节
    private List<VideoVo> children = new ArrayList<>();
}

小节实体类VideoVo

@Data
public class VideoVo {
    private String id;

    private String title;
}

第二步、编写controller

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

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

    }

}


第三步、编写service
service接口

public interface EduChapterService extends IService<EduChapter> {
//根据课程id进行查询章节小节
    List<ChapterVo> getChapterByCourseId(String courseId);
}

service实现类

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

    @Autowired
    private EduVideoService videoService;//因为这里面查询不了video,所以 注入小节service
    //课程大纲列表,根据课程id进行查询
    @Override
    public List<ChapterVo> getChapterByCourseId(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<>();
        wrapperChapter.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);

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

        //4 遍历查询小节list集合,进行封装
        return finalList;
    }
}

第四步、swagger测试
输入课程id查询章节和小节
在这里插入图片描述

课程大纲列表前端实现

第一步、在api中把方法定义
chapter.js

import request from '@/utils/request'
export default {
    //1 根据课程id获取章节和小节数据列表
    getAllChapterVideo(courseId) {
        return request({
            url: '/eduservice/chapter/getChapterVideo/'+courseId,
            method: 'get'
          })
    }
}

第二步在chapter.vue中导入api
定义:一个空的数组用来接收返回是数据

定义方法并且在初始化里面调用

import chapter from '@/api/edu/chapter'
export default {
    data() {
        return{
            saveBtnDisabled:false,
            courseId:'',  //课程id 
            chapterVideoList:[]
        }

    },
    created(){
      if(this.$route.params && this.$route.params.id){
        this.courseId =this.$route.params.id
        //根据课程id查询章节和小节
          this.getChapterVideo()
      }
   
  },
    methods:{
      //根据课程id查询章节和小节
       getChapterVideo(){
          chapter.getChapterVideo(this.courseId)
          .then(response=>{
          this.chapterVideoList=  response.data.allChapterVideo
          })
       } ,
        previous(){
        this.$router.push({path:'/course/info/1'})
        },
        next(){
            // 跳转到第二步
            console.log(response.data.courseId)
            this.$router.push({path:'/course/publish/1'})
        }
    }
}
</script>

在这里插入图片描述
这个就是课程id通过this.$route.params.id提取出来
第三步、编写一个组件测试

  <ul>
      <li v-for=" chapter in chapterVideoList" :key="chapter.id">
        {{chapter.title}}
        <ul>
          <li v-for="video in chapter.children" :key="video.id">
             {{video.title}}
          </li>
        </ul>
      <li/>
    </ul>


把url里面id改为18测试,要是出现网络错误可能是api里面的问题,注意不要多逗号
在这里插入图片描述
第四步、删除测试代码加入最终代码

<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>


    <!-- 章节 -->
    <ul class="chanpterList">
        <li
            v-for="chapter in chapterVideoList"
            :key="chapter.id">
            <p>
                {{ chapter.title }}

                <span class="acts">
                    <el-button style="" type="text" @click="openVideo(chapter.id)">添加小节</el-button>
                    <el-button style="" type="text" @click="openEditChatper(chapter.id)">编辑</el-button>
                    <el-button type="text" @click="removeChapter(chapter.id)">删除</el-button>
                </span>
            </p>
        
            <!-- 视频 -->
            <ul class="chanpterList videoList">
                <li
                    v-for="video in chapter.children"
                    :key="video.id">
                    <p>{{ video.title }}

                <span class="acts">
                    
                    <el-button style="" type="text">编辑</el-button>
                    <el-button type="text" @click="removeVideo(video.id)">删除</el-button>
                </span>
                    </p>
                </li>
            </ul>
           
        </li>
    </ul>
    <div>
        <el-button @click="previous">上一步</el-button>
        <el-button :disabled="saveBtnDisabled" type="primary" @click="next">下一步</el-button>
    </div>
  </div>
</template>
<script>
import chapter from '@/api/edu/chapter'
export default {
    data() {
        return{
            saveBtnDisabled:false,
            courseId:'',  //课程id 
            chapterVideoList:[]
        }

    },
    created(){
      if(this.$route.params && this.$route.params.id){
        this.courseId =this.$route.params.id
        //根据课程id查询章节和小节
          this.getChapterVideo()
      }
   
  },
    methods:{
      //根据课程id查询章节和小节
       getChapterVideo(){
          chapter.getAllChapterVideo( this.courseId)
          .then(response=>{
          this.chapterVideoList=  response.data.allChapterVideo
          })
       } ,
        previous(){
        this.$router.push({path:'/course/info/1'})
        },
        next(){
            // 跳转到第二步
            console.log(response.data.courseId)
            this.$router.push({path:'/course/publish/1'})
        }
    }
}
</script>
<style scoped>
.chanpterList{
    position: relative;
    list-style: none;
    margin: 0;
    padding: 0;
}
.chanpterList li{
  position: relative;
}
.chanpterList p{
  float: left;
  font-size: 20px;
  margin: 10px 0;
  padding: 10px;
  height: 70px;
  line-height: 50px;
  width: 100%;
  border: 1px solid #DDD;
}
.chanpterList .acts {
    float: right;
    font-size: 14px;
}

.videoList{
  padding-left: 50px;
}
.videoList p{
  float: left;
  font-size: 14px;
  margin: 10px 0;
  padding: 10px;
  height: 50px;
  line-height: 30px;
  width: 100%;
  border: 1px dotted #DDD;
}

</style>

在这里插入图片描述

修改课程基本信息

在这里插入图片描述

返回上一步和修改的开发后端接口

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

一、根据课程id查询课程基本信息和修改课程信息的接口
第一步、在EduCourseController添加方法

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

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

EduCourseService接口

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

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

EduCourseServiceImpl实现类

 @Override
    public CourseInfoVo getCourseInfo(String courseId) {
       //1 查询课程表
        EduCourse eduCourse = baseMapper.selectById(courseId);
        CourseInfoVo courseInfoVo = new CourseInfoVo();
        BeanUtils.copyProperties(eduCourse, courseInfoVo);

        //2 查询描述表
        EduCourseDescription courseDescription = courseDescriptionService.getById(courseId);
        courseInfoVo.setDescription(courseDescription.getDescription());

        return courseInfoVo;
    }

    @Override
    public void updateCourseInfo(CourseInfoVo courseInfoVo) {
        //1 修改课程表
        EduCourse eduCourse = new EduCourse();
        BeanUtils.copyProperties(courseInfoVo, eduCourse);
        int update = baseMapper.updateById(eduCourse);
        if (update == 0) {
            throw new BuguException(20001, "修改课程信息失败");
        }
        //修改描述表
        EduCourseDescription description = new EduCourseDescription();
        description.setId(courseInfoVo.getId());
        description.setDescription(courseInfoVo.getDescription());
        courseDescriptionService.updateById(description);
    }

返回上一步数据回显的前端实现

第一步、aoi里面定义接口的方法
course.js

 //根据课程id查询课程基本信息
    getCourseInfoId(id){
        return request({
            url:'eduservice/course/getCourseInfo/'+id,
            method:'get'          
        })
    },
    //修改课程信息
    updateCourseInfo(courseInfo){
        return request({
            url:'eduservice/course/updateCourseInfo',
            method:'post',
            data:courseInfo          
        })
    }

第二步、修改chapter页面,跳转路径
在这里插入图片描述
第三步、在info.vue页面实现数据的回显
定义一个课程id

 courseId:'',//课程id

在初始化的时候获取路由的id,然后把id赋值给courseId,然后调用根据id查询课程的方法:把查询出来的课程信息赋值给courseInfoVo,courseInfoVo和from表单里面的内容是双向绑定的从而实现数据回显

 created(){
        //获取路由id值‘
         if(this.$route.params && this.$route.params.id){
         this.courseId =this.$route.params.id
         //调用根据id查询课程的方法、
         this.getInfo()
        }
   
      //初始化所有讲师
       this.getListTeacher()
       //初始化一级分类
       this.getOneSubject()
    },
    methods:{
        //根据课程id查询信息
        getInfo(){
        course.getCourseInfoId(this.courseId)
         .then(response=>{
            this.courseInfo=response.data.courseInfoVo
         })
        },

测试代码出现403状态码
1、跨域
2、路径写错了
在这里插入图片描述
应该是course
最终测试
在这里插入图片描述

问题1:测试发现二级分类里面回显的二级分类的id,里面没有数据

因为一级分类中有值,二级分类 subjectTwoList:[]中为空。
下拉列表 的原理
在这里插入图片描述
修改info.vue里面的方法

 created(){
        //获取路由id值‘
         if(this.$route.params && this.$route.params.id){
         this.courseId =this.$route.params.id
         //调用根据id查询课程的方法、
         this.getInfo()
        }else{
        //初始化所有讲师
        this.getListTeacher()
        //初始化一级分类
        this.getOneSubject()
        }
   
     
    },
    methods:{
        //根据课程id查询信息
        getInfo(){
        course.getCourseInfoId(this.courseId)
         .then(response=>{
             //在courseInfo课程基本信息中包含一级分类id和二级分类id
            this.courseInfo=response.data.courseInfoVo
            //1 查询所有的分类包含一级二级分类
            subject.getSubjectList()
               .then(response=>{
                   // 2 获取所有的一级分类
                   this.subjectOneList=response.data.list
                   //3 把所有的一级分类数组进行遍历,比较当前的courseInfo里面的一级分类id和所有的一级分类id
                   for(var i=0;i<this.subjectOneList.length;i++){
                       //获取每一个一级分类
                       var oneSubject=this.subjectOneList[i]
                       //比较当前courseInfo里面一级分类id和所有的一级分类id
                       if(this.courseInfo.subjectParentId == oneSubject.id){
                           //获取一级分类所有的二级分类
                           this.subjectTwoList=oneSubject.children
                       }
                   }
               })
               //初始化所有教师
               this.getListTeacher()
         })
        },
问题2:当数据回显之后,再点击添加课程列表的路由,表单里面的信息没有清空

在这里插入图片描述
解决方法:
如果路由中没有id在初始化一级列表的时候使得courseInfo为空,并且每次路由

  watch: {  //监听
    $route(to, from) { //路由变化方式,路由发生变化,方法就会执行
      this.getOneSubject()
    }
  },

路由中没有id会执行else初始化一级分类
在这里插入图片描述
一级分类方法中加入清空的课程信息
在这里插入图片描述

修改课程信息前端最终实现

数据回显之后再点下一步就是修改课程信息
在这里插入图片描述

一、用来的saveOrUpdate()方法是添加的,选择在里面写给判断什么时候添加什么时候修改

    //添加课程
      addCourse(){
          course.
          addCourseInfo(this.courseInfo)
          .then(response=>{
            //提示
             this.$message({
                type: 'success',
                message: '添加课程信息成功'
            });
             // 跳转到第二步  这个responseresponse.data.courseId一定要在response里面取值
            this.$router.push({path:'/course/chapter/'+response.data.courseId})
          });
      },
      //修改课程
      updateCourse(){
          course.updateCourseInfo(this.courseInfo)
          .then(response=>{
               //提示
             this.$message({
                type: 'success',
                message: '修改课程信息成功'
            });
             // 跳转到第二步  修改之后不会返回课程id,但是可以取到回显数据时候得到的课程id
            this.$router.push({path:'/course/chapter/'+this.courseId})
          })
      },
     saveOrUpdate(){
          //判断添加还是修改
          if(!this.courseInfo.id){
              //如果里面没有课程信息id值说明是空的做添加
             this.addCourse()
          }else{
              this.updateCourse()
          }
           
        }

课程章节的增加

在这里插入图片描述
第一步、先增加一个按钮
chapter.vue dialogChapterFormVisible=true表示可以弹出表单dialogChapterFormVisible

<el-button type="text" @click="dialogChapterFormVisible=true">添加章节</el-button>

在这里插入图片描述
第二步、点击添加按钮后可以弹出一个表单

 <!-- 添加和修改章节表单 -->
    <el-dialog :visible.sync="dialogChapterFormVisible" title="添加章节">
        <el-form :model="chapter" label-width="120px">
            <el-form-item label="章节标题">
                <el-input v-model="chapter.title"/>
            </el-form-item>
            <el-form-item label="章节排序">
                <el-input-number v-model="chapter.sort" :min="0" controls-position="right"/>
            </el-form-item>
        </el-form>
        <div slot="footer" class="dialog-footer">
            <el-button @click="dialogChapterFormVisible = false">取 消</el-button>
            <el-button type="primary" @click="saveOrUpdate">确 定</el-button>
        </div>
    </el-dialog>

在这里插入图片描述

开发章节接口:添加 删除 修改

删除章节
在这里插入图片描述
EduChapterController.java

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

    //根据章节id查询
    @GetMapping("getChapterInfo/{chapterId}")
    public R getChapterInfo(@PathVariable String chapterId) {
        EduChapter eduChapter = chapterService.getById(chapterId);
        return R.ok().data("chapter", eduChapter);
    }

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

    //删除的方法
    @DeleteMapping("{chapterId}")
    public R deleteChapter(@PathVariable String chapterId) {
       boolean flag= chapterService.deleteChapter(chapterId);
        if (flag) {
            return R.ok();
        } else {
            return R.error();
        }
    }
}

EduChapterService接口

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

EduChapterServiceImpl

  @Override
    public boolean deleteChapter(String chapterId) {
      //根据chapterid章节id 查询小节表,如果查询数据,不进行删除
        QueryWrapper<EduVideo> wrapper = new QueryWrapper<>();
        wrapper.eq("chapter_id", chapterId);
        int count = videoService.count(wrapper);
        //判断
        if (count > 0) {//能查询出小节,不进行删除
            throw new BuguException(20001, "不能删除");
        } else { //不能查询数据,进行删除
         //删除章节
            int result = baseMapper.deleteById(chapterId);
            //成功 result为1 1>0为true
            return result > 0;
        }
    }

添加章节前端

第一步、在api中定义方法
chapter.js

  //添加章节
    addChapter(chapter) {
        return request({
            url: '/eduservice/chapter/addChapter',
            method: 'post',
            data:chapter
          })
    },
    //根据id查询章节
    getChapter(chapterId) {
        return request({
            url: '/eduservice/chapter/getChapterInfo/'+chapterId,
            method: 'get'     
          })
    },
    //修改章节
    updateChapter(chapter) {
        return request({
            url: '/eduservice/chapter/updateChapter',
            method: 'post',
            data:chapter
          })
    },
    //删除章节
    deleteChapter(chapterId) {
        return request({
            url: '/eduservice/chapter/'+chapterId,
            method: 'delete'
          })
    }

第二步在页面调用
chapter.vue
data里面给chapter添加两个属性
在这里插入图片描述
编写添加的方法

 methods:{
       //添加章节
        addChapter(){
         //设置课程id到chapter对象里面
         this.chapter.courseId=this.courseId
        chapter.addChapter(this.chapter)
        .then(response=>{
            //添加之后要关闭弹框
            this.dialogChapterFormVisible=false   
            //提示
             this.$message({
                type: 'success',
                message: '添加章节成功'
            });
            //刷新页面
            this.getChapterVideo()
        })
        },
        
        saveOrUpdate(){
           this.addChapter()
        },

点这个按钮就弹出表单

<el-button type="text" @click="dialogChapterFormVisible=true">添加章节</el-button>

在这里插入图片描述
点击确定就添加了
在这里插入图片描述
问题:点击添加之后出现了执行了全局异常表明后端代码有问题

Caused by: java.sql.SQLException: Field 'course_id' doesn't have a default value

添加传的是chapter对象
前端的chapter中没有course_id,course_id不能为空
所以要把courseId设置到chapter对象中
在这里插入图片描述

在chapter对象添加属性courseId
在这里插入图片描述

最终测试添加章节成功
在这里插入图片描述

问题:再次点击添加章节里面的内容没有变空
在添加按钮绑定一个事件里面加一个方法

<el-button type="text" @click="openChapterDialog()">添加章节</el-button>
 

方法

 //弹出添加章节页面
        openChapterDialog(){
           //弹框
           this.dialogChapterFormVisible=true
           //表单数据清空      
           this.chapter.title=''
           this.chapter.sort=0
        },

修改章节

一、添加修改章节的按钮
在这里插入图片描述
在这里插入图片描述

点击编辑要回显数据到添加章节的表单中
在这里插入图片描述
让弹框中数据回显的方法

//修改章节弹框数据回显
        openEditChatper(chapterId){
         chapter.getChapter(chapterId)
         .then(response=>{
             //弹框
              this.dialogChapterFormVisible=true
             //调用接口
             this.chapter=response.data.chapter
         })
        },

回显成功
在这里插入图片描述

修改弹框中的数据

在这里插入图片描述
点确定后提交修改后的数据
修改的方法

  //修改章节的方法
        updateChapter(){
            chapter.updateChapter(this.chapter)
                 .then(response=>{
            //添加之后要关闭弹框
            this.dialogChapterFormVisible=false   
            //提示
             this.$message({
                type: 'success',
                message: '修改章节成功'
            });
            //刷新页面
            this.getChapterVideo()
          })        
       },
        saveOrUpdate(){
            //如果提交的内容章节没有chapter.id是提交,chapter.id只有添加的时候自动创建
            if(!this.chapter.id){
                this.addChapter()
            }else{
                this.updateChapter()
            }
         
        },

删除章节

删除按钮绑定事件
在这里插入图片描述
在这里插入图片描述
删除章节的方法

  //删除章节
        removeChapter(chapterId){
     this.$confirm('此操作将删除章节, 是否继续?', '提示', {
            confirmButtonText: '确定',
            cancelButtonText: '取消',
            type: 'warning'
        }).then(() => {  //点击确定执行这个方法
        //调用删除方法
            chapter.deleteChapter(chapterId)
            .then(response => { //删除成功
                //提示信息
                this.$message({
                type: 'success',
                message: '删除成功!'
                })
                //刷新数据
                this.getChapterVideo()               
          });
        })
        },

在这里插入图片描述

添加小节

在这里插入图片描述

添加删除修改小节后端接口实现

EduVideoController.java

@RestController
@RequestMapping("/eduservice/video")
@CrossOrigin  //解决跨域
public class EduVideoController {
    @Autowired
    private EduVideoService videoService;

    //添加小节
    @PostMapping("addVideo")
    public R addVideo(@RequestBody EduVideo eduVideo) {
        videoService.save(eduVideo);
        return R.ok();
    }

    //删除小节
    //TODO 后面这个方法需要完善:删除小节的时候同时把里面的视频也要删除
    @DeleteMapping("{id}")
    public R deleteVideo(@PathVariable String id) {
        videoService.removeById(id);
        return R.ok();
    }
     //根据小节id查询
    @GetMapping("getVideo/{videoId}")
    public R getVideo(@PathVariable String videoId) {
        EduVideo eduVideo = videoService.getById(videoId);
        return R.ok().data("video", eduVideo);
    }

    //修改小节

    @PostMapping("updateVideo")
    public R updateCourseInfo(@RequestBody EduVideo eduVideo) {
        videoService.updateById(eduVideo);
        return R.ok();
    }

}


添加小节前端实现

一、先增加一个添加小节的按钮绑定一个事件
chapter.vue
在这里插入图片描述
二、写一个添加和修改课时(小节)表单

    <!-- 添加和修改课时(小节)表单 -->
    <el-dialog :visible.sync="dialogVideoFormVisible" title="添加课时">
    <el-form :model="video" label-width="120px">
        <el-form-item label="课时标题">
        <el-input v-model="video.title"/>
        </el-form-item>
        <el-form-item label="课时排序">
        <el-input-number v-model="video.sort" :min="0" controls-position="right"/>
        </el-form-item>
        <el-form-item label="是否免费">
        <el-radio-group v-model="video.free">
            <el-radio :label="true">免费</el-radio>
            <el-radio :label="false">默认</el-radio>
        </el-radio-group>
        </el-form-item>
        <el-form-item label="上传视频">
        <!-- TODO -->
        </el-form-item>
    </el-form>
    <div slot="footer" class="dialog-footer">
        <el-button @click="dialogVideoFormVisible = false">取 消</el-button>
        <el-button  type="primary" @click="saveOrUpdateVideo">确 定</el-button>
    </div>
    </el-dialog>

三、在data里面添加小节对象和小节弹框的属性默认为false

  video:{  //小节对象
               title:'',
               sort:0,
               isFree:false,
               videoSourceId:'' 
            },
            dialogChapterFormVisible:false,//章节弹框默认关闭 
            dialogVideoFormVisible :false //小节弹框
        }

四、先写个方法看弹框能不能弹出

 openVideo(chapterId){
            //弹框
            this.dialogVideoFormVisible=true
         },

点击添加小节就会弹出这个弹框表单
在这里插入图片描述
五、api中定义方法添加修改查询删除
video.js

import request from '@/utils/request'
export default {
    
    //添加小节
    addVideo(video) {
        return request({
            url: '/eduservice/video/addVideo',
            method: 'post',
            data:video
          })
    },
    //修改章节
    updateVideo(video) {
        return request({
            url: '/eduservice/video/updateVideo',
            method: 'post',
            data:video
          })
    },
     //根据id查询小节
     getVideo(videoId) {
        return request({
            url: '/eduservice/video/getVideo/'+videoId,
            method: 'get'     
          })
    },
    //删除章节
    deleteVideo(id) {
        return request({
            url: '/eduservice/video/'+id,
            method: 'delete'
          })
    }

}

六、在页面中引入video.js

import video from '@/api/edu/video'

七、完善添加的方法让它可以添加到数据库中

  openVideo(chapterId){
            //弹框
            this.dialogVideoFormVisible=true
            //设置章节id
            this.video.chapterId=chapterId
            //调用完添加或者修改方法就把弹框的小节对象清空,让下次点击添加小节的时候里面没有内容
            this.video.sort=0
            this.video.title=''
            this.video.id=''
            this.video.isFree=false
         },
         //添加小节需要课程id和章节id
         addVideo(){
             //设置课程id
             this.video.courseId=this.courseId
           video.addVideo(this.video)
           .then(response=>{
               //添加之后就关闭弹框
               this.dialogVideoFormVisible=false
               //提示信息
                this.$message({
                    type: 'success',
                    message: '添加小节成功!'
                })
                //刷新数据
                this.getChapterVideo() 
           })
         },
         saveOrUpdateVideo(){
            // 调用添加的方法
            this.addVideo()
           
         },

出现network网络错误是因为没有解决跨域问题
在这里插入图片描述
最终测试成功
在这里插入图片描述

小节的删除功能

在这里插入图片描述
在这里插入图片描述
删除的方法

 //删除小节
        removeVideo(id) {
            this.$confirm('此操作将删除小节, 是否继续?', '提示', {
                confirmButtonText: '确定',
                cancelButtonText: '取消',
                type: 'warning'
            }).then(() => {  //点击确定,执行then方法
                //调用删除的方法
                video.deleteVideo(id)
                    .then(response =>{//删除成功
                    //提示信息
                    this.$message({
                        type: 'success',
                        message: '删除小节成功!'
                    });
                    //刷新页面
                    this.getChapterVideo()
                })
            }) //点击取消,执行catch方法
        },

删除成功
在这里插入图片描述

小节的修改功能

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

一、添加一个修改(编辑)小节弹框数据回显

//修改小节弹框数据回显
         openEditVideo(videoId){
          video.getVideo(videoId)
          .then(response=>{
             //弹框
              this.dialogVideoFormVisible=true
             //调用接口
             this.video=response.data.video
         })
         },

二、添加修改的方法
点确定修改
在这里插入图片描述

 //修改小节
         updateVideo(){
        video.updateVideo(this.video)
                 .then(response=>{
            //添加之后要关闭弹框
            this.dialogVideoFormVisible=false   
            //提示
             this.$message({
                type: 'success',
                message: '修改章节成功'
            });
            //刷新页面
            this.getChapterVideo()
          })     
         },
         saveOrUpdateVideo(){
              //如果提交的内容章节没有video.id是提交,video.id只有添加的时候自动创建
            if(!this.video.id){
              // 调用添加的方法
              this.addVideo()
             
            }else{
                this.updateVideo()
            }            
         },

课程信息确认(课程最终发布)

在这里插入图片描述
在这里插入图片描述
自己手动写sql实现多表查询,通过课程表的id查询不同表的数据

# 左外连接 多表查询
 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=?

后端实现(自己写sql语句)

一、定义课程最终发布的实体类
entity/vo/CoursePublishVo .java


/**课程最终发布的实体类
 * @author YHN
 * @create 2021-03-08 19:20
 */
@Data
public class CoursePublishVo {
    @ApiModelProperty(value = "课程ID")
    private String id;

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

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

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

    @ApiModelProperty(value = "一级分类ID")
    private String subjectLevelOne;

    @ApiModelProperty(value = "二级分类级ID")
    private String subjectLevelTwo;

    @ApiModelProperty(value = "课程讲师姓名")
    private String teacherName;

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

}

二、在课程的mapper接口中中定义查询最终课程信息的方法
mapper/EduCourseMapper.java

public interface EduCourseMapper extends BaseMapper<EduCourse> {
    public CoursePublishVo getPublishCourseInfo(String courseId);
}

三、在mapper配置文件中编写sql语句对应的方法
EduCourseMapper.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.yhn.eduservice.mapper.EduCourseMapper">
<!--sql语句 根据课程id查询课程确认信息             返回类型-->
    <select id="getPublishCourseInfo" resultType="com.yhn.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=#{courseId}
    </select>
</mapper>

四、在controller中编写接口
EduCourseController.java

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

五、编写service
EduCourseService接口

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

EduCourseServiceImpl实现类

 @Override
    public CoursePublishVo publishCourseInfo(String id) {
         //调用mapper,调用自己写的sql方法只能用baseMapper
        CoursePublishVo publishCourseInfo = baseMapper.getPublishCourseInfo(id);
        return publishCourseInfo;
    }

六、在swagger测试中出现问题
配置文件没有加载到target文件中
在这里插入图片描述
第一步、在pom文件中加入下面的过滤代码

<build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
        </resources>

第二步、在springboot配置文件中添加配置

#配置mapper.xml文件的当前路径
mybatis-plus.mapper-locations=classpath:com/yhn/eduservice/mapper/xml/*.xml

最终测试成功

课程信息确认-前端

一、api中定义方法‘
course.js

//课程确认信息显示

    getPublsihCourseInfo(id){
        return request({
            url:'/eduservice/course/getPublishCourseInfo/'+id,
            method:'get'

        })
    }

二、导入api
publish.vue

import course from '@/api/edu/course'

三、data中定义变量
publish.vue

			courseId:'',
            coursePublish:{}

四、在初始化时候获取路由课程id,并且调用根据课程id查询的方法

 created() {
    //获取路由课程id值
    if(this.$route.params && this.$route.params.id) {
      this.courseId = this.$route.params.id
      //调用接口方法根据课程id查询
      this.getCoursePublishId()
    }
  },

  methods: {

    //根据课程id查询
    getCoursePublishId() {
      course.getPublsihCourseInfo(this.courseId)
        .then(response => {
          this.coursePublish = response.data.publishCourse
        })
    },

五、页面调用组件显示

 <template>

  <div class="app-container">

    <h2 style="text-align: center;">发布新课程</h2>

    <el-steps :active="3" process-status="wait" align-center style="margin-bottom: 40px;">
      <el-step title="填写课程基本信息"/>
      <el-step title="创建课程大纲"/>
      <el-step title="发布课程"/>
    </el-steps>

    <div class="ccInfo">
      <img :src="coursePublish.cover">
      <div class="main">
        <h2>{{ coursePublish.title }}</h2>
        <p class="gray"><span>{{ coursePublish.lessonNum }}课时</span></p>
        <p><span>所属分类:{{ coursePublish.subjectLevelOne }}{{ coursePublish.subjectLevelTwo }}</span></p>
        <p>课程讲师:{{ coursePublish.teacherName }}</p>
        <h3 class="red">{{ coursePublish.price }}</h3>
      </div>
    </div>

    <div>
      <el-button @click="previous">返回修改</el-button>
      <el-button :disabled="saveBtnDisabled" type="primary" @click="publish">发布课程</el-button>
    </div>
  </div>
</template>

在这里插入图片描述
这样样式会冲突,要把<el-from 里面的内容都覆盖掉,全部粘贴过来
在这里插入图片描述

课程最终发布

后端
EduCourseController.java

//课程最终发布
    //修改课程状态
    @PostMapping("publishCourse/{id}")
    public R publishCourse(@PathVariable String id) {
        EduCourse eduCourse = new EduCourse();
        eduCourse.setId(id);
        eduCourse.setStatus("Normal");//设置课程发布状态 Normal表示已发布
        courseService.updateById(eduCourse);
        return R.ok();
    }

前端
api中定义方法
course.js

 //课程最终发布
     publishCourse(id){
        return request({
            url:'/eduservice/course/publishCourse/'+id,
            method:'post'
        })
    }

publish.vue
方法

 publish() {
      course.publishCourse(this.courseId)
        .then(response => {
          //提示
          this.$message({
              type: 'success',
              message: '课程发布成功!'
          });
          //跳转课程列表页面
          this.$router.push({ path: '/course/list' })
        })
    }

在这里插入图片描述
点发布课程调用这个方法
在这里插入图片描述
最终发布课程成功跳转到课程列表页面

day09 课程列表和整合阿里云视频点播

课程列表功能

后端代码

多条件组合查询带分页
第一步把条件值传递到接口里
1、 把条件值封装到对象中
在entity下创建一个vo包,在里面创建一个CourseQuery类

@Data
public class CourseQuery {
    @ApiModelProperty(value = "课程名称,模糊查询")
    private String title;

    @ApiModelProperty(value = "课程状态 Draft未发布 Normal已发布")
    private String status;

    @ApiModelProperty(value = "查询开始时间", example = "2019-01-01 10:10:10")
    private String begin;//注意,这里使用的是String类型,前端传过来的数据无需进行类型转换

    @ApiModelProperty(value = "查询结束时间", example = "2019-12-01 10:10:10")
    private String end;
}

编写EduCourseController.java

  //添加查询课程列表带分页的方法
    @PostMapping("pageCourseCondition/{current}/{limit}")
    public R pageTeacherCondition(@PathVariable long current,
                                  @PathVariable long limit, @RequestBody(required=false) CourseQuery courseQuery) {
//创建一个page对象
        Page<EduCourse> pageCourse = new Page<>(current, limit);
        //构建条件
        QueryWrapper<EduCourse> wrapper = new QueryWrapper<>();
        //多条件组合查询 类似动态sql
        String title = courseQuery.getTitle();
        String status = courseQuery.getStatus();
        String begin = courseQuery.getBegin();
        String end = courseQuery.getEnd();
        //判断条件值是否为空,如果不为空拼接条件
        if (!StringUtils.isEmpty(title)) {
            //构建条件
            wrapper.like("title", title);
        }
        if (!StringUtils.isEmpty(status)) {
            wrapper.eq("status", status);
        }
        if (!StringUtils.isEmpty(begin)) {
            wrapper.ge("gmt_create", begin);//gmt_create是表中的字段名,ge是大于等于
        }
        if (!StringUtils.isEmpty(end)) {
            wrapper.le("gmt_create", end);//le 是小于等于
        }
        //排序
        wrapper.orderByDesc("gmt_create");
        //调用方法实现条件查询分页
        courseService.page(pageCourse, wrapper);
        long total = pageCourse.getTotal();//总记录数

        List<EduCourse> records = pageCourse.getRecords();//数据list集合

        return R.ok().data("total", total).data("rows", records);//data是map集合可以这样put

    }

前端
/course/list.vue
api中定义方法
course.js

 //分页查询课程列表
      getCourseListPage(current,limit,courseQuery){
        return request({  //表示用到引入的request
            
            url: `/eduservice/course/pageCourseCondition/${current}/${limit}`,//用的是飘号``
            method: 'post',
           //courseQuery 条件对象,后端使用RequestBody获取数据
           data:courseQuery //data表示把参数对象转换为json进行传递到接口里面
          })

    }

前端页面显示

<template>

  <div class="app-container">
    <!-- 课程列表 -->
    <!--查询表单 在一行显示 -->
    <el-form :inline="true" class="demo-form-inline" >
      <el-form-item :xs="4" > 
          <!-- name可以不在data中定义 -->
        <el-input v-model="courseQuery.title" placeholder="课程名"/>
      </el-form-item>

      <el-form-item >
        <el-select v-model="courseQuery.status" clearable placeholder="课程状态">
          <el-option :value="this.statusList.Dr" label="未发布"/>
          <el-option :value="this.statusList.Fo" label="已发布"/>
        </el-select>
      </el-form-item>

      <el-form-item label="添加时间" >
        <el-date-picker
          v-model="courseQuery.begin"
          type="datetime"
          placeholder="选择开始时间"
          value-format="yyyy-MM-dd HH:mm:ss"
          default-time="00:00:00"
        />
      </el-form-item>
      <el-form-item>
        <el-date-picker
          v-model="courseQuery.end"
          type="datetime"
          placeholder="选择截止时间"
          value-format="yyyy-MM-dd HH:mm:ss"
          default-time="00:00:00"
        />
      </el-form-item>

      <el-button type="primary" icon="el-icon-search" @click="getList()">查询</el-button>
      <el-button type="default" @click="resetData()">清空</el-button>
    </el-form>

    <!-- 表格 -->
    <el-table
    :data="list"
    border
    element-loading-text="数据加载中"
    stripe
    style="width: 100%">
     <el-table-column
        label="序号"
        width="70"
        align="center">
        <template slot-scope="scope">
          {{ (page - 1) * limit + scope.$index + 1 }}
        </template>
      </el-table-column>
      <el-table-column prop="title" label="课程名称" width="80" />

      <el-table-column label="课程状态" width="80">
          <!-- 整个表是一个域scope,可以得到整个表中的内容 -->
        <template slot-scope="scope">  
            <!-- == 只判断大小,===判断大小和类型 -->
          {{ scope.row.status==="Normal"?'已发布':'未发布' }}
        </template>
     </el-table-column>

      <el-table-column prop="lessonNum" label="课时数" />
       <el-table-column prop="price" label="价格" />

      <el-table-column prop="gmtCreate" label="添加时间" width="160"/>

      <el-table-column prop="viewCount" label="浏览量" width="60" />

      <el-table-column label="操作" width="200" align="center">
        <template slot-scope="scope">
          <!-- 通过路由方式跳转到回显页面-->
          <router-link :to="'/course/edit/'+scope.row.id">
            <el-button type="primary" size="mini" icon="el-icon-edit">编辑课程信息</el-button>
          </router-link>   
          <router-link :to="'/course/edit/'+scope.row.id">
            <el-button type="primary" size="mini" icon="el-icon-edit">编辑课程大纲</el-button>
          </router-link>                                                 <!--scope代表整个表单 讲师每行的id值 -->
          <el-button type="danger" size="mini" icon="el-icon-delete" @click="removeDataById(scope.row.id)">删除课程信息</el-button>
        </template>
      </el-table-column>
    </el-table>
    <!-- 分页 -->
   
    <el-pagination
    :current-page="page"
    :page-size="limit"
    :total="total"
    background
    style="text-align:center;"
    layout="total,prev, pager, next,jumper"
    @current-change="getList"
    >
    </el-pagination>
    
  </div>
</template>
<script>

import course from '@/api/edu/course'

export default { //表示可以被其他的调用
    //写核心代码的位置
    // data:{
    // },
    data(){   //定义变量和初始值
        return {
            list:null,  //查询之后接口返回集合
            page:1,//当前页
            limit:10,//每页记录数
            total:0,//总记录数
            courseQuery:{
              
            },//条件封装对象
         statusList:{
           Dr:"Draft",
           Fo:"Normal"

         }
      }
    },
    created(){ //在页面渲染之前之前执行,调用methods定义的方法
      //调用
      this.getList()
    },
    methods:{//创建具体的方法,调用teacher.js定义的方法
        //讲师列表的方法
        getList(page=1){
            this.page=page //为了做到分页的切换,页数会从分页的 @current-change="getList"中传过来
            course.getCourseListPage(this.page,this.limit,this.courseQuery)
               .then(response=>{ //请求成功
                   //response接口返回的数据
                  //console.log(response)
                  this.list = response.data.rows
                  this.total = response.data.total //total不用写错,写错分页用不了
               })  
               .catch(error=>{
                   console.log(error)})  //请求失败
      },
      
      resetData(){ //清空的方法
         
         this.courseQuery={}

         //查询所有讲师的数据
         this.getList()

      }
    }
}
</script>

在这里插入图片描述

课程删除-后端

EduCourseController.java

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

EduCourseService.java接口


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

EduCourseServiceImpl实现类

    //课程描述的注入
    @Autowired
    private EduCourseDescriptionService courseDescriptionService;

    //注入小节和章节service
    @Autowired
    private EduVideoService eduVideoService;

    @Autowired
    private EduChapterService eduChapterService;
    
 @Override
    public void removeCourse(String courseId) {
        //根据课程id删除小节
        eduVideoService.removeVideoByCourseId(courseId);

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

        //根据课程id删除描述(因为课程id和课程描述是一对一的所以可以直接删除)
        courseDescriptionService.removeById(courseId);
        //根据课程id删除课程本身
        int result = baseMapper.deleteById(courseId);
        if (result == 0) {
            //删除失败
            throw new BuguException(20001, "删除失败");
        }
    }

再根据EduCourseServiceImpl实现类中需要的方法到章节小节的service中编写对应的方法
EduVideoService里面的方法

public interface EduVideoService extends IService<EduVideo> {
 //根据课程id删除小节
    void removeVideoByCourseId(String courseId);
}

EduVideoServiceImpl实现类里编写根据课程id删除小节的方法

@Service
public class EduVideoServiceImpl extends ServiceImpl<EduVideoMapper, EduVideo> implements EduVideoService {

    //TODO 删除小节,删除对应的视频文件
    @Override
    public void removeVideoByCourseId(String courseId) {
        QueryWrapper<EduVideo> wrapper = new QueryWrapper<>();
        wrapper.eq("course_id", courseId);
        baseMapper.delete(wrapper);
    }
}

EduChapterService接口

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

EduChapterServiceImpl实现类

@Override
    public void removeChapterByCourseId(String courseId) {
        QueryWrapper<EduChapter> wrapper = new QueryWrapper<>();
        wrapper.eq("course_id", courseId);
        baseMapper.delete(wrapper);
    }

swagger测试删除成功

课程删除前端

api中定义方法
course.js

 //删除课程
    deleteCourseId(id){
        return request({  //表示用到引入的request
        
            url: `/eduservice/course/${id}`, //用的是飘号``
            method: 'delete'
          })
    }

页面中调用方法
edu/course/list.vue

removeDataById(id){

          this.$confirm('此操作将永久删除讲师记录, 是否继续?', '提示', {
            confirmButtonText: '确定',
            cancelButtonText: '取消',
            type: 'warning'
        }).then(() => {  //点击确定执行这个方法
        //调用删除方法
            course.deleteCourseId(id)
            .then(response => { //删除成功
                //提示信息
                this.$message({
                type: 'success',
                message: '删除成功!'
                })
                //回到列表页面,刷新数据
                this.getList()               
          });
        })
     }
    

在这里插入图片描述

阿里云视频点播

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

视频点播sdk获取视频地址(测试练习)

在这里插入图片描述

先引入依赖 之前的父pom文件中都定义过版本号引入过
在这里插入图片描述
一、在service模块下新建一个maven模块service_vod
在service_vod的pom文件中引入依赖

<dependencies>
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>aliyun-java-sdk-core</artifactId>
        </dependency>
        <dependency>
            <groupId>com.aliyun.oss</groupId>
            <artifactId>aliyun-sdk-oss</artifactId>
        </dependency>
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>aliyun-java-sdk-vod</artifactId>
        </dependency>
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>aliyun-sdk-vod-upload</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
        </dependency>
        <dependency>
            <groupId>org.json</groupId>
            <artifactId>json</artifactId>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
        </dependency>

        <dependency>
            <groupId>joda-time</groupId>
            <artifactId>joda-time</artifactId>
        </dependency>
    </dependencies>

因为aliyun-java-vod-upload-1.4.11还暂时没开源,需要直接引入jar包到项目中。

先去官网下载:https://help.aliyun.com/document_detail/51992.html?spm=a2c4g.11186623.6.1029.2dab6cecZfMGvO
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
输入命令:
输入前加一个环境变量
在这里插入图片描述

mvn install:install-file -DgroupId=com.aliyun -DartifactId=aliyun-sdk-vod-upload -Dversion=1.4.11 -Dpackaging=jar -Dfile=aliyun-java-vod-upload-1.4.11.jar

在这里插入图片描述
引入依赖
父工程
在这里插入图片描述
子项目:

      <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>aliyun-java-sdk-vod</artifactId>
        </dependency>
public class TestVod {

    public static void main(String[] args) {
        //上传视频
        String accessKeyId = "LTAI4GJ8qrUvsDHAWoeL5QLR";
        String accessKeySecret = "50UPe6R0lUhFTZolVNn4G5DctF2v0U";

        String title = "6 - What If I Want to Move Faster - upload by sdk";   //上传之后文件名称
        String fileName = "C:/Users/17788/Desktop/项目源码/测试视频.mp4";  //本地文件路径和名称
        //上传视频的方法
        UploadVideoRequest request = new UploadVideoRequest(accessKeyId, accessKeySecret, title, fileName);
        /* 可指定分片上传时每个分片的大小,默认为2M字节 */
        request.setPartSize(2 * 1024 * 1024L);
        /* 可指定分片上传时的并发线程数,默认为1,(注:该配置会占用服务器CPU资源,需根据服务器情况指定)*/
        request.setTaskNum(1);

        UploadVideoImpl uploader = new UploadVideoImpl();
        UploadVideoResponse response = uploader.uploadVideo(request);

        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");
        }
    }

    //1 根据视频iD获取视频播放凭证
    public static void getPlayAuth() throws Exception{
        //初始化对象
        DefaultAcsClient client = InitObject.initVodClient("LTAI4GJ8qrUvsDHAWoeL5QLR", "50UPe6R0lUhFTZolVNn4G5DctF2v0U");
        //创建获取视频地址request和response
        GetVideoPlayAuthRequest request = new GetVideoPlayAuthRequest();
        GetVideoPlayAuthResponse response = new GetVideoPlayAuthResponse();
        //向request对象里面设置视频id
        request.setVideoId("7e4fc14ede874d26b144f507a2ac7b25");
         //调用初始化对象的方法得到凭证
        response = client.getAcsResponse(request);
        System.out.println("playAuth:"+response.getPlayAuth());
    }

    //1 根据视频iD获取视频播放地址
    public static void getPlayUrl() throws Exception{
        //创建初始化对象
        DefaultAcsClient client = InitObject.initVodClient("LTAI4GJ8qrUvsDHAWoeL5QLR", "50UPe6R0lUhFTZolVNn4G5DctF2v0U");

        //创建获取视频地址request和response
        GetPlayInfoRequest request = new GetPlayInfoRequest();
        GetPlayInfoResponse response = new GetPlayInfoResponse();

        //向request对象里面设置视频id
        request.setVideoId("7e4fc14ede874d26b144f507a2ac7b25");

        //调用初始化对象里面的方法,传递request,获取数据
        response = client.getAcsResponse(request);

        List<GetPlayInfoResponse.PlayInfo> playInfoList = response.getPlayInfoList();
        //播放地址
        for (GetPlayInfoResponse.PlayInfo playInfo : playInfoList) {
            System.out.print("PlayInfo.PlayURL = " + playInfo.getPlayURL() + "\n");
        }
        //Base信息
        System.out.print("VideoBase.Title = " + response.getVideoBase().getTitle() + "\n");
    }
}

添加小节上传视频-后端

在这里插入图片描述

第一步、在service下创建maven工程service_vod引入依赖
同上的依赖
第二步、创建application.properties

#服务端口 和service_edu中的端口号要用区别
server.port=8003
#服务名
spring.application.name=service-vod

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

#阿里云 OSS
#不同的服务器,地址不同
aliyun.oss.file.keyid=LTAI4GJ8qrUvsDHAWoeL5QLR
aliyun.oss.file.keysecret=50UPe6R0lUhFTZolVNn4G5DctF2v0U

第三步、创建启动类
com.yhn.vod包下
VodApplication .java

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)//上传视频不需要数据库,设置默认不加载数据库
@ComponentScan(basePackages = {"com.yhn"}) //包的扫描规则就能用到swagger,返回的R对象
public class VodApplication {
    public static void main(String[] args) {
        SpringApplication.run(VodApplication.class, args);
    }
}

第四步、编写controller

@RestController
@RequestMapping("/eduvod/video")
public class VodController {
    @Autowired
    private VodService vodService;

    //上传视频到阿里云的方法
    @PostMapping("uploadAlyVideo")//上传都是用post方式 MultipartFile得到上传文件
    public R uploadAlyVideo(MultipartFile file) {
        //返回上传视频的id
        String videoId=vodService.uploadVideoAly(file);
        return R.ok().data("videoId", videoId);
    }
}

第五步、编写service
service接口
VodService接口

public interface VodService {
    String uploadVideoAly(MultipartFile file);
}

编写一个工具类


/**工具类用来获取密钥和keyid
 * @author YHN
 * @create 2021-03-09 20:49
 */
 @Component //一定要加这个注解把它交给spring管理不然数据不能注入进来
public class ConstantVodUtils implements InitializingBean {
    @Value("${aliyun.vod.file.keyid}")
    private String keyid;
    @Value("${aliyun.vod.file.keysecret}")
    private String keysecret;

    public static String ACCESS_KEY_SECRET;
    public static String ACCESS_KEY_ID;
    @Override
    public void afterPropertiesSet() throws Exception {
        ACCESS_KEY_SECRET =keysecret ;
        ACCESS_KEY_ID = keyid;

    }
}


VodServiceImpl实现类

@Service
public class VodServiceImpl implements VodService{

    @Override
    public String uploadVideoAly(MultipartFile file) {

        try {
            //accessKeyId, accessKeySecret
            //fileName:上传文件原始名称
            // 01.03.09.mp4
            String fileName = file.getOriginalFilename();
            //title:上传之后显示名称
            String title = fileName.substring(0, fileName.lastIndexOf("."));
            //inputStream:上传文件输入流
            InputStream inputStream = file.getInputStream();
            UploadStreamRequest request = new UploadStreamRequest(ConstantVodUtils.ACCESS_KEY_ID, ConstantVodUtils.ACCESS_KEY_SECRET, title, fileName, inputStream);

            UploadVideoImpl uploader = new UploadVideoImpl();
            UploadStreamResponse response = uploader.uploadStream(request);

            String videoId = null;
            if (response.isSuccess()) {
                videoId = response.getVideoId();
            } else { //如果设置回调URL无效,不影响视频上传,可以返回VideoId同时会返回错误码。其他情况上传失败时,VideoId为空,此时需要根据返回错误码分析具体错误原因
          //   System.out.print("ErrorMessage=" + response.getMessage() + "\n");//得到错误信息

                videoId = response.getVideoId();
            }
            return videoId;
        }catch(Exception e) {
            e.printStackTrace();
            return null;
        }

    }
}

启动service_vod服务运行的是8003端口
测试用swagger的时候要把端口改为8003测试
在这里插入图片描述

在这里插入图片描述
解决方式:在springboot配置文件中
application.properties进行文件大小的设置


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

添加小节上传视频-前端实现

一、前端页面添加一个上传视频的组件
chapter.vue

<!--:file-list有文件列表  :on-remove表示有删除功能 before-remove删除之前会有弹框提示-->
       <el-form-item label="上传视频">
        <el-upload
            :on-success="handleVodUploadSuccess"
            :on-remove="handleVodRemove"
            :before-remove="beforeVodRemove"
            :on-exceed="handleUploadExceed"
            :file-list="fileList"
            :action="BASE_API+'/eduvod/video/uploadAlyVideo'"
            :limit="1"
            class="upload-demo">
        <el-button size="small" type="primary">上传视频</el-button>
        <el-tooltip placement="right-end">
            <div slot="content">最大支持1G,<br>
                支持3GP、ASFAVIDATDVFLVF4V<br>
                GIFM2TM4VMJ2MJPEGMKVMOVMP4<br>
                MPEMPGMPEGMTSOGGQTRMRMVB<br>
                SWFTSVOBWMVWEBM 等视频格式上传</div>
            <i class="el-icon-question"/>
        </el-tooltip>
        </el-upload>
    </el-form-item>

二、编写方法

 //上传视频成功调用的方法
        handleVodUploadSuccess(response, file, fileList) {
            //上传视频id赋值
            this.video.videoSourceId = response.data.videoId
            //上传视频名称赋值
            this.video.videoOriginalName = file.name
        },
        handleUploadExceed() {
            this.$message.warning('想要重新上传视频,请先删除已上传的视频')
        },

三、参数定义视频id视频名称,上传文件列表,接口API地址
在这里插入图片描述

二、在nginx,conf配置文件中加入端口8003的配置
在这里插入图片描述
三、nginx支持上传大小有限
上传视频的时候浏览器F12的console报错如下:

Access to XMLHttpRequest at ‘http://localhost:9001/eduvod/video/uploadVideo’ from origin ‘http://localhost:9528’ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource.
在网络里也出现点完出现全局异常,发现Java后端报错不支持"get"
在这里插入图片描述

在这里插入图片描述
解决方法

client_max_body_size 1024m;

添加小节的删除视频

在这里插入图片描述
在这里插入图片描述
有对勾之后才是上传成功才可以删除

后端接口

一、在工具包下创建一个类。用来获取初始化对象

public class InitObject {
    public static DefaultAcsClient initVodClient(String accessKeyId, String accessKeySecret) throws ClientException {
        String regionId = "cn-shanghai";  // 点播服务接入区域
        DefaultProfile profile = DefaultProfile.getProfile(regionId, accessKeyId, accessKeySecret);
        DefaultAcsClient client = new DefaultAcsClient(profile);
        return client;
    }
}

二、编写controller
VodController.java

 //根据视频id删除阿里云视频
    @DeleteMapping("removeAlyVideo/{id}")
    public R removeAlyVideo(@PathVariable String id) {

        try {
            //初始化对象
            DefaultAcsClient client = InitObject.initVodClient(ConstantVodUtils.ACCESS_KEY_ID, ConstantVodUtils.ACCESS_KEY_SECRET);
            //创建一个删除视频request对象
            DeleteVideoRequest request = new DeleteVideoRequest();
            //向request设置视频id
            request.setVideoIds(id);
            //调用初始化对象的方法实现删除
            client.getAcsResponse(request);
            return R.ok();
        } catch (Exception e) {
            e.printStackTrace();
            throw new BuguException(20001, "删除视频失败");
        }

    }
前端部分

一、api中定义删除的方法
video.js

  //删除视频
    deleteAliyunvod(id){
        return request({
            url: '/eduvod/video/removeAlyVideo/'+id,
            method: 'delete'
        })
    }

二、页面中编写方法
chapter.vue
在这里插入图片描述

//点击确定删除调用的方法
        handleVodRemove(){
           //调用接口的删除视频的方法
           video.deleteAliyunvod(this.video.videoSourceId)
           .then(response=>{
                 //提示信息
                    this.$message({
                        type: 'success',
                        message: '删除视频成功!'
                    });
                    //删除成功之后把文件列表清空
                    this.fileList=[]
                     //把video视频id和视频名称清空
                    this.video.videoSourceId = ''
                    this.video.videoOriginalName = ''
           })
        },
        //点击x调用的方法
        beforeVodRemove(file,fileList){
            return this.$confirm(`确定移除${file.name}?`);
        },

上传视频成功后再删除视频,网速慢还没有上传完就上传可能删除失败

**bug:**删除视频后添加小节视频的名字和id还是加到数据库中,这是因为我们先做了上传把视频的id和名字已经赋值给变量了提交小节就把这些也加到数据库中,我们删除只是把阿里云中的视频删除了
解决方法:删除视频后把video视频id和视频名称清空
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值