SpringBoot学习笔记
文章目录
1、Hello World
在idea中新建project,选择Spring Initializr,点击Next ,设置项目所需要的东西,设置Spring Web的依赖,建立一个空的spring boot工程
不需要任何配置,spring boot已经自动配置好了,只需要在controller层写接口,在浏览器访问路径就可以得到想要的结果。
controller层
@RestController
public class HelloWorld {
@RequestMapping("hello")
public String hello() {
return "hello world";
}
}
2、yaml
SpringBoot使用一个全局的配置文件 , 配置文件名称是固定的 。可以使用application.properties
或者application.yaml
-
application.properties
-
- 语法结构 :key=value
-
application.yml
-
- 语法结构 :key:空格 value,一定要把空格加上,不然不会生效!
配置文件的作用 : 修改SpringBoot自动配置的默认值
用实例了解一下用法:
Dog实体类
@Component
public class Dog {
private String name;
private Integer age;
//有参、无参、get/set、toString省略
}
Person实体类
@Component
@ConfigurationProperties(prefix = "person")
public class Person {
private String name;
private Integer age;
private Boolean answer;
private Date date;
private List<Object> list;
private Map<String,Object> map;
private Dog dog;
}
其中,@ConfigurationProperties(prefix = "person")
的prefix必须加上
@ConfigurationProperties
作用:将配置文件中配置的每一个属性的值,映射到这个组件中;告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定参数 prefix = “person” : 将配置文件中的person下面的所有属性一一对应。如果将配置文件的key 值和属性的值设置为不一样,则结果输出为null。
@PropertySource: 加载指定的配置文件;
@configurationProperties :默认从全局配置文件中获取值;
application.yaml
person:
name: ljh
age: 18
answer: true
date: 2021/3/4
list:
- 听歌
- 敲代码
- 打游戏
map: {key1: value1,key2: value2}
dog:
name: xm
age: 3
yaml一定要注意空格和层级关系,一列在同一直线的是同一级
上面写出了对象,String,Integer,Boolean,Date,List,Map的写法。
person和dog是对象,name是String类型,age是Integer类型,answer是Boolean类型,list是List类型,map则是Map格式!
3、JSR303数据校验
Springboot中可以用@validated
来校验数据
@Component //注册bean
@ConfigurationProperties(prefix = "person")
@Validated //数据校验
public class Person {
@Email(message="邮箱格式错误") //name必须是邮箱格式
private String name;
}
常见参数:
@NotNull(message="名字不能为空")
private String userName;
@Max(value=120,message="年龄最大不能查过120")
private int age;
@Email(message="邮箱格式错误")
private String email;
空检查
@Null 验证对象是否为null
@NotNull 验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty 检查约束元素是否为NULL或者是EMPTY.
Booelan检查
@AssertTrue 验证 Boolean 对象是否为 true
@AssertFalse 验证 Boolean 对象是否为 false
长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内
@Length(min=, max=) string is between min and max included.
日期检查
@Past 验证 Date 和 Calendar 对象是否在当前时间之前
@Future 验证 Date 和 Calendar 对象是否在当前时间之后
@Pattern 验证 String 对象是否符合正则表达式的规则
4、thymeleaf
4.1、导入依赖
想要在templates里面的index.html直接起作用,需要导入thymeleaf依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
thymeleaf与SpringBoot完美整合,SpringBoot提供了Thymeleaf的默认配置,并且为Thymeleaf设置了视图解析器,我们可以像以前操作jsp一样来操作Thymeleaf
4.2、基本用法
在使用Thymeleaf时页面要引入命名空间: xmlns:th="http://www.thymeleaf.org"
1、th属性,常用th属性如下:
1)th:text:文本替换;
2)th:utext:支持html的文本替换。
3)th:value:属性赋值
4)th:each:遍历循环元素
5)th:if:判断条件,类似的还有th:unless,th:switch,th:case
6)th:insert:代码块引入,类似的还有th:replace,th:include,常用于公共代码块提取的场景
7)th:fragment:定义代码块,方便被th:insert和th:replace(html名称::fragment后面的值)引用,可以用(urlxx=‘xxx’)传入参数。 th:insert= “~{templatename::fragmentname}”,用波浪线。
8)th:object:声明变量,一般和*{}一起配合使用,达到偷懒的效果。
9)th:attr:设置标签属性,多个属性可以用逗号分隔
2、标准表达式语法:
${...}
变量表达式,Variable Expressions
@{...}
链接表达式,Link URL Expressions,适用于url及链接
#{...}
消息表达式,Message Expressions,适用于国际化取值
~{...}
代码块表达式,Fragment Expressions
*{...}
选择变量表达式,Selection Variable Expressions
<link th:href="@{/webjars/bootstrap/4.0.0/css/bootstrap.css}" rel="stylesheet">
<link th:href="@{/main/css/styleSheet.css}" rel="stylesheet">
<form class="form-login" th:action="@{/user/login}" th:method="post" >
<a class="btn btn-sm" th:href="@{/login.html(l='zh_CN')}">中文</a>
<a class="btn btn-sm" th:href="@{/login.html(l='en_US')}">English</a>
开发期间模板引擎页面修改以后,要实时生效。可以关闭模板引擎的缓存(application.yaml)
spring:
thymeleaf:
cache: false
5、员工管理系统
5.1、前期准备
导入相关依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--thymeleaf-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
暂时不用数据库,不涉及Mybatis,直接模拟数据
在pojo中新建员工表和部门表
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Department {
private Integer id;
private String departmentName;
}
@Data
public class Employee {
private Integer id;
private String lastName;
private String email;
private Integer gender;//0表示女,1表示男
private Department department;
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date birth;
public Employee() {
}
public Employee(Integer id, String lastName, String email, Integer gender, Department department) {
this.id = id;
this.lastName = lastName;
this.email = email;
this.gender = gender;
this.department = department;
this.birth = new Date();//用当前时间表示生日
}
}
编写dao层
部门dao
@Repository
public class DepartmentDao {
private static Map<Integer, Department> departments = null;
static {
departments = new HashMap<Integer, Department>();
departments.put(1, new Department(1, "后勤部"));
departments.put(2, new Department(2, "管理部"));
departments.put(3, new Department(3, "财政部"));
departments.put(4, new Department(4, "经理部"));
}
//获得所有部门信息
public Collection<Department> getDepartments() {
return departments.values();
}
//根据id获得部门信息
public Department getDepartmentById(Integer id) {
return departments.get(id);
}
}
员工dao
@Repository
public class EmployeeDao {
private static Map<Integer, Employee> employees = null;
@Autowired
private DepartmentDao departmentDao;
static {
employees = new HashMap<Integer, Employee>();
employees.put(101, new Employee(101,"花0","125478@qq.com",1,new Department(1,"后勤部")));
employees.put(102, new Employee(102,"花1","125478@qq.com",0,new Department(1,"后勤部")));
employees.put(103, new Employee(103,"花2","125478@qq.com",1,new Department(1,"后勤部")));
employees.put(104, new Employee(104,"花3","125478@qq.com",0,new Department(1,"后勤部")));
}
private static Integer initId = 105;
//增加
public void addEmployee(Employee employee) {
if(employee.getId()==null) {
employee.setId(initId++);
}
employee.setDepartment(departmentDao.getDepartmentById(employee.getDepartment().getId()));
//因为不涉及数据库,自己模拟数据,所以把id先保存下来,方便以后的更新操作。
employees.put(employee.getId(),employee);
}
//查
public Collection<Employee> getEmployees() {
return employees.values();
}
public Employee getEmployeeById(Integer id) {
return employees.get(id);
}
//删除
public void removeEmployee(Integer id) {
employees.remove(id);
}
}
设置主页
新建config包,包下新建MyMvcConfig
添加 @Configuration
并实现 WebMvcConfigurer
接口
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
}
}
5.2、国际化
properties中配置的国际化资源引发乱码问题
在idea中配置 File Encoding
的格式全为UTF-8,然后勾选 transparent native-to-ascii conversion
在resources下新建i18n
,在这个文件下新建 login.properties
,再新建login_zh_CN.properties
,idea会自动生成Resource Bundle ‘login’,然后再添加一个en_US(login_en_US.properties
)
idea下面就会出现Resource Boundle,然后依次添加需要的中英文及默认值就可以了
其次,需要在yaml中添加下列代码,否则也会乱码
spring:
messages:
basename: i18n/login
配置静态页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h2>这里是主页</h2>
<form action="">
<div th:text="#{login.tip}">请登录</div>
<div>
<input type="text" th:placeholder="#{login.username}">
</div>
<div>
<input type="password" th:placeholder="#{login.password}">
</div>
<input type="submit" th:value="#{login.btn}">
<div>
<a th:href="@{/index.html(l='zh_CN')}">中文</a>
<a th:href="@{/index.html(l='en_US')}">English</a>
</div>
</form>
</body>
</html>
config包下新建MyLocaleResolver类
public class MyLocaleResolver implements LocaleResolver {
@Override
public Locale resolveLocale(HttpServletRequest request) {
String language = request.getParameter("l");
Locale locale = Locale.getDefault();
if(!StringUtils.isEmpty(language)) {
String[] split = language.split("_");
locale = new Locale(split[0], split[1]);
}
return locale;
}
@Override
public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) {}
}
再MyMvcConfig下注册bean
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
}
@Bean
public LocaleResolver localeResolver() {//这里方法名必须是localeResolver,否则不执行这个方法
return new MyLocaleResolver();
}
}
注意:需要配置i18n
文件,注册Bean方法名必须是localeResolver
,html中取值用#{}
小知识:springboot2.4.x
添加导航栏自定义图标
先去favicon.ico制作网站制作一个favicon.ico,如https://tool.lu/favicon/
将favicon.ico复制入resources->static->images目录下
在需要的网页head中添加下面代码
<link rel="icon" th:href="@{/images/favicon.ico}" type="image/x-icon"/>
<link rel="bookmark" th:href="@{/images/favicon.ico}" type="image/x-icon"/>
我把index.html放到resources->templates目录下,上面的href必须是/images/favicon.ico,我尝试过更改为…/static/images/favicon.icon但是图标不显示。
5.3、登录
从主页form表单提交,核对用户名和密码,正确则进入dashboard.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h2>这里是主页</h2>
<form th:action="@{/login}">
<div th:text="#{login.tip}">请登录</div>
<p th:text="${msg}" style="color: red" th:if="${not #strings.isEmpty(msg)}"></p>
<div>
<input type="text" required name="username" th:placeholder="#{login.username}">
</div>
<div>
<input type="password" required name="password" th:placeholder="#{login.password}">
</div>
<input type="submit" th:value="#{login.btn}">
<div>
<a th:href="@{/index.html(l='zh_CN')}">中文</a>
<a th:href="@{/index.html(l='en_US')}">English</a>
</div>
</form>
</body>
</html>
LoginController
@Controller
public class LoginController {
@RequestMapping("/login")
public String login(@RequestParam("username") String username,
@RequestParam("password") String password,
Model model) {
if(!StringUtils.isEmpty(username) && "123456".equals(password)){
return "redirect:/main.html";//重定向,更符合实际开发场景
}
model.addAttribute("msg","用户名或密码错误");
return "index";
}
}
在MyMvcConfig里添加重定向
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
//登陆成功,页面路径会是http://localhost:8080/main.html,页面是dashboard.html
registry.addViewController("/main.html").setViewName("dashboard");
}
5.4、拦截器
防止未登录就可以进入后台,需要设置拦截器
在LoginController里面返回路径之前把username放入session中
@Controller
public class LoginController {
@RequestMapping("/login")
public String login(@RequestParam("username") String username,
@RequestParam("password") String password,
Model model, HttpSession session) {
if(!StringUtils.isEmpty(username) && "123456".equals(password)){
session.setAttribute("loginUser",username);
return "redirect:/main.html";
}
model.addAttribute("msg","用户名或密码错误");
return "index";
}
}
新建LoginHandlerInterceptor类
public class LoginHandlerInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Object loginUser = request.getSession().getAttribute("loginUser");
if(loginUser==null) {
request.setAttribute("msg","您没有权限进入该页面!");
request.getRequestDispatcher("/index.html").forward(request,response);
return false;
}else{
return true;
}
}
}
在MyMvcConfig里面注册拦截器
//注册拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginHandlerInterceptor()).addPathPatterns("/**").excludePathPatterns("/","/index.html","/login","/images/**");
}
上面代码意思就是拦截除了路径为"/","/index.html","/login","/images/**"之外的所有路径
前端页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div>这里是登录成功的页面</div>
<a>[[${session.loginUser}]]</a>
</body>
</html>
5.5、查询员工列表
EmployeeController
@Controller
public class EmployeeController {
@Autowired
private EmployeeDao employeeDao;
@RequestMapping("/emps")
public String list(Model model) {
Collection<Employee> employees = employeeDao.getEmployees();
model.addAttribute("emps", employees);
return "emp/list";
}
}
登陆成功后点击员工管理
<a th:href="@{/emps}">员工管理</a>
跳转到list.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!--layui必须放在libs目录下,否则报404-->
<!--<link rel="stylesheet" th:href="@{/libs/layui/css/layui.css}">-->
<!--<link rel="stylesheet" th:href="@{/css/common.css}">-->
</head>
<body>
<div>
<table>
<thead>
<tr>
<td>id</td>
<td>lastName</td>
<td>email</td>
<td>gender</td>
<td>department</td>
<td>birth</td>
<td>操作</td>
</tr>
</thead>
<tbody>
<tr th:each="emp : ${emps}">
<td th:text="${emp.getId()}"></td>
<td th:text="${emp.getLastName()}"></td>
<td th:text="${emp.getEmail()}"></td>
//下面的0不要加'',传过来是整数,写上''就会全变成男
<td th:text="${emp.getGender()==0?'女':'男'}"></td>
<td th:text="${emp.department.getDepartmentName()}"></td>
<td th:text="${#dates.format(emp.getBirth(),'yyyy-MM-dd')}"></td>
<td>
<button>编辑</button>
<button>删除</button>
</td>
</tr>
</tbody>
</table>
</div>
</body>
</html>
5.6、添加员工
在list.html中添加按钮
<div>
<button><a th:href="@{/emp}">添加员工</a></button>
</div>
在templates/emp文件夹下新建add.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div>
<form th:action="@{/emp}" method="post">
<input type="hidden" name="id">
<div>
姓名<input type="text" name="lastName">
</div>
<div>
邮箱<input type="email" name="email">
</div>
<div>性别:
<label><input type="radio" name="gender" value="1">男生</label>
<label><input type="radio" name="gender" value="0">女生</label>
</div>
<div>
部门<select name="department.id">//这里传回的是department的id
<option
th:each="department : ${departments}"
th:text="${department.getDepartmentName()}"
th:value="${department.getId()}">1</option>
</select>
</div>
<div>
生日<input type="date" name="birth">
</div>
<input type="submit" value="提交">
</form>
</div>
</body>
</html>
在EmployeeController中写接口
@Controller
public class EmployeeController {
@Autowired
private EmployeeDao employeeDao;
@Autowired
private DepartmentDao department;
@RequestMapping("/emps")
public String list(Model model) {
Collection<Employee> employees = employeeDao.getEmployees();
model.addAttribute("emps", employees);
return "emp/list";
}
@GetMapping("/emp")
public String toAdd(Model model) {
Collection<Department> departments = department.getDepartments();
model.addAttribute("departments",departments);
return "emp/add";
}
@PostMapping("/emp")
public String addEmp(Employee employee) {
employeeDao.addEmployee(employee);
return "redirect:/emps";
}
}
注意点:前端用type="date"传递给后端参数,一定要在pojo类的对应字段加上@DateTimeFormat(pattern = "yyyy-MM-dd")
5.7、修改员工信息
controller层编写接口
@GetMapping("/emp/{id}")
public String toUpdatePage(@PathVariable("id")Integer id, Model model) {
Employee employee = employeeDao.getEmployeeById(id);
model.addAttribute("employee", employee);
Collection<Department> departments = department.getDepartments();
model.addAttribute("departments", departments);
return "emp/update";
}
@PostMapping("/update")
public String updateEmp(Employee employee) {
employeeDao.addEmployee(employee);
return "redirect:/emps";
}
update.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div>
更新
<form th:action="@{/update}" method="post">
<input type="hidden" name="id" th:value="${employee.id}">
<div>
姓名<input type="text" th:value="${employee.lastName}" name="lastName">
</div>
<div>
邮箱<input type="email" th:value="${employee.email}" name="email">
</div>
<div>性别:
<label><input type="radio" th:checked="${employee.gender}==1" name="gender" value="1">男生</label>
<label><input type="radio" th:checked="${employee.gender}==0" name="gender" value="0">女生</label>
</div>
<div>
部门<select name="department.id">
<option th:each="dep:${departments}"
th:selected="${employee.getDepartment().id==dep.id}"
th:text="${dep.departmentName}"
th:value="${dep.id}">1</option>
</select>
</div>
<div>
生日<input type="date" name="birth" th:value="${#dates.format(employee.getBirth(),'yyyy-MM-dd')}">
</div>
<input type="submit" value="提交">
</form>
</div>
</body>
</html>
其中,1
value要写dep.id,如果写employee.getDepartment().id,这是一个常量,不会更改数据
在list.html中添加编辑按钮
<!--<button><a th:href="@{/emp/}+${emp.getId()}">编辑</a></button>-->
<button><a th:href="@{'/emp/'+${emp.getId()}}">编辑</a></button>
注意:上面两种写法都能出来效果,但是第一种idea会报红波浪线,不影响正常编译。
5.8、删除员工
controller层
@RequestMapping("/delete/{id}")
public String delEmp(@PathVariable("id") Integer id) {
employeeDao.removeEmployee(id);
return "redirect:/emps";
}
list.html添加删除按钮
<button><a th:href="@{'/delete/'+${emp.getId()}}">删除</a></button>
5.9、404页面
直接在template下面新建error文件夹,文件夹下新建404.html,springboot会自动识别其中的404.html
5.10、注销登录
直接把session清理掉即可
controller接口
@RequestMapping("/logout")
public String layout(HttpSession session) {
session.invalidate();
return "redirect:/index.html";
}
注销登录按钮
<a th:href="@{/logout}">注销登录</a>
6、整合Mybatis
6.1、springboot的jdbc
导入依赖,导入Spring Web、JDBC API、MySQL Driver依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
application.yml配置数据源
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/javatest?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
测试
@SpringBootTest
class Springbootdemo02ApplicationTests {
@Autowired
DataSource dataSource;
@Test
void contextLoads() throws SQLException {
//默认数据源hikari
System.out.println(dataSource.getClass());//class com.zaxxer.hikari.HikariDataSource
Connection connection = dataSource.getConnection();
System.out.println(connection);
connection.close();
}
}
6.2、jdbc的CRUD
新建JDBCController
@RestController
public class JDBCController {
@Autowired
JdbcTemplate jdbcTemplate;//这里面封装了全部的增删改查方法
@RequestMapping("/studentList")
public List<Map<String, Object>> studentList() {
String sql = "select * from student";
List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
return maps;
}
@RequestMapping("/addStudent")
public String addStudent() {
String sql = "insert into student(id,name,phone) values (3,'公孙里','1008611')";
jdbcTemplate.update(sql);
return "add-ok";
}
@RequestMapping("/updateStudent/{id}")
public String updateStudent(@PathVariable("id") int id) {
// String sql = "update student set name='豆豆',phone='888888' where id=?";
String sql = "update student set name=?,phone=? where id=" + id;
Object[] objects = new Object[2];
objects[0] = "豆豆";
objects[1] = "888888";
jdbcTemplate.update(sql, objects);
return "update-ok";
}
@RequestMapping("/deleteStudent/{id}")
public String deleteStudent(@PathVariable("id") int id) {
String sql = "delete from student where id=?";
jdbcTemplate.update(sql, id);
return "delete-ok";
}
}
6.3、整合Druid
导入Druid依赖
<!-- druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.5</version>
</dependency>
然后只需要在application.yml中添加type属性,即可更改数据源
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/javatest?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
此时数据源就变为class com.alibaba.druid.pool.DruidDataSource
配置Druid
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/javatest?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
#Spring Boot 默认是不注入这些属性值的,需要自己绑定
#druid 数据源专有配置
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
#配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
#如果允许时报错 java.lang.ClassNotFoundException: org.apache.log4j.Priority
#则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
然后导入log4j的依赖
<!--log4j-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
在resources文件下新建log4j.properties,不创建的话一会测试会报错
log4j.rootLogger=DEBUG, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
在config中新建DruidConfig
@Configuration
public class DruidConfig {
@Bean
@ConfigurationProperties("spring.datasource")
public DataSource druidDataSource() {
return new DruidDataSource();
}
}
@ConfigurationProperties(prefix = "spring.datasource")
:作用就是将 全局配置文件中前缀为 spring.datasource的属性值注入到 com.alibaba.druid.pool.DruidDataSource 的同名参数中
测试
@SpringBootTest
class Springbootdemo02ApplicationTests {
@Autowired
DataSource dataSource;
@Test
void contextLoads() throws SQLException {
//默认数据源hikari
System.out.println(dataSource.getClass());//class com.zaxxer.hikari.HikariDataSource
Connection connection = dataSource.getConnection();
DruidDataSource druidDataSource = (DruidDataSource) dataSource;
System.out.println("getMaxActive是:"+druidDataSource.getMaxActive());//20
connection.close();
}
}
配置Druid数据源监控
DruidConfig中配置
//配置 Druid 监控管理后台的Servlet;
//内置 Servlet 容器时没有web.xml文件,所以使用 Spring Boot 的注册 Servlet 方式
@Bean
public ServletRegistrationBean statViewServlet() {
ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
// 这些参数可以在 com.alibaba.druid.support.http.StatViewServlet
// 的父类 com.alibaba.druid.support.http.ResourceServlet 中找到
Map<String, String> initParams = new HashMap<>();
initParams.put("loginUsername", "admin"); //后台管理界面的登录账号
initParams.put("loginPassword", "123456"); //后台管理界面的登录密码
//后台允许谁可以访问
//initParams.put("allow", "localhost"):表示只有本机可以访问
//initParams.put("allow", ""):为空或者为null时,表示允许所有访问
initParams.put("allow", "");
//deny:Druid 后台拒绝谁访问
//initParams.put("deny", "192.168.0.1");表示禁止此ip访问
//设置初始化参数
bean.setInitParameters(initParams);
return bean;
}
http://localhost:8080/druid 访问此链接进入druid登录页面,输入账号密码登录
配置 Druid web 监控 filter 过滤器
DruidConfig中配置
//配置 Druid 监控之web监控的 filter
//WebStatFilter:用于配置Web和Druid数据源之间的管理关联监控统计
@Bean
public FilterRegistrationBean webStatFilter() {
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new WebStatFilter());
//exclusions:设置哪些请求进行过滤排除掉,从而不进行统计
Map<String, String> initParams = new HashMap<>();
initParams.put("exclusions", "*.js,*.css,/druid/*,/jdbc/*");
bean.setInitParameters(initParams);
//"/*" 表示过滤所有请求
//bean.setUrlPatterns(Arrays.asList("/*"));
return bean;
}
6.4、整合Mybatis
一定要导入依赖!!!
<!-- mybatis-spring-boot-starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
其余的依赖为:
<dependencies>
<!-- mybatis-spring-boot-starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<!--jdbc-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!--thymeleaf-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
创建实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
private int id;
private String name;
private String phone;
}
在application.yml中配置数据源
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/javatest?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
在com.ljh.mapper中编写mapper接口
@Repository
@Mapper
public interface StudentMapper {
List<Student> queryAllStudents();
Student queryStudentById(int id);
int addStudent(Student student);
int delStudent(int id);
int updateStudent(Student student);
}
可以直接在mapper接口上添加@Mapper
,也可以在工程的主入口处添加@MapperScan("com.ljh.mapper")
@Mapper表示这是Mybatis的一个Mapper类
在application.yml中配置别名和mapper路径
mybatis:
type-aliases-package: com.ljh.pojo
mapper-locations: classpath:mybatis/mapper/*.xml
resources目录下新建resources->mybatis->mapper->xxx.xml
StudentMapper.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">
<!--namespace绑定一个Mapper-->
<mapper namespace="com.ljh.mapper.StudentMapper">
<select id="queryAllStudents" resultType="Student">
select * from student
</select>
</mapper>
编写接口
@RestController
public class StudentController {
@Autowired
StudentMapper studentMapper;
// 下面是set方式注入
// @Autowired
// public void setStudentMapper(StudentMapper studentMapper) {
// this.studentMapper = studentMapper;
// }
@RequestMapping("/queryAllStudent")
public List<Student> queryAllStudent() {
List<Student> students = studentMapper.queryAllStudents();
return students;
}
}
其他增删改查大同小异,不再多写。
7、SpringSecurity
Spring Security是一个框架,侧重于为Java应用程序提供身份验证和授权。
7.1、基本使用
现有templates->level1、level2、level3下新建1.html,2.html,3.html
编写RouterController
@Controller
public class RouterController {
//主页
@RequestMapping({"/","/index","index.html"})
public String index() {
return "index";
}
@RequestMapping("/level1/{id}")
public String level1(@PathVariable("id") int id) {
return "level1/"+id;
}
@RequestMapping("/level2/{id}")
public String level2(@PathVariable("id") int id) {
return "level2/"+id;
}
@RequestMapping("/level3/{id}")
public String level3(@PathVariable("id") int id) {
return "level3/"+id;
}
}
测试接口运行。
此时导入security的依赖
<!--security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
“认证”(Authentication)
身份验证是关于验证您的凭据,如用户名/用户ID和密码,以验证您的身份。
身份验证通常通过用户名和密码完成,有时与身份验证因素结合使用。
“授权” (Authorization)
授权发生在系统成功验证您的身份后,最终会授予您访问资源(如信息,文件,数据库,资金,位置,几乎任何内容)的完全权限。
编写配置类SecurityConfig
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// 定制请求的授权规则
// 首页所有人可以访问,其他页面只有对应角色可以进入
http.authorizeRequests().antMatchers("/").permitAll()
.antMatchers("/level1/**").hasRole("vip1")
.antMatchers("/level2/**").hasRole("vip2")
.antMatchers("/level3/**").hasRole("vip3");
// 开启自动配置的登录功能
// /login 请求来到登录页
// /login?error 重定向到这里表示登录失败
http.formLogin();
// 注销,注销成功后返回到首页
http.logout().logoutSuccessUrl("/");
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//下面的密码必须要加上,否则报错
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("admin").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3")
.and()
.withUser("ljh").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3");
}
}
一定要加上@EnableWebSecurity
,然后继承WebSecurityConfigurerAdapter
,重写config
在thymeleaf上显示,index.html
首先需要导入thymeleaf
和thymeleaf-extras-springsecurity5
依赖
<!-- thymeleaf-extras-springsecurity5 -->
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
<!--thymeleaf-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
index.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h2 style="color: red">首页</h2>
<div style="margin: 100px auto;width: 900px;height: 400px;">
<div sec:authorize="hasRole('vip1')" style="display: inline-block;width: 290px;height: 400px;">
<a th:href="@{/level1/1}">level1-1</a>
<a th:href="@{/level1/2}">level1-2</a>
<a th:href="@{/level1/3}">level1-3</a>
</div>
<div sec:authorize="hasRole('vip2')" style="display: inline-block;width: 290px;height: 400px;">
<a th:href="@{/level2/1}">level2-1</a>
<a th:href="@{/level2/2}">level2-2</a>
<a th:href="@{/level2/3}">level2-3</a>
</div>
<div sec:authorize="hasRole('vip3')" style="display: inline-block;width: 290px;height: 400px;">
<a th:href="@{/level3/1}">level3-1</a>
<a th:href="@{/level3/2}">level3-2</a>
<a th:href="@{/level3/3}">level3-3</a>
</div>
</div>
<div sec:authorize="!isAuthenticated()">
<a th:href="@{/login}">登录</a>
</div>
<div sec:authorize="isAuthenticated()">
用户名:<span sec:authentication="principal.username"></span>
角色:<span sec:authentication="principal.authorities"></span>
<a th:href="@{/logout}">注销</a>
</div>
</body>
</html>
sec:authorize
相当于vue中的v-show,v-if,就是判断显不显示
注意:一定要加上命名空间
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5"
如果 sec:authorize
不生效,可能是版本问题,可以在这个方面考虑。
7.2、自定义登录
只需要在config中加入http.rememberMe();
就可以保存cookie
但是这里默认使用的都是springsecurity自带的登录页面,所以需要自定义登录页面
跳转到自己的登录页,需要写http.formLogin().loginPage("/xxx")跳转到指定路由
首页登录按钮
<div sec:authorize="!isAuthenticated()">
<a th:href="@{/toLogin}">登录</a>
</div>
SecurityConfig
http.formLogin().usernameParameter("user").passwordParameter("pwd").loginPage("/toLogin");
//记住我
http.rememberMe().rememberMeParameter("remember");
//关闭csrf功能:跨站请求伪造,默认只能通过post方式提交logout请求
http.csrf().disable();
login.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form th:action="@{/toLogin}" method="post">
<div>用户名
<input type="text" name="user">
</div>
<div>密码
<input type="password" name="pwd">
</div>
<div>
<input type="checkbox" name="remember">记住我
</div>
<input type="submit" value="登录">
</form>
</body>
</html>
注意点:SecurityConfig中的usernameParameter和passwordParameter不是必须的,当前端传过来的用户名和密码对应name值为username和password时不必写,系统默认就是username和password。但是如果像上面一样name分别是user和pwd,那么usernameParameter和passwordParameter必须写上,否则程序报错404.
另外,rememberme也需要添加参数对应前端的name值
登录页面请求方式必须是POST
注销时,如果报错404,可以添加下面的代码
//关闭csrf功能:跨站请求伪造,默认只能通过post方式提交logout请求,但是前端写的是一个a标签,不是一个form表单,所以需要设置关闭csrf功能
http.csrf().disable();
8、shiro
- subject: 应用代码直接交互的对象是Subject, 也就是说Shiro的对外API核心就是Subject, Subject代表了当前的用户。Subject其实是一一个门面, SecurityManageer 才是实际的执行者
- SecurityManager: 安全管理器,即所有与安全有关的操作都会与SercurityManager交互, 并且它管理着所有的Subject,可以看出它是Shiro的核心,它负责与Shiro的其他组件进行交互,它相当于SpringMVC的DispatcherServlet的角色
- Realm: Shiro从Realm获取安全数据 (如用户,角色,权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较,来确定用户的身份是否合法;也需要从Realm得到用户相应的角色、权限,进行验证用户的操作是否能够进行,可以把Realm看DataSource;
8.1、整合spring
新建springboot空项目,导入web和thymeleaf依赖
导入shiro-spring
依赖
<!-- shiro-spring -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.7.1</version>
</dependency>
目录结构为:templates下新建views,views下新建add.html和update.html
新建login.html和index.html
<!--login.html-->
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h2>登录页</h2>
<p style="color: red" th:text="${msg}"></p>
<form th:action="@{/login}">
<p>用户名:<input type="text" name="username"></p>
<p>密码:<input type="password" name="password"></p>
<p><input type="submit"></p>
</form>
</body>
</html>
<!--index.html-->
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h2>首页</h2>
<a th:href="@{/add}">add</a>
<a th:href="@{/update}">update</a>
</body>
</html>
先配置各自的接口
@Controller
public class MyController {
@RequestMapping({"/","/index"})
public String index() {
return "index";
}
@RequestMapping("/add")
public String add() {
return "views/add";
}
@RequestMapping("/update")
public String update() {
return "views/update";
}
@RequestMapping("/toLogin")
public String toLogin() {
return "login";
}
@RequestMapping("/login")
public String login(String username, String password, Model model) {
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try {
subject.login(token);
return "index";
} catch (UnknownAccountException e){//用户名不存在
model.addAttribute("msg","用户名错误");
return "login";
}catch (IncorrectCredentialsException e){//密码不存在
model.addAttribute("msg","密码错误");
return "login";
}
}
}
最后面的/login接口得到当前用户subject,再判断是否用户名和密码正确,跳转到相应页面。
配置ShiroConfig和UserRealm
public class UserRealm extends AuthorizingRealm {
// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了授权doGetAuthorizationInfo");
return null;
}
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行了认证doGetAuthenticationInfo");
//模拟数据
String name = "root";
String password = "123456";
//强制转换为UsernamePasswordToken
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
if(!token.getUsername().equals(name)) {
return null;//表示用户名不存在
}
//密码shiro自己做
return new SimpleAuthenticationInfo("",password,"");
}
}
@Configuration
public class ShiroConfig {
//ShiroFilterFactoryBean
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
//添加shiro的内置过滤器
/*
anon:无需认证就能访问
authc:必须认证才能访问
user:必须拥有记住我功能才能访问
perms:拥有某个资源的权限才能访问
role:拥有某个角色权限才能访问
*/
Map<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/add","authc");
filterMap.put("/update","authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
shiroFilterFactoryBean.setLoginUrl("/toLogin");
return shiroFilterFactoryBean;
}
//DefaultWebSecurityManager
@Bean(name = "securityManager")
public SecurityManager getSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(userRealm);
return securityManager;
}
// Realm
@Bean
public UserRealm userRealm() {
return new UserRealm();
}
}
注意:ShiroConfig从下往上写,userRealm->getSecurityManager->getShiroFilterFactoryBean
传值引用bean时直接在参数前加@Qualifier("xxx")
8.2、整合mybatis
导入mybatis依赖
<!-- mybatis-spring-boot-starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<!-- druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.5</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--jdbc-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
除此之外,shiro想要用thymeleaf,整合thymeleaf
<!-- thymeleaf-extras-shiro -->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
配置数据库,下面是总的配置application.yml
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/javatest?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
#Spring Boot 默认是不注入这些属性值的,需要自己绑定
#druid 数据源专有配置
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
#配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
#如果允许时报错 java.lang.ClassNotFoundException: org.apache.log4j.Priority
#则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
thymeleaf:
cache: false
mybatis:
type-aliases-package: com.ljh.pojo
mapper-locations: classpath:mybatis/mapper/*.xml
新建pojo,mapper,service
Student实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
private int id;
private String name;
private String password;
private String perms;
}
mapper接口
@Repository
@Mapper
public interface StudentMapper {
Student getStudentByName(String name);
}
service接口
public interface StudentService {
Student getStudentByName(String name);
}
service接口实现类
@Service
public class StudentServiceImpl implements StudentService {
StudentMapper studentMapper;
@Autowired
public void setStudentMapper(StudentMapper studentMapper) {
this.studentMapper = studentMapper;
}
@Override
public Student getStudentByName(String name) {
return studentMapper.getStudentByName(name);
}
}
在resources下新建mybatis->mapper->StudentMapper.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">
<!--namespace绑定一个Mapper-->
<mapper namespace="com.ljh.mapper.StudentMapper">
<select id="getStudentByName" resultType="Student">
select * from student where name = #{name}
</select>
</mapper>
配置好可以在测试类中测试。
主要介绍shiro与mybatis整合
新建ShiroConfig配置文件
@Configuration
public class ShiroConfig {
//ShiroFilterFactoryBean
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
//添加shiro的内置过滤器
/*
anon:无需认证就能访问
authc:必须认证才能访问
user:必须拥有记住我功能才能访问
perms:拥有某个资源的权限才能访问
role:拥有某个角色权限才能访问
*/
Map<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/add","perms[add]");
filterMap.put("/update","perms[update]");
//设置登录页url
shiroFilterFactoryBean.setLoginUrl("/toLogin");
//未授权
shiroFilterFactoryBean.setUnauthorizedUrl("/noauth");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
return shiroFilterFactoryBean;
}
//DefaultWebSecurityManager
@Bean(name = "securityManager")
public SecurityManager getSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(userRealm);
return securityManager;
}
// Realm
@Bean
public UserRealm userRealm() {
return new UserRealm();
}
//shiro thymeleaf
@Bean
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
}
上面差不多都是固定代码,只需要更改ShiroFilterFactoryBean的具体实现即可。
UserRealm类
public class UserRealm extends AuthorizingRealm {
@Autowired
StudentService studentService;
// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了授权doGetAuthorizationInfo");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//拿到当前用户登陆对象
Subject subject = SecurityUtils.getSubject();
//这里拿到的就是认证时传来的Student对象
Student currentStudent = (Student) subject.getPrincipal();
info.addStringPermission(currentStudent.getPerms());
// info.addStringPermission("add");
return info;
}
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行了认证doGetAuthenticationInfo");
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
Student student = studentService.getStudentByName(token.getUsername());
if(student==null) {
return null;
}
String name = student.getName();
String password = student.getPassword();
//上面根据前端输入框输入的用户名在数据库查询,
// 返回null或具体的Student对象,下面的代码没有作用
//因为忽略了student可能为空,一开始总是报空指针错误!
// if (!token.getUsername().equals(name)) {
// return null;
// }
//new SimpleAuthenticationInfo(principal,credentials,realmName)
//第一个参数是返回的值,可以通过subject.getPrincipal获取到当前的Student对象
//第二个是密码验证
return new SimpleAuthenticationInfo(student, password, "");
}
}
controller
@RequestMapping("/login")
public String login(String username, String password, Model model, HttpSession session) {
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try {
subject.login(token);
session.setAttribute("loginUser",username);
return "index";
} catch (UnknownAccountException e){//用户名不存在
model.addAttribute("msg","用户名错误");
return "login";
}catch (IncorrectCredentialsException e){//密码不存在
model.addAttribute("msg","密码错误");
return "login";
}
}
@RequestMapping("/noauth")
@ResponseBody
public String noauth() {
return "未经授权,无法访问";
}
@RequestMapping("/logout")
public String logout() {
Subject subject = SecurityUtils.getSubject();
subject.logout();
return "redirect:/index";
}
index.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h2>首页</h2>
<div th:if="${session.loginUser==null}">
<a th:href="@{/toLogin}">登陆</a>
</div>
<div th:if="${session.loginUser!=null}">
<a th:href="@{/logout}">注销</a>
</div>
<div shiro:hasPermission="add">
<a th:href="@{/add}">add</a>
</div>
<div shiro:hasPermission="update">
<a th:href="@{/update}">update</a>
</div>
</body>
</html>
上面需要引入shiro的命名空间 xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"
shiro:hasPermission="add"允许有这个权限的显示add,这个权限在ShiroConfig里面通过map来设置,如果路径为/user/add,则parms[user:add],若有多个权限,中间用逗号分开
login.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h2>登录页</h2>
<p style="color: red" th:text="${msg}"></p>
<form th:action="@{/login}">
<p>用户名:<input type="text" name="username"></p>
<p>密码:<input type="password" name="password"></p>
<p><input type="submit"></p>
</form>
</body>
</html>
9、Swagger
- 号称世界上最流行的API框架
- Restful Api 文档在线自动生成器 => API 文档 与API 定义同步更新
- 直接运行,在线测试API
9.1、基本使用
导入swagger2
和swagger-ui
的pom依赖
<!-- swagger2 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<!-- swagger-ui -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
集成Swagger
SwaggerConfig配置类
@Configuration
@EnableSwagger2 // 开启Swagger2的自动配置
public class SwaggerConfig {
@Bean
public Docket docket() {
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo());
}
//通过apiInfo()属性配置文档信息
private ApiInfo apiInfo() {
return new ApiInfo(
"swagger",
"最好的API",
"v1.0",
"http://baidi.com",
new Contact("Ljh","www.baidu.com","ljhprogram@163.com"),
"Apache 2.0",
"http://www.apache.org/licenses/LICENSE-2.0",
new ArrayList<>()
);
}
}
访问http://localhost:8080/swagger-ui.html即可。
9.2、配置扫描接口
@Bean
public Docket docket() {
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo())
//通过.select()方法,去配置扫描接口,select和build同时出现
.select()
//扫描controller下的所有api
.apis(RequestHandlerSelectors.basePackage("com.ljh.controller"))
//.paths(PathSelectors.ant("/ljh/**"))过滤ljh包下的所有文件
.build();
}
RequestHandlerSelectors可以跟以下参数
any() // 扫描所有,项目中的所有接口都会被扫描到
none() // 不扫描接口
// 通过方法上的注解扫描,如withMethodAnnotation(GetMapping.class)只扫描get请求
withMethodAnnotation(final Class<? extends Annotation> annotation)
// 通过类上的注解扫描,如.withClassAnnotation(Controller.class)只扫描有controller注解的类中的接口
withClassAnnotation(final Class<? extends Annotation> annotation)
basePackage(final String basePackage) // 根据包路径扫描接口
9.3、配置Swagger开关
new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).enable(false)
//配置是否启用Swagger,如果是false,在浏览器将无法访问
项目需要创立默认的application.yml和开发环境、生产环境
server:
port: 8080
spring:
profiles:
active: dev #设置为开发环境
开发环境: application-dev.yml
# 开发环境
server:
port: 8081
生产环境: application-pro.yml
# 生产环境
server:
port: 8082
如何设置在开发环境dev显示swagger,在生产环境pro不显示
@Bean
public Docket docket(Environment environment) {
Profiles of = Profiles.of("dev");
// 判断当前是否处于dev环境
boolean b = environment.acceptsProfiles(of);
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo())
// 通过 enable() 接收此参数判断是否要显示
.enable(b)
//通过.select()方法,去配置扫描接口,select和build同时出现
.select()
//扫描controller下的所有api
.apis(RequestHandlerSelectors.basePackage("com.ljh.controller"))
//.paths(PathSelectors.ant("/ljh/**"))过滤ljh报下的所有文件
.build();
}
测试时注意端口号发生了变动,要更改端口号。
9.4、配置API分组
new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).groupName("Ljh")
// 配置分组,默认为default,现在修改为Ljh
分组可以设置多个,通过new Docket多个实例并定义groupName实现多个分组
@Bean
public Docket docket1() {
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo())
.groupName("A");
}
@Bean
public Docket docket2() {
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo())
.groupName("B");
}
@Bean
public Docket docket3() {
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo())
.groupName("C");
}
9.5、常用注解
实体类
@ApiModel("用户实体类")
public class User {
@ApiModelProperty("用户名")
public String name;
@ApiModelProperty("密码")
public String password;
}
controller
@RestController
public class Hello {
@GetMapping("/hello")
@ApiOperation("你好接口")
public String hello() {
return "hello";
}
@PostMapping("/user")
@ApiOperation("空user接口")
public User user() {
return new User();
}
@GetMapping("/getUserName")
@ApiOperation("得到名字接口")
public String getUserName(@ApiParam("名字") String name) {
return name;
}
@GetMapping("/getPassword")
public String getPassword(@ApiParam("密码")String password) {
return password;
}
}
各个注解作用:
- @Api(tags = “xxx模块说明”)作用在模块类上
- @ApiOperation(“xxx接口说明”)作用在接口方法上
- @ApiModel(“xxxPOJO说明”)作用在返回对象类上
- @ApiModelProperty(value = “xxx属性说明”,hidden = true)作用在类方法和属性上,hidden设置为true可以隐藏该属性
- @ApiParam(“xxx参数说明”)作用在参数、方法和字段上,类似@ApiModelProperty
10、异步任务、邮件发送、定时任务
10.1、异步任务
@Service
public class AsyncService {
@Async
public void hello() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("正在执行中。。");
}
}
如果没加@Async,代码会同步执行,等待3秒后在控制台输出语句。
在方法上加了@Async
注解之后,函数的主入口还需要添加一个注解@EnableAsync
@EnableAsync //开启异步注解功能
@SpringBootApplication
public class SpringbootTestApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootTestApplication.class, args);
}
}
controller
@RestController
public class AsyncController {
@Autowired
AsyncService asyncService;
@RequestMapping("/hello")
public String hello() {
asyncService.hello();
return "执行完毕!";
}
}
没有异步,前端请求参数时,就必须等待3秒,在浏览器就会出现一直转圈3秒的情况,用户体验很不好。
现在加了两个注解之后,用户就会直接得到页面信息。
10.2、邮件发送
先导入邮件依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
设置application.yml,配置mail的相关属性,具体属性可到MailProperties.class中查看。
spring:
mail:
username: xxxxxxx@163.com
password: UPXWKNESESBSXPOA
host: smtp.163.com
密码获取:到https://mail.163.com/登录,打开设置, 打开POP3/SMTP/IMAP ,开启相应的服务,会让你发送一条短信,然后给你一条密钥。
另外qq需要配置ssl:
spring.mail.properties.mail.smtp.ssl.enable=true
在测试类中实现简单邮件(只有文字)
@SpringBootTest
class SpringbootTestApplicationTests {
@Autowired
JavaMailSender mailSender;
@Test
void contextLoads() {
SimpleMailMessage message = new SimpleMailMessage();
message.setSubject("你好呀~");
message.setText("第一个简单邮件发送");
message.setTo("xxxxxx@163.com");
message.setFrom("xxxxxx@163.com");
mailSender.send(message);
}
}
测试运行,邮件发送成功。
在测试类中实现简单邮件,加了图片
@SpringBootTest
class SpringbootTestApplicationTests {
@Autowired
JavaMailSender mailSender;
@Test
void contextLoad2() throws MessagingException {
MimeMessage mimeMessage = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
helper.setSubject("复杂");
helper.setText("这是一个复杂邮件");
发送附件,下面发送的是图片和视频
helper.addAttachment("pic.jpg",new File("E:\\电脑壁纸1920×1080\\pic.jpg"));
helper.addAttachment("知足.mp4",new File("D:\\迅雷下载\\zhizu.mp4"));
helper.setTo("xxxxxx@163.com");
helper.setFrom("xxxxxx@163.com");
mailSender.send(mimeMessage);
}
}
10.3、定时任务
开启定时任务,首先要在主函数上加一个注解@EnableScheduling
@EnableScheduling //开启基于注解的定时任务
@SpringBootApplication
public class SpringbootTestApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootTestApplication.class, args);
}
}
ScheduleService
@Service
public class ScheduledService {
@Scheduled(cron = "0/2 * * * * ? ")
public void hello() {
System.out.println("hello");
}
}
只需要在对应方法上添加注解@Scheduled(cron表达式)
即可。
10.4、cron表达式
Cron语法格式,共7个部分(域)组成:
Seconds(秒) Minutes(分钟) Hours(小时) DayofMonth(天/几号) Month(月) DayofWeek(星期几,0和7代表星期天) Year(年)
每一个域可出现的字符如下:
Seconds:可出现", - * /",有效范围为0-59的整数
Minutes:可出现", - * /",有效范围为0-59的整数
Hours:可出现", - * /",有效范围为0-23的整数
DayofMonth(天/几号):可出现", - * / ? L W C",有效范围为0-31的整数
Month:可出现", - * /",有效范围为1-12的整数
DayofWeek(星期几) :可出现", - * / ? L C #",有效范围为1-7的整数或SUN-SAT(星期天开始)两个范围
Year:可出现", - * /"
(1) *:表示匹配该域的任意值。
(2) ?:只能用在DayofMonth和DayofWeek。DayofMonth和 DayofWeek会相互影响。设置其中某一个值后,为了避免冲突,需要将另一个的值设为“?”
(3) -:表示范围,在某一个时间范围内触发一次
(4) /:表示起始时间开始触发,然后每隔固定时间触发一次
例如在分钟域里使用5/10,则意味着5分钟触发一次,15,25等分别触发一次.
在(分钟)里的“0/15”,表示从第0分钟开始,每15分钟触发一次
(5) ,:表示and
(6) L:表示最后,
在天/日(月)域中,“L”表示一个月的最后一天
在天(星期)域中,“L”表示一个星期的最后一天
L只能出现在DayofWeek和DayofMonth域
在线生成cron表达式:https://cron.qqe2.com/