SpringBoot学习笔记

SpringBoot学习笔记

1、Hello World

在idea中新建project,选择Spring Initializr,点击Next ,设置项目所需要的东西,设置Spring Web的依赖,建立一个空的spring boot工程

不需要任何配置,spring boot已经自动配置好了,只需要在controller层写接口,在浏览器访问路径就可以得到想要的结果。

controller层

@RestController
public class HelloWorld {

  @RequestMapping("hello")
  public String hello() {
    return "hello world";
  }
}

2、yaml

SpringBoot使用一个全局的配置文件 , 配置文件名称是固定的 。可以使用application.properties 或者application.yaml

  • application.properties

    • 语法结构 :key=value
  • application.yml

    • 语法结构 :key:空格 value,一定要把空格加上,不然不会生效!

    配置文件的作用 : 修改SpringBoot自动配置的默认值

用实例了解一下用法:

Dog实体类

@Component
public class Dog {

  private String name;
  private Integer age;
  //有参、无参、get/set、toString省略
}

Person实体类

@Component
@ConfigurationProperties(prefix = "person")
public class Person {
  private String name;
  private Integer age;
  private Boolean answer;
  private Date date;
  private List<Object> list;
  private Map<String,Object> map;
  private Dog dog;
}

其中,@ConfigurationProperties(prefix = "person")的prefix必须加上

@ConfigurationProperties 作用:将配置文件中配置的每一个属性的值,映射到这个组件中;告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定参数 prefix = “person” : 将配置文件中的person下面的所有属性一一对应。如果将配置文件的key 值和属性的值设置为不一样,则结果输出为null。

@PropertySource: 加载指定的配置文件;

@configurationProperties :默认从全局配置文件中获取值;

application.yaml

person:
  name: ljh
  age: 18
  answer: true
  date: 2021/3/4
  list:
    - 听歌
    - 敲代码
    - 打游戏
  map: {key1: value1,key2: value2}
  dog:
    name: xm
    age: 3

yaml一定要注意空格和层级关系,一列在同一直线的是同一级

上面写出了对象,String,Integer,Boolean,Date,List,Map的写法。

person和dog是对象,name是String类型,age是Integer类型,answer是Boolean类型,list是List类型,map则是Map格式!

3、JSR303数据校验

Springboot中可以用@validated来校验数据

@Component //注册bean
@ConfigurationProperties(prefix = "person")
@Validated  //数据校验
public class Person {

    @Email(message="邮箱格式错误") //name必须是邮箱格式
    private String name;
}
常见参数:
@NotNull(message="名字不能为空")
private String userName;
@Max(value=120,message="年龄最大不能查过120")
private int age;
@Email(message="邮箱格式错误")
private String email;

空检查
@Null       验证对象是否为null
@NotNull    验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank   检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty   检查约束元素是否为NULL或者是EMPTY.
    
Booelan检查
@AssertTrue     验证 Boolean 对象是否为 true  
@AssertFalse    验证 Boolean 对象是否为 false  
    
长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内  
@Length(min=, max=) string is between min and max included.

日期检查
@Past       验证 Date 和 Calendar 对象是否在当前时间之前  
@Future     验证 Date 和 Calendar 对象是否在当前时间之后  
@Pattern    验证 String 对象是否符合正则表达式的规则

4、thymeleaf

4.1、导入依赖

想要在templates里面的index.html直接起作用,需要导入thymeleaf依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

thymeleaf与SpringBoot完美整合,SpringBoot提供了Thymeleaf的默认配置,并且为Thymeleaf设置了视图解析器,我们可以像以前操作jsp一样来操作Thymeleaf

4.2、基本用法

在使用Thymeleaf时页面要引入命名空间: xmlns:th="http://www.thymeleaf.org"

1、th属性,常用th属性如下:

1)th:text:文本替换;

2)th:utext:支持html的文本替换。

3)th:value:属性赋值

4)th:each:遍历循环元素

5)th:if:判断条件,类似的还有th:unless,th:switch,th:case

6)th:insert:代码块引入,类似的还有th:replace,th:include,常用于公共代码块提取的场景

7)th:fragment:定义代码块,方便被th:insert和th:replace(html名称::fragment后面的值)引用,可以用(urlxx=‘xxx’)传入参数。 th:insert= “~{templatename::fragmentname}”,用波浪线。

8)th:object:声明变量,一般和*{}一起配合使用,达到偷懒的效果。

9)th:attr:设置标签属性,多个属性可以用逗号分隔

2、标准表达式语法:

${...} 变量表达式,Variable Expressions

@{...} 链接表达式,Link URL Expressions,适用于url及链接

#{...} 消息表达式,Message Expressions,适用于国际化取值

~{...} 代码块表达式,Fragment Expressions

*{...} 选择变量表达式,Selection Variable Expressions

<link th:href="@{/webjars/bootstrap/4.0.0/css/bootstrap.css}" rel="stylesheet">
<link th:href="@{/main/css/styleSheet.css}" rel="stylesheet">
<form class="form-login" th:action="@{/user/login}" th:method="post" >
<a class="btn btn-sm" th:href="@{/login.html(l='zh_CN')}">中文</a>
<a class="btn btn-sm" th:href="@{/login.html(l='en_US')}">English</a>

开发期间模板引擎页面修改以后,要实时生效。可以关闭模板引擎的缓存(application.yaml)

spring:
  thymeleaf:
    cache: false

5、员工管理系统

5.1、前期准备

导入相关依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>

    <!--thymeleaf-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-configuration-processor</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

暂时不用数据库,不涉及Mybatis,直接模拟数据

在pojo中新建员工表和部门表

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Department {
  private Integer id;
  private String departmentName;
}
@Data
public class Employee {
  private Integer id;
  private String lastName;
  private String email;
  private Integer gender;//0表示女,1表示男
  private Department department;
  @DateTimeFormat(pattern = "yyyy-MM-dd")
  private Date birth;

  public Employee() {
  }

  public Employee(Integer id, String lastName, String email, Integer gender, Department department) {
    this.id = id;
    this.lastName = lastName;
    this.email = email;
    this.gender = gender;
    this.department = department;
    this.birth = new Date();//用当前时间表示生日
  }
}

编写dao层

部门dao

@Repository
public class DepartmentDao {
  private static Map<Integer, Department> departments = null;

  static {
    departments = new HashMap<Integer, Department>();
    departments.put(1, new Department(1, "后勤部"));
    departments.put(2, new Department(2, "管理部"));
    departments.put(3, new Department(3, "财政部"));
    departments.put(4, new Department(4, "经理部"));
  }

  //获得所有部门信息
  public Collection<Department> getDepartments() {
    return departments.values();
  }
  //根据id获得部门信息
  public Department getDepartmentById(Integer id) {
    return departments.get(id);
  }
}

员工dao

@Repository
public class EmployeeDao {
  private static Map<Integer, Employee> employees = null;

  @Autowired
  private DepartmentDao departmentDao;

  static {
    employees = new HashMap<Integer, Employee>();
    employees.put(101, new Employee(101,"花0","125478@qq.com",1,new Department(1,"后勤部")));
    employees.put(102, new Employee(102,"花1","125478@qq.com",0,new Department(1,"后勤部")));
    employees.put(103, new Employee(103,"花2","125478@qq.com",1,new Department(1,"后勤部")));
    employees.put(104, new Employee(104,"花3","125478@qq.com",0,new Department(1,"后勤部")));
  }

  private static Integer initId = 105;
  //增加
  public void addEmployee(Employee employee) {
    if(employee.getId()==null) {
      employee.setId(initId++);
    }
    employee.setDepartment(departmentDao.getDepartmentById(employee.getDepartment().getId()));
    //因为不涉及数据库,自己模拟数据,所以把id先保存下来,方便以后的更新操作。
    employees.put(employee.getId(),employee);
  }

  //查
  public Collection<Employee> getEmployees() {
    return employees.values();
  }
  public Employee getEmployeeById(Integer id) {
    return employees.get(id);
  }

  //删除
  public void removeEmployee(Integer id) {
    employees.remove(id);
  }
}

设置主页

新建config包,包下新建MyMvcConfig

添加 @Configuration 并实现 WebMvcConfigurer 接口

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

  @Override
  public void addViewControllers(ViewControllerRegistry registry) {
    registry.addViewController("/").setViewName("index");
    registry.addViewController("/index.html").setViewName("index");
  }
}
5.2、国际化

properties中配置的国际化资源引发乱码问题

在idea中配置 File Encoding 的格式全为UTF-8,然后勾选 transparent native-to-ascii conversion

在resources下新建i18n,在这个文件下新建 login.properties ,再新建login_zh_CN.properties,idea会自动生成Resource Bundle ‘login’,然后再添加一个en_US(login_en_US.properties

idea下面就会出现Resource Boundle,然后依次添加需要的中英文及默认值就可以了

其次,需要在yaml中添加下列代码,否则也会乱码

spring:
  messages:
    basename: i18n/login

配置静态页面

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h2>这里是主页</h2>
<form action="">
    <div th:text="#{login.tip}">请登录</div>
    <div>
        <input type="text" th:placeholder="#{login.username}">
    </div>
    <div>
        <input type="password" th:placeholder="#{login.password}">
    </div>
    <input type="submit" th:value="#{login.btn}">
    <div>
        <a th:href="@{/index.html(l='zh_CN')}">中文</a>
        <a th:href="@{/index.html(l='en_US')}">English</a>
    </div>
</form>
</body>
</html>

config包下新建MyLocaleResolver类

public class MyLocaleResolver implements LocaleResolver {

  @Override
  public Locale resolveLocale(HttpServletRequest request) {
    String language = request.getParameter("l");
    Locale locale = Locale.getDefault();
    if(!StringUtils.isEmpty(language)) {
      String[] split = language.split("_");
      locale = new Locale(split[0], split[1]);
    }
    return locale;
  }

  @Override
  public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) {}
}

再MyMvcConfig下注册bean

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

  @Override
  public void addViewControllers(ViewControllerRegistry registry) {
    registry.addViewController("/").setViewName("index");
    registry.addViewController("/index.html").setViewName("index");
  }

  @Bean
  public LocaleResolver localeResolver() {//这里方法名必须是localeResolver,否则不执行这个方法
    return new MyLocaleResolver();
  }
}

注意:需要配置i18n文件,注册Bean方法名必须是localeResolver,html中取值用#{}

小知识:springboot2.4.x添加导航栏自定义图标

先去favicon.ico制作网站制作一个favicon.ico,如https://tool.lu/favicon/

将favicon.ico复制入resources->static->images目录下

在需要的网页head中添加下面代码

<link rel="icon" th:href="@{/images/favicon.ico}" type="image/x-icon"/>
<link rel="bookmark" th:href="@{/images/favicon.ico}" type="image/x-icon"/>

我把index.html放到resources->templates目录下,上面的href必须是/images/favicon.ico,我尝试过更改为…/static/images/favicon.icon但是图标不显示。

5.3、登录

从主页form表单提交,核对用户名和密码,正确则进入dashboard.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h2>这里是主页</h2>
<form th:action="@{/login}">
    <div th:text="#{login.tip}">请登录</div>
    <p th:text="${msg}" style="color: red" th:if="${not #strings.isEmpty(msg)}"></p>
    <div>
        <input type="text" required name="username" th:placeholder="#{login.username}">
    </div>
    <div>
        <input type="password" required name="password" th:placeholder="#{login.password}">
    </div>
    <input type="submit" th:value="#{login.btn}">
    <div>
        <a th:href="@{/index.html(l='zh_CN')}">中文</a>
        <a th:href="@{/index.html(l='en_US')}">English</a>
    </div>
</form>
</body>
</html>

LoginController

@Controller
public class LoginController {

  @RequestMapping("/login")
  public String login(@RequestParam("username") String username,
                      @RequestParam("password") String password,
                      Model model) {
    if(!StringUtils.isEmpty(username) && "123456".equals(password)){
      return "redirect:/main.html";//重定向,更符合实际开发场景
    }
    model.addAttribute("msg","用户名或密码错误");
    return "index";
  }
}

在MyMvcConfig里添加重定向

public void addViewControllers(ViewControllerRegistry registry) {
    registry.addViewController("/").setViewName("index");
    registry.addViewController("/index.html").setViewName("index");
    //登陆成功,页面路径会是http://localhost:8080/main.html,页面是dashboard.html
    registry.addViewController("/main.html").setViewName("dashboard");
}
5.4、拦截器

防止未登录就可以进入后台,需要设置拦截器

在LoginController里面返回路径之前把username放入session中

@Controller
public class LoginController {

  @RequestMapping("/login")
  public String login(@RequestParam("username") String username,
                      @RequestParam("password") String password,
                      Model model, HttpSession session) {
    if(!StringUtils.isEmpty(username) && "123456".equals(password)){
      session.setAttribute("loginUser",username);
      return "redirect:/main.html";
    }
    model.addAttribute("msg","用户名或密码错误");
    return "index";
  }
}

新建LoginHandlerInterceptor类

public class LoginHandlerInterceptor implements HandlerInterceptor {
  @Override
  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    Object loginUser = request.getSession().getAttribute("loginUser");
    if(loginUser==null) {
      request.setAttribute("msg","您没有权限进入该页面!");
      request.getRequestDispatcher("/index.html").forward(request,response);
      return false;
    }else{
      return true;
    }
  }
}

在MyMvcConfig里面注册拦截器

//注册拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(new LoginHandlerInterceptor()).addPathPatterns("/**").excludePathPatterns("/","/index.html","/login","/images/**");
}

上面代码意思就是拦截除了路径为"/","/index.html","/login","/images/**"之外的所有路径

前端页面

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
	<div>这里是登录成功的页面</div>
	<a>[[${session.loginUser}]]</a>
</body>
</html>
5.5、查询员工列表

EmployeeController

@Controller
public class EmployeeController {

  @Autowired
  private EmployeeDao employeeDao;

  @RequestMapping("/emps")
  public String list(Model model) {
    Collection<Employee> employees = employeeDao.getEmployees();
    model.addAttribute("emps", employees);
    return "emp/list";
  }
}

登陆成功后点击员工管理

<a th:href="@{/emps}">员工管理</a>

跳转到list.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <!--layui必须放在libs目录下,否则报404-->
    <!--<link rel="stylesheet" th:href="@{/libs/layui/css/layui.css}">-->
    <!--<link rel="stylesheet" th:href="@{/css/common.css}">-->
</head>
<body>
    <div>
        <table>
            <thead>
            <tr>
                <td>id</td>
                <td>lastName</td>
                <td>email</td>
                <td>gender</td>
                <td>department</td>
                <td>birth</td>
                <td>操作</td>
            </tr>
            </thead>
            <tbody>
                <tr th:each="emp : ${emps}">
                    <td th:text="${emp.getId()}"></td>
                    <td th:text="${emp.getLastName()}"></td>
                    <td th:text="${emp.getEmail()}"></td>
                    //下面的0不要加'',传过来是整数,写上''就会全变成男
                    <td th:text="${emp.getGender()==0?'':''}"></td>
                    <td th:text="${emp.department.getDepartmentName()}"></td>
                    <td th:text="${#dates.format(emp.getBirth(),'yyyy-MM-dd')}"></td>
                    <td>
                        <button>编辑</button>
                        <button>删除</button>
                    </td>
                </tr>
            </tbody>
        </table>
    </div>
</body>
</html>
5.6、添加员工

在list.html中添加按钮

<div>
    <button><a th:href="@{/emp}">添加员工</a></button>
</div>

在templates/emp文件夹下新建add.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div>
    <form th:action="@{/emp}" method="post">
        <input type="hidden" name="id">
        <div>
            姓名<input type="text" name="lastName">
        </div>
        <div>
            邮箱<input type="email" name="email">
        </div>
        <div>性别:
            <label><input type="radio" name="gender" value="1">男生</label>
            <label><input type="radio" name="gender" value="0">女生</label>
        </div>
        <div>
            部门<select name="department.id">//这里传回的是department的id
            <option
                    th:each="department : ${departments}"
                    th:text="${department.getDepartmentName()}"
                    th:value="${department.getId()}">1</option>
        </select>
        </div>

        <div>
            生日<input type="date" name="birth">
        </div>
        <input type="submit" value="提交">
    </form>
</div>
</body>
</html>

在EmployeeController中写接口

@Controller
public class EmployeeController {

  @Autowired
  private EmployeeDao employeeDao;
  @Autowired
  private DepartmentDao department;

  @RequestMapping("/emps")
  public String list(Model model) {
    Collection<Employee> employees = employeeDao.getEmployees();
    model.addAttribute("emps", employees);
    return "emp/list";
  }
  @GetMapping("/emp")
  public String toAdd(Model model) {
    Collection<Department> departments = department.getDepartments();
    model.addAttribute("departments",departments);
    return "emp/add";
  }

  @PostMapping("/emp")
  public String addEmp(Employee employee) {
    employeeDao.addEmployee(employee);
    return "redirect:/emps";
  }
}

注意点:前端用type="date"传递给后端参数,一定要在pojo类的对应字段加上@DateTimeFormat(pattern = "yyyy-MM-dd")

5.7、修改员工信息

controller层编写接口

@GetMapping("/emp/{id}")
public String toUpdatePage(@PathVariable("id")Integer id, Model model) {

    Employee employee = employeeDao.getEmployeeById(id);
    model.addAttribute("employee", employee);
    Collection<Department> departments = department.getDepartments();
    model.addAttribute("departments", departments);
    return "emp/update";
}

@PostMapping("/update")
public String updateEmp(Employee employee) {
    employeeDao.addEmployee(employee);
    return "redirect:/emps";
}

update.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div>
    更新
    <form th:action="@{/update}" method="post">
        <input type="hidden" name="id" th:value="${employee.id}">
        <div>
            姓名<input type="text" th:value="${employee.lastName}" name="lastName">
        </div>
        <div>
            邮箱<input type="email" th:value="${employee.email}" name="email">
        </div>
        <div>性别:
            <label><input type="radio" th:checked="${employee.gender}==1" name="gender" value="1">男生</label>
            <label><input type="radio" th:checked="${employee.gender}==0" name="gender" value="0">女生</label>
        </div>
        <div>
            部门<select name="department.id">
            <option th:each="dep:${departments}"
                    th:selected="${employee.getDepartment().id==dep.id}"
                    th:text="${dep.departmentName}"
                    th:value="${dep.id}">1</option>
        </select>
        </div>

        <div>
            生日<input type="date" name="birth" th:value="${#dates.format(employee.getBirth(),'yyyy-MM-dd')}">
        </div>
        <input type="submit" value="提交">
    </form>
</div>
</body>
</html>

其中,1

value要写dep.id,如果写employee.getDepartment().id,这是一个常量,不会更改数据

在list.html中添加编辑按钮

<!--<button><a th:href="@{/emp/}+${emp.getId()}">编辑</a></button>-->
<button><a th:href="@{'/emp/'+${emp.getId()}}">编辑</a></button>

注意:上面两种写法都能出来效果,但是第一种idea会报红波浪线,不影响正常编译。

5.8、删除员工

controller层

@RequestMapping("/delete/{id}")
public String delEmp(@PathVariable("id") Integer id) {
    employeeDao.removeEmployee(id);
    return "redirect:/emps";
}

list.html添加删除按钮

<button><a th:href="@{'/delete/'+${emp.getId()}}">删除</a></button>
5.9、404页面

直接在template下面新建error文件夹,文件夹下新建404.html,springboot会自动识别其中的404.html

5.10、注销登录

直接把session清理掉即可

controller接口

@RequestMapping("/logout")
public String layout(HttpSession session) {
    session.invalidate();
    return "redirect:/index.html";
}

注销登录按钮

<a th:href="@{/logout}">注销登录</a>

6、整合Mybatis

6.1、springboot的jdbc

导入依赖,导入Spring Web、JDBC API、MySQL Driver依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

application.yml配置数据源

spring:
  datasource:
    username: root
    password: 123456
    url: jdbc:mysql://localhost:3306/javatest?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC&useSSL=false
    driver-class-name: com.mysql.cj.jdbc.Driver

测试

@SpringBootTest
class Springbootdemo02ApplicationTests {

  @Autowired
  DataSource dataSource;

  @Test
  void contextLoads() throws SQLException {
    //默认数据源hikari
    System.out.println(dataSource.getClass());//class com.zaxxer.hikari.HikariDataSource
    Connection connection = dataSource.getConnection();
    System.out.println(connection);
    connection.close();
  }
}
6.2、jdbc的CRUD

新建JDBCController

@RestController
public class JDBCController {

  @Autowired
  JdbcTemplate jdbcTemplate;//这里面封装了全部的增删改查方法

  @RequestMapping("/studentList")
  public List<Map<String, Object>> studentList() {
    String sql = "select * from student";
    List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
    return maps;
  }

  @RequestMapping("/addStudent")
  public String addStudent() {
    String sql = "insert into student(id,name,phone) values (3,'公孙里','1008611')";
    jdbcTemplate.update(sql);
    return "add-ok";
  }

  @RequestMapping("/updateStudent/{id}")
  public String updateStudent(@PathVariable("id") int id) {
//    String sql = "update student set name='豆豆',phone='888888' where id=?";
    String sql = "update student set name=?,phone=? where id=" + id;
    Object[] objects = new Object[2];
    objects[0] = "豆豆";
    objects[1] = "888888";
    jdbcTemplate.update(sql, objects);
    return "update-ok";
  }

  @RequestMapping("/deleteStudent/{id}")
  public String deleteStudent(@PathVariable("id") int id) {
    String sql = "delete from student where id=?";
    jdbcTemplate.update(sql, id);
    return "delete-ok";
  }
}
6.3、整合Druid

导入Druid依赖

<!-- druid -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.5</version>
</dependency>

然后只需要在application.yml中添加type属性,即可更改数据源

spring:
  datasource:
    username: root
    password: 123456
    url: jdbc:mysql://localhost:3306/javatest?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC&useSSL=false
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource

此时数据源就变为class com.alibaba.druid.pool.DruidDataSource

配置Druid

spring:
  datasource:
    username: root
    password: 123456
    url: jdbc:mysql://localhost:3306/javatest?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC&useSSL=false
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource

    #Spring Boot 默认是不注入这些属性值的,需要自己绑定
    #druid 数据源专有配置
    initialSize: 5
    minIdle: 5
    maxActive: 20
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true

    #配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
    #如果允许时报错  java.lang.ClassNotFoundException: org.apache.log4j.Priority
    #则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j
    filters: stat,wall,log4j
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

然后导入log4j的依赖

<!--log4j-->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

在resources文件下新建log4j.properties,不创建的话一会测试会报错

log4j.rootLogger=DEBUG, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n

在config中新建DruidConfig

@Configuration
public class DruidConfig {

  @Bean
  @ConfigurationProperties("spring.datasource")
  public DataSource druidDataSource() {
    return new DruidDataSource();
  }
}

@ConfigurationProperties(prefix = "spring.datasource"):作用就是将 全局配置文件中前缀为 spring.datasource的属性值注入到 com.alibaba.druid.pool.DruidDataSource 的同名参数中

测试

@SpringBootTest
class Springbootdemo02ApplicationTests {
  @Autowired
  DataSource dataSource;

  @Test
  void contextLoads() throws SQLException {
    //默认数据源hikari
    System.out.println(dataSource.getClass());//class com.zaxxer.hikari.HikariDataSource
    Connection connection = dataSource.getConnection();
    DruidDataSource druidDataSource = (DruidDataSource) dataSource;
    System.out.println("getMaxActive是:"+druidDataSource.getMaxActive());//20
    connection.close();
  }
}

配置Druid数据源监控

DruidConfig中配置

//配置 Druid 监控管理后台的Servlet;
//内置 Servlet 容器时没有web.xml文件,所以使用 Spring Boot 的注册 Servlet 方式
@Bean
public ServletRegistrationBean statViewServlet() {
    ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");

    // 这些参数可以在 com.alibaba.druid.support.http.StatViewServlet 
    // 的父类 com.alibaba.druid.support.http.ResourceServlet 中找到
    Map<String, String> initParams = new HashMap<>();
    initParams.put("loginUsername", "admin"); //后台管理界面的登录账号
    initParams.put("loginPassword", "123456"); //后台管理界面的登录密码

    //后台允许谁可以访问
    //initParams.put("allow", "localhost"):表示只有本机可以访问
    //initParams.put("allow", ""):为空或者为null时,表示允许所有访问
    initParams.put("allow", "");
    //deny:Druid 后台拒绝谁访问
    //initParams.put("deny", "192.168.0.1");表示禁止此ip访问

    //设置初始化参数
    bean.setInitParameters(initParams);
    return bean;
}

http://localhost:8080/druid 访问此链接进入druid登录页面,输入账号密码登录

配置 Druid web 监控 filter 过滤器

DruidConfig中配置


//配置 Druid 监控之web监控的 filter
//WebStatFilter:用于配置Web和Druid数据源之间的管理关联监控统计
@Bean
public FilterRegistrationBean webStatFilter() {
    FilterRegistrationBean bean = new FilterRegistrationBean();
    bean.setFilter(new WebStatFilter());

    //exclusions:设置哪些请求进行过滤排除掉,从而不进行统计
    Map<String, String> initParams = new HashMap<>();
    initParams.put("exclusions", "*.js,*.css,/druid/*,/jdbc/*");
    bean.setInitParameters(initParams);

    //"/*" 表示过滤所有请求
    //bean.setUrlPatterns(Arrays.asList("/*"));
    return bean;
}
6.4、整合Mybatis

一定要导入依赖!!!

<!-- mybatis-spring-boot-starter -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.4</version>
</dependency>

其余的依赖为:

<dependencies>
    <!-- mybatis-spring-boot-starter -->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.1.4</version>
    </dependency>

    <!--jdbc-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    <!--thymeleaf-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <!--web-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!--mysql-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    <!--lombok-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <!--test-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

创建实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
  private int id;
  private String name;
  private String phone;
}

在application.yml中配置数据源

spring:
  datasource:
    username: root
    password: 123456
    url: jdbc:mysql://localhost:3306/javatest?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC&useSSL=false
    driver-class-name: com.mysql.cj.jdbc.Driver

在com.ljh.mapper中编写mapper接口

@Repository
@Mapper
public interface StudentMapper {
  List<Student> queryAllStudents();

  Student queryStudentById(int id);

  int addStudent(Student student);

  int delStudent(int id);

  int updateStudent(Student student);
}

可以直接在mapper接口上添加@Mapper,也可以在工程的主入口处添加@MapperScan("com.ljh.mapper")

@Mapper表示这是Mybatis的一个Mapper类

在application.yml中配置别名和mapper路径

mybatis:
  type-aliases-package: com.ljh.pojo
  mapper-locations: classpath:mybatis/mapper/*.xml

resources目录下新建resources->mybatis->mapper->xxx.xml

StudentMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace绑定一个Mapper-->
<mapper namespace="com.ljh.mapper.StudentMapper">

  <select id="queryAllStudents" resultType="Student">
    select * from student
  </select>

</mapper>

编写接口

@RestController
public class StudentController {

  @Autowired
  StudentMapper studentMapper;
//  下面是set方式注入
//  @Autowired
//  public void setStudentMapper(StudentMapper studentMapper) {
//    this.studentMapper = studentMapper;
//  }

  @RequestMapping("/queryAllStudent")
  public List<Student> queryAllStudent() {
    List<Student> students = studentMapper.queryAllStudents();
    return students;
  }
}

其他增删改查大同小异,不再多写。

7、SpringSecurity

Spring Security是一个框架,侧重于为Java应用程序提供身份验证和授权。

7.1、基本使用

现有templates->level1、level2、level3下新建1.html,2.html,3.html

编写RouterController

@Controller
public class RouterController {

   //主页
  @RequestMapping({"/","/index","index.html"})
  public String index() {
    return "index";
  }

  @RequestMapping("/level1/{id}")
  public String level1(@PathVariable("id") int id) {
    return "level1/"+id;
  }
  @RequestMapping("/level2/{id}")
  public String level2(@PathVariable("id") int id) {
    return "level2/"+id;
  }
  @RequestMapping("/level3/{id}")
  public String level3(@PathVariable("id") int id) {
    return "level3/"+id;
  }
}

测试接口运行。

此时导入security的依赖

<!--security-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

“认证”(Authentication)

身份验证是关于验证您的凭据,如用户名/用户ID和密码,以验证您的身份。

身份验证通常通过用户名和密码完成,有时与身份验证因素结合使用。

“授权” (Authorization)

授权发生在系统成功验证您的身份后,最终会授予您访问资源(如信息,文件,数据库,资金,位置,几乎任何内容)的完全权限。

编写配置类SecurityConfig

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    // 定制请求的授权规则
    // 首页所有人可以访问,其他页面只有对应角色可以进入
    http.authorizeRequests().antMatchers("/").permitAll()
            .antMatchers("/level1/**").hasRole("vip1")
            .antMatchers("/level2/**").hasRole("vip2")
            .antMatchers("/level3/**").hasRole("vip3");
    // 开启自动配置的登录功能
    // /login 请求来到登录页
    // /login?error 重定向到这里表示登录失败
    http.formLogin();

    // 注销,注销成功后返回到首页
    http.logout().logoutSuccessUrl("/");
  }

  @Override
  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    //下面的密码必须要加上,否则报错
    auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
            .withUser("admin").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3")
            .and()
            .withUser("ljh").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3");
  }
}

一定要加上@EnableWebSecurity,然后继承WebSecurityConfigurerAdapter,重写config

在thymeleaf上显示,index.html

首先需要导入thymeleafthymeleaf-extras-springsecurity5依赖

<!-- thymeleaf-extras-springsecurity5 -->
<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
<!--thymeleaf-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

index.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h2 style="color: red">首页</h2>

<div style="margin: 100px auto;width: 900px;height: 400px;">
    <div sec:authorize="hasRole('vip1')" style="display: inline-block;width: 290px;height: 400px;">
        <a th:href="@{/level1/1}">level1-1</a>
        <a th:href="@{/level1/2}">level1-2</a>
        <a th:href="@{/level1/3}">level1-3</a>
    </div>
    <div sec:authorize="hasRole('vip2')" style="display: inline-block;width: 290px;height: 400px;">
        <a th:href="@{/level2/1}">level2-1</a>
        <a th:href="@{/level2/2}">level2-2</a>
        <a th:href="@{/level2/3}">level2-3</a>
    </div>
    <div sec:authorize="hasRole('vip3')" style="display: inline-block;width: 290px;height: 400px;">
        <a th:href="@{/level3/1}">level3-1</a>
        <a th:href="@{/level3/2}">level3-2</a>
        <a th:href="@{/level3/3}">level3-3</a>
    </div>
</div>
<div sec:authorize="!isAuthenticated()">
    <a th:href="@{/login}">登录</a>
</div>
<div sec:authorize="isAuthenticated()">
    用户名:<span sec:authentication="principal.username"></span>
    角色:<span sec:authentication="principal.authorities"></span>
    <a th:href="@{/logout}">注销</a>
</div>
</body>
</html>

sec:authorize相当于vue中的v-show,v-if,就是判断显不显示

注意:一定要加上命名空间

xmlns:th="http://www.thymeleaf.org" 
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5"

如果 sec:authorize 不生效,可能是版本问题,可以在这个方面考虑。

7.2、自定义登录

只需要在config中加入http.rememberMe();就可以保存cookie

但是这里默认使用的都是springsecurity自带的登录页面,所以需要自定义登录页面

跳转到自己的登录页,需要写http.formLogin().loginPage("/xxx")跳转到指定路由

首页登录按钮

<div sec:authorize="!isAuthenticated()">
    <a th:href="@{/toLogin}">登录</a>
</div>

SecurityConfig

http.formLogin().usernameParameter("user").passwordParameter("pwd").loginPage("/toLogin");

//记住我
http.rememberMe().rememberMeParameter("remember");

//关闭csrf功能:跨站请求伪造,默认只能通过post方式提交logout请求
http.csrf().disable();

login.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form th:action="@{/toLogin}" method="post">
    <div>用户名
        <input type="text" name="user">
    </div>
    <div>密码
        <input type="password" name="pwd">
    </div>
    <div>
        <input type="checkbox" name="remember">记住我
    </div>
    <input type="submit" value="登录">
</form>
</body>
</html>

注意点:SecurityConfig中的usernameParameter和passwordParameter不是必须的,当前端传过来的用户名和密码对应name值为username和password时不必写,系统默认就是username和password。但是如果像上面一样name分别是user和pwd,那么usernameParameter和passwordParameter必须写上,否则程序报错404.

另外,rememberme也需要添加参数对应前端的name值

登录页面请求方式必须是POST

注销时,如果报错404,可以添加下面的代码

//关闭csrf功能:跨站请求伪造,默认只能通过post方式提交logout请求,但是前端写的是一个a标签,不是一个form表单,所以需要设置关闭csrf功能
http.csrf().disable();

8、shiro

  • subject: 应用代码直接交互的对象是Subject, 也就是说Shiro的对外API核心就是Subject, Subject代表了当前的用户。Subject其实是一一个门面, SecurityManageer 才是实际的执行者
  • SecurityManager: 安全管理器,即所有与安全有关的操作都会与SercurityManager交互, 并且它管理着所有的Subject,可以看出它是Shiro的核心,它负责与Shiro的其他组件进行交互,它相当于SpringMVC的DispatcherServlet的角色
  • Realm: Shiro从Realm获取安全数据 (如用户,角色,权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较,来确定用户的身份是否合法;也需要从Realm得到用户相应的角色、权限,进行验证用户的操作是否能够进行,可以把Realm看DataSource;
8.1、整合spring

新建springboot空项目,导入web和thymeleaf依赖
导入shiro-spring依赖

<!-- shiro-spring -->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.7.1</version>
</dependency>

目录结构为:templates下新建views,views下新建add.html和update.html

新建login.html和index.html

<!--login.html-->
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h2>登录页</h2>
<p style="color: red" th:text="${msg}"></p>
<form th:action="@{/login}">
    <p>用户名:<input type="text" name="username"></p>
    <p>密码:<input type="password" name="password"></p>
    <p><input type="submit"></p>
</form>
</body>
</html>
<!--index.html-->
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h2>首页</h2>
<a th:href="@{/add}">add</a>
<a th:href="@{/update}">update</a>
</body>
</html>

先配置各自的接口

@Controller
public class MyController {

  @RequestMapping({"/","/index"})
  public String index() {
    return "index";
  }
  @RequestMapping("/add")
  public String add() {
    return "views/add";
  }
  @RequestMapping("/update")
  public String update() {
    return "views/update";
  }
  @RequestMapping("/toLogin")
  public String toLogin() {
    return "login";
  }
  @RequestMapping("/login")
  public String login(String username, String password, Model model) {
    Subject subject = SecurityUtils.getSubject();
    UsernamePasswordToken token = new UsernamePasswordToken(username, password);
    try {
      subject.login(token);
      return "index";
    } catch (UnknownAccountException e){//用户名不存在
      model.addAttribute("msg","用户名错误");
      return "login";
    }catch (IncorrectCredentialsException e){//密码不存在
      model.addAttribute("msg","密码错误");
      return "login";
    }
  }
}

最后面的/login接口得到当前用户subject,再判断是否用户名和密码正确,跳转到相应页面。

配置ShiroConfig和UserRealm

public class UserRealm extends AuthorizingRealm {

  // 授权
  @Override
  protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    System.out.println("执行了授权doGetAuthorizationInfo");
    return null;
  }
  // 认证
  @Override
  protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    System.out.println("执行了认证doGetAuthenticationInfo");
	//模拟数据
    String name = "root";
    String password = "123456";
    //强制转换为UsernamePasswordToken
    UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
    if(!token.getUsername().equals(name)) {
      return null;//表示用户名不存在
    }
    //密码shiro自己做
    return new SimpleAuthenticationInfo("",password,"");
  }
}
@Configuration
public class ShiroConfig {

  //ShiroFilterFactoryBean
  @Bean
  public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") SecurityManager securityManager) {
    ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
    shiroFilterFactoryBean.setSecurityManager(securityManager);

    //添加shiro的内置过滤器
    /*
        anon:无需认证就能访问
        authc:必须认证才能访问
        user:必须拥有记住我功能才能访问
        perms:拥有某个资源的权限才能访问
        role:拥有某个角色权限才能访问
     */
    Map<String, String> filterMap = new LinkedHashMap<>();
    filterMap.put("/add","authc");
    filterMap.put("/update","authc");
    shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);

    shiroFilterFactoryBean.setLoginUrl("/toLogin");

    return shiroFilterFactoryBean;
  }
  //DefaultWebSecurityManager
  @Bean(name = "securityManager")
  public SecurityManager getSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    securityManager.setRealm(userRealm);
    return securityManager;
  }
  // Realm
  @Bean
  public UserRealm userRealm() {
    return new UserRealm();
  }
}

注意:ShiroConfig从下往上写,userRealm->getSecurityManager->getShiroFilterFactoryBean

传值引用bean时直接在参数前加@Qualifier("xxx")

8.2、整合mybatis

导入mybatis依赖

<!-- mybatis-spring-boot-starter -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.4</version>
</dependency>
<!-- druid -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.5</version>
</dependency>
<!--mysql-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>
<!--lombok-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>
<!--jdbc-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

除此之外,shiro想要用thymeleaf,整合thymeleaf

<!-- thymeleaf-extras-shiro -->
<dependency>
    <groupId>com.github.theborakompanioni</groupId>
    <artifactId>thymeleaf-extras-shiro</artifactId>
    <version>2.0.0</version>
</dependency>

配置数据库,下面是总的配置application.yml

spring:
  datasource:
    username: root
    password: 123456
    url: jdbc:mysql://localhost:3306/javatest?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC&useSSL=false
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource

    #Spring Boot 默认是不注入这些属性值的,需要自己绑定
    #druid 数据源专有配置
    initialSize: 5
    minIdle: 5
    maxActive: 20
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true

    #配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
    #如果允许时报错  java.lang.ClassNotFoundException: org.apache.log4j.Priority
    #则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j
    filters: stat,wall,log4j
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
  thymeleaf:
    cache: false
mybatis:
  type-aliases-package: com.ljh.pojo
  mapper-locations: classpath:mybatis/mapper/*.xml

新建pojo,mapper,service

Student实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
  private int id;
  private String name;
  private String password;
  private String perms;
}

mapper接口

@Repository
@Mapper
public interface StudentMapper {
  Student getStudentByName(String name);
}

service接口

public interface StudentService {
  Student getStudentByName(String name);
}

service接口实现类

@Service
public class StudentServiceImpl implements StudentService {

  StudentMapper studentMapper;

  @Autowired
  public void setStudentMapper(StudentMapper studentMapper) {
    this.studentMapper = studentMapper;
  }

  @Override
  public Student getStudentByName(String name) {
    return studentMapper.getStudentByName(name);
  }
}

在resources下新建mybatis->mapper->StudentMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace绑定一个Mapper-->
<mapper namespace="com.ljh.mapper.StudentMapper">

  <select id="getStudentByName" resultType="Student">
    select * from student where name = #{name}
  </select>

</mapper>

配置好可以在测试类中测试。

主要介绍shiro与mybatis整合

新建ShiroConfig配置文件

@Configuration
public class ShiroConfig {

  //ShiroFilterFactoryBean
  @Bean
  public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") SecurityManager securityManager) {
    ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
    shiroFilterFactoryBean.setSecurityManager(securityManager);

    //添加shiro的内置过滤器
    /*
        anon:无需认证就能访问
        authc:必须认证才能访问
        user:必须拥有记住我功能才能访问
        perms:拥有某个资源的权限才能访问
        role:拥有某个角色权限才能访问
     */
    Map<String, String> filterMap = new LinkedHashMap<>();
    filterMap.put("/add","perms[add]");
    filterMap.put("/update","perms[update]");

    //设置登录页url
    shiroFilterFactoryBean.setLoginUrl("/toLogin");


    //未授权
    shiroFilterFactoryBean.setUnauthorizedUrl("/noauth");

    shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);

    return shiroFilterFactoryBean;
  }
  //DefaultWebSecurityManager
  @Bean(name = "securityManager")
  public SecurityManager getSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    securityManager.setRealm(userRealm);
    return securityManager;
  }
  // Realm
  @Bean
  public UserRealm userRealm() {
    return new UserRealm();
  }

  //shiro thymeleaf
  @Bean
  public ShiroDialect shiroDialect() {
    return new ShiroDialect();
  }
}

上面差不多都是固定代码,只需要更改ShiroFilterFactoryBean的具体实现即可。

UserRealm类

public class UserRealm extends AuthorizingRealm {

  @Autowired
  StudentService studentService;

  // 授权
  @Override
  protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    System.out.println("执行了授权doGetAuthorizationInfo");

    SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
    //拿到当前用户登陆对象
    Subject subject = SecurityUtils.getSubject();
    //这里拿到的就是认证时传来的Student对象
    Student currentStudent = (Student) subject.getPrincipal();
    info.addStringPermission(currentStudent.getPerms());
//      info.addStringPermission("add");
    return info;
  }

  // 认证
  @Override
  protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    System.out.println("执行了认证doGetAuthenticationInfo");

    UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
    Student student = studentService.getStudentByName(token.getUsername());
    if(student==null) {
      return null;
    }
    String name = student.getName();
    String password = student.getPassword();
    //上面根据前端输入框输入的用户名在数据库查询,
    // 返回null或具体的Student对象,下面的代码没有作用
    //因为忽略了student可能为空,一开始总是报空指针错误!
//    if (!token.getUsername().equals(name)) {
//      return null;
//    }
    //new SimpleAuthenticationInfo(principal,credentials,realmName)
    //第一个参数是返回的值,可以通过subject.getPrincipal获取到当前的Student对象
    //第二个是密码验证
    return new SimpleAuthenticationInfo(student, password, "");
  }
}

controller

@RequestMapping("/login")
public String login(String username, String password, Model model, HttpSession session) {
    Subject subject = SecurityUtils.getSubject();
    UsernamePasswordToken token = new UsernamePasswordToken(username, password);
    try {
        subject.login(token);
        session.setAttribute("loginUser",username);
        return "index";
    } catch (UnknownAccountException e){//用户名不存在
        model.addAttribute("msg","用户名错误");
        return "login";
    }catch (IncorrectCredentialsException e){//密码不存在
        model.addAttribute("msg","密码错误");
        return "login";
    }
}
@RequestMapping("/noauth")
@ResponseBody
public String noauth() {
    return "未经授权,无法访问";
}

@RequestMapping("/logout")
public String logout() {
    Subject subject = SecurityUtils.getSubject();
    subject.logout();
    return "redirect:/index";
}

index.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
      xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h2>首页</h2>

<div th:if="${session.loginUser==null}">
    <a th:href="@{/toLogin}">登陆</a>
</div>
<div th:if="${session.loginUser!=null}">
    <a th:href="@{/logout}">注销</a>
</div>
<div shiro:hasPermission="add">
    <a th:href="@{/add}">add</a>
</div>
<div shiro:hasPermission="update">
    <a th:href="@{/update}">update</a>
</div>
</body>
</html>

上面需要引入shiro的命名空间 xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"

shiro:hasPermission="add"允许有这个权限的显示add,这个权限在ShiroConfig里面通过map来设置,如果路径为/user/add,则parms[user:add],若有多个权限,中间用逗号分开

login.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h2>登录页</h2>

<p style="color: red" th:text="${msg}"></p>
<form th:action="@{/login}">
    <p>用户名:<input type="text" name="username"></p>
    <p>密码:<input type="password" name="password"></p>
    <p><input type="submit"></p>
</form>
</body>
</html>

9、Swagger

  • 号称世界上最流行的API框架
  • Restful Api 文档在线自动生成器 => API 文档 与API 定义同步更新
  • 直接运行,在线测试API
9.1、基本使用

导入swagger2swagger-ui的pom依赖

<!-- swagger2 -->
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.9.2</version>
</dependency>
<!-- swagger-ui -->
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.9.2</version>
</dependency>

集成Swagger

SwaggerConfig配置类

@Configuration
@EnableSwagger2   // 开启Swagger2的自动配置
public class SwaggerConfig {


  @Bean
  public Docket docket() {
    return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo());
  }
    
  //通过apiInfo()属性配置文档信息
  private ApiInfo apiInfo() {
    return new ApiInfo(
            "swagger",
            "最好的API",
            "v1.0",
            "http://baidi.com",
            new Contact("Ljh","www.baidu.com","ljhprogram@163.com"),
            "Apache 2.0",
            "http://www.apache.org/licenses/LICENSE-2.0",
            new ArrayList<>()
    );
  }
}

访问http://localhost:8080/swagger-ui.html即可。

9.2、配置扫描接口
@Bean
public Docket docket() {
    return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo())
        //通过.select()方法,去配置扫描接口,select和build同时出现
        .select()
        //扫描controller下的所有api
        .apis(RequestHandlerSelectors.basePackage("com.ljh.controller"))
        //.paths(PathSelectors.ant("/ljh/**"))过滤ljh包下的所有文件
        .build();
}

RequestHandlerSelectors可以跟以下参数

any() // 扫描所有,项目中的所有接口都会被扫描到
none() // 不扫描接口
// 通过方法上的注解扫描,如withMethodAnnotation(GetMapping.class)只扫描get请求
withMethodAnnotation(final Class<? extends Annotation> annotation)
// 通过类上的注解扫描,如.withClassAnnotation(Controller.class)只扫描有controller注解的类中的接口
withClassAnnotation(final Class<? extends Annotation> annotation)
basePackage(final String basePackage) // 根据包路径扫描接口
9.3、配置Swagger开关
new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).enable(false)
//配置是否启用Swagger,如果是false,在浏览器将无法访问

项目需要创立默认的application.yml和开发环境、生产环境

server:
  port: 8080
spring:
  profiles:
    active: dev  #设置为开发环境

开发环境: application-dev.yml

# 开发环境
server:
  port: 8081

生产环境: application-pro.yml

# 生产环境
server:
  port: 8082

如何设置在开发环境dev显示swagger,在生产环境pro不显示

@Bean
public Docket docket(Environment environment) {

    Profiles of = Profiles.of("dev");
    // 判断当前是否处于dev环境
    boolean b = environment.acceptsProfiles(of);

    return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo())
        // 通过 enable() 接收此参数判断是否要显示
        .enable(b)
        //通过.select()方法,去配置扫描接口,select和build同时出现
        .select()
        //扫描controller下的所有api
        .apis(RequestHandlerSelectors.basePackage("com.ljh.controller"))
        //.paths(PathSelectors.ant("/ljh/**"))过滤ljh报下的所有文件
        .build();
}

测试时注意端口号发生了变动,要更改端口号。

9.4、配置API分组
new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).groupName("Ljh") 
// 配置分组,默认为default,现在修改为Ljh

分组可以设置多个,通过new Docket多个实例并定义groupName实现多个分组

@Bean
public Docket docket1() {
    return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo())
        .groupName("A");
}
@Bean
public Docket docket2() {
    return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo())
        .groupName("B");
}
@Bean
public Docket docket3() {
    return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo())
        .groupName("C");
}
9.5、常用注解

实体类

@ApiModel("用户实体类")
public class User {
  @ApiModelProperty("用户名")
  public String name;
  @ApiModelProperty("密码")
  public String password;
}

controller

@RestController
public class Hello {

  @GetMapping("/hello")
  @ApiOperation("你好接口")
  public String hello() {
    return "hello";
  }

  @PostMapping("/user")
  @ApiOperation("空user接口")
  public User user() {
    return new User();
  }

  @GetMapping("/getUserName")
  @ApiOperation("得到名字接口")
  public String getUserName(@ApiParam("名字") String name) {
    return name;
  }
  @GetMapping("/getPassword")
  public String getPassword(@ApiParam("密码")String password) {
    return password;
  }
}

各个注解作用:

  • @Api(tags = “xxx模块说明”)作用在模块类上
  • @ApiOperation(“xxx接口说明”)作用在接口方法上
  • @ApiModel(“xxxPOJO说明”)作用在返回对象类上
  • @ApiModelProperty(value = “xxx属性说明”,hidden = true)作用在类方法和属性上,hidden设置为true可以隐藏该属性
  • @ApiParam(“xxx参数说明”)作用在参数、方法和字段上,类似@ApiModelProperty

10、异步任务、邮件发送、定时任务

10.1、异步任务
@Service
public class AsyncService {
  @Async
  public void hello() {
    try {
      Thread.sleep(3000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println("正在执行中。。");
  }
}

如果没加@Async,代码会同步执行,等待3秒后在控制台输出语句。

在方法上加了@Async注解之后,函数的主入口还需要添加一个注解@EnableAsync

@EnableAsync   //开启异步注解功能
@SpringBootApplication
public class SpringbootTestApplication {

  public static void main(String[] args) {
    SpringApplication.run(SpringbootTestApplication.class, args);
  }

}

controller

@RestController
public class AsyncController {

  @Autowired
  AsyncService asyncService;

  @RequestMapping("/hello")
  public String hello() {
    asyncService.hello();
    return "执行完毕!";
  }
}

没有异步,前端请求参数时,就必须等待3秒,在浏览器就会出现一直转圈3秒的情况,用户体验很不好。

现在加了两个注解之后,用户就会直接得到页面信息。

10.2、邮件发送

先导入邮件依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
</dependency>

设置application.yml,配置mail的相关属性,具体属性可到MailProperties.class中查看。

spring:
  mail:
    username: xxxxxxx@163.com
    password: UPXWKNESESBSXPOA   
    host: smtp.163.com

密码获取:到https://mail.163.com/登录,打开设置, 打开POP3/SMTP/IMAP ,开启相应的服务,会让你发送一条短信,然后给你一条密钥。

另外qq需要配置ssl:

spring.mail.properties.mail.smtp.ssl.enable=true

在测试类中实现简单邮件(只有文字)

@SpringBootTest
class SpringbootTestApplicationTests {

  @Autowired
  JavaMailSender mailSender;

  @Test
  void contextLoads() {
    SimpleMailMessage message = new SimpleMailMessage();
    message.setSubject("你好呀~");
    message.setText("第一个简单邮件发送");

    message.setTo("xxxxxx@163.com");
    message.setFrom("xxxxxx@163.com");

    mailSender.send(message);
  }
}

测试运行,邮件发送成功。

在测试类中实现简单邮件,加了图片

@SpringBootTest
class SpringbootTestApplicationTests {

  @Autowired
  JavaMailSender mailSender;

  @Test
  void contextLoad2() throws MessagingException {
    MimeMessage mimeMessage = mailSender.createMimeMessage();
    MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);

    helper.setSubject("复杂");
    helper.setText("这是一个复杂邮件");
    发送附件,下面发送的是图片和视频
    helper.addAttachment("pic.jpg",new File("E:\\电脑壁纸1920×1080\\pic.jpg"));
    helper.addAttachment("知足.mp4",new File("D:\\迅雷下载\\zhizu.mp4"));

    helper.setTo("xxxxxx@163.com");
    helper.setFrom("xxxxxx@163.com");
    mailSender.send(mimeMessage);
  }
}
10.3、定时任务

开启定时任务,首先要在主函数上加一个注解@EnableScheduling

@EnableScheduling //开启基于注解的定时任务
@SpringBootApplication
public class SpringbootTestApplication {

  public static void main(String[] args) {
    SpringApplication.run(SpringbootTestApplication.class, args);
  }

}

ScheduleService

@Service
public class ScheduledService {

  @Scheduled(cron = "0/2 * * * * ? ")
  public void hello() {
    System.out.println("hello");
  }
}

只需要在对应方法上添加注解@Scheduled(cron表达式)即可。

10.4、cron表达式

Cron语法格式,共7个部分(域)组成:
Seconds(秒) Minutes(分钟) Hours(小时) DayofMonth(天/几号) Month(月) DayofWeek(星期几,0和7代表星期天) Year(年)

每一个域可出现的字符如下:
Seconds:可出现", - * /",有效范围为0-59的整数
Minutes:可出现", - * /",有效范围为0-59的整数
Hours:可出现", - * /",有效范围为0-23的整数
DayofMonth(天/几号):可出现", - * / ? L W C",有效范围为0-31的整数
Month:可出现", - * /",有效范围为1-12的整数
DayofWeek(星期几) :可出现", - * / ? L C #",有效范围为1-7的整数或SUN-SAT(星期天开始)两个范围
Year:可出现", - * /"

(1) *:表示匹配该域的任意值。

(2) ?:只能用在DayofMonth和DayofWeek。DayofMonth和 DayofWeek会相互影响。设置其中某一个值后,为了避免冲突,需要将另一个的值设为“?”

(3) -:表示范围,在某一个时间范围内触发一次

(4) /:表示起始时间开始触发,然后每隔固定时间触发一次
例如在分钟域里使用5/10,则意味着5分钟触发一次,15,25等分别触发一次.
在(分钟)里的“0/15”,表示从第0分钟开始,每15分钟触发一次

(5) ,:表示and

(6) L:表示最后,
在天/日(月)域中,“L”表示一个月的最后一天
在天(星期)域中,“L”表示一个星期的最后一天
L只能出现在DayofWeek和DayofMonth域

在线生成cron表达式:https://cron.qqe2.com/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值