1简介
1.1、什么是spirngboot?
springboot在spring的基础之上,搭建起来的框架,能够帮助我们整合市面上最流行的框架,帮助我们快速搭建起来项目。
springboot不是新的技术,而是新的框架,是基于spring来搭建起来的。
特性:约定大于配置!
1.2、为什么使用springboot
- 开发效率快,内置有配置好的版本依赖。
- 基于spring。
- 轻松上手
2 第一个SpringBoot程序
我的开发环境:
- java 8
- Maven-3.6.1
- SpringBoot 2.6.11
创建springboot有两种方式:
- 在https://start.spring.io/上创建后,下载完成,通过IDEA打开即可。
- 在IDEA中直接创建。
3 原理初探
自动装配 :
pom.xml
- spring-boot-dependencies :核心依赖在父工程中!
- 引入一些springboot依赖的时候,不需要指定版本,因为有这些版本
启动器
-
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency>
-
启动器:springboot的启动场景
-
比如 spring-boot-starter-web ,它就会自动帮我们自动导入web环境所有的依赖
-
springboot会将所有功能场景,变成一个个的启动器
-
我们需要使用什么功能,就需要找到对应的启动器就可以了
主程序
package com.lei;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
//@SpringBootApplication :标注这个类是一个springboot的应用
@SpringBootApplication
public class Springboot01HellowordApplication {
public static void main(String[] args) {
//将spring boot应用启动
SpringApplication.run(Springboot01HellowordApplication.class, args);
}
}
注解
@SpringBootConfiguration :spring boot的配置
@Configuration :spring配置类
@Component:这是一个spring的组件
@EnableAutoConfiguration :自动配置
@AutoConfigurationPackage :自动配置包
@Import({Registrar.class}) :自动配置包注册·
@Import({AutoConfigurationImportSelector.class}) :导入选择器
获取候选的配置
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = new ArrayList(SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()));
ImportCandidates.load(AutoConfiguration.class, this.getBeanClassLoader()).forEach(configurations::add);
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you are using a custom packaging, make sure that file is correct.");
return configurations;
}
META-INF/spring.factories :自动配置的核心配置
结论:spring boot 所有自动配置都是在启动的时候扫描并加载:spring.factories所有的自动配置类都在这里面,但不一定生效,要进行判断条件是否成立,只要导入对应的start ,就有对应的启动器,我们自动装配就会生效,然后配置成功!
1.spring boot 在启动的时候,从类路径下META-INF/spring.factories获取指定EnableAutoConfiguration的值;
2.将这些自动配置的类导入容器,自动配置就会生效,进行自动配置
3.以前需要配置的东西,现在spring boot帮我们做了!
4.整合javaEE 整体解决方案和自动配置都在springboot-autoconfigure的jar包中;
5.它会把需要导入的组件,以类名的方式返回,这些组件就会被添加导容器
6.它会给容器中导入非常多的自动配置类 (xxxAutoConfiguration), 就是给容器中导入这个场景需要的所有组件 , 并配置好这些组件 ;并自动配置,@Configuration
7.有了自动配置类 , 免去了我们手动编写配置注入功能组件等的工作;
4 Spring Boot 配置
springboot支持两种格式的配置文件,.properties
,.yml
如果在同一位置有同时具有.properties和.yml格式的配置文件,.properties优先。
如果存在多个相同配置,会有优先级。
4.1、配置文件区别
.properties
,.yml
区别在于语法结构不同。
.properties
结构 :key=value
server.port=8081
.yml
结构 :key: value
server: port: 8081
4.2、实体类获取配置信息
加载单个配置
// @Value 加载单个配置@Value("${student.name}")
加载多个配置
案例:创建学生对象,用于默认就把配置信息加载进去。
1、在springboot项目中的resources目录下新建一个文件 application.yml
student:
name: Zhang San
birthdate: 1990/09/01
interests: [eat, sleep]
2、添加实体类
//注册bean到容器中@Component
// 开头为student配置
@ConfigurationProperties(prefix = "student")
@Data
public class Student {
private String name;
private Date birthdate;
private List<String> interests;
}
idea提示没有找到springboot配置注解处理器。
需要添加springboot配置注解处理器,方便在Test测试。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
3、测试类中测试。
@SpringBootTestclass DemoApplicationTests {
@Autowired
Student student;
@Test
void contextLoads() {
System.out.println(student);
}
}
结果:
4.3、加载指定的配置文件
日常开发中,配置文件可能存在多个。
1、我们去在resources目录下新建一个student.properties
文件,yaml
不生效。
student.name=Wang moustudent.birthdate=1995/09/01student.interests=[sleep,dream]
2、修改配置类
//注册bean到容器中
@Component
// 开头为student配置
@ConfigurationProperties(prefix = "student")
// 资源路径
@PropertySource(value = "classpath:student.properties")
@Data
public class Student {
private String name;
private Date birthdate;
private List<String> interests;
}
3、测试查看效果
4.4、yaml语法总结
- 语法要求严格
- 空格不能省略
- 以缩进来控制层级关系
- 对大小写敏感
# 普通格式:k: v# 对象格式:k: v1: v2:
# 数组模式:k: - v - v - v
# 行内写法k: {k1:v1,k2:v2}k: {v,v,v}
4.5、配置文件优化级
springboot 启动会扫描以下位置的application.properties或者application.yml文件作为Spring boot的默认配置文件:
1.类路径
1.类路径
2.类路径/config包
2.当前目录
1.当前目录
2.当前目录中的/config子目录
3.子目录的/config直接子目录
4.6、多环境切换
profile是Spring对不同环境提供不同配置功能的支持,可以通过激活不同的环境版本,实现快速切换环境;
我们在主配置文件编写的时候,文件名可以是 application-{profile}.properties/yml
, 用来指定多个环境版本;
例如:
application-test.yml:代表测试环境配置
application-dev.yml:代表开发环境配置
但是Springboot并不会直接启动这些配置文件,它默认使用application.properties主配置文件,如果没有就会找application.yml
。
我们需要通过一个配置来选择需要激活的环境:
spring: profiles: active: dev #使用开发环境。
4.7、自动配置原理
SpringBoot官方文档中有大量的配置。我们无法全部记住。
我们来简单分析一下。
5.1、分析自动配置原理
这块内容,建议大家去看视频。
我们以HttpEncodingAutoConfiguration(Http编码自动配置)为例。
解释自动配置原理;
@Configuration //表示这是一个配置类,和以前编写的配置文件一样,也可以给容器中添加组件;//启动指定类的ConfigurationProperties功能; //进入这个HttpProperties查看,将配置文件中对应的值和HttpProperties绑定起来; //并把HttpProperties加入到ioc容器中@EnableConfigurationProperties({HttpProperties.class}) //Spring底层@Conditional注解 //根据不同的条件判断,如果满足指定的条件,整个配置类里面的配置就会生效; //这里的意思就是判断当前应用是否是web应用,如果是,当前配置类生效@ConditionalOnWebApplication(type = Type.SERVLET)//判断当前项目有没有这个类CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器;@ConditionalOnClass({CharacterEncodingFilter.class})//判断配置文件中是否存在某个配置:spring.http.encoding.enabled; //如果不存在,判断也是成立的 //即使我们配置文件中不配置spring.http.encoding.enabled=true,也是默认生效的;@ConditionalOnProperty( prefix = "spring.http.encoding", value = {"enabled"}, matchIfMissing = true)public class HttpEncodingAutoConfiguration { //他已经和SpringBoot的配置文件映射了 private final Encoding properties; //只有一个有参构造器的情况下,参数的值就会从容器中拿 public HttpEncodingAutoConfiguration(HttpProperties properties) { this.properties = properties.getEncoding(); } //给容器中添加一个组件,这个组件的某些值需要从properties中获取 @Bean @ConditionalOnMissingBean //判断容器没有这个组件? public CharacterEncodingFilter characterEncodingFilter() { CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter(); filter.setEncoding(this.properties.getCharset().name()); filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.REQUEST)); filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.RESPONSE)); return filter; } //。。。。。。。}
一句话总结 :根据当前不同的条件判断,决定这个配置类是否生效!
如果没有把对应的依赖引用进来,这个配置类也会不生效。
通过不同的条件来进行判断是否要启动配置。
- 一但这个配置类生效;这个配置类就会给容器中添加各种组件;
- 这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的;
- 这样就可以形成我们的配置文件可以动态的修改springboot的内容。
- 所有在配置文件中能配置的属性都是在xxxxProperties类中封装着;
- 配置文件能配置什么就可以参照某个功能对应的这个属性类
对比小结
@Value这个使用起来并不友好!我们需要为每个属性单独注解赋值,比较麻烦;我们来看个功能对比图
1、@ConfigurationProperties只需要写一次即可 , @Value则需要每个字段都添加
2、松散绑定:这个什么意思呢? 比如我的yml中写的last-name,这个和lastName是一样的, - 后面跟着的字母默认是大写的。这就是松散绑定。可以测试一下
3、JSR303数据校验 , 这个就是我们可以在字段是增加一层过滤器验证 , 可以保证数据的合法性
4、复杂类型封装,yml中可以封装对象 , 使用value就不支持
结论:
配置yml和配置properties都可以获取到值 , 强烈推荐 yml;
如果我们在某个业务中,只需要获取配置文件中的某个值,可以使用一下 @value;
如果说,我们专门编写了一个JavaBean来和配置文件进行一一映射,就直接@configurationProperties,不要犹豫!
自动装配 精髓
1、SpringBoot启动会加载大量的自动配置类
2、我们看我们需要的功能有没有在SpringBoot默认写好的自动配置类当中;
3、我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件存在在其中,我们就不需要再手动配置了)
4、给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们只需要在配置文件中指定这些属性的值即可;
xxxxAutoConfigurartion:自动配置类;给容器中添加组件
xxxxProperties:封装配置文件中相关属性;
5 Spring Boot Web 开发
静态资源
要解决的问题:
- 导入静态资源
- 首页
- jsp,模板引擎Thymeleaf
- 装配SpringMvc
- 增删改查
- 拦截器
- 国际化
1 在springboot,我们可以使用以下的方式处理静态资源
- webjars http://localhost:8080/webjars/
- public , static , /** , resource http://localhost:8080/
2 优先级:resource> static(默认)>public
首页如何定制
-
静态资源路径下 index.html
-
可以配置静态资源路径。
-
不能与静态资源前缀共用。
网页小图标可以自定义 把图片名字改成favicon.ico放到静态资源路径下
模板引擎
thymeleaf
结论:只有需要使用thymeleaf,只需要导入对应的依赖就可以了!我们将html放到我们的templates目录下即可
6 员工管理系统
项目结构
pojo 实体类
Department 部门表
package com.lei.pojo;
/**
* @author lei
* @verson:1.8
*/
//部门表
public class Department {
private Integer id;
private String department;
public Department() {
}
public Department(Integer id, String department) {
this.id = id;
this.department = department;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getDepartment() {
return department;
}
public void setDepartment(String department) {
this.department = department;
}
}
Employee 员工表
package com.lei.pojo;
import java.util.Date;
/**
* @author lei
* @verson:1.8
*/
public class Employee {
private Integer id;
private String lastName;
private String email;
private Integer gender;
private Department department;
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();
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Integer getGender() {
return gender;
}
public void setGender(Integer gender) {
this.gender = gender;
}
public Department getDepartment() {
return department;
}
public void setDepartment(Department department) {
this.department = department;
}
public Date getBirth() {
return birth;
}
public void setBirth(Date birth) {
this.birth = birth;
}
}
dao包(模拟数据库)
DepartmentDao
package com.lei.dao;
import com.lei.pojo.Department;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.springframework.stereotype.Repository;
/**
* @author lei
* @verson:1.8
*/
//部门dao
@Repository
public class DepartmentDao {
//模拟数据库
private static Map<Integer, Department> departments =null;
static {
departments=new HashMap<>();//创建一个部门表
departments.put(101,new Department(101,"教学部"));
departments.put(102,new Department(102,"市场部"));
departments.put(103,new Department(103,"教研部"));
departments.put(104,new Department(104,"运营部"));
departments.put(105,new Department(105,"后勤部"));
}
//获得所有部门信息
public Collection<Department> getDepartment(){
return departments.values();
}
//通过id得到部门
public Department getDepartment(Integer id){
return departments.get(id);
}
}
EmployeeDao
package com.lei.dao;
import com.lei.pojo.Department;
import com.lei.pojo.Employee;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
/**
* @author lei
* @verson:1.8
*/
//员工dao
@Repository
public class EmployeeDao {
private static Map<Integer, Employee> employees =null;
@Autowired
private DepartmentDao departmentDao;
static {
employees=new HashMap<>();//创建一个部门表
employees.put(1001,new Employee(1001,"AA","[email protected]",1,new Department(101,"教学部")));
employees.put(1002,new Employee(1002,"bb","[email protected]",0,new Department(101,"市场部")));
employees.put(1003,new Employee(1003,"cc","[email protected]",1,new Department(101,"教研部")));
employees.put(1004,new Employee(1004,"dd","[email protected]",0,new Department(101,"运营部")));
employees.put(1005,new Employee(1005,"ee","[email protected]",1,new Department(101,"后勤部")));
}
//主键自增
private static Integer intId = 1006;
//增加一个员工
public void save(Employee employee){
if (employee.getId()==null){
employee.setId(intId++);
}
employee.setDepartment(departmentDao.getDepartment(employee.getDepartment().getId()));
employees.put(employee.getId(),employee);
}
//查询所有员工信息
public Collection<Employee> getAll(){
return employees.values();
}
//通过id查询员工
public Employee getEmployeeByqid(Integer id){
return employees.get(id);
}
//通过id删除员工
public void delete(Integer id){
employees.remove(id);
}
}
1.首页配置:
1.注意点,所有页面的静态资源都需要使用thmeleaf来接管;
2.url: @{}
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>Signin Template for Bootstrap</title>
<link rel="canonical" href="https://getbootstrap.com/docs/4.0/examples/sign-in/">
<!-- Bootstrap core CSS -->
<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
<!-- Custom styles for this template -->
<link th:href="@{/css/signin.css}" rel="stylesheet">
</head>
<body class="text-center">
<form class="form-signin" th:action="@{/user/login}">
<img class="mb-4" src="https://getbootstrap.com/docs/4.0/assets/brand/bootstrap-solid.svg" alt="" width="72" height="72">
<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign in</h1>
<!--/*@thymesVar id="msg" type="ch"*/-->
<!-- 如果msg的值为空,则不显示消息-->
<p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>
<input type="text" name="username" class="form-control" th:placeholder="#{login.user}" required autofocus>
<input type="password" name="password" class="form-control" th:placeholder="#{login.password}" required>
<div class="checkbox mb-3">
<label>
<input type="checkbox" value="remember-me" th:text="#{login.remember}">
</label>
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{login.btn}">Sign in</button>
<p class="mt-5 mb-3 text-muted">© 2017-2018</p>
<a class="btn btn-sm" th:href="@{/index.html(l='zh_CN')}">中文</a>
<a class="btn btn-sm" th:href="@{/index.html(l='en_US')}">English</a>
</form>
</body>
</html>
2.页面国际化:
1.我们需要配置i18n文件
login.password=密码
login.remember=记住我
login.tip=请登录
login.email=电子邮件
login.btn=登录
2.我们如果需要在项目中进行自动切换,我们需要自定义一个组件LocaleResolver
package com.lei.config;
import java.util.Locale;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.LocaleResolver;
import org.thymeleaf.util.StringUtils;
/**
* @author lei
* @verson:1.8
*/
public class MyLocaleResolver implements LocaleResolver {
//解析请求
@Override
public Locale resolveLocale(HttpServletRequest request) {
//获取请求中的语言参数
String language = request.getParameter("l");
Locale locale = Locale.getDefault();//如果没有就使用默认的
//如果请求的链接携带了国际化的参数
if (!StringUtils.isEmpty(language)){
//zh_CN
String[] split = language.split("_");
//国家,地区
return new Locale(split[0], split[1]);
}
return locale;
}
@Override
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
}
}
3.记得将自己写的组件配置到Spring容器 @Bean
package com.lei.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;
/**
* @author lei
* @verson:1.8
*/
@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 localResolver(){
return new MyLocaleResolver();
}
}
3 登录实现
LoginController类编写
package com.lei.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* @author lei
* @verson:1.8
*/
@Controller
//跳转首页 登录验证
public class LoginController {
@RequestMapping("/user/login")
public String login(@RequestParam("username")String name, @RequestParam("password")String password, Model model){
if (StringUtils.hasText(name)&&"123456".equals(password)){
return "redirect:/main.html";
}else {
model.addAttribute("msg","用户名或密码错误");
return "index";
}
}
}
视图解析器
package com.lei.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.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @author lei
* @verson:1.8
*/
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
registry.addViewController("/main.html").setViewName("dashboard");
}
//自定义国际化组件
@Bean
public LocaleResolver localResolver(){
return new MyLocaleResolver();
}
//后面添加的
//添加拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginHandlerInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/index.html","/","/user/login","/css/**","/js/**","img/**","/emp/**");
}
}
登录表单
form class="form-signin" th:action="@{/user/login}">
<img class="mb-4" src="https://getbootstrap.com/docs/4.0/assets/brand/bootstrap-solid.svg" alt="" width="72" height="72">
<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign in</h1>
<!--/*@thymesVar id="msg" type="ch"*/-->
<!-- 如果msg的值为空,则不显示消息-->
<p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>
<input type="text" name="username" class="form-control" th:placeholder="#{login.user}" required autofocus>
<input type="password" name="password" class="form-control" th:placeholder="#{login.password}" required>
<div class="checkbox mb-3">
<label>
<input type="checkbox" value="remember-me" th:text="#{login.remember}">
</label>
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{login.btn}">Sign in</button>
<p class="mt-5 mb-3 text-muted">© 2017-2018</p>
<a class="btn btn-sm" th:href="@{/index.html(l='zh_CN')}">中文</a>
<a class="btn btn-sm" th:href="@{/index.html(l='en_US')}">English</a>
</form>
4 登录拦截器
编写LoginHandlerInterceptor 拦截类
public class LoginHandlerInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//登录成功之后 应该有session用户
Object user = request.getSession().getAttribute("loginUser");
if (user==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","/","/user/login","/css/**","/js/**","img/**");
}
5 员工展示
1提取公共页面
th:fragment="topbar"
<div th:insert="~{commons/commons::topbar}"></div>
如果要获取后端的值
<div th:insert="~{commons/commons::sidebar(active='main.html')}"></div>
2获取用户数据
查询所有的员工
@Controller
public class EmployeeController {
@Autowired
EmployeeDao employeeDao;
//查询所有的员工
@RequestMapping("/emps")
public String list(Model model){
Collection<Employee> employees = employeeDao.getAll();
model.addAttribute("emps",employees);
return "emp/list";
}
}
前端获取 遍历
<table class="table table-striped table-sm">
<thead>
<tr>
<th>id</th>
<th>lastName</th>
<th>email</th>
<th>gender</th>
<th>department</th>
<th>birth</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<!--/*@thymesVar id="emps" type="com"*/-->
<tr th:each="emp:${emps}">
<!--/*@thymesVar id="getId" type=""*/-->
<td th:text="${emp.getId()}"></td>
<td th:text="${emp.getLastName()}"></td>
<td th:text="${emp.getEmail()}"></td>
<td th:text="${emp.getGender()==0?'女':'男'}"></td>
<td th:text="${emp.department.getDepartment()}"></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>
</table>
6添加员工
1 按钮提交
<h2><a class="btn btn-sm btn-success" th:href="@{/emp}">添加员工</a> </h2>
2跳转到添加页面
//跳转到添加页面
@GetMapping("/emp")
public String toAddpage(Model model){
//查出所有部门信息
Collection<Department> department = departmentDao.getDepartment();
model.addAttribute("department",department);
return "emp/add";
}
3添加页
<!DOCTYPE html>
<!-- saved from url=(0052)http://getbootstrap.com/docs/4.0/examples/dashboard/ -->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>Dashboard Template for Bootstrap</title>
<!-- Bootstrap core CSS -->
<link href="/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom styles for this template -->
<link href="/css/dashboard.css" rel="stylesheet">
<style type="text/css">
/* Chart.js */
@-webkit-keyframes chartjs-render-animation {
from {
opacity: 0.99
}
to {
opacity: 1
}
}
@keyframes chartjs-render-animation {
from {
opacity: 0.99
}
to {
opacity: 1
}
}
.chartjs-render-monitor {
-webkit-animation: chartjs-render-animation 0.001s;
animation: chartjs-render-animation 0.001s;
}
</style>
</head>
<body>
<div th:insert="~{commons/commons::topbar}"></div>
<div class="container-fluid">
<div class="row">
<div th:insert="~{commons/commons::sidebar(active='list.html')}"></div>
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<form th:action="@{/emp}" method="post">
<div class="form-group">
<label>LastName</label>
<input type="text"name="lastName" class="form-control" placeholder="海绵宝宝">
</div>
<div class="form-group">
<label>Email</label>
<input type="email" name="email" class="form-control" placeholder="[email protected]">
</div>
<div class="form-group">
<label>Gender</label><br>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="gender" value="1">
<label class="form-check-label">男</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="gender" value="0">
<label class="form-check-label">女</label>
</div>
</div>
<div class="form-group">
<label>department</label>
<select class="form-control" name="department.id">
<option th:each="dept:${department}" th:text="${dept.getDepartment()}" th:value="${dept.getId()}"></option>
</select>
</div>
<div class="form-group">
<label>Birth</label>
<input type="text" name="birth" class="form-control" placeholder="">
<button class="btn btn-primary">添加</button>
</div>
</form>
</main>
</div>
</div>
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script type="text/javascript" src="/js/jquery-3.2.1.slim.min.js"></script>
<script type="text/javascript" src="/js/popper.min.js"></script>
<script type="text/javascript" src="/js/bootstrap.min.js"></script>
<!-- Icons -->
<script type="text/javascript" src="/js/feather.min.js"></script>
<script>
feather.replace()
</script>
<!-- Graphs -->
<script type="text/javascript" src="/js/Chart.min.js"></script>
<script>
var ctx = document.getElementById("myChart");
var myChart = new Chart(ctx, {
type: 'line',
data: {
labels: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
datasets: [{
data: [15339, 21345, 18483, 24003, 23489, 24092, 12034],
lineTension: 0,
backgroundColor: 'transparent',
borderColor: '#007bff',
borderWidth: 4,
pointBackgroundColor: '#007bff'
}]
},
options: {
scales: {
yAxes: [{
ticks: {
beginAtZero: false
}
}]
},
legend: {
display: false,
}
}
});
</script>
</body>
</html>
4 添加成功 返回首页
//添加请求
@PostMapping("/emp")
public String Addpemp(Employee employee){
System.out.println(employee);
employeeDao.save(employee);
return "redirect:/emps";
}
7 修改员工
提交按钮
<a class="btn btn-sm btn-primary" th:href="@{/emp/}+${emp.getId()}">编辑</a>
跳转到添加页面
//到员工修改页面
public String toUpdateEmp(@PathVariable("id") Integer id,Model model){
//查询原来的书籍
Employee employeeByqid = employeeDao.getEmployeeByqid(id);
model.addAttribute("emp",employeeByqid);
//查出所有部门信息
Collection<Department> department = departmentDao.getDepartment();
model.addAttribute("department",department);
return "emp/update";
}
修改页
<!DOCTYPE html>
<!-- saved from url=(0052)http://getbootstrap.com/docs/4.0/examples/dashboard/ -->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>Dashboard Template for Bootstrap</title>
<!-- Bootstrap core CSS -->
<link href="/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom styles for this template -->
<link href="/css/dashboard.css" rel="stylesheet">
<style type="text/css">
/* Chart.js */
@-webkit-keyframes chartjs-render-animation {
from {
opacity: 0.99
}
to {
opacity: 1
}
}
@keyframes chartjs-render-animation {
from {
opacity: 0.99
}
to {
opacity: 1
}
}
.chartjs-render-monitor {
-webkit-animation: chartjs-render-animation 0.001s;
animation: chartjs-render-animation 0.001s;
}
</style>
</head>
<body>
<div th:insert="~{commons/commons::topbar}"></div>
<div class="container-fluid">
<div class="row">
<div th:insert="~{commons/commons::sidebar(active='list.html')}"></div>
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<form th:action="@{/updateEmp}" method="post">
<input type="hidden" name="id" th:value="${emp.getId()}">
<div class="form-group">
<label>LastName</label>
<input th:value="${emp.getLastName()}" type="text"name="lastName" class="form-control" placeholder="海绵宝宝">
</div>
<div class="form-group">
<label>Email</label>
<input th:value="${emp.getEmail()}"type="email" name="email" class="form-control" placeholder="[email protected]">
</div>
<div class="form-group">
<label>Gender</label><br>
<div class="form-check form-check-inline">
<input th:checked="${emp.getGender()==1}" class="form-check-input" type="radio" name="gender" value="1">
<label class="form-check-label">男</label>
</div>
<div class="form-check form-check-inline">
<input th:checked="${emp.getGender()==0}" class="form-check-input" type="radio" name="gender" value="0">
<label class="form-check-label">女</label>
</div>
</div>
<div class="form-group">
<label>department</label>
<select class="form-control" name="department.id">
<option th:selected="${dept.getId()==emp.getDepartment().getId()}" th:each="dept:${department}" th:text="${dept.getDepartment()}" th:value="${dept.getId()}"></option>
</select>
</div>
<div class="form-group">
<label>Birth</label>
<input th:value="${#dates.format(emp.getBirth(),'yyyy-MM-dd:HH:mm')}" type="text" name="birth" class="form-control" placeholder="">
<button class="btn btn-primary">修改</button>
</div>
</form>
</main>
</div>
</div>
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script type="text/javascript" src="/js/jquery-3.2.1.slim.min.js"></script>
<script type="text/javascript" src="/js/popper.min.js"></script>
<script type="text/javascript" src="/js/bootstrap.min.js"></script>
<!-- Icons -->
<script type="text/javascript" src="/js/feather.min.js"></script>
<script>
feather.replace()
</script>
<!-- Graphs -->
<script type="text/javascript" src="/js/Chart.min.js"></script>
<script>
var ctx = document.getElementById("myChart");
var myChart = new Chart(ctx, {
type: 'line',
data: {
labels: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
datasets: [{
data: [15339, 21345, 18483, 24003, 23489, 24092, 12034],
lineTension: 0,
backgroundColor: 'transparent',
borderColor: '#007bff',
borderWidth: 4,
pointBackgroundColor: '#007bff'
}]
},
options: {
scales: {
yAxes: [{
ticks: {
beginAtZero: false
}
}]
},
legend: {
display: false,
}
}
});
</script>
</body>
</html>
修改成功
@PostMapping("/updateEmp")
public String updateEmp(Employee employee){
employeeDao.save(employee);
return "redirect:/emps";
}
8 删除
删除链接
<a class="btn btn-sm btn-danger" th:href="@{/delemp/}+${emp.getId()}">删除</a>
删除collerter
//删除员工
@GetMapping("/delemp/{id}")
public String deleteEmp(@PathVariable("id") Integer id){
employeeDao.delete(id);
return "redirect:/emps";
}
9 注销
<a class="nav-link" th:href="@{/user/logout}">注销</a>
//删除员工
@GetMapping("/delemp/{id}")
public String deleteEmp(@PathVariable("id") Integer id){
employeeDao.delete(id);
return "redirect:/emps";
}
7 druid 数据源
yam配置
spring:
datasource:
username: root
password: "0414"
url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
#SpringBoot默认是不注入这些的,需要自己绑定
#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.Properity
#则导入log4j 依赖就行
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionoProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
druid 配置类
package com.lei.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import java.util.HashMap;
import javax.sql.DataSource;
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;
/**
* @author lei
* @verson:1.8
*/
@Configuration
public class DruidConfig {
@ConfigurationProperties(prefix = "spring.datasource")
@Bean
public DataSource druiddDataSource(){
return new DruidDataSource();
}
@Bean
//后台监控
public ServletRegistrationBean a(){
ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
//后台需要有人登录 登录密码
HashMap<String, String> initParameters = new HashMap<>();
//增加配置
initParameters.put("loginUsername","admin"); //登录key 是固定的 loginUsername loginPassword
initParameters.put("loginPassword","123456");
//允许谁访问
initParameters.put("allow","");
bean.setInitParameters(initParameters);//设置初始化参数
return bean;
}
}
8 Mybatis
配置文件
spring.datasource.username=root
spring.datasource.password=0414
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#整合mybatis
mybatis.type-aliases-package=com.lei.pojo
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml
创建实体类
public class User {
private int id ;
private String name;
private String pwd;
public User(int i, String aa, String s) {
}
}
mapper接口
package com.lei.mapper;
import com.lei.pojo.User;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
/**
* @author lei
* @verson:1.8
*/
//这个注解表示了这是一个mybatis 的mapper类:dao
@Mapper
@Repository
public interface UserMapper {
List<User> queryUserList();
User queryUserById(int id);
int addUser(User user);
int updateUser(User user);
int deleteUser(int id);
}
在resources中创建一个mybatis包 在mybatis包中建一个mapper包
在mapper包里UserMapper
-
resources
-
mybatis
-
mapper
-
UserMapper.xml
<?xml version="1.0" encoding="utf-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.lei.mapper.UserMapper"> <select id="queryUserList" resultType="User"> select *from mybatis.user u </select> <select id="queryUserById" resultType="User"> select *from mybatis.user u where id=#{id} </select> <insert id="addUser" parameterType="User"> insert into mybatis.user(id, name, pwd) VALUES (#{id},#{name},#{pwd}) </insert> <update id="updateUser" parameterType="User"> update mybatis.user set name=#{name},pwd=#{pwd} where id=#{id} </update> <delete id="deleteUser" parameterType="int"> delete mybatis.user u where id=#{id} </delete> </mapper>
-
-
-
9 SpringSecurity(安全)
在web开发中、安全第一位!过滤器 拦截器-
shiro,SpringSecurity:很像~除了类不一样,名字不一样
认证 ,授权(vip1,vip2,vip3)
功能权限
访问功能
菜单权限
拦截器,过滤器:大量的原生代码-
aop横切类
认识SpringSecurity
Spring Security 是针对Spring项目的安全框架,也是Spring Boot底层安全模块默认的技术选型,他可以实现强大的Web安全控制,对于安全控制,我们仅需要引入 spring-boot-starter-security 模块,进行少量的配置,即可实现强大的安全管理!
记住几个类:
- WebSecurityConfigurerAdapter:自定义Security策略
- AuthenticationManagerBuilder:自定义认证策略
- @EnableWebSecurity:开启WebSecurity模式
Spring Security的两个主要目标是 “认证” 和 “授权”(访问控制)。
“认证”(Authentication)
身份验证是关于验证您的凭据,如用户名/用户ID和密码,以验证您的身份。
身份验证通常通过用户名和密码完成,有时与身份验证因素结合使用。
“授权” (Authorization)
授权发生在系统成功验证您的身份后,最终会授予您访问资源(如信息,文件,数据库,资金,位置,几乎任何内容)的完全权限。
这个概念是通用的,而不是只在Spring Security 中存在。
认证和授权
目前,我们的测试环境,是谁都可以访问的,我们使用 Spring Security 增加上认证和授权的功能
1、引入 Spring Security 模块
编写基础配置类
package com.lei.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;
/**
* @author lei
* @verson:1.8
*/
//aop
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//链式编程
@Override
protected void configure(HttpSecurity http) throws Exception {
//首页所有人可以访问,功能页只有对应有权限的才能访问
//请求授权的规则
http.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/level1/**").hasAnyRole("vip1")
.antMatchers("/level2/**").hasAnyRole("vip2")
.antMatchers("/level3/**").hasAnyRole("vip3");
//没有权限去登录页
http.formLogin().loginPage("/toLogin").usernameParameter("user").passwordParameter("pwd").loginProcessingUrl("/login");
//注销
http.logout().logoutSuccessUrl("/");
//开启记住我功能
http.rememberMe();
}
//认证
//密码加密
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("lei").password( new BCryptPasswordEncoder().encode("123")).roles("vip2","vip3")
.and()
.withUser("root").password(new BCryptPasswordEncoder().encode("123")).roles("vip1");
}
}
、我们在前端,增加一个注销的按钮,index.html 导航栏中
<a class="item" th:href="@{/logout}">
<i class="address card icon"></i> 注销
</a>
3、我们可以去测试一下,登录成功后点击注销,发现注销完毕会跳转到登录页面!
4、但是,我们想让他注销成功后,依旧可以跳转到首页,该怎么处理呢?
// .logoutSuccessUrl("/"); 注销成功来到首页
http.logout().logoutSuccessUrl("/");
5、测试,注销完毕后,发现跳转到首页OK
6、我们现在又来一个需求:用户没有登录的时候,导航栏上只显示登录按钮,用户登录之后,导航栏可以显示登录的用户信息及注销按钮!还有就是,比如kuangshen这个用户,它只有 vip2,vip3功能,那么登录则只显示这两个功能,而vip1的功能菜单不显示!这个就是真实的网站情况了!该如何做呢?
我们需要结合thymeleaf中的一些功能
sec:authorize="isAuthenticated()":是否认证登录!来显示不同的页面
Maven依赖:
<!-- https://mvnrepository.com/artifact/org.thymeleaf.extras/thymeleaf-extras-springsecurity4 -->
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
<version>3.0.4.RELEASE</version>
</dependency>
7、修改我们的 前端页面
-
导入命名空间
-
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5"
-
修改导航栏,增加认证判断
-
<!--登录注销--> <div class="right menu"> <!--如果未登录--> <div sec:authorize="!isAuthenticated()"> <a class="item" th:href="@{/login}"> <i class="address card icon"></i> 登录 </a> </div> <!--如果已登录--> <div sec:authorize="isAuthenticated()"> <a class="item"> <i class="address card icon"></i> 用户名:<span sec:authentication="principal.username"></span> 角色:<span sec:authentication="principal.authorities"></span> </a> </div> <div sec:authorize="isAuthenticated()"> <a class="item" th:href="@{/logout}"> <i class="address card icon"></i> 注销 </a> </div> </div>
8、重启测试,我们可以登录试试看,登录成功后确实,显示了我们想要的页面;
9、如果注销404了,就是因为它默认防止csrf跨站请求伪造,因为会产生安全问题,我们可以将请求改为post表单提交,或者在spring security中关闭csrf功能;我们试试:在 配置中增加 http.csrf().disable();
http.csrf().disable();//关闭csrf功能:跨站请求伪造,默认只能通过post方式提交logout请求
http.logout().logoutSuccessUrl("/");
10、我们继续将下面的角色功能块认证完成!
<!-- sec:authorize="hasRole('vip1')" -->
<div class="column" sec:authorize="hasRole('vip1')">
<div class="ui raised segment">
<div class="ui">
<div class="content">
<h5 class="content">Level 1</h5>
<hr>
<div><a th:href="@{/level1/1}"><i class="bullhorn icon"></i> Level-1-1</a></div>
<div><a th:href="@{/level1/2}"><i class="bullhorn icon"></i> Level-1-2</a></div>
<div><a th:href="@{/level1/3}"><i class="bullhorn icon"></i> Level-1-3</a></div>
</div>
</div>
</div>
</div>
<div class="column" sec:authorize="hasRole('vip2')">
<div class="ui raised segment">
<div class="ui">
<div class="content">
<h5 class="content">Level 2</h5>
<hr>
<div><a th:href="@{/level2/1}"><i class="bullhorn icon"></i> Level-2-1</a></div>
<div><a th:href="@{/level2/2}"><i class="bullhorn icon"></i> Level-2-2</a></div>
<div><a th:href="@{/level2/3}"><i class="bullhorn icon"></i> Level-2-3</a></div>
</div>
</div>
</div>
</div>
<div class="column" sec:authorize="hasRole('vip3')">
<div class="ui raised segment">
<div class="ui">
<div class="content">
<h5 class="content">Level 3</h5>
<hr>
<div><a th:href="@{/level3/1}"><i class="bullhorn icon"></i> Level-3-1</a></div>
<div><a th:href="@{/level3/2}"><i class="bullhorn icon"></i> Level-3-2</a></div>
<div><a th:href="@{/level3/3}"><i class="bullhorn icon"></i> Level-3-3</a></div>
</div>
</div>
</div>
</div>
11、测试一下!
12、权限控制和注销搞定!
记住我
现在的情况,我们只要登录之后,关闭浏览器,再登录,就会让我们重新登录,但是很多网站的情况,就是有一个记住密码的功能,这个该如何实现呢?很简单
1、开启记住我功能
//定制请求的授权规则
@Override
protected void configure(HttpSecurity http) throws Exception {
//。。。。。。。。。。。
//记住我
http.rememberMe();
}
2、我们再次启动项目测试一下,发现登录页多了一个记住我功能,我们登录之后关闭 浏览器,然后重新打开浏览器访问,发现用户依旧存在!
思考:如何实现的呢?其实非常简单
我们可以查看浏览器的cookie
3、我们点击注销的时候,可以发现,spring security 帮我们自动删除了这个 cookie
4、结论:登录成功后,将cookie发送给浏览器保存,以后登录带上这个cookie,只要通过检查就可以免登录了。如果点击注销,则会删除这个cookie,具体的原理我们在JavaWeb阶段都讲过了,这里就不在多说了!
定制登录页
现在这个登录页面都是spring security 默认的,怎么样可以使用我们自己写的Login界面呢?
1、在刚才的登录页配置后面指定 loginpage
http.formLogin().loginPage("/toLogin");
2、然后前端也需要指向我们自己定义的 login请求
<a class="item" th:href="@{/toLogin}">
<i class="address card icon"></i> 登录
</a>
3、我们登录,需要将这些信息发送到哪里,我们也需要配置,login.html 配置提交请求及方式,方式必须为post:
在 loginPage()源码中的注释上有写明:
<form th:action="@{/login}" method="post">
<div class="field">
<label>Username</label>
<div class="ui left icon input">
<input type="text" placeholder="Username" name="username">
<i class="user icon"></i>
</div>
</div>
<div class="field">
<label>Password</label>
<div class="ui left icon input">
<input type="password" name="password">
<i class="lock icon"></i>
</div>
</div>
<input type="submit" class="ui blue submit button"/>
</form>
4、这个请求提交上来,我们还需要验证处理,怎么做呢?我们可以查看formLogin()方法的源码!我们配置接收登录的用户名和密码的参数!
http.formLogin()
.usernameParameter("user")
.passwordParameter("pwd")
.loginPage("/toLogin")
.loginProcessingUrl("/login"); // 登陆表单提交请求
5、在登录页增加记住我的多选框
<input type="checkbox" name="remember"> 记住我
6、后端验证处理!
//定制记住我的参数!
http.rememberMe().rememberMeParameter("remember");
10shiro
subject 用户
SecurityManager 管理所有用户
Realm 连接数据
<!-- 导入shiro包 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
<version>1.10.0</version>
</dependency>
配置shiro
ShiroConfig
package com.lei.config;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author lei
* @verson:1.8
*/
@Configuration
public class ShiroConfig {
//ShiroFilterFactoryBean
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager getdefaultWebSecurityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//设置安全管理器
shiroFilterFactoryBean.setSecurityManager(getdefaultWebSecurityManager);
return shiroFilterFactoryBean;
}
//DefaultWebSecurityManager
@Bean
public DefaultWebSecurityManager getdefaultWebSecurityManager(UserRealm userRealm){
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
//关联userRealm
defaultWebSecurityManager.setRealm(userRealm);
return defaultWebSecurityManager;
}
// 创建 ream 对象,需要自定义类
@Bean
public UserRealm userRealm(){
return new UserRealm();
}
}
UserRealm
package com.lei.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;
/**
* @author lei
* @verson:1.8
*/
// 自定义的 UserRealm
public class UserRealm extends AuthorizingRealm {
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
return null;
}
}
登录拦截 和授权
在ShiroConfig中编写
package com.lei.config;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import java.util.HashMap;
import java.util.LinkedHashMap;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author lei
* @verson:1.8
*/
@Configuration
public class ShiroConfig {
//ShiroFilterFactoryBean
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager getdefaultWebSecurityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//设置安全管理器
shiroFilterFactoryBean.setSecurityManager(getdefaultWebSecurityManager);
//添加shiro内置过滤器
/*
anon : 无需认证就可以访问
authc:必须认证才能访问
user:必须拥有 记住我的功能才能用
perms: 拥有对某个资源权限才能用
role: 拥有某个角色权限才能用
*/
//拦截
HashMap<String, String> filterMap = new LinkedHashMap<>();
//授权
filterMap.put("/user/add","perms[user:add]");
filterMap.put("/user/*","authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
//设置登录请求
shiroFilterFactoryBean.setLoginUrl("/toLogin");
//未授权页面
shiroFilterFactoryBean.setUnauthorizedUrl("/noauth");
return shiroFilterFactoryBean;
}
//DefaultWebSecurityManager
@Bean
public DefaultWebSecurityManager getdefaultWebSecurityManager(UserRealm userRealm){
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
//关联userRealm
defaultWebSecurityManager.setRealm(userRealm);
return defaultWebSecurityManager;
}
// 创建 ream 对象,需要自定义类
@Bean
public UserRealm userRealm(){
return new UserRealm();
}
//ShiroDialect:用来整合shiro thymeleaf
@Bean
public ShiroDialect shiroDialect(){
return new ShiroDialect();
}
}
实现用户认证和认证
在MyController 中 编辑请求
@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";
}
}
@RequestMapping("/noauth")
@ResponseBody
public String unexpected(){
return "未授权不能访问";
}
在 UserRealm认证和授权
package com.lei.config;
import javax.jws.soap.SOAPBinding.Use;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
/**
* @author lei
* @verson:1.8
*/
// 自定义的 UserRealm
public class UserRealm extends AuthorizingRealm {
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//用户名, 密码 数据库中取
String name ="root";
String password ="123";
UsernamePasswordToken userToken = (UsernamePasswordToken) authenticationToken;
if (!userToken.getUsername().equals(name)){
return null;//抛出异常
}
//密码认证,shiro做
return new SimpleAuthenticationInfo("",password,"");
}
}
整合数据库
1导入包
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.7-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.lei</groupId>
<artifactId>springboot-07-shiro</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-07-shiro</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- 数据库 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.11</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
<!--
subject 用户
SecurityManager 管理所有用户
Realm 连接数据-->
<!-- 导入shiro包 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
<version>1.10.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</pluginRepository>
</pluginRepositories>
</project>
配置durid数据源
application.yml
spring:
datasource:
username: root
password: "0414"
url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
#SpringBoot默认是不注入这些的,需要自己绑定
#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.Properity
#则导入log4j 依赖就行
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionoProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
配置mybatis
mybatis.type-aliases-package=com.lei.pojo
mybatis.mapper-locations=classpath:mapper/*.xml
shior认证和授权
package com.lei.config;
import com.lei.pojo.User;
import com.lei.service.UserService;
import javax.jws.soap.SOAPBinding.Use;
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.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
/**
* @author lei
* @verson:1.8
*/
// 自定义的 UserRealm
public class UserRealm extends AuthorizingRealm {
@Autowired
UserService userService;
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addStringPermission("user:add");
return info;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//用户名, 密码 数据库中取
UsernamePasswordToken userToken = (UsernamePasswordToken) authenticationToken;
//连接数据库
User user = userService.queryByname(userToken.getUsername());
if (user==null){//没有这些人
return null;//抛出异常
}
Subject subject = SecurityUtils.getSubject();
Session session = subject.getSession();
session.setAttribute("loginUser",user);
//密码认证,shiro做
return new SimpleAuthenticationInfo("",user.getPwd(),"");
}
}
整合thymeleaf
导包
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.1.0</version>
</dependency>
在ShiroConfig中写一个方法
//ShiroDialect:用来整合shiro thymeleaf
@Bean
public ShiroDialect shiroDialect(){
return new ShiroDialect();
}
前端页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml" xmlns:shiro="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<p th:text="${msg}"></p>
<hr>
<!--从session中判断值-->
<div th:if="${session.loginUser==null}">
<a th:href="@{/toLogin}">登录</a>
</div>
<div shiro:hasPermission="user:add">
<a th:href="@{/user/add}">add</a>
</div>
<div shiro:hasPermission="user:update">
<a th:href="@{/user/update}">update</a>
</div>
</body>
</html>
11 Swagger
- RestFul Api 文档在线自动生成工具=》Api文档与Api定义同步更新
- 直接运行,可以在线测试Api接口
- 支持多种语言
在项目中使用Swagger需要 springbox;
- swagger2
- swaggerui
Spring boot 集成Swagger
1.创建项目
2.导入相关依赖
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
3.编写一个hello工程
4.配置Swagger
/**
* @author lei
* @verson:1.8
*/
@Configuration
@EnableSwagger2 //开启swagger
public class SwaggerConfig {
}
swagger版本不兼容
在服务的application.properties配置中添加如下配置:
# 处理SpringBoot2.6.x与Swagger2 3.0.0版本冲突
spring.mvc.pathmatch.matching-strategy=ant_path_matcher
5.测试链接 http://localhost:8080/swagger-ui.html
配置Swagger
Swagger的bean的实例 Dochet
package com.lei.Config;
import java.util.ArrayList;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
/**
* @author lei
* @verson:1.8
*/
@Configuration
@EnableSwagger2 //开启swagger
public class SwaggerConfig {
//配置Swagger的Docket的bean实例
@Bean
public Docket docket(){
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo());
}
//配置swagger信息
public ApiInfo apiInfo(){
Contact contact = new Contact("磊", "", "[email protected]");
return new ApiInfo(
"磊的SwaggerApi文档",
"不要放弃",
"1.0",
"urn:tos",
contact,
"Apache 2.0",
"http://www.apache.org/licenses/LICENSE-2.0",
new ArrayList());
}
}
Swagger配置扫描接口和 配置是否启动swagger
Docket.select()
Docket.enable()
//配置Swagger的Docket的bean实例
@Bean
public Docket docket(){
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
//enable是否启动swagger,如果为false,则不能访问
.enable(false)
.select()
//RequestHandlerSelectors. 配置要扫描接口的方式
//.basePackage指定要扫描的包
//any()扫描全部
//none() 不扫描
//withClassAnnotation() 扫描类上的注解,参数是一个注解的反射对象
//withMethodAnnotation()扫描方法上的注解
.apis(RequestHandlerSelectors.basePackage("com.lei.controller"))
//过滤 过滤路径
//.paths(PathSelectors.ant("/lei/**"))
.build();
}
//配置swagger信息
public ApiInfo apiInfo(){
配置api文档分组
.groupName("磊")
配置多个组
public Docket docket1(){
return new Docket(DocumentationType.SWAGGER_2).groupName("SS");
}
实体类配置
package com.lei.pojo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
/**
* @author lei
* @verson:1.8
*/
@ApiModel("用户实体类")
public class User {
@ApiModelProperty("用户名")
public String username;
public String password;
}
package com.lei.controller;
import com.lei.pojo.User;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author lei
* @verson:1.8
*/
@RestController
public class HelloController {
@GetMapping ("/hello")
public String hello(){
return "hello";
}
//只要接口中有返回实体类
@ApiOperation("hello控制")
@PostMapping("/user")
public User USER(){
return new User();
}
}
可以在线测试
可以通过swagger给一些较难理解的属性或接口,增加注释信息
12 任务
异步任务
编写service
package com.lei.service;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
/**
* @author lei
* @verson:1.8
*/
@Service
public class AsyncService {
//告诉spring这是一个异步方法
@Async
public void hello(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("数据正在处理");
}
}
编写controller
package com.lei.controller;
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
import com.lei.service.AsyncService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author lei
* @verson:1.8
*/
@RestController
public class AsyncController {
@Autowired
AsyncService asyncService;
@RequestMapping("/hello")
public String hello(){
asyncService.hello();
return "ok";
}
}
在main方法开启异步注解
//开启异步注解
@EnableAsync
@SpringBootApplication
public class Springboot09TestApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot09TestApplication.class, args);
}
}
邮件
添加包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
配置
[email protected]
spring.mail.password=nwehbjceuakvdefb
spring.mail.host=smtp.qq.com
#开启加密认证‘
spring.mail.properties.mail.smtp.ssl.enable=true
测试
package com.lei;
import java.io.File;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.mail.javamail.MimeMessageHelper;
@SpringBootTest
class Springboot09TestApplicationTests {
@Autowired
JavaMailSenderImpl mailSender;
@Test
void contextLoads() {
//一个简单的邮件
SimpleMailMessage mailMessage = new SimpleMailMessage();
mailMessage.setSubject("磊你好啊");
mailMessage.setText("xxxxxxxx");
mailMessage.setTo("[email protected]");
mailMessage.setFrom("[email protected]");
mailSender.send(mailMessage);
}
@Test
void contextLoads1() throws MessagingException {
//一个复杂的邮件
MimeMessage mimeMessage = mailSender.createMimeMessage();
//组装
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage,true);
helper.setSubject("磊你好啊");
helper.setText("<p style='color:red'>谢谢自己吧<p>",true);
helper.setTo("[email protected]");
helper.setFrom("[email protected]");
//附件
helper.addAttachment("微信图片_20221107150707.jpg",new File("E:\\截图\\微信图片_20221107150707.jpg"));
mailSender.send(mimeMessage);
}
}
定时任务
TakScheduler 任务调度者
TaskExecutor 任务执行者
//开启定时功能的注解
@EnableScheduling
@Scheduled//什么时候执行
Cron表达式
在主方法main开启功能注解
//开启定时功能的注解
@EnableScheduling
编写
package com.lei.service;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
@Service
public class ScheduledService {
//在一个特定的时间执行这个方法
//cron 表达式
//秒 分 时 日 月 周几
@Scheduled(cron = "0 6 0 * * ?")
public void hello(){
System.out.println("hello,你被执行了");
}
}
13 分布式 Dubbo+Zookeeper+Springboot
zookeeper :注册中心
dubbo-admin:是一个监管管理后台-查看我们注册了那些服务,那些服务被销毁
dubbo:jar包
标签:SpringBoot,spring,springframework,org,import,lei,public From: https://www.cnblogs.com/lei0/p/17015986.html