谷粒学苑项目前置知识
项目分为三篇:
谷粒学苑项目前置知识
谷粒学苑项目前台界面
谷粒学苑后台管理系统
资料链接:谷粒学苑 提取码:p6er
视频教程:
尚硅谷-谷粒学苑
项目模块介绍:
项目技术点:
后端: 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
一、项目环境搭建
前后端分离概念:
准备数据库表:
完整项目工程结构总览 :
创建父工程:
父工程删除 src 文件夹,只留下 pom 文件。
父工程依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<!--坐标信息-->
<groupId>com.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 命令行
安装 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
第二种方法:
解压我的 maven 仓库,将 jar 包拷贝到你的仓库
链接:https://pan.baidu.com/s/1_RVsyZI8PlCI_paf0XOOVg
提取码:v4wf
创建 service 子模块 :
- 删除 src,只留下 pom 文件
- 由于还没有用到微服务,先将依赖注释掉,否则会报错
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 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:
写 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 插件自动生成的代码,并没有使用老师提供的代码生成器。
首先在插件商店里下载 MyBatisX 插件:
Setting – Plugins – 搜索MyBatisX — 重启 IDEA
启动类:
@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();
}
}
测试:
修改返回数据的时间格式:
#返回json的全局时间格式
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8
到此步基本的模块结构
二、讲师管理模块后端
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 的依赖即可
创建配置类 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(); } }
在 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 的中扫描 这个配置类
为什么不直接配置 com.atguigu.servicebase 包名呢?
这是因为 主启动类在 com.atguigu.demo 包下,ComponentScan 会覆盖 SpringBootApplication 的包扫描,因此会冲突。只好扫描他们的公共包了~~
访问:
http://localhost:8001/swagger-ui.html#
定义接口和参数说明:
- 定义在类上:@Api
- 定义在方法上:@ApiOperation
- 定义在参数上:@ApiParam
总结:
- 使用 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 目录结构 : **
结果测试:
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());
}
多条件组合查询带分页
比如前端的页面, 有四个查询条件,这四个查询条件任意组合进行查询。
一般的做法是将
前端的参数封装到一个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;
}
目录结构:
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 参数介绍:
在 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 测试:
4.增加讲师
首先将
gmt_create、gmt_modified
俩个字段设置自动填充
- 在 EduTeacher实体类中 增加 @TableField 注解
- 增加自动填充配置类
在 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
注解
根据 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 依赖删除。
演示结果:
特定异常处理:
@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());
}
手动抛出异常:
由于是我们自定义的,系统不会抛出该异常,需要我们自己手动抛出 !
service_base 目录结构:
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 配置文件中的
日志配置
- 在 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>
自动生成的日志文件
将错误信息打印到日志中去:
- 在全局处理异常的类
GlobalExceptionHandler
上 增加 @Slf4j 注解
- 使用
log.error()
方法将错误信息打印到日志中去
测试结果:
如果想有更多的错误信息,使用以下的工具类:
- 在 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(); } }
测试结果:
三、前端基础知识
1.VSCode 安装和使用
安装中文界面:
- 首先安装中文插件:Chinese (Simplified) Language Pack for Visual Studio Code
- 右下角弹出是否重启vs,点击“yes”
- 有些机器重启后如果界面没有变化,则 点击 左边栏Manage -> Command Paletet…【Ctrl+Shift+p】
- 在搜索框中输入“configure display language”,回车
- 打开locale.json文件,修改文件下的属性 “locale”:“zh-cn”
安装常用插件:
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>
当对象中有重复属性时,后面的会把前面的覆盖掉
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 框架
特点:
- 遵循 MVVM 模式
- 编码简洁, 体积小, 运行效率高, 适合移动/PC 端开发3. 它本身只关注 UI, 也可以引入其它第三方库开发项目
使用开发版本可以使用 Vue 插件调试,插件直接在浏览器插件商店中搜索
Vue.js devtools
安装即可
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>
总结:
双向绑定一般都应用在表单类元素上(如:input、select等),简单来说就是带有 value 属性
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指令:
用于展示列表数据
语法:v-for=“(item, index) in xxx” :key=“yyy”
可遍历:数组、对象、字符串(用的很少)、指定次数(用的很少)
<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>
演示结果图:
简单理解一下什么是 DOM ?
DOM 是
文档对象模型(Document Object Model)
的缩写, 是 W3C(万维网联盟)的标准。是一种用于HTML和XML文档的编程接口。那么在 HTML 中的 DOM 中,所有的事务都可以理解为 节点, **DOM 被理解为是节点数的 HTML,**所有的 节点都可以通过 JavaScript 进行修改,删除… 但是如果频繁的操作 DOM 对象,效率是非常慢的,这也是为什么有数据代理的原因。这样的 DOM 也可以称为 真实 DOM
什么是 虚拟 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 路由
- 理解: 一个路由(route)就是一组映射关系(key - value),多个路由需要路由器(router)进行管理。
- 前端路由:key是路径,value是组件。
- 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>
查看层级关系:
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 命令行查看版本:
使用 node.js 执行 js 文件 :
在 js 文件所在的目录打开 cmd 命令行,执行: node js文件
8.Npm 包管理
什么是 NPm ?
NPM全称Node Package Manager,是Node.js包管理工具,是全球最大的模块生态系统,里面所有的模块都是开源免费的;也是Node.js的包管理工具,相当于前端的Maven 。
node.js 已经继承了 npm。使用 npm -v
查看版本。
使用 npm 管理项目
- 创建文件夹,对项目进行初始化
#建立一个空文件夹,在命令提示符进入该文件夹 执行命令初始化
npm init
#按照提示输入相关信息,如果是用默认值则直接回车即可。
#name: 项目名称
#version: 项目版本号
#description: 项目描述
#keywords: {Array}关键词,便于用户搜索到我们的项目
#最后会生成package.json文件,这个是包的配置文件,相当于maven的pom.xml
#我们之后也可以根据需要进行修改。
#如果想直接生成 package.json 文件,那么可以使用命令
npm init -y
- 修改 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
- 使用 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
其他命令:
#更新包(更新到最新版本)
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 需要配置环境变量。
在 终端输入: npm config get prefix
命令获取路径,复制这个路径增加到 环境变量中
打开环境变量: 控制面板 搜 环境变量
新建环境变量:
babel 工具的使用:
- 使用前一定要项目初始化:
npm init -y
- 创建 js 文件,编辑一段 es6 代码
// 转码前
// 定义数据
let input = [1, 2, 3]
// 将数组的每个元素 +1
input = input.map(item => item + 1)
console.log(input)
- 创建
.babelrc
配置文件,presets字段设定转码规则,将es2015规则加入 .babelrc:
{
"presets": ["es2015"],
"plugins": []
}
- 在项目中安装转码器
npm install --save-dev babel-preset-es2015
- 进行转码
# 转码结果写入一个文件
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
转码后:
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
使用 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
打包过程:
- 创建 js 文件,用于打包操作
- common.js、utils.js 用于定义方法
- 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));
---------------------------------------------------
- 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' //输出文件
}
}
- 使用命令进行打包
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