三、jt项目笔记
一1.完成项目安装(一周完成)1.1 VUE脚手架安装B站名称: 不二子阳VUE后台脚手架安装过程1.2 VMware安装说明VMware配置说明2 IDEA环境配置2.1 准备项目目录程序员操守: 1.路径中不要出现中文/空格/特殊字符.2.中文对C语言的程序有致命的影响.目录:IDEA软件打开2.2 IDEA环境配置具体配置查看PPT文档2.3 关于Maven环境说明组件:1. 远程仓库 官方
目录
1.2.3 @SpringBootConfiguration注解说明
1.2.3 @EnableAutoConfiguration注解说明
一
1.完成项目安装(一周完成)
1.1 VUE脚手架安装
B站名称: 不二子阳
VUE后台脚手架安装过程
1.2 VMware安装说明
VMware配置说明
2 IDEA环境配置
2.1 准备项目目录
程序员操守: 1.路径中不要出现中文/空格/特殊字符.
2.中文对C语言的程序有致命的影响.
- 目录:
- IDEA软件打开
2.2 IDEA环境配置
具体配置查看PPT文档
2.3 关于Maven环境说明
组件:
1. 远程仓库 官方维护了几乎所有的项目的jar包.(版权问题oracle数据)
2. 私服镜像 在国内仓库. 阿里云镜像服务器.
3. 本地仓库 用户在自己本地维护的jar包文件.
settings文件说明:
1. 配置本地仓库路径
2. 配置私服镜像
<mirror>
<id>aliyun</id>
<name>aliyun for maven</name>
<mirrorOf>*</mirrorOf>
<url>https://maven.aliyun.com/repository/public</url>
</mirror>
IDEA整合Maven
2.4 创建项目不能识别问题
如图: 创建项目之后,不能被maven项目标识.
- 解决方案
2.5 关于Server URL地址说明
课上使用默认配置 jar包版本下载2.5.3
路径: https://start.spring.io/
https://start.aliyun.com/
- 1
- 2
3 SpringBoot入门demo创建
3.1 项目创建
3.2 项目版本
3.3 关于项目报错说明
如果项目中出现parent标签报错/插件报错说明:
1.parent标签报错
2. maven插件报错
4. 关于maven 重要说明
4.1 jar包依赖传递性说明
例子: 假设: A.jar 依赖 B.jar , B.jar 依赖于 C.jar
说明: 在项目中 添加了web的jar包,则可以依赖其他的jar包,为项目提供支持.
4.1.1 知识点1 jar包下载不完整解决方案:
如果pom.xml文件jar包文件报错. 检查本地仓库中的jar包文件是否完整.如果下载不完整. 则手动删除文件.之后重写下载.
4.1.2 知识点2: jar包传递性的原理.
说明: 当maven依赖jar包文件时,首先根据坐标查找本地仓库的路径,之后添加jar包文件. 之后再次加载该文件目录的xxx.pom文件.读取其中的依赖项,进行再次依赖.以此类推.
4.2 jar包下载安全性说明(扩展)
说明: maven数据传输 通过sha1的数字摘要 包装数据的完整性
4.2.1 知识讲解:
SHA-1(英语:Secure Hash Algorithm 1,中文名:安全散列算法1)是一种密码散列函数,美国国家安全局设计,并由美国国家标准技术研究所(NIST)发布为联邦数据处理标准(FIPS)。SHA-1可以生成一个被称为消息摘要的160位(20字节)散列值,散列值通常的呈现形式为40个十六进制数。
4.2.2 知识扩展:
问题1: 如果数据相同,采用相同的hash函数 问: 值是否相同? 答案:必然相同!!!!
问题2: 常见hash编码 是多少位16进制数?? 答案: 8位16进制数
问题3: 8位16进制数,取值区间多少? 答案: 2^32
00000000-FFFFFFFF
问题4: 1kb的数据和1gb数据 hash谁快? 答案: 一样快
问题5: 数据相同则hash码相同,hash码相同数据一定相同? 不正确. **hash碰撞!**
降低碰撞概率, 增大hash长度.
(2^4)^8=2^32
- 1
4.2.3 数据传递有效性
5.SpringBoot高级用法
5.1 pom.xml文件说明
5.1.1 parent标签作用
<!--
继承特点: 将父级内容进行引用.(简化)
SpringBoot理解: SpringBoot是框架的框架
问题: jar包冲突问题严重,之后需要有人统一管理
A.jar ~~~~ 5.1.jar
B.jar ~~~~ 4.8.jar
SpringBoot项目: 官网将大部分框架在内部进行了整合.
并且制定了,统一的版本依赖信息.
parent标签的作用: 定义当前SpringBoot所有依赖的版本号
-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.3</version>
<relativePath/>
</parent>
5.1.2 dependency标签作用
<!--按需导入
历史说明: 2010 原来SSM 需要手动的编辑大量的的配置文件
思想: SpringBoot使用体现了"开箱即用"的思想
-->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<!--Springboot的启动器 在内部已经将整合的配置写好,实现拿来就用-->
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
5.1.3 build标签作用
<!--build标签
springboot项目在打包部署发布时,需要依赖maven工具API
如果不添加该插件,则直接影响项目发布
-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.5.3</version>
</plugin>
</plugins>
</build>
5.1.4 Maven指令
1.clean 删除编译后的目录 target .java 文件源码 .jar包(xxx.class文
件)
- 用法: 1.当可能出现编译问题时 使用clean. 2.项目打包前使用
2.install 项目发布 将xxx.class文件 打包成为 xxx.jar
-jar包位置: 1.target目录下 2.本地仓库中.
target目录下:
本地仓库目录下:
5.1.5 java项目发布命令
- 进入jar包的根目录
2.前提 必须配置JDK
说明: 通过java -version
检查JDK是否配置正常.
如果提示java不是内部命令 则环境变量配置一定有问题.
环境变量配置: - 项目发布
命令:java -jar xxx.jar
5.1.6 项目关闭问题
如果项目发布成功之后,需要关闭. 则直接关闭dos命令窗口即可.
如果依然不能正常关闭.则杀死进程/或者重启计算机.
右键结束进程项即可
5.1.7 关于dos命令窗口锁定问题
说明: 当通过dos命令 启动tomcat服务器时,可能由于误操作.锁定当前dos窗口
解决方案: 万能取消指令 ctrl + c
6. SpringBoot高级用法
6.1 @Value注解
6.1.1 需求说明
有时需要动态的获取数据,不能直接写死在代码中, 如何处理???
6.1.2 编辑YML文件
#语法说明
## 语法1: 数据结构 key-value结构
## 语法2: 写法 key:(空格)value
## 语法3: yml文件 默认字符集是UTF-8编码 中文不乱码
## 语法4: yml文件 有层级效果 注意缩进
server:
port: 8080
# SpringBoot启动时 默认加载yml文件
msg:
hello: "今天下雨,记得带伞~~"
6.1.3 编辑HelloController
@RestController //将该类交给Spring管理
public class HelloController {
/**
* 规则:
* 1. 当Spring容器启动时,会加载YML配置文件.
* 会将内部的key-value结构 加载到spring维护的内存空间中
* 2. @Value功能,从spring容器中根据key 动态赋值
* 3. springel表达式 简称:spel
*
* 使用场景:
* 如果代码中需要给成员变量赋值时,一般采用动态赋值的方式.
*/
@Value("${msg.hello}")
private String msg;
@RequestMapping("/hello")
public String hello(){
return msg;
}
}
6.1.4 页面效果展现
7 GIT操作
7.1 常规Git操作
git 工作区 缓存区 本地仓库 远程仓库
1.检查当前分支 git branch
2.创建分支 git checkout -b 新分支名称
3.推送新分支 git push -u origin 新分支名称 第一次推送需要写-u
4.将文件添加到暂存区 git add .
5.提交代码 git commit -m “提交消息”
6.推动代码到云端 git push
7.合并代码到主分支 git merge 分支名称 在主线中操作
8.克隆代码 git clone “仓库地址”
9.将远程库的数据更新到本地 git pull
7.2 git中的分支
说明:开发中需要创建自己的分支结构,如果分支中的代码准确无误,应该将分支结构并入主线(master)
步骤:
1. 创建分支 git checkout -b 新分支名称
2. 推送新分支 git push -u origin 新分支名称
3. 修改工作区的内容
4. 提交数据 git add. git commit -m “xxxx” git push 提交到分支中
5. 切换到主线分支
6. 合并分支 git merge 分支名称 (在本地库中合并)
7. 将数据推送到云端 git push
二
1. SpringBoot使用注意事项
1.1 关于Build标签说明
如果没有build标签则项目打包之后不能正常执行.报错如下
1.2 关于主启动类说明
1.2.1 关于元注解的说明
@Target(ElementType.TYPE) 该注解对类有效
@Retention(RetentionPolicy.RUNTIME) 该注解在运行期有效
@Documented 生成源码时 是否动态生成注释文档
@Inherited 标识注解是否可以被继承
1.2.2 @ComponentScan说明
说明: 当springBoot启动时,会实例化很多的对象,但是需要排除特定的类型
原因: SpringBoot内部兼容了很多的第三方框架,但是其中个别加入之后会影响整个代码流程,所以通过这样的方式将个别的类排除在外.
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
1.2.3 @SpringBootConfiguration注解说明
关键词: 配置文件/配置类 作用:整合第三方框架(配置)
@Configuration: 该注解一般标识类文件,告知springBoot程序 这个类是用来整合第三方框架/配置的.
说明2: @SpringBootConfiguration修饰主启动类,说明主启动类也是一个配置类.
说明3: SpringBoot主启动类会扫描用户自己的配置类,并且实例化对象.
1.2.3 @EnableAutoConfiguration注解说明
说明: 自动化的配置如图所示:
1.2.3.1@AutoConfigurationPackage说明
该注解表示自动配置的包扫描的路径.
工作原理: 该注解会动态的获取主启动类当前的包路径.之后开启包扫描的机制.
知识点: 以后写代码都应该在主启动类的同包及子包中.
1.2.3.2@Import(AutoConfigurationImportSelector.class)
知识回顾: SpringBoot使用体现了"开箱即用"的思想,用户引入jar包之后 可以直接使用(少量配置).
思考: dependencies代表程序的依赖项,(将其他的包加载到自己的项目中), 只是加载不负责执行.
当SpringBoot主启动项执行时,内部会加载@Import(AutoConfigurationImportSelector.class),
该注解的作用就是根据依赖信息,根据SpringBoot内部的配置信息,依次加载执行.
当匹配了用户添加的启动器之后,程序负责实例化框架(执行jar包中的内容).
如果匹配不到,则继续向下执行.最终SpringBoot程序加载成功.
1.2.4 主启动类注解结构图
1.2.5 关于依赖考题
问题: 用户暂时不需要数据库的配置. 问:能否先行添加数据源的启动项?? (不添加任何注解!!!)
答案: 不可以,因为启动项执行需要完整配置,数据源信息并未添加.导致报错.
总结: SpringBoot项目中的依赖包尤其启动项,添加时慎重. 如果需要添加启动器,则必须保证配置完整.否则程序启动必然失败!!!.
2. SpringBoot高级用法
2.1 属性赋值
2.1.1 @Value为属性赋值
YML配置信息
# SpringBoot启动时 默认加载yml文件
msg:
hello: "今天下雨,记得带伞~~"
动态赋值:
2.2 properties文件为属性赋值
2.2.1 业务说明
业务有时会需要动态赋值,如果将大量的业务数据写到yml文件中 导致代码混乱.最好的方式应该指定properties文件 为业务赋值.
2.2.2 编辑properties文件
#语法:
#数据结构: key=value
#注意事项: 不要添加""号 本身就是字符串.
#字符集编码: 写法采用utf-8,程序读取时采用ISO-8859-1
pro.msg=配置信息
2.2.3 动态为属性赋值
package com.jt.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController //将该类交给Spring管理
//spring负责加载指定的配置文件
//如果注解中只有一个属性名称为value 则可以省略不写
@PropertySource(value="classpath:/msg.properties",encoding = "utf-8")
public class HelloController {
/**
* 规则:
* 1. 当Spring容器启动时,会加载YML配置文件.
* 会将内部的key-value结构 加载到spring维护的内存空间中
* 2. @Value功能,从spring容器中根据key 动态赋值
* 3. springel表达式 简称:spel
*
* 使用场景:
* 如果代码中需要给成员变量赋值时,一般采用动态赋值的方式.
*/
@Value("${msg.hello}")
private String msg;
@Value("${pro.msg}")
private String proMsg;
@RequestMapping("/hello")
public String hello(){
return msg+"||"+proMsg;
}
}
2.2 环境切换
要求: 不管是几个环境,要求配置项都必须相同,值可以不同
语法:
1.使用—实现YML配置文件的分割
2.定义环境名称
3.定义默认环境名称
#指定默认的环境
spring:
profiles:
active: test
---
#关于YML配置文件说明
# 1.数据结构 key=value
# 2.数据类型 默认都是字符串 如果需要类型转化,则框架已经在内部完成
# 3.yml写法: 1.key: (空格)value 2.有层级结构 编辑时注意
# 4.字符集编码: 默认程序加载时都是UTF-8
#定义环境名称
spring:
config:
activate:
on-profile: dev
server:
port: 8080
#项目发布的路径
servlet:
# /缺省值 localhost:8080/jt/xxxx请求路径
context-path: /
#2.为属性赋值 hello代表前缀 key=hello.msg
hello:
msg: "我喜欢开发"
#环境分割线
---
spring:
config:
activate:
on-profile: test
server:
port: 8090
#项目发布的路径
servlet:
# /缺省值 localhost:8080/jt/xxxx请求路径
context-path: /
#2.为属性赋值 hello代表前缀 key=hello.msg
hello:
msg: "我喜欢测试"
2.3 热部署
2.3.1 添加依赖
<!--支持热部署 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
2.3.2 配置IDEA环境
快捷键: ctrl + shift +alt + / 或者 ctrl + alt + a
勾选属性即可实现热部署
2.4 Lombok插件安装
2.4.1 插件安装
2.4.2 添加jar包
<!--引入插件lombok 自动的set/get/构造方法插件 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
2.4.3 lombok作用
动态生成常见 get/set/toString等方法
//标准写法
//知识点: 为什么需要添加无参构造???
// 利用反射机制实例化对象时,默认调用无参构造
@Data //get/set/toString/equals/hashcode
@NoArgsConstructor //无参构造
@AllArgsConstructor//全参构造
@Accessors(chain = true)
public class User implements Serializable {
private Integer id;
private String name;
//链式加载的底层原理 返回User对象
/* public User setId(Integer id){
this.id = id;
return this; //代表当前对象!!!!!
}
public User setName(String name){
this.name = name;
return this;
}*/
}
2.4.4 lombok知识点
问题: lombok使用需要提前安装lombok插件!!, 如果项目发布在Linux系统中.问: 系统是否需要提前安装插件?
答案: 不要 因为lombok插件在编译期有效!!!
xxx.java文件编译为xxx.class文件.
知识衍生: 写的是java源码, 运行的是编译后的.class
2.5 SpringBoot测试类用法
2.5.1 业务说明
说明:有时由于业务需要,需要进行单元测试, 单元测试中的对象可能会依赖Spring容器.
需求: 需要Spring容器管理一个User对象. id=101 name=“好好学习”
分析: 难点: 定制化属性内容!!! 具体业务功能. 利用配置类实现!!!
2.5.2 编辑配置类
//作用: 将自己需要的对象交给Spring容器管理
@Configuration //标识这是一个配置类
public class UserConfig {
/**
* 知识点:1.Spring内部维护一个Map集合
* 2. Map集合 key/value 分别是谁???
* 3.IOC管理的对象的方式 1.@Controller等注解 2.@bean
*
* 1.注解组合: @Configuration + @Bean
* 2.什么是bean: 被spring管理的对象就是bean
* 3.@Bean注解的作用
* 将方法的返回值对象交给Spring管理
* 4.Spring如何管理对象?
* 数据结构: Map集合 Map<K,V>
* key: 对象的ID
* value: 实例化之后的对象
* demo1: Spring如何管理对象
* key: 方法的名称
* value: 方法的返回值 Map<user,new User()>
*
* demo2:
* @Controller
* HelloController{}
* key: helloController 首字母小写
* value: spring通过反射创建的HelloController对象
* @return
*/
@Bean
public User user(){
return new User(101,"好好学习");
}
}
2.5.3 Spring对象注入原理
1.根据对象类型进行注入
2.5.4 编辑测试类
package com.jt.test;
import com.jt.pojo.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.context.SpringBootTest;
/**
* 原理说明:
* 1.当测试类执行时,会根据SpringBoot的配置,动态启动Spring容器.
* 2.当spring容器启动时,会加载所有的注解和配置类,最终将所有的对象
* 交给Spring容器管理 Map集合
* 3.@SpringBootTest 通过这个注解,可以动态的从spring容器中
* 获取对象
*
*/
@SpringBootTest
public class TestSpring {
/**
* 注入方式:
* 1. 根据类型注入 (默认规则)
* 2. 根据名称注入 @Autowired
* @Qualifier("user")
*/
@Autowired
//@Qualifier(value="user")
private User user;
@Test
public void testUser(){
System.out.println(user.toString());
}
}
3 作业
- 新创建一个项目 demo2_mybatis 要求实现SpringBoot整合Mybatis
- 难点1. 引入jar包
- 难点2. 编辑YML配置文件 整合数据源 整合mybais
- 要求: 通过测试类 完成CURD操作 库和表使用之前项目的
三
1. SpringBoot整合Mybatis
1.1 跳过测试类打包
1.1.1 需求说明
说明: maven执行打包命令时, 默认条件下,会执行测试类中的方法. 并且这些方法都要执行. 隐患 可能某些测试方法由于种种原因 执行报错. 则install的操作执行失败.
矛盾点: 项目打包与test类之间的问题.
解决方案: 要求install时 不要执行test方法,
1.1.2 跳过测试类打包
<properties>
<!--指定JDK版本-->
<java.version>1.8</java.version>
<!--跳过测试类打包-->
<skipTests>true</skipTests>
</properties>
1.2 创建项目
1.2.1 手动创建项目
- 选择方式
2.编辑项目
3.导入pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.3</version>
<relativePath/>
</parent>
<properties>
<!--指定JDK版本-->
<java.version>1.8</java.version>
<!--跳过测试类打包-->
<skipTests>true</skipTests>
</properties>
<!--按需导入
历史说明: 2010 原来SSM 需要手动的编辑大量的的配置文件
思想: SpringBoot使用体现了"开箱即用"的思想
-->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<!--Springboot的启动器 在内部已经将整合的配置写好,实现拿来就用-->
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--支持热部署 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<!--引入插件lombok 自动的set/get/构造方法插件 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<!--build标签
springboot项目在打包部署发布时,需要依赖maven工具API
如果不添加该插件,则直接影响项目发布
-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.5.3</version>
</plugin>
</plugins>
</build>
- 编辑主启动类
@SpringBootApplication
public class RunAPP {
public static void main(String[] args) {
SpringApplication.run(RunAPP.class,args);
}
}
1.3 导入数据库
1.3.1 链接数据库
1.3.2 导入数据库
说明: 导入数据库之后,需要刷新数据库即可.
注意事项: 使用资料中提供的版本.
1.4 整合mybatis
1.4.0 序列化作用
1.4.1 导入jar包
导入3个jar包
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.3</version>
<relativePath/>
</parent>
<properties>
<!--指定JDK版本-->
<java.version>1.8</java.version>
<!--跳过测试类打包-->
<skipTests>true</skipTests>
</properties>
<!--按需导入
历史说明: 2010 原来SSM 需要手动的编辑大量的的配置文件
思想: SpringBoot使用体现了"开箱即用"的思想
-->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<!--Springboot的启动器 在内部已经将整合的配置写好,实现拿来就用-->
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--支持热部署 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<!--引入插件lombok 自动的set/get/构造方法插件 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--引入数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--springBoot数据库连接 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!--spring整合mybatis 暂时 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
</dependencies>
<!--build标签
springboot项目在打包部署发布时,需要依赖maven工具API
如果不添加该插件,则直接影响项目发布
-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.5.3</version>
</plugin>
</plugins>
</build>
1.4.2 编辑YML文件
编辑 1.数据源 2.整合mybatis
#端口配置
server:
port: 8090
#配置数据源
spring:
datasource:
#如果使用高版本驱动 则添加cj
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/jt?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
username: root
password: root
#Spring整合Mybatis
mybatis:
#定义别名包
type-aliases-package: com.jt.pojo
#导入映射文件
mapper-locations: classpath:/mappers/*.xml
#开启驼峰映射
configuration:
map-underscore-to-camel-case: true
spring:
datasource:
#如果使用高版本驱动 则添加cj
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/jt?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
username: root
password: root
数据库配置参数:
1.serverTimezone=GMT%2B8& 时区
2.useUnicode=true& 是否使用unicode编码
3.characterEncoding=utf8& 字符集使用utf-8
4.autoReconnect=true& 自动重连
5.allowMultiQueries=true 运行批量操作
1.4.3 编辑映射文件
关键词: namespace id 标签 别名 resultType resultMap
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.jt.mapper.UserMapper">
<!--Oracle中 ;号必定报错 不要加;号 -->
<select id="findAll" resultType="com.jt.pojo.User">
select * from demo_user
</select>
</mapper>
1.4.4 编辑主启动类
关键词: mapper/dao(接口) Spring为接口创建对象
说明: @Mapper注解 一般标识接口中. 如果有多个接口,则应该标识多次.
优化: 可以采用 @MapperScan(“xxxx”)进行优化.
1.4.5 编辑测试类
@SpringBootTest
public class TestMybatis {
@Autowired
private UserMapper userMapper; //必须有对象!!!!
@Test
public void test01(){
List<User> userList = userMapper.findAll();
System.out.println(userList);
}
}
1.5.6 关于整合的BUG
说明: 如果xml的映射文件,则根目录下(不建议), 则加载时采用classpath*😕*.xml的结构.
强制加载根目录下的所有的xml文件.
1.5.7 IDEA关于Mapper注入问题
说明: 通过包扫描的路径,在运行期间实现对象的创建. 实现了数据的绑定.所以下边的红线属于误报…
解决方案(一):
修改IDEA 数据校验 将√号去除即可.
解决方案(二):
如果是最新版本的IDEA 则修改如下的选项
2. MybatisPlus学习
2.1 ORM思想
对象关系映射(英语:Object Relational Mapping,简称ORM,或O/RM,或O/R mapping),是一种程序设计技术,用于实现面向对象编程语言里不同类型系统的数据之间的转换。
总结: 以面向对象的方式操作数据库.
知识升华:
1. 以对象的方式实现数据库CRUD操作.
2. 要求通过某种机制将对象动态的转化为Sql,之后实现数据库操作.(自己不写Sql)
2.2 Mybatis优点/缺点
优点:
1.Mybatis 内部整合了JDBC, 简化了持久层开发.
2.Mybatis可以自动的封装结果集的对象 满足ORM中的一个条件.所以将Mybatis称之为半自动化的ORM映射框架.
3.Mybatis有缓存机制 一级缓存/二级缓存.提高用户得查询效率.
4.Mybatis支持多种类型的数据库. 整合简单.
缺点:
1.实现数据的封装 resultMap 封装复杂!!!
2.针对于单表的CRUD的操作 不够便捷. Sql都需要手写!!!
3.个别的情况下二级缓存配置不生效!!!
2.3 MybatisPlus介绍
MyBatis-Plus (opens new window)(简称 MP)是一个 MyBatis (opens new window)的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
特性:
无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
内置性能分析插件:可输出 SQL 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作
2.4 MP入门案例
2.4.1 导入jar包
<!--spring整合mybatis-plus 删除mybatis的包 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3</version>
</dependency>
2.4.2 实现映射
关键词: 对象 表 属性 字段 一一映射.
说明:
1.对象名称与表的名称一一对应.
2.对象的属性与表中的字段一一对应.
2.4.3 继承特定的接口
关键词: 封装—多态—继承
说明: MP在内部准备一个BaseMapper的接口 BaseMapper内部几乎将单表的CURD操作 都进行了编辑. 用户自己的Mapper接口可以继承即可.
2.4.4 编辑YML文件
说明: 将mybatis 修改为mybatis-plus
#Spring整合MP
mybatis-plus:
#定义别名包
type-aliases-package: com.jt.pojo
#导入映射文件
mapper-locations: classpath:/mappers/*.xml
#开启驼峰映射
configuration:
map-underscore-to-camel-case: true
2.4.5 Demo测试
@SpringBootTest
public class TestMP {
@Autowired
private UserMapper userMapper;
/**
* 完成数据的入库操作
* 新增user数据(name="阿富汗",age=40,sex="厉害")
* Sql: insert into demo_user value(xx,xx,xx,xx)
* 思考: MP实现入库流程!!!
*/
@Test
public void test01(){
User user = new User();
user.setName("阿富汗").setAge(40).setSex("厉害");
//以对象的方式操作数据!!!
userMapper.insert(user);
System.out.println("入库操作成功!!!!");
}
}
2.5 MP操作原理
问题: 用户操作对象,如何实现数据库操作!!!
分析问题: 对象如何转化为Sql.
2.5.1 核心理论
1.例子: userMapper.insert(user);
dogMapper.insert(dog);
Sql:
1. insert into 表名(字段名称…) value (属性的值…)
总结: MP将常规的操作进行抽取, 采用公共的接口进行定义. 之后只需要按照用户的参数, 动态的拼接Sql即可.
2.5.2 MP理论流程(了解)
重点: 对java反射机制 有一定的了解
-
userMapper.insert(user); 用户调用接口方法,完成业务.
-
根据用户传递的对象,user.getClass(),获取的是类型.
Class userClass = user.getClass(); -
根据class类型,通过反射动态获取指定的注解
TableName tableName = (TableName) userClass.getAnnotation(TableName.class); -
获取表名 String 表名 = tableName.value();
-
根据User的类型可以获取属性名称. 根据属性名称 可以获取注解 @TableField(“name”),
最终获取字段的名称. -
拼接Sql: insert into demo_user(id,age,name,sex) value (user.getId()…)
-
最终将Sql 由MP交给Mybatis执行sql. 最终实现数据的入库操作.
2.5.3 MP预习
1.参考官网 了解MP基本CURD用法.
2. 提高项目整合能力
四
1. 脚手架安装
1.1 导入项目
说明: 前端的完整项目在码云资源文件中. 用户将资源下载到本地 通过vue-cli客户端导入即可.
- 路径说明
- 挑选路径 点击导入即可
- 运行项目效果如图
2. MybatisPlus 学习
2.1 MP入门案例
package com.jt;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.jt.mapper.UserMapper;
import com.jt.pojo.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@SpringBootTest
public class TestMP {
@Autowired
private UserMapper userMapper;
/**
* 完成数据的入库操作
* 新增user数据(name="中秋",age=40,sex="厉害")
* Sql: insert into demo_user value(xx,xx,xx,xx)
* 思考: MP实现入库流程!!!
* 选择A: 讲mp入库流程 理论为主.
*/
@Test
public void test01(){
User user = new User();
user.setName("中秋").setAge(40)
.setSex("厉害");
//以对象的方式操作数据!!!
userMapper.insert(user);
System.out.println("入库操作成功!!!!");
}
/**
* 以mybatis的方式实现数据库查询
* 1.实现user入库操作 insert into
* 2.update 将name="国庆" 改为 name="中秋"
* 3.delete 将name="国庆"数据删除.
* 4.select 查询 name="小乔" 并且 性别 ="女"
* 5.select 查询age < 18岁 性别="女"
* 6.select 查询 name包含 '君'字的数据
* 7.select 查询 sex="女" 按照年龄倒序排列.
* 8.根据 name/sex 不为null的数据查询. 动态Sql!!
* name="xxx" sex=null
* name="xxx" sex="xx"
* name=null sex=null
*/
//1.查询ID查询数据库 id=231 主键查询
@Test
public void selectById(){
int id = 231; //模拟用户参数.
User user = userMapper.selectById(id);
System.out.println(user);
}
/**
* 2.查询 name="小乔" 并且 性别 ="女"
* 思路: 如果将来有多个结果 则使用List进行接收.
* Sql: select * from demo_user where name="小乔" and sex="女"
* 注意事项: 默认的连接符 and
*/
@Test
public void select01(){
//1.通过对象封装数据
User user = new User();
user.setName("小乔").setSex("女");
//2.构建条件构造器 根据对象中不为null的属性充当where条件!
QueryWrapper<User> queryWrapper = new QueryWrapper(user);
//3.根据条件构造器 实现数据查询
List<User> userList = userMapper.selectList(queryWrapper);
System.out.println(userList);
}
/**
* 3.查询 name="小乔" 并且 性别 ="女"
* 逻辑运算符: = eq, > gt, < lt
* >= ge, <= le
* != ne
*/
@Test
public void select02(){
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("name","小乔")
.eq("sex","女");
List<User> userList =
userMapper.selectList(queryWrapper);
System.out.println(userList);
}
/**
* 4.案例: select 查询age < 18岁 性别="女"
* 方式1: 利用mybatis方式实现
* 方式2: 利用MP方式实现
*/
@Test
public void select03(){
//1.mybatis写法
List<User> userList = userMapper.findList(18,"女");
System.out.println(userList);
//2.mp写法
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.lt("age",18)
.eq("sex","女");
List<User> userList2 = userMapper.selectList(queryWrapper);
System.out.println(userList2);
}
/**
* 5.select 查询 name包含 '君'字的数据
* 关键字: like "%xxx%"
* 以君开头: likeRight "君%"
* 以君结尾: likeLeft "%君"
*/
@Test
public void select04(){
QueryWrapper queryWrapper = new QueryWrapper();
queryWrapper.like("name","君");
List<User> userList = userMapper.selectList(queryWrapper);
System.out.println(userList);
}
/**
* 6.select 查询 sex="女" 按照年龄倒序排列.
*/
@Test
public void select05(){
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("sex","女")
.orderByDesc("age");
List<User> userList = userMapper.selectList(queryWrapper);
System.out.println(userList);
}
/**
* 7.需求: 动态Sql查询. 如果数据有值 则拼接where条件.
* 如果数据为null 则不拼接where条件
* 语法: condition: true 拼接where条件
* false 不拼接where条件
*/
@Test
public void select06(){
String name = "貂蝉";
int age = 19;
boolean nameFlag = name == null ? false : true;
boolean ageFlag = age == 0 ? false : true;
QueryWrapper<User> queryWrapper = new QueryWrapper();
queryWrapper.eq(nameFlag,"name",name)
.eq(ageFlag,"age",age);
List<User> userList = userMapper.selectList(queryWrapper);
System.out.println(userList);
}
/**
* 8.批量查询 查询id= 1,4,5,6......的数据
*/
@Test
public void selectIn(){
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.in("id",1,4,5,6);
List<User> userList = userMapper.selectList(queryWrapper);
System.out.println(userList);
//数组在未来由用户负责传递. 注意使用包装类型
Integer[] array = new Integer[]{1,4,5,6};
//数组转化为List集合
List ids = Arrays.asList(array);
List<User> userList2 = userMapper.selectBatchIds(ids);
System.out.println(userList2);
}
/**
* 9.查询性别为男的用户,只查询ID字段
* selectObjs(); 只查询第一列字段(主键)
* 实际用途: 根据业务只需要主键的查询
*/
@Test
public void selectObjs(){
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("sex","男");
List<Object> ids = userMapper.selectObjs(queryWrapper);
System.out.println(ids);
}
}
2.2 关于查询总结
1.MP核心: 以对象的方式操作数据库
2.条件构造器: new QueryWrapper<>(); 动态拼接where条件.
3.拼接规则: 根据对象中不为null的属性充当where条件.
4.特殊转义字符: = eq, > gt, < lt, >= ge, <= le,!= ne
5.xml文件中的万能的转义字符: <![CDATA[ sql语句 ]]>
6.关键字: like order by in
7.动态Sql语法:
condition: true 拼接where条件
false 不拼接where条件
2.3 MP更新操作
3 前后端交互
3.1 框架之间的关系
1.SpringBoot (工具/框架的框架) 2.Spring(整合第三方)
3.SpringMVC(实现前后端交互) 4.Mybatis/MP(实现持久化操作)
3.2 完成后端层级代码
说明: MVC思想主要的目的实现代码的解耦. 根据MVC思想 演化出层级代码结构 Controller/Service /Dao/Mapper
编码顺序: 1.编辑POJO 2.编辑Mapper接口 3.编辑Service层 4.编辑Controller
3.3 查询案例
3.3.1 业务需求
用户浏览器输入地址: http://localhost:8090/findAll,
要求: 利用MP查询数据库,返回集合的JSON数据.
3.3.2 编辑UserController
package com.jt.controller;
import com.jt.pojo.User;
import com.jt.service.UserService;
import com.jt.service.UserServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
//@Controller
//@ResponseBody //将服务端数据转化为JSON串返回
public class UserController {
//编码规则: 面向接口编程 解耦
@Autowired
private UserService userService;
/**
* 查询所有的数据
* URL: http://localhost:8090/findAll
* 参数: 没有参数
* 返回值: List<User>
*/
@RequestMapping("/findAll")
public List<User> findAll(){
return userService.findAll();
}
}
3.3.3 编辑UserServiceImpl
@Service
public class UserServiceImpl implements UserService{
@Autowired
private UserMapper userMapper;
//查询所有的数据 没有where条件所以参数为null
@Override
public List<User> findAll() {
return userMapper.selectList(null);
}
}
3.3.4 页面效果展现
SpringMVC调用原理(自己学习)
SpringMVC参数传值原理-Servlet机制
五
1. SpringMVC参数传递
1.1 简单参数接收
1.1.1 需求说明
根据id查询用户信息 http://localhost:8090/findUserById?id=1
1.1.2 编辑UserController
/**
*
* 不同类型的请求注解说明:
* @PostMapping("")
* @PutMapping("")
* @DeleteMapping("")
* 参数说明:
* 1.参数名称必须与URL中的名称一致.
* 2.SpringMVC可以根据用户的需求,自动的实现类型的转化
* 底层实现: springmvc所有的参数默认都是String类型
* 根据用户参数的类型,自动实现转化.
*
* URL地址: http://localhost:8090/findUserById?id=1
* 请求类型: GET/DELETE /POST/PUT
* 参数: id=1
* 返回值结果: User对象的json串
*
*/
//@RequestMapping(value = "/findUserById",method = RequestMethod.GET)
@GetMapping("/findUserById") //只允许接收get类型
public User findUserById(Integer id){
return userService.findUserById(id);
}
1.1.3 编辑UserServiceImpl
@Override
public User findUserById(Integer id) {
return userMapper.selectById(id);
}
1.1.4 页面效果展现
1.2 对象参数接收
1.2.1 需求
案例: 根据name=“王昭君” sex=女 查询用户数据?
URL: http://localhost:8090/findUserByNS?name=王昭君&sex=女
1.2.2 编辑UserController
/**
* 规则: SpringMVC 可以利用对象的方式接收
* 底层实现: 参数name="xxx" 拼接set形成setName,之后检查对象中
* 是否有对应的setName(), 如果匹配该方法,则为对象赋值.
* 注意事项: 参数名称最好以属性名称一致
*
* URL地址: http://localhost:8090/findUserByNS?name=王昭君&sex=女
* 参数: name=xxx&sex=xx
* 返回值:List<User>
*/
@GetMapping("/findUserByNS")
public List<User> findUserByNS(User user){
return userService.findUserByNS(user);
}
1.2.3 编辑UserServiceImpl
//MP 可以根据对象中不为null的属性拼接where条件
@Override
public List<User> findUserByNS(User user) {
QueryWrapper<User> queryWrapper = new QueryWrapper<>(user);
return userMapper.selectList(queryWrapper);
}
1.2.4 页面效果展现
1.3 RestFul参数接收
1.3.1 RestFul介绍
REST 指的是一组架构约束条件和原则。满足这些约束条件和原则的应用程序或设计就是 RESTful。
定义: RESTFul 是一种请求的规则(语法/定义)
1.3.2 RESTFul说明
Get请求: http://localhost:8090/findUserByNS?name=王昭君&sex=女
信息: 1.查询请求
2.参数直观 name=xxx
3.请求的结构冗余. 不合适多个参数的写法.
请求优化:
http://localhost:8090/user/王昭君/女
优势:
1. 用户不能了解请求的意图 规定:请求方法名称不能出现 “动词”,只能写名词.
2. 参数保密, 只有后端服务器清楚参数的意义.
3. 请求字节传输量少 简洁.
注意事项:
1. URL地址中参数与参数之间使用 /分隔.
2. 请求的参数的位置一旦固定,不可轻易修改.
3. 用户发请求时,就应该按照restFul的结构执行.
4. restFul请求一般以get请求为主. put/delete/post
1.3.3 RESTFul案例
查询 name=“貂蝉” age> 10 岁 的用户.
URL: http://localhost:8090/user/貂蝉/10
/**
* 后端服务器接收规则:
* 1.参数与参数之后使用 /分隔
* 2.参数的位置一旦确定,一般不变.
* 3.接收的参数使用 {形参变量}
* 4.使用@PathVariable 接收
* 5.如果参数有多个建议使用对象接收 参数必须与属性一致,SpringMVC自动封装
* 注意事项: 如果名称不统一,则需要转化 具体如下:
* @PathVariable("name") String username
*
* url: http://localhost:8090/user/貂蝉/10
* 参数: name/age
* 返回值: List<User>
*/
@GetMapping("/user/{name}/{age}")
public List<User> findUserByNA(User user){
return userService.findUserByNA(user);
}
/*
说明: restFul写法1
@GetMapping("/user/{name}/{age}")
public List<User> findUserByNA(@PathVariable String name,
@PathVariable Integer age){
System.out.println(name);
System.out.println(age);
return null;
}*/
//规则: 字段与属性的逻辑运算符为 '=号'时使用实体(user)对象封装
//查询 name="貂蝉" age> 10 岁 的用户.
@Override
public List<User> findUserByNA(User user) {
QueryWrapper<User> queryWrapper = new QueryWrapper();
queryWrapper.eq("name",user.getName())
.gt("age",user.getAge());
return userMapper.selectList(queryWrapper);
}
1.3.4 同名提交问题
用户查询id=1,3,4,5的数据. 如果有同名参数一般采用 ',号 ’ 分隔
URL: http://localhost:8090/getUserByIds?ids=1,3,4,5
/**
* 规则: 如果参数使用,号分隔,则SpringMVC可以自动的转化为数组.
* 查询多个用户
* URL: http://localhost:8090/getUserByIds?ids=1,3,4,5
* 参数: ids = 1,3,4,5
* 返回值: List<User>
*/
@GetMapping("/getUserByIds")
public List<User> getUserByIds(Integer[] ids){
return userService.getUserByIds(ids);
}
@Override
public List<User> getUserByIds(Integer[] ids) {
List<Integer> idList = Arrays.asList(ids);
return userMapper.selectBatchIds(idList);
}
2 VUE课题笔记
2.1 VUE 入门案例
2.2 var/let/const说明
关于JS中变量定义说明:
1.var 没有作用域的概念 容易出现安全性问题. 成员变量
2.let 作用于var类似, 有作用域的概念 局部变量
3.const 定义常量
2.3 v-text/v-html说明
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>入门案例</title>
</head>
<body>
<div id="app">
<!-- 规则说明:
1.{{msg}} 当页面没有渲染成功时.以原标签展现.
2.v-text 当页面没有渲染完成,则不予展现.
3.v-html 将html代码片段 渲染展现
-->
<h1>{{msg}}</h1>
<!-- 1.v-text -->
<h1 v-text="msg"></h1>
<!-- 2.v-html -->
<div v-html="div"></div>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
msg: "vue入门案例 哈哈哈哈",
div: "<h3>我是一个标题标签</h3>"
}
})
</script>
</body>
</html>
2.4 双向数据绑定
2.4.1 页面demo
2.4.2 MVVM思想:
M: model 数据层
V: view 视图层
VM: 视图数据的控制层
流程:
1. Model变化, 虚拟DOM操作内存数据变化. 所以页面变化.
2. View变化, 虚拟DOM操作内存数据变化, 数据变化.
2.5 事件绑定
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>事件绑定</title>
</head>
<body>
<div id="app">
<!-- 事件绑定的语法:
v-on:click="简单的计算"
v-on:click="点击事件"
-->
展现数据: {{num}}
<button v-on:click="num++">自增</button>
<!-- <button v-on:click="addNum">自增函数</button> -->
<button @click="addNum">自增函数</button>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
num: 100
},
methods: {
addNum: function(){
this.num ++
}
}
})
</script>
</body>
</html>
2.6 分支结构
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>分支结构</title>
</head>
<body>
<div id="app">
<!-- 需求: 用户可以手动的输入成绩.
根据成绩,自动计算等级.
等级规范:
>=90 优秀
>=80 良好
>=70 中等
否则 继续努力
命令:
1.v-if 如果判断为true 则展现元素
2.v-else-if 如果不满足v-if,检查是否满足v-else-if
3.v-else 上述都不满足 才展现.
-->
请输入成绩: <input type="text" v-model="score"/><br>
等级:
<h3 v-if="score >=90">优秀</h3>
<h3 v-else-if="score >=80">良好</h3>
<h3 v-else-if="score >=70">中等</h3>
<h3 v-else>继续努力</h3>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
score: 0
},
methods: {
}
})
</script>
</body>
</html>
2.7 循环结构
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>循环结构</title>
</head>
<body>
<div id="app">
<!-- 指令: v-for 展现的标签 -->
<!-- 案例1: 以循环方式输出爱好 -->
<div id="demo1">
<!-- 如果只有一个参数,则value表示数据 -->
<p v-for="value in hobby" v-text="value"></p>
</div>
<hr />
<!-- 案例2: 以循环方式输出爱好 展现下标
arg1: value的值的数据
arg2: 下标的数据.
-->
<div id="demo2">
<p v-for="arg1,arg2 in hobby">
<span v-text="arg2+1"></span>
----
<span v-text="arg1"></span>
</p>
</div>
<hr />
<div id="demo3">
<table border="1px" width="90%">
<tr align="center">
<td>编号</td>
<td>名称</td>
<td>年龄</td>
</tr>
<tr align="center" v-for="user in userList">
<td v-text="user.id"></td>
<td v-text="user.name"></td>
<td v-text="user.age"></td>
</tr>
</table>
</div>
<hr />
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
hobby: ["敲代码","改BUG","钻研技术"],
userList: [{id:100,name:"泰森",age:60},
{id:101,name:"嘴炮",age:40},
{id:102,name:"泰坦巨猿",age:300000},
],
},
methods: {
}
})
</script>
</body>
</html>
六
1.MVC思想说明
2. VUE基本用法
2.1 表单双向数据绑定
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>表单双向数据绑定</title>
</head>
<body>
<div id="app">
<!-- 问题: 表单中的哪些标签可以使用双向数据绑定
双向数据绑定说明: 用户可以录入的标签.
答案:
1.文本框 2.单选框 3.多选框 4.下拉框 5.文本域
-->
<form action="http://baidu.com">
<div>
用户名: <input type="text" v-model="username"/>
</div>
<div>
性别: <input type="radio" value="男" name="sex" v-model="sex"/>男
<input type="radio" value="女" name="sex" v-model="sex"/>女
</div>
<div>
爱好:
<input type="checkbox" name="hobby" value="敲代码" v-model="hobby"/>敲代码
<input type="checkbox" name="hobby" value="打游戏" v-model="hobby"/>打游戏
<input type="checkbox" name="hobby" value="打豆豆" v-model="hobby"/>打豆豆
</div>
<div>
<!--
VUE的点击事件中有一个属性,可以取消标签的默认行为
@click.prevent
常用场景:
1. form标签 阻止 action 提交
2. a标签 阻止 href 属性跳转
-->
<input type="submit" value="提交" @click.prevent="formBtn"/>
</div>
</form>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
username: 'aaaa',
sex: '女',
hobby: ['敲代码']
},
methods: {
formBtn(){
alert("点击按钮 ajax提交数据")
}
}
})
</script>
</body>
</html>
2.2 常用双向数据绑定属性
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>表单双向数据绑定</title>
</head>
<body>
<div id="app">
<!--1.v-model.number 将输入的内容转化为数值类型 -->
年龄: <input type="number" v-model.number="ageTest"/>
<button @click="addNum">加法</button>
<br>
<!--2.去除多余的空格 -->
用户名: <input type="text" v-model.trim="username" />
用户输入的长度 {{username.length}}
<br>
<!-- 3.懒加载方式 用户离焦触发 -->
信息: <input type="text" v-model.lazy="msg" /> {{msg}}
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
ageTest: '',
username: '',
msg: ''
},
methods: {
addNum(){
this.ageTest = this.ageTest + 1
}
}
})
</script>
</body>
</html>
2.3 VUE生命周期
2.3.1 如何理解生命周期
说明: VUE中有一整套完整的VUE对象创建/使用/销毁的流程. 如果用户需要在某个特定的点 有特殊的需求,可以对VUE对象进行扩展!
2.3.2 生命周期流程
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>测试vue生命周期函数</title>
</head>
<body>
<!-- 知识梳理:
1.生命周期函数的方法名称 必须固定.
2.生命周期函数是VUE对象特有的函数.应该放到根目录下.
3.当页面渲染成功之后, 一共执行了4个生命周期方法.
第一类: VUE对象的创建.
beforeCreate:对象创建前调用
created: VUE对象已经创建完成之后调用
第二类: VUE对象的挂载(渲染)
beforeMount: el : "#app",VUE对象在找到@APP标签之前先执行该函数.
mounted: 当挂载完成(渲染完成),用户可以看到页面数据之后调用该函数
4. 用户修改阶段:
第三类:
beforeUpdate: 当用户修改数据 点击"回车" 之后调用该函数
过渡: 数据已经被修改
updated: 数据已经被修改之后调用该函数
5. 销毁阶段:
销毁函数,VUE对象默认不会调用. 可以通过函数由用户调用
beforeDestroy: 在销毁方法执行前
destroyed: 标志着VUE对象已经销毁.
-->
<div id="app">
<h3 v-text="msg"></h3>
<button @click="destroy">销毁</button>
</div>
<!--引入js函数类库 -->
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el : "#app",
data : {
msg: "vue生命周期"
},
methods:{
},
//在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用。
beforeCreate(){
console.log("beforeCreate")
},
//在实例创建完成后被立即调用
created(){
console.log("created")
},
//在挂载开始之前被调用:相关的 render 函数首次被调用。
beforeMount(){
console.log("beforeMount")
},
//实例被挂载后调用,这时 el 被新创建的 vm.$el 替换了。
mounted(){
console.log("mounted")
},
//数据更新时调用,发生在虚拟 DOM 打补丁之前
beforeUpdate(){
console.log("beforeUpdate")
},
//由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。
updated(){
console.log("updated")
},
//实例销毁之前调用。在这一步,实例仍然完全可用
beforeDestroy(){
console.log("beforeDestroy")
},
//实例销毁后调用。
destroyed(){
console.log("destroyed")
},
methods:{
destroy(){
this.$destroy()
}
}
})
</script>
</body>
</html>
3 VUE 前后端调用
3.1 前后端调用流程
3.2 Ajax
3.2.1 Ajax介绍
Ajax即Asynchronous Javascript And XML(异步JavaScript和XML)在 2005年被Jesse James Garrett提出的新术语,用来描述一种使用现有技术集合的‘新’方法,包括: HTML 或 XHTML, CSS, JavaScript, DOM, XML, XSLT, 以及最重要的XMLHttpRequest。 [3] 使用Ajax技术网页应用能够快速地将增量更新呈现在用户界面上,而不需要重载(刷新)整个页面,这使得程序能够更快地回应用户的操作。 [3]
特点:
局部刷新,异步访问
3.2.2 Ajax异步原理
请求同步说明:
用户向服务器发起请求,如果服务器正忙,这时程序处于等待的状态.这时页面处于加载 ,同时用户不能操作.
为了让用户的体验更好,则一般采用异步.
异步调用的原理:
步骤:
1. 用户发起Ajax请求, Ajax内部机制,将请求交给Ajax引擎处理.
2. Ajax引擎接收到用户的请求之后,重写发起一个新的请求.访问后端服务器.
3. 当服务器端接收到了 Ajax请求之后,完成业务处理.之后将数据响应给Ajax引擎.
4. Ajax引擎通过事先约定好的 回调函数, 将服务器数据 交还给用户.
5.用户在请求的过程中,可以完成自己的任务.
注意事项: 多个Ajax的请求 不关注顺序.
3.3 Axios 入门案例
3.3.1 编辑页面
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Axios入门案例</title>
</head>
<body>
<h1>Ajax请求练习</h1>
<!-- 使用步骤:
1.导入JS函数类库
2.发起Ajax请求,之后业务处理.
-->
<script src="../js/axios.js"></script>
<script>
/* axios请求方式
1.请求类型
1. get 执行查询业务
2. post 执行form表单提交(登录/新增)
3. put 执行修改操作
4. delete 执行删除业务时.
2. axios语法
axios.get("url地址信息","参数信息")
.then(function(args){
})
3. 关于args参数说明
axios为了统筹请求状态.将所有的数据封装为
promise对象
*/
/*
案例1: 利用axios获取后台用户列表信息
url: http://localhost:8090/findAll
*/
axios.get("http://localhost:8090/findAll")
.then(function(promise){
console.log(promise)
console.log(promise.data)
})
</script>
</body>
</html>
3.3.2 开启跨域
3.3.3 编辑UserController
3.3.4 编辑UserServiceImpl
4. Axios 请求案例
4.1 GET-带参数(方式1)
4.1.1 编辑页面html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>前后端调用</title>
</head>
<body>
<h1>前后端调用</h1>
<script src="../js/axios.js"></script>
<script>
/* 需求1: 查询id=1的用户
GET方式1: 动态拼接参数的写法
url: http://localhost:8090/axios/getUserById?id=1
*/
let url1 = "http://localhost:8090/axios/getUserById?id=1"
axios.get(url1).then(promise => {
console.log(promise.data)
})
</script>
</body>
</html>
4.1.2 编辑AxiosController
@RestController
@CrossOrigin
@RequestMapping("/axios")
public class AxiosController {
@Autowired
private UserService userService;
/**
* URL: http://localhost:8090/axios/getUserById?id=1
* 参数: id=1
* 返回值: User对象
*/
@GetMapping("/getUserById")
public User getUserById(Integer id){
return userService.findUserById(id);
}
}
4.1.3 编辑AxiosService
4.2 GET-带参数(restFul格式)
4.2.1 编辑页面HTML
/*
GET方式2: 利用restFul查询数据
需求: 查询sex=女 age>18
原因: restFul结构越来越受欢迎,则拼接restFul结构 变得繁琐.
模板字符串:
语法: 一对反引号 ``
取值: ${key} 形式取值
优势: 保留代码的格式
*/
let sex2 = "女"
let age2 = 18
//let url2 = "http://localhost:8090/axios/user/"+sex2+"/"+age2
let url2 = `http://localhost:8090/axios/user/${sex2}/${age2}`
axios.get(url2).then(promise => {
console.log(promise.data)
})
4.2.2 编辑AxiosController
4.2.3 编辑UserServiceImpl
4.2.4 页面效果展现
4.3 GET-带参数(对象写法)
4.3.1 编辑html页面
/**
* GET方式3: 利用对象实现参数传递
* 需求3: 查询 name="王昭君" sex=女 age=19
* 参数语法:
* 数据结构: {}
* 关键字key(固定写法): params
* value: 用户需要传递的值
*/
let user = {
name: '王昭君',
sex: '女',
age: 19
}
let url3 = "http://localhost:8090/axios/getUser"
axios.get(url3,{params : user})
.then(promise => {
console.log(promise.data)
})
4.3.2 编辑AxiosController
4.3.3 编辑UserServiceImpl
4.3.4 页面效果展现
七
1. 前后端调用
1.1 请求类型说明
分组:
1.GET/DELETE 用法一致.
2.POST/PUT 用法一致.
1.2 Delete请求
1.2.1 编辑页面HTML
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>DELETE请求</title>
</head>
<body>
<h1>前后端调用-DELETE请求测试</h1>
<script src="../js/axios.js"></script>
<script>
/* DELETE测试1:
需求: 删除ID=232的数据
*/
let url = "http://localhost:8090/axios/deleteById?id=232"
axios.delete(url)
.then( promise => {
console.log(promise.data)
})
</script>
</body>
</html>
1.2.2 编辑AxiosController
1.2.3 编辑UserServiceImpl
1.2.4 页面效果展现
1.3 POST请求
1.3.1 编辑页面html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>POST请求</title>
</head>
<body>
<h1>前后端调用-POST请求测试</h1>
<script src="../js/axios.js"></script>
<script>
/**
* 需求: 实现用户新增操作
* 语法: axios.post(url,新增参数)
* 规则: 如果post/put 传递对象则直接赋值.
*/
let user = {
name: '小燕子',
age: 18,
sex: '女'
}
let url = "http://localhost:8090/saveUser"
axios.post(url,user)
.then(promise => {
console.log(promise.data)
})
</script>
</body>
</html>
1.3.2 请求参数的结构
1.url请求地址
2.参数结构
说明: http协议中只能识别 字符串!!!
1.3.3 编辑 AxiosController
/*
* 需求: 实现用户入库操作
* URL: http://localhost:8090/axios/saveUser
* 参数: {name: "小燕子", age: 18, sex: "女"}
* 返回值: String 新增成功!!!
* 难点:
* json 互转 user对象
* 1. user对象转化为json @ResponseBody
* 2. json转化为user对象 @RequestBody
*/
@PostMapping("/saveUser")
public String saveUser(@RequestBody User user){
userService.saveUser(user);
return "新增用户成功!!!";
}
1.3.4 编辑 AxiosService
1.3.5 页面效果展现
1.4 PUT请求(一)
1.4.1 编辑页面html
/**
* 需求: 要求将id="238"
* 改为name="小鬼当家",age=4,sex=男
*/
let user = {
id: 238,
name: "小鬼当家",
age: 4,
sex: "男"
}
let url = "http://localhost:8090/axios/updateUser"
axios.put(url,user)
.then(promise => {
console.log(promise.data)
})
1.4.2 编辑AxiosController
1.4.3 编辑UserServiceImpl
1.4 PUT请求(二)
1.4.1 编辑页面html
/**
* 需求2: 要求将name="小燕子"
* 改为name="小鬼当家",age=18,sex=男
* 难点: 如果有多个参数,并且重复时 如何封装?
* 解决方案: 1.restFul + 对象
*/
let name = "小燕子"
let user2 = {
name: "小鬼当家",
age: 18,
sex: "男"
}
let url2 = `http://localhost:8090/axios/updateUserByName/${name}`
axios.put(url2,user2)
.then(promise => {
console.log( promise.data)
})
1.4.2 编辑AxiosController
/**
* 需求: 根据name修改数据
* URL: http://localhost:8090/axios/updateUserByName/小燕子
* 参数: name条件, {name: "小鬼当家",age: 18,sex: "男"}
* 返回值: 修改成功
* 注意事项: restFul可以为对象的属性赋值.
* 注意restFul的名称不要与属性重名.否则会覆盖
* 引发BUG
*/
@PutMapping("/updateUserByName/{whereName}")
public String updateByName(@RequestBody User user,
@PathVariable String whereName){
userService.updateUserByName(user,whereName);
return "修改数据成功2222";
}
1.4.3 编辑UserServiceImpl
//userMapper.update(arg1,arg2)
//arg1: set条件的数据,
//arg2: where条件的数据
@Override
public void updateUserByName(User user, String whereName) {
//updateWrapper 与 queryWrapper 功能一样,可以混用
UpdateWrapper<User> updateWrapper = new UpdateWrapper();
updateWrapper.eq("name",whereName);
userMapper.update(user,updateWrapper);
}
2. Vue-Axios案例
2.1 需求说明
- 当展现页面时,用户发起请求 http://localhost:8090/vue/findAll,获取所有的user数据.
- 通过Vue.js 要求在页面中展现数据, 以表格的形式展现.
- 为每行数据添加 修改/删除的按钮
- 在一个新的DIV中 编辑3个文本框 name/age/sex 通过提交按钮实现新增.
- 如果用户点击修改按钮,则在全新的DIV中 回显数据.
- 用户修改完成数据之后,通过提交按钮 实现数据的修改.
- 当用户点击删除按钮时,要求实现数据的删除操作.
2.2 用户列表展现
2.2.1 编辑html页面
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>案例练习</title>
</head>
<body>
<div id="app">
<!-- 新增div -->
<div align="center">
<h3>用户新增</h3>
姓名: <input type="text" />
年龄: <input type="text" />
性别: <input type="text" />
<button>新增</button>
</div>
<hr />
<!-- 修改div -->
<div align="center">
<h3>用户修改</h3>
<p>
编号: <input type="text" disabled/>
姓名: <input type="text" />
</p>
<p>
年龄: <input type="text" />
性别: <input type="text" />
</p>
<p>
<button>修改</button>
</p>
</div>
<hr />
<!-- 展现div -->
<div>
<table border="1px" align="center" width="80%">
<tr align="center">
<th colspan="5"><h3>用户列表</h3></th>
</tr>
<tr align="center">
<th>编号</th>
<th>姓名</th>
<th>年龄</th>
<th>性别</th>
<th>操作</th>
</tr>
<tr align="center" v-for="user in userList">
<th v-text="user.id"></th>
<th v-text="user.name"></th>
<th v-text="user.age"></th>
<th v-text="user.sex"></th>
<th width="20%">
<button>修改</button>
<button>删除</button>
</th>
</tr>
</table>
</div>
</div>
<!-- 1.引入类库 vue/axios -->
<script src="../js/vue.js"></script>
<script src="../js/axios.js"></script>
<!-- 2.创建vue对象 -->
<script>
//3.定义axios请求的前缀
axios.defaults.baseURL = "http://localhost:8090/vue"
const app = new Vue({
el: "#app",
data: {
//1.定义用户数据
userList: []
},
methods: {
getUserList(){
axios.get("/findAll")
.then(promise => {
//console.log(promise.data)
this.userList = promise.data
//console.log(this.userList)
})
}
},
//利用生命周期函数触发ajax
created(){
//alert("生命周期函数")
this.getUserList()
}
})
</script>
</body>
</html>
2.2.2 编辑VueController
@RestController
@CrossOrigin
@RequestMapping("/vue")
public class VueController {
@Autowired
private UserService userService;
/**
* 需求: 查询所有的用户数据
* URL: http://localhost:8090/vue/findAll
* 参数: null
* 返回值: List<User>
*/
@GetMapping("/findAll")
public List<User> findAll(){
return userService.findAll();
}
}
2.2.3 页面效果展现
2.3 用户新增操作
2.3.1 编辑html页面
<!-- 新增div -->
<div align="center">
<h3>用户新增</h3>
姓名: <input type="text" v-model="addUser.name"/>
年龄: <input type="text" v-model="addUser.age"/>
性别: <input type="text" v-model="addUser.sex"/>
<!-- 点击调用入库函数 -->
<button @click="addUserBtn">新增</button>
</div>
2.3.2 编辑页面JS
2.3.3 编辑VueController
2.4 用户删除操作
2.4.1 添加点击事件
2.4.2 编辑JS方法
2.4.3 编辑VueController
3 知识点梳理
3.1 vue初始化操作
用法:vue页面初始化时,如果发起ajax请求.
3.2 Axios定义请求前缀
//3.定义axios请求的前缀
axios.defaults.baseURL = "http://localhost:8090/vue"
- 1
- 2
3.3 Axios与Vue数据结合
说明: 当通过axios获取服务器端数据之后,应该将该数据传给vue.data的属性. Vue对象才可以对数据进行二次操作.
3.4 用户输入双向绑定
说明: 在vue.js中 一般情况下,如果遇到用户输入,一般使用双向数据绑定的形式.
作业
- 完成用户的修改操作.
- 将课堂案例写3遍、
八
1.axios 简化操作
1.1 async-await关键字
1.1.1 关键字说明
ES6以后推出的新的代码规范,目的简化现有axios ajax请求的代码结构.提高用户的开发效率.
关键字用法:
1. async 用来标识函数!!!
2. await 用来标识请求的!!!
1.1.2 代码演示
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Axios简化操作</title>
</head>
<body>
<h1>简化操作语法</h1>
<script src="../js/axios.js"></script>
<script>
//1.定义请求的前缀
axios.defaults.baseURL = "http://localhost:8090/vue"
//2.定义函数!!!!
async function findAll(){
//解构赋值操作
let {data: result} = await axios.get("/findAll")
console.log(result)
}
//3.调用函数
findAll()
</script>
</body>
</html>
1.1.3 关于简化操作说明
async - await 是axios为了简化then()的一种全新的语法. 语法如此.
该用法只能用到Ajax请求中.
2 组件化
2.1 知识回顾
说明: 传统的页面开发,会将大量的HTML/CSS/JS进行引入,但是引入之后结构混乱 不便于管理. 开发维护时 成本较高.
组件化思想:
在VUE中 可以将一个组件,看作是一个页面. 在其中可以引入独立的样式/JS/HTML 进行单独的管理.
组件可以进行复用.
关键字: 组件–页面 (html/css/js)
核心知识点: 组件化的思想体现了"分治思想"
补充知识: 为了保证组件化 相互之间互不干扰,则应该在组件内部 单独定义html/js/css.
2.2 组件化入门案例
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>全局组件定义</title>
</head>
<body>
<div id="app">
<!-- 如果需要使用组件,则在vue渲染区中中使用 -->
<!-- 规则:
1.在vue中使用标签时默认都是小写字母.如果需要
引入驼峰规则.则使用-线代替.
2.使用组件通过标签引用
-->
<add-num-com></add-num-com>
<add-num-com></add-num-com>
<add-num-com></add-num-com>
</div>
<!-- 定义模板标签语法 必须有根标签-->
<template id="numTem">
<div>
<h1>我是一个组件</h1>
获取数值: {{num}}
</div>
</template>
<!-- 1.导入JS -->
<script src="../js/vue.js"></script>
<!-- 2.定义全局组件 -->
<script>
/*
组件的定义
参数:
1.组件名称
2.组件实体内容
*/
Vue.component("addNumCom",{
//属性
data(){
return {
//自定义属性
num: 100
}
},
//页面标记
template: "#numTem"
})
/* 2.实例化vue对象 找到区域交给vue渲染 */
const app = new Vue({
el: "#app"
})
</script>
</body>
</html>
2.3 局部组件
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>局部组件定义</title>
</head>
<body>
<div id="app">
<msg-com></msg-com>
<msg-com></msg-com>
</div>
<!-- 定义app2 局部组件只能在特定位置使用,
所以该位置 不能解析-->
<div id="app2">
<msg-com></msg-com>
<msg-com></msg-com>
</div>
<!-- 模板标签必须定义在vue渲染div外边 -->
<template id="msgTem">
<div>
<h3>我是一个局部组件</h3>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
let msgCom = {
template: "#msgTem"
}
//定义局部组件 只对当前vue对象有效
const app = new Vue({
el: "#app",
components: {
//key: value
//msgCom: msgCom,
//如果在JS中key-value一样的.可以只写一个
msgCom
}
})
const app2 = new Vue({
el: "#app2",
})
</script>
</body>
</html>
2.4 关于组件知识点
-
为什么使用组件?
答:
1.原来用户编辑页面时,需要引入/维护大量的JS/CSS等.如果数量很多,则导致代码结构混乱.所以需要引入组件化思想.
2.前端框架为了更好的解耦,采用了"分治"思想的构建代码.前端向后端代码结构看齐.(微服务框架)
3.组件采用树形结构, 可以将功能小型化.单独维护.
4.组件是一个独立的个体,内部包含HTML/CSS/JS
5.使用组件看做就是一个页面 -
如何理解template属性?
答:
1.template标签是组件中定义html标记的模板.
2.template标签必须有根标签div
3.template标签最好定义在body标签内部,写法方便. -
关于组件使用有什么注意事项?
答:
1. 组件的使用必须在VUE对象渲染的区域中使用.
2. 组件有全局的/有局部的.注意作用域.
3. 在html标签中使用组件时,注意大小写问题.如果是驼峰规则,则使用 "-"连接.
4. 组件必须先定义再使用.
3 VUE路由介绍
3.1 路由说明
说明: 用户发起一个请求,在互联网中经过多个站点的跳转.最终获取服务器端的数据. 把互联网中网络的链路称之为路由. (网络用语)
VUE中的路由: 根据用户的请求URL地址,展现特定的组件(页面)信息. (控制用户程序跳转过程)
3.2 入门案例
3.2.1 实现步骤
- 引入JS类库
- 指定路由跳转链接
- 指定路由填充位
- 封装路由对象
- vue对象绑定路由
3.2.2 路由入门案例实现
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>路由入门案例</title>
</head>
<body>
<div id="app">
<h1>实现路由案例</h1>
<!-- 2.定义路由标签
1.a标签说明
标签说明: a标签 超链接标签
href属性: 请求跳转的地址
<a href="http://www.baidu.com">百度</a>
2.路由标签说明
router-link 解析为a标签
to 解析之后变成 href属性
-->
<router-link to="/home">主页</router-link>
<!-- 3.指定路由填充位(占位符)
需要给组件一个展现的位置,需要提前定义.
-->
<router-view></router-view>
</div>
<!-- 定义template标签 -->
<template id="homeTem">
<div>
<h1>这里是系统首页</h1>
</div>
</template>
<!-- 1.引入JS 路由需要依赖vue 注意顺序-->
<script src="../js/vue.js"></script>
<script src="../js/vue-router.js"></script>
<script>
<!-- 4.封装路由对象 -->
let HomeCom = {
template: "#homeTem"
}
let router = new VueRouter({
//routes 定义请求与组件的映射关系
routes: [
{path: "/home", component: HomeCom}
]
})
/* 5.实现路由对象绑定 */
const app = new Vue({
el: "#app",
//router: router
router
})
</script>
</body>
</html>
3.3 路由嵌套机制(难点!!!)
3.3.1 编辑页面JS
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>路由的嵌套</title>
</head>
<body>
<div id="app">
<h1>江哥动物园</h1>
<router-link to="/tiger">老虎</router-link>
<router-link to="/lion">狮子</router-link>
<router-view></router-view>
</div>
<template id="tigerTem">
<div>
<h1>我是一只凶猛的喵</h1>
<img src="images/1.jpeg"/>
</div>
</template>
<template id="lionTem">
<div>
<h1>我是狮子王 辛巴!!!!</h1>
<img src="./images/2.png"/>
<h3>
<!-- 实现路由的嵌套机制 -->
<router-link to="/lion/one">原配</router-link>
<router-link to="/lion/two">小妾</router-link>
<!-- 子级应该在该区域展现数据!!!! -->
<router-view></router-view>
</h3>
</div>
</template>
<template id="oneTem">
<div>
<h1>我是原配-老大</h1>
<img src="images/3.png"/>
</div>
</template>
<template id="twoTem">
<div>
<h1>我是小妾-更喜欢我!!!</h1>
<img src="images/4.png"/>
</div>
</template>
<!-- 1.引入JS 路由需要依赖vue 注意顺序-->
<script src="../js/vue.js"></script>
<script src="../js/vue-router.js"></script>
<script>
let tigerCom = {
template: "#tigerTem"
}
let lionCom = {
template: "#lionTem"
}
let oneCom = {
template: "#oneTem"
}
let twoCom = {
template: "#twoTem"
}
/*
1.如果需要在App根标签中跳转,则写到routes根目录下
2.如果需要进行父子嵌套,则应该使用children属性
3.如果使用children属性,则在自身的router-view展现数据
*/
let router = new VueRouter({
routes: [
{path: "/tiger",component: tigerCom},
{path: "/lion",component: lionCom,
children: [
{path: "/lion/one",component: oneCom},
{path: "/lion/two",component: twoCom},
],
}
]
})
const app = new Vue({
el: "#app",
router
})
</script>
</body>
</html>
3.3.2 页面效果展现
3.4 重定向/转发(web经典面试题)
3.4.1 转发说明
说明: 用户请求服务器时,由服务器将请求转给另一台服务器的过程.叫做转发
3.4.2 重定向说明
说明: 用户请求服务器时,服务器由于特殊原因告知用户应该访问服务器B,之后用户再次发起请求访问服务器B. 这个过程称之为重定向
3.4.3 vue中的重定向
VUE中使用redirect实现重定向效果.
4 作业
- 整理组件相关知识.
- 整理路由跳转流程
- 安装vue脚手架
九
1.构建后台项目
1.1 创建项目
1.2 编辑pom.xml文件
<?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">
<modelVersion>4.0.0</modelVersion>
<groupId>com.jt</groupId>
<artifactId>jt</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.3</version>
<relativePath/>
</parent>
<properties>
<!--指定JDK版本-->
<java.version>1.8</java.version>
<!--跳过测试类打包-->
<skipTests>true</skipTests>
</properties>
<!--按需导入
历史说明: 2010 原来SSM 需要手动的编辑大量的的配置文件
思想: SpringBoot使用体现了"开箱即用"的思想
-->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<!--Springboot的启动器 在内部已经将整合的配置写好,实现拿来就用-->
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--支持热部署 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<!--引入插件lombok 自动的set/get/构造方法插件 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--引入数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--springBoot数据库连接 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!--spring整合mybatis-plus 删除mybatis的包 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3</version>
</dependency>
</dependencies>
<!--build标签
springboot项目在打包部署发布时,需要依赖maven工具API
如果不添加该插件,则直接影响项目发布
-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.5.3</version>
</plugin>
</plugins>
</build>
</project>
1.3 导入src文件
说明: 从码云中下载jt的完整代码,之后粘贴到本地项目中,如图所示:
1.4 修改YML文件
1.5 编辑启动项
- 点击修改
2.为启动项修改文件.方便以后使用
2.前端项目搭建
2.1 关于前端路径说明
为了让全国同学们了解前端的编码规则,所以全国最好统一项目路径.
要求: 将前端项目放到IDEA维护的工作目录中.
如图所示:
2.2 操作步骤
- 码云下载前端资料
2, 项目解压之后复制到IDEA维护的目录中
注意事项: 不要出现目录的嵌套,要求目录的根 就是项目. 如图
3.导入项目 - 运行前端项目
- 命令启动
3 代码调试
3.1 前端代码调试
目的: 经常性的出现本来编辑的是李逵的代码,但是由于代码不熟练,编辑的李鬼. 所以有如下的测试.
编辑App.vue文件
代码测试:
4.用户登录业务实现
4.1 项目划分
前端项目网址: http://localhost:8080/
后端项目网址: http://localhost:8091/
用户操作项目请求的流程: 用户----前端服务器------后端服务器
4.2 用户模块分析
4.2.1 表设计分析
注意事项:
1.密码 加密处理
2.created/updated 每张表中都有该字段
4.2.2 POJO分析
4.3 用户登录业务实现
4.3.1 项目开发流程
人员配置:
1.项目经理(统筹规划协调资源,控制项目进度)
2.职能经理: 凭技术吃饭的 项目组长.
3.产品经理: 根据甲方要求,绘制项目原型. 出原型图
4.UI设计: 根据产品的图,用美学的眼光 绘制图片/按钮/logo
5.前端开发: 绘制页面(html/css/js)
6.后端开发: 根据业务接口文档.实现项目
7.测试工程师: 工具测试(黑白盒测试) 测试机型!!!
8.实施/运维工程师 事情杂/工资低
4.3.2 接口文档说明
- 请求路径: /user/login
- 请求方式: POST
- 请求参数
参数名称 | 参数说明 | 备注 |
---|---|---|
username | 用户名 | 不能为空 |
password | 密码 | 不能为空 |
- 响应数据 SysResult对象
参数名称 | 参数说明 | 备注 |
---|---|---|
status | 状态信息 | 200表示服务器请求成功 201表示服务器异常 |
msg | 服务器返回的提示信息 | 可以为null |
data | 服务器返回的业务数据 | 返回密钥token信息 |
返回值格式如下:
{"status":200,"msg":"服务器调用成功!","data":"1e893a97634847b3a8b499b173bea620"}
4.3.3 编辑SysResult对象
说明: 该对象主要负责前端项目与后端项目的数据交互. 几乎所有的后台服务器的返回值都是SysResult对象.
package com.jt.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import java.io.Serializable;
@Data
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class SysResult implements Serializable {
private Integer status; //200业务成功 201业务失败
private String msg; //服务器提示信息
private Object data; //封装后台返回值
public static SysResult fail(){
return new SysResult(201,"业务执行失败",null);
}
public static SysResult success(){
return new SysResult(200,"业务执行成功!", null);
}
//服务器返回值业务数据
public static SysResult success(Object data){
return new SysResult(200,"业务执行成功!", data);
}
public static SysResult success(String msg,Object data){
return new SysResult(200,msg,data);
}
}
4.3.4 页面JS分析
1.路由跳转规则
2. axios 全局定义
3. axios的使用
4.3.5 MD5介绍
MD5信息摘要算法(英语:MD5 Message-Digest Algorithm),一种被广泛使用的密码散列函数,可以产生出一个128位(16字节)的散列值(hash value),用于确保信息传输完整一致。MD5由美国密码学家罗纳德·李维斯特(Ronald Linn Rivest)设计,于1992年公开,用以取代MD4算法。这套算法的程序在 RFC 1321 标准中被加以规范。1996年后该算法被证实存在弱点,可以被加以破解,对于需要高度安全性的数据,专家一般建议改用其他算法,如SHA-2。2004年,证实MD5算法无法防止碰撞(collision),因此不适用于安全性认证,如SSL公开密钥认证或是数字签名等用途。
理论: 1.MD5不可以被破解的. 只能由明文加密为密文. 不可以反向编译
4.3.6 编辑UserController
用户名: admin123
密码: admin123456
/**
* 业务说明: 实现用户登录
* 思想: 根据参数,查询数据库
* 有值: 用户名和密码正确
* 没有值: 用户名和密码错误
*
* URL:/user/login
* 参数: username/password json
* 类型: post
* 返回值: SysResult对象(token)
*/
@PostMapping("/login")
public SysResult login(@RequestBody User user){
//需求:要求登录成功之后,返回值标识符信息
String token = userService.login(user);
//如果token为null,说明登录失败
if(token == null || token.length()==0){
return SysResult.fail();
}
//否则 正确返回
return SysResult.success(token);
}
4.3.7 编辑UserService
/**
* 思路:
* 1.将密码进行加密处理
* 2.根据username/password查询数据库
* 3.有值:
* 登录成功,返回秘钥
* 没有值:
* 登录失败,返回null
* @param user
* @return
*/
@Override
public String login(User user) {
//1.获取明文
String password = user.getPassword();
//2.加密处理
String md5 = DigestUtils.md5DigestAsHex(password.getBytes());
user.setPassword(md5);
System.out.println(md5);
//3.查询数据库
QueryWrapper<User> queryWrapper= new QueryWrapper<>(user);
//4.获取数据库对象
User userDB = userMapper.selectOne(queryWrapper);
//5.判断登录是否正确
if(userDB == null){
return null;
}
String token = "我是秘钥,明天维护";
return token;
}
晚自习串讲
同源策略
规定: 浏览器要求在解析Ajax请求时,要求浏览器的路径与Ajax的请求的路径必须满足三个要求.则满足同源策略.可以访问服务器.
三个要求:
请求协议://域名:端口号都必须相同!!!
要素:
1.浏览器的请求路径.
2.Ajax请求的网址
同源策略案例
案例1:
1.浏览器地址 http://localhost:8090/findAll
2.Ajax请求地址 http://localhost:8090/aaaa
满足同源策略.服务器可以正常访问.
案例2:
1.浏览器地址 http://localhost:8091/findAll
2.Ajax请求地址 http://localhost:8090/aaaa
不满足同源策略. 端口号不同. 属于跨域请求.
案例3:
1.浏览器地址 http://localhost:8090/findAll
2.Ajax请求地址 https://localhost:8090/aaaa
不满足同源策略. 协议不同. 属于跨域请求.
案例4:
前提: IP与域名映射
1.浏览器地址 http://www.baidu.com/findAll
2.Ajax请求地址 http://10.0.1.1/aaaa
不满足同源策略. 域名不同.
案例5:
1.浏览器地址 http://10.0.1.1:80/findAll
2.Ajax请求地址 http://10.0.1.1/aaaa
满足同源策略. 默认端口号就是80
案例6:
1.浏览器地址 https://10.0.1.1:443/findAll
2.Ajax请求地址 https://10.0.1.1:443/aaaa
满足同源策略
什么是跨域
违反了同源策略的请求,就是跨域的请求.
跨域解决方法
JSONP(了解)
**JSONP(JSON with Padding)是JSON的一种“使用模式”,可用于解决主流浏览器的跨域数据访问的问题。**由于同源策略,一般来说位于 server1.example.com 的网页无法与不是 server1.example.com的服务器沟通,而 HTML 的
核心用法: 利用
返回值语法固定的: callback(JSON数据)
CORS方式
说明: CORS(Cross-origin resource sharing) “跨域资源共享”,现在的主流的浏览器都支持cors的方式. 如果需要跨域,则需要配置响应头信息.标识是否允许.
服务器端标识:
检查响应头信息:
CORS调用原理图:
十
1.用户登录操作
1.1 关于token的说明
1.由于服务器需要标识已经登录的用户,所以服务器动态生成一个独一无二的token,返回给用户.
2.用户将token保存到本地,方便下次访问时携带.
1.2 生成UUID
/**
* 思路:
* 1.将密码进行加密处理
* 2.根据username/password查询数据库
* 3.有值:
* 登录成功,返回秘钥
* 没有值:
* 登录失败,返回null
* @param user
* @return
*/
@Override
public String login(User user) {
//1.获取明文
String password = user.getPassword();
//2.加密处理
String md5 = DigestUtils.md5DigestAsHex(password.getBytes());
user.setPassword(md5);
System.out.println(md5);
//3.查询数据库
QueryWrapper<User> queryWrapper= new QueryWrapper<>(user);
//4.获取数据库对象
User userDB = userMapper.selectOne(queryWrapper);
//5.判断登录是否正确
if(userDB == null){
return null;
}
//6.使用UUID动态生成TOKEN,根据当前时间毫秒数+随机数利用hash算法生成
// 几乎可以保证不重复.
String token = UUID.randomUUID().toString()
.replace("-","");
return token;
}
1.3 Session和Cookie
1.3.1 Session介绍
Session:在计算机中,尤其是在网络应用中,称为“会话控制”。Session对象存储特定用户会话所需的属性及配置信息。这样,当用户在应用程序的Web页之间跳转时,存储在Session对象中的变量将不会丢失,而是在整个用户会话中一直存在下去。当用户请求来自应用程序的 Web页时,如果该用户还没有会话,则Web服务器将自动创建一个 Session对象。当会话过期或被放弃后,服务器将终止该会话。Session 对象最常见的一个用法就是存储用户的首选项。例如,如果用户指明不喜欢查看图形,就可以将该信息存储在Session对象中。有关使用Session 对象的详细信息,请参阅“ASP应用程序”部分的“管理会话”。注意会话状态仅在支持cookie的浏览器中保留。
小结:
1.Session称之为 “会话机制”
2.在浏览器中打开网页 就是一个会话.
3.用户的数据可以保存到会话中,但是有生命周期. 当会话关闭,则数据消失.
1.3.2 Cookie机制
Cookie,有时也用其复数形式 Cookies。类型为“小型文本文件”,是某些网站为了辨别用户身份,进行Session跟踪而储存在用户本地终端上的数据(通常经过加密),由用户客户端计算机暂时或永久保存的信息 [1] 。
说明:
1.cookie是一个小型的文本文件
2.cookie中存储的数据一般都是密文.
3.cookie中的数据的生命周期可控. 几天.几年!!!
1.3.3 session和cookie区别
1.session的数据是临时存储.cookie的数据可以永久保存. (生命周期不同)
2.sesion是浏览器中的一个内存对象!而cookie是一个实际的本地文件. (形式不同).
3.session一般存储是一些涉密数据.cookie一般存储相对公开的数据(免密登录). (安全性)
1.4 前端保存用户信息
login(){
//获取表单对象之后进行数据校验
//valid 表示校验的结果 true表示通过 false表示失败
this.$refs.loginFormRef.validate(async valid => {
//如果没有完成校验则直接返回
if(!valid) return
//如果校验成功,则发起ajax请求
const {data: result} = await this.$http.post('/user/login',this.loginForm)
if(result.status !== 200) return this.$message.error("用户登录失败")
this.$message.success("用户登录成功")
//获取用户token信息
let token = result.data
window.sessionStorage.setItem("token",token)
//用户登录成功之后,跳转到home页面
this.$router.push("/home")
})
}
1.5 实现系统首页跳转
编辑router/index.js 添加组件信息,实现首页跳转
2 权限校验-路由导航守卫
2.1 业务需求
前端页面跳转是通过路由进行控制. 规定: 如果用户没有登录,则只允许访问登录页面.只有登录之后才能访问其它页面.
难点: 如何实现用户请求的拦截.
拦截器作用: 拦截用户的请求.
结果1: 请求放行
结果2: 请求拦截,一般配合重定向使用!!
2.2 路由导航守卫实现
2.2.1 参数说明
2.2.2 配置前端路由导航守卫
const router = new VueRouter({
routes
})
/**
* 定义路由导航守卫
* 参数1. to 路由跳转的网址
* 参数2. from 路由从哪里来
* 参数3. next 是一个函数,表示放行或重定向
* next() 放行
* next("/login") 重定向
* 业务实现:
* 核心逻辑: 检查是否有token.
* 如果访问login页面 直接放行.
* 有token 表示已经登录,放行请求
* 没有token 表示用户没有登录,重定向到登录页面
*/
router.beforeEach((to,from,next) => {
if(to.path === "/login"){
return next()
}
//说明用户访问的页面不是login 请求需要校验
//获取token数据.
let token = window.sessionStorage.getItem("token")
//if(token !==null && token.length>0)
//下列if 解释为: 如果token不为null
if(token){
return next()
}
next("/login")
})
3. 左侧菜单列表实现
3.1 左侧菜单页面分析
3.2 编辑权限层级代码
3.2.1 表设计分析
说明:
name: 权限名称
parent_id: 父级权限 实现了父子级关系.
path: 一级菜单没有路径的, 二级/三级才有路径.
3.2.2 编辑POJO
说明:
1.当前对象中children属性不属于字段,所以使用@TableField(exist = false)进行标识.
2.权限列表中有父子级关系,所以通过children封装子级
3.2.3 搭建Rights层级代码
3.2.4 业务接口说明
只查询一级和二级的信息
- 请求路径 /rights/getRightsList
- 请求类型 GET
- 请求参数 无
- 响应数据 SysResult对象
参数名称 | 参数说明 | 备注 |
---|---|---|
status | 状态信息 | 200表示服务器请求成功 201表示服务器异常 |
msg | 服务器返回的提示信息 | 可以为null |
data | 服务器返回的业务数据 | 返回权限List集合 |
- 响应数据如图所示
3.2.5 编辑RightsController
3.2.6 编辑RightsService
/**
* 实现思路:
* 1.先查询一级菜单信息 parent_id = 0
* 2.将一级菜单循环遍历 一级菜单对象.
* 3.根据一级菜单信息,查询当前菜单下的二级.
* 4.将查询得到的二级菜单,封装到一级对象中
* 实现思路二(扩展):
* 利用左连接 实现关联查询 封装数据.
* @return
*/
@Override
public List<Rights> getRightsList() {
//1.查询一级列表信息
QueryWrapper<Rights> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("parent_id",0);
List<Rights> oneList = rightsMapper.selectList(queryWrapper);
//2.遍历一级列表
for (Rights oneRights : oneList){
//根据一级查询二级
queryWrapper.clear(); //清除之前的条件
queryWrapper.eq("parent_id",oneRights.getId());
List<Rights> twoList = rightsMapper.selectList(queryWrapper);
//将查询的结果封装一级对象中
oneRights.setChildren(twoList);
}
return oneList;
}
3.2.7 页面效果展现
3.3 debug说明
说明: debug是编程中常用的代码的调试工具. 可以让代码一行一行执行.
4. 用户模块实现
4.1 实现用户页面跳转
4.1.1 Home组件说明
业务说明: URL:/user 要求跳转组件 User.vue组件,组件的跳转应该在Home组件内部跳转.
Home组件说明: 在main的区域中,设定 作用 Home的子级标签将来组件在该区域展现.
4.1.2 定义子级组件
说明: 通过路由实现父子组件嵌套.
4.1.3 页面效果展现
5 ElementUI学习(了解)
在elementUI的组件中 绘制项目
<template>
<div>
<!-- 标签使用原则:
规则: 先定义,后使用
语法: el-breadcrumb 找到名称为Breadcrumb组件进行展现
定义elementUI的组件: 按需导入组件
从elementUI中导入特定的组件
1.import {Breadcrumb} from 'element-ui'
2.Vue.use(Breadcrumb) 声明为全局组件.子组件可以直接调用
-->
<!-- 1.添加面包屑导航 -->
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
<el-breadcrumb-item>活动管理</el-breadcrumb-item>
<el-breadcrumb-item>活动列表</el-breadcrumb-item>
<el-breadcrumb-item>活动详情</el-breadcrumb-item>
</el-breadcrumb>
<!-- 2.定义卡片视图 -->
<el-card class="box-card">
<div class="text item">
<!-- 4.栅格: 每行24格,可以动态的缩放 -->
<el-row :gutter="20">
<el-col :span="8">
<!-- 3.定义文本输入框-->
<el-input placeholder="请输入内容" v-model="input3" class="input-with-select">
<el-button slot="append" icon="el-icon-search"></el-button>
</el-input>
</el-col>
<el-col :span="3">
<el-button type="primary">主要按钮</el-button>
</el-col>
</el-row>
</div>
</el-card>
</div>
</template>
<script>
//对外声明组件属性/方法等参数.要被根组件调用
export default {
data(){
return {
}
}
}
</script>
<style lang="less" scoped>
</style>
十一
1. ElementUI说明
1.1 表格数据
1.1.1 编辑UI表格
<!-- 定义表格
:data 一般采用数组的方式 定义表格数据!!!
label="ID" 列字段名称
stripe: 默认为false 启用为true 斑马纹
border: 默认为false 启用为true 边框线
-->
<h1>定义表格</h1>
<el-table :data="tableData" style="width: 100%" stripe border>
<el-table-column
label="编号" prop="id">
</el-table-column>
<el-table-column
label="名称" prop="name">
</el-table-column>
<el-table-column
label="年龄" prop="age">
</el-table-column>
<el-table-column
label="性别" prop="sex">
</el-table-column>
</el-table>
</el-card>
<script>
//对外声明组件属性/方法等参数.要被根组件调用
export default {
data(){
return {
tableData: [
{id:100, name:"黑熊精", age: 3000, sex:"男"},
{id:100, name:"黑旋风", age: 3000, sex:"男"},
{id:100, name:"黑心肠", age: 3000, sex:"男"},
{id:100, name:"黑手党", age: 3000, sex:"男"}
]
}
}
}
</script>
1.1.2 表格数据展现
1.2 分页插件说明
1.2.1 分页页面JS
<!-- 分页工具
:page-sizes 每页展现的条数信息
:total="400" 设定总记录数
layout 展现的数据有哪些
:page-size 初始化时页面的条数 当作参数传递给后端
:current-page 当前看到的页数
@size-change: 当每页的条数发生变化时,触发事件
@current-change:当前展现的页数发生变化时,触发事件
-->
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="100"
:page-sizes="[10, 20, 30, 40]"
:page-size="10"
layout="total, sizes, prev, pager, next, jumper"
:total="1000">
</el-pagination>
2. 用户业务实现
2.1 用户列表展现
2.1.1 页面JS分析
- 生命周期函数
//利用钩子函数实现数据查询
mounted(){
this.getUserList()
}
2.获取用户列表数据
async getUserList(){
const {data: result} = await this.$http.get('/user/list',{
params: this.queryInfo
})
if(result.status !== 200) return this.$message.error("用户列表查询失败")
this.userList = result.data.rows
this.total = result.data.total
console.log("总记录数:"+this.total)
},
3.页面URL分析
2.1.2 业务接口文档
- 请求路径: /user/list
- 请求类型: GET
- 请求参数: 后台使用PageResult对象接收
- 请求案例: http://localhost:8091/user/list?query=查询关键字&pageNum=1&pageSize=10
参数名称 | 参数说明 | 备注信息 |
---|---|---|
query | 用户查询的数据 | 可以为null |
pageNum | 分页查询的页数 | 必须赋值不能为null |
pageSize | 分页查询的条数 | 必须赋值不能为null |
- 响应参数: SysResult对象 需要携带分页对象 PageResult
参数名称 | 参数说明 | 备注信息 |
---|---|---|
status | 状态信息 | 200表示服务器请求成功 201表示服务器异常 |
msg | 服务器返回的提示信息 | 可以为null |
data | 服务器返回的业务数据 | 返回值PageResult对象 |
- PageResult 对象介绍
参数名称 | 参数类型 | 参数说明 | 备注信息 |
---|---|---|---|
query | String | 用户查询的数据 | 可以为null |
pageNum | Integer | 查询页数 | 不能为null |
pageSize | Integer | 查询条数 | 不能为null |
total | Long | 查询总记录数 | 不能为null |
rows | Object | 分页查询的结果 | 不能为null |
- 返回值效果
{"status":200,
"msg":"服务器调用成功!",
"data":
{"query":"",
"pageNum":1,
"pageSize":2,
"total":4,
"rows":[
{"created":"2021-02-18T11:17:23.000+00:00",
"updated":"2021-03-26T06:47:20.000+00:00",
"id":1,
"username":"admin",
"password":"a66abb5684c45962d887564f08346e8d",
"phone":"13111112222",
"email":"1235678@qq.com",
"status":true,
"role":null
},
{"created":"2021-02-18T11:17:23.000+00:00",
"updated":"2021-03-13T08:50:30.000+00:00",
"id":2,
"username":"admin123",
"password":"a66abb5684c45962d887564f08346e8d",
"phone":"13111112223",
"email":"1235678@qq.com",
"status":false,
"role":null
}
]
}
}
2.1.3 编辑PageResult对象
@Data
@Accessors(chain = true)
public class PageResult implements Serializable {
private String query;
private Integer pageNum;
private Integer pageSize;
private Long total;
private Object rows;
}
2.1.3 编辑UserController
/**
* 业务说明: 实现用户列表的分页查询
* URL地址: http://localhost:8091/user/list?query=查询关键字&pageNum=1&pageSize=10
* 参数: pageResult接收
* 返回值: SysResult对象(pageResult)
*/
@GetMapping("/list")
public SysResult getUserList(PageResult pageResult){//3
pageResult = userService.getUserList(pageResult);
return SysResult.success(pageResult);//5
}
2.1.4 编辑UserServiceImpl
/**
* 分页Sql:
* 语法: select * from user limit 起始位置,每页条数
* 规则: 数组 含头不含尾
* 查询第一页:
* select * from user limit 0,10
* 查询第二页:
* select * from user limit 10,10
* 查询第三页:
* select * from user limit 20,10
* 查询第N页:
* select * from user limit (页数-1)条数,条数
* @param pageResult
* 方式1: 手写Sql
* 方式2: MP的方式实现
* @return
*/
@Override
public PageResult getUserList(PageResult pageResult) {
//1.获取总记录数 Integer--long 自动转化
long total = userMapper.selectCount(null);
//2.获取分页结果
int size = pageResult.getPageSize();
int start = (pageResult.getPageNum() - 1) * size;
List<User> userList = userMapper.findListByPage(start,size);
pageResult.setTotal(total)
.setRows(userList);
return pageResult;
}
2.1.5 编辑UserMapper
说明: 利用注解实现Sql查询, 映射文件和注解标签二选一 不能同时使用
2.1.6 页面效果展现
2.2 MP实现用户分页
2.2.1 UserController方法
/**
* 业务说明: 实现用户列表的分页查询
* URL地址: http://localhost:8091/user/list?query=查询关键字&pageNum=1&pageSize=10
* 参数: pageResult接收
* 返回值: SysResult对象(pageResult)
*/
@GetMapping("/list")
public SysResult getUserList(PageResult pageResult){//3
pageResult = userService.getUserList(pageResult);
return SysResult.success(pageResult);//5
}
2.2.2 编辑UserService方法
/*
* 业务说明: 利用MP方式查询数据库.
* 步骤梳理:
* 1.构建MP的分页对象
* 2.根据分页对象查询数据.
* 3.从分页对象中获取数据
* 4.封装PageResult对象
* 5.编辑配置类 封装分页拦截器
* */
@Override
public PageResult getUserList(PageResult pageResult) {
//1.定义分页对象
IPage<User> page = new Page<>(pageResult.getPageNum(),
pageResult.getPageSize());
//2.定义条件构造器 指定动态查询Sql
boolean flag = StringUtils.hasLength(pageResult.getQuery());
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.like(flag, "username",pageResult.getQuery());
//3.进行分页查询
page = userMapper.selectPage(page,queryWrapper);
//4.从封装后的分页对象中获取数据
pageResult.setTotal(page.getTotal())
.setRows(page.getRecords());
return pageResult;
}
2.2.3 编辑配置类
@Configuration
public class MybatisPlusConfig {
// MybatisPlus在执行分页操作时,会被该拦截器拦截
// 拦截器的作用 动态拼接where条件!!!
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MARIADB));
return interceptor;
}
}
2.3 用户状态修改
2.3.1 业务说明
说明: 通过开关 可以控制数据类型的 true/false
参数说明: 1.传递userId主键
2.传递当前状态信息 true/false
2.3.2 页面JS
- 作用域插槽
作用: 可以在表格中的任意单元格,获取当前行的元素信息.
关键语法: scope.row 获取行元素.
<el-table-column prop="status" label="状态">
<!-- 定义作用于插槽-->
<template slot-scope="scope">
<el-switch v-model="scope.row.status" @change="updateStatus(scope.row)"
active-color="#13ce66" inactive-color="#ff4949">
</el-switch>
</template>
</el-table-column>
- Ajax业务调用
async updateStatus(user){
//实现用户状态修改 注意使用模版字符串 ES6中提出的新用法 ${key}
const {data: result} = await this.$http.put(`/user/status/${user.id}/${user.status}`)
if(result.status !== 200) return this.$message.error("用户状态修改失败!")
this.$message.success("用户状态修改成功!")
},
2.3.3 业务接口文档
- 请求路径 /user/status/{id}/{status}
- 请求类型 PUT
- 请求参数: 用户ID/状态值数据
参数名称 | 参数类型 | 参数说明 | 备注信息 |
---|---|---|---|
id | Integer | 用户ID号 | 不能为null |
status | boolean | 参数状态信息 | 不能为null |
- 返回值结果: SysResult对象
{"status":200,"msg":"服务器调用成功!","data":null}
2.3.4 编辑UserController
/**
* 业务说明: 修改状态信息
* URL: /user/status/{id}/{status}
* 参数: id/status
* 返回值: SysResult
*/
@PutMapping("/status/{id}/{status}")
public SysResult updateStatus(User user){
userService.updateStatus(user);
return SysResult.success();
}
- 1
2.3.4 编辑UserService
//规则: 根据对象中不为null的元素当作set条件,
// Id当作唯一where条件
//update user set status=true where id=xxx
@Override
public void updateStatus(User user) {//id/status
userMapper.updateById(user);
}
- 1
2.4 用户新增操作
2.4.1 对话框
说明: 通过属性dialogVisible 控制对话框是否可见.
<!--
:visible.sync 控制对话框是否可见 true/false
-->
<el-dialog
title="提示"
:visible.sync="dialogVisible"
width="30%">
<span>这是一段信息</span>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取 消</el-button>
<el-button type="primary" @click="dialogVisible = false">确 定</el-button>
</span>
</el-dialog>
2.4.2 页面JS分析
2.4.3 用户新增业务接口
- 请求路径 /user/addUser
- 请求类型 POST
- 请求参数: 整个form表单数据
参数名称 | 参数类型 | 参数说明 | 备注信息 |
---|---|---|---|
username | String | 用户名 | 不能为null |
password | String | 密码 | 不能为null |
phone | String | 电话号码 | 不能为null |
String | 密码 | 不能为null |
- 返回值结果: SysResult对象
{"status":200,"msg":"服务器调用成功!","data":null}
- 1
2.4.4 编辑UserController
/**
* 业务: 实现用户新增
* url: /user/addUser
* 参数: 整个form表单 对象 json
* 返回值: SysResult对象
*/
@PostMapping("/addUser")
public SysResult addUser(@RequestBody User user){
userService.addUser(user);
return SysResult.success();
}
2.4.5 编辑UserServiceImpl
/**
* 说明:
* 1.用户入库操作需要手动补齐的数据有
* 创建时间/修改时间 保证一致.
* status=true 手动填充.
* 2.密码加密处理 新增和登录加密算法必须一致
* @param user
*/
@Override
public void addUser(User user) {
String password = user.getPassword();
//加密处理
password = DigestUtils.md5DigestAsHex(password.getBytes());
user.setPassword(password)
.setStatus(true)
.setCreated(new Date())
.setUpdated(user.getCreated());
userMapper.insert(user);
}
十二
1. 用户业务实现
1.1 用户修改数据回显
1.1.1 业务需求分析
当用户点击修改按钮时应该出现弹出框.其中展现用户的数据信息.
展现方式:
1. 获取用户ID,动态查询后台数据库信息,之后实现数据回显. 最新数据!!!
2. 可以利用作用域插槽,获取当前行的对象,之后实现数据回显. 页面数据!!!
1.1.2 页面JS分析
<el-table-column label="操作">
<template slot-scope="scope">
<el-button type="primary" icon="el-icon-edit" size="small" @click="updateUserBtn(scope.row)"></el-button>
<el-button type="danger" icon="el-icon-delete" size="small" @click="deleteUser(scope.row)"></el-button>
</template>
</el-table-column>
//JS方法
async updateUserBtn(user){
this.updateDialogVisible = true
const {data: result} = await this.$http.get("/user/"+user.id)
if(result.status !== 200) return this.$message.error("用户查询失败")
this.updateUserModel = result.data
},
1.1.3 业务接口文档
- 请求路径: /user/{id}
- 请求类型: GET
- 返回值: SysResult对象
参数名称 | 参数说明 | 备注 |
---|---|---|
status | 状态信息 | 200表示服务器请求成功 201表示服务器异常 |
msg | 服务器返回的提示信息 | 可以为null |
data | 服务器返回的业务数据 | 返回user对象 |
- JSON格式如下:
{
"status":200,
"msg":"服务器调用成功!",
"data":{
"created":"2021-02-18T11:17:23.000+00:00",
"updated":"2021-05-17T11:33:46.000+00:00",
"id":1,
"username":"admin",
"password":"a66abb5684c45962d887564f08346e8d",
"phone":"13111112222",
"email":"1235678@qq.com",
"status":true,
"role":null
}
}
1.1.4 编辑UserController
/**
* 业务: 根据id查询数据库
* URL: /user/{id}
* 参数: 主键id
* 返回值: SysResult(User对象)
*/
@GetMapping("/{id}")
public SysResult getUserById(@PathVariable Integer id){
User user = userService.getUserById(id);
return SysResult.success(user);
}
1.1.5 编辑UserService
@Override
public User getUserById(Integer id) {
return userMapper.selectById(id);
}
1.1.6 页面效果展现
1.2 用户修改实现
1.2.1 页面JS分析
说明: 当用户点击确定按钮时,会触发点击事件
<span slot="footer" class="dialog-footer">
<el-button @click="updateDialogVisible = false" >取 消</el-button>
<el-button type="primary" @click="updateUser">确 定</el-button>
</span>
点击确定之后,页面JS 发送情况.
1.2.2 业务接口文档
- 请求路径: /user/updateUser
- 请求类型: PUT
- 请求参数: User对象结构
参数名称 | 参数说明 | 备注 |
---|---|---|
ID | 用户ID号 | 不能为null |
phone | 手机信息 | 不能为null |
邮箱地址 | 不能为null |
- 返回值: SysResult对象
参数名称 | 参数说明 | 备注 |
---|---|---|
status | 状态信息 | 200表示服务器请求成功 201表示服务器异常 |
msg | 服务器返回的提示信息 | 可以为null |
data | 服务器返回的业务数据 | null |
- JSON格式如下:
{
"status":200,
"msg":"服务器调用成功!",
"data":{}
}
1.2.3 编辑UserController
/**
* 业务需求: 实现用户更新操作
* URl: /user/updateUser
* 参数: 对象提交 JSON串 注解接收
* 返回值: SysResult
*/
@PutMapping("/updateUser")
public SysResult updateUser(@RequestBody User user){
userService.updateUser(user);
return SysResult.success();
}
1.2.4 编辑UserService
//不为null的元素当作set条件,Id当作唯一where条件
@Override
public void updateUser(User user) {
userMapper.updateById(user);
}
1.3 用户删除业务实现
1.3.1 页面JS分析
<el-table-column label="操作">
<template slot-scope="scope">
<el-button type="primary" icon="el-icon-edit" size="small" @click="updateUserBtn(scope.row)"></el-button>
<el-button type="danger" icon="el-icon-delete" size="small" @click="deleteUser(scope.row)"></el-button>
</template>
</el-table-column>
async deleteUser(user){
//1.消息确认框
const result = await this.$confirm('此操作将永久删除 '+user.username+', 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).catch(error => error)
//如果确认 confirm 如果取消 cancel
if(result !== 'confirm'){
return this.$message.info("删除取消")
}
const {data: result2} = await this.$http.delete(`/user/${user.id}`)
if(result2.status !== 200) return this.$message.error("删除失败")
this.$message.success("删除成功")
//重新加载 数据
this.getUserList()
}
}
1.3.2 业务接口文档
- 请求路径: /user/{id}
- 请求类型: delete
- 请求参数:
参数名称 | 参数说明 | 备注 |
---|---|---|
ID | 用户ID号 | 不能为null |
- 返回值: SysResult对象
参数名称 | 参数说明 | 备注 |
---|---|---|
status | 状态信息 | 200表示服务器请求成功 201表示服务器异常 |
msg | 服务器返回的提示信息 | 可以为null |
data | 服务器返回的业务数据 | null |
1.3.3 编辑UserController
/**
* 业务: 根据ID删除用户数据
* URL: /user/{id}
* 参数: id
* 返回值: SysResult对象
*/
@DeleteMapping("/{id}")
public SysResult deleteUserById(@PathVariable Integer id){
userService.deleteUserById(id);
return SysResult.success();
}
1.3.4 编辑UserService
@Override
public void deleteUserById(Integer id) {
userMapper.deleteById(id);
}
- 1
2. 全局异常处理机制
2.1 业务需求说明
说明: 如果后台服务器发生运行时异常,应该第一时间通知用户.否则用户没有任何提示.影响用户体验.
分析:
1.页面没有提示的原因是服务器没有返回201的报错数据.
2.后端服务器报错之后,没有异常处理机制
总结:
异常的处理是项目中经常用到的机制,但是如果将大量的异常处理代码写在方法中,则影响程序的结构.导致代码混乱.
2.2 全局异常处理用法
2.2.1 实现原理 AOP
AOP说明:
名称: “面向切面编程”
作用: 在不影响源码的条件下,对方法进行扩展,降低了业务的耦合性.
通知:
1.前置通知: before
2.后置通知: afterReturning
3.异常通知: afterThrowing
4.最终通知: after
上述的四大通知,不能改变程序的运行的状态.
5.环绕通知: around
环绕通知是功能最为强大的通知方法,可以控制程序的流转过程.
2.2.2 全局异常处理实现
/**
* 注解的作用:
* 1.该注解只拦截Controller层抛出的异常信息!
* controller ---Service-----Mapper 异常向上抛出.
* 2.需要配合指定异常的类型.
*/
@RestControllerAdvice
public class SystemAOP {
//当前Controller层,只拦截运行时异常.
//@ExceptionHandler({RuntimeException.class, SQLException.class})
@ExceptionHandler({RuntimeException.class})
public SysResult exception(Exception e){
//控制台打印异常.
e.printStackTrace();
return SysResult.fail();
}
}
3. Spring中的事务机制
3.1 事务
如果后台服务器发生问题,则数据信息应该回滚,而不是提交操作.
事务(Transaction),一般是指要做的或所做的事情。在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。事务通常由高级数据库操纵语言或编程语言(如SQL,C++或Java)书写的用户程序的执行所引起,并用形如begin transaction和end transaction语句(或函数调用)来界定。事务由事务开始(begin transaction)和事务结束(end transaction)之间执行的全体操作组成。
事务作用: 可以保证数据的 /持久性(永久性)/一致性/隔离性/原子性
3.2 控制事务
说明: 在业务层 执行数据库更新操作时,添加注解,控制数据库事务.
4. 实现数据自动填充功能
4.1 业务说明
在完成数据库入库操作时,其中创建时间和修改时间 都是用户必须添加的参数.但是该参数完全由用户填写,显得不智能. 公共的内容应该由框架完成.
4.2 自动填充实现
4.2.1 编辑POJO属性
说明: 在入库操作时,自动填充 created/updated时间.
在修改操作时,自动填充 updated时间.
4.2.2 配置自动填充功能
@Component //将该对象交给Spring容器管理
public class MyMetaObjectHandler implements MetaObjectHandler {
//完成新增入库操作应该如何填充 created/updated
//MetaObject代表自动填充的默认配置.
@Override
public void insertFill(MetaObject metaObject) {
Date date = new Date();
this.setFieldValByName("created",date,metaObject);
this.setFieldValByName("updated",date,metaObject);
}
//完成修改操作应该如何填充 updated
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("updated",new Date(),metaObject);
}
}
5. 商品分类实现
5.1 实现商品分类页面跳转
5.1.1 页面分析
需求: 当用户跳转/itemCat时,应该展现ItemCat.vue组件.
编辑路由:
5.2 ItemCat 设计说明
5.2.1 表设计说明
说明: 商品分类结构的字段信息. 通过parent_id 控制父子级关系.
5.2.2 常见业务说明
说明: 每个层级下,都会有自己的单独的子级. 1级下边有2级 2级下边有3级.
一般层级结构,不要超过3级.
5.2.3 定义POJO属性
5.3 层级代码结构展现
5.4 商品分类数据查询结构
/*查询一级商品分类信息*/
SELECT * FROM item_cat WHERE parent_id=0
/*查询二级商品分类信息*/
SELECT * FROM item_cat WHERE parent_id=438
/*查询三级商品分类信息*/
SELECT * FROM item_cat WHERE parent_id=486
5.5 商品分类页面分析
- 生命周期函数
- 页面Ajax调用
5.6 商品分类业务接口文档
- 请求路径: /itemCat/findItemCatList/{level}
- 请求类型: get
- 请求参数: level
参数名称 | 参数说明 | 备注 |
---|---|---|
level | 查询级别 | 1查询一级分类 2查询1-2 级商品分类 3查询1-2-3级商品分类 |
- 业务说明: 查询3级分类菜单数据 要求三层结构嵌套
- 返回值: SysResult对象
参数名称 | 参数说明 | 备注 |
---|---|---|
status | 状态信息 | 200表示服务器请求成功 201表示服务器异常 |
msg | 服务器返回的提示信息 | 可以为null |
data | 服务器返回的业务数据 | 3级商品分类信息 |
5.7 编辑ItemCatController
/**
* 业务说明: 实现商品分类查询
* URL: /itemCat/findItemCatList/{level}
* 参数: level 查询的层级
* 返回值: SysResult(List)
*/
@GetMapping("/findItemCatList/{level}")
public SysResult findItemCatList(@PathVariable Integer level){
List<ItemCat> itemCatList = itemCatService.findItemCatList(level);
return SysResult.success(itemCatList);
}
5.8 编辑ItemCatService
/**
* 1.查询所有的一级菜单
* 2.遍历一级查询所有的二级菜单
* 3.遍历二级查询所有的三级菜单
* 4. 2级封装3级菜单, 1级封装2级菜单
* @param level
* @return
*/
@Override
public List<ItemCat> findItemCatList(Integer level) {
//记录开始时间
long startTime = System.currentTimeMillis();
QueryWrapper<ItemCat> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("parent_id",0);
List<ItemCat> oneList = itemCatMapper.selectList(queryWrapper);
//2.遍历一级菜单 根据一级查询二级菜单
for (ItemCat oneItemCat : oneList){
queryWrapper.clear();
queryWrapper.eq("parent_id",oneItemCat.getId());
List<ItemCat> twoList = itemCatMapper.selectList(queryWrapper);
//3.将二级列表进行遍历,根据二级ID查询三级
for(ItemCat twoItemCat : twoList){
queryWrapper.clear();
queryWrapper.eq("parent_id",twoItemCat.getId());
List<ItemCat> threeList = itemCatMapper.selectList(queryWrapper);
//数据的封装
twoItemCat.setChildren(threeList);
}
//将二级数据封装到一级中
oneItemCat.setChildren(twoList);
}
long endTime = System.currentTimeMillis();
System.out.println("程序耗时:"+(endTime - startTime));
return oneList;
}
5.9 页面效果展现
作业
- 完成课堂代码,完成课堂业务要求. 用户模块/商品分类模块列表展现
- 完成用户模块的重构
1.1 手写User.VUE页面和JS
1.2 重写用户模块业务 列表/新增/状态修改/修改/删除操作. - 选做 优化ItemCat查询方式 将查询速度优化到 100毫秒之内
十三
1. 商品分类业务实现
1.1 问题说明
原始代码结构.出现2层循环结构. 如果外层循环10个,每个内层循环也是10个.完成这项业务需要查询100次数据库.
矛盾点: 多次查询数据库!
优化的策略: 数据库只查询一次,就可以获取商品分类三级嵌套结构
程序设计:
1.数据结构 Map<父级ID,子级列表> 列表信息中不包含嵌套关系
例如: Map<0,一级列表信息> 一级列表不包含二级/三级
Map<一级ID,二级列表信息> 只有2级列表 不包含3级
Map<二级ID,三级列表信息> 只有3级列表信息.
2.根据数据结构动态根据level查询子级.
1.2 列表业务实现
1.2.1 业务封装原理图
1.2.2 封装Map集合
/**
* 1.查询所有的商品分类列表. 查询一次数据库.
* 2.循环遍历所有的数据,按照parentId,List<ItemCat>方式封装数据.
* @return
*/
private Map<Integer, List<ItemCat>> getMap() {
Map<Integer,List<ItemCat>> map = new HashMap<>();
List<ItemCat> list = itemCatMapper.selectList(null);
for(ItemCat itemCat : list){
//获取parentId
int parentId = itemCat.getParentId();
if(map.containsKey(parentId)){
//key存在
map.get(parentId).add(itemCat);
}else{
//key不存在
List<ItemCat> childrenList = new ArrayList<>();
childrenList.add(itemCat);
//将第一个元素封装到map中
map.put(parentId,childrenList);
}
}
return map;
}
1.2.3 代码优化
/**
* 1.数据结构: Map<K,V> key=parentId value="List<ItemCat>"
* 2.封装Map的数据类型
* 3.如果level=1 只获取一级.
* 4.如果level=2 获取一级,一级嵌套二级
* 5.如果level=3 获取一级,一级嵌套二级,二级嵌套三级.
* @param level
* @return
*/
@Override
public List<ItemCat> findItemCatList(Integer level) {
long startTime = System.currentTimeMillis();
//1.封装Map集合
Map<Integer,List<ItemCat>> map = getMap();
//2.判断level的值
if(level == 1){
return map.get(0);
}
if(level == 2){
return getTwoList(map);
}
//如果level不是1-2级则一定是三级
List<ItemCat> list = getThreeList(map);
long endTime = System.currentTimeMillis();
System.out.println("耗时:"+(endTime - startTime)+"毫秒");
return list;
}
private List<ItemCat> getThreeList(Map<Integer, List<ItemCat>> map) {
//获取一级和二级
List<ItemCat> oneList = getTwoList(map);
//封装三级,遍历二级菜单,之后封装.
for(ItemCat oneItemCat : oneList){
//获取二级集合
List<ItemCat> twoList = oneItemCat.getChildren();
if(twoList == null || twoList.size() == 0){
System.out.println("执行跳过循环操作");
//由于业务数据不合理,跳过本次循环,执行下一次
continue;
}
for (ItemCat twoItemCat : twoList){
//查询三级列表,需要parentId=二级Id
int parentId = twoItemCat.getId();
List<ItemCat> threeList = map.get(parentId);
twoItemCat.setChildren(threeList);
}
}
return oneList;
}
private List<ItemCat> getTwoList(Map<Integer, List<ItemCat>> map) {
//1.先获取一级列表
List<ItemCat> oneList = map.get(0);
//2.根据一级查询二级
for(ItemCat oneItemCat :oneList){
//查询二级,所以parentId是一级的Id
int parentId = oneItemCat.getId();
List<ItemCat> twoList = map.get(parentId);
//封装数据
oneItemCat.setChildren(twoList);
}
return oneList;
}
/**
* 1.查询所有的商品分类列表. 查询一次数据库.
* 2.循环遍历所有的数据,按照parentId,List<ItemCat>方式封装数据.
* @return
*/
private Map<Integer, List<ItemCat>> getMap() {
Map<Integer,List<ItemCat>> map = new HashMap<>();
List<ItemCat> list = itemCatMapper.selectList(null);
for(ItemCat itemCat : list){
//获取parentId
int parentId = itemCat.getParentId();
if(map.containsKey(parentId)){
//key存在
map.get(parentId).add(itemCat);
}else{
//key不存在
List<ItemCat> childrenList = new ArrayList<>();
childrenList.add(itemCat);
//将第一个元素封装到map中
map.put(parentId,childrenList);
}
}
return map;
}
1.3 商品分类状态修改
1.3.1 业务分析
说明: 当用户点击状态码时,应该实现数据的修改操作.
1.3.2 页面JS分析
<el-table-column prop="status" label="状态">
<!-- 定义作用域插槽 展现数据 scope.row展现行级元素 -->
<template slot-scope="scope">
<el-switch v-model="scope.row.status" active-color="#13ce66" inactive-color="#ff4949"
@change="updateStatus(scope.row)"></el-switch>
</template>
</el-table-column>
//根据ID修改状态信息
async updateStatus(itemCat) {
const {
data: result
} = await this.$http.put(`/itemCat/status/${itemCat.id}/${itemCat.status}`)
if (result.status !== 200) return this.$message.error("修改状态失败")
this.$message.success("状态修改成功")
},
1.3.3 业务接口实现
- 请求路径: /itemCat/status/{id}/{status}
- 请求类型: put
- 请求参数:
参数名称 | 参数说明 | 备注 |
---|---|---|
id | 用户ID值 | 不能为null |
status | 用户的状态信息 | 不能为null |
- 返回值: SysResult对象
参数名称 | 参数说明 | 备注 |
---|---|---|
status | 状态信息 | 200表示服务器请求成功 201表示服务器异常 |
msg | 服务器返回的提示信息 | 可以为null |
data | 服务器返回的业务数据 | 可以为null |
1.3.4 编辑ItemCatController
/**
* 业务需求: ItemCat状态修改
* URL地址: /itemCat/status/{id}/{status}
* 参数: id/status
* 返回值: SysResult对象
*/
@PutMapping("/status/{id}/{status}")
public SysResult updateStatus(ItemCat itemCat){
itemCatService.updateStatus(itemCat);
return SysResult.success();
}
1.3.5 编辑ItemCatService
@Override
@Transactional //事务控制
public void updateStatus(ItemCat itemCat) {
itemCatMapper.updateById(itemCat);
}
1.4 商品分类新增
1.4.1 业务说明
说明: 商品分类实现中,需要添加一级/二级/三级分类信息. 但是父级下拉框中勾选1-2级菜单. 因为三级菜单不能当作父级. 当用户编辑完成之后,点击确定实现商品分类入库.
1.4.2 页面JS分析
<!-- 2.1定义一行 使用栅格-->
<el-row>
<el-col :span="24">
<el-button type="primary" @click="showAddItemCatDialog">新增分类</el-button>
</el-col>
</el-row>
//当展现新增商品分类时,应该渲染级联框数据
showAddItemCatDialog() {
this.findParentItemCatList()
this.addItemCatDialogVisible = true
},
async findParentItemCatList() {
//动态获取商品分类信息 type=2表示获取2级商品分类信息
const {
data: result
} = await this.$http.get("/itemCat/findItemCatList/2")
if (result.status !== 200) return this.$message.error("获取商品分类列表失败!!")
this.parentItemCatList = result.data
},
//商品分类确定按钮实现
<span slot="footer" class="dialog-footer">
<el-button @click="addItemCatDialogVisible = false">取 消</el-button>
<el-button type="primary" @click="addItemCatForm">确 定</el-button>
</span>
//新增商品分类JS
async addItemCatForm() {
//先将整个表单进行校验
this.$refs.itemCatFormRef.validate(async validate => {
if (!validate) return
const {
data: result
} = await this.$http.post("/itemCat/saveItemCat", this.itemCatForm)
if (result.status !== 200) return this.$message.error("新增商品分类失败")
this.$message.success("新增商品分类成功!!!")
//新增成功,则刷新分类列表信息
this.findItemCatList();
this.addItemCatDialogVisible = false
})
},
1.4.3 业务接口文档
- 请求路径: /itemCat/saveItemCat
- 请求类型: post
- 请求参数: 表单数据
参数名称 | 参数说明 | 备注 |
---|---|---|
name | 商品分类名称 | 不能为null |
parentId | 用户父级ID | 不能为null |
level | 分类级别 | 1 2 3 商品分类级别 |
- 返回值: SysResult对象
参数名称 | 参数说明 | 备注 |
---|---|---|
status | 状态信息 | 200表示服务器请求成功 201表示服务器异常 |
msg | 服务器返回的提示信息 | 可以为null |
data | 服务器返回的业务数据 | 可以为null |
1.4.4 编辑ItemCatController
/**
* 业务分析: 完成商品分类新增
* 请求类型: POST请求
* URL: /itemCat/saveItemCat
* 参数: 利用ItemCat对象接收 JSON
* 返回值: SysResult对象
*/
@PostMapping("/saveItemCat")
public SysResult saveItemCat(@RequestBody ItemCat itemCat){
itemCatService.saveItemCat(itemCat);
return SysResult.success();
}
1.4.5 编辑ItemCatService
@Override
@Transactional
public void saveItemCat(ItemCat itemCat) {
itemCat.setStatus(true) ;
itemCatMapper.insert(itemCat);
}
1.5 商品分类修改操作
1.5.1 修改按钮JS分析
<el-table-column label="操作">
<!-- 定义作用域插槽 定义标签等级-->
<template slot-scope="scope">
<el-button type="success" icon="el-icon-edit" @click="updateItemCatBtn(scope.row)">编辑</el-button>
<el-button type="danger" icon="el-icon-delete" @click="deleteItemCatBtn(scope.row)">删除</el-button>
</template>
</el-table-column>
//由于有层级关系,所有修改只能修改名称
updateItemCatBtn(itemCat) {
this.updateItemCatForm = itemCat
this.updateItemCatDialogVisible = true
},
1.5.2 修改-确定按钮实现
<!-- 添加修改分类对话框 -->
<el-dialog title="修改商品分类" :visible.sync="updateItemCatDialogVisible" width="50%">
<!-- 定义分类表单 -->
<el-form :model="updateItemCatForm" :rules="rules" ref="upDateItemCatForm" label-width="100px">
<el-form-item label="分类名称:" prop="name">
<el-input v-model="updateItemCatForm.name"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="updateItemCatDialogVisible = false">取 消</el-button>
<el-button type="primary" @click="updateItemCat">确 定</el-button>
</span>
</el-dialog>
async updateItemCat() {
//修改商品分类信息
const {
data: result
} = await this.$http.put('/itemCat/updateItemCat', this.updateItemCatForm)
if (result.status !== 200) return this.$message.error("更新商品分类失败")
this.$message.success("更新商品分类成功")
this.findItemCatList();
this.updateItemCatDialogVisible = false;
},
1.5.3 业务接口文档
- 请求路径: /itemCat/updateItemCat
- 请求类型: put
- 请求参数: 表单数据 ItemCat对象
- 返回值: SysResult对象
参数名称 | 参数说明 | 备注 |
---|---|---|
status | 状态信息 | 200表示服务器请求成功 201表示服务器异常 |
msg | 服务器返回的提示信息 | 可以为null |
data | 服务器返回的业务数据 | 可以为null |
1.5.4 编辑ItemCatController
/**
* 需求: 实现商品分类修改操作
* URL: /itemCat/updateItemCat
* 参数: form表单 json 对象接收
* 返回值: SysResult对象
*/
@PutMapping("/updateItemCat")
public SysResult updateItemCat(@RequestBody ItemCat itemCat){
itemCatService.updateItemCat(itemCat);
return SysResult.success();
}
1.5.5 编辑ItemCatService
@Override
@Transactional
public void updateItemCat(ItemCat itemCat) {
itemCatMapper.updateById(itemCat);
}
1.6 商品分类删除操作
1.6.1 业务说明
规则:
1.如果删除的商品分类是三级,则可以直接删除.
2.如果删除的商品分类是二级,则先删除三级,在删除二级.
3.如果删除的商品分类是一级,则先删除三级/二级/一级
注意事务的控制.
1.6.2 页面JS分析
deleteItemCatBtn(itemCat) {
//删除商品分类信息,如果为父级节点则需要删除所有的子级信息
this.$confirm('此操作将永久删除该数据, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () =>{
//传递分类id
const {data: result} = await this.$http.delete("/itemCat/deleteItemCat",{params:{id:itemCat.id,level:itemCat.level}})
if(result.status !== 200) return this.$message.error("删除商品分类失败")
this.$message.success("删除数据成功")
//删除成功之后,刷新页面数据
this.findItemCatList()
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除AAAAAAA'
});
});
}
1.6.3 业务接口实现
- 请求路径: /itemCat/deleteItemCat
- 请求类型: delete
- 业务描述: 当删除节点为父级时,应该删除自身和所有的子节点
- 请求参数:
参数名称 | 参数说明 | 备注 |
---|---|---|
id | 用户id号 | 不能为null |
level | 商品分类级别 一级,二级,三级 |
- 返回值结果 SysResult对象
参数名称 | 参数说明 | 备注 |
---|---|---|
status | 状态信息 | 200表示服务器请求成功 201表示服务器异常 |
msg | 服务器返回的提示信息 | 可以为null |
data | 服务器返回的业务数据 | 可以为null |
1.6.4 编辑ItemCatController
/**
* 业务需求: 实现商品分类删除
* URL: /itemCat/deleteItemCat?id=xx&level=2
* 参数: id/level
* 返回值: SysResult对象
*/
@DeleteMapping("/deleteItemCat")
public SysResult deleteItemCats(ItemCat itemCat){
itemCatService.deleteItemCats(itemCat);
return SysResult.success();
}
1.6.5 编辑ItemCatService
/**
* 需求: 删除商品分类信息
* 条件: 如果有子级,应该先删除子级.
* Sql:
* DELETE FROM item_cat WHERE (parent_id IN (?,?) OR parent_id = ? OR id = ?)
* @param itemCat
*/
@Override
@Transactional
public void deleteItemCats(ItemCat itemCat) {
int level = itemCat.getLevel();
if(level == 3){
//表示需要删除的数据是三级菜单,可以直接删除
itemCatMapper.deleteById(itemCat.getId());
}
if(level == 2){
QueryWrapper<ItemCat> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("parent_id",itemCat.getId())
.or()
.eq("id",itemCat.getId());
itemCatMapper.delete(queryWrapper);
}
if(level == 1){
//1.必须获取二级ID
QueryWrapper<ItemCat> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("parent_id",itemCat.getId());
//2获取结果的第一列字段(主键) 二级Id
List twoIdsList = itemCatMapper.selectObjs(queryWrapper);
//3.删除三级的数据
queryWrapper.clear();
//删除parent_id中包含二级Id的数据,实则删除的是三级数据
queryWrapper.in(twoIdsList.size()>0,"parent_id",twoIdsList)
.or()
//删除parent_id 等于一级ID的,实则删除的是二级数据
.eq("parent_id",itemCat.getId())
.or()
//删除id=一级Id 则删除一级数据.
.eq("id",itemCat.getId() );
itemCatMapper.delete(queryWrapper);
}
}
十四
1. 商品模块实现
1.1 业务分析
1.1.1 表设计
1.1.2 编辑POJO对象
/**
* @author 刘昱江
* 时间 2021/4/7
*/
@TableName("item")
@Data
@Accessors(chain = true)
public class Item extends BasePojo{
@TableId(type = IdType.AUTO)
private Integer id; //商品Id号
private String title; //商品标题信息
private String sellPoint; //卖点信息
private Integer price; //商品价格 扩大100倍 缩小100倍值不变
private Integer num; //商品数量
private String images; //商品图片
private Integer itemCatId; //商品分类ID号
private Boolean status; //状态信息 0 下架 1 上架
}
1.1.3 编辑Item的层级代码
1.1.4 实现前台页面跳转
页面效果展现:
1.2 商品列表展现
1.2.1 页面JS分析
1.生命周期函数
created() {
//1.获取商品列表数据
this.getItemList()
},
2.实现页面Ajax请求
//实现商品信息分页查询
async getItemList() {
const {
data: result
} = await this.$http.get("/item/getItemList", {
params: this.queryItemInfo
})
if (result.status !== 200) return this.$message.error("商品列表查询失败")
this.itemList = result.data.rows
this.total = result.data.total
},
1.2.2 业务接口说明
- 请求路径: /item/getItemList?query=&pageNum=1&pageSize=10
- 请求类型: get
- 请求参数: 使用pageResult对象接收
参数名称 | 参数说明 | 备注信息 |
---|---|---|
query | 用户查询的数据 | 可以为null |
pageNum | 分页查询的页数 | 必须赋值不能为null |
pageSize | 分页查询的条数 | 必须赋值不能为null |
- 返回值结果:
参数名称 | 参数说明 | 备注 |
---|---|---|
status | 状态信息 | 200表示服务器请求成功 201表示服务器异常 |
msg | 服务器返回的提示信息 | 可以为null |
data | 服务器返回的业务数据 | 商品分页对象 |
1.2.3 编辑ItemController
/**
* 需求: 查询商品列表 分页查询
* URL: /item/getItemList?query=&pageNum=1&pageSize=10
* 参数: pageResult
* 返回值: SysResult(pageResult)
*/
@GetMapping("/getItemList")
public SysResult getItemList(PageResult pageResult){//3
//参照user模块完成商品分页查询.
pageResult = itemService.getItemList(pageResult);//+2
return SysResult.success(pageResult);
}
1.2.4 编辑ItemService
/**
* 需求: 实现商品分页查询
* @param pageResult
* @return
*/
@Override
public PageResult getItemList(PageResult pageResult) {
IPage page = new Page(pageResult.getPageNum(),pageResult.getPageSize());
QueryWrapper<Item> queryWrapper = new QueryWrapper<>();
//判断条件: 用户传递query 则添加where条件
String query = pageResult.getQuery();
boolean flag = StringUtils.hasLength(query);
queryWrapper.like(flag,"title",query);
//page接口原来只有2个,经过分页查询之后,有四个结果
page = itemMapper.selectPage(page,queryWrapper);
long total = page.getTotal();
List<Item> rows = page.getRecords();
return pageResult.setTotal(total).setRows(rows);
}
1.2.5 页面效果展现
列宽度修改: 由于按钮显示不全,所以可以将width改为300px.
1.3 VUE过滤器用法
1.3.1 需求说明
说明: 需要将价格信息缩小100倍. 保留2位小数.
1.3.2 过滤器使用
1.3.3 过滤器定义
说明: 由于过滤器是全局变量,写在main.js中
/* 定义过滤器
1.参数: 将|左侧的数据,当作参数传递给函数.
2.返回值: 必须添加返回值!!!
Vue.filter("过滤器名称",过滤器动作函数(参数){
})
*/
Vue.filter("priceFormat",function(price){
return (price / 100).toFixed(2)
})
- 1
1.4 添加商品
1.4.1 商品新增页面跳转
当用户点击添加商品时,应该跳转到商品添加页面
2. 页面跳转
- 页面效果展现
1.4.2 商品新增分析
1.点击按钮分析
<!-- 定义添加商品按钮-->
<el-button type="primary" class="addItemBtnClass" @click="addItemBtn">添加商品</el-button>
2.检查JS函数
/* 添加商品按钮 */
async addItemBtn(){
//console.log(this.addItemForm)
//1.完成表单校验
this.$refs.addItemFormRef.validate( valid => {
if(!valid) return this.$message.error("请输入商品必填项")
})
//2.完成商品参数的封装
//2.0 将商品价格扩大100倍
this.addItemForm.price = this.addItemForm.price * 100
//2.1 将商品图片的数据转化为字符串
this.addItemForm.images = this.addItemForm.images.join(",")
//2.5 实现商品数据提交
let submitAddItem = {
item : this.addItemForm,
itemDesc: this.itemDesc
}
console.log(submitAddItem)
let {data: result} = await this.$http.post("/item/saveItem",submitAddItem)
if(result.status !== 200) return this.$message.error("商品添加失败")
this.$message.success("商品添加成功")
//2.5添加完成之后,将数据重定向到商品展现页面
this.$router.push("/item")
}
1.4.3 业务接口说明
- 请求路径: http://localhost:8091/item/saveItem
- 请求类型: post
- 前端传递参数分析
{
item: {
images: "/2021/05/20/da0c1d4781c1499399f090da8b60f359.jpg,/2021/05/20/2ac1c34776a7465887eb019655354c3c.jpg"
itemCatId: 560
num: "100"
price: 718800
sellPoint: "【华为官方直供,至高12期免息0首付,原装正品】送华为原装无线充+运动蓝牙耳机+蓝牙音箱+三合一多功能数据线+钢化膜等!"
title: "华为P40 Pro 5G手机【12期免息可选送豪礼】全网通智能手机"
},
itemDesc: {
itemDesc: "<ul><li>品牌: <a href=https://list.jd.com/list.html"....... "
},
itemParam: {
dynamicArray: [
{paramId: 1, paramVals: "亮黑色,釉白色"},
{paramId: 2, paramVals: "8GB+128GB,8GB+256GB"}
],
staticArray: [
{"paramId": 3,"paramVals": "华为Mate 40 Pro"},
{"paramId": 4,"paramVals": "0.575kg"}.....
]
}
}
- 请求参数: 使用ItemVO对象接收
参数名称 | 参数类型 | 参数说明 | 备注 |
---|---|---|---|
item | Item | 商品基本信息对象封装 | 不能为null |
itemDesc | ItemDesc | 商品详情信息 | 不能为null |
itemParam | ItemParam | 商品参数信息 | 不能为null |
- ItemVO参数详解:
- Item对象
参数名称 | 参数类型 | 参数说明 | 备注 |
---|---|---|---|
title | String | 商品标题信息 | 不能为null |
sellPoint | String | 商品卖点信息 | 不能为null |
price | Integer | 商品价格信息 | 不能为null 需要将数据扩大100倍 |
num | Integer | 商品数量信息 | 不能为null |
images | String | 商品图片地址信息 | 不能为null |
itemCatId | Integer | 商品父级分类ID | 不能为null |
status | Boolean | 商品状态信息 | 不能为null |
- itemDesc 对象
-
为了降低商品提交代码的耦合性,将大字段信息详情,采用ItemDesc对象进行封装
- 1
参数名称 | 参数类型 | 参数说明 | 备注 |
---|---|---|---|
id | Integer | 商品Id信息 | 因为Item和ItemDesc是一对一关系 所以需要依赖Item对象的Id值 |
itemDesc | String | 商品详情信息 | 内部包含了大量的html语句 |
1.4.4 封装ItemVO对象
/**
* @author 刘昱江
* 时间 2021/4/16
*/
@Data
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class ItemVO { //该对象封装商品所有的参数信息
private Item item;
private ItemDesc itemDesc;
private ItemParam itemParam;
}
1.5 商品状态修改
1.5.1 业务分析
//1.修改按钮的JS
<el-table-column prop="status" label="状态" width="80px">
<template slot-scope="scope">
<el-switch v-model="scope.row.status" active-color="#13ce66" inactive-color="#ff4949"
@change="updateStatus(scope.row)"></el-switch>
</template>
</el-table-column>
async updateStatus(item) {
const {
data: result
} = await this.$http.put("/item/updateItemStatus", {
id: item.id,
status: item.status
})
if (result.status !== 200) return this.$message.error("更新状态失败")
this.$message.success("更新状态成功")
},
1.5.2 业务接口
- 请求路径: /item/updateItemStatus
- 请求类型: put
- 请求参数: 使用对象接收
参数名称 | 参数说明 | 备注 |
---|---|---|
id | 商品id | 不能为null |
status | 状态信息 | 不能为null |
- 返回值结果:
参数名称 | 参数说明 | 备注 |
---|---|---|
status | 状态信息 | 200表示服务器请求成功 201表示服务器异常 |
msg | 服务器返回的提示信息 | 可以为null |
data | 服务器返回的业务数据 | 可以为null |
1.5.3 编辑ItemController
/**
* 需求: 实现商品状态修改
* URL: /item/updateItemStatus
* 参数: {id,status} json串
* 返回值: SysResult
* 问题: 什么时候使用restFul,什么时候使用get/put/delete请求方式?
*
*/
@PutMapping("/updateItemStatus")
public SysResult updateStatus(@RequestBody Item item){
itemService.updateStatus(item);
return SysResult.success();
}
1.5.4 编辑ItemService
@Override
public void updateStatus(Item item) {
itemMapper.updateById(item);
}
1.6 商品详情说明
1.6.1 业务说明
说明: Item中存储的是商品的基本信息,通常用于检索,或者数据展现. itemDesc一般存储的都是商品的详情信息. 为了用户的检索效率更好,所以一般先查询item数据,(itemDesc一般采用大字段的形式,存储html代码的片段).
要求: 一个商品对应一个详情信息, 所以 item和itemDesc一对一
一个商品对应一个详情
一个详情对应一个商品
隐藏属性: item.id = itemDesc.id
补充知识: 数据库中常见对应关系
一对一: 老婆和老公(通常)
一对多: 老公和情人
多对多: 老师和学生
一个老师对应多个学生
一个学生对应多个老师
1.6.2 表设计
1.6.3 编辑ItemDesc
1.6.4 重构ItemServiceImpl
//实现商品入库操作
@Override
@Transactional
public void saveItem(ItemVO itemVO) {
//item 主键自增 数据库入库之后,才会有主键!!!
Item item = itemVO.getItem();
item.setStatus(true);
itemMapper.insert(item);
//问题: 入库之后才有ID,现阶段item.id=null
//mybatis实现业务功能,自动回显主键!!!
//MP自动的将主键的回显功能实现!!!
//规则: itemId与ItemDescId是一样的.
ItemDesc itemDesc = itemVO.getItemDesc();
itemDesc.setId(item.getId());
itemDescMapper.insert(itemDesc);
}
- 1
1.7 富文本编辑器
说明: 使用富文本编辑器,可以得到 “所见即所得”
用法:
- 引入JS
/* 导入富文本编辑器 */
import VueQuillEditor from 'vue-quill-editor'
/* 导入富文本编辑器对应的样式 */
import 'quill/dist/quill.core.css' // import styles
import 'quill/dist/quill.snow.css' // for snow theme
import 'quill/dist/quill.bubble.css' // for bubble theme
- 页面展现
<!-- 定义富文本编辑器-->
<quill-editor ref="myQuillEditor" v-model="itemDesc.itemDesc">
</quill-editor>
注意事项: 图片/视频默认转化为01字节信息,并且以16进制的方式展现.
3.页面效果
2 关于数据库主外键说明
说明:
1. 数据库提供了主外键的约束关系, 操作数据时必须严格的遵守.但是如果添加了主键/外键,则必然会影响执行的效率. 所以一般主键和外键的约束由程序员通过代码进行控制.
2. 一般项目中 很少直接定义主键/外键.
十五
1. ElementUI中文件上传
1.1 UI入门案例
<el-upload
class="upload-demo"
action="https://jsonplaceholder.typicode.com/posts/"
:on-preview="handlePreview"
:on-remove="handleRemove"
:file-list="fileList"
list-type="picture">
<el-button size="small" type="primary">点击上传</el-button>
<div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div>
</el-upload>
<script>
//对外声明组件属性/方法等参数.要被根组件调用
export default {
data(){
return {
tableData: [
{id:100, name:"黑熊精", age: 3000, sex:"男"},
{id:100, name:"黑旋风", age: 3000, sex:"男"},
{id:100, name:"黑心肠", age: 3000, sex:"男"},
{id:100, name:"黑桃A", age: 3000, sex:"男"}
],
dialogVisible: true,
fileList: [{name: 'food.jpeg', url: 'https://fuss10.elemecdn.com/3/63/4e7f3a15429bfda99bce42a18cdd1jpeg.jpeg?imageMogr2/thumbnail/360x360/format/webp/quality/100'}, {name: 'food2.jpeg', url: 'https://fuss10.elemecdn.com/3/63/4e7f3a15429bfda99bce42a18cdd1jpeg.jpeg?imageMogr2/thumbnail/360x360/format/webp/quality/100'}]
}
},
methods: {
handleSizeChange(size){
alert("当前条数:"+size)
},
handleCurrentChange(current){
alert("当前页数:"+current)
},
handleRemove(file, fileList) {
console.log("点击删除按钮时,触发JS")
console.log(file, fileList);
},
handlePreview(file) {
console.log("点击url地址之后,触发!!!!!")
console.log(file);
}
}
}
</script>
2. 文件上传入门案例
2.1 UI页面JS分析
<!--
了解:
action: 文件上传提交的地址
name: 默认文件上传的名称 file
-->
<el-upload class="upload-demo" :action="uploadUrl" :on-preview="handlePreview" :on-remove="handleRemove"
:on-success="handleSuccess" list-type="picture" multiple drag>
<el-button size="small" type="primary">点击上传</el-button>
<div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div>
</el-upload>
2.2 业务接口说明
- 请求路径: http://localhost:8091/file/upload
- 请求类型: post
- 请求参数:
参数名称 | 参数说明 | 备注 |
---|---|---|
file | 文件上传的参数名称 | file中携带的是二进制信息 |
- 返回值结果:
参数名称 | 参数说明 | 备注 |
---|---|---|
status | 状态信息 | 200表示服务器请求成功 201表示服务器异常 |
msg | 服务器返回的提示信息 | 可以为null |
data | 服务器返回的业务数据 | 返回ImageVO对象 |
- ImageVO对象说明
参数名称 | 参数类型 | 参数说明 | 备注 |
---|---|---|---|
virtualPath | String | 图片实际路径 不包含磁盘信息 | 例如: 2021/11/11/a.jpg 不需要写磁盘地址 |
urlPath | String | 图片url访问地址 | http://image.jt.com/2021/11/11/a.jpg 需要指定域名地址 |
fileName | String | 文件上传后的文件名称 | UUID.type |
2.3 编辑ImageVO
说明: 为了封装文件上传之后的结果,需要封装VO对象 格式如下
@Data
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class ImageVO implements Serializable {
private String virtualPath; //虚拟地址,不包含磁盘地址
private String urlPath; //URL地址信息.
private String fileName; //文件名称
}
2.4 入门案例实现
package com.jt.controller;
import com.jt.vo.SysResult;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
@RestController
@CrossOrigin
@RequestMapping("/file")
public class FileController {
/**
* 业务: 实现文件上传
* url: /file/upload
* 请求类型: POST
* 参数: file
* 返回值: SysResult(imageVO)
* 高级API:MultipartFile 自动维护了缓存流/自动开关
*
* 文件上传步骤:
* 1.获取文件名称.
* 2.准备上传文件的目录
* 3.封装文件全路径 目录/文件名称
* 4.实现文件上传
*/
@PostMapping("/upload")
public SysResult upload(MultipartFile file) throws IOException {
//1.获取文件名称 a.jpg
String fileName = file.getOriginalFilename();
//2.准备文件目录
String fileDir = "G:/images/";
//2.1 判断目录是否存在
File dir = new File(fileDir);
if(!dir.exists()){
//如果目录不存在,则创建多级目录
dir.mkdirs();
}
//3.准备文件全路径
String localPath = fileDir + fileName;
//4.实现文件输出
file.transferTo(new File(localPath));
System.out.println("文件上传成功!!!!");
return SysResult.success();
}
}
3. 图片上传业务实现
3.1 文件上传的注入事项
- 控制文件上传的类型(后台为主)
- 控制恶意程序的上传 通过宽度和高度
- 为了提高检索的速度,应该分目录存储.
- 动态生成文件名称 UUID,防止文件重名.
- 实现文件上传 注意路径.
3.2 正则表达式
3.2.1 正则说明
正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。
3.2.2 语法介绍
-
标识开头/结尾/匹配多次
regex: abc 标识只能匹配固定的字符abc
regex: abc* 标识匹配 ab,c可以任意次.
regex: abc? 标识匹配 ab,c可以1次或者0次. -
匹配确定次
regex: c{3} c只能出现3次
regex: c{3,} c出现>=3次
regex: c{3,10} c出现>=3 <=10次
regex: .* 匹配所有字符. -
匹配固定字符
regex:
a[ab] 匹配2个字符 第一个字符必须为a, 第二个字符 必须a 或者b
[a-z][0-9] 第一个字符是a-z 第二个字符必须是0-9
- 1
- 2
- 分组匹配
demo: (png|jpg|gif) 字符要么是png,要么是ipg 要么是gif.
3.3 编辑FileController
@RestController
@CrossOrigin
@RequestMapping("/file")
public class FileController {
@Autowired
private FileService fileService;
@PostMapping("/upload")
public SysResult upload(MultipartFile file) throws IOException {
ImageVO imageVO = fileService.upload(file);
if(imageVO == null){//说明业务执行有误
return SysResult.fail();
}
return SysResult.success(imageVO);
}
/**
* 业务: 实现文件上传
* url: /file/upload
* 请求类型: POST
* 参数: file
* 返回值: SysResult(imageVO)
* 高级API:MultipartFile 自动维护了缓存流/自动开关
*
* 文件上传步骤:
* 1.获取文件名称.
* 2.准备上传文件的目录
* 3.封装文件全路径 目录/文件名称
* 4.实现文件上传
*/
/*@PostMapping("/upload")
public SysResult upload(MultipartFile file) throws IOException {
//1.获取文件名称 a.jpg
String fileName = file.getOriginalFilename();
//2.准备文件目录
String fileDir = "G:/images/";
//2.1 判断目录是否存在
File dir = new File(fileDir);
if(!dir.exists()){
//如果目录不存在,则创建多级目录
dir.mkdirs();
}
//3.准备文件全路径
String localPath = fileDir + fileName;
//4.实现文件输出
file.transferTo(new File(localPath));
System.out.println("文件上传成功!!!!");
return SysResult.success();
}*/
}
3.4 编辑FileService
package com.jt.service;
import com.jt.vo.ImageVO;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.UUID;
@Service
public class FileServiceImpl implements FileService{
private String localDir = "G:/images";
/**
* 完成校验:
* 1.校验是否为图片
* 2.木马.exe.jpg 判断是否满足图片固有属性 高度/宽度
* 3.为了提高查询效率,要求分目录存储.
* 3.1 按照后缀名分配 jpg,png,gif 效率提升不能满足要求
* 3.2 按照日期分 yyyy/MM/dd/HH 可以
* 3.3 商品分类 出现分布不均现象.
* 3.4 根据名称hash 之后截串
* demo: hash(a)=qw|er|as|dg/a.jpg
* 弊端: hash码可能出现分布不均的现象.
* 4.防止文件重名 使用uuid代替名称
* @param file
* @return
*/
@Override
public ImageVO upload(MultipartFile file) {
//1.获取图片名称 demo: abc.jpg abc.JPG
String fileName = file.getOriginalFilename();
//bug说明: 由于windows系统不区分大小写,所以将字母全部转化为小写
fileName = fileName.toLowerCase();
//利用正则判断是否为图片.
if(!fileName.matches("^.+\\.(jpg|png|gif)$")){
//如果不是图片,则返回null
return null;
}
//2.检查文件是否为恶意程序.
try {
BufferedImage bufferedImage = ImageIO.read(file.getInputStream());
int width = bufferedImage.getWidth();
int height = bufferedImage.getHeight();
if(width == 0 || height == 0){
//说明文件不是图片.
return null;
}
//3.根据时间实现目录的创建 时间--yyyy/MM/dd
String dateDir = new SimpleDateFormat("/yyyy/MM/dd/")
.format(new Date());
// "G:/images/2021/11/11
String localDirPath = localDir + dateDir;
//创建目录
File dirFile = new File(localDirPath);
if(!dirFile.exists()){
dirFile.mkdirs();
}
//4. 使用uuid替换文件名称 唯一:系统内部唯一
String uuid = UUID.randomUUID().toString()
.replace("-","");
//截取文件的后缀 aa.bb.cc.jpg
int index = fileName.lastIndexOf(".");
//获取类型 .jpg
String fileType = fileName.substring(index);
String newFileName = uuid + fileType;
//5.实现文件上传操作 目录/文件名称
String realFilePath = localDirPath + newFileName;
file.transferTo(new File(realFilePath));
System.out.println("文件上传成功!!!");
/*
6.封装返回值
* 封装虚拟路径 在各个系统之间可以灵活切换,只保存动态变化的目录
* path = 时间/uuid.type
*/
String virtualPath = dateDir + newFileName;
String url = "https://img14.360buyimg.com/n0/jfs/t1/157402/13/13529/158789/60517f36E2e8f9939/958bdb78df7c145f.jpg";
return new ImageVO(virtualPath,url,newFileName);
} catch (IOException e) {
e.printStackTrace();
return null; //表示程序有问题
}
}
}
3.5 页面效果展现
十六
1. 关于商品新增文件模块说明
重点: 业务需求. 业务顺序
1.1商品基本模块
- 用户点击添加商品按钮时, 1.获取商品分类3级列表信息. 2.跳转到商品新增页面.
- 用户录入基本的商品信息.
- 用户录入价格时 需要在后期将数据扩大100倍.
1.2 商品上传
- 当用户点击上传图片时,根据 属性名称 file=“图片的字节信息”,实现数据的传递.
- 后端通过特定的接口MultipartFile(mvc专门接收字节信息,内部封装IO流)
- 上传文件时 首先使用正则表达式保证文件的类型正确
- 判断图片是否为恶意程序.由于图片有特定的属性width/height 获取文件的宽度和高度,如果有一项为0则上传的文件不是图片.
- 为了提高文件的查询效率,将文件进行分目录存储. 采用的策略以"时间为维度"进行的目录分隔.
- 为了防止文件重名,采用UUID的方式重新定义文件名称.
- 最终实现文件上传.返回特定的VO数据供前端展现.
1.3 商品详情
- 通常商品信息和详情信息是分开维护的.由于商品详情是大字段(文本域),查询的效率低.
- 商品表与详情表是一对一的对应关系. 设计时 item.id=itemDesc.id.
- 之后利用富文本编辑器(所见即所得),展现详情信息,其中存储的是html代码片段.
最后将item/itemDesc封装之后提交入库.
2.实现图片功能
2.1 图片删除
2.1.1 业务需求说明
当用户点击删除图片时,应该发起请求同时删除服务器的数据.
重点: 如果需要删除数据,则应该传递虚拟路径信息
2.1.2 删除JS分析
//移除图片的方法
async handleRemove(file) {
//移除数组中的数据
let virtualPath = file.response.data.virtualPath
//通过findIndex函数 获取数组中指定数据的位置
let index = this.addItemForm.images.findIndex(x => x === virtualPath)
//删除数组中指定的数据
this.addItemForm.images.splice(index, 1)
//删除服务中的文件
let {
data: result
} = await this.$http.delete("/file/deleteFile", {
params: {
virtualPath: virtualPath
}
})
if (result.status !== 200) return this.$message.error("删除图片失败")
this.$message.success("删除图片成功")
},
2.1.3 业务接口说明
- 请求路径: http://localhost:8091/file/deleteFile
- 请求类型: delete
- 请求参数:
参数名称 | 参数说明 | 备注 |
---|---|---|
virtualPath | 文件上传的虚拟的路径 | 删除时需要磁盘路径一起删除 |
- 返回值结果:
参数名称 | 参数说明 | 备注 |
---|---|---|
status | 状态信息 | 200表示服务器请求成功 201表示服务器异常 |
msg | 服务器返回的提示信息 | 可以为null |
data | 服务器返回的业务数据 | 可以为null |
2.1.4 编辑FileController
/**
* 业务: 删除图片
* URL: http://localhost:8091/file/deleteFile?virtualPath=xxxxx
* 参数: virtualPath 虚拟路径
* 返回值: SysResult对象
*/
@DeleteMapping("/deleteFile")
public SysResult deleteFile(String virtualPath){
fileService.deleteFile(virtualPath);
return SysResult.success();
}
2.1.5 编辑FileServiceImpl
/**
* 实现思路:
* 1.根据虚拟地址,拼接磁盘地址
* 2.判断文件是否存在
* 3.实现文件删除
* @param virtualPath
*/
@Override
public void deleteFile(String virtualPath) {
//1.生成本地磁盘地址
String path = localDir + virtualPath;
File file = new File(path);
if(file.exists()){
file.delete();
}
}
2.2 图片网络路径说明
2.2.1 业务说明
如果用户需要查看服务器中的图片,则一般使用网络地址.
难点: 图片真实存在于磁盘地址中. 如何实现磁盘地址与网络地址的映射!
业务分析:
1.本地磁盘地址: G:\images\2021\09\09\abc.jpg
2.网络访问地址: http://image.jt.com\2021\09\09\abc.jpg
2.2.2 封装图片网络地址
说明: 编辑完整的FileService
@Service
public class FileServiceImpl implements FileService{
//封装路径的前缀
private String localDir = "G:/images";
private String preUrl = "http://image.jt.com";
/**
* 完成校验:
* 1.校验是否为图片
* 2.木马.exe.jpg 判断是否满足图片固有属性 高度/宽度
* 3.为了提高查询效率,要求分目录存储.
* 3.1 按照后缀名分配 jpg,png,gif 效率提升不能满足要求
* 3.2 按照日期分 yyyy/MM/dd/HH 可以
* 3.3 商品分类 出现分布不均现象.
* 3.4 根据名称hash 之后截串
* demo: hash(a)=qw|er|as|dg/a.jpg
* 弊端: hash码可能出现分布不均的现象.
* 4.防止文件重名 使用uuid代替名称
* @param file
* @return
*/
@Override
public ImageVO upload(MultipartFile file) {
//1.获取图片名称 demo: abc.jpg abc.JPG
String fileName = file.getOriginalFilename();
//bug说明: 由于windows系统不区分大小写,所以将字母全部转化为小写
fileName = fileName.toLowerCase();
//利用正则判断是否为图片.
if(!fileName.matches("^.+\\.(jpg|png|gif)$")){
//如果不是图片,则返回null
return null;
}
//2.检查文件是否为恶意程序.
try {
BufferedImage bufferedImage = ImageIO.read(file.getInputStream());
int width = bufferedImage.getWidth();
int height = bufferedImage.getHeight();
if(width == 0 || height == 0){
//说明文件不是图片.
return null;
}
//3.根据时间实现目录的创建 时间--yyyy/MM/dd
String dateDir = new SimpleDateFormat("/yyyy/MM/dd/")
.format(new Date());
// "G:/images/2021/11/11
String localDirPath = localDir + dateDir;
//创建目录
File dirFile = new File(localDirPath);
if(!dirFile.exists()){
dirFile.mkdirs();
}
//4. 使用uuid替换文件名称 唯一:系统内部唯一
String uuid = UUID.randomUUID().toString()
.replace("-","");
//截取文件的后缀 aa.bb.cc.jpg
int index = fileName.lastIndexOf(".");
//获取类型 .jpg
String fileType = fileName.substring(index);
String newFileName = uuid + fileType;
//5.实现文件上传操作 目录/文件名称
String realFilePath = localDirPath + newFileName;
file.transferTo(new File(realFilePath));
System.out.println("文件上传成功!!!");
/*
* 6.封装返回值
* 封装虚拟路径 在各个系统之间可以灵活切换,只保存动态变化的目录
* path = 时间/uuid.type
* 网络地址://http://image.jt.com/2021/11/11/a.jpg
*/
String virtualPath = dateDir + newFileName;
String url = preUrl + virtualPath;
System.out.println("磁盘地址:"+realFilePath);
System.out.println("网络地址:"+url);
return new ImageVO(virtualPath,url,newFileName);
} catch (IOException e) {
e.printStackTrace();
return null; //表示程序有问题
}
}
/**
* 实现思路:
* 1.根据虚拟地址,拼接磁盘地址
* 2.判断文件是否存在
* 3.实现文件删除
* @param virtualPath
*/
@Override
public void deleteFile(String virtualPath) {
//1.生成本地磁盘地址
String path = localDir + virtualPath;
System.out.println(path);
File file = new File(path);
if(file.exists()){
file.delete();
}
}
}
2.3 后台数据优化
2.3.1 业务说明
说明: 如果路径信息会发生变化,则最好的方式通过动态赋值的方式完成.
解决方案:
1. 编辑properties文件
2. 通过@Value注解动态赋值.
2.3.2 编辑image.properties文件
#通过配置文件 动态赋值
image.localDir=G:/images
image.preUrl=http://image.jt.com
2.3.3 动态为属性赋值
2. 代理机制
2.1 业务说明
网络地址: http://image.jt.com/2021/09/09/8a3e2666cfbc4f1c9f0136339d42a562.jpg
磁盘地址: G:/images/2021/09/09/8a3e2666cfbc4f1c9f0136339d42a562.jpg
如果用户直接通过网络地址进行访问,是无法直接获取图片信息. 如果需要获取图片则应该实现 域名与磁盘地址的映射.
2.2 反向代理
反向代理服务器位于用户与目标服务器之间,但是对于用户而言,反向代理服务器就相当于目标服务器,即用户直接访问反向代理服务器就可以获得目标服务器的资源。同时,用户不需要知道目标服务器的地址,也无须在用户端作任何设定。反向代理服务器通常可用来作为Web加速,即使用反向代理作为Web服务器的前置机来降低网络和服务器的负载,提高访问效率。
特点:
1.反向代理服务器位于用户和服务器之间.
2.用户访问反向代理服务器,就可以获取真实的资源.
3.反向代理机制 用户无需了解真实的服务器信息.
4.反向代理保护了服务器端信息,也称之为服务器端代理.
2.3 正向代理
正向代理,意思是一个位于客户端和原始服务器(origin server)之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。客户端才能使用正向代理。
特点:
1. 正向代理服务器位于用户和服务器之间.
2. 用户发起请求时,非常明确自己访问的服务器到底是谁
3. 真实的服务器不清楚真实的用户是谁.保护了用户的信息.
所以称之为客户端代理.
2.4 正向/反向代理说明
说明: 用户每一次请求都包含了正向代理和反向代理.
正向代理一般适用于网络的通信.
反向代理一般适用于服务器获取信息.
3.Nginx
3.1 Nginx介绍
Nginx (engine x) 是一个高性能的HTTP和反向代理web服务器,同时也提供了IMAP/POP3/SMTP服务。Nginx是由伊戈尔·赛索耶夫为俄罗斯访问量第二的Rambler.ru站点(俄文:Рамблер)开发的,第一个公开版本0.1.0发布于2004年10月4日。
其将源代码以类BSD许可证的形式发布,因它的稳定性、丰富的功能集、简单的配置文件和低系统资源的消耗而闻名。2011年6月1日,nginx 1.0.4发布。
Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,在BSD-like 协议下发行。其特点是占有内存少,并发能力强,事实上nginx的并发能力在同类型的网页服务器中表现较好,中国大陆使用nginx网站用户有:百度、京东、新浪、网易、腾讯、淘宝等。
3.2 Nginx特点
- nginx是非常优秀的反向代理服务器.
- 占用内存少 不到2M tomcat服务器占用多大内存 200M左右
- 并发(负载)能力强 3-5万次/秒
tomcat服务器并发能力 250-500次/秒
调优之后可以实现1000次/秒 - nginx可以当作负载均衡服务器使用.
3.3 Nginx下载
注意事项:
1.nginx开发语言 C语言,对中文不友好.所以注意程序员操守
2.nginx启动时会默认占用80端口
访问测试:
3.4 nginx启动报错说明
说明:nginx启动时会占用80端口.所以需要释放80资源.
步骤1: 查询 80端口被哪个进程占用
步骤2: 关闭进程
步骤3: 如果80端口 被PID=4占用,则需要升级驱动配置.
3.5 nginx进程项说明
说明: 在windows中nginx服务每次点击启动之后,都会生成2个进程项.
注意事项: 在windows中nginx只能启动一次.
异常信息如下:
关于启动2项说明:
进程项1: nginx主要进程信息.
进程项2: nginx的守护进程 主要的任务防止主进程意外关闭.
关闭nginx 应该先关闭守护(内存晓得)再关闭主进程(内存大的).
3.5 nginx命令
说明: nginx命令执行 需要nginx.exe所在的根目录中执行.
命令:
1. 启动命令 start nginx
2. 重启命令 nginx -s reload
3. 关闭命令 nginx -s stop
3.6 反向代理入门案例
知识点:
1. nginx反向代理需要http协议支持.
2. server 每个反向代理服务都是一个server.
3. listen 关键字 默认监听80端口.
4. server_name 根据指定的域名进行反向代理
5. location 反向代理时拦截的策略 / 所有的请求
6. root 代表反向代理的是一个目录
7. index 默认访问的页面
反向代理配置:
http {
server {
listen 80;
server_name localhost;
location / {
root html;
index index.html index.htm;
}
}
}
3.7 实现图片的反向代理
3.7.1 业务说明
磁盘地址:G:/images/2021/09/09/a.jpg
网络地址:http://image.jt.com/2021/09/09/a.jpg
问题: 如何通过网络地址访问磁盘的图片?
1.用户通过域名访问真实的服务器.
2.nginx根据自身的配置进行拦截,根据配置文件将域名http://image.jt.com转化为具体的磁盘地址 G:/
3.根据磁盘地址访问真实的服务器资源.
4/5. 服务器将数据交给nginx,之后nginx将数据返回给用户.至此实现了反向代理.
3.7.2 图片服务器代理配置
注意事项:
1.启动时没有报错信息
2.重启时才会有报错. 所有最好先执行启动,再执行重启
#配置图片服务器
#拦截域名:http://image.jt.com:80
#代理路径:G:/images
server {
listen 80;
server_name image.jt.com;
location / {
root G:/images;
}
}
3.7.3 图片回显流程
业务说明: 操作系统为了测试方便,在计算中保留了hosts文件. 该文件的主要的作用就是实现域名与IP地址的映射.但是该映射,只对本机有效.
3.7.4 修改hosts文件
路径: C:\Windows\System32\drivers\etc
# IP 与 域名映射
# 127.0.0.1 localhost
# ::1 localhost
#图片服务器配置
127.0.0.1 image.jt.com
#前端服务器配置
127.0.0.1 www.jt.com
#后端服务器配置
127.0.0.1 manage.jt.com
- 1
解决方案:
1.添加管理权限 选中hosts文件之后 右键属性.
方式2: 以超级管理员的方式运行Switch hosts软件
方式3: 添加指定的用户权限 步骤 1.获取当前计算机的名称 PC 2.添加用户信息.
注意事项: 计算机名称不要写中文.
方式4: 取消只读属性
3.7.5 页面效果展现
十七
1. 京淘项目前端项目发布
1.1 关于前端说明
- vue项目开发阶段采用脚手架的方式进行的开发,如果开发完成应该将项目打包编译.编译为浏览器可以识别的静态资源文件 (HTML/CSS/JS)
- Nginx可以作为web服务器.并且默认端口号80.
1.2 前端项目发布
1.2.1路径修改
说明: 前端向后端发起请求时,网址 http://localhost:8091/xxxx,实际开发中服务器都是通过域名的方式访问,所以需要将前端的网址统一改为域名.
- 修改main.js 修改ajax请求的前缀
- 修改AddItem.vue文件 修改文件上传的路径
1.2.2 前端项目打包
通过build方式,将前端项目编译打包.
1.2.3 前端项目发布
说明: 将前端生成的dist目录 复制到nginx根目录中 如图所示:
1.2.4 前端反向代理
需求: 用户通过域名http://www.jt.com 访问系统的首页index.html
配置信息:
#配置前端服务器 www.jt.com
server {
listen 80;
server_name www.jt.com;
location / {
root dist;
index index.html;
}
}
页面效果:
2. 京淘项目后端发布
2.1 项目部署流程
2.2 部署2台tomcat服务器
2.2.1 去除热部署
说明: 现在需要准备2台tomcat服务器,需要执行main方法2次.如果有热部署,则修改代码之后重启会影响配置流程. 所有关闭热部署.
2.2.2 动态获取端口
说明: 由于nginx中有负载均衡的策略 所以需要动态获取端口,验证是否负载均衡.
@RestController
public class PortController {
@Value("${server.port}")
private Integer port;
@GetMapping("/getPort")
public String getPort(){
return "访问端口:" + port;
}
}
2.2.3 代码调试
1.根据8091启动服务器.
2.修改yml文件中的端口号8092,之后再次启动服务
如图所示:
3. 根据端口号测试
如果上述的测试通过,则表示后台服务器配置正确.
2.2.4 IDEA主启动项说明
如图需要将允许多个运行勾选,之后可以运行多个java程序.
2.3 Nginx实现tomcat集群部署
2.3.1 配置nginx服务器
#定义tomcat集群
# 负载均衡策略: 1.轮询策略
upstream tomcats {
server 127.0.0.1:8091;
server 127.0.0.1:8092;
}
#配置后台服务器 manage.jt.com 8091/8092
server {
listen 80;
server_name manage.jt.com;
location / {
#代理的是一个请求路径
proxy_pass http://tomcats;
}
}
2.3.2 测试效果
2.4 Nginx负载均衡策略
2.4.1 轮询策略
说明: tomcat服务器依次访问.
#定义tomcat集群
# 负载均衡策略: 1.轮询策略
upstream tomcats {
server 127.0.0.1:8091;
server 127.0.0.1:8092;
}
2.4.2 权重策略
说明: 根据权重设定,分配网络请求到不同的服务器中. 值越大访问越多.
#定义tomcat集群
# 负载均衡策略: 1.轮询策略 2.权重策略
upstream tomcats {
server 127.0.0.1:8091 weight=10;
server 127.0.0.1:8092 weight=1;
}
2.4.3 IPHASH策略
需求: 如果需要让用户与服务器绑定.则可以使用ip_hash策略
使用说明:
1.方便进行压力测试.
2.某些用户的数据保存到服务器的Session中时,需要绑定数据.
3.公司特殊业务场景可能用到iphash.
#定义tomcat集群
# 负载均衡策略: 1.轮询策略 2.权重策略 3.iphash策略
upstream tomcats {
ip_hash;
server 127.0.0.1:8091;
server 127.0.0.1:8092;
}
算法说明:
3 Linux
具体内容参见课前资料的文档.
3.1 部署JDK
3.1.1 部署JDK流程
- 上传JDK安装包 /usr/local/src
- 解压安装包
- 修改Linux环境变量
- JDK环境测试
3.1.2 上传JDK
说明: 通过远程工具中的sftp协议,实现文件上传.
3.1.3 解压命令
命令: tar -xvf jdk-8u51-linux-x64.tar.gz
2.修改文件名称 mv jdk1.8.0_51 jdk1.8
3.1.4 编辑Linux环境变量
1.路径: /etc/profile
2.修改环境变量:
#设定jdk环境
export JAVA_HOME=/usr/local/src/jdk1.8
export PATH=$JAVA_HOME/bin:$PATH
export CLASSPATH=.:$JAVA_HOME/lib
3.让环境变量生效
source /etc/profile
- 1
3.1.5 检查JDK是否有效
作业
-
预习Linux 安装mariadb数据库 安装之前先打快照
数据库安装文档 -
参照2105班课堂笔记完成Linux tomcat发布
-
预习项目发布流程 B站:2105-8-13下午视频
十八
1. 京淘后台项目发布流程
1.1 安装Mariadb数据库
1.2 完成单台tomcat部署
1.2.1 创建图片存储目录
说明: 准备文件上传的根目录
1.2.2 修改图片上传配置信息
编辑路径时注意空格问题
#通过配置文件 动态赋值
#image.localDir=G:/images
image.localDir=/usr/local/src/images
image.preUrl=http://image.jt.com
- 1
- 2
- 3
- 4
1.2.3 maven项目打包
检查数据源用户名和密码正确之后,将项目打包.
1.2.4 上传JDK的安装包
1.2.5 运行服务器
命令: [root@localhost tomcats]# java -jar 8091.jar &
项目运行正常,效果如下
1.2.6 服务器调试
说明:通过IP:端口直接测试tomcat服务是否正常.
1.3 关闭服务器进程
1.3.1 查询java服务
命令: jps
1.3.2 杀死进程
1.kill PID号 常规关闭
2.kill -15 PID号 较为严格的关闭
3.kill -9 PID号 强制关闭
1.3.3 "前台"项目发布
说明: 准备3台tomcat服务器,并且修改其中的端口号 分别改为8091/8092/8093.并且打包,如图所示.
执行启动命令: java -jar 8091.jar & java -jar 8092.jar & java -jar 8093.jar &
注意事项:
1. 前台项目发布一般用于代码的调试.如果报错 效果非常明显.
2. 如果链接Linux的终端关闭,则内部的所有的tomcat服务器都将关闭.
1.4 "后台"项目发布
1.4.1 需求说明
在前台发布调试成功之后,需要将tomcat服务器进行后台发布.后台发布时,tomcat会一直运行在Linux系统的内部,不会随终端的开关受到影响.
1.4.2 命令
nohup java -jar 8091.jar => 8091.log &
- 1
检查启动项: 检查日志是否正常,检查tomcat服务器是否正常
1.5 脚本方式启动
1.5.1 创建脚本
命令: vim start.sh
#!/bin/sh
nohup java -jar 8091.jar => 8091.log &
nohup java -jar 8092.jar => 8092.log &
nohup java -jar 8093.jar => 8093.log &
- 1
- 2
- 3
- 4
- 5
1.5.2 运行脚本
1.6 日志查看命令
cat 输出文件所有的内容
more 输出文档所有的内容,分页输出,空格浏览下一屏,q退出
less 用法和more相同,只是通过PgUp、PgDo键来控制
tail 用于显示文件后几号,使用频繁
tail -10 nginx.conf 查看nginx.conf的最后10行
tail –f nginx.conf 动态查看日志,方便查看日志新增的信息
ctrl+c 结束查看
2 Linux 安装Nginx
2.1 补充知识 关闭任意服务
命令: ps -ef |grep 服务名称
2.2 关于端口号说明
如果需要启动防火墙,则应该开放如下端口:
1. 80端口
2. 3306端口
3. 8091/8092/8093
4. 22
开放端口之后,记得重启防火墙
更多推荐
所有评论(0)