谷粒学苑项目前置知识

谷粒学苑项目前置知识

项目分为三篇
谷粒学苑项目前置知识
谷粒学苑项目前台界面
谷粒学苑后台管理系统

资料链接:谷粒学苑 提取码:p6er

视频教程:
尚硅谷-谷粒学苑

前端代码:前端代码
后端代码:后端代码

部署方法

项目模块介绍:

image-20220802160709035

项目技术点:

后端: SpringBoot,SpringCloud,MyBatisPlus,Spring SECURITY,redis,maven,easyExcel,jwt,OAuth2

前端: Vue,element-ui,axios,node.js

其他技术: 阿里云oss,阿里云视频点播服务,阿里云短信服务,微信支付和登录,docker,git,Jenkins


有关 MyBatis-Plus 的学习笔记:https://blog.youkuaiyun.com/aetawt/article/details/126105008

学习视频: BV1VP4y1c7j7


一、项目环境搭建

前后端分离概念:

image-20220804133412366

准备数据库表:

image-20220921111417790

完整项目工程结构总览 :

image-20220805133335619

创建父工程

父工程删除 src 文件夹,只留下 pom 文件。

image-20220804141428127

父工程依赖:

<?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.atguigu</groupId>
    <artifactId>guli_parent</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>guli_parent</name>

    <packaging>pom</packaging>

    <properties>
        <java.version>1.8</java.version>
        <guli.version>0.0.1-SNAPSHOT</guli.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.SR1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!--spring cloud alibaba 2.1.0.RELEASE-->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>2.2.2.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!--mybatis-plus 持久层-->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>3.5.1</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</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>

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

</project>

关于 aliyun-java-vod-upload 无法依赖爆红的原因:

由于 aliyun-java-vod-upload没有开源,所以不能直接在pom文件中直接引入依赖。

因此需要在官网下载 jar 包 :

下载链接

下载解压之后进入 lib 目录,进入 cmd 命令行

image-20220804171843331

安装 jar 包:

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

image-20220804172321694

第二种方法:

解压我的 maven 仓库,将 jar 包拷贝到你的仓库

链接:https://pan.baidu.com/s/1_RVsyZI8PlCI_paf0XOOVg
提取码:v4wf

创建 service 子模块 :

  1. 删除 src,只留下 pom 文件
  2. 由于还没有用到微服务,先将依赖注释掉,否则会报错
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>guli_parent</artifactId>
        <groupId>com.atguigu</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>service</artifactId>
    <packaging>pom</packaging>
    <modules>
        <module>service_edu</module>
    </modules>

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

        <!--SpringCloud ailibaba nacos -->
<!--        <dependency>
            <groupId>com.alibaba.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>
</project>

创建 service_edu 子子模块

依旧遵循五步走:

  • 建 model
  • 改 pom
    • pom 使用 service 的 pom,无需在修改
  • 写 yaml/properties
  • 启动类
  • 业务类

建 model:

image-20220805132439328

写 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/guli?serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=1234

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

我使用的是 MyBatisplusX 插件自动生成的代码,并没有使用老师提供的代码生成器。

  1. 首先在插件商店里下载 MyBatisX 插件:

    ​ Setting – Plugins – 搜索MyBatisX — 重启 IDEA

  2. image-20220805141019834

  3. image-20220805141124278

  4. image-20220805141220698

启动类:

@SpringBootApplication
@MapperScan("com.atguigu.demo.mapper")
public class EduTeacherApplication {
    public static void main(String[] args) {
        SpringApplication.run(EduTeacherApplication.class,args);
    }
}

业务类:

@RestController
@RequestMapping("eduservice/teacher")
public class EduTeacherController {

    @Autowired
    private EduTeacherService eduTeacherService;

    @GetMapping("/findAll")
    public List<EduTeacher> findAll(){
        return eduTeacherService.list();
    }
}

测试:

image-20220805143154988

修改返回数据的时间格式:

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

到此步基本的模块结构

image-20220805140522493

二、讲师管理模块后端

1.整合 Swagger

Swagger 是什么?

前后端分离开发模式中,api 文档是最好的沟通方式。

Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务

特点:

  • 及时性 (接口变更后,能够及时准确地通知相关前后端开发人员)
  • 规范性 (并且保证接口的规范性,如接口的地址,请求方式,参数及响应格式和错误信息)
  • 一致性 (接口信息一致,不会出现因开发人员拿到的文档版本不一致,而出现分歧)
  • 可测性 (直接在接口文档上进行测试,以方便理解业务)

在 guli_parent 下创建 common 子模块,在 common 模块下创建 service_base 子子模块,方便其他服务都能使用 Swagger

common 子模块pom 文件:

删除 src 文件夹,只留下 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>
        <!--swagger-->
        <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>

创建 service_base 模块:

  • 注意父模块和路径关系
  • 不用修改 pom ,使用 父模块 common 的依赖即可

image-20220805145915033

创建配置类 SwaggerConfig 配置 Swagger:

@Configuration
@EnableSwagger2
public class SwaggerConfig {

    @Bean
    public Docket webApiConfig(){

        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("webApi")
                .apiInfo(webApiInfo())
                .select()
                .paths(Predicates.not(PathSelectors.regex("/admin/.*")))
                .paths(Predicates.not(PathSelectors.regex("/error.*")))
                .build();

    }

    private ApiInfo webApiInfo(){

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

image-20220805153751200

在 service 模块中引入 service_base 依赖,使 service 模块下的所有模块都能够使用 Swagger:

        <dependency>
            <groupId>com.atguigu</groupId>
            <artifactId>service_base</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

配置启动类的包扫描:

由于我们在 service_base 模块中创建了 Swagger 配置类,需要在 service_edu 的中扫描 这个配置类

image-20220805151230149

image-20220805151512852

为什么不直接配置 com.atguigu.servicebase 包名呢?

这是因为 主启动类在 com.atguigu.demo 包下,ComponentScan 会覆盖 SpringBootApplication 的包扫描,因此会冲突。只好扫描他们的公共包了~~

访问:

http://localhost:8001/swagger-ui.html#

定义接口和参数说明:

  1. 定义在类上:@Api
  2. 定义在方法上:@ApiOperation
  3. 定义在参数上:@ApiParam

image-20220805154254156

总结:

  • 使用 Swagger 测试接口更加的方便,并且会生成测试API文档。
  • 使用 Swagger 步骤:
    • 在公共模块中增加依赖
    • 编写配置类
    • 在使用Swagger的模块中引入公共模块
    • 配置 公共模块的包扫描

2.统一返回数据格式

项目中我们会将响应封装成json返回,一般我们会将所有接口的数据格式统一, 使前端(iOS Android, Web)对数据的操作更一致、轻松。

一般情况下,统一返回数据格式没有固定的格式,只要能描述清楚返回的数据状态以及要返回的具体数据就可以。但是一般会包含 状态码、返回消息、数据 这几部分内容

统一定义返回结果:

{
"success": 布尔, //响应是否成功
"code": 数字, //响应码
"message": 字符串, //返回消息
"data": HashMap //返回数据,放在键值对中
}

在 common 模块下创建子模块 common_utils ,并 定义返回状态码以及返回数据格式

返回状态码:

public interface ResultCode {
    public static Integer SUCCESS = 20000; // 成功
    public static Integer ERROR = 20001; // 失败

}

返回数据格式:

@Data
public class R {
    @ApiModelProperty(value = "是否成功")
    private Boolean success;

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

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

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

    // 把 构造方法私有化的作用: 除本类外不允许 new 本类
    private R (){}

    // 成功的返回方法
    // 加上 static 可通过 类名. 的方式调用
    public static R ok(){
        R r = new R();
        r.setCode(ResultCode.SUCCESS);
        r.setMessage("成功");
        r.setSuccess(true);
        return  r;
    }

    // 失败的返回方法
    // 加上 static 可通过 类名. 的方式调用
    public static R error(){
        R r = new R();
        r.setCode(ResultCode.ERROR);
        r.setMessage("失败");
        r.setSuccess(false);
        return  r;
    }

    public R success(Boolean success){
        this.setSuccess(success);
        return this;
    }

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

谁调用 this ,this 就代表谁~~~

private R (){} 已经将 类 私有化了,外部无法 new R() 这个对象,只能通过 R. 的方式去调用方法。因此 this 代表当前对象,也就是 R 对象

在 service_edu 模块中使用公共返回结果:

@Api("讲师管理")
@RestController
@RequestMapping("eduservice/teacher")
public class EduTeacherController {

    @Autowired
    private EduTeacherService eduTeacherService;

    @ApiOperation("查询所有教师")
    @GetMapping("/findAll")
    public R findAll() {

        List<EduTeacher> list = eduTeacherService.list();
        return list.isEmpty() ? R.error() : R.ok().data("items", list);

    }

    // 逻辑删除
    @ApiOperation("逻辑删除教师")
    @DeleteMapping("{id}")
    public R removeTeacher(
            @ApiParam(name = "id", value = "讲师ID", required = true)
            @PathVariable Integer id) {

        boolean flag = eduTeacherService.removeById(id);
        return flag ? R.ok() : R.error();

    }
}

R.ok().data("items", list); : 这种叫做 链式调用,返回对象都一样

**common_utils 目录结构 : **

image-20220806174732022

结果测试:

image-20220805172914550

3.分页查询

在配置类里配置分页插件

@Configuration
public class MyConfig {

    // 开启分页功能
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor (){
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor() ;
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return  interceptor ;
    }
}

controller 层编写业务:

current : 当前页

size : 每一页显示内容条数

    @ApiOperation("普通分页功能")
    @GetMapping("/pageTeacher/{current}/{size}")
    public R pageTeacher(
            @PathVariable long current,
            @PathVariable long size
    ){
        // 创建分页对象
        Page<EduTeacher> page = new Page<>(current, size);
        eduTeacherService.page(page);
        // 返回总页数 和 分页数据
        return R.ok().data("rows",page.getRecords()).data("total",page.getTotal());
    }

多条件组合查询带分页

image-20220805203745975

比如前端的页面, 有四个查询条件,这四个查询条件任意组合进行查询。

一般的做法是将 前端的参数封装到一个vo对象中,然后将对象传到接口中

vo : 表示 View Object ,视图层对象,用于前端 --》 后端

DTO : 表示Data Transfer Object,数据传输对象,用于后端 – 》前端。

在 service_edu 模块中创建 vo 对象:

@Api("封装讲师查询条件对象")
@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;
}

目录结构:

image-20220805204253466

controller 层业务编写:

    @ApiOperation("多条件组合查询分页功能")
    @PostMapping("/pageQuery/{current}/{size}")
    public R pageQuery(
            @PathVariable(required = false) long current,
            @PathVariable(required = false) long size,
            @RequestBody(required = false) TeacherQuery teacherQuery
    ) {
        // 创建分页对象
        Page<EduTeacher> pageTeacher = new Page<>(current, size);
        QueryWrapper<EduTeacher> queryWrapper = new QueryWrapper<>();
        // 获取条件
        String name = teacherQuery.getName();
        Integer level = teacherQuery.getLevel();
        // begin ~ end 课程创建时间
        String begin = teacherQuery.getBegin();
        String end = teacherQuery.getEnd();

        // 使用 condition 组装条件
        // 这种链式调用,MyBatisPlus 会自动使用 and 连接 SQL 语句,如果想用 or 连接,使用 .or() 方法
        queryWrapper.like(StringUtils.isNotBlank(name), "name", name)
                .eq(level != null, "level", level)
                .ge(StringUtils.isNotBlank(begin), "gmt_create", begin)
                .le(StringUtils.isNotBlank(end), "gmt_create", end);


        eduTeacherService.page(pageTeacher, queryWrapper);
        // 返回总页数 和 分页数据
        return R.ok().data("rows", pageTeacher.getRecords()).data("total", pageTeacher.getTotal());
    }

required = false : 这个属性表示这个参数不是必须要传的

@RequestBody : 获取请求体中的数据,只有POST 请求才会请求体。

@ResponseBody 区别:

@ResponseBody 是 返回 JSON数据到前端

condition 参数介绍:

image-20220805213552496

在 QueryWrapper 里面每一个方法都有 condition 参数,是一个 boolean 值,它表示是否使用后边的参数。

比如:

queryWrapper.like(StringUtils.isNotBlank(name), "name", "王")

  • 如果 StringUtils.isNotBlank(name)为 true :
    • SQL 语句: select * from table where name like ‘%王%’ ;
  • 如果 StringUtils.isNotBlank(name)为 false :
    • SQL 语句: select * from table ;

使用Swagger 测试:

image-20220805214223157

4.增加讲师

首先将 gmt_create、gmt_modified 俩个字段设置自动填充

  • 在 EduTeacher实体类中 增加 @TableField 注解
  • 增加自动填充配置类

image-20220806135736812

在 service_base 模块下,com.atguigu.servicebase 包下,创建 handler 包,创建 配置类 :

@Api("自动填充配置类")
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        // fieldName : 是实体类的属性名,不是字段名
        this.strictInsertFill(metaObject,"gmtCreate", Date.class,new Date());
        this.strictInsertFill(metaObject,"gmtModified", Date.class,new Date());
    }

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

5.修改讲师

  • 根据 ID 进行查询【用于页面回显数据】
  • 根据 ID 修改讲师

根据ID查询:

    @ApiOperation("根据ID查询讲师")
    @GetMapping("/getTeacher/{id}")
    public R getTeacher(@PathVariable Long id){
        EduTeacher teacher = eduTeacherService.getById(id);

        return  teacher != null ? R.ok().data("teacher",teacher) : R.error().message("您查询的讲师不存在");
    }

根据 ID 修改 :

@ApiOperation("根据修改讲师")
    @PostMapping("/updateTeacher")
    public R updateTeacher( @RequestBody EduTeacher eduTeacher){

        return eduTeacherService.updateById(eduTeacher) ? R.ok() : R.error();
    }

测试时,gmt_create、gmt_modified 俩个字段不要写。

6.逻辑删除讲师

在逻辑删除字段 is_delete 属性上增加:@TableLogic 注解

image-20220812163354354

根据 ID 删除讲师:

    // 逻辑删除
    @ApiOperation("逻辑删除教师")
    @DeleteMapping("{id}")
    public R removeTeacher(
            @ApiParam(name = "id", value = "讲师ID", required = true)
            @PathVariable String id) {

        boolean flag = eduTeacherService.removeById(id);
        return flag ? R.ok() : R.error();

    }

7.全局统一异常处理

com.atguigu.servicebase.handler 包下,创建 GlobalExceptionHandler 处理全局异常:

@ControllerAdvice // 增强 Controller
public class GlobalExceptionHandler{

    @ExceptionHandler({Exception.class})
    @ResponseBody  //相应到浏览器上
    public R handlerException(Exception e) {
        e.printStackTrace();
        return R.error().message("全局处理异常~~~");
    }
}

service_base 中引入 common_utils 依赖,在 service 中可以把 common_utils 依赖删除。

演示结果:

image-20220806150512820

特定异常处理:

    @ApiOperation("特定异常处理~")
    @ExceptionHandler({ArithmeticException.class})
    @ResponseBody  //响应到浏览器上
    public R error(ArithmeticException e) {
        e.printStackTrace();
        return R.error().message("ArithmeticException异常处理~~~");
    }

8.自定义异常

  • 创建类自定义异常,继承 RunTimeException
  • 全局统一异常处理自定义的异常
  • 手动抛出自定义异常

自定义异常:

@Api("自定义异常~")
@Data
@AllArgsConstructor // 有参构造
@NoArgsConstructor // 无参构造
public class GuliException extends RuntimeException{

    // 状态码
    private Integer code ;
    // 错误信息
    private String msg ;
}

GlobalExceptionHandler 中处理自定义异常:

    @ApiOperation("处理自定义异常~~")
    @ExceptionHandler({GuliException.class})
    @ResponseBody  //响应到浏览器上
    public R error(GuliException e) {
        e.printStackTrace();
        return R.error().code(e.getCode()).message(e.getMsg());
    }

手动抛出异常:

由于是我们自定义的,系统不会抛出该异常,需要我们自己手动抛出 !

image-20220806175351479

service_base 目录结构:

image-20220806174516997

9.统一日志处理

日志记录器(Logger)的行为是分等级的。如下表所示:

分为:OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL

后边的日志输出包含前面等级的日志输出

默认情况下,spring boot从控制台打印出来的日志级别只有INFO及以上级别,可以配置日志级别

LogBack 和 log4j 类似,会比 log4j 有一些优点:https://blog.youkuaiyun.com/caisini_vc/article/details/48551287

  • 删除 application 配置文件中的日志配置

image-20220806215534123

  • 在 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上下文中。定义变量后,可以使“${}”来使用变量。 -->
    <property name="log.path" value="C:/18_gulixueyuan/log_info" />

    <!-- 彩色日志 -->
    <!-- 配置格式变量: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.guli" 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>

自动生成的日志文件

image-20220806215950669

将错误信息打印到日志中去:

  • 在全局处理异常的类 GlobalExceptionHandler 上 增加 @Slf4j 注解

image-20220806220357775

  • 使用 log.error() 方法将错误信息打印到日志中去

image-20220806220740021

测试结果:

image-20220806220818908

如果想有更多的错误信息,使用以下的工具类:

  • 在 common_utils 模块创建 ExceptionUtil 用来保存详细的错误信息
  • 写完记得刷新一下调用该模块的 pom 文件,更新一下。
public class ExceptionUtil {

	public static String getMessage(Exception e) {
		StringWriter sw = null;
		PrintWriter pw = null;
		try {
			sw = new StringWriter();
			pw = new PrintWriter(sw);
			// 将出错的栈信息输出到printWriter中
			e.printStackTrace(pw);
			pw.flush();
			sw.flush();
		} finally {
			if (sw != null) {
				try {
					sw.close();
				} catch (IOException e1) {
					e1.printStackTrace();
				}
			}
			if (pw != null) {
				pw.close();
			}
		}
		return sw.toString();
	}
}

测试结果:

image-20220806221442046

三、前端基础知识

1.VSCode 安装和使用

下载地址:Documentation for Visual Studio Code

安装中文界面:

  • 首先安装中文插件:Chinese (Simplified) Language Pack for Visual Studio Code
  • 右下角弹出是否重启vs,点击“yes”
  • 有些机器重启后如果界面没有变化,则 点击 左边栏Manage -> Command Paletet…【Ctrl+Shift+p】
  • 在搜索框中输入“configure display language”,回车
  • 打开locale.json文件,修改文件下的属性 “locale”:“zh-cn”

安装常用插件:

image-20220806222802608

2.ECMAScript 6 简介

ECMAScript 和 JavaScript 的关系是,前者是后者的规格,后者是前者的一种实现(另外的 ECMAScript 方言还有 Jscript 和 ActionScript)

3.ES6基本语法

3.1 如何定义一个变量

在 JS 中使用 var 在 ES6 中使用 let

俩个的区别:

  • var 没有局部作用域,let 有局部作用域
  • var 可以定义多次,let 只能定义一次
<script>
    
    { // 代码块

        var a = 1;
        let b = 2;

    }

    console.log(a)
	// // Uncaught ReferenceError: b is not defined
	// b 没有被声明
    console.log(b) 


    var m = 2 ;
    var m = 3 ;

    let n = 4 ;
    let n = 5 ;

    console.log(m)
	//Uncaught SyntaxError: Identifier 'n' has already been declared 
	// n 变量已经被定义过了
    console.log(n) 
</script>

3.2 定义一个常量

const a = xxx ;

  • 定义常量必须有初始值
  • 常量只能赋一次值
    const name = "李四"
    name = "王五"
    // ncaught TypeError: Assignment to constant variable.
    console.log(name)

    const sex ; 
    // Uncaught SyntaxError: Missing initializer in const declaration
    console.log(sex)

3.3 数组结构赋值

<script>
    // 传统方式
    var a = 1, b = 2, c = 3;
    console.log(a, b, c)


    // ES6 方式
    let [x, y, z] = [1, 2, 3];
    console.log(x, y, z) // 1,2,3 
    
</script>

3.4 对象结构赋值

<script>
    // 定义对象
    let user = { "name": "张三", "age": 20 };

    // 传统方式
    let name1 = user.name;
    let age1 = user.age
    console.log(name1 + "————" + age1)


    // ES6 方式
	// {} 里面的属性必须都是 user 对象的属性
    let {name,age} = user 
    console.log(name + "------" + age)

</script>

3.5 模板字符串

模板字符串 : ``

  • 支持换行
  • 可以镶嵌 ${ JavaScript代码 }
  • 可以调用函数 : ${ function() }
<script>

    // 1.模板字符串实现换行
    let v1 = `hello,world !!
    我是模板字符串~`
    console.log(v1)

    // 2. 模板字符串可以使用 ${} ,放入 Javascript 代码
    let name = "李四"
    let age = 20 ;
    let v2 = `my name is ${name}, my age is ${age+1} next year`

    console.log(v2)


    // 3. ${} 调用函数
    function a() {
        return "hello"
    }

    let v3 = `${a()},jack`
    console.log(v3)
</script>

3.6 定义方法

<script>

    // 定义一个对象 p
    const p = {
        // 传统方式
        method1: function () {
            console.log("method1")
        },

        // ES6 方式
        method2() {

        }
    }

    // 调用
    p.method1()
    p.method2()

</script>

3.7 对象拓展运算符

作用:

  • 拷贝对象
  • 合并对象
<script>
    // 1. 拷贝对象
    const user = { "name": "zhangsan", "age": 20 }

    const user1 = { ...user }
    console.log(user1)


    // 2.合并对象
    const book = { "bookName": "活着", "id": 1 }
    const book1 = { "bookName": "苏格拉底的申辩", "price": 100 }
    const books = { ...book, ...book1 }
    console.log(books)

</script>

当对象中有重复属性时,后面的会把前面的覆盖掉

image-20220807165334825

3.8 箭头函数

语法: 参数 => 函数体

如果有多个参数使用 () 括起来,用 , 分割

<script>
    // 传统方式
    let m1 = function (a) {
        return a
    }
    console.log(m1(1))

    // 箭头函数
    let m2 = a => a;
    console.log(m2(2));


    //  多个参数
    let m3 = function (a, b) {
        return a + b
    }
    console.log(m3(2, 2));

    // 传统方式
    let m4 = (a, b) => a + b
    console.log(m4(2, 3));
</script>

4.Vue基础

Vue是 动态构建用户界面的渐进式 JavaScript 框架

特点:

  1. 遵循 MVVM 模式
  2. 编码简洁, 体积小, 运行效率高, 适合移动/PC 端开发3. 它本身只关注 UI, 也可以引入其它第三方库开发项目

下载地址安装 — Vue.js (vuejs.org)

image-20220807175456965

使用开发版本可以使用 Vue 插件调试,插件直接在浏览器插件商店中搜索 Vue.js devtools 安装即可

image-20220807175557716

4.1 Vue 入门

  • 引入 Vue.js 文件
  • 编写案例
<body>
    <!-- 引入 vue 文件 -->
    <script src="js/vue.js"></script>
    <!-- 创建一个容器 -->
    <div id="app">
        <h1>{{message}}</h1>
    </div>

    <script>
        // 创建 Vue 实例对象
        new Vue({
            el: '#app',
            data: {
                message: 'hello,world!!'
            },
            // data的第二种写法
            // data() {
            //     return {
            //         message: 'hello,world!!'
            //     }
            // },
            
        })
    </script>
</body>

关于 Vue 的一些基础知识:

  • 想让Vue工作,就必须创建一个Vue实例 【new Vue()】,且要传入一个配置对象
  • 容器里的代码称为 【Vue模板】
  • 真实开发中只有一个Vue实例,并且会配合着组件一起使用;
  • el : 指明当前Vue实例为哪个容器服务,值通常为 css 选择器字符串。
    • 类选择器:el:'.xxxx'
    • ID 选择器: el:'#xxxx'
    • 标签选择器:el:'xxxx'
  • data : 用于存储数据,数据供 el 所指定的容器使用
    • data 的第二种写法,return 返回的是需要的数据

4.2 抽取 Vue 公共的代码片段

Vue 实例的编写大多都是固定的,将固定的代码抽取出来设置快捷方式。

文件 => 首选项 => 用户代码片段 => 新建全局代码片段/或文件夹代码片段:vue-html.code-snippets, 后缀名不能变

复制以下内容,快捷键就是: vuehtm

{
	"vue htm": {
		"scope": "html",
		"prefix": "vuehtml",
		"body": [
			"<!DOCTYPE html>",
			"<html lang=\"en\">",
			"",
			"<head>",
			"    <meta charset=\"UTF-8\">",
			"    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">",
			"    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">",
			"    <title>Document</title>",
			"</head>",
			"",
			"<body>",
			"    <div id=\"app\">",
			"",
			"    </div>",
			"    <script src=\"js/vue.js\"></script>",
			"    <script>",
			"        new Vue({",
			"            el: '#app',",
			"            data: {",
			"                $1",
			"            }",
			"        })",
			"    </script>",
			"</body>",
			"",
			"</html>",
		],
		"description": "my vue template in html"
	}
}

4.3 Vue模板语法

Vue模板语法有俩种:

  • 插值语法 : 用于解析标签体内容
    • {{ js表达式 }}
  • 指令语法 : 用于解析标签(包括:标签属性、标签体内容、绑定事件…)
    • v-xxxx :v-bind、v-model、v-if、v-for…Vue中有很多指令,都是以 v-xxx 这种格式的

什么时候使用插值,是什么时候使用指令呢?

插值语法一般都用在标签体内,指令语法用于给某个属性赋值的时候、

4.4 v-bind, v-model 指令

指令语法一般都使用在 标签中,给某个属性赋值

v-bind : 单向数据绑定,只能从data流向页面。

<body>
    <div id="app">
        <!-- 复杂写法 -->
        <!-- <input type="text" name="username" v-bind:value="name"> -->
        
        <!-- 简写 -->
        <input type="text" name="username" :value="name">
    </div>
    <script src="js/vue.js"></script>
    <script>
        new Vue({
            el: '#app',
            data: {
                name: 'jack'
            }
        })
    </script>
</body>

v-model : 双向数据绑定,数据不仅能从data流向页面,还可以从页面流向data。

<body>
    <div id="app">
        <!-- <input type="text" name="双向绑定" v-model:value="message"> -->

        <!-- 简写模式 -->
        <input type="text" name="双向绑定" v-model="message">

    </div>
    <script src="js/vue.js"></script>
    <script>
        new Vue({
            el: '#app',
            data: {
                message: 'hello,v-model'
            }
        })
    </script>
</body>

image-20220807220410608

总结:

  1. 双向绑定一般都应用在表单类元素上(如:input、select等),简单来说就是带有 value 属性

  2. v-model:value 可以简写为 v-model,因为v-model默认收集的就是value值。

4.5 事件绑定

使用 v-on:xxx 或 @xxx 绑定事件,其中 xxx 是事件名;

<body>
    <div id="app">
        <!-- <button v-on:click="f1">事件绑定</button> -->

        <!-- 简写形式 -->
        <button @click="f2">事件绑定</button>

    </div>
    <script src="js/vue.js"></script>
    <script>
        new Vue({
            el: '#app',
            data: {
                
            },
            // 定义函数
            methods: {
                f1(){
                    alert('点击事件')
                },
                f2(){
                    alert("点击事件2")
                }
            },
        })
    </script>
</body>

在绑定事件时调用函数可不加括号,但是在插值语法中调用函数必须加小括号

4.5 事件修饰符

事件修饰符 : 即阻止事件原本的默认行为

Vue中的事件修饰符:
1 .prevent:阻止默认事件(常用);
2 .stop:阻止事件冒泡(常用);
3 .once:事件只触发一次(常用);
4 .capture:使用事件的捕获模式;
5 .self:只有event.target是当前操作的元素时才触发事件;
6 .passive:事件的默认行为立即执行,无需等待事件回调执行完毕;

<body>
    <div id="app">
        <!--1.prevent:阻止默认事件(常用);-->
        <a href="http://www.baidu.com" @click.prevent="showInfo1">阻止默认事件跳转</a>

        <!-- 2.stop:阻止事件冒泡(常用);-->
        <div @click="showInfo2">
            <button @click.stop="showInfo2">阻止事件冒泡</button>
        </div>

        <!-- 3.once:事件只触发一次(常用);-->
        <button @click.once="showInfo3">事件只触发一次</button>
    </div>
    <script src="js/vue.js"></script>
    <script>
        new Vue({
            el: '#app',
            data: {

            },
            methods:{
             showInfo1(event) {
                 alert("阻止默认事件跳转")
             },

            showInfo2(event) {
                alert("阻止事件冒泡")
            },
            showInfo3(event) {
                alert("事件只触发一次")
            }
        }
        })
    </script>
</body>

事件冒泡 :在javascript事件传播过程中,当事件在一个元素上发生之后,事件会逐级传播给先辈元素,直到document 为止,有的浏览器可能到 window 为止。 并不是所有的事件都有冒泡现象,比如如下几个: blur 事件 , focus 事件, load 事件。

当第二个 button 不使用 .stop 修饰符修饰的话,button 会执行showInfo2,div 也会执行 showInfo2。

4.6 条件渲染

**v-if :**适用于切换频率较低的场景

v-if =“表达式”

v-else-if = “表达式”

v-else =“表达式”

就相当于:

if(){

}else if(){

}else{

}

v-show: 适用于切换频率较高的场景

用法: v-show=“表达式”

<body>
    <div id="app">
        <h1>n : {{n}}</h1>
        <br>
        <button @click="n++">点我n的值+1</button>
        <button @click="n--">点我n的值-1</button>

        <!-- v-if 条件渲染 -->
        <h3 v-if="n==0">尚硅谷</h3>
        <h3 v-else-if="n==1">yyds</h3>
        <h3 v-else>我爱尚硅谷</h3>

        <h3 v-show="n==4">n的值等于4,显示我~~</h3>
    </div>
    <script src="js/vue.js"></script>
    <script>
        new Vue({
            el: '#app',
            data: {
                n: ""
            }
        })
    </script>
</body>

v-if 的特点:

  • 未展示的 DOM 元素会被删除
  • v-if可以和:v-else-if、v-else一起使用,但要求结构不能被“打断”。

v-show的特点:

  • 未展示的 DOM 元素不会被删除

4.7 列表渲染 v-for

v-for指令:

  1. 用于展示列表数据

  2. 语法:v-for=“(item, index) in xxx” :key=“yyy”

  3. 可遍历:数组、对象、字符串(用的很少)、指定次数(用的很少)

<body>
    <div id="app">
        <ul>
            <!-- 简单遍历 -->
            <li v-for="(item, index) in 10" :key="index">{{item}} --- {{index}}</li>
        </ul>

        <!-- 遍历数组 -->
        <table border="1">
            <tr v-for="(user, index) in userList" :key="user.id">
                <td>{{user.id}}</td>
                <td>{{user.username}}</td>
                <td>{{user.age}}</td>
            </tr>
        </table>
    </div>
    <script src="js/vue.js"></script>
    <script>
        new Vue({
            el: '#app',
            data: {
                userList: [
                    { id: 1, username: 'helen', age: 18 },
                    { id: 2, username: 'peter', age: 28 },
                    { id: 3, username: 'andy', age: 38 }
                ]
            }
        })
    </script>
</body>

演示结果图:

image-20220808120041037

简单理解一下什么是 DOM ?

DOM 是文档对象模型(Document Object Model)的缩写, 是 W3C(万维网联盟)的标准。是一种用于HTML和XML文档的编程接口。

那么在 HTML 中的 DOM 中,所有的事务都可以理解为 节点, **DOM 被理解为是节点数的 HTML,**所有的 节点都可以通过 JavaScript 进行修改,删除… 但是如果频繁的操作 DOM 对象,效率是非常慢的,这也是为什么有数据代理的原因。这样的 DOM 也可以称为 真实 DOM

image-20220808120730922

什么是 虚拟 DOM ?

虚拟dom 本质上就是 一个 普通的 JS对象,用于描述视图的界面结构 在vue中,每个组件都有 一个 render 函数,每个render函数都会返回 一个虚拟dom 树

index : 遍历的索引

:key :

  • key 是虚拟DOM 的标识
  • 当数据发生变化时,Vue 会根据【新数据】生成【新的虚拟DOM】 ,
  • 随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下:
    • 【旧虚拟DOM】中找到了与【新虚拟DOM】相同的key:
      • 若虚拟DOM中内容没变, 直接使用之前的真实DOM!
      • 若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM。
    • 旧虚拟DOM中未找到与新虚拟DOM相同的key
      • 创建新的真实DOM,随后渲染到到页面。

开发中如何选择 key ?

最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。

4.8 Vue 组件

<body>
    <div id="app">
        <!-- 使用组件 -->
        <Navbar></Navbar>
    </div>
    <script src="js/vue.js"></script>
    <script>
        new Vue({
            el: '#app',
            data: {

            },
            components: {
                //组件的名字
                'Navbar': {
                    //组件的内容
                    template: '<ul><li>首页</li><li>学员管理</li></ul>'
                }
            }
        })
    </script>
</body>

全局组件的定义:

创建一个 Navbar.js 文件,里边定义全局组件。

// 全局组件
Vue.component('Navbar',{
    template: '<ul><li>首页</li><li>学员管理</li><li>讲师管理</li></ul>'
})

在使用组件的 html 中引入 js 文件,并使用组件。

<body>
    <div id="app">
        <!-- 使用组件 -->
        <Navbar></Navbar>
    </div>
    <script src="js/vue.js"></script>
    <!--引入全局组件-->
    <script src="./components/Navbar.js"></script>

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

4.9 Vue 生命周期

生命周期

created 方法: 页面渲染之前执行

mounted :页面渲染之后执行

5.0 路由

  1. 理解: 一个路由(route)就是一组映射关系(key - value),多个路由需要路由器(router)进行管理。
  2. 前端路由:key是路径,value是组件。
  3. Route — 路由 , Router — 路由器
<body>
    <div id="app">
        <h1>Hello App!</h1>
        <p>
            <!-- 使用 router-link 组件来导航. -->
            <!-- 通过传入 `to` 属性指定链接. -->
            <!-- <router-link> 默认会被渲染成一个 `<a>` 标签 -->
            <router-link to="/">首页</router-link>
            <router-link to="/student">会员管理</router-link>
            <router-link to="/teacher">讲师管理</router-link>
        </p>
        <!-- 路由出口 -->
        <!-- 路由匹配到的组件将渲染在这里 -->
        <router-view></router-view>
    </div>
    <!-- 顺序不能颠倒,先引入 vue.js,后引入 vue-router  -->
    <script src="js/vue.js"></script>
    <script src="js/vue-router.min.js"></script>

    <script>
        // 1. 定义(路由)组件。
        // 可以从其他文件 import 进来
        const Welcome = { template: '<div>欢迎</div>' }
        const Student = { template: '<div>student list</div>' }
        const Teacher = { template: '<div>teacher list</div>' }

        // 2. 定义路由
        // 每个路由应该映射一个组件。
        const routes = [
            { path: '/', redirect: '/welcome' }, //设置默认指向的路径
            { path: '/welcome', component: Welcome },
            { path: '/student', component: Student },
            { path: '/teacher', component: Teacher }
        ]

        // 3. 创建 router 实例,多个路由需要路由器 router 来管理。
        const router = new VueRouter({
            routes // (缩写)相当于 routes: routes
        })

        // 4. 创建和挂载根实例。
        // 从而让整个应用都有路由功能
        const app = new Vue({
            el: '#app',
            router
        })

        // 现在,应用已经启动了!
    </script>
</body>

5.axios

axios 是独立的一个项目, 不是 vue 里面的一部分,axios 经常和 vue 一起使用完成 ajax 操作

演示:

使用 axios 模拟发送请求,数据使用 JSON 传模拟。

创建 JSON 文件,模拟数据:

{
    "success":  true,
    "message": "成功",
    "code": 20000,
    "data": { 
        "items": [
            {"name": "张三","age":10},
            {"name": "李四","age":20},
            {"name": "王五","age":30}
        ]
    }
}
<body>
    <div id="app">
        <table border="1">
            <!-- 遍历数据 -->
            <tr v-for="(user, index) in userList" :key="index">
                <td>{{user.name}}</td>
                <td>{{user.age}}</td>
            </tr>
        </table>
    </div>
    <script src="../VueDemo/js/vue.js"></script>
    <!-- 引用 axios  -->
    <script src="../VueDemo/js/axios.min.js"></script>

    <script>
        new Vue({
            el: '#app',
            // 1、一般在 data 里定义变量,初始化数据
            data: {
                userList: [],
            },
            //  2、在页面渲染之前执行,一般在这里调用方法
            created() {
                this.getUserList()
            },
            //  3、定义方法
            methods: {
                getUserList() {
                    // getUserList("请求路径")
                    axios.get("data.json").
                        then((response) => { // 请求成功执行的方法,response 返回的数据

                            this.userList = response.data.data.items;
                            // console.log(response)

                            console.log(this.userList)
                        })
                        .catch((err) => { // 请求失败执行的方法
                            console.log("**** 请求失败")
                        });
                }
            },
        })
    </script>
</body>

查看层级关系:

image-20220809122317899

6.element-ui

element-ui 是饿了么前端出品的基于 Vue.js 的 后台组件库,方便程序员进行页面快速布局和构建

官网: https://element.eleme.cn/#/zh-CN

7.node.js

简单的说 Node.js 就是运行在服务端的 JavaScript。

Node.js是一个事件驱动I/O服务端JavaScript环境,基于Google的V8引擎,V8引擎执行Javascript的速度非常快,性能非常好。

node.js 的作用:

如果你是一个前端程序员,你不懂得像PHP、Python或Ruby等动态编程语言,然后你想创建自己的服务,那么Node.js是一个非常好的选择。

Node.js 是运行在服务端的 JavaScript,如果你熟悉Javascript,那么你将会很容易的学会Node.js。

当然,如果你是后端程序员,想部署一些高性能的服务,那么学习Node.js也是一个非常好的选择

下载:

官网:https://nodejs.org/en/

中文网:http://nodejs.cn/

LTS:长期支持版本

Current:最新版

安装完 cmd 命令行查看版本:

image-20220809125133549

使用 node.js 执行 js 文件 :

在 js 文件所在的目录打开 cmd 命令行,执行: node js文件

image-20220809130013460

8.Npm 包管理

什么是 NPm ?

NPM全称Node Package Manager,是Node.js包管理工具,是全球最大的模块生态系统,里面所有的模块都是开源免费的;也是Node.js的包管理工具,相当于前端的Maven 。

node.js 已经继承了 npm。使用 npm -v 查看版本。

使用 npm 管理项目

  1. 创建文件夹,对项目进行初始化
#建立一个空文件夹,在命令提示符进入该文件夹  执行命令初始化
npm init
#按照提示输入相关信息,如果是用默认值则直接回车即可。
#name: 项目名称
#version: 项目版本号
#description: 项目描述
#keywords: {Array}关键词,便于用户搜索到我们的项目
#最后会生成package.json文件,这个是包的配置文件,相当于maven的pom.xml
#我们之后也可以根据需要进行修改。



#如果想直接生成 package.json 文件,那么可以使用命令
npm init -y
  1. 修改 npm 镜像

NPM官方的管理的包都是从 http://npmjs.com下载的,但是这个网站在国内速度很慢。

这里推荐使用淘宝 NPM 镜像 http://npm.taobao.org/ ,淘宝 NPM 镜像是一个完整 npmjs.com 镜像,同步频率目前为 10分钟一次,以保证尽量与官方服务同步。

#经过下面的配置,以后所有的 npm install 都会经过淘宝的镜像地址下载
npm config set registry https://registry.npm.taobao.org 

#查看npm配置信息
npm config list
  1. 使用 install 命令下载依赖
# 使用 npm install 安装依赖包的最新版,
# 模块安装的位置:项目目录\node_modules
# 安装会自动在项目目录下添加 package-lock.json文件,这个文件帮助锁定安装包的版本
# 同时package.json 文件中,依赖包会被添加到dependencies节点下,类似maven中的 <dependencies>
npm install jquery


# npm管理的项目在备份和传输的时候一般不携带node_modules文件夹
npm install # 根据package.json中的配置下载依赖,初始化项目


# 如果安装时想指定特定的版本
npm install jquery@2.1.x


# devDependencies节点:开发时的依赖包,项目打包到生产环境的时候不包含的依赖
# 使用 -D参数将依赖添加到devDependencies节点
npm install --save-dev eslint
# 或
npm install -D eslint


# 全局安装
# Node.js全局安装的npm包和工具的位置:用户目录\AppData\Roaming\npm\node_modules
# 一些命令行工具常使用全局安装的方式
npm install -g webpack

image-20220809170603418

其他命令:

#更新包(更新到最新版本)
npm update 包名
#全局更新
npm update -g 包名

#卸载包
npm uninstall 包名
#全局卸载
npm uninstall -g 包名

9.babel

Babel是一个广泛使用的转码器,可以将ES6代码转为ES5代码,从而在现有环境执行执行。

ES6 代码浏览器兼容性很差,如果使用 ES5 浏览器兼容性很好。

这意味着,你可以现在就用 ES6 编写程序,而不用担心现有环境是否支持。

安装 babel 工具:

前提必须将项目进行初始化: npm init

npm install --global babel-cli

#查看是否安装成功
babel --version

在查询版本时,爆出以下错误, 说明全局安装 babel 需要配置环境变量。

image-20220809174426178

在 终端输入: npm config get prefix 命令获取路径,复制这个路径增加到 环境变量中

image-20220809174818725

打开环境变量: 控制面板 搜 环境变量

image-20220809174606286

新建环境变量:

image-20220809174853982

babel 工具的使用:

  1. 使用前一定要项目初始化: npm init -y
  2. 创建 js 文件,编辑一段 es6 代码
// 转码前
// 定义数据
let input = [1, 2, 3]
// 将数组的每个元素 +1
input = input.map(item => item + 1)
console.log(input)
  1. 创建 .babelrc 配置文件,presets字段设定转码规则,将es2015规则加入 .babelrc:
{
    "presets": ["es2015"],
    "plugins": []
}
  1. 在项目中安装转码器
npm install --save-dev babel-preset-es2015
  1. 进行转码
# 转码结果写入一个文件
mkdir dist1


# --out-file 或 -o 参数指定输出文件
babel src/example.js --out-file dist1/compiled.js
# 或者
babel src/example.js -o dist1/compiled.js

# 整个目录转码
mkdir dist2
# --out-dir 或 -d 参数指定输出目录
babel src --out-dir dist2
# 或者
babel src -d dist2

image-20220809175821907

转码后

image-20220809175853160

10.模块化

什么是模块化?

每个文件就是一个模块,有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。 JavaScript 之间的调用。

ES5 规范写法:

创建 01.js 文件,定义 方法并暴露出去:

// 1.定义方法
const sum = function(a,b){
    return parseInt(a) + parseInt(b)
}
const subtract = function(a,b){
    return parseInt(a) - parseInt(b)
}

// 2.暴露该模块中的方法
module.exports={
    sum,
    subtract

创建 02.js 文件,引用模块并调用方法:

// 3.引用模块.当前目录下使用 ./
const m = require('./01.js')

const result1 = m.sum(1,2)
console.log(result1)
const result2 = m.subtract(1,2)
console.log(result2)

最终在 终端 使用 node.js 执行!!

node ./02.js

第一种ES6 模块化代码规范:

01.js 中定义方法:


// 定义方法,使用 export 暴露
export function getList(){
    console.log('getList~')
}

export function insert(){
    console.log('insert~')
}

02.js 中引用模块 ,并且调用方法

//  引用模块
import { getList,insert } from "./01";

// 调用方法
getList(),
insert();

ES6 规范的代码无法在 node.js 中执行,需要使用 babel 转换 ES5

image-20220810135500629

使用 babel 转换成 ES5

# -d 转换整个文件夹
babel moduleEs6 -d moduleEs5

第二种ES6 规范的代码:

01.js 文件

使用 export default 统一暴露

// 定义方法,
export default {
    getList() {
        console.log('getList!!')
    },
    insert() {
        console.log("insert!!")
    }
}

02.js 文件:

起一个别名代替具体的方法名。

//  引用模块
import user from "./01";

// 调用方法
user.getList(),
user.insert();

11.webpack

Webpack 是一个前端资源加载/打包工具。它将根据模块的依赖关系进行静态分析,然后将这些模块按照指定的规则生成对应的静态资源。

从图中我们可以看出,Webpack 可以将多种静态资源 js、css、less 转换成一个静态文件,减少了页面的请求。

安装 webpack

提前进行初始化

npm install -g webpack webpack-cli

查看版本号:

webpack -v

打包过程:

  1. 创建 js 文件,用于打包操作
    1. common.js、utils.js 用于定义方法
    2. main.js 用于调用方法
// 定义方法
// common.js 内容
exports.info = function (str) {
    document.write(str);
}
---------------------------------------------------
// utils.js 内容
exports.add = function (a, b) {
    return a + b;
}
---------------------------------------------------
// main.js 内容
// ES5 写法
const common = require('./common');
const utils = require('./utils');

common.info('Hello world!' + utils.add(100, 200));
---------------------------------------------------
  1. webpack目录下创建配置文件 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' //输出文件
    }
}

image-20220810153758839

  1. 使用命令进行打包
webpack #有黄色警告
webpack --mode=development #没有警告
#执行后查看bundle.js 里面包含了上面两个js文件的内容并进行了代码压缩

测试:

创建一个 html 文件,引入打包完成的 bundle.js 文件。

<script src="./dist/bundle.js"></script>

打包 css 样式文件:

Webpack 本身只能处理 JavaScript 模块,如果要处理其他类型的文件,就需要使用 loader 进行转换。

Loader 可以理解为是模块和资源的转换器。

首先我们需要安装相关Loader插件,css-loader 是将 css 装载到 javascript;style-loader 是让 javascript 认识css

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

创建 style.css 样式

body{
    background-color: aquamarine;
}

在 main.js 中引入 css 样式文件:

// ES5 写法
const common = require('./common');
const utils = require('./utils');
// 引入 css 样式文件
require('./style.css')

common.info('Hello world!' + utils.add(100, 200));

修改 webpack.config.js 配置文件:

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

重新打包: webpack

image-20220810155750366

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

鲨瓜2号

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

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

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

打赏作者

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

抵扣说明:

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

余额充值