Web开发
一、web前端介绍
1. 组成
- html:网页的结构(页面的元素和内容)
- css:网页的表现(页面元素的外观、位置等界面)
- JavaScript:网页的行为(进行交付)
2. JavaScript的介绍
-
js的引入方式:
- 内部脚本:将js代码定义在HTML中,JavaScript代码必须位于script>/script>中,在HTML文档中,可以在任意位置,放置任意数量的script>/script>;一般把脚本放在body>元素的底部,可改变显示速度
- 外部脚本:外部JS文件中只包含JS代码,不包含script>标签;script>不能自闭和,eg:script src="路径">/script>
-
输出语句:
- 使用window.alert()写入警告框window可以省略
- 使用document.write()写入HTML的输出
- 使用console.log()写入到浏览器控制台
-
变量:
- 使用var(variable缩写)关键字来声明变量,var声明的变量是全局变量,可以重复申明;ECMAScript6新增了let关键字来定义变量,变量只在代码块内有效同时变量只能被定义一次;又新增了const关键字,来定义常量,一旦声明,值就无法再改变
- JavaScript是一门弱类型语言,变量可以存放不同类型的值
- 变量名需要遵循这些规程:(1)组成字符可以是字母、数组、下划线_或者美元符$ (2)数字不能开头 (3)驼峰式命名规则
-
数据类型:分为原始类型和**引用类型 **
- number:数字(整数、小数NaN(Not a Number))
- string:字符串
- boolean:布尔类型
- null:对象为空
- undefined:当声明的变量未初始化时,该变量的默认值是undefined
-
运算符:==会进行数据类型的转换,===不会进行数据类的转换
<script> let a = 10 alert(a=='10')//true alert(a==='10')//false </script>
-
数据类型的转换:
-
字符串转为数字:使用parseInt关键字,如果字面值不是数字则转换为 NaN
alert(parseInt('123'))//123 alert(parseInt('13#23'))//13 alert(parseInt('#23'))//NaN
-
其他类型转为boolean:
- number:0和NaN为false,其他都是true
- String:空字符为false,其他都为true
- Null和undefined:均转为false
-
-
函数:使用function关键字定义和调用
<script> //定义函数方式1 function add(a,b){ return a + b } //函数的调用 alert(add(10,39)) </script> <script> //定义函数方式2 var sum = function add(a,b){ return a + b } //函数的调用 alert(sum(10,30)) </script>
-
数组
- 定义: var arr = new Array(1,3,2)或者var arr = [1,2,3]
- 访问:arr[1]
- 特点:长度可变、类型可变
-
遍历:
- for循环遍历
-
forEach遍历,只会输出有值的元素
var arr = [1,2,3,4] arr.forEach(function(e){ console.log(e) })
-
箭头函数:简化输出
var arr = [1,2,3,4] arr.forEach((e)=>{ console.log(e) })
-
常用方法:
- push():尾部添加
- splice(int start,int dellength):start开始删除的位置,dellength删除元素的个数
-
字符串
- 定义:和Java一样
-
常用方法:
- charAt(index):得到index的元素
- indexOf():检索元素所在下标
- trim():去除左右两侧的空格
- substring(start,end):[start,end)
-
自定义对象
-
定义格式
//自定义对象 var user = { name :'tom', age : 20, gender : 'man', //函数完整定义 eat : function(){ alert('用膳') } // 函数的简写 eat(){ alert('用膳') } }
-
调用格式:
- 对象名.属性名
-
对象名.方法名
// 调用 alert(user.name) user.eat()
-
-
JSON
- 概念:JavaScript Object Notation,Javascript对象标记法
- JSON是通过Javascript对象标记书写的文本
- 由于其语法简单,层次结构鲜明,现在多用于作为数据载体,在网络中进行数据传输
-
定义:特别注意:变量名只能用" "包裹
// 自定义JSON var userStr = '{"name":"zjl","age":20,"gender":"man"}'
-
JSON与JS对象的转换
// 将JSON字符串转为JS对象 var obj = JSON.parse(userStr) alert(obj.age) // 将JS对象转换为字符串 var str = JSON.stringify(obj) alert(str)
-
BOM
- 概念:浏览器对象模型,允许JavaScript与浏览器对话,JavaScript将浏览器的各个组成部分封装成对象
-
组成:
- Window:浏览器窗口对象
- Navigator:浏览器对象
- Screen:屏幕对象
- History:历史记录对象
- Location:地址栏对象
-
Window:浏览器窗口对象
- 获取:直接使用Window,其中Window.可以直接省略
- 属性:history、location、navigator
-
方法 :
- alert():警告框
- confirm():确认和取消按钮
- setLnterval():按照的周期(以毫秒计)来调用函数
-
setTimeout():指定的毫秒数来调用函数
<script> //确认与取消按钮 // var flag = confirm('你确认删除记录吗') /* var i = 0; 每隔3秒调用一次 setInterval(()=>{ i++; console.log(i); },3000); */ //获取当前导航栏地址 alert(location.href); // 修改导航栏地址并跳转 location.href = 'https:www.baidu.com' </script>
-
DOM
- 概念:文档对象模型
-
将标记语言的各个组成部分封装成对应的对象:
- Document:整个文档对象
- Element:元素对象
- Attribute:属性对象
- Text:文本对象
- Comment:注释对象
-
Javascript通过DOM,就能够对HTML进行操作:
- 改变HTML元素的内容
- 改变HTML元素的样式(CSS)
- 对HTML DOM事件做出反应
- 添加和删除HTML元素
-
DOM是W3C的标准,定义了访问HTML和XML文档的标准,分为3个不同的部分:
- Core DOM 所有文档的标准
- XML DOM -XML 文档的标准模型,是Core DOM的子集
- HTML DOM -HTML 文档的标准
-
DOM案例
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <img id="img" src="./images/home.png" alt="家"> <div class="cls">zjl</div> <div class="cls">qs</div> <input type="checkbox" name="hobby">电影 <input type="checkbox" name="hobby">旅游 <input type="checkbox" name="hobby">游戏 </body> <script> //修改图片标签的路径 var img = document.getElementById('img'); img.src = './images/home-fill.png'; //通过获取class修改div var div = document.getElementsByClassName('cls'); for (let i = 0;i < div.length;i++){ const dv = div[i]; dv.innerHTML += '<font style="color: red;">very good</font>'; } // 通过获取name全选复选框 var checkbox = document.getElementsByName('hobby'); for (let i = 0; i < checkbox.length; i++) { const ch = checkbox[i]; ch.checked = true; } </script> </html>
-
事件:HTML事件是发生在HTML元素上的事件"事件",eg:
- 按钮被点击
- 鼠标移动到元素上
- 按下键盘按钮
-
事件监听:JavaScript可以在事件被侦测时执行代码
-
事件绑定
-
方式1:通过标签中事件属性进行绑定
<body> <button id="bt1" onclick="on()">按钮1</button> </body> <script> //通过标签中事件属性进行绑定 function on(){ alert('按钮1被点击了'); } </script>
-
方式2:通过DOM元素属性绑定
<button id="bt2" >按钮2</button> </body> <script> //通过DOM元素进行绑定 document.getElementById('bt2').onclick = function(){ alert('按钮2被点击了'); } </script>
-
-
常见事件
- onclick:鼠标点击事件
- onblur:元素失去焦点
- onfocus:元素获得焦点
- onload:某个元素或者图像被加载完成
- onsubmit:当表单提交时触发事件
- onkeydown:某个键盘的键被按下时触发
- onmouseover:鼠标移动都某个元素上
- onmouseout:鼠标从某个元素上移开
-
案例
<body> <img id="img" src="./images/home.png" alt="家"><br> <button onclick="bt1()">变绿</button> <button onclick="bt2()">恢复</button><br><br> <input type="text" id="ipt" value="IT" onfocus="lower()" onblur="upper()"><br> <input type="checkbox" name="hobby">旅游 <input type="checkbox" name="hobby"> 电影 <input type="checkbox" name="hobby">游戏<br> <button class="all" onclick="checkAll()">全选</button> <button class="none" onclick="checkNone()">反选</button> </body> <script> //变绿按钮的点击绑定 function bt1(){ let img = document.getElementById('img'); img.src = './images/home-fill.png'; } // 恢复按钮的点击绑定 function bt2(){ let img = document.getElementById('img'); img.src = './images/home.png'; } // 聚焦变小写 function lower(){ let input = document.getElementById('ipt'); input.value = input.value.toLowerCase(); } // 失去焦变大写 function upper(){ let input = document.getElementById('ipt'); input.value = input.value.toUpperCase(); } // 全选按钮的点击绑定 function checkAll(){ let checkboxs = document.getElementsByName('hobby'); for(let i = 0;i < checkboxs.length;i++){ const check = checkboxs[i]; check.checked = true; } } // 反选按钮的点击绑定 function checkNone(){ let checkboxs = document.getElementsByName('hobby'); for(let i = 0;i < checkboxs.length;i++){ const check = checkboxs[i]; check.checked = false; } } </script>
-
3. Vue
- 框架:是一个半成品软件,是一套可重复、通用的、软件基础代码模型。基于框架开发,更加便捷、更加高效
- Vue是一套前端框架,免除原生Javascript中的DOM操作,简化书写
- 基于MVVM(Model-View-ViewModel)思想,实现数据的双向绑定,将编程的关注点放在数据上
-
常用指令:
-
v-bind:为HTML表情绑定属性值,如设置href,css样式等
-
v-model:在表单元素上创建双向数据绑定
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script src="./js/vue.js"></script> </head> <body> <div id="app"> <a v-bind:href="URL" target="_blank">链接1</a> <a :href="URL" target="_blank">链接2</a> <input type="text" v-model="URL">{{URL}} </div> </body> <script> //定义Vue对象 new Vue({ el:"#app",//vue接管区 data:{ URL:"https:www.baidu.com" } }) </script> </html>
-
v-on:为HTML标签绑定事件
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script src="./js/vue.js"></script> </head> <body> <div id="app"> <input type="button" value="点我一下1" v-on:click="handle()"> <input type="button" value="点我一下2" @click="handle()"> </div> </body> <script> //定义Vue对象 new Vue({ el:"#app",//vue接管区 data:{ URL:"https:www.baidu.com" }, methods:{ handle: function(){ alert("你点了我一下"); } } }) </script> </html>
- v-if、v-else-if、v-else、v-show:条件性的渲染某元素,判断为true时渲染,否则不渲染
-
v-show:根据条件展示某元素,区别在于切换的是display属性的值
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script src="./js/vue.js"></script> </head> <body> <div id="app"> 年龄<input type="text" v-model="age">经判定为: <span v-if="age < 35">年轻人(35岁以下)</span> <span v-else-if="age < 60">中年人(35~60)</span> <span v-else>老人(60岁及以上)</span> <br> 年龄<input type="text" v-model="age">经判定为: <span v-show="age < 35">年轻人(35岁以下)</span> <span v-show="age >= 35 && age < 60">中年人(35~60)</span> <span v-show="age >= 60">老人(60岁及以上)</span> </div> </body> <script> //定义Vue对象 new Vue({ el:"#app",//vue接管区 data:{ age: 20 }, methods:{ handle: function(){ alert("你点了我一下"); } } }) </script> </html>
-
v-for:列表渲染展示
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script src="./js/vue.js"></script> </head> <body> <div id="app"> <div v-for="addr in address">{{addr}}</div> <div v-for="(addr,index) in address">{{index}}.{{addr}}</div> </div> </body> <script> //定义Vue对象 new Vue({ el:"#app",//vue接管区 data:{ address: ["北京", "昆明", "哈尔滨", "成都"] }, methods:{ handle: function(){ alert("你点了我一下"); } } }) </script> </html>
-
Vue案例
<!DOCTYPE html>
<tr align="center" v-for="(user,index) in users">编号 姓名 年龄 性别 成绩 等级 {{index+1}} {{user.name}} {{user.age}} <span v-if="user.gender == 1">男 <span v-if="user.gender == 2">女 {{user.score}} <span v-if="user.score >= 85">优秀 <span v-else-if="user.score >= 60">及格 <span v-else style="color: red;">不及格 -
Vue生命周期
-
生命周期:指一个对象从创建到销毁
-
生命周期的八个阶段:触发一个生命周期事件,会自动执行一个生命周期的方法(钩子)
状态 阶段周期 beforeCreate 创建前 created 创建后 beforeMount 挂载前 mounted 挂载完成 beforeUpdate 更新前 updated 更新后 beforeDestroy 销毁前 destoryed 销毁后
4.Ajax
- 概念:异步的Javascript和XML
-
作用:
- 数据交换:通过Ajax可以给服务器发送请求,并获取服务器相应的数据
- 异步交互:可以在不重新加载整个页面的情况下,与服务器交换数据并更新部分页面的技术,如:搜索联想、用户名是否可以校验等等
-
Axios:
- 介绍:Axios对原生的Ajax进行封装,简化书写,快速开发
-
Vue项目使用Axios:
-
在项目目录下安装axios:
npm install axios;
-
需要使用axios时,导入
import axios from 'axios';
-
-
官网:https://www.axios-http.cn/
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script src="./js/axios.js"></script> </head> <body> <input type="button" value="获取数据get" onclick="get()"> <input type="button" value="删除数据post" onclick="post()"> </body> <script> function get(){ axios.get("https://api.apiopen.top/api/sentences").then(result => { console.log(result.data); }) } function post(){ axios.post("https://api.apiopen.top/api/sentences").then(result => { console.log(result.data); }) } </script> </html>
5.Element
- Element:一套位开发者、设计师和产品经理准备的基于Vue2.0的桌面端组件库
- 组件:组成网页的部件,例如 超链接、按钮、图片等
- 官网:https://element.eleme.cn/2.11/#/zh-CN
-
快速入门:
-
安装ElementUI组件库(在当前工程目录下),在命令行执行指令:
npm install element-ui@2.15.3
-
引入ElementUI组件库
-
访问官网,复制组件代码,调整
-
二、后端
1.maven
- 管理和构建Java项目的工具
-
Maven的作用:
- 依赖的管理:方便快捷的管理项目的依赖的资源(jar包),避免版本冲突问题
- 统一项目结构:提供标准、统一的项目结构
- 项目构建:标准跨平台(Linux、Windows)的自动化项目构建方式
- maven仓库:https://mvnrepository.com/
三、Web入门
1.Spring
- 官网: Spring | Home
- Spring 发展到如今已经形成了一种开发生态圈,Spring提供了若干子系统,每个项目用于完成特定的功能
2.SpringBoot
- 是Spring的子系统,简化了Spring的配置
-
SpringBootWeb快速入门:
- 创建SpringBoot工程,并勾选web开发的相关依赖
- 定义HelloController类,添加方法hello,并添加注释
- 运行测试
3.HTTP
- 概念:超文本传输协议,规定了浏览器和服务器之间数据传输的规则
-
特点:
- 基于TCP协议:面向连接、安全
- 基于请求-响应模型的:一次请求对应一次响应
-
HTTP协议是无状态的协议:对于事物处理没有记忆能力。每次请求-响应都是独立的
- 缺点:多次请求间不能共享数据
- 优点:速度快
-
请求格式
- 请求行
- 请求头
- 请求体
-
响应格式
- 请求行
- 请求头: 格式 key : value
- 请求体
- 响应状态码:https://cloud.tencent.com/developer/chapter/13553
4.请求
-
简单请求:
-
原始方式:在原始的web程序中,获取请求参数,需要通过HttpServletRequest对象手动获取,但过于繁琐,还需要手动转换数据类型
@RestController public class RequestController { @RequestMapping("/simpleParam") public String simpleParam(HttpServletRequest request){ // 获取请求参数 String name = request.getParameter("name"); String ageStr = request.getParameter("age"); int age = Integer.parseInt(ageStr); System.out.println(name + ": " + age); return "OK"; } }
-
SpringBoot方式:参数名和形参名相同,定义形参即可接受参数;如果参数不一致需要使用@RequestParam映射
@RestController public class RequestController { @RequestMapping("/simpleParam") public String simpleParam(String name,Integer age){ System.out.println(name + ": " + age); return "OK"; } }
-
@RequestMapping("/simpleParam")
public String simpleParam(@RequestParam(name = "name",required = false) String name, Integer age){
System.out.println(name + ": " + age);
return "OK";
}
-
实体参数
-
简单实体参数
// 2.简单实体参数 @RequestMapping("/simplePojo") public String simplePojo(User user){ System.out.println(user); return "OK"; }
-
复杂实体参数
// 3.复杂实体参数 @RequestMapping("/complexPojo ") public String complexPojo(User user){ System.out.println(user); return "OK"; }
-
-
数组集合参数
- 数组参数:请求参数名与形参的名字相同且请求参数为多个,可以直接使用数组封装
- 集合参数:请求参数名和形参中集合变量名相同,通用@RequestParam绑定参数关系
-
日期参数:使用@DateTimeFormat注解完成日期的格式
// 4.日期参数 @RequestMapping("/dateParam") public String dateParam(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime updateTime){ System.out.println(updateTime); return "OK"; }
-
Json参数
// 5.json参数 @RequestMapping("/jsonParam") public String jsonParam(@RequestBody User user){ System.out.println(user); return "OK"; }
-
路径参数:通过URL直接传递参数,使用{...}来标识该路径参数,需要使用@PathVariable获取路径参数
// 6.路径参数 @RequestMapping("/path/{id}/{name}") public String pathParam(@PathVariable int id,@PathVariable String name){ System.out.println(id); System.out.println(name); return "OK"; }
-
ResponseBody
- 类型:方法注解、类注解
- 位置:Controller方法上/类上
- 作用:将方法返回值直接响应,如果返回值类型是实体对象/集合,将会转换为JSON格式响应
- 说明:@RestController=@Controller+@ResponseBody
-
统一响应结果Result(code,msg,data)
5.三层架构
- Controller:控制层,接收前端发送的请求,对请求进行处理,并响应数据
- Service:业务逻辑层,处理具体的业务逻辑
- Dao:数据访问层(Data Access Object)(持久层),负责数据访问操作,包括数据的增、删、改、查
6.分层解耦
- 内聚:软件中各个功能模块内的联系
- 耦合:衡量软件中各个层/模块之间的依赖、关联的程度
- 控制反转:Inversion Of Control,简称IOC。对象的创建控制权由程序自身转移到外部(容器),这种思想称为控制反转
- 依赖注入:Dependency Injected,简称DI。容器为应用程序提供运行时所依赖的资源,称之为依赖注入
- Bean对象:IOC容器中创建、管理的对象,称之为Bean
7.声明bean的注解
- @Component,@Controller,@Service,@Repository
- SpringBootApplication具有包扫描作用,默认扫描当前包及其子包
8.Bean组件扫描
- 声明bean的四大注解,要想生效,还需要被组件扫描注解@ComponentScan扫描
- @ComponentScan虽然没有显示配置,但是实际上已经包含在启动类声明注解@SpringBootApplication中,默认扫描的范围:当前包及其子包
9.DI详解
- @Autowired:默认按照类型自动装配
-
如果存在类型的Bean:、
- @Primary
- @AutoWired+@Qualifier("Bean名称")
- @Resource(name="Bean名称")
-
@Resource与@AutoWired区别:
- @Autowired是Spring框架提供的注解,而@Resource是JDK提供的
- @AutoWired默认是按照类型注入,而@Resource是按照名称注入的
四、 MyBatis
1. Mybatis入门
- 连接池
- JDBC
2. lombok
- Lombok是一个实用的Java类库,能通过注解的形式自动生成构造器,getter/setter、equals、hashcode、toString等方法,并可以自动化生成变量,简化Java开发、提高效率
-
里面的注解
- @Getter:get方法
- @Setter:set方法
- @ToString:toString方法
- @EqualsAndHashCode:Equals和HashCode方法
- @Data:包含@Getter、@Setter、@ToString、@EqualsAndHashCode四个注解
- @NoArgsConstructor:无参构造器
- @AllArgsConstructor:全参构造器
3. 删除功能
-
pojo
import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.time.LocalDate; import java.time.LocalDateTime; @Data @NoArgsConstructor @AllArgsConstructor public class Emp { private Integer id; private String username; private String password; private String name; private Short gender; private String image; private Short job; private LocalDate entrydate; private Integer deptId; private LocalDateTime createTime; private LocalDateTime updateTime; }
-
mapper
import org.apache.ibatis.annotations.*; @Mapper public interface EmpMapper { // 根据ID删除数据 @Delete("delete from emp where id = #{id}") public int delete(Integer id); }
-
test
@SpringBootTest class SpringbootMybatisCrudApplicationTests { @Autowired private EmpMapper empMapper; @Test public void testDelete(){ int delete = empMapper.delete(17); System.out.println(delete); } }
4. 预编译SQL
- 性能更高
- 更安全(防止SQL注入)
- SQL注入:是通过操作输入的数据来修改事先定义好的SQL语句,已达到执行代码对服务器进行攻击的方式
5. 插入功能
-
mapper
@Mapper public interface EmpMapper { @Options(useGeneratedKeys = true,keyProperty = "id") // 主键值的返回 @Insert("insert into emp(username, name, gender, image, job, entrydate, dept_id, create_time, update_time) " + "VALUES(#{username},#{name},#{gender},#{image},#{job},#{entrydate},#{deptId},#{createTime},#{updateTime})") public void insert(Emp emp); }
-
test
@Test public void testInsert(){ Emp emp = new Emp(); emp.setUsername("zj"); emp.setPassword("123"); emp.setName("zjl"); emp.setGender((short)1); emp.setGender((short) 1); emp.setEntrydate(LocalDate.of(2023, 12, 4)); emp.setCreateTime(LocalDateTime.now()); emp.setUpdateTime(LocalDateTime.now()); empMapper.insert(emp); System.out.println(emp.getId()); }
-
返回主键值:@Options(useGeneratedKeys = true,keyProperty = "id") // 主键值的返回
6. 根据Id查询员工
-
Mapper
// 根据员工查询数据 @Select("select * from emp where id = #{id}") public Emp getById(Integer id);
-
Test
public void testGetById() { Emp emp = empMapper.getById(20); System.out.println(emp); }
-
开启驼峰式命名的自动映射
# 开启Mybatis驼峰命令的自动映射 a_Colum -> aColum mybatis.configuration.map-underscore-to-camel-case=true
7. 条件查询员工
-
Mapper
// 条件查询员工信息 @Select("select * from emp where name like concat('%',#{name},'%') and gender = #{gender} and " + "entrydate between #{begin} and #{end} order by update_time desc") public List<Emp> getEmp(String name, Short gender, LocalDate begin,LocalDate end);
-
test
@Test public void testGetEmp(){ List<Emp> emp = empMapper.getEmp("郑", (short) 1, LocalDate.of(2010, 1, 1), LocalDate.of(2023, 12, 6)); System.out.println(emp); }
8. XML映射文件
-
规范
- XML映射文件的名称与Mapper接口名称一致,并且将XML映射文件和Mapper接口放置在相同包下(同胞同名)
- XML映射文件的namespace属性为Mapper接口全限定名一致
- XML映射文件中SQL语句的id和Mapper接口中的方法名一致,并保持返回类型一致
-
EmpMapper.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.zjl.mapper.EmpMapper"> <!--resultType: 单条记录所封装的类型--> <select id="getEmp" resultType="com.zjl.pojo.Emp"> select * from emp where name like concat('%',#{name},'%') and gender = #{gender} and entrydate between #{begin} and #{end} order by update_time desc </select> </mapper>
-
EmpMapper:不需要注解
public List<Emp> getEmp(String name, Short gender, LocalDate begin,LocalDate end);
-
SQL语句复杂的时候建议使用XML映射文件,SQL语句简单的时候建议使用注解
9. Mybatis动态SQL
- 动态SQL:随着用户的输入或者外部条件的变化而变化的SQL语句
- if:用于判断条件是否成立,使用test属性进行条件的判断,如果条件为true,则拼接SQL
-
where:where元素只会在子元素有内容的情况下才插入where子句,而且会自动去除句子开头的and或or
<?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.zjl.mapper.EmpMapper"> <!--resultType: 单条记录所封装的类型--> <select id="getEmp" resultType="com.zjl.pojo.Emp"> select * from emp <where> <if test="name != null"> name like concat('%', #{name}, '%') </if> <if test="gender != null"> and gender = #{gender} </if> <if test="begin != null and end != null"> and entrydate between #{begin} and #{end} </if> </where> order by update_time desc </select> </mapper>
-
set:用于update语句,去除多余的,
-
foreach
-
XML映射文件
<!--批量删除员工(18,19,20)--> <!-- collection:需要遍历的集合 item:遍历出来的每一个元素 separator:分隔符 open:遍历前开始拼接的SQL片段 close:遍历结束后开始拼接的SQL片段 --> <delete id="deleteByIds"> delete from emp where id in <foreach collection="ids" item="id" separator="," open="(" close=")"> #{id} </foreach> </delete>
-
Mapper
// 批量删除员工 public void deleteByIds(List<Integer> ids);
-
Test
@Test public void testDeleteByIds(){ List<Integer> ids = Arrays.asList(18,16,20); empMapper.deleteByIds(ids); }
-
-
sql和include标签,sql负责抽取SQL片段,include负责引用sql片段
-
sql片段
<sql id="commonSelect"> select id, username, password, name, gender, image, job, entrydate, dept_id, create_time, update_time from emp </sql>
-
include引用sql片段
<select id="getEmp" resultType="com.zjl.pojo.Emp"> /*引用sql片段*/ <include refid="commonSelect"/> <where> <if test="name != null"> name like concat('%', #{name}, '%') </if> <if test="gender != null"> and gender = #{gender} </if> <if test="begin != null and end != null"> and entrydate between #{begin} and #{end} </if> </where> order by update_time desc </select>
-
三、案例
1. 准备工作
- 需求分析
-
环境搭建
- 准备数据库表(dept、emp)
- 创建springboot工程,引入对应的起步依赖(web、mybatis、mysql驱动、lombok)
- 配置文件application.properties中引入mybatis的配置信息,准备对应的实体类
- 准备对应的Mapper、Service(接口、实现类)、Controller基础结构
2. 部门查询
-
Controller
@Slf4j @RestController public class DeptController { // 日志 可以用@Slf4j代替 //private static Logger log = (Logger) LoggerFactory.getLogger(DeptController.class); @Autowired private DeptService deptService; //@RequestMapping(value = "/depts",method = RequestMethod.GET) // 指定请求方式为GET @GetMapping("/depts") public Result list(){ log.info("查询全部的部门数据"); // 调用Service查询部门数据 List<Dept> deptList= deptService.list(); return Result.success(deptList); } }
-
Service
public interface DeptService { /* * 查询全部部门的数据 * */ List<Dept> list(); }
-
ServiceImpl
@Service public class DeptServiceImpl implements DeptService { @Autowired private DeptMapper deptMapper; @Override public List<Dept> list() { return deptMapper.list(); } }
-
Mapper
@Mapper public interface DeptMapper { /* * 查询部门的全部数据 * */ @Select("select * from dept") List<Dept> list(); }
3. 根据Id删除部门
-
Controller
// 根据id删除部门 @DeleteMapping("/{id}") public Result deleteById(@PathVariable Integer id){ log.info("删除的部门号Id为:" + id); deptService.deleteById(id); return Result.success(); }
-
Service
List<Dept> list(); // 根据id删除部门号 void deleteById(Integer id);
-
ServiceImpl
@Override public void deleteById(Integer id) { deptMapper.deleteById(id); }
-
Mapper
// 根据id删除部门号 @Delete("delete from dept where id = #{id}") void deleteById(Integer id);
4. 新增员工
-
Controller
// 新增部门 @PostMapping public Result insertDept(@RequestBody Dept dept){ log.info("新增部门:" + dept); deptService.insertDept(dept); return Result.success(); }
-
Service
//新增员工 void insertDept(Dept dept);
-
ServiceImpl
@Override public void insertDept(Dept dept) { dept.setCreateTime(LocalDateTime.now()); dept.setUpdateTime(LocalDateTime.now()); deptMapper.insertDept(dept); }
-
Mapper
// 新增员工 @Insert("insert into dept(name, create_time, update_time) values (#{name},#{createTime},#{updateTime})") void insertDept(Dept dept);
5. 修改部门
-
Controller
// 得到修改的id @GetMapping("/{id}") public Result getUpdateId(@PathVariable Integer id) { log.info("根据Id查询:" + id); Dept dept = deptService.getUpdateId(id); return Result.success(dept); } // 修改部门 @PutMapping public Result updateDept(@RequestBody Dept dept){ log.info("修改部门名为:" + dept.getName()); deptService.updateDept(dept); return Result.success(); }
-
Service
// 修改部门 void updateDept(Dept dept); // 根据id查询 Dept getUpdateId(Integer id);
-
ServiceImpl
@Override public void updateDept(Dept dept) { dept.setUpdateTime(LocalDateTime.now()); deptMapper.updateDept(dept); } @Override public Dept getUpdateId(Integer id) { return deptMapper.getUpdateId(id); }
-
Mapper
// 修改部门名称 @Update("update dept set name = #{name},update_time = #{updateTime} where id = #{id}") void updateDept(Dept dept); // 查询id @Select("select * from dept where id = #{id}") Dept getUpdateId(Integer id);
6. 员工信息分页查询
- @RequestParam(defaultValue = "1"):给参数设置默认值
-
Controller
// 分页查询 @GetMapping("/emps") public Result page(@RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer pageSize){ log.info("分页查询, 参数:{},{}",page,pageSize); PageBean pageBean = empService.page(page,pageSize); return Result.success(pageBean); }
-
Service
PageBean page(Integer page, Integer pageSize);
-
ServiceImpl
@Override public PageBean page(Integer page, Integer pageSize) { // 获取总记录数 Long count = empMapper.count(); // 获取分页的数据 Integer start = (page - 1) * pageSize; List<Emp> empList = empMapper.page(start, pageSize); // 封装pageBean PageBean pageBean = new PageBean(count, empList); return pageBean; }
-
Mapper
// 获取记录数 @Select("select count(*) from emp") public Long count(); // 获取分页的数据 // start 查询第几页 pageSize每页的大小 @Select("select * from emp limit #{start},#{pageSize}") public List<Emp> page(Integer start,Integer pageSize);
7. 分页插件PageHelper
-
引入依赖
<dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>1.4.6</version> </dependency>
-
Controller
// 分页查询 @GetMapping("/emps") public Result page(@RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer pageSize){ log.info("分页查询, 参数:{},{}",page,pageSize); PageBean pageBean = empService.page(page,pageSize); return Result.success(pageBean); }
-
Service
PageBean page(Integer page, Integer pageSize);
-
ServiceImpl
@Override public PageBean page(Integer page, Integer pageSize) { // 设置分页参数 PageHelper.startPage(page,pageSize); // 执行查询 List<Emp> empList = empMapper.page(); Page<Emp> p = (Page<Emp>) empList; // 封装pageBean PageBean pageBean = new PageBean(p.getTotal(), p.getResult()); return pageBean; }
-
Mapper
@Mapper public interface EmpMapper { @Select("select * from emp") public List<Emp> page(); }
-
映射文件.xml
<!--条件查询--> <select id="list" resultType="com.zjl.pojo.Emp"> select * from emp <where> <if test="name != null"> name like concat('%',#{name},'%') </if> <if test="gender != null"> and gender = #{gender} </if> <if test="begin != null and end != null"> and entrydate between #{begin} and #{end} </if> </where> order by update_time desc </select>
8. 员工信息
- 类注解:@RequestMapping("/emps")
-
Controller
// 删除员工 @DeleteMapping("/{ids}") public Result delete(@PathVariable List<Integer> ids){ log.info("删除的员工id:{}",ids); empService.delete(ids); return Result.success(); }
-
Service
//删除员工 void delete(List<Integer> ids);
-
ServiceImpl
@Override public void delete(List<Integer> ids) { empMapper.delete(ids); }
-
Mappering
// 删除员工 void delete(List<Integer> ids);
-
映射文件.xml
<mapper namespace="com.zjl.mapper.EmpMapper"> <!--删除员工id:1,2,3--> <delete id="delete"> delete from emp where id in <foreach collection="ids" item="id" separator="," open="(" close=")"> #{id} </foreach> </delete> </mapper>
9.员工增加
-
Controller
// 增加员工 @PostMapping public Result insert(@RequestBody Emp emp){ log.info("新增员工:{}",emp); empService.insert(emp); return Result.success(); }
-
Service
// 新增员工 void insert(Emp emp);
-
ServiceImpl
@Override public void insert(Emp emp) { // 补充基本数据 emp.setCreateTime(LocalDateTime.now()); emp.setUpdateTime(LocalDateTime.now()); empMapper.insert(emp); }
-
Mapper
// 新增员工 @Insert("insert into emp(username, name, gender, image, job, entrydate, dept_id, create_time, update_time) VALUES " + "(#{username},#{name},#{gender},#{image},#{job},#{entrydate},#{deptId},#{createTime},#{updateTime})") void insert(Emp emp);
10.文件上传-本地存储
-
构造一个唯一通用识别符(Uuid)
String uuid = UUID.randomUUID().toString();
-
HTML
<form action="/upload" method="post" enctype="multipart/form-data"> 姓名: <input type="text" name="username"><br> 年龄: <input type="text" name="age"><br> 头像: <input type="file" name="image"><br> <input type="submit" value="提交"> </form>
-
Controller
public class UploadController { @PostMapping("/upload") public Result upload(String username, Integer age, MultipartFile image) throws Exception { log.info("上传文件:{}, {}, {}",username,age,image); // 获取文件的后缀 String originalFilename = image.getOriginalFilename(); int index = originalFilename.lastIndexOf("."); String extname = originalFilename.substring(index); // 构造一个唯一通用识别符(Uuid) String uuid = UUID.randomUUID().toString(); // 文件全名 String newFileName = uuid + extname; log.info("新的文件名:{}",newFileName); // 将文件存储在服务器的磁盘文件中 image.transferTo(new File("C:\\Users\\21136\\Desktop\\Java学习\\" + newFileName)); return Result.success(); } }
-
application.properties
# 配置当个文件大小 spring.servlet.multipart.max-file-size=10MB # 配置单次上传多个文件大小 spring.servlet.multipart.max-request-size = 10MB
11.文件上传-阿里云OSS
-
入门程序:上传一张图片
import com.aliyun.oss.ClientException; import com.aliyun.oss.OSS; import com.aliyun.oss.common.auth.*; import com.aliyun.oss.OSSClientBuilder; import com.aliyun.oss.OSSException; import com.aliyun.oss.model.PutObjectRequest; import com.aliyun.oss.model.PutObjectResult; import java.io.FileInputStream; import java.io.InputStream; public class Aliyun { public static void main(String[] args) throws Exception { // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。 String endpoint = "https://oss-cn-hangzhou.aliyuncs.com"; // 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。 EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider(); String accessKeyId = "LTAI5tAFhq3q2yMchrSBGjkM"; String accesskeySecret = "gt8ixSRg9Cs8VP0Acn6CS7EJP2jIFJ"; // 填写Bucket名称,例如examplebucket。 String bucketName = "web-zjl"; // 文件上传后的名字 String objectName = "1.jpg"; // 填写本地文件的完整路径,例如D:\\localpath\\examplefile.txt。 // 如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件流。 String filePath= "C:\\Users\\21136\\Desktop\\图片\\logo\\banner2.png"; // 创建OSSClient实例。 OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId,accesskeySecret); try { InputStream inputStream = new FileInputStream(filePath); // 创建PutObjectRequest对象。 PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectName, inputStream); // 创建PutObject请求。 PutObjectResult result = ossClient.putObject(putObjectRequest); } catch (OSSException oe) { System.out.println("Caught an OSSException, which means your request made it to OSS, " + "but was rejected with an error response for some reason."); System.out.println("Error Message:" + oe.getErrorMessage()); System.out.println("Error Code:" + oe.getErrorCode()); System.out.println("Request ID:" + oe.getRequestId()); System.out.println("Host ID:" + oe.getHostId()); } catch (ClientException ce) { System.out.println("Caught an ClientException, which means the client encountered " + "a serious internal problem while trying to communicate with OSS, " + "such as not being able to access the network."); System.out.println("Error Message:" + ce.getMessage()); } finally { if (ossClient != null) { ossClient.shutdown(); } } } }
12. 阿里云OSS-集成
-
步骤:
- 引入阿里云OSS上传文件工具类(根据官方文档修改)
- 上传图片接口的开发
-
AliOSSUtils(工具类)
@Component public class AliOSSUtils { private String endpoint = "https://oss-cn-hangzhou.aliyuncs.com"; private String accessKeyId = "LTAI5tAFhq3q2yMchrSBGjkM"; private String accessKeySecret = "gt8ixSRg9Cs8VP0Acn6CS7EJP2jIFJ"; private String bucketName = "web-zjl"; /** * 实现上传图片到OSS */ public String upload(MultipartFile file) throws IOException { // 获取上传的文件的输入流 InputStream inputStream = file.getInputStream(); // 避免文件覆盖 String originalFilename = file.getOriginalFilename(); String fileName = UUID.randomUUID().toString() + originalFilename.substring(originalFilename.lastIndexOf(".")); //上传文件到 OSS OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret); ossClient.putObject(bucketName, fileName, inputStream); //文件访问路径 String url = endpoint.split("//")[0] + "//" + bucketName + "." + endpoint.split("//")[1] + "/" + fileName; // 关闭ossClient ossClient.shutdown(); return url;// 把上传到oss的路径返回 } }
-
Controller
public class UploadController { @Autowired private AliOSSUtils aliOSSUtils; @PostMapping("/upload") public Result upload(MultipartFile image) throws IOException { log.info("上传的文件名是:{}",image.getOriginalFilename()); // 获取url String url = aliOSSUtils.upload(image); return Result.success(url); } }
13. 修改员工
-
查询回显
-
Controller
// 查询回显 @GetMapping("/{id}") public Result getById(@PathVariable Integer id){ log.info("查询回显的Id:{}",id); Emp emp = empService.getById(id); return Result.success(emp); }
-
Service
// 修改员工 void update(Emp emp);
-
ServiceImpl
@Override public void update(Emp emp) { // 修改 修改时间 emp.setUpdateTime(LocalDateTime.now()); empMapper.update(emp); }
-
Mapper
// 查询回显 @Select("select * from emp where id = #{id}") Emp getById(Integer id);
-
-
修改员工
-
Controller
// 修改员工 @PutMapping public Result update(@RequestBody Emp emp){ log.info("更新员工信息为:{}",emp); empService.update(emp); return Result.success(); }
-
Service
// 修改员工 void update(Emp emp);
-
ServiceImpl
@Override public void update(Emp emp) { // 修改 修改时间 emp.setUpdateTime(LocalDateTime.now()); empMapper.update(emp); }
-
Mapper
//修改员工 void update(Emp emp);
-
映射文件xml
<!-- 修改员工 --> <update id="update"> update emp <set> <if test="username != null and username != ''"> username = #{username}, </if> <if test="password != null and password != ''"> password = #{password}, </if> <if test="name != null and name != ''"> name = #{name}, </if> <if test="gender != null"> gender = #{gender}, </if> <if test="image != null and image!= ''"> image = #{image}, </if> <if test="job != null"> job = #{job}, </if> <if test="entrydate != null"> entrydate = #{entrydate}, </if> <if test="deptId != null"> dept_id = #{deptId}, </if> <if test="updateTime != null"> update_time = #{updateTime} </if> </set> where id = #{id} </update>
-
14. 配置格式
-
常见配置文件格式对比
-
xml(臃肿)
<server> <port>8080</port> <address>127.0.0.1</address> </server>
-
properties
server.port=8080 server.address=127.0.0.1
-
yml/yaml(推荐)
server: port: 9000 address: 127.0.0.1
-
-
yml的基础语法
- 大小写敏感
- 数值前边必须有空格,作为分隔符
- 使用缩进表示层级关系,缩进时,不允许使用Tab键,只能使用空格(idea中自动将Tab转换为空格)
- 缩进的空格数目不重要,只要相同层级的元素左侧对齐即可
- #表示注释
- port: 9000 9090前面必须有空格
-
yml数据格式
-
对象/Map集合:
# 定义map集合 user: name: Tom age: 20 address: yunnan
-
数组/List/Set集合:
# 定义数组 hobby: - java - python - c
-
文件配置
# 定义map集合 #user: # name: Tom # age: 20 # address: yunnan # ## 定义数组 #hobby: # - java # - python # - c spring: # 数据库的连接信息 datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/zjl username: root password: 801322 # 文件上传 servlet: multipart: max-file-size: 10MB max-request-size: 100MB # mybatis配置 mybatis: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl map-underscore-to-camel-case: true # 阿里云OSS aliyun: oss: endpoint: https://oss-cn-hangzhou.aliyuncs.com accessKeyId: LTAI5tAFhq3q2yMchrSBGjkM accessKeySecret: gt8ixSRg9Cs8VP0Acn6CS7EJP2jIFJ bucketName: web-zjl
-
15. 登录功能
-
Controller
@PostMapping("/login") public Result login(@RequestBody Emp emp){ log.info("员工登录:{}", emp); Emp e = empService.login(emp); return e != null ? Result.success() : Result.error("用户名或者密码错误"); }
-
Service
// 登录 Emp login(Emp emp);
-
ServiceImpl
@Override public Emp login(Emp emp) { return empMapper.getByUsernameAndPassword(emp); }
-
Mapper
// 登录 @Select("select * from emp where username = #{username} and password = #{password}") Emp getByUsernameAndPassword(Emp emp);
16. 登录校验
- 登录标记:用户登录成功之后,每一次请求中,都可以获取到该标记
-
统一拦截
- 过滤器Filter
- 拦截器Interceptor
-
会话技术
- 会话:用户打开浏览器,访问web服务器的资源,会话建立,直到有一方断开连接,会话结束。在一次会话中可以包含多个请求和响应
- 会话跟踪:一种维护浏览器状态的方法,服务器需要识别多次请求是否来自于同一浏览器,以便在同一次会话的多次请求间共享数据
-
会话跟踪方案:
- 客户端会话跟踪技术:Cookie
- 服务端会话跟踪技术:Session
- 令牌技术
-
JWT令牌
-
引入依赖
<!-- JWT令牌 --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency>
-
生成JWT
/* * 生成JWT令牌 * */ @Test public void testGenJWT(){ Map<String,Object> claims = new HashMap<>(); claims.put("id",1); claims.put("name","Tom"); String jwt = Jwts.builder() .signWith(SignatureAlgorithm.HS256, "itzjl") // 签名算法 .setClaims(claims) // 自定义内容(数据载荷) .setExpiration(new Date(System.currentTimeMillis() + 3600 * 1000))//设置有效期为1h .compact();//设置为字符串类型 System.out.println(jwt); // eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiVG9tIiwiaWQiOjEsImV4cCI6MTcwNTQ2NDQ1M30.P0tgZ2hetzQlM6D1gUwMylwS_iqROy21kwLs2JjKZDA }
-
解析JWT
@Test public void testParseJWT(){ final Claims claims = Jwts.parser() .setSigningKey("itzjl") //密钥 .parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiVG9tIiwiaWQiOjEsImV4cCI6MTcwNTQ2NDQ1M30.P0tgZ2hetzQlM6D1gUwMylwS_iqROy21kwLs2JjKZDA") .getBody(); System.out.println(claims); // {name=Tom, id=1, exp=1705464453} }
-
-
具有JWT令牌的登录功能
-
Controller
@PostMapping("/login") public Result login(@RequestBody Emp emp){ log.info("员工登录:{}", emp); Emp e = empService.login(emp); if (e != null) { Map<String,Object> claims = new HashMap<>(); claims.put("id", e.getId()); claims.put("name", e.getName()); claims.put("username", e.getUsername()); String jwt = JwtUtils.generateJwt(claims); return Result.success(jwt); } return Result.error("用户名或者密码错误"); }
-
-
Filter过滤器:
- 需要在使用类上加上注解@ServletComponentScan
- @WebFilter(urlPatterns = "/")拦截的路径,可以具体拦截某个路径urlPatterns = "/login";拦截所有urlPatterns = "/";按照目录拦截urlPatterns = "/emps/*"
-
过滤器链:多个过滤器形成,执行的顺序是根据类名的字母顺序
package com.zjl.filter; import jakarta.servlet.*; import jakarta.servlet.annotation.WebFilter; import java.io.IOException; @WebFilter(urlPatterns = "/*") public class DemoFilter implements Filter { @Override // 指挥被调用一次 public void init(FilterConfig filterConfig) throws ServletException { System.out.println("初始化方法被执行了"); } @Override // 拦截到请求后调用,可以多次被调用 public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("拦截到请求"); // 放行 filterChain.doFilter(servletRequest,servletResponse); } @Override // 也是只被调用一次 public void destroy() { System.out.println("销毁方法被执行了"); } }
-
具有jwt+Filter的登录功能
package com.zjl.filter; import com.alibaba.fastjson.JSONObject; import com.zjl.pojo.Result; import com.zjl.utils.JwtUtils; import jakarta.servlet.*; import jakarta.servlet.annotation.WebFilter; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.util.StringUtils; import java.io.IOException; @Slf4j @WebFilter(urlPatterns = "/*") public class LoginCheckFilter implements Filter { @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse) servletResponse; // 1. 获取请求的url String url = request.getRequestURL().toString(); log.info("请求的路径为:{}",url); // 2. 判断url是否包含login,如果包含,说明是登录操作,放行 if (url.contains("login")){ log.info("登录操作,放行"); filterChain.doFilter(servletRequest,servletResponse); return; } // 3.获取请求头的令牌(Token) String jwt = request.getHeader("token"); // 4. 判断令牌是否存在,如果不存在,返回错误结果(未登录) if (!StringUtils.hasLength(jwt)){ log.info("请求头token为空,返回未登录的信息"); Result error = Result.error("NOT_LOGIN"); // 手动转为json格式 ----> 阿里巴巴fastJSON String notLogin = JSONObject.toJSONString(error); response.getWriter().write(notLogin); return; } // 5. 解析Token,如果解析失败,返回错误结果(未登录) try { JwtUtils.parseJWT(jwt); } catch (Exception e) { e.printStackTrace(); log.info("解析令牌失败,返回未登录错误信息"); Result error = Result.error("NOT_LOGIN"); // 手动转为json格式 ----> 阿里巴巴fastJSON String notLogin = JSONObject.toJSONString(error); response.getWriter().write(notLogin); return; } // 6. 放行 log.info("令牌合法,放行"); filterChain.doFilter(servletRequest,servletResponse); } }
-
拦截器(Interceptor):类似于过滤器
- 概念:是一种动态拦截方法调用的机制,类似于过滤器。Spring框架中提供的,用来动态拦截控制器方法的执行
- 作用:拦截请求,在指定的方法调用前后,根据业务需求执行预先设定的代码
-
需要配置类
public class LoginCheckInterceptor implements HandlerInterceptor { @Override // 目标资源方法运行前运行,返回true放行,不放行返回false public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String url = request.getRequestURL().toString(); log.info("请求的路径为:{}",url); // 2. 判断url是否包含login,如果包含,说明是登录操作,放行 if (url.contains("login")){ log.info("登录操作,放行"); return true; } // 3.获取请求头的令牌(Token) String jwt = request.getHeader("token"); // 4. 判断令牌是否存在,如果不存在,返回错误结果(未登录) if (!StringUtils.hasLength(jwt)){ log.info("请求头token为空,返回未登录的信息"); Result error = Result.error("NOT_LOGIN"); // 手动转为json格式 ----> 阿里巴巴fastJSON String notLogin = JSONObject.toJSONString(error); response.getWriter().write(notLogin); return false; } // 5. 解析Token,如果解析失败,返回错误结果(未登录) try { JwtUtils.parseJWT(jwt); } catch (Exception e) { e.printStackTrace(); log.info("解析令牌失败,返回未登录错误信息"); Result error = Result.error("NOT_LOGIN"); // 手动转为json格式 ----> 阿里巴巴fastJSON String notLogin = JSONObject.toJSONString(error); response.getWriter().write(notLogin); return false; } // 6. 放行 log.info("令牌合法,放行"); return true; } }
17. 异常处理
- @RestControllerAdvice = @Controller + @RespondBody
-
转为josn格式
// 全局异常处理器 @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(Exception.class) public Result ex(Exception exception){ exception.printStackTrace(); return Result.error("对不起,请联系管理员"); } }
18. 事务管理
-
Spring事务管理
- 注解:@Transactional
- 位置:业务(Service)层的方法上、类上、接口上
- 作用:将前面的方法交给Spring进行事务管理,方法执行前,开启事务;成功执行完毕后,提交事务;出现异常,回滚事务;
-
事务进阶
- 默认情况下,只出现RunTimeException才会回滚,rollbackFor属性用于控制出现何种异常类型,回滚事务
- @Transactional(rollbackFor = Exception.class):使用该属性后,所有类型都可以回滚
- 事务传播行为propagation:
- Propagation.REQUIRED:共用一个事务
- Propagation.REQUIRES_NEW:不公用事务,互不影响
19. AOP基础
- AOP:面向切面编程,其实就是面向特定方法的编程
-
在pom.xml引入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
-
编写AOP程序:*** com.zjl.service..(..)**// 切入点表达式
@Slf4j @Component @Aspect // AOP类 public class TimeAspect { @Around("execution(* com.zjl.service.*.*(..))") // 切入点表达式 public Object recordTime(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { // 1. 记录开始时间 Long begin = System.currentTimeMillis(); // 2. 调用原始方法运行 Object result = proceedingJoinPoint.proceed(); // 3. 记录结束时间,计算方法执行耗时 Long end = System.currentTimeMillis(); log.info(proceedingJoinPoint.getSignature() + "方法的执行耗时:{}ms",end - begin); return result; } }
20. AOP的核心
- 连接点:JionPiont,可以被AOP控制的方法(暗含方法执行时的相关信息)
- 通知:Advice,指那些重复的逻辑,也就是共性功能(最终体现出一个方法)
- 切入点:PiontCut,匹配连接点的条件,通知仅会在切入点方法执行时被应用
- 切面:Aspect,描述通知与切入点的对应关系(通知+切入点)
- 目标对象:Target,通知所应用的对象
21. AOP的进阶
-
通知类型
- @Around:环绕通知,次注解标注的通知方法在目标方法前、后都会被执行
- @Before:前置通知,此注解标注的通知方法在目标方法前执行
- @After:后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会被执行
- @AfterReturning:返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会被执行
- @AfterThrowing:异常后通知,此注解标注的通知方法发生异常后执行
-
切入点的抽取
@Pointcut("execution(* com.zjl.service.*.*(..))") public void pt(){}
-
通知顺序
- 根据类名字母顺序
- 可用@Order(1)注解来指定执行顺序,前置通知数字越小越先执行,后置通知相反
-
切入点表达式
-
常见形式
- execution
-