微服务架构
微服务是一种架构风格,开发构建应用的时候把应用的业务构建成一个个的小服务(这就类似于把我们的应用程序构建成了一个个小小的盒子,它们在一个大的容器中运行,这种一个个的小盒子我们把它叫做服务),通过服务的组合来完成具体的业务。服务之间相互独立,互不影响
第一个springboot程序
环境:
springboot2.7.4
maven3.6.2
初始化项目
通过idea集成工具初始化springboot
1.new一个新的项目,选择spring初始化,选择官方地址(这里我们也可以使用下面的custom填入alibaba的地址来初始化),点击next
2.填写组名和项目名,选择jdk版本为8,删除多余的包路径(避免包路径过于复杂),点击next
3.初始化导入依赖,这里导入两个(也可以什么都不用导入,后期我们手动导入),web和devtools(热部署工具,每次修改不同重新启动服务器)。左上角直接搜索devtools即可,点击next
4.最后一步点击finish,至此项目初始化完毕
@ResponseBody的使用(返回json字符串)
@ResponseBody是在标注了@Controller的类中使用的,它本身标注在类的方法上,代表该类的这个方法只返回json字符串,不经过视图解析进行视图跳转。和@RestController不同的是,它可以使用在控制视图跳转的类(标注了@Controller的类)中,只标注一个方法不进入视图解析器。
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class HelloController {
@RequestMapping("/hello")
@ResponseBody
public String hello() {
return "hello,world!";
}
}
修改默认端口号
在application.properties文件中或者application.yml文件中
# 修改项目的端口号
server.port=8081
修改启动默认logo
因为springboot的默认配置。默认配置中存在一个路径resource/banner.txt,如果该路径下的banner.txt文件存在,则启动logo显示为该文件内容。如果不存在,则显示默认logo。所以我们只需要新建一个文件banner.txt就可以替换logo
springboot自动装配原理
springboot项目都是基于自动配置
springboot的版本管理
在springboot项目中,我们导入的依赖不需要版本号,这是因为所有的版本都在springboot项目中的父依赖管理了,springboot的父依赖规划好了所有我们可能会用到的jar包的版本。选择了springboot的版本也就是选择了之后所有用到jar包的版本。springboot核心依赖都在父工程中
springboot启动器
所有带有starter的依赖都是springboot的启动器。启动器其实就是springboot把我们实际会用到的的项目的一些场景给抽象出来了。比如web启动器,就是给web环境准备的,它会把web需要用到的jar打包进入一个依赖(这里依赖就是启动器),我们只需要导入这个springboot打包好的依赖,就可以直接进行相关场景的开发。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
springboot主程序
就像一个普通的java程序一样,springboot也就一个主程序入口。这个入口用来启动我们整个springboot项目
//标注一个springboot应用程序
@SpringBootApplication
public class Springboot01HelloworldApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot01HelloworldApplication.class, args);
}
}
@SpringBootApplication内部原理
这个注解的内部其实也有一个个的其他注解组成。
@SpringBootConfiguration : springboot配置类
@Configuration : spring配置类
@Component : 组件
@EnableAutoConfiguration : 启用自动配置
@AutoConfigurationPackage : 自动配置包
@Import({AutoConfigurationImportSelector.class}) : 自动配置包注册
@Import({AutoConfigurationImportSelector.class}) : 导入选择器
yaml格式的配置文件
application.yaml非配置的写法
# 1.可以写kv键值对
name: zhong
# 2.对象
student:
name: zhong
age: 3
# 3.对象的行内写法
student2: {name: zhong, age: 4}
# 4.数组
pets:
- dog
- cat
- pig
# 5.数组的行内写法
pets2: [cat, dog, pig]
yaml给实体类赋值@ConfigurationProperties
首先在application.yaml中准备好数据
person:
name: zhong
age: 3
happy: false
birth: 2000/1/1
maps: {k1: va, k2: v2}
list:
- code
- music
- girl
dog:
name: 旺财
age: 3
在实体类中加上注解@ConfigurationProperties(prefix = “person”),括号内的是指定需要给实体类绑定yaml文件中的哪个数据。加上注解之后会爆红提示我们配置,但是这个配置可有可无,它并不影响程序的正常运行,如需解决爆红,在pom.xml文件中加入依赖
JSR303校验
@Validated在类上标注,意思是开启数据校验。然后通过在属性上添加注解来实现校验。
多环境配置和配置文件的位置
配置文件可以在四个位置被检测到
优先级:项目/config > 项目 > resource/config > resource
首先第二套环境配置文件名为application-dev.yaml
# springboot 的多环境配置可以选择激化哪一个环境
spring:
profiles:
active: dev
yaml存在多文档格式
server:
port: 8081
spring:
profiles:
active: test
#多文档模式,使用 --- 分隔开
---
server:
port: 8082
# 给第二套环境命名,此方式已经弃用
spring:
profiles: test
# 在2.4之后的版本中推荐使用
spring:
config:
activate:
on-profile: dev
自动配置原理理解
AutoConfiguration注解去装载一个XXXproperties.java文件,这个文件又去通过注解加载了yml文件中我们自定义的一些配置。那么如果我们yml中的配置存在,XXXproperties文件生效。自动装配去找到这个文件并且把它加载进来实现加载我们的自定义配置。如果不存在自定义配置,那么XXXproperties就会失效,AutoConfiguration只会加载它自己已经定义好的一些默认值配置
总结
springboot启动会加载大量的配置类
看我们需要的功能在springboot默认写好的配置类中是否存在
配置类中存在哪些组件(只要我们要用的组件存在,我们就不需要手动配置)
给容器中自动配置类添加组件的时候,会从xxproperties类中获取某些属性,我们需要在配置文件中指定这些属性的值就好
springboot的web开发
导入静态资源
可以使用maven的方式导入前端的一些包(以webjar的方式)
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.4.1</version>
</dependency>
resource下的这些目录的内容都可以被直接访问
优先级: resource > static > public
制作首页
springboot有自己的一个首页的配置类,它指定了一个在resource目录下的index首页文件会被进入项目于时加载,如果我们没有指定他就会加载配置好的一个默认的首页。
我们在resource目录的static目录下新建index.html文件。启动项目即可直接加载该文件
在templates目录下的所有资源,只能够通过controller跳转或访问。类似于jsp开发中是WEB-INF目录。如果是需要跳转到templates目录下的页面,需要模板引擎的支持
图标定制
<link rel="icon" th:href="@{/public/favicon.ico}" type="image/x-icon"/>
模板引擎Thymeleaf
导入依赖
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-java8time</artifactId>
</dependency>
模板写在templates目录下
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--:th:test表示thymeleaf接管此标签,并且绑定标签中的某个属性-->
<div th:text="${msg}"></div>
</body>
</html>
html文件需要使用模板引擎要导入约束
<html lang="en" xmlns::th="http://www.thymeleaf.org">
thymeleaf语法
<!--utext表示接收的值不会被转义,也就是不会被全部识别成字符串-->
<div th:text="${msg}"></div>
<!--正常模式下的text绑定会被识别成字符串的形式例如<h1>标签也会被页面当成普通字符处理-->
<div th:text="${msg}"></div>
<!--遍历后端传递过来的list挨个取出里面的值-->
<div th:each="user:${userList}" th:text="${user}"></div>
<!--行内写法,通过两个中括号中间加入${}来取值-->
<div th:each="user:${userList}">[[${user}]]</div>
装配扩展springMVC
自定义配置,首先新建目录config,再新建配置类MyMvcConfig
package com.zhong.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
//这个类用来扩展mvc
@Configuration
//@EnableWebMvc 如果需要全面自定义mvc配置,不想用自动配置的需要加上这个注解
public class MyMvcConfig implements WebMvcConfigurer {
/*重写视图跳转*/
@Override
public void addViewControllers(ViewControllerRegistry registry) {
/*注册一个视图控制器,请求/zhong路径的请求将会返回一个名字为test的视图*/
/*这可能是controller注解的mvc底层实现*/
registry.addViewController("/zhong").setViewName("tset");
}
}
根目录下的请求都推荐使用这种sprimvc的视图跳转方式配置
package com.zhong.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
//这个类用来扩展mvc
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
}
}
thymeleaf的路径写法使用@{/} 这里/代表项目根路径,下面的静态资源目录不用写,直接使用它们内部的目录,静态资源都使用thymeleaf接管
配置项目根路径
server:
servlet:
context-path: /zhong
国际化
在资源目录下新建目录i18n(国际化英文单词的缩写)。新建两个文件login.properties,login_zh__CN.properties,新建完成后ide会自动合并两个文件,将它们放在同一个目录下。后面需要加入其他的语言包可以直接在这个ide生成的目录右键add
在idea主界面下方有个选项Resource Bundle可以进行可视化配置,配置好我们的语言包之后,需要在springboot配置文件中配置
# 配置文件的真实位置
spring:
messages:
basename: i18n.login
然后在html需要使用到语言包的位置上使用#{}取值
扩展本地解析器来做语言包之间的切换,我们可以通过请求中的lang属性来指定返回的语言。在@{}路径中使用()来传递参数
<a class="btn btn-sm" th:href="@{/index(l=zh_CH)}">中文</a>
<a class="btn btn-sm" th:href="@{/index(l=en_US)}">English</a>
在html切换语言按钮中请求连接在config下新建MyLocaleResolver
package com.zhong.config;
import org.springframework.web.servlet.LocaleResolver;
import org.thymeleaf.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;
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 request, HttpServletResponse response, Locale locale) {
}
}
把它配置到springmvc中让spring接管
@Bean
public LocaleResolver localeResolver() {
return new MyLocaleResolver();
}
项目实战
登陆功能实现
LoginController
package com.zhong.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.thymeleaf.util.StringUtils;
@Controller
public class LoginController {
@RequestMapping("/user/login")
public String login(@RequestParam("username") String username, @RequestParam("password") String password, Model model) {
//登陆业务
if (!StringUtils.isEmpty(username) && "123456".equals(password)) {
return "redirect:/main";
}else {
model.addAttribute("loginError", "用户名或密码错误");
return "index";
}
}
}
MyMvcConfig
package com.zhong.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
//这个类用来扩展mvc
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
registry.addViewController("/main.html").setViewName("dashboard");
registry.addViewController("/main").setViewName("dashboard");
}
@Bean
public LocaleResolver localeResolver() {
return new MyLocaleResolver();
}
}
拦截器
新建类实现拦截器接口LoginHandlerInterceptor
package com.zhong.config;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
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) {
// System.out.println("======================================================2121212");
request.setAttribute("loginError", "请先登录");
request.getRequestDispatcher("/index").forward(request, response);
return false;
}else {
return true;
}
}
}
LoginController
package com.zhong.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.thymeleaf.util.StringUtils;
import javax.servlet.http.HttpSession;
@Controller
public class LoginController {
@RequestMapping("/user/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";
}else {
model.addAttribute("loginError", "用户名或密码错误");
return "index";
}
}
}
MyMvcConfig
package com.zhong.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
//这个类用来扩展mvc
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
registry.addViewController("/main.html").setViewName("dashboard");
registry.addViewController("/main").setViewName("dashboard");
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
/*配置拦截器*/
registry.addInterceptor(new LoginHandlerInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/index.html","/index","/","/user/login","/asserts/css/*","/asserts/js/**","/asserts/img/**");
}
}
thymeleaf抽取公共页面
使用th:fragment="名字"
可以抽取出这部分的代码作为一个类似插件的部分
在需要插入的地方写上
<!--th:insert的意思是将抽取出来的部分插入到div里面作为子元素-->
<div th:insert="~{dashboard::topbar}"></div>
需要使用~{抽取出插件的页面名字::插件名字},同时这种写法也可使用()传递参数,()传递参数就相当于以前的jsp?传递参数
在动态设置导航栏激活状态的时候,可以使用表达式加上三元运算符来判断激活状态
<!-- 接收参数并且判断-->
<a th:class="${active=='main.html'?'nav-link active':'nav-link'}" th:href="@{/main}"></a>
<!--传递参数-->
<div th:insert="~{commons/commons::sidebar(active='list.html')}"></div>
页面操作
<thead>
<tr>
<th>id</th>
<th>lastName</th>
<th>email</th>
<th>sex</th>
<th>department</th>
<th>birth</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr th:each="emp:${emps}">
<td th:text="${emp.getId()}"></td>
<td>[[ ${emp.getLastName()} ]]</td>
<td th:text="${emp.getEmail()}"></td>
<td th:text="${emp.getSex()==0?'女':'男'}"></td>
<td th:text="${emp.getDepartment().getDepartmentName()}"></td>
<td th:text="${#dates.format(emp.getBirth(),'yyyy-MM-dd HH:mm:ss')}"></td>
<td>
<button class="btn btn-sm btn-primary">编辑</button>
<button class="btn btn-sm btn-danger">删除</button>
</td>
</tr>
</tbody>
404页面定制
springboot帮助我们配置好的存放错误页面的目录和导向错误页面的路由。我们只需要在templates目录下新建error目录,在该目录下新建名字为404或者500的页面即可
spring Data整合JDBC
导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
配置数据源,这里可能需要设置时区,但是在my.ini里配置过就不需要设置
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&&characterEncoding=utf-8&&useSSL=false
driver-class-name: com.mysql.jdbc.Driver
原始的就是在需要使用JDBC是类中自动装配DataSource就可以使用,但是一般我们不这样用,我们会用springboot帮我们写好的bean
@Autowired
DataSource dataSource;
@Test
void contextLoads() throws SQLException {
//默认数据源
System.out.println(dataSource.getClass());
Connection connection = dataSource.getConnection();
System.out.println(connection);
connection.close();
}
这里介绍一种springboot写好的bean模板叫做 XXXtemplate,例如springboot写好的jdbc的bean就叫做jdbc template,使用它可以直接连接数据库。并且这个被springboot封装过的jdbc做好了事务,它会帮我们自动提交事务。
package com.zhong.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
@RestController
public class JDBCController {
@Autowired
private JdbcTemplate jdbcTemplate;
@GetMapping("/userList")
public List<Map<String, Object>> userList() {
String sql = "select * from mybatis.user";
List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
return maps;
}
@GetMapping("/addUser")
public String addUser() {
String sql = "insert into mybatis.user(id, name, pwd) values(5, 'xxx', '123456')";
int update = jdbcTemplate.update(sql);
return "addUserok";
}
@GetMapping("/updateUser/{id}")
public String updateUser(@PathVariable int id) {
String sql = "update mybatis.user set name = ?,pwd = ? where id = ?;";
Object[] objects = new Object[3];
objects[0] = "aaa";
objects[1] = "123";
objects[2] = "5";
int update = jdbcTemplate.update(sql, objects);
return "updateUserok";
}
@GetMapping("/deleteUser/{id}")
public String deleteUser(@PathVariable int id) {
String sql = "delete from mybatis.user where id = ?";
int update = jdbcTemplate.update(sql, id);
return "deleteUserok";
}
}
整合Druid数据源
导入依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.14</version>
</dependency>
通过type指定springboot的数据源
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&&characterEncoding=utf-8&&useSSL=false
driver-class-name: com.mysql.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
#durid数据源专有配置
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: select 1 from dual
testWileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
#配置监控统计的拦截的filter,stat:监控统计、log4j:日志记录、wall:防御sql注入
#如果运行时报错 类找不到异常:log4j
#就导入log4j依赖
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
userGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
新建目录config,新建类DruidConfig
package com.zhong.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
import java.util.HashMap;
//标注这个类是一个配置类
@Configuration
public class DruidConfig {
//将yml文件中配置的属性绑定进来
@ConfigurationProperties(prefix = "spring.datasource")
@Bean //将这个new出来的对象托管给spring
public DataSource druidDataSource() {
return new DruidDataSource();
}
//后台监控
//ServletRegistrationBean意思是帮助我们把一些druid的插件注册成bean
@Bean //需要注册为bean
public ServletRegistrationBean statViewServlet() {
//new一个bean的注册,参数传递一些插件,同时指定路径,进入该路径的时候会进入该插件的图形界面
ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
//监控后台需要登陆
HashMap<String, String> initParameters = new HashMap<>();
//增加配置
initParameters.put("loginUsername","admin");//loginUsername固定写法
initParameters.put("loginPassword","123456");//固定写法
//允许访问
initParameters.put("allow", "");//如果该属性为空,则代表所有人可以访问
//禁止访问
// initParameters.put("kuangsheng", "这里写ip地址");//只要这样配置之后,这个ip地址就会被禁止访问监控后台
bean.setInitParameters(initParameters);//设置初始化参数
return bean;
}
//注册filter
@Bean
public FilterRegistrationBean webStatFilter() {
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new WebStatFilter());
HashMap<String, String> initParameters = new HashMap<>();
//对请求这些资源或者路劲的请求不统计
initParameters.put("exclusions", "*.js,*.css,/druid/*");
bean.setInitParameters(initParameters);
return bean;
}
}
整合mybatis框架
导入依赖
<dependencies>
<!--非官方写的启动器会以框架名字开头-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<!--官方启动器则会以spring-boot开头-->
<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>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
配置数据源
spring:
datasource:
url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&&characterEncoding=utf-8&&useSSL=false
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
# springboot整合mybatis就是接管了mybatis的核心配置文件
mybatis:
# 我们需要在这里配置好mybatis的别名映射
type-aliases-package: com.zhong.pojo
# 配置接口实现xml文件的位置
mapper-locations: classpath:mybatis/mapper/*.xml
编写实体类
package com.zhong.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
@Data
@AllArgsConstructor
@ToString
@NoArgsConstructor
public class User {
private int id;
private String name;
private String pwd;
}
编写Mapper接口
package com.zhong.mapper;
import com.zhong.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.stereotype.Repository;
import java.util.List;
@Mapper//表示这个类是一个mybaits的一个mapper
//@MapperScan("com.zhong.mapper")或者在主启动类上添加mapper包扫描
@Repository//在spring中表示这是一个dao层对象
public interface UserMapper {
List<User> queryUserList();
User queryUserById(int id);
int addUser(User user);
int updateUser(User user);
int deleteUserById(int id);
}
在resource下编写mapper的实现
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zhong.mapper.UserMapper">
<select id="queryUserList" resultType="user">
select * from user;
</select>
<select id="queryUserById" resultType="user" parameterType="_int">
select * from user where id = #{id};
</select>
<insert id="addUser" parameterType="user">
insert into user(id,name,pwd) values(#{id},#{name},#{pwd});
</insert>
<update id="updateUser" parameterType="user">
update user set name = #{name}, pwd = #{pwd} where id = #{id};
</update>
<delete id="deleteUserById" parameterType="_int">
delete from user where id = #{id};
</delete>
</mapper>
编写controller测试一下
package com.zhong.controller;
import com.zhong.mapper.UserMapper;
import com.zhong.pojo.User;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.List;
@RestController
public class UserController {
@Resource
private UserMapper userMapper;
@GetMapping("/queryUserList")
public String queryUserList() {
List<User> userList = userMapper.queryUserList();
return userList.toString();
}
}
SpringSecurity(安全)
这是一个权限认证框架,如果不使用它,我们用过滤器拦截器一样能够完成权限认证的功能。
导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
编写配置类
方式一,web安全适配器(已启用)
package com.zhong.config;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@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");
//配置如果访问没有权限,默认跳转到登录页
http.formLogin();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//从内存拿到数据来进行验证
//这里需要配置一个密码加密方式,否则会跑不起来
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
//配置认证的用户,用户角色可以配置多个
.withUser("zhong").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3")
//可以通过and配置多个认证用户
.and()
.withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3")
.and()
.withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1");
}
}
方式二
新方式不需要继承WebSecurityConfigurerAdapter,而是注入一个过滤链的Bean,通过这个过滤链去处理用户登录的请求
package com.zhong.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authz) -> authz
.antMatchers("/").permitAll()
.antMatchers("/level1/**").hasRole("vip1")
.antMatchers("/level2/**").hasRole("vip2")
.antMatchers("/level3/**").hasRole("vip3")
)
.formLogin();
return http.build();
}
@Bean
public InMemoryUserDetailsManager userDetailsService() {
PasswordEncoder encoder = new BCryptPasswordEncoder();
UserDetails user = User.withDefaultPasswordEncoder()
.username("root")
.password("123456")
.roles("vip1")
.build();
//该设置密码加密的方式已经弃用,只用来写代码示例
UserDetails user2 = User.withDefaultPasswordEncoder()
.username("zhong")
.password("123456")
.roles("vip1")
.build();
return new InMemoryUserDetailsManager(user,user2);
}
}
注销功能实现
//开启注销功能,并清除cookie,清除session.但实际上一般不会这么干
// http.logout().deleteCookies("remove").invalidateHttpSession(true);
//开启注销功能,并让它注销成功之后跳转到我们指定的路径
http.logout().logoutSuccessUrl("/");
权限显示控制
导入依赖
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
<version>3.0.4.RELEASE</version>
</dependency>
导入命名空间
在html文件中需要导入命名空间,方便使用代码提示。不导入程序一样能够跑起来。
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
动态显示内容
<!--登录注销-->
<div class="right menu">
<!--未登录-->
<div sec:authorize="!isAuthenticated()">
<a class="item" th:href="@{/toLogin}">
<i class="address card icon"></i> 登录
</a>
</div>
<div sec:authorize="isAuthenticated()">
<a class="item">
<!--获取用户名-->
用户名: <span sec:authentication="name"></span>
<!--获取用户角色-->
角色: <span sec:authentication="authorities"></span>
</a>
</div>
<!--已登陆,显示用户名,注销-->
<div sec:authorize="isAuthenticated()">
<!--注销-->
<a class="item" th:href="@{/logout}">
<i class="sign-out icon"></i> 注销
</a>
</div>
<!--动态菜单-->
<div class="column" sec:authorize="hasRole('vip1')">
跨域配置
//关闭默认开启的网站防御攻击,以此支持跨域请求
http.csrf().disable();
记住我和首页定制
开启记住我功能会保存用户的登陆状态,通过向用户电脑存放一个cookie来实现保存登陆状态,cookie有效期为两周
http.rememberMe();
定制登录页
//定制登录页,并且指定处理登陆请求的控制器。这里表单提交必须要是post
.formLogin().loginPage("/toLogin").loginProcessingUrl("/login");
定制登录页的前提是,前端传递的参数必须是username和password,如果它们的name是其他的名称,框架识别不到就会登陆失败。当然,这种参数名字也可以定制,通过以下两种方法为参数指定一个新的接收前端参数的名字。
.formLogin().loginPage("/toLogin").usernameParameter("user").passwordParameter("pwd").loginProcessingUrl("/login");
同样的,记住我功能也可以定制参数
http.rememberMe().rememberMeParameter("remember");
Shiro
导入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springboot-08-shiro</artifactId>
<groupId>com.zhong</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>hello-shiro</artifactId>
<dependencies>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.10.0</version>
</dependency>
<!-- configure logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>2.0.3</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.8.0-beta4</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.8.0-beta4</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>
</project>
日志配置
log4j.rootLogger=INFo,stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p 【%c 】 - ‰m %n
#General Apache libraries
log4j.logger.org.apache=WARN
#Spring
log4j.logger.org.springframework=WARN
#Default Shiro Logging
log4j.logger.org.apache.shiro=INFO
#Disable verbose Logging
log4j.logger.org.apache.shiro.util.ThreadContext=WARN
log4j.logger.org.apache.shiro.cache.ehcache.EhCache=WARN
shiro配置
[users]
# user 'root' with password 'secret' and the 'admin' role
root = secret, admin
# user 'guest' with the password 'guest' and the 'guest' role
guest = guest, guest
# user 'presidentskroob' with password '12345' ("That's the same combination on
# my luggage!!!" ;)), and role 'president'
presidentskroob = 12345, president
# user 'darkhelmet' with password 'ludicrousspeed' and roles 'darklord' and 'schwartz'
darkhelmet = ludicrousspeed, darklord, schwartz
# user 'lonestarr' with password 'vespa' and roles 'goodguy' and 'schwartz'
lonestarr = vespa, goodguy, schwartz
# -----------------------------------------------------------------------------
# Roles with assigned permissions
#
# Each line conforms to the format defined in the
# org.apache.shiro.realm.text.TextConfigurationRealm#setRoleDefinitions JavaDoc
# -----------------------------------------------------------------------------
[roles]
# 'admin' role has all permissions, indicated by the wildcard '*'
admin = *
# The 'schwartz' role can do anything (*) with any lightsaber:
schwartz = lightsaber:*
# The 'goodguy' role is allowed to 'drive' (action) the winnebago (type) with
# license plate 'eagle5' (instance specific id)
goodguy = winnebago:drive:eagle5
快速启动
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.text.IniRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Simple Quickstart application showing how to use Shiro's API.
*
* @since 0.9 RC2
*/
public class Quickstart {
private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);
public static void main(String[] args) {
// The easiest way to create a Shiro SecurityManager with configured
// realms, users, roles and permissions is to use the simple INI config.
// We'll do that by using a factory that can ingest a .ini file and
// return a SecurityManager instance:
// Use the shiro.ini file at the root of the classpath
// (file: and url: prefixes load from files and urls respectively):
DefaultSecurityManager defaultSecurityManager=new DefaultSecurityManager();
IniRealm iniRealm=new IniRealm("classpath:shiro.ini");
defaultSecurityManager.setRealm(iniRealm);
// for this simple example quickstart, make the SecurityManager
// accessible as a JVM singleton. Most applications wouldn't do this
// and instead rely on their container configuration or web.xml for
// webapps. That is outside the scope of this simple quickstart, so
// we'll just do the bare minimum so you can continue to get a feel
// for things.
SecurityUtils.setSecurityManager(defaultSecurityManager);
// Now that a simple Shiro environment is set up, let's see what you can do:
// get the currently executing user:
Subject currentUser = SecurityUtils.getSubject();
// Do some stuff with a Session (no need for a web or EJB container!!!)
Session session = currentUser.getSession();
session.setAttribute("someKey", "aValue");
String value = (String) session.getAttribute("someKey");
if (value.equals("aValue")) {
log.info("Retrieved the correct value! [" + value + "]");
}
// let's login the current user so we can check against roles and permissions:
if (!currentUser.isAuthenticated()) {
UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
token.setRememberMe(true);
try {
currentUser.login(token);
} catch (UnknownAccountException uae) {
log.info("There is no user with username of " + token.getPrincipal());
} catch (IncorrectCredentialsException ice) {
log.info("Password for account " + token.getPrincipal() + " was incorrect!");
} catch (LockedAccountException lae) {
log.info("The account for username " + token.getPrincipal() + " is locked. " +
"Please contact your administrator to unlock it.");
}
// ... catch more exceptions here (maybe custom ones specific to your application?
catch (AuthenticationException ae) {
//unexpected condition? error?
}
}
//say who they are:
//print their identifying principal (in this case, a username):
log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");
//test a role:
if (currentUser.hasRole("schwartz")) {
log.info("May the Schwartz be with you!");
} else {
log.info("Hello, mere mortal.");
}
//test a typed permission (not instance-level)
if (currentUser.isPermitted("lightsaber:wield")) {
log.info("You may use a lightsaber ring. Use it wisely.");
} else {
log.info("Sorry, lightsaber rings are for schwartz masters only.");
}
//a (very powerful) Instance Level permission:
if (currentUser.isPermitted("winnebago:drive:eagle5")) {
log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'. " +
"Here are the keys - have fun!");
} else {
log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
}
//all done - log out!
currentUser.logout();
System.exit(0);
}
}
springboot集成shiro
导入依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.1</version>
</dependency>
编写配置类
package com.zhong.config;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
public class UserRealm extends AuthorizingRealm {
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了授权");
return null;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行了认证");
return null;
}
}
package com.zhong.config;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ShiroConfig {
//shiroFilterFactoryBean
@Bean
public ShiroFilterFactoryBean getShiroFilterFactorBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//设置安全管理
bean.setSecurityManager(defaultWebSecurityManager);
return bean;
}
//defaultWebSecurityManager
@Bean(name = "securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//管理userRealm
securityManager.setRealm(userRealm);
return securityManager;
}
//创建realm对象
@Bean
public UserRealm userRealm() {
return new UserRealm();
}
}
shiro实现登陆认证
@Bean
public ShiroFilterFactoryBean getShiroFilterFactorBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//设置安全管理
bean.setSecurityManager(defaultWebSecurityManager);
//添加shiro的内置过滤器实现登陆认证
/*
anno: 不用认证直接访问
authc: 认证之后才能访问
user: 开启记住我功能才能访问
perms: 拥有某个资源的权限才能访问
role: 拥有某个角色权限才能访问
*/
LinkedHashMap<String, String> filterMap = new LinkedHashMap<>();
//设置登陆拦截
filterMap.put("/user/add", "authc");
filterMap.put("/user/update", "authc");
bean.setFilterChainDefinitionMap(filterMap);
//设置登陆请求
bean.setLoginUrl("/toLogin");
return bean;
}
shiro实现用户认证
userRealm
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行了认证");
String username = "root";
String password = "123456";
UsernamePasswordToken userToken = (UsernamePasswordToken) authenticationToken;
if (!userToken.getUsername().equals(username)) {
return null;//返回空,shiro会帮助我们抛出一个不明账户异常(UnknownAccountException),我们就可以在调用类中捕获来处理这种情况
}
//shiro不需要我们手动认证密码。它的实现类会自动帮我们验证密码。我们只需要新建一个实现类,将用户密码传递进去即可
return new SimpleAuthenticationInfo("",password,"");
}
控制器
@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);//执行登陆方法,shrio帮助我们认证用户
//shiro会在这里帮助我们调用realm的认证方法。从而使用到userrealm类
return "index";
} catch (UnknownAccountException e) {//异常捕获,我们用来处理一些错误情况,给前端返回一些错误信息
//用户不存在
model.addAttribute("msg", "用户名错误");
return "login";
} catch (IncorrectCredentialsException e) {
//密码错误
model.addAttribute("msg", "密码错误");
return "login";
}
}
shiro整合mybatis
导入依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.14</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
配置数据源
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://localhost:3306/myabtis?useUnicode=true&&characterEncoding=utf-8&&useSSL=false
username: root
password: 123456
#durid数据源专有配置
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: select 1 from dual
testWileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
#配置监控统计的拦截的filter,stat:监控统计、log4j:日志记录、wall:防御sql注入
#如果运行时报错 类找不到异常:log4j
#就导入log4j依赖
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
userGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
#mybatis配置
mybatis:
type-aliases-package: com.zhong.pojo
mapper-locations: classpath:mybatis/mapper/*.xml
userRealm
package com.zhong.config;
import com.zhong.pojo.User;
import com.zhong.service.UserServiceImpl;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import javax.annotation.Resource;
public class UserRealm extends AuthorizingRealm {
//整合mybatis,连接到数据库
@Resource
UserServiceImpl userService;
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了授权");
return null;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行了认证");
UsernamePasswordToken userToken = (UsernamePasswordToken) authenticationToken;
//连接数据库
User user = userService.queryUserByName(userToken.getUsername());
if (user == null) {
return null;//返回空,shiro会帮助我们抛出一个不明账户异常(UnknownAccountException),我们就可以在调用类中捕获来处理这种情况
}
//shiro不需要我们手动认证密码。它的实现类会自动帮我们验证密码。我们只需要新建一个实现类,将数据库的用户密码丢给它,让他去验证用户输入的密码即可
return new SimpleAuthenticationInfo("",user.getPwd(),"");
}
}
shrio请求授权实现
我们需要在shiro的配置文件中,配置授权信息
package com.zhong.config;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
@Configuration
public class ShiroConfig {
//shiroFilterFactoryBean
@Bean
public ShiroFilterFactoryBean getShiroFilterFactorBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//设置安全管理
bean.setSecurityManager(defaultWebSecurityManager);
//添加shiro的内置过滤器实现登陆认证
/*
anno: 不用认证直接访问
authc: 认证之后才能访问
user: 开启记住我功能才能访问
perms: 拥有某个资源的权限才能访问
role: 拥有某个角色权限才能访问
*/
//授权组,用来存放资源被授权给哪些权限
LinkedHashMap<String, String> filterMap = new LinkedHashMap<>();
//将该路劲下的资源文件授权给拥有user:add权限的用户。其他用户访问会报401
filterMap.put("/user/add","perms[user:add]");
filterMap.put("/user/update","perms[user:update]");
//设置user路径下的所有请求需要先认证才能放行
filterMap.put("/user/*", "authc");
//将权限组放进shiro责任链实例对象bean
bean.setFilterChainDefinitionMap(filterMap);
//设置登陆请求
bean.setLoginUrl("/toLogin");
//设置未授权请求
bean.setUnauthorizedUrl("/noauth");
return bean;
}
}
控制器配置未授权请求跳转
@RequestMapping("/noauth")
@ResponseBody
public String unauthorized() {
return "未经授权,无法访问";
}
配置认证
package com.zhong.config;
import com.zhong.pojo.User;
import com.zhong.service.UserServiceImpl;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import javax.annotation.Resource;
public class UserRealm extends AuthorizingRealm {
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了授权");
//设置授权
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//设置授权名称
// info.addStringPermission("user:add");
//拿到当前登陆的对象
Subject subject = SecurityUtils.getSubject();
User currentUser = (User) subject.getPrincipal();//取出认证方法中存入的用户
//设置当前用户权限
info.addStringPermission(currentUser.getPerms());
return info;
}
}
用户注销
filterMap.put("/logout", "logout");
//shiro注销实现在配置文件的内置过滤器,控制器只需要做跳转即可
@RequestMapping("/logout")
public String logout() {
return "redirect:/toLogin";
}
shrio整合thymeleaf
动态菜单
导入依赖
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.1.0</version>
</dependency>
配置shrio配置文件,创建一个整合实例对象托管给spring
//整合shrioDialect:用来整合thymeleaf
@Bean
public ShiroDialect getShiroDialect() {
return new ShiroDialect();
}
命名空间导入
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"
通过判断认证的身份来动态显示子元素
<!--shiro:guest=""验证是是否为未登录用户,是就显示子元素-->
<a th:href="@{/toLogin}" shiro:guest="">登陆</a>
<!--hasAnyPermissions拥有指定的任何一个权限即可显示子元素-->
<div shiro:hasAnyPermissions="'user:add','user:update'"></div>
<!--指定拥有特定权限的用户才会显示子元素-->
<div shiro:hasPermission="'user:add'">
<a th:href="@{/user/add}">add</a> <br>
</div>
<div shiro:hasPermission="'user:uodate'">
<a th:href="@{/user/update}">update</a>
</div>
Swagger
- Restful Api 文档在线自动生成工具,Api文档和Api定义同步更新
- 直接运行,可以在线测试
在项目中使用swagger需要springfox
- swagger2
- swaggerUI
在springboot中集成swagger
在springboot2.7以上的版本可能会和swagger存在不兼容的问题,原因是springboot改变了swagger需要的一个源码的路径,为了兼容swagger3我们需要做一些配置。
-
新建一个项目,依赖中含有web启动器
-
导入swagger依赖
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>3.0.0</version> </dependency> <!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>3.0.0</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-boot-starter</artifactId> <version>3.0.0</version> </dependency>
-
配置application.yml
spring: mvc: pathmatch: matching-strategy: ant_path_matcher
-
在启动类上加上注解@EnableOpenApi
package com.zhong.swagger; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import springfox.documentation.oas.annotations.EnableOpenApi; @SpringBootApplication @EnableOpenApi public class SwaggerDemoApplication { public static void main(String[] args) { SpringApplication.run(SwaggerDemoApplication.class, args); } }
-
创建swagger的配置类config/SwaggerConfig.java
package com.zhong.swagger.config; import org.springframework.context.annotation.Configuration; import springfox.documentation.swagger2.annotations.EnableSwagger2; @Configuration //让springboot帮助我们把swagger加载到配置中 @EnableSwagger2 //开启swagger配置 public class SwaggerConfig { }
-
创建测试的类
package com.zhong.swagger.controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class HelloController { @RequestMapping("/hello") public String hello() { return "hello"; } }
-
运行项目并访问http://localhost:8080/swagger-ui/index.html,成功会显示如下内容
swagger配置
public class SwaggerConfig {
//配置swagger的docket的bean实例
@Bean
public Docket docket() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo());
}
//配置swagger文档基本信息
private ApiInfo apiInfo() {
//作者信息
Contact contact = new Contact("钟", "", "2132121@qq.com");
return new ApiInfo(
"swagger文档",
"这里填写文档信息",
"v1.0",
"urn:tos",
contact,
"Apache 2.0",
"http://www.apache.org/licenses/LICENSE-2.0",
new ArrayList());
}
}
swagger配置扫描接口
//配置swagger的docket的bean实例
@Bean
public Docket docket() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
//配置接口扫描
.select()
//RequestHandlerSelectors配置接口扫描的方式
//.basePackage()基于包名扫描
//.any()扫描全部
//.none()全部不扫描
//.withClassAnnotation()扫描类上的注解,需要一个注解的反射对象
//.withMethodAnnotation()扫描方法上的注解,需要一个注解的反射对象
.apis(RequestHandlerSelectors.withMethodAnnotation())
//过滤器,可以让swagger不扫描哪些路径
//PathSelectors.ant()参数传递com包下的路径
.paths(PathSelectors.ant())
//建造者模式
.build();
}
配置是否启动swagge
//配置swagger的docket的bean实例
@Bean
public Docket docket() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
//配置swagger是否开启
.enable(false)
.select()
.apis(RequestHandlerSelectors.basePackage("com.zhong.swagger.controller"))
.build();
}
根据配置文件配置的环境决定是否开启swagger
#激活环境
spring:
profiles:
active: pro
mvc:
pathmatch:
matching-strategy: ant_path_matcher
#多文档模式,使用 --- 分隔开
---
# 在2.4之后的版本中推荐使用on-profile命名环境
spring:
config:
activate:
on-profile: dev
---
spring:
config:
activate:
on-profile: pro
public class SwaggerConfig {
@Bean
public Docket docket(Environment environment) {
//配置需要开启swagger的环境
Profiles profiles = Profiles.of("dev","test");
//接收一个环境名字判断是否处于该环境中
boolean flag = environment.acceptsProfiles(profiles);
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.enable(flag)
}
}
配置API文档分组
.groupName("zhong")
配置多个文档分组也就是创建多个Docket类并且注册到bean,并且使用不同的groupname
swagger中的model
只要被swagger扫描的接口中存在有接口的返回值是一个实体类,那么这个实体类就会被swagger扫描并且配置到swagger文档中,实体类中必须存在属性的get方法,该属性才会被swagger扫描到文档中。可以用过注解@ApiModel和@ApiModelProperty来给文档中的model层加上一些注释
@ApiModel("用户实体类")
public class User {
@ApiModelProperty("用户名")
private String username;
@ApiModelProperty("密码")
private String password;
}
@ApiOperation()给接口加上swagger注释,@ApiParam()给接口中的参数加上注释
任务
异步任务
在需要开启多线程处理的业务中加上注解@Async表示这是一个异步任务
@Service
public class AsyncService {
//告诉spring这是一个异步任务
@Async
public void hello() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("数据处理完成");
}
}
在启动类上需要加上@EnableAsync,之后在控制器中正常调用就好,spring会帮我们自动开启多线程处理
@EnableAsync//开启异步注解扫描
@SpringBootApplication
public class Springboot09TestApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot09TestApplication.class, args);
}
}
定时任务
在启动类上加上注解开启定时任务@EnableScheduling
//cron是指在linux上是时间服务器
//这里使用cron表达式来定时,秒 分 时 日 月 星期
//定时任务本身就是一个异步任务,到了指定的时间自动执行
@Scheduled(cron = "40 46 19 * * ?")//在周一到周天中,任何时间内的第0秒执行这个程序
public void hello() {
System.out.println("hello被执行");
}
邮件发送
springboot中配置邮件任务需要一个邮件启动器的依赖,不过根据springboot的版本不同可能会存在mail启动器没有这个版本下的依赖,这个时候需要手动设置mail启动器的版本
springboot版本为2.7.9
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
<version>2.7.8</version>
</dependency>
简单邮件发送
配置
spring:
mail:
username: 99268317@qq.com
password: oqfmuurtzr
host: smtp.qq.com
# qq邮箱需要配置SSL加密
properties.mail.smtl.ssl.enable=: true
邮件发送
@Resource
private JavaMailSender javaMailSender;
@Test
void contextLoads() {
// 简单邮件发送
SimpleMailMessage simpleMailMessage = new SimpleMailMessage();//封装一个邮件对象
simpleMailMessage.setSubject("springboot邮件发送测试");//设置邮件标题
simpleMailMessage.setText("刘先生,收到请回复收到请回复,over");//设置邮件正文
simpleMailMessage.setTo("1426717@qq.com");//设置收件人
simpleMailMessage.setFrom("992616@qq.com");//设置发件人
javaMailSender.send(simpleMailMessage);//调用发送
}
复杂邮件发送
@Test
void contextLoads2() throws MessagingException {
//复杂邮件发送
MimeMessage mimeMessage = javaMailSender.createMimeMessage();//创建一个复杂邮件对象
//对复杂邮件对象进行封装
MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true);
//必须设置一个邮件标题和正文
mimeMessageHelper.setSubject("springboot附件邮件测试");
mimeMessageHelper.setText("<p style='color:red'>带有附件的复杂邮件类型</p>", true);
//附件
mimeMessageHelper.addAttachment("1.jpg", new File("D:\\sourceCode\\JAVA\\springboot-09-test\\target\\classes\\static\\1.jpg"));
//封装源和目标
mimeMessageHelper.setTo("1420917@qq.com");
mimeMessageHelper.setFrom("992616@qq.com");
javaMailSender.send(mimeMessage);//调用发送
}