谷粒商城笔记

谷粒商城笔记

  • 1. Mybatis_plus复习
    • 1.1 数据库配置(字符编码设置)***
    • 1.2 mybatis_plus日志
    • 1.3 mybatis_plus主键策略
    • 1.4 分页查询
    • 1.5 逻辑删除**
    • 1.6 条件构造器
    • 1.7 Mp封装Server层***
    • 1.8 自动填充
    • 1.9 乐观锁(**)
    • 总结
  • 2.项目搭建(父工程)
    • 2.1 配置pom文件
    • 2.2 子项目(service)
  • 3.service-edu模块
    • 3.1 代码生成器
    • 3.2 讲师列表(mapper注入)
    • 3.3 json时间格式
    • 3.4 逻辑删除讲师
  • 4.整合swagger
    • 不同工程引入问题
    • swagger注解
  • 5.统一返回类型(swagger新注解)
  • 6.get post传参
  • 7.前端传递JSON数组
  • 8.讲师条件分页查询
    • 问题
  • 9.添加讲师
  • 10.修改讲师
  • 11.统一异常处理类
    • 全局异常处理
    • 特定异常处理
    • 自定义异常处理
  • 12.统一日志处理
    • 配置日志级别
    • 日志输出到文件
    • 将错误日志输出到文件
  • 13.ES6基本语法
    • let,var使用
    • 解构赋值
    • 模板字符串
    • 声明对象
    • 对象拓展运算符
    • 首次创建vue对象
    • 修饰符
    • es6模块化开发(export,export default)
    • Webpack
      • webpack打包js文件
      • 打包css文件
      • 总结
  • 14.前端模板文件分析
    • src目录
    • 前端入口文件
    • config目录
  • 15.登录(跨域)
    • vue前端功能实现过程
    • 登录改造本地
    • 解决跨域问题
  • 16.路由相关
    • 路由知识
    • 创建教师路由
  • 17.前端
    • 讲师列表(CRUD+element-ui)
      • 路由及页面初始化
      • 查询讲师
      • 删除讲师
      • 添加修改讲师
      • 路由传参
  • 18.对象存储阿里云oss
    • 开通对象存储OSS服务
    • 创建Bucket
    • 上传默认头像
    • java使用阿里云OSS
  • 19.搭建阿里云后台环境
  • 20.实现文件上传
    • 从配置文件读取常量
    • 上传文件
    • 问题
  • 21.nginx
  • 22.头像上传
  • 23.EasyExcel读写文件
    • EasyExcel写操作
    • EasyExcel读操作
  • 24.excel添加课程分类(后端)
    • 代码生成器
    • easyExcel读取
    • 总结
  • 25.添加课程分类前端
  • 26.课程分类列表
    • 前端
    • 后端
  • 27.课程添加
    • 后端
    • 课程信息
    • 课程章节大纲
    • 课程发布(***)
    • el-select
  • 28.课程列表
  • 29.视频点播
    • 视频上传实例
    • 视频上传项目创建
    • 启动类配置数据库问题
    • 文件上传后端
    • 文件上传nginx配置(*****)
    • 上传视频前端
    • 删除视频
  • spring cloud
    • nacos注册中心
    • feign

1. Mybatis_plus复习

mybatis_plus是好久之前学习的,现在已经有点遗忘了,还好老师讲了一些知识点。

(1)导入mybatis_plus的依赖。
springboot对应的mybatis_plus依赖,一定要选带starter启动器的。

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>最新版本</version>
</dependency>
<dependency>
     <groupId>mysql</groupId>
     <artifactId>mysql-connector-java</artifactId>
</dependency>

个人遇到的错误
打开mybatis_plus首页发现最新版本,直接复制粘贴,后来一直报错,最后才发现引入的时spring对应的mybatis_plus依赖,不是springboot的。说多了都是泪呀
谷粒商城笔记_第1张图片

1.1 数据库配置(字符编码设置)***

(2)配置文件配置数据库连接,一定要加characterEncoding=utf8,否则在查询时会失败。

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/guli?characterEncoding=utf8&serverTimezone=GMT%2B8
    username: root
    password: 747699

上面在引入mysql依赖的时候没有表明是mysql5还是mysql8,所以会使用自己电脑的版本。
com.mysql.cj.jdbc.Driver 这里的cj表示默认使用mysql8的驱动。
spring boot2版本使用的是mysql8的驱动。

(3)创建实体类User

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private Long id;
    private String name;
    private Integer age;
    private String email;
}

(4)创建mapper接口类(核心)

@Mapper
public interface UserMapper extends BaseMapper<User> {
}

@Mapper注解表示将我们的mapper注入到容器中。
每个自定义的mapper接口都要继承BaseMapper,< User >表示mapper接口操作的表,也可以理解为实体类。 BaseMapper是mybatis_plus为我们封装好了许多常用数据库操作函数。

(5)测试
测试时不小心将自己的测试类删除了,使用注解@SpringBootTest手动创建了测试类,但是在注入bean时,出现一个错误,提示没有找到userMapper。
谷粒商城笔记_第2张图片

原因:注解中没有加启动类,即@SpringBootTest(classes = 启动类.class)。因为学习springboot有讲过,启动类中的run函数会返回IOC容器,里面有我们注入的所有bean。只有和启动类同一包下的注解才可以扫描,然后注入到容器中。那么在使用bean时,应该也在同一包下,如果不在同一包下,应该加入上面的注解。但是,当初始化创建一个项目时,创建好的测试类只有@SpringBootTest也可以使用容器中的Bean。


总之使用了上面的注解可以正常使用bean组件,但是具体原因还不确定。

谷粒商城笔记_第3张图片
如果没有删除初始化的测试类,下面的代码不会报错

@SpringBootTest
class MybatisPlusApplicationTests {

    @Autowired
    UserMapper userMapper;
    @Test
    void contextLoads() {
        List<User> users = userMapper.selectList(null);
    }

}

其它相关操作mybatis_plus官方文档有相应介绍。

1.2 mybatis_plus日志

日志是方便我们在程序出现错误时,及时发现错误的原因。
在配置文件中设置开启

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

执行mybatis_plus插入操作时,控制台日志输出

谷粒商城笔记_第4张图片

1.3 mybatis_plus主键策略

通过@TableId注解实现,为什么说这是mybatis_plus主键策略呢,因为该注解的来源如下:
import com.baomidou.mybatisplus.annotation.TableId;
使用主键策略前提时,在数据库表中,将主键设置为自增。
主键策略如下所示:

谷粒商城笔记_第5张图片

NONE:该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT)即默认策略。

AUTO:数据库ID自增。增加幅度为1。较为常用

INPUT:用户输入ID,该类型可以通过自己注册自动填充插件进行填充。

ASSIGN_ID:分配ID (主键类型为number或string),number类型较为常用,会默认为我们分配ID,也遵循自动增长原则,但是增长的幅度不确定。

ASSIGN_UUID:分配UUID (主键类型为 string) ,主键中一定包含字符,对于需要主键排序的情况不适用,所以不如ASSIGN_ID常用。
总结
一般在插入数据时,我们是不会设置主键值,所以此时主键策略起到了很大作用。一帮使用ASSIGN_ID或AUTO策略。
例如:在user类的主键加入逐渐策略

@TableId(type = IdType.ASSIGN_ID)
private Long id;

执行插入时,就可以不用设置主键值,系统会通过策略自动生成主键id。下面是一条插入示例

 void insert(){
        User user = new User();
        user.setAge(23);
        user.setEmail("[email protected]");
        user.setName("mfx");

        int insert = userMapper.insert(user);
        System.out.println(insert);
    }

执行两次插入操作:
谷粒商城笔记_第6张图片
可以发现两次的id都是随机的,但是有一个特点都是自增,但是增加的幅度不同。

1.4 分页查询

(1)配置分页插件配置文件,创建MpConfig配置类,Mp=Mybatis_plus。

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

(2)分页查询
selectPage函数执行分页查询操作,两个参数:
Page:使用Page类设置起始页和每页数据条数,(1,3)表示第一页,每页包含三条数据。
QueryWrapper:查询条件,具体见官方文档。

Page<User> userPage = userMapper.selectPage(new Page<>(1,3), null);
// 根据page查询到的数据
System.out.println(userPage.getRecords());
// 表中数据总条数
System.out.println(userPage.getTotal());
// 总页数
System.out.println(userPage.getPages());
// 每页大小
System.out.println(userPage.getSize());
// 是否有下一页
System.out.println(userPage.hasNext());
// 是否由前置页
System.out.println(userPage.hasPrevious());

结果如下
谷粒商城笔记_第7张图片
注意:如果不实现分页插件配置类,也可以查询,只是每次查询得到的结果都是所有数据。即能查询,不能分页查询。

1.5 逻辑删除**

最初接触的删除操作是直接将数据库中数据删除,也可以成为物理删除。但是实际中使用的时逻辑删除,原理为修改操作。
逻辑删除是为了方便数据恢复和保护数据本身价值等等的一种方案,但实际就是删除。
如果你需要频繁查出来看就不应使用逻辑删除,而是以一个状态去表示。

使用方法
由于使用的版本时旧版本,所以逻辑删除的配置会比较繁琐。最新版本mp不需要注入bean操作。
(1)配置文件设置逻辑删除规则

mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: flag # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
      logic-delete-value: 1 # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)

(2)实体类添加逻辑删除字段,并添加逻辑删除注解。通过自动填充,创建对象时将delete字段自动填充为0。

@TableLogic
@TableField(fill = FieldFill.INSERT)
private Integer deleted;

自动填充配置类insertFill函数添加配置代码:

@Override
public void insertFill(MetaObject metaObject) {
 ......
this.setFieldValByName("deleted", 0, metaObject);
}

(3)数据库表中添加字段deleted
(4)配置类注册bean

  @Bean
    public ISqlInjector sqlInjector(){
        return new LogicSqlInjector();
    }

(5)测试

先插入数据
在这里插入图片描述
删除最后一条数据
谷粒商城笔记_第8张图片
逻辑删除成功,将最后一条数据的deleted字段设置为1.

再次执行查询所有数据操作
谷粒商城笔记_第9张图片
总结:逻辑删除并不会删除该条数据,只是在查询时无法查询而已,方便我们后续的数据恢复。

1.6 条件构造器

主要使用的有个类:QueryWrapperLambdaQueryWrapper

QueryWrapper较为简单,直接参考官方文档:QueryWrapper

LambdaQueryWrapperQueryWrapper类似,但是支持Lambda表达式,较为新颖。

示例:
查询用户名为mfx的数据
QueryWrapper写法

 void query(){
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.eq("name","mfx");
        List<User> users = userMapper.selectList(wrapper);
        System.out.println(users);

    }

LambdaQueryWrapper`写法

void lambdaQuery(){
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(User::getName,"mfx");
        List<User> users = userMapper.selectList(wrapper);
        System.out.println(users);

    }

而这唯一的区别就是在条件查询函数中第一个参数的书写形式不同。个人觉得LambdaQueryWrapper好用一些,不易出错。

注意:Mp提供的这些函数只适用于单表操作,当涉及到多表的复杂业务时需要xml文件mybatis实现。

1.7 Mp封装Server层***

Mp不仅对mapper实现了封装,也对server层进行了封装。
正常后端代码的步骤是:controller 层调用server层,server层调用mapper层。但是如果只是简单的单表增删改查,可以只是用server层实现,因为Mp对server层进行了封装。

(1)创建server接口,要继承IService,原理和mapper继承baseMapper相同

public interface UserServer extends IService<User> {
}

(2)创建serverImp,@Service注入容器,要继承 ServiceImpl,并且实现server接口

@Service
public class UserServerImp extends ServiceImpl<UserMapper, User> implements UserServer {
}

谷粒商城笔记_第10张图片
ServiceImpl继承了BaseMapper,并且实现了IService,所以本质上还是mapper对表的操作,只是Mp对server层的封装。我们可以直接使用server代替之前mapper对表的操作。

测试:

  @Autowired
    UserServer userServer;
    @Test
    void userServers(){
        List<User> list = userServer.list();
        System.out.println(list);
    }

上面list是Mp为我们封装好的查询所有数据的方法,相当于mapper的selectList方法。

总结
对于接口的实现,有两种方法。
(1)只是简单单表操作:在controller注入server,直接执行对数据库的操作。
(2)涉及到复杂操作,mybatis通过xml实现对表的操作,在server中注入mapper,在contorller中注入server,最后对数据库进行操作。
(3)Mp的意义就是要简化我们的操作和代码书写,但是只针对于简单的单表操作,正常开发中还是需要(2)中的步骤来实现一个接口。

1.8 自动填充

每一张表都会设置两个字段,创建时间和更新时间。如果我们插入数据时,我们需要每次set两个时间,然后insert。但是,这项工作是重复的,所以mp帮我们实现这一步骤,也就是自动填充。
自动填充官网


自动填充分三步:

  1. 数据库创建两个字段create_time,update_time;
  2. 实体类添加两个字段createTime,updateTime,并添加注解@TableField
  3. 创建配置类,并注入到容器;

实体类添加字段

public class User {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String name;
    private Integer age;
    private String email;
    @TableLogic
    private Integer deleted;

    @TableField(fill = FieldFill.INSERT)
    private Date createTime;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;
}

配置类

package com.example.mybatis_plus.config;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;

import java.util.Date;

/**
 * @Author: mfx
 * @Description:
 * @Date: Created in 15:08 2022/8/11
 */
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        this.setFieldValByName("createTime", new Date(),metaObject);
        this.setFieldValByName("updateTime", new Date(),metaObject);
    }

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

效果展示
在这里插入图片描述

1.9 乐观锁(**)

(1)乐观锁
首先来看乐观锁,顾名思义,乐观锁就是持比较乐观态度的锁。就是在操作数据时非常乐观,认为别的线程不会同时修改数据,所以不会上锁,但是在更新的时候会判断在此期间别的线程有没有更新过这个数据。
(2)悲观锁(一般不用)
反之,悲观锁就是持悲观态度的锁。就在操作数据时比较悲观,每次去拿数据的时候认为别的线程也会同时修改数据,所以每次在拿数据的时候都会上锁,这样别的线程想拿到这个数据就会阻塞直到它拿到锁。


根据乐观锁的定义,我们可以知道要使用version版本号实现乐观锁。
mp实现乐观锁:

  1. 表中添加创建version字段;
  2. 实体类添加对应字段,使用@Version注解,并使用@TableField自动填充默认值为1;
@Version
@TableField(fill = FieldFill.INSERT)
private Integer version;
  1. mpConfig配置插件(注入Bean)
// Spring Boot 方式
@Configuration
public class MybatisPlusConfig {
    /**
     * 旧版
     */
    @Bean
    public OptimisticLockerInterceptor optimisticLockerInterceptor() {
        return new OptimisticLockerInterceptor();
    }

    /**
     * 新版
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
        mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return mybatisPlusInterceptor;
    }
}

测试:
先添加一条数据

谷粒商城笔记_第11张图片
修改数据后version变为了2,每修改一次就会+1。

先查询获取版本号,修改后和数据库中得版本号比对,一致就修改,必须先查后改

void update(){
        User user = userMapper.selectById(1542771054261653511L);
        user.setName("mfx111");
        
        int i = userMapper.updateById(user);
        System.out.println(i);
    }

谷粒商城笔记_第12张图片

使用sql语句不查询直接修改
update user set name = ‘version_test’ where id = 1542771054261653511
发现name修改成功,但是version没有+1,即乐观锁没有起作用。所以使用乐观锁,必须先查后改。

谷粒商城笔记_第13张图片

总结

Mp特性:

  • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
  • 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
  • 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
  • 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
  • 支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer2005、SQLServer 等多种数据库
  • 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
  • 支持 XML 热加载:Mapper 对应的 XML 支持热加载,对于简单的 CRUD 操作,甚至可以无 XML 启动
  • 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
  • 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
  • 支持关键词自动转义:支持数据库关键词(order、key…)自动转义,还可自定义关键词
  • 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
  • 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
  • 内置性能分析插件:可输出 Sql 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
  • 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作
  • 内置 Sql 注入剥离器:支持 Sql 注入剥离,有效预防 Sql 注入攻击

2.项目搭建(父工程)

glkt表示谷粒课堂。利用maven搭建项目,大致模块划分如下:
谷粒商城笔记_第14张图片
创建父工程guli_parent,spring boot使用2.2.1版本。
谷粒商城笔记_第15张图片
总结:
(1)guli-parent为父工程,里面定义依赖版本和依赖管理,不涉及使用依赖,不需要src文件夹;
(2)service为父工程下的一个子模块,由于其下面还有子模块,所以service是父模块,也不需要src文件夹,service使用父工程管理的部分依赖,service的子模块可以直接使用service中的依赖;
(3)service-edu为service中的子模块,需要src,里面写一些接口。由于service已经引入了依赖,所以service-edu中不需要再引入依赖。

2.1 配置pom文件

guli-parent后面加上下面代码

<packaging>pom</packaging>

pom含义:

项目的打包类型pom、jar、war
父工程都要设置为pom类型,pom项目不存放java代码,只适用于聚合子项目和传递项目依赖。

建立父工程,父工程负责管理整个项目的依赖。将父工程初始删除,替换为如下部分。
内容包括:定义版本号,根据定义的版本号导入依赖。
因为在父工程中定义了依赖的版本号,所以后续子模块只需要引入依赖,不需要加版本号。

	<properties>
        <java.version>1.8</java.version>
        <guli.version>0.0.1-SNAPSHOT</guli.version>
        <mybatis-plus.version>3.0.5</mybatis-plus.version>
        <velocity.version>2.0</velocity.version>
        <swagger.version>2.7.0</swagger.version>
        <aliyun.oss.version>2.8.3</aliyun.oss.version>
        <jodatime.version>2.10.1</jodatime.version>
        <poi.version>3.17</poi.version>
        <commons-fileupload.version>1.3.1</commons-fileupload.version>
        <commons-io.version>2.6</commons-io.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-sdk-oss.version>3.1.0</aliyun-sdk-oss.version>
        <aliyun-java-sdk-vod.version>2.15.2</aliyun-java-sdk-vod.version>
        <aliyun-java-vod-upload.version>1.4.11</aliyun-java-vod-upload.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>
        <canal.client.version>1.1.0</canal.client.version>
        <docker.image.prefix>zx</docker.image.prefix>
        <cloud-alibaba.version>0.2.2.RELEASE</cloud-alibaba.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <!--Spring Cloud-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${cloud-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>

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

            <!--xls-->
            <dependency>
                <groupId>org.apache.poi</groupId>
                <artifactId>poi</artifactId>
                <version>${poi.version}</version>
            </dependency>
            <!--xlsx-->
            <dependency>
                <groupId>org.apache.poi</groupId>
                <artifactId>poi-ooxml</artifactId>
                <version>${poi.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>

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

            <dependency>
                <groupId>com.google.code.gson</groupId>
                <artifactId>gson</artifactId>
                <version>${gson.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.oss</groupId>
                <artifactId>aliyun-sdk-oss</artifactId>
                <version>${aliyun-sdk-oss.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-java-vod-upload</artifactId>
                <version>${aliyun-java-vod-upload.version}</version>
            </dependency>
            <dependency>
                <groupId>com.aliyun</groupId>
                <artifactId>aliyun-sdk-vod-upload</artifactId>
                <version>${aliyun-sdk-vod-upload.version}</version>
            </dependency>
            <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>commons-dbutils</groupId>
                <artifactId>commons-dbutils</artifactId>
                <version>${commons-dbutils.version}</version>
            </dependency>

            <dependency>
                <groupId>com.alibaba.otter</groupId>
                <artifactId>canal.client</artifactId>
                <version>${canal.client.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

2.2 子项目(service)

在父工程下创建service模块,前面父工程中只是定义了依赖管理即dependencyManagement,依赖的使用在service使用。service下面也有许多子模块,所以也要变为pom项目。即 节点后面添加 pom类型。

使用依赖,并将前四个依赖注释掉,因为现在还不是用,如果不注释会报错。

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

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

         <dependency>
             <groupId>org.apache.poi</groupId>
             <artifactId>poi-ooxml</artifactId>
         </dependency>
        <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
        </dependency>

        <!--httpclient-->
        <dependency>
             <groupId>org.apache.httpcomponents</groupId>
             <artifactId>httpclient</artifactId>
             </dependency>
         <!--commons-io-->
         <dependency>
             <groupId>commons-io</groupId>
             <artifactId>commons-io</artifactId>
             </dependency>
         <!--gson-->
         <dependency>
             <groupId>com.google.code.gson</groupId>
             <artifactId>gson</artifactId>
         </dependency>

         <dependency>
             <groupId>junit</groupId>
             <artifactId>junit</artifactId>
             <version>4.12</version>
         </dependency>
    </dependencies>

3.service-edu模块

在service中创建service-edu模块
配置文件

server:
  port: 8001

# 服务名
spring:
  application:
    name:  service-edu

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

  # mysql数据库连接
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/guli?serverTimezone=GMT%2B8
    username: root
    password: 747699
    
#mybatis日志
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

3.1 代码生成器

可以利用代码生成器为数据库中的某一张自动生成对应的entity、Controller、service、mapper类。
可以将以下代码在test中运行。
核心部分:

  • setOutputDir:生成Controller、service、mapper层代码的存放位置;
  • setUrl:数据库连接;
  • setDriverName:数据库驱动;
  • setUsername:用户名;
  • setPassword:密码;
  • PackageConfig:包配置;
  • setInclude:表名。
    1、输出路径
    2、逐渐策略(根据数据库表字段设置)
    3、数据库连接字段

代码生成器依赖:

<dependency>
    <groupId>org.apache.velocity</groupId>
    <artifactId>velocity-engine-core</artifactId>
</dependency>

代码生成器代码:

package com.atguigu.demo;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import org.junit.Test;


public class CodeGenerator {

    @Test
    public void run() {

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

        // 2、全局配置
        GlobalConfig gc = new GlobalConfig();
        String projectPath = System.getProperty("user.dir");
        gc.setOutputDir(projectPath + "/src/main/java");
        gc.setAuthor("mfx");
        gc.setOpen(false); //生成后是否打开资源管理器
        gc.setFileOverride(false); //重新生成时文件是否覆盖
        gc.setServiceName("%sService");	//去掉Service接口的首字母I
        gc.setIdType(IdType.ID_WORKER_STR); //主键策略
        gc.setDateType(DateType.ONLY_DATE);//定义生成的实体类中日期类型
        gc.setSwagger2(true);//开启Swagger2模式

        mpg.setGlobalConfig(gc);

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

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

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

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

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

        mpg.setStrategy(strategy);


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

实现效果:

谷粒商城笔记_第16张图片

3.2 讲师列表(mapper注入)

编写controller接口

@RestController
@RequestMapping("/edu/teacher")
public class TeacherController {

    @Autowired
    private TeacherService teacherService;

    @GetMapping("findAll")
    public List<Teacher> findAll(){
        return teacherService.list(null);
    }
}

由于我们是创建的maven项目,所以还没有启动类,所以要创建一个启动类

@SpringBootApplication

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

如果此时执行启动类,会出现bean工厂中找不到teacherService
原因:teacherService需要使用teacherMapper,代码生成器生成的mapper接口没有注入到容器中。
解决方法:

  1. 在每个mapper接口类上加入@Mapper注解;
  2. 创建配置类,通过@MapperScan注解扫描注入所有mapper
@Configuration
@MapperScan("com.atguigu.edu.mapper")
public class EduConfig {
}

3.3 json时间格式

时间格式问题:返回的数据中对于修改时间字段信息显示如下:"gmtModified":"2019-11-12T05:36:36.000+0000",这是默认的标准时间格式,但是,这与我们正常的时间格式不同,只需要在配置文件中配置一下json时间格式即可。

spring:
	 jackson:
	    date-format: yyyy-MM-dd HH:mm:ss
	    time-zone: GMT+8

修改后的时间显示"gmtModified":"2019-11-12 13:36:36"

3.4 逻辑删除讲师

前面第一章节mp中已经讲过了逻辑删除的步骤。所以这里就不贴代码了。
步骤:配置文件配置规则、@TableLogic、controller接口

4.整合swagger

由于浏览器只支持get post请求测试,所以如果有其它请求时,无法通过浏览器测试,如果使用postman测试,还需要下载软件。所以可以通过swagger进行接口测试。

swagger配置流程

  • 在父工程下建立一个common模块;
  • common模块下建立一个services-base模块;
  • services-base模块中定义一个swagger配置类;
  • 由于我们要在教师所在模块service-edu使用,所以我们需要先将services-base引入到service模块,由于service-eduservice的子模块,所以service-edu可以使用该swagger配置类。补充:service是我们书写具体业务的模块;
  • 为了使得swagger配置类生效,我们还需要在启动类上加一个组件扫描,使得从其他模块导入的配置类可以成功配置

具体实现
(1)创建common模块,并导入swagger相关依赖,由于common也是父模块,所以加入 pom
common pom文件

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

(2)创建子模块service-base,并创建swagger配置类所在位置
Swagger2Config

package com.atguigu.ggkt.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

/**
 * @Author: mfx
 * @Description:
 * @Date: Created in 20:08 2022/7/5
 */

@Configuration
@EnableSwagger2
public class Swagger2Config {
    @Bean
    public Docket webApiConfig(){
        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("ggkt")
                .apiInfo(webApiInfo())
                .select()
                //只显示api路径下的页面
                //.paths(Predicates.and(PathSelectors.regex("/api/.*")))
                .build();
    }

    private ApiInfo webApiInfo(){
        return new ApiInfoBuilder()
                .title("网站-API文档")
                .description("本文档描述了网站微服务接口定义")
                .version("1.0")
                .contact(new Contact("atguigu", "http://atguigu.com", "atguigu.com"))
                .build();
    }
}

(3)在service中引入该模块

<!-- 引入common内的service-base模块,service-base中引入了common-utils模块-->
        <dependency>
            <groupId>com.atguigu</groupId>
            <artifactId>service-base</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

(4)在ServiceVod模块启动类上加入注解@ComponentScan("com.atguigu")
(5)访问swagger文档,swagger访问方式为http://localhost:端口号/swagger-ui.html

谷粒商城笔记_第17张图片

不同工程引入问题

上面edu模块引入了commons中swagger,由于跨工程,需要导入工程依赖,还需要在启动类上添加注解@ComponentScan("com.atguigu")

swagger注解

swagger还提供了多个注解,@ApiOperation、@ApiParam,分被用于描述方法和方法参数
例如

    @ApiOperation("查询所有讲师")
    @GetMapping ("/findAll")
    public List<Teacher> findAllTeacher(){
        return teacherService.list();
    }

    @DeleteMapping("remove/{id}")
    public boolean removeById(@ApiParam(name = "id", value="ID", required = true) @PathVariable Long id){
        boolean isSuccess = teacherService.removeById(id);
        return isSuccess;
    }

5.统一返回类型(swagger新注解)

@ApiModel,@ApiModelProperty两个注解分别加在类名和类属性名上。

统一返回类型Result:

package com.atguigu.utils;

import com.sun.org.apache.xpath.internal.operations.Bool;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.util.HashMap;
import java.util.Map;

/**
 * @Author: mfx
 * @Description:
 * @Date: Created in 19:32 2022/8/16
 */
@Data
public class Result {
    @ApiModelProperty(value = "是否成功")
    private Boolean success;

    @ApiModelProperty(value = "状态码")
    private Integer code;

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


    // 构造函数私有化,保证只能使用静态方法
    private Result(){}

    public static Result ok(){
        Result res = new Result();
        res.setCode(20000);
        res.setSuccess(true);
        res.setMessage("成功");

        return res;
    }

    public static Result error(){
        Result result = new Result();
        result.setCode(20001);
        result.setSuccess(false);
        result.setMessage("失败");

        return result;
    }

    public Result success(Boolean success){
        this.success = success;
        return this;
    }

    public Result message(String msg){
        this.message = msg;
        return this;
    }

    public Result code(Integer code){
        this.code = code;
        return this;
    }

    public Result data(String str, Object obj){
        this.data.put(str, obj);
        return this;
    }

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


上面代码中有一个知识点,类方法通过return this,可以实现链式编程。
例如:Result.ok(..).message(...).code(..)

在网页访问swagger时出现错误
swagger报错java.lang.NumberFormatException: For input string: ““

解决方法:
我使用的是io.springfox:springfox-swagger2:2.9.2的版本,而该版本依赖了swagger-models的1.5.20版本(会导致报此错),深挖原因是1.5.20版本中的example只判断了是否为null,没有判断是否为空串;1.5.21版本对两者都进行了判断。

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

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
        </dependency>
        <dependency>
            <groupId>io.swagger</groupId>
            <artifactId>swagger-annotations</artifactId>
            <version>1.5.21</version>
        </dependency>
        <dependency>
            <groupId>io.swagger</groupId>
            <artifactId>swagger-models</artifactId>
            <version>1.5.21</version>
        </dependency>

6.get post传参

注意:只要是路径传参,后端必须@PathVariable接收,data传参必须@RequestBody接收。不然后端接收到的值为空。使用@RequestBody时应设置参数可以为空,即@RequestBody(require=false)
get post是常用的两种传参方式,参数存放的位置有两个:params,data。
如果使用data传参,请一定使用post方式,因为get可能不能接受@RequestBody的参数
params传参即将参数放在请求路径中,get,post都可以使用,使用方法相同。

对应的接受方式:
(1)基础类型接收,名字对应即可;

// method
const params = {
    id: '123456789',
    name: '张三'
}
test(params)

// api
export function test (params) {
  return axios({
    url: url,
    method: 'GET',
    params: params
  })
}

// 后台
@PostMapping("/test")
public Result test(Long id, String name) {
    return Res.ok();
}

(2)使用Map接收,需要添加 RequestParam 注解;

// method
const params = {
    id: '123456789',
    name: '张三'
}
test(params)

// api
export function test (params) {
  return axios({
    url: url,
    method: 'POST',
    params: params
  })
}

// 后台
@PostMapping("/test")
public Result test(@RequestParam Map<String, Object> map) {
    return Res.ok();
}

(3)使用实体类接收。

// 实体类
@Data
public class TestEntity {
    Long id;
    String name;
}

// method
const params = {
    id: '123456789',
    name: '张三'
}
test(params)

// api
export function test (params) {
  return axios({
    url: url,
    method: 'POST', 
    params: params
  })
}

// 后台
@PostMapping("/test")
public Result test(TestEntity testEntity) {
    return Res.ok();
}

(4)接收列表元素,需要@RequestParam


// method
const list= [a,b,c,d]
test(params)

// api
export function test (list) {
  return axios({
    url: url,
    method: 'GET', 
    params: list
  })
}

// 后台
@PostMapping("/test")
public Result test(@RequestParam("list") List<泛型> list) {
    return Res.ok();
}

data传参是将参数放在请求体里面,正常情况只有post可以使用。
对应的接收方式:
使用实体类接收

// 实体类
@Data
public class TestEntity {
    Long id;
    String name;
}

// method
const params = {
    id: '123456789',
    name: '张三'
}
test(params)

// api
export function test (params) {
  return axios({
    url: url,
    method: 'POST', 
    data: params
  })
}

@PostMapping("/test")
public Result test(@RequestBody TestEntity testEntity) {
    return Res.ok();
}

7.前端传递JSON数组

如果前端传送的json数组,后端应该使用List对象接受,因为是对象,所以要用@RequestBody注解

以批量删除讲师为例

 // 前端传递的参数是json数组[1,2,3...]
    @ApiOperation("批量删除讲师")
    @DeleteMapping("removeBatch")
    public Result removeBatch(@RequestBody List<Long> idList){
        teacherService.removeByIds(idList);
        return Result.ok(null);
    }

8.讲师条件分页查询

(1)创建条件类

@ApiModel(value = "teacher条件查询类")
@Data
public class TeacherQuery {
    private static final long serialVersionUID = 1L;
    @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)contorller

	@ApiOperation("条件分页查询讲师")
    @PostMapping("pageListByQuery/{current}/{limit}")
    public Result pageListByQuery(@PathVariable Long current, @PathVariable Long limit,
                                  @RequestBody(required = false) TeacherQuery teacherQuery)
    {
        Page<Teacher> page = new Page<>(current, limit);
        return teacherService.pageQuery(page,teacherQuery);
    }

(3)service

Result pageQuery(Page<Teacher> page, TeacherQuery teacherQuery);

(4)serviceImpl
注入teacherMapper时,编译器可能会提示没有这个bean的爆红,但是实际是有的,因为我们在配置类中通过MapperScan注入了所有的mapper,应该是编译器问题。

@Autowired
    TeacherMapper teacherMapper;

    @Override
    public Result pageQuery(Page<Teacher> page, TeacherQuery teacherQuery) {
        QueryWrapper<Teacher> wrapper = new QueryWrapper<>();
        wrapper.orderByAsc("sort");
        if(teacherQuery != null){
            String name = teacherQuery.getName();
            Integer level = teacherQuery.getLevel();
            String begin = teacherQuery.getBegin();
            String end = teacherQuery.getEnd();
            if(!StringUtils.isEmpty(name))
                wrapper.like("name",name);
            if(!StringUtils.isEmpty(level))
                wrapper.eq("level",level);
            if(!StringUtils.isEmpty(begin))
                wrapper.ge("gmt_create", begin);
            if(!StringUtils.isEmpty(end))
                wrapper.le("gmt_create", end);
        }
        System.out.println(teacherMapper);
        System.out.println(page);
        System.out.println(wrapper);
        IPage<Teacher> teacherIPage = teacherMapper.selectPage(page, wrapper);
        long total = teacherIPage.getTotal();
        List<Teacher> records = teacherIPage.getRecords();
        HashMap<String, Object> map = new HashMap<>();
        map.put("total", total);
        map.put("list", records);
        return Result.ok().data(map);
    }

(5)swagger测试
谷粒商城笔记_第18张图片
结果:
谷粒商城笔记_第19张图片

问题

在测试时,最初配置文件中的数据库连接没有设置数据库的字符编码,导致mp执行模糊查询时查不到结果,但是数据库使用相同sql则可以成功。
解决方法:
添加字符编码characterEncoding=utf8即可:
jdbc:mysql://localhost:3306/guli?characterEncoding=utf8&serverTimezone=GMT%2B8

9.添加讲师

添加讲师比较简单,唯一需要注意的是,要设置自动填充
自动填充在第一节有讲到,只需要@TableFiled注解和相关实体类。由于所有模块都会用到自动填充,所以在common模块下的service-base中创建自动填充的相关实体类,将其放在handler包中。

谷粒商城笔记_第20张图片

10.修改讲师

修改操作分为两步:

  1. 根据id查询;
  2. 修改数据。
@ApiOperation("根据id查询")
    @GetMapping("{id}")
    public Result getTeacher(@PathVariable String id){
        Teacher teacher = teacherService.getById(id);
        return Result.ok().data("item", teacher);
    }

    @ApiOperation("修改讲师信息")
    @PostMapping
    public Result updateById(@RequestBody Teacher teacher){
        boolean b = teacherService.updateById(teacher);
        return Result.ok();
    }

11.统一异常处理类

common工程下的service-base中创建except包,然后创建异常处理类。
由于异常类需要common-utils中的Result类,所以需要引入common-utils模块,又由于service模块之前即引入了common-utils,又引入了service-base模块,所以此时service可以修改为只需要引入service-base模块,因为service-base中包含commo-utils模块。原理是依赖传递
所有的异常处理函数都是放在统一异常处理类中,大都数成为全局异常处理类,为了防止和下面的全局异常处理冲突,暂且命名为统一异常处理类。

异常处理的顺序是,先看有没有特定的异常,如果没有才会全局异常处理。因为全局异常处理可以处理所有的异常,可以认为保底的异常处理手段。

@RestControllerAdvice//用于controller层异常捕获,并且返回类型为json类型
public class GlobalException {

}

后面所讲的全局异常、特定异常、自定义异常只是不同的异常处理函数,他们的区别只是异常类参数不同。
谷粒商城笔记_第21张图片

全局异常处理

  • @RestControllerAdvice,@ControllerAdvice用于标识为异常处理类,Rest返回类型为json类型。
  • @ExceptionHandler加在处理异常方法上,是该方法可以执行。
@RestControllerAdvice//用于controller层异常捕获,并且返回类型为json类型
public class GlobalException {

    @ExceptionHandler(Exception.class)
    public Result handler(Exception e){
        return Result.fail(null).message("全局异常处理");
    }
}

当异常处理函数和@ExceptionHandler的参数为Exception时,代表全局异常处理,因为Exception是所有异常的父类。所以后面特定异常处理只需要修改参数即可。

特定异常处理

特定异常处理只需要修改处理异常函数和处理异常注解的参数即可

// 特定异常处理示例:ArithmeticException
    @ExceptionHandler(ArithmeticException.class)
    public Result handler(ArithmeticException e){
        return Result.fail(null).message("执行ArithmeticException异常处理");
    }

自定义异常处理

自定义异常需要手动抛出。
谷粒商城笔记_第22张图片
(1)创建自定义异常类。
先看一下官方的异常类如何定义的:
谷粒商城笔记_第23张图片
由上图可知,创建自定义异常类,需要继承RuntimeException类。创建自己的异常类 GgktException

@Data
@AllArgsConstructor
@NoArgsConstructor
public class GgktException extends RuntimeException{
    private Integer code;
    private String msg;
}

(2)在自定义类中创建属性,我们已经创建了两个属性code、msg
(3)在统一异常处理类中添加自定义异常处理方法。

 // 自定义异常处理 GgktException
    @ExceptionHandler(GgktException.class)
    public Result handler(GgktException e){
        e.printStackTrace();
        return Result.fail(null).code(e.getCode()).message(e.getMsg());
    }

(4)手动抛出自定义异常。
我们假设在查询所有讲师接口中尝试捕获某个异常,然后手动抛出。

  public Result findAllTeacher(){
        try{
            int i  = 10/ 0;
        }catch(Exception e){
            throw new GgktException(201, "执行自定义异常处理");
        }
        return Result.ok(teacherService.list()).message("查询数据成功");
    }

throw new GgktException(201, "执行自定义异常处理");就是手动抛出异常,此句执行后,就会被我们自定义的异常处理函数捕获,然后返回异常信息。

12.统一日志处理

配置日志级别

日志记录器(Logger)的行为是分等级的。如下表所示:
分为:OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL
默认情况下,spring boot从控制台打印出来的日志级别只有INFO及以上级别,可以配置日志级别
配置文件设置

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

日志输出到文件

默认的日志只输出在控制台,无法输出到文件,logback可以实现日志输出到文件。
(1)删除配置文件中有关日志的代码,包括logging和mp的logging。否则会出错。
(2)resources 中创建 logback-spring.xml(文件名字固定),因为我们目前使用的是service-edu项目,所以在该项目下的resources中创建。(这个配置文件使用时直接拿来用,只需要按需修改相关字段即可


    <configuration scan="true" scanPeriod="10 seconds">
    
    
    
    
    <contextName>logbackcontextName>
    

    
    <property name="log.path" value="D:/Develop/IDEA/work/guli_log/edu" />
    
    
    
    
    
    
    
    <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">
        
        
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>INFOlevel>
        filter> <encoder> <Pattern>${CONSOLE_LOG_PATTERN}Pattern>
        
        <charset>UTF-8charset>
    encoder>
    appender>
    
    
    <appender name="INFO_FILE"
              class="ch.qos.logback.core.rolling.RollingFileAppender">
        
        <file>${log.path}/log_info.logfile>
        
        <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level
            %logger{50} - %msg%npattern> <charset>UTF-8charset>
        encoder>
        
        <rollingPolicy
                class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            

            <fileNamePattern>${log.path}/info/log-info-%d{yyyy-MM�dd}.%i.logfileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy
                class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MBmaxFileSize>
            timeBasedFileNamingAndTriggeringPolicy>
            
            <maxHistory>15maxHistory>
        rollingPolicy>
        
        <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>INFOlevel> <onMatch>ACCEPTonMatch> <onMismatch>DENYonMismatch>
        filter>
    appender>
    
    <appender name="WARN_FILE"
              class="ch.qos.logback.core.rolling.RollingFileAppender">
        
        <file>${log.path}/log_warn.logfile>
        
        <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level
            %logger{50} - %msg%npattern> <charset>UTF-8charset> 
        encoder>
        
        <rollingPolicy
                class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>${log.path}/warn/log-warn-%d{yyyy-MM�dd}.%i.logfileNamePattern> <timeBasedFileNamingAndTriggeringPolicy
                class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <maxFileSize>100MBmaxFileSize>
        timeBasedFileNamingAndTriggeringPolicy>
            
            <maxHistory>15maxHistory>
        rollingPolicy>
        
        <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>warnlevel> <onMatch>ACCEPTonMatch> <onMismatch>DENYonMismatch>
        filter>

    appender>
    
    <appender name="ERROR_FILE"
              class="ch.qos.logback.core.rolling.RollingFileAppender">
        
        <file>${log.path}/log_error.logfile>
        
        <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level
            %logger{50} - %msg%npattern> <charset>UTF-8charset> 
        encoder>
        
        <rollingPolicy
                class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>${log.path}/error/log-error-%d{yyyy-MM�dd}.%i.logfileNamePattern> <timeBasedFileNamingAndTriggeringPolicy
                class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <maxFileSize>100MBmaxFileSize>
        timeBasedFileNamingAndTriggeringPolicy>
            
            <maxHistory>15maxHistory>
        rollingPolicy>
        
        <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>ERRORlevel> <onMatch>ACCEPTonMatch> <onMismatch>DENYonMismatch>
        filter>
    appender>
    
    
    
    <springProfile name="dev">
        
        <logger name="com.guli" level="INFO" />
        
        <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>

控制台日志输出

谷粒商城笔记_第24张图片

日志文件输出路径即对应输出文件。

谷粒商城笔记_第25张图片

将错误日志输出到文件

java中错误一般都会引发异常,当我们出现异常时,后台一般只会抛出打印异常,并不会出现错误日志。如下图所示,没有错误日志。
在这里插入图片描述
如果想要将错误日志打印并输出到文件,下面两步操作。
(1)异常处理类上加@Slf4j注解
(2)log.error(e.message)输出错误日志,由于我们设置了将日志存储文件,所以日志只要输出就会存储到文件中。

相关代码:

package com.atguigu.except;

import com.atguigu.utils.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

/**
 * @Author: mfx
 * @Description:
 * @Date: Created in 14:22 2022/8/19
 */
@Slf4j
@RestControllerAdvice
public class GlobalException {
    // 全局异常处理
    @ExceptionHandler(Exception.class)
    public Result handler(Exception e){
        log.error(e.getMessage());
        return Result.error().message("执行了全局异常处理");
    }

    // 特定异常处理
    @ExceptionHandler(ArithmeticException.class)
    public Result handler(ArithmeticException e){
        log.error(e.getMessage());
        return Result.error().message("执行了特定异常ArithmeticException处理");
    }

    //自定义异常处理
    @ExceptionHandler(MyException.class)
    public Result handler(MyException e){
        e.printStackTrace();
        log.error(e.getMsg());
        return Result.error().code(e.getCode()).message(e.getMsg());
    }
}

测试:
自定义异常不仅抛出,而且还打印了错误日志,并且成功输入到了文件。

在这里插入图片描述
错误日志文件

谷粒商城笔记_第26张图片

13.ES6基本语法

let,var使用

let是ES6声明变量方式,var是ES5声明变量方式。

var 声明的变量没有局部作用域,let 声明的变量 有局部作用域

{
var a = 0
let b = 1
}
console.log(a)  // 0
console.log(b)  // ReferenceError: b is not defined

var 可以声明多次,let 只能声明一次

var m = 1
var m = 2
let n = 3
let n = 4
console.log(m)  // 2
console.log(n)  // Identifier 'n' has already been declared

解构赋值

解构赋值是对赋值运算符的扩展。他是一种针对数组或者对象进行模式匹配,然后对其中的变量进行赋值。在代码书写上简洁且易读,语义更加清晰明了;也方便了复杂对象中数据字段获取。

(1)数组解构赋值

 	// 传统
    let a = 1, b =2, c=3
    console.log(a,b,c)
    // ES6
    let [x, y, z] = [2, 4, 6]
    console.log(x,y,z)

(2)对象解构赋值

//2、对象解构
let user = {name: 'Helen', age: 18}
// 传统
let name1 = user.name
let age1 = user.age
console.log(name1, age1)
// ES6
let { name, age } =  user//注意:结构的变量必须是user中的属性
console.log(name, age)

解构赋值思想:将传统的多个语句赋值修改为一行代码的语句赋值。

模板字符串

模板字符串相当于加强版的字符串,用反引号 `,除了作为普通字符串,还可以在字符串中加入变量和表达式。

字符串插入变量和表达式。变量名写在 ${} 中,${} 中可以放入 JavaScript 表达式。

let name = "Mike"
let age = 27
let info = `My Name is ${name},I am ${age+1} years old next year.`
console.log(info)
// My Name is Mike,I am 28 years old next year.

字符串中调用函数

function f(){
    return "have fun!"
}
let string2 = `Game start,${f()}`
console.log(string2);  // Game start,have fun!

声明对象

在js中创建对象时,写key时可以不加双引号

 	let name = 'mfx'
    let age = 22
    // 传统写法
    let person = {name:name,age:age}
    console.log(person)
    // ES6
    let persons = {name,age}
    console.log(persons)

对象拓展运算符

拓展运算符...用于取出参数对象所有可遍历属性然后拷贝到当前对象。

 	// 1、对象复制
    let person = {name:'mfx',age:22}
    let person1 = {...person}
    console.log(person1)
    // 2、对象合并
    let persons = {grade:90,color:'yellow'}
    let person2 = {...person,...persons}
    console.log(person2)

输出结果
在这里插入图片描述

首次创建vue对象

正常开发中都是直接创建vue文件,然后使用vue框架进行编程。但是要理解vue的原理,还是要学会如何创建vue对象。因为创建vue文件也是创建的一个vue对象。
(1)创建vueDemo.html文件,并使用快捷键!+Tab插入html模板
(2)引入vue.js,我将vue.js放在了同级目录下

<body>
	<script src="vue.js">script>
body>

(3)在script中创建vue对象,new Vue({...})

<script>
    new Vue({
      el: '#app',
      data: {
        message: 'hello vue'
      }
    })
</script>

(4)创建div标签,并将id复制为Vue对象中el的值即app。名通过{{ }}获取vue对象的变量。全部代码如下:

doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport"
        content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
  >
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Documenttitle>
head>
<body>
<div id="app">
  {{message}}
div>
  <script src = "vue.js">script>
  <script>
    new Vue({
      el: '#app',
      data: {
        message: 'hello vue'
      }
    })
  script>
body>
html>

修饰符

vue修饰符详解
.prevent为例,.prevent表示 阻止标签默认行为
这里举一个使用例子方便理解:使用@click.prevent阻止超链接点击跳转事件。
写一个不自动跳转的超链接,代码如下:

<a href='https://www.baidu.com/' @click.prevent='click1()'>可能跳转到百度a>
click1(){
	alert('没想到吧!')
}

这样,就不会跳转到百度了,阻止了浏览器的默认行为。

es6模块化开发(export,export default)

正常情况下,一个js中方法是私有的,是不可以在其他文件中使用的。为了解决该问题,es6可以使用exportexport default暴露文件中的某个方法,或对象,以供其它文件使用。

例如:
下面文件创建了vuex,我们想在其他文件中使用vuex,我们可以通过export default暴露该实例,在其他文件中直接import该文件路径即可。
注意:export default{要暴露的内容}

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

//对外暴露store的一个实例
export default new Vuex.Store({
    state:{},
    mutations:{},
    actions:{},
    
})

exportexport default使用

export使用时,每个对外暴露的实例对应一个export。如下所示,对外暴露一个方法,一个变量,所以需要两个export

test.js文件

export function list(){
	....
}
export let a = 10;

export的引用
方式一

import {a, list} from '...js'

方式二

// 这里的test要和js文件名对应
import * as test from 'test.js'
test.a     // 调用变量
test.list  // 调用函数

注意:export 不能直接写成这样子

export{
    "":""    // 这样会报错
    ....
}

export default使用
test.js文件

export default{
	a: 10,
	list: () => {...}
}

export default引用

import test from 'test.js'

注意export default不能类似这样的写 一样也是会报错的

export default let a=10   

总结:export和export default的定义方式不同,都不能使用对方的定义方法,引入方法也不同。export default更简洁一些,建议使用。一个js文件是可以有多个 export,但是一个js文件中只能有一个export default

Webpack

Webpack 是一个前端资源加载/打包工具。它将根据模块的依赖关系进行静态分析,然后将这些模块按照指定的规则生成对应的静态资源。
从图中我们可以看出,Webpack 可以将多种静态资源 js、css、less 转换成一个静态文件,减少了页面的请求。
谷粒商城笔记_第27张图片
webpack 安装
npm install -g webpack webpack-cli

webpack打包js文件

(1)创建webpack文件夹,并创建多个js文件,用于打包测试
谷粒商城笔记_第28张图片
common.js

exports.info = function(str) {
  document.write(str)// 浏览器输出
}

utils.js

exports.add = function(a, b) {
  return a + b
}

main.js

import common from './common'
import utils from './utils'

common.info('hello common' + utils.add(1, 2))

(2)创建webpack.config.js文件

const path = require("path"); //Node.js内置模块
module.exports = {
	entry: './src/main.js', //配置入口文件
	output: {
		path: path.resolve(__dirname, './dist'), //输出路径,__dirname:当前文件所在路径
		filename: 'bundle.js' //输出文件
	}
}

(3)命令打包操作
webpack #有黄色警告
webpack --mode=development #没有警告
测试:进入webpackDeno文件终端目录执行webpack命令,成功打包,根据webpack.config.js文件设置,dist是打包好的目录,bundle.js是输出文件
谷粒商城笔记_第29张图片
(4)测试
创建一个html文件,引入打包好的js文件,会执行main.js中的操作
test.html

成功输出main.js中内容:hello common3

打包css文件

(1)创建css文件style.css

body{
  background: red;
}

(2)main.js中引入css文件

require('./style.css')

(3)安装style-loader和css-loader

npm install --save-dev style-loader css-loader

(4)修改打包配置文件webpack.config.js

const path = require('path') // Node.js内置模块
module.exports = {
  entry: './src/main.js', // 配置入口文件
  output: {
    path: path.resolve(__dirname, './dist'), // 输出路径,__dirname:当前文件所在路径
    filename: 'bundle.js' // 输出文件
  },
  module: {
    rules: [
      {
        test: /\.css$/,	// 	打包规则应用到css结尾的文件上
        use: ['style-loader', 'css-loader']
      }
    ]
  }
}

(5)测试,运行tets.html文件
输出还是为hello common3,由于我们设置了css背景色为红色,所以这里的背景色为红色。
谷粒商城笔记_第30张图片
Module build failed: TypeError: this.getOptions is not a function at Object.loader错误:
安装完style-loader,css-loader后再打包时出现上面错误,原因是style-loader,css-loader版本过高。通过下面代码降低版本。

npm install [email protected] --save-dev
npm install [email protected]  --save-dev

总结

上面是使用webpack打包,实际上我们是可以通过使用vue-cli脚手架的npm run build打包的。

14.前端模板文件分析

我们使用的前端模板是老版本,和视频中的一样,可能目录结构和新版本有所不同。

src目录

谷粒商城笔记_第31张图片

前端入口文件

index.html

DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>vue-admin-templatetitle>
  head>
  <body>
    <div id="app">div>
    
  body>
html>

main.js

import Vue from 'vue'

import 'normalize.css/normalize.css' // A modern alternative to CSS resets

import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import locale from 'element-ui/lib/locale/lang/en' // lang i18n

import '@/styles/index.scss' // global css

import App from './App'
import router from './router'
import store from './store'

import '@/icons' // icon
import '@/permission' // permission control

Vue.use(ElementUI, { locale })

Vue.config.productionTip = false

new Vue({
  el: '#app',
  router,
  store,
  render: h => h(App)
})

config目录

谷粒商城笔记_第32张图片
index.js是一些配置,包括端口号的设置等。dev,prod是两个运行环境,以dev为例
谷粒商城笔记_第33张图片
后面会将base_api更换为后端的地址。

15.登录(跨域)

vue前端功能实现过程

  1. 创建路由和vue页面
  2. 定义api
  3. 前端发出请求获取数据
  4. 数据展示

登录改造本地

(1)dev.env.js文件
谷粒商城笔记_第34张图片
将上面的base_api改为后端地址:http://localhost:8001,配置文件一旦重启,必须重启项目。
在这里插入图片描述

(2)vuex中的user.js中actions涉及两个登陆相关函数,Login,GetInfo。
根据代码中的信息,可以确定后断两个接口:登录和获取用户信息,登录接口返回token,用户信息接口返回角色、名称、头像。

actions: {
    // 登录
    Login({ commit }, userInfo) {
      const username = userInfo.username.trim()
      return new Promise((resolve, reject) => {
        login(username, userInfo.password).then(response => {
          const data = response.data
          setToken(data.token)
          commit('SET_TOKEN', data.token)
          resolve()
        }).catch(error => {
          reject(error)
        })
      })
    },

    // 获取用户信息
    GetInfo({ commit, state }) {
      return new Promise((resolve, reject) => {
        getInfo(state.token).then(response => {
          const data = response.data
          if (data.roles && data.roles.length > 0) { // 验证返回的roles是否是一个非空数组
            commit('SET_ROLES', data.roles)
          } else {
            reject('getInfo: roles must be a non-null array !')
          }
          commit('SET_NAME', data.name)
          commit('SET_AVATAR', data.avatar)
          resolve(response)
        }).catch(error => {
          reject(error)
        })
      })
    }
    }

(3)根据(2)中分析创建后端接口
此时创建的后端接口还不涉及数据库,只是返回给前端某个固定的自定义值,用户测试登陆改造是否成功

@RestController
@RequestMapping("/eduservice/user")
public class LoginController {

    @PostMapping("login")
    public Result login(){
        return Result.ok().data("token","admin");
    }

    @GetMapping("info")
    public Result info(){
        return Result.ok().data("roles","[admin]").data("name","admin")
        .data("avatar","https://guli-file-190513.oss-cn-beijing.aliyuncs.com/avatar/default.jpg");
    }
}

(4)将前端api中login、getInfo的url修改为后端定义的接口路径
'/eduservice/user/login','/eduservice/user/info'
将user.js中的登录请求与后端接口对应/admin/vod/user/login

export function login(data) {
  return request({
    url: '/admin/vod/user/login',
    method: 'post',
    data
  })
}

(5)跨域解决

跨域原因:如果出现协议、ip地址、端口号任意一个不一样就会出现跨域问题。
解决
在contorller类上加入@CrossOrigin解决跨域问题。

(6)前端登陆测试

登陆成功

谷粒商城笔记_第35张图片
获取用户信息成功

谷粒商城笔记_第36张图片
成功进入后台首页

谷粒商城笔记_第37张图片

解决跨域问题

解决跨域方法:前端、后端、gateway。
目前先使用后端解决跨域。
后端解决跨域方法也有多种:第一种是在所有controller类上加注解@CrossOrigin
所以目前先使用该方法。

16.路由相关

路由知识

vue模板项目路由示例

import Layout from '@/layout'
export const constantRoutes = [
  {
    path: '/login',
    component: () => import('@/views/login/index'),
    hidden: true
  },

  {
    path: '/404',
    component: () => import('@/views/404'),
    hidden: true
  },

  {
    path: '/',
    component: Layout,
    redirect: '/dashboard',
    children: [{
      path: 'dashboard',
      name: 'Dashboard',
      component: () => import('@/views/dashboard/index'),
      meta: { title: 'Dashboard', icon: 'dashboard' }
    }]
  },

  {
    path: '/example',
    component: Layout,
    redirect: '/example/table',
    name: 'Example',
    meta: { title: 'Example', icon: 'el-icon-s-help' },
    children: [
      {
        path: 'table',
        name: 'Table',
        component: () => import('@/views/table/index'),
        meta: { title: 'Table', icon: 'table' }
      },
      {
        path: 'tree',
        name: 'Tree',
        component: () => import('@/views/tree/index'),
        meta: { title: 'Tree', icon: 'tree' }
      }
    ]
  }
 }

根据上面代码可以发现每一个一级路由的component都是Layout,这样可以保证每个一级路由的子路由都可以在Layoutview中显示。

component: Layout解释:
component:每一个路由都会对应一个路由组件即vue页面,由component赋值,这里的Layout是整个后台系统的布局,并且在route的index.js文件中已经引入了import Layout from '@/layout'
layout组件结构
谷粒商城笔记_第38张图片
layout 的index.js
可以看到就是后台的整体框架,分为左右结构

<template>
  <div :class="classObj" class="app-wrapper">
    <div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
    <sidebar class="sidebar-container" />
    <div class="main-container">
      <div :class="{'fixed-header':fixedHeader}">
        <navbar />
      </div>
      <app-main />
    </div>
  </div>
</template>

如下图所:
谷粒商城笔记_第39张图片

创建教师路由

所以,我们需要添加教师管理的路由,可以直接仿照example路由。在example路由下面加入

{
    path: '/vod',
    component: Layout,
    redirect: '/vod/teacher/list',
    name: 'vod',
    meta: { title: '讲师管理', icon: 'el-icon-s-help' },
    children: [
      {
        path: 'teacher/list',
        name: 'TeacherList',
        component: () => import('@/views/table/index'),
        meta: { title: '讲师列表', icon: 'table' }
      },
      {
        path: 'teacher/create',
        name: 'TeacherCreate',
        component: () => import('@/views/tree/index'),
        meta: { title: '添加讲师', icon: 'tree' }
      }
    ]
  },

效果展示
谷粒商城笔记_第40张图片

17.前端

讲师列表(CRUD+element-ui)

路由及页面初始化

  1. 创建讲师路由

    {
        path: '/teacher',
        component: Layout,
        redirect: '/teacher/list',
        name: 'Teacher',
        meta: { title: '讲师管理', icon: 'example' },
        children: [
          {
            path: 'list',
            name: 'List',
            component: () => import('@/views/edu/teacher/list'),
            meta: { title: '讲师列表', icon: 'table' }
          },
          {
            path: 'save',
            name: 'Save',
            component: () => import('@/views/edu/teacher/save'),
            meta: { title: '添加讲师', icon: 'tree' }
          }
        ]
      }
    
  2. 创建vue页面

    创建目录edu/teacher,并创建list.vue、save.vue页面,用于展示讲师列表和添加讲师。

  3. 创建讲师相关api文件teacher.js

    注意:js中创建函数有三种

    a(){}
    a: function(){}
    a: ()=>{}
    

    teacher.js

    import request from '../../utils/request'
    const api_name = '/edu/teacher'
    
    export default {
      // 条件分页查询讲师列表
      getTeacherListPage(current, limit, teacherQuery) {
        return request({
          url: `${api_name}/pageListByQuery/${current}/${limit}`,
          method: 'post',
          data: teacherQuery
        })
      }
    }
    

查询讲师

  1. 构建list.vue讲师列表页面
    (1)创建getList函数,调用前面封装好的条件获取讲师列表的api接口。

    	// 条件分页查询讲师列表
        getList() {
          teacher.getTeacherListPage(this.page, this.limit, this.teacherQuery)
            .then(res => {
              console.log(res.data)
            })
            .catch(error => {
              alert(error)
            })
        }
    

    (2)element-ui表格显示讲师数据

    <el-table
      :data="list"
      stripe
      style="width: 100%">
      <el-table-column
        label="序号"
        width="120"
        align="center">
        <template slot-scope="scope">
          {{ (page - 1) * limit + scope.$index + 1 }}
        </template>
      </el-table-column>
      <el-table-column
        prop="name"
        label="姓名"
        width="120"/>
      <el-table-column label="头衔">
        <template slot-scope="scope">
          {{ scope.row.level === 1 ? '高级讲师' : '首席讲师' }}
        </template>
      </el-table-column>
      <el-table-column prop="intro" label="资历" width="460"/>
      <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>
    

    temple模版(****)

    template(模版)在这里属于一个固定用法: