本文主要是介绍布谷课堂1,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
布谷学院项目介绍
布谷课堂(谷粒学院)二
一、功能简介
谷粒学院,是一个B2C模式的职业技能在线教育系统,分为前台用户系统和后台运营平台。
二、技术架构
系统开发阶段使用了前后端分离架构,部署阶段使用了容器技术
day01
day02
开发讲师模块后端
准备工作
1、创建数据库bugu
2、数据表 guli_edu.sql
一、工程结构
guli-parent:根目录(父工程),管理子模块:
-
canal-client:canal数据库表同步模块
-
common:公共模块父节点
-
common-util:工具类模块,所有模块都可以依赖于它
-
service-base:service服务的base包,包含service服务的公共配置类,所有service模块依赖于它
-
spring-security:认证与授权模块,需要认证授权的service服务依赖于它
-
-
infrastructure:基础服务模块父节点
- api-gateway:api网关服务
-
service:api接口服务父节点
-
service-edu:教学相关api接口服务
-
service-oss:阿里云oss api接口服务
-
service-cms:cms api接口服务
-
service-sms:短信api接口服务
-
service-trade:订单和支付相关api接口服务
-
service-statistics:统计报表api接口服务
-
service-ucenter:会员api接口服务
-
service-vod:视频点播api接口服务
-
二、创建父工程guli-parent
1、创建Spring Boot项目
使用 Spring Initializr 快速初始化一个 Spring Boot 项目
Group:com.yhn
Artifact:bugu-parent
2、删除src
3、版本改为2.2.1
在artifactId下面加一个pom标签表示是一个pom文件
<artifactId>bugu_parent</artifactId><packaging>pom</packaging>
4、pom的依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.2.1.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.yhn</groupId><artifactId>bugu_parent</artifactId><packaging>pom</packaging><version>0.0.1-SNAPSHOT</version><name>bugu_parent</name><description>Demo project for Spring Boot</description><properties><java.version>1.8</java.version><cloud.version>Hoxton.RELEASE</cloud.version><alibaba.version>2.2.0.RELEASE</alibaba.version><mybatis-plus.version>3.3.1</mybatis-plus.version><velocity.version>2.0</velocity.version><swagger.version>2.7.0</swagger.version><aliyun.oss.version>3.1.0</aliyun.oss.version><jodatime.version>2.10.1</jodatime.version><commons-fileupload.version>1.3.1</commons-fileupload.version><commons-io.version>2.6</commons-io.version><commons-lang.version>3.9</commons-lang.version><httpclient.version>4.5.1</httpclient.version><jwt.version>0.7.0</jwt.version><aliyun-java-sdk-core.version>4.3.3</aliyun-java-sdk-core.version><aliyun-java-sdk-vod.version>2.15.2</aliyun-java-sdk-vod.version><aliyun-sdk-vod-upload.version>1.4.11</aliyun-sdk-vod-upload.version><fastjson.version>1.2.28</fastjson.version><gson.version>2.8.2</gson.version><json.version>20170516</json.version><commons-dbutils.version>1.7</commons-dbutils.version><alibaba.easyexcel.version>2.1.1</alibaba.easyexcel.version><apache.xmlbeans.version>3.1.0</apache.xmlbeans.version></properties><dependencyManagement><dependencies><!--Spring Cloud--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>${cloud.version}</version><type>pom</type><scope>import</scope></dependency><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-alibaba-dependencies</artifactId><version>${alibaba.version}</version><type>pom</type><scope>import</scope></dependency><!--mybatis-plus 持久层--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>${mybatis-plus.version}</version></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-generator</artifactId><version>${mybatis-plus.version}</version></dependency><!-- velocity 模板引擎, Mybatis Plus 代码生成器需要 --><dependency><groupId>org.apache.velocity</groupId><artifactId>velocity-engine-core</artifactId><version>${velocity.version}</version></dependency><!--swagger--><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>${swagger.version}</version></dependency><!--swagger ui--><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId><version>${swagger.version}</version></dependency><!--aliyunOSS--><dependency><groupId>com.aliyun.oss</groupId><artifactId>aliyun-sdk-oss</artifactId><version>${aliyun.oss.version}</version></dependency><!--日期时间工具--><dependency><groupId>joda-time</groupId><artifactId>joda-time</artifactId><version>${jodatime.version}</version></dependency><!--文件上传--><dependency><groupId>commons-fileupload</groupId><artifactId>commons-fileupload</artifactId><version>${commons-fileupload.version}</version></dependency><!--commons-io--><dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId><version>${commons-io.version}</version></dependency><!--commons-lang3--><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>${commons-lang.version}</version></dependency><!--httpclient--><dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId><version>${httpclient.version}</version></dependency><!-- JWT --><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>${jwt.version}</version></dependency><!--aliyun--><dependency><groupId>com.aliyun</groupId><artifactId>aliyun-java-sdk-core</artifactId><version>${aliyun-java-sdk-core.version}</version></dependency><dependency><groupId>com.aliyun</groupId><artifactId>aliyun-java-sdk-vod</artifactId><version>${aliyun-java-sdk-vod.version}</version></dependency><dependency><groupId>com.aliyun</groupId><artifactId>aliyun-sdk-vod-upload</artifactId><version>${aliyun-sdk-vod-upload.version}</version></dependency><!--json--><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>${fastjson.version}</version></dependency><dependency><groupId>org.json</groupId><artifactId>json</artifactId><version>${json.version}</version></dependency><dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId><version>${gson.version}</version></dependency><dependency><groupId>commons-dbutils</groupId><artifactId>commons-dbutils</artifactId><version>${commons-dbutils.version}</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>${alibaba.easyexcel.version}</version></dependency><dependency><groupId>org.apache.xmlbeans</groupId><artifactId>xmlbeans</artifactId><version>${apache.xmlbeans.version}</version></dependency></dependencies></dependencyManagement><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>
5、在bugu_parent下创建maven模块service
把src文件也可以删除
配置pom文件
<artifactId>service</artifactId><packaging>pom</packaging>
<dependencies><dependency><groupId>com.yhn</groupId><artifactId>common-util</artifactId><version>0.0.1-SNAPSHOT</version></dependency><!--<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-ribbon</artifactId></dependency>--><!--hystrix依赖,主要是用 @HystrixCommand --><!--<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-hystrix</artifactId></dependency>--><!--服务注册--><!-- <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency>--><!--服务调用--><!-- <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency>--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--mybatis-plus--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.0.5</version></dependency><!--mysql--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><!-- velocity 模板引擎, Mybatis Plus 代码生成器需要 --><dependency><groupId>org.apache.velocity</groupId><artifactId>velocity-engine-core</artifactId></dependency><!--swagger--><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId></dependency><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId></dependency><!--lombok用来简化实体类:需要安装lombok插件--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><!--日期时间工具--><dependency><groupId>joda-time</groupId><artifactId>joda-time</artifactId></dependency><!--lang3--><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId></dependency><dependency><groupId>commons-fileupload</groupId><artifactId>commons-fileupload</artifactId></dependency><!--commons-io--><dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId></dependency><!--json--><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId></dependency><dependency><groupId>org.json</groupId><artifactId>json</artifactId></dependency><dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId></dependency><!--httpclient--><dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId></dependency>
</dependencies>
7、在service中再创建一个子maven模块service_edu(讲师模块)
8、在bugu_parent下创建一个common的maven模块
然后删除src
- 8.1 引入依赖
<artifactId>common</artifactId><packaging>pom</packaging>
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><scope>provided </scope></dependency><!--mybatis-plus--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><scope>provided </scope></dependency><!--lombok用来简化实体类:需要安装lombok插件--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><scope>provided </scope></dependency><!--swagger--><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><scope>provided </scope></dependency><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId><scope>provided </scope></dependency><!-- redis --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- spring2.X集成redis所需common-pool2<dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId><version>2.6.0</version></dependency>--></dependencies>
</project>
- 8.2 在common创建一个子maven工程service_base
在里面再创建一个com.yhn.servicebase包里面创建SwaggerConfig配置类
@Configuration//配置类
@EnableSwagger2 //swagger注解
public class SwaggerConfig {@Beanpublic 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 {@Testpublic 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); //重新生成时文件是否覆盖//UserServiegc.setServiceName("%sService"); //去掉Service接口的首字母Igc.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.eduservicepc.setParent("com.yhn");//包 com.yhn.eduservice.controllerpc.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注入@Autowiredprivate 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中配置逻辑删除的插件
/*** ;逻辑删除插件*/@Beanpublic ISqlInjector sqlInjector() {return new LogicSqlInjector();}
2、在EduTeacher类中给逻辑删除字段加一个注解 @TableLogic
@ApiModelProperty(value = "逻辑删除 1(true)已删除, 0(false)未删除")@TableLogicprivate 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注入@Autowiredprivate 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类中配置分页插件
/*** 分页插件*/@Beanpublic 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<>();//多条件组合查询 类似动态sqlString 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 {@Overridepublic void insertFill(MetaObject metaObject) {//传的类中的属性名称,不是字段名称this.setFieldValByName("gmtCreate", new Date(), metaObject);this.setFieldValByName("gmtModified", new Date(), metaObject);}@Overridepublic 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)@ResponseBodypublic 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)@ResponseBodypublic 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)@ResponseBodypublic R error(BuguException e) {log.error(e.getMsg());//把错误信息写到文件中去e.printStackTrace();return R.error().code(e.getCode()).message(e.getMsg());}
day03-项目前端
vscode介绍
创建文件写代码并执行,使用open with Live server,执行。
vue.js是什么
vue介绍
使用!快捷生成html页面
2、在vscode中抽取vue的代码片段
day04-项目前端和前端环境搭建
搭建项目前端页面环境
1、把vue-admin-template-master的这个前端框架解压到自己的vscode工作目录下
2、用vscode把这个文件用终端打开
3、安装依赖,npm install下载package.json里面的依赖
下载完成后的依赖在node_modules文件夹下。如果失败就把这个文件删除再重新下载
4、启动下载好依赖的项目 命令
npm run dev
框架中目录介绍
框架可以自动帮我们把es6转为es5执行
day05-讲师管理前端开发
注意:每次改完前端都要 ctrl + s保存
后台系统登录功能改造
4、开发接口
在controller层中新建一个类
/*** 模拟登录的方法** @author YHN* @create 2021-03-03 10:32*/
@RestController
@RequestMapping("/eduservice/user")
@CrossOrigin //解决跨域
public class EduLoginController {//login@PostMapping("login")public R login() {return R.ok().data("token", "admin");//和前端中user.js里面返回数据的名字token一样}//info@GetMapping("info")public R info() { //头像return R.ok().data("roles","[admin]").data("name","admin").data("avatar","https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif");}
}
5、修改api文件夹login.js文件修改本地接口路径
url: '/eduservice/user/login',method: 'post',url: '/eduservice/user/info',method: 'get',
6、最终测试。启动前端测试
然后点登录出现问题
7、跨域的解决方式
(1) 在后端接口controller加一个注解
@CrossOrigin //解决跨域
public class EduLoginController {
(2)使用网关解决
测试成功
每次都是两次同样请求,第一次先做一个域请求看服务器是否能联通,能联通之后才发送请求
框架开发过程介绍
第一步添加路由,按照index.js里面实例代码改
第二步点击某个路由显示里面的内容
第三步在api文件中创建js文件,定义接口地址和参数
import request from '@/utils/request'export function getList(params) {return request({ //表示用到引入的requesturl: '/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.rowsthis.total = response.data.total //total不用写错,写错分页用不了}) .catch(error=>{console.log(error)}) //请求失败}}
}
</script>
第五步 把数据在页面中显示需要使用到element插件
list.vue
<template><div class="app-container"><!-- 表格 --><el-table:data="list"borderelement-loading-text="数据加载中"stripestyle="width: 100%"><el-table-columnlabel="序号"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" backgroundstyle="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-pickerv-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-pickerv-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({ //表示用到引入的requesturl: `/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/tablename: '添加讲师',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"),//组件,和添加讲师路由使用一个页面savemeta:{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;@Overridepublic 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 {@Autowiredprivate OssService ossService;//上传头像的方法@PostMappingpublic 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 {//上传头像到ossString uploadFileAvatar(MultipartFile file);
}
实现类OssServiceImpl
package com.yhn.oss.service.impl;@Service
public class OssServiceImpl implements OssService {//上传头像到oss@Overridepublic 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.jpgfileName = uuid+fileName;//2 把文件按照日期进行分类//获取当前日期// 2019/11/12String datePath = new DateTime().toString("yyyy/MM/dd");//拼接// 2019/11/12/ewtqr313401.jpgfileName = 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.jpgString 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 stopnginx.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-cropperv-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.urlthis.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内容@Overridepublic void invoke(DemoData data, AnalysisContext analysisContext) {System.out.println("****"+data);}//读取表头的内容public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {System.out.println("表头:"+ headMap);}//读取完成之后@Overridepublic 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 {@Autowiredprivate 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*/@Overridepublic 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对象@Overridepublic 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表示等于0EduSubject 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;}@Overridepublic 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/listname: '课程分类列表',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-uploadref="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 =truethis.loading =true//js: document.getElementById("upload").submit()this.$refs.upload.submit() //官方给的提交表单的方法},//上传成功fileUploadSuccess(){//提示信息this.loading = falsethis.$message({type: 'success',message: '添加课程分类成功'})//跳转课程分类列表 //路由跳转this.$router.push({path:'/subject/list'})},fileUploadError(){this.loading = falsethis.$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实现类
//课程分类列表(数形)@Overridepublic List<OneSubject> getAllOneTwoSubject() {//1、查询所有的一级分类 parentid=0QueryWrapper<EduSubject> wrapperOne = new QueryWrapper<>();wrapperOne.eq("parent_id", "0");//baseMapper在EduSubjectServiceImpl继承的ServiceImpl中做了封装可以直接调用List<EduSubject> oneSubjectList = baseMapper.selectList(wrapperOne);//2、查询所有的二级分类parentid!=0QueryWrapper<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({ //表示用到引入的requesturl: `/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-treeref="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 truereturn 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 {@Autowiredprivate 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 {//课程描述的注入@Autowiredprivate EduCourseDescriptionService courseDescriptionService;@Overridepublic 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, "添加课程信息失败");}//获取添加之后课程idString cid = eduCourse.getId();//2 向课程简介表添加课程信息EduCourseDescription courseDescription = new EduCourseDescription();courseDescription.setDescription(courseInfoVo.getDescription());//设置描述id就是课程idcourseDescription.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/listname: '课程列表',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({ //表示用到引入的requesturl: `/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-selectv-model="courseInfo.subjectParentId"placeholder="一级分类" @change="subjectLevelOneChanged"><el-optionv-for="subject in subjectOneList":key="subject.id":label="subject.title":value="subject.id"/></el-select><!-- 二级分类 --><el-select v-model="courseInfo.subjectId" placeholder="二级分类"><el-optionv-for="subject in subjectTwoList":key="subject.id":label="subject.title":value="subject.id"/></el-select></el-form-item><!-- 课程讲师 TODO --><!-- 课程讲师 --><el-form-item label="课程讲师"><el-selectv-model="courseInfo.teacherId"placeholder="请选择"><el-optionv-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: '',//二级分类idsubjectParentId:'',//一级分类idteacherId: '',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-selectv-model="courseInfo.teacherId"placeholder="请选择"><el-optionv-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-selectv-model="courseInfo.subjectParentId"placeholder="一级分类" @change="subjectLevelOneChanged"><el-optionv-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中定义的,用的时候需要加thisfor(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-optionv-for="subject in subjectTwoList":key="subject.id":label="subject.title":value="subject.id"/></el-select></el-form-item>
问题:再次点另外一个一级分类二级分类中原来的显示没有清空,但是下拉列表是对的
解决方法:每次点击一级分类时候给二级分类里面值清空
课程封面
1、定义接口地址
2、编写方法
methods:{//上传封面成功调用的方法 res就是responsehandleAvatarSuccess(res,file){//把返回的地址封装到courseInfo的cover里面this.courseInfo.cover= res.data.url},//上传之前调用的方法beforeAvatarUpload(file) {const isJPG = file.type === 'image/jpeg'const isLt2M = file.size / 1024 / 1024 < 2if (!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 {@Autowiredprivate 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 {@Autowiredprivate EduVideoService videoService;//因为这里面查询不了video,所以 注入小节service//课程大纲列表,根据课程id进行查询@Overridepublic 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"><liv-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"><liv-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实现类
@Overridepublic 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;}@Overridepublic 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和二级分类idthis.courseInfo=response.data.courseInfoVo//1 查询所有的分类包含一级二级分类subject.getSubjectList().then(response=>{// 2 获取所有的一级分类this.subjectOneList=response.data.list//3 把所有的一级分类数组进行遍历,比较当前的courseInfo里面的一级分类id和所有的一级分类idfor(var i=0;i<this.subjectOneList.length;i++){//获取每一个一级分类var oneSubject=this.subjectOneList[i]//比较当前courseInfo里面一级分类id和所有的一级分类idif(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,但是可以取到回显数据时候得到的课程idthis.$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
@Overridepublic 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为truereturn 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.courseIdchapter.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 {@Autowiredprivate 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//设置章节idthis.video.chapterId=chapterId//调用完添加或者修改方法就把弹框的小节对象清空,让下次点击添加小节的时候里面没有内容this.video.sort=0this.video.title=''this.video.id=''this.video.isFree=false},//添加小节需要课程id和章节idaddVideo(){//设置课程idthis.video.courseId=this.courseIdvideo.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 subjectLevelTwoFROM edu_course ecLEFT 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.01private 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 subjectLevelTwoFROM edu_course ecLEFT 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实现类
@Overridepublic CoursePublishVo publishCourseInfo(String id) {//调用mapper,调用自己写的sql方法只能用baseMapperCoursePublishVo 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<>();//多条件组合查询 类似动态sqlString 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({ //表示用到引入的requesturl: `/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-pickerv-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-pickerv-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"borderelement-loading-text="数据加载中"stripestyle="width: 100%"><el-table-columnlabel="序号"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"backgroundstyle="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.rowsthis.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实现类
//课程描述的注入@Autowiredprivate EduCourseDescriptionService courseDescriptionService;//注入小节和章节service@Autowiredprivate EduVideoService eduVideoService;@Autowiredprivate EduChapterService eduChapterService;@Overridepublic 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 删除小节,删除对应的视频文件@Overridepublic void removeVideoByCourseId(String courseId) {QueryWrapper<EduVideo> wrapper = new QueryWrapper<>();wrapper.eq("course_id", courseId);baseMapper.delete(wrapper);}
}
EduChapterService接口
//根据课程id删除章节void removeChapterByCourseId(String courseId);
EduChapterServiceImpl实现类
@Overridepublic 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({ //表示用到引入的requesturl: `/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和responseGetVideoPlayAuthRequest request = new GetVideoPlayAuthRequest();GetVideoPlayAuthResponse response = new GetVideoPlayAuthResponse();//向request对象里面设置视频idrequest.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和responseGetPlayInfoRequest request = new GetPlayInfoRequest();GetPlayInfoResponse response = new GetPlayInfoResponse();//向request对象里面设置视频idrequest.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 {@Autowiredprivate VodService vodService;//上传视频到阿里云的方法@PostMapping("uploadAlyVideo")//上传都是用post方式 MultipartFile得到上传文件public R uploadAlyVideo(MultipartFile file) {//返回上传视频的idString 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;@Overridepublic void afterPropertiesSet() throws Exception {ACCESS_KEY_SECRET =keysecret ;ACCESS_KEY_ID = keyid;}
}
VodServiceImpl实现类
@Service
public class VodServiceImpl implements VodService{@Overridepublic String uploadVideoAly(MultipartFile file) {try {//accessKeyId, accessKeySecret//fileName:上传文件原始名称// 01.03.09.mp4String 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设置视频idrequest.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和视频名称清空
这篇关于布谷课堂1的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!