布谷学院项目介绍
一、功能简介
谷粒学院,是一个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介绍
- swagger2介绍
-
测试,在启动类EduApplication中先启动项目
- http://localhost:8001/swagger-ui.html查看swagger是否启动成功
- 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是什么
使用!快捷生成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、ASF、AVI、DAT、DV、FLV、F4V、<br>
GIF、M2T、M4V、MJ2、MJPEG、MKV、MOV、MP4、<br>
MPE、MPG、MPEG、MTS、OGG、QT、RM、RMVB、<br>
SWF、TS、VOB、WMV、WEBM 等视频格式上传</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和视频名称清空