SpringBoot(跟着狂神老师学习的笔记)

SpringBoot
(愿岁月清净,抬头遇见皆是柔情)

微服务阶段

javase:OOP

mysql:持久化

html+css+js+jquery+框架:视图层,css 美观不够好

javaweb:独立开发MVC三层框架的网站了:原始

ssm:框架。简化了我们的开发流流程,配置较为复杂

war包:Tomcat运行

spring在简化:springboot:微服务架构!——jar包:内嵌Tomcat;

服务越来越多:springcloud;

1.配置文件

springboot使用一个全局的配置文件,配置文件名称是固定的

  • application.properties
    • 语法结构: key = value
  • application.yaml(可以直接给实体类赋值)
    • 语法结构:key: 空格 value

配置文件的作用:修改SpringBoot自动配置的默认值,因为SpringBoot在底层都给我们自动配置好了

2.实体类和配置文件绑定

  • yaml

@ConfigurationProperties(prefix = "person")

​ yaml中 name : xiao${random.uuid}

  • properties

    @PropertySource(value = "classpath:xiao.properties")

    @Value("${name}")

1.@ConfigurationProperties与@Value对比

@Value注释是核心容器功能,它不提供与类型安全配置属性相同的功能。下表总结了@ConfigurationProperties@Value支持的功能:

特征@ConfigurationProperties@Value
复杂型封装YesNo
JSR303数据校验@Validated @Email()YesNo
SpEL evaluationNoYes
松散绑定(一个用-,一个用驼峰命名)YesNo

如果为自己的组件定义一组配置键,我们建议您将它们分组到带有@ConfigurationProperties注释的POJO中。您还应该知道,由于@Value不支持宽松绑定,因此如果您需要使用环境变量来提供值,则它不是一个好的候选者。

2.常用的JSR303校验注解

Constraint详细信息作用类型
@Null被注释的元素必须为 null引用类型
@NotNull被注释的元素必须不为 null引用类型
@AssertTrue被注释的元素必须为 trueboolean
@AssertFalse被注释的元素必须为 falseboolean
@Min(value)被注释的元素必须是一个数字,其值必须大于等于指定的最小值byte、short、int、long及对应的包装类型以及BigDecimal、BigInteger
@Max(value)被注释的元素必须是一个数字,其值必须小于等于指定的最大值byte、short、int、long及对应的包装类型以及BigDecimal、BigInteger
@DecimalMin(value)被注释的元素必须是一个数字,其值必须大于等于指定的最小值byte、short、int、long及对应的包装类型以及BigDecimal、BigInteger、String
@DecimalMax(value)被注释的元素必须是一个数字,其值必须小于等于指定的最大值byte、short、int、long及对应的包装类型以及BigDecimal、BigInteger、String
@Size(max, min)被注释的元素的大小必须在指定的范围内String、Collection、Map和数组
@Digits (integer, fraction)被注释的元素必须是一个数字,其值必须在可接受的范围内byte、short、int、long及各自的包装类型以及BigDecimal、BigInteger、String
@Past被注释的元素必须是一个过去的日期java.util.Date,java.util.Calendar
@Future被注释的元素必须是一个将来的日期java.util.Date,java.util.Calendar
@Pattern(regex=)被注释的元素必须符合指定的正则表达式String
@Valid被注释的元素需要递归验证引用对象
以下是hibernate Validator新增的
@Email被注释的元素必须是电子邮箱地址String
@Length(min=下限, max=上限)被注释的字符串的大小必须在指定的范围内String
@NotEmpty被注释的元素的必须非空并且size大于0String、Collection、Map和数组
@NotBlank被注释的元素必须不为空且不能全部为’ '(空字符串)String
@Range(min=最小值, max=最大值)被注释的元素必须在合适的范围内byte、short、int、long及各自的包装类型以及BigDecimal、BigInteger、String

例如:

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

    @Email()
    private String name;
    private Integer age;
    private Boolean happy;
    private Date birth;
    private Map<String,Object> maps;
    private List<Object> lists;
    private Dog dog;

3.配置文件的位置

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UZF16PIA-1608988709335)(C:\Users\Administrator.SC-202001221340\AppData\Roaming\Typora\typora-user-images\image-20201220220753800.png)]

注:多环境配置时:spring.profiles.active=dev,也可以写在一个yaml中(多环境切换)

3.自动配置原理

1、SpringBoot启动会加载大量的自动配置类

2、我们看我们需要的功能有没有在SpringBoot默认写好的自动配置类当中;

3、我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件存在在其中,我们就不需要再手动配置了)

4、给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们只需要在配置文件中指定这些属性的值即可;

**xxxxAutoConfigurartion:自动配置类;**给容器中添加组件

xxxxProperties:封装配置文件中相关属性

注:debug: true可以查看自动配置类是否生效的日志输出

3.静态资源导入

1.在springboot中,我们可以使用以下方式处理静态资源

- webjars            localhost8080/webjars/
- public,static,/**,resources           localhost8080/

2.优先级:resources>static(默认)>public

4.首页首页和图标定制

1.首页定制

Spring Boot支持静态和模板化的欢迎页面。它首先在配置的静态内容位置中查找index.html文件。如果找不到,则会查找index模板。如果找到任何一个,它将自动用作应用程序的欢迎页面。

//在templates目录下的所有页面,只能通过controller跳转
//这个需要模板引擎  thymeleaf
@Controller
public class IndexController {
    @RequestMapping("/index")
    public String index(){
        return "index";
    }
}

2.图标定制

新版没有了。

Spring Boot在配置的静态内容位置和类路径的根(按此顺序)中查找favicon.ico。如果存在这样的文件,它将自动用作应用程序的favicon。

5.thymeleaf模板引擎

img

官网部分翻译:反正就是各种好

  • Thymeleaf是用来开发Web和独立环境项目的服务器端的Java模版引擎
  • Spring官方支持的服务的渲染模板中,并不包含jsp。而是Thymeleaf和Freemarker等,而Thymeleaf与SpringMVC的视图技术,及SpringBoot的自动化配置集成非常完美,几乎没有任何成本,你只用关注Thymeleaf的语法即可。

1.Thymeleaf的特点

  • 动静结合:Thymeleaf 在有网络和无网络的环境下皆可运行,即它可以让美工在浏览器查看页面的静态效果,也可以让程序员在服务器查看带数据的动态页面效果。这是由于它支持 html 原型,然后在 html 标签里增加额外的属性来达到模板+数据的展示方式。浏览器解释 html 时会忽略未定义的标签属性,所以 thymeleaf 的模板可以静态地运行;当有数据返回到页面时,Thymeleaf 标签会动态地替换掉静态内容,使页面动态显示。
  • 开箱即用:它提供标准和spring标准两种方言,可以直接套用模板实现JSTL、 OGNL表达式效果,避免每天套模板、该jstl、改标签的困扰。同时开发人员也可以扩展和创建自定义的方言。
  • 多方言支持:Thymeleaf 提供spring标准方言和一个与 SpringMVC 完美集成的可选模块,可以快速的实现表单绑定、属性编辑器、国际化等功能。
  • 与SpringBoot完美整合,SpringBoot提供了Thymeleaf的默认配置,并且为Thymeleaf设置了视图解析器,我们可以像以前操作jsp一样来操作Thymeleaf。代码几乎没有任何区别,就是在模板语法上有区别。

2.SpringBoot与之整合

pom.xml

<dependencies>
        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf-spring5</artifactId>
        </dependency>
        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-java8time</artifactId>
        </dependency>
</dependencies>

1.导入约束:

<html xmlns:th="http://www.thymeleaf.org">

自动配置

自动给我们默认分配了模版的前缀和后缀,我们只需要按部就班的将模版丢进去即可

img

我们写一个Controller:

img

将返回的对应的模版添加到默认的位置下:

img

编写配置文件application.yml:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DHyhiwio-1608988709357)()]

3.Thymeleaf常用语法

Thymeleaf的主要作用是把model中的数据渲染到html中,因此其语法主要是如何解析model中的数据。从以下方面来学习:

  • 变量、方法、条件判断、循环、运算 [ 逻辑运算、布尔运算、比较运算、条件运算 ]
  • 其它
表达式

preview

1、简单表达式语法
  • #{...} : Message 表达式
<p th:utext="#{home.welcome(${session.user.name})}"> Welcome to our grocery store, Sebastian Pepper!</p>
<p th:utext="#{${welcomeMsgKey}(${session.user.name})}"> Welcome to our grocery store, Sebastian Pepper!</p> 12
  • ${}:变量表达式
ongl标准语法,方法也可以被调用1
  • *{} :选择变量表达式
<div th:object="${session.user}">
    <p>Name: <span th:text="*{firstName}">Sebastian</span>.</p>
    <p>Surname: <span th:text="*{lastName}">Pepper</span>.</p> 
    <p>Nationality: <span th:text={nationality}">Saturn</span>.</p>
</div> 
等价于
<div>
    <p>Name: <span th:text="${session.user.firstName}">Sebastian</span>.</p> 
    <p>Surname: <span th:text="${session.user.lastName}">Pepper</span>.</p> 
    <p>Nationality: <span th:text="${session.user.nationality}">Saturn</span>.</p>
</div>
当然了,这两者可以混合使用
还有一种方式
<div>
    <p>Name: <span th:text="*{session.user.name}">Sebastian</span>.</p> 
    <p>Surname: <span th:text="*{session.user.surname}">Pepper</span>.</p> 
    <p>Nationality: <span th:text="*{session.user.nationality}">Saturn</span>.</p>
</div>  123456789101112131415161718
  • @{}: 链接 URL 表达式
<!-- Will produce 'http://localhost:8080/gtvg/order/details?orderId=3' (plus rewriting) --> <a href="details.html"

th:href="@{http://localhost:8080/gtvg/order/details(orderId=${o.id})}">view</a> <!-- Will produce '/gtvg/order/details?orderId=3' (plus rewriting) -->

<a href="details.html" th:href="@{/order/details(orderId=${o.id})}">view</a>

<!-- Will produce '/gtvg/order/3/details' (plus rewriting) -->

<a href="details.html" th:href="@{/order/{orderId}/details(orderId=${o.id})}">view</a>123456789
2、变量
分类示例
文本‘one text’ , ‘Another one!’ ,…
数字0 , 34 , 3.0 , 12.3 ,…
真假true , false
文字符号one , sometext , main ,…
3、字符连接
分类示例
+‘The name is ‘+${name}
|…||The name is ${name}|
4、 算数运算
语法示例
+, -, *, /, %二元运算符
-减号(一元运算符)
5、 真假运算
分类示例
and , or二元运算符
! , not否定(一元运算符)
6、比较运算
分类示例
>, <, >=, <= (gt, lt, ge, le)比较
== , != ( eq , ne )平等
7、 条件运算
分类示例
if-then(if) ? (then)
if-then-else(if) ? (then) : (else)
Default(value) ?: (defaultvalue)

综合示例:

'User is of type ' + (${user.isAdmin()} ? 'Administrator' : (${user.type} ?: 'Unknown'))1
4、表达式中使用内置对象
#dates :

utility methods for java.util.Date objects: formatting, component extraction, etc. #calendars : analogous to #dates , but for java.util.Calendar objects.

#numbers :
utility methods for formatting numeric objects.

#strings : 
utility methods for String objects: contains, startsWith, prepending/appending, etc. #objects : utility methods for objects in general.

#bools : 
utility methods for boolean evaluation. #arrays : utility methods for arrays.

#lists :
utility methods for lists.

#sets : 
utility methods for sets.

#maps : 
utility methods for maps.

#aggregates : 
utility methods for creating aggregates on arrays or collections.

#messages : 
utility methods for obtaining externalized messages inside variables expressions, in the same way as they would be obtained using #{...} syntax.

#ids : 
utility methods for dealing with id attributes that might be repeated (for example, as a result of an iteration).123456789101112131415161718192021222324252627282930
5、预处理
__${expression}__1
6、循环
<tr th:each="prod : ${prods}">
    <td th:text="${prod.name}">Onions</td>
    <td th:text="${prod.price}">2.41</td>
    <td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
</tr>

迭代器的状态
index: 当前的索引,从0开始
count: 当前的索引,从1开始
size:总数
current:
even/odd:
first
last
<table> 
    <tr>
        <th>NAME</th>
        <th>PRICE</th>
        <th>IN STOCK</th>
    </tr>
    <tr th:each="prod,iterStat : ${prods}" th:class="${iterStat.odd}? 'odd'">
    <td th:text="${prod.name}">Onions</td>
    <td th:text="${prod.price}">2.41</td>
    <td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
  </tr>
</table>1234567891011121314151617181920212223242526
7、判断
if

<a href="comments.html" th:href="@{/product/comments(prodId=${prod.id})}" th:if="${not #lists.isEmpty(prod.comments)}">view</a>

unless

<a href="comments.html" th:href="@{/comments(prodId=${prod.id})}" th:unless="${#lists.isEmpty(prod.comments)}">view</a>

switch

<div th:switch="${user.role}">
    <p th:case="'admin'">User is an administrator</p> <p th:case="#{roles.manager}">User is a manager</p>
</div>

<div th:switch="${user.role}">
    <p th:case="'admin'">User is an administrator</p> <p th:case="#{roles.manager}">User is a manager</p> <p th:case="*">User is some other thing</p>
</div>123456789101112131415161718
8、模板布局
th:fragment

示例

templates/footer.html

<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-4.dtd"> 
<html xmlns="http://www.w3.org/1999/xhtml"
    <body>
         <div th:fragment="copy">
            © 2011 The Good Thymes Virtual Grocery
         </div>
    </body>
</html>

templates/index.html中使用

    <body> ...
        <div th:include="footer :: copy"></div> 
    </body>

或者
    ...
    <div id="copy-section">
        © 2011 The Good Thymes Virtual Grocery 
    </div>
    ...

使用

    <body> ...
        <div th:include="footer :: #copy-section"></div>
    </body>


th:include 和 th:replace 区别

th:include 加入代码

th:replace 替换代码

模板传参:参数传递顺序不强制

    定义

<div th:fragment="frag (onevar,twovar)">
    <p th:text="${onevar} + ' - ' + ${twovar}">...</p>
</div>

    使用

<div th:include="::frag (${value1},${value2})">...</div>

<div th:include="::frag (onevar=${value1},twovar=${value2})">...</div> 
等价于 <div th:include="::frag" th:with="onevar=${value1},twovar=${value2}">)12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455
9、移除标签 th:remove

取值范围

  • all:移除所有
  • body:不移除自己,但移除他的子标签
  • tag: 只移除自己,不移除他的子标签
  • all-but-first:移除所有内容除第一个外
  • none:啥都不做
10、执行顺序

这里写图片描述

11、thymeleaf注释语法
  • html 看不到,并且 thymeleaf 不会执行
<!--/* This code will be removed at thymeleaf parsing time! */-->1
  • and 未运行可以在 html 中看到,运行后就消失
<!--/*-->
   <div>you can see me only before thymeleaf processes me! </div>
<!--*/-->123
  • 运行后才会看到
<span>hello!</span>
<!--/*/
<div th:text="${true}">...</div>
/*/-->
<span>goodbye!</span>12345
12、th:block 的使用
<table>
    <th:block th:each="user : ${users}">
    <tr>
        <td th:text="${user.login}">...</td> <td th:text="${user.name}">...</td>
    </tr>
    <tr>
        <td colspan="2" th:text="${user.address}">...</td> 
    </tr>
    </th:block>
</table>12345678910

推荐下面写法(编译前看不见)

<table>
    <tr>
        <td th:text="${user.login}">...</td>
        <td th:text="${user.name}">...</td> </tr>
        <tr>
        <td colspan="2" th:text="${user.address}">...</td>
    </tr>
    <!--/*/ </th:block> /*/--> 
</table>123456789
13、文本内联th:inline

th:inline 可以等于 text , javascript(dart) , none

  • text: [[…]]
<p th:inline="text">Hello, [[#{test}]]</p>1
  • javascript: /[[…]]/
<script th:inline="javascript">
    var username = /*[[
        #{test}
    ]]*/;
    var name = /*[[
        ${param.name[0]}+${execInfo.templateName}+'-'+${#dates.createNow()}+'-'+${#locale}
    ]]*/;
</script>12345678
<script th:inline="javascript">

/*<![CDATA[*/

    var username = [[#{test}]];

    var name = [[${param.name[0]}+${execInfo.templateName}+'-'+${#dates.createNow()}+'-'+${#locale}]];

/*]]>*/

</script>1234567891011
  • adding code: /* [+…+]*/
var x = 23;
/*[+
var msg = 'Hello, ' + [[${session.user.name}]]; +]*/
var f = function() {
...12345
  • removind code: /[- / and /* -]*/
var x = 23;
/*[- */
var msg = 'This is a non-working template'; /* -]*/
var f = function() {
...12345
14、验证模板的正确性
<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-4.dtd">
<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-transitional-thymeleaf-4.dtd">
<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-frameset-thymeleaf-4.dtd">
<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml11-thymeleaf-4.dtd">1234
15、特殊用法展示
<td th:text="${#aggregates.sum(o.orderLines.{purchasePrice * amount})}">23.32</td>1

以上表示List orderLines的所有订单的总价

附件A: 基础对象

ctx:对应org.thymeleaf.spring[3|4].context.SpringWebContext

/*
* ======================================================================
* See javadoc API for class org.thymeleaf.context.IContext
* ====================================================================== */
${#ctx.locale} ${#ctx.variables}
/*
* ======================================================================
* See javadoc API for class org.thymeleaf.context.IWebContext
* ====================================================================== */
${#ctx.applicationAttributes} 
${#ctx.httpServletRequest} 
${#ctx.httpServletResponse} 
${#ctx.httpSession} 
${#ctx.requestAttributes} 
${#ctx.requestParameters} 
${#ctx.servletContext} 
${#ctx.sessionAttributes}1234567891011121314151617

locale: 对应java.util.Locale

vars: 对应 org.thymeleaf.context.VariablesMap

/*
* ====================================================================== * See javadoc API for class org.thymeleaf.context.VariablesMap
* ====================================================================== */
${#vars.get('foo')} 
${#vars.containsKey('foo')} 
${#vars.size()}
...1234567

param

${param.foo} 是一个 String[] 如果要获取需要${param.foo[0]}
/*
* ============================================================================ * See javadoc API for class org.thymeleaf.context.WebRequestParamsVariablesMap * ============================================================================ */
${param.foo} // Retrieves a String[] with the values of request parameter 'foo' 
${param.size()}
${param.isEmpty()}
${param.containsKey('foo')}
...1234567

session

application

httpServletRequest

themes : as JSP tag spring:theme

Spring Beans 的访问

<div th:text="${@authService.getUserName()}">...</div>1
附件B:工具对象
  • dates See javadoc API for class org.thymeleaf.expression.Dates
  • calendars See javadoc API for class org.thymeleaf.expression.Calendars
  • numbers See javadoc API for class org.thymeleaf.expression.Numbers
  • strings See javadoc API for class org.thymeleaf.expression.Strings
  • objects See javadoc API for class org.thymeleaf.expression.Objects
  • bools See javadoc API for class org.thymeleaf.expression.Bools
  • arrays See javadoc API for class org.thymeleaf.expression.Arrays
  • lists See javadoc API for class org.thymeleaf.expression.Lists
  • sets See javadoc API for class org.thymeleaf.expression.Sets
  • maps See javadoc API for class org.thymeleaf.expression.Maps
  • aggregates See javadoc API for class org.thymeleaf.expression.Aggregates
  • messages See javadoc API for class org.thymeleaf.expression.Messages
  • ids See javadoc API for class org.thymeleaf.expression.Ids
附件C:DOM 选择器语法

DOM选择器借语法功能从XPath,CSS和jQuery,为了提供一个功能强大且易于使用的方法来指定模板片段

<div th:include="mytemplate :: [//div[@class='content']]">...</div>1
<div th:include="mytemplate :: [div.content]">...</div>

4.JS模板

模板引擎不仅可以渲染html,也可以对JS中的进行预处理。而且为了在纯静态环境下可以运行,其Thymeleaf代码可以被注释起来:

[复制代码](javascript:void(0)😉

<script th:inline="javascript">
    const user = /*[[${user}]]*/ {};
    const age = /*[[${user.age}]]*/ 20;
    console.log(user);
    console.log(age)
</script>

[复制代码](javascript:void(0)😉

  • 在script标签中通过th:inline="javascript"来声明这是要特殊处理的js脚本

  • 语法结构:

    const user = /*[[Thymeleaf表达式]]*/ "静态环境下的默认值";
    

    因为Thymeleaf被注释起来,因此即便是静态环境下, js代码也不会报错,而是采用表达式后面跟着的默认值。且User对象会被直接处理为json格式。

6.MVC配置原理

7.项目制作

1.首页配置:

注意点,所有页面的静态资源都需要使用thymeleaf接管;@{}

2.页面国际化:

1.注意setting都要是UTF-8

2.我们需要配i18n

3.我们如果需要在项目中进行按钮自动切换,我们需要自定义一个组件LocaleResolve

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zQUoTFbr-1608988709368)(C:\Users\Administrator.SC-202001221340\AppData\Roaming\Typora\typora-user-images\image-20201222122039913.png)]

//MyLocaleResolver
package com.xiao.springboot2.config;

import org.springframework.web.servlet.LocaleResolver;
import org.thymeleaf.util.StringUtils;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;

public class MyLocaleResolver implements LocaleResolver {
    //解析请求
    @Override
    public Locale resolveLocale(HttpServletRequest request ) {
        //获取请求中的语言参数
        String language = request.getParameter("l");
        System.out.println("debug===>"+language);
        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) {

    }
}

4.记得将自己写的组件配置到spring容器的Bean中 @Bean

//自定义的国际化组件就生效了
@Bean
public LocaleResolver localeResolver(){
    return new MyLocaleResolver();
}

5.#{}

<button class="btn btn-lg btn-primary btn-block" type="submit" >[[#{login.btn}]]</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>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gvE4z8gW-1608988709369)(C:\Users\Administrator.SC-202001221340\AppData\Roaming\Typora\typora-user-images\image-20201222112813871.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CaePgNBR-1608988709370)(C:\Users\Administrator.SC-202001221340\AppData\Roaming\Typora\typora-user-images\image-20201222113632075.png)]

3.登录功能实现

1.显示用户名或密码错误的消息

<!--          如果msg为空,则不显示消息-->
         <p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>

2.重定向

if(!StringUtils.isEmpty(username)&&"123456".equals(password)) {
    return "redirect:/main.html";

3.LoginController

package com.xiao.springboot2.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.thymeleaf.util.StringUtils;

@Controller
public class LoginController {
    @RequestMapping("/user/login")

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

    }
}

4.拦截器

1.写拦截器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fG2WS5kl-1608988709372)(C:\Users\Administrator.SC-202001221340\AppData\Roaming\Typora\typora-user-images\image-20201222135453635.png)]

package com.xiao.springboot2.config;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class LoginHandlerInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //登陆成功之后,应该有用户的Session;
        Object loginUser = request.getSession().getAttribute("loginUser");
        if(loginUser==null){
            request.setAttribute("msg","没有权限,请先登录");
            request.getRequestDispatcher("/index.html").forward(request,response);
            return false;
        }
        else {
            return true;
        }
    }

}

2.注入到容器中

@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(new LoginHandlerInterceptor()).
            addPathPatterns("/**").excludePathPatterns("/index.html","/user/login","/css/*","/js/*","/img/*");
}

3.把名字改成用户的

<a class="navbar-brand col-sm-3 col-md-2 mr-0" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">[[${session.loginUser}]]</a>

5.员工列表展示

1.提取公共页面

1.th:fragment="sidebar"
2.th:replace="~{commons/commons::sidebar}"
3.如果要传递参数,可以直接使用()传参,接收判断即可

2.列表循环展示

6.添加员工

1.按钮提交

2.跳转到添加页面

3.添加员工成功

4.返回首页

7.CRUD

8.404

9.注销

10.总结

1.模板:

bootstrap、X-admin(推荐的后端模板)

2.前端界面:

至少自己能够通过前端框架,组合一个网站页面

3.让这个网站运行起来

8.整合JDBC使用(重点)

增删改查

package com.xiao.Controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.Map;

@RestController
public class JDBCController {
    @Autowired
    JdbcTemplate jdbcTemplate;
    //查询数据库的所有信息
    //没有实体类,数据库中的东西怎么获取  Map
    @GetMapping
    public List<Map<String,Object>> userList(){
        String sql="select * from books";
        List<Map<String,Object>> list_maps = jdbcTemplate.queryForList(sql);
        return list_maps;
    }
    
    
    //增加
    @GetMapping("/addUser")
    public String addUser(){
        String sql = "insert into ssmbuild.books(bookID,bookName,bookCounts,detail) values(100,'1','1','78')" ;
        jdbcTemplate.update(sql);
        return "update-ok";
    }
    
    
    //更新
    @GetMapping("/updateUser/{id}")
    public String updateUser(@PathVariable("id") int id){
        String sql = "update ssmbuild.books set bookName=? ,bookCounts=? where bookID="+id;
        //封装
        Object[] objects = new Object[2];
        objects[0]="xia";
        objects[1]=4;
        jdbcTemplate.update(sql,objects);
        return "update-ok";
    }
    
    //删除
    @GetMapping("/deleteUser/{id}")
    public String deleteUser(@PathVariable("id") int id){
        String sql = "delete from ssmbuild.books where bookID=?";
        jdbcTemplate.update(sql,id);
        return "delete-ok";
    }
}

9.整合Druid数据源

1.Druid是什么?

Druid是Java语言中最好的数据库连接池。Druid能够提供强大的监控和扩展功能。

2.怎么获取Druid的源码

Druid是一个开源项目,源码托管在github上,源代码仓库地址是 https://github.com/alibaba/druid 。同时每次Druid发布正式版本和快照的时候,都会把源码打包,你可以从上面的下载地址中找到相关版本的源码

3.导入依赖

Druid 0.1.18之后版本都发布到maven中央仓库中,所以你只需要在项目的pom.xml中加上dependency就可以了。例如:

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>${druid-version}</version>
    </dependency>

4.druid的yml专有配置(一部分)

#druid专有配置
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validation-query: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
#    配置监控统计拦截的filters 去掉后监控界面sql无法统计,‘wall’用于防火墙
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMills=500

注意:如果要用log4j日志功能,需要导入log4j的依赖

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

5.DruidConfig类

package com.xiao.Config;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class DruidConfig {
    @ConfigurationProperties(prefix = "spring.datasource")
    @Bean
    public DataSource druidDataSource(){
        return new DruidDataSource();
    }

    //后台监控:=web.xml
    @Bean
    public ServletRegistrationBean statViewServlet(){
        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","");

        //禁止谁能访问
        initParameters.put("kuangshen","192.168.11.123");


        bean.setInitParameters(initParameters);//设置初始化参数
        return bean;
    }
    //filter过滤
    @Bean
    public FilterRegistrationBean webStatFilter(){
        FilterRegistrationBean bean = new FilterRegistrationBean();
        bean.setFilter(new WebStatFilter());
        //可以过滤哪些请求
        Map<String,String> initParameters = new HashMap<>();
        initParameters.put("exclusions","*.js,*.css,/druid/*");
        bean.setInitParameters();
        
        
        return bean;
    }
}

10.Mybatis整合

  1. 导入 MyBatis 所需要的依赖

    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.1.3</version>
    </dependency>
    12345
    
  2. 配置数据库连接信息(不变)

    spring:
      datasource:
        username: root
        password: 123456
        #?serverTimezone=UTC解决时区的报错
        url: jdbc:mysql://localhost:3306/springboot?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
        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
    123456789101112131415161718192021222324252627282930
    
  3. 测试数据库是否连接成功!

  4. 创建实体类,导入 Lombok!

    User.java

    package nuc.ss.pojo;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class User {
        private int id;
        private String name;
        private String pwd;
    }
    1234567891011121314
    
  5. 创建mapper目录以及对应的 Mapper 接口

    UserMapper.java

    package nuc.ss.mapper;
    
    import nuc.ss.pojo.User;
    import org.apache.ibatis.annotations.Mapper;
    import org.springframework.stereotype.Repository;
    
    import java.util.List;
    
    // 这个注解表示了这是一个 mybatis 的 mapper 类
    @Mapper
    @Repository
    public interface UserMapper {
    
        List<User> queryUserList();
    
        User queryUserById(int id);
    
        int addUser(User user);
    
        int updateUser(User user);
    
        int deleteUser(int id);
    }
    1234567891011121314151617181920212223
    
  6. 对应的Mapper映射文件

    UserMapper.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=绑定一个对应的Dao/Mapper接口-->
    <mapper namespace="nuc.ss.mapper.UserMapper">
        
        <select id="queryUserList" resultType="User">
            select * from mybatis.user;
        </select>
    
        <select id="queryUserById" resultType="User">
            select * from mybatis.user 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 from mybatis.user where id = #{id}
        </delete>
    </mapper>
    123456789101112131415161718192021222324252627
    
  7. maven配置资源过滤问题

    <resources>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.xml</include>
            </includes>
            <filtering>true</filtering>
        </resource>
    </resources>
    123456789
    
  8. 编写部门的 UserController 进行测试!

    @RestController
    public class UserController {
        @Autowired
        private UserMapper userMapper;
    
        @GetMapping("/queryUserList")
        public List<User> queryUserList() {
            List<User> userList = userMapper.queryUserList();
    
            for (User user : userList) {
                System.out.println(user);
            }
    
            return userList;
        }
        
         //添加一个用户
        @GetMapping("/addUser")
        public String addUser() {
            userMapper.addUser(new User(7,"阿毛","123456"));
            return "ok";
        }
    
        //修改一个用户
        @GetMapping("/updateUser")
        public String updateUser() {
            userMapper.updateUser(new User(7,"阿毛","123456"));
            return "ok";
        }
    
        @GetMapping("/deleteUser")
        public String deleteUser() {
            userMapper.deleteUser(7);
    
            return "ok";
        }
    }
    

总结:

1.导入包

2.配置文件

3.mybatis配置

4.编写sql

5.业务层调用dao层

6.controller调用servlet

11.SpringSecurity(安全)

漏洞:隐私泄露

架构一旦固定不容易添加

shiro、SpringSecurity很像,除了类不一样,名字不一样

认证,授权(vip1,vip2,vip3)

  • 功能权限
  • 访问权限
  • 菜单权限
  • … 拦截器,过滤器:大量的原生代码~冗余

实验环境搭建

1、新建一个初始的springboot项目web模块,thymeleaf模块

2、导入静态资源

welcome.html
|views
|level1
        1.html
        2.html
        3.html
|level2
        1.html
        2.html
        3.html
|level3
        1.html
        2.html
        3.html
Login.html

3、controller跳转!

package com.xiao.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class RouterController {
    @RequestMapping({"/","/index"})
    public String index(){
        return "index";
    }
    @RequestMapping("/toLogin")
    public String toLogin(){
        return "views/login";
    }
    @RequestMapping("/level1/{id}")
    public String level1(@PathVariable("id") int id){
        return "views/level1/"+id;
    }
    @RequestMapping("/level2/{id}")
    public String level2(@PathVariable("id") int id){
        return "views/level2/"+id;
    }
    @RequestMapping("/level3/{id}")
    public String level3(@PathVariable("id") int id){
        return "views/level3/"+id;
    }

}

4、测试实验环境是否OK!

认识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 模块

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

2、编写 Spring Security 配置类

参考官网:https://spring.io/projects/spring-security

查看我们自己项目中的版本,找到对应的帮助文档:

https://docs.spring.io/spring-security/site/docs/5.3.0.RELEASE/reference/html5 #servlet-applications 8.16.4

img

3、编写基础配置类

package com.kuang.config;

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;

@EnableWebSecurity // 开启WebSecurity模式
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {

    }
}

4、定制请求的授权规则

@Override
protected void configure(HttpSecurity http) throws Exception {
    // 定制请求的授权规则
    // 首页所有人可以访问
    http.authorizeRequests().antMatchers("/").permitAll()
    .antMatchers("/level1/**").hasRole("vip1")
    .antMatchers("/level2/**").hasRole("vip2")
   .antMatchers("/level3/**").hasRole("vip3");
}

5、测试一下:发现除了首页都进不去了!因为我们目前没有登录的角色,因为请求需要登录的角色拥有对应的权限才可以!

6、在configure()方法中加入以下配置,开启自动配置的登录功能!

// 开启自动配置的登录功能
// /login 请求来到登录页
// /login?error 重定向到这里表示登录失败
http.formLogin();

7、测试一下:发现,没有权限的时候,会跳转到登录的页面!

img

8、查看刚才登录页的注释信息;

我们可以定义认证规则,重写configure(AuthenticationManagerBuilder auth)方法

//定义认证规则
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    //在内存中定义,也可以在jdbc中去拿....
    auth.inMemoryAuthentication()
            .withUser("kuangshen").password("123456").roles("vip2","vip3")
            .and()
            .withUser("root").password("123456").roles("vip1","vip2","vip3")
            .and()
            .withUser("guest").password("123456").roles("vip1","vip2");
}

9、测试,我们可以使用这些账号登录进行测试!发现会报错!

There is no PasswordEncoder mapped for the id “null”

img

10、原因,我们要将前端传过来的密码进行某种方式加密,否则就无法登录,修改代码

//定义认证规则
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    //在内存中定义,也可以在jdbc中去拿....
    //Spring security 5.0中新增了多种加密方式,也改变了密码的格式。
    //要想我们的项目还能够正常登陆,需要修改一下configure中的代码。我们要将前端传过来的密码进行某种方式加密
   //spring security 官方推荐的是使用bcrypt加密方式。
    auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
           .withUser("kuangshen").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3")
           .and()
           .withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3")
          .and()
         .withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2");

}

11、测试,发现,登录成功,并且每个角色只能访问自己认证下的规则!搞定

权限控制和注销

1、开启自动配置的注销的功能

//定制请求的授权规则
@Override

protected void configure(HttpSecurity http) throws Exception {
    //....
    //开启自动配置的注销的功能
      // /logout 注销请求

    http.logout();

}

2、我们在前端,增加一个注销的按钮,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、修改我们的 前端页面

  1. 导入命名空间

    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、开启记住我功能

//定制请求的授权规则
@Overrid
protected void configure(HttpSecurity http) throws Exception {
    //。。。。。。。。。。。
    //记住我
    http.rememberMe();
}

2、我们再次启动项目测试一下,发现登录页多了一个记住我功能,我们登录之后关闭 浏览器,然后重新打开浏览器访问,发现用户依旧存在!

思考:如何实现的呢?其实非常简单

我们可以查看浏览器的cookie

img

3、我们点击注销的时候,可以发现,spring security 帮我们自动删除了这个 cookie

img4、结论:登录成功后,将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()源码中的注释上有写明:

img

<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("username")
    .passwordParameter("password")
    .loginPage("/toLogin")
    .loginProcessingUrl("/login"); // 登陆表单提交请求

5、在登录页增加记住我的多选框

<input type="checkbox" name="remember"> 记住我

6、后端验证处理!

//定制记住我的参数!
http.rememberMe().rememberMeParameter("remember");

7、测试,OK

完整代码

package com.kuang.config;
 
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
 
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
 
    //定制请求的授权规则
    @Override
    protected void configure(HttpSecurity http) throws Exception {
 
        http.authorizeRequests().antMatchers("/").permitAll()
        .antMatchers("/level1/**").hasRole("vip1")
        .antMatchers("/level2/**").hasRole("vip2")
        .antMatchers("/level3/**").hasRole("vip3");
 
 
        //开启自动配置的登录功能:如果没有权限,就会跳转到登录页面!
            // /login 请求来到登录页
            // /login?error 重定向到这里表示登录失败
        http.formLogin()
            .usernameParameter("username")
            .passwordParameter("password")
            .loginPage("/toLogin")
            .loginProcessingUrl("/login"); // 登陆表单提交请求
 
        //开启自动配置的注销的功能
            // /logout 注销请求
            // .logoutSuccessUrl("/"); 注销成功来到首页
 
        http.csrf().disable();//关闭csrf功能:跨站请求伪造,默认只能通过post方式提交logout请求
        http.logout().logoutSuccessUrl("/");
 
        //记住我
        http.rememberMe().rememberMeParameter("remember");
    }
 
    //定义认证规则
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //在内存中定义,也可以在jdbc中去拿....
        //Spring security 5.0中新增了多种加密方式,也改变了密码的格式。
        //要想我们的项目还能够正常登陆,需要修改一下configure中的代码。我们要将前端传过来的密码进行某种方式加密
        //spring security 官方推荐的是使用bcrypt加密方式。
 
        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
                .withUser("kuangshen").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3")
                .and()
                .withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3")
                .and()
                .withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2");
    }
}

12.shiro

什么是Shiro?

Apache Shiro是一个Java 的安全(权限)框架。
Shiro可以非常容易的开发出足够好的应用,其不仅可以用在JavaSE环境,也可以用在JavaEE环境。
Shiro可以完成,认证,授权,加密,会话管理,Web集成,缓存等.
●下载地址: http://shiro.apache.org/

在这里插入图片描述

有哪些功能

在这里插入图片描述
Authentication: 身份认证、登录,验证用户是不是拥有相应的身份;
Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限,即判断用户能否进行什么操作,如:验证某个用户是否拥有某个角色,或者细粒度的验证某个用户对某个资源是否具有某个权限!
Session Manager: 会话管理,即用户登录后就是第-次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通的JavaSE环境,也可以是Web环境;
Cryptography: 加密,保护数据的安全性,如密码加密存储到数据库中,而不是明文存储;
Web Support: Web支持,可以非常容易的集成到Web环境;
Caching: 缓存,比如用户登录后,其用户信息,拥有的角色、权限不必每次去查,这样可以提高效率
Concurrency: Shiro支持多线程应用的并发验证,即,如在-个线程中开启另-一个线程,能把权限自动的传
播过去
Testing:提供测试支持;
RunAs:允许一个用户假装为另-一个用户(如果他们允许)的身份进行访问;
Remember Me:记住我,这个是非常常见的功能,即一-次登录后, 下次再来的话不用登录了

Shiro架构(外部)

从外部来看Shiro,即从应用程序角度来观察如何使用shiro完成工作:
在这里插入图片描述

subject: 应用代码直接交互的对象是Subject, 也就是说Shiro的对外API核心就是Subject, Subject代表了当前的用户,这个用户不-定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等,与Subject的所有交互都会委托给SecurityManager; Subject其实是一一个门面, SecurityManageer 才是
实际的执行者
SecurityManager: 安全管理器,即所有与安全有关的操作都会与SercurityManager交互, 并且它管理着所有的Subject,可以看出它是Shiro的核心,它负责与Shiro的其他组件进行交互,它相当于SpringMVCDispatcherServlet的角色
Realm: ShiroRealm获取安全数据 (如用户,角色,权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较,来确定用户的身份是否合法;也需要从Realm得到用户相应的角色、权限,进行验证用户的操作是否能够进行,可以把RealmDataSource;

Shiro架构(内部)

在这里插入图片描述

Subject: 任何可以与应用交互的用户;
Security Manager:相当于SpringMVC中的DispatcherSerlet; 是Shiro的心脏, 所有具体的交互都通过Security Manager进行控制,它管理者所有的Subject, 且负责进行认证,授权,会话,及缓存的管理。
Authenticator:负责Subject认证, 是-一个扩展点,可以自定义实现;可以使用认证策略(Authentication Strategy),即什么情况下算用户认证通过了;
Authorizer:授权器,即访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中
的那些功能;
Realm: 可以有-一个或者多个的realm, 可以认为是安全实体数据源,即用于获取安全实体的,可以用JDBC实现,也可以是内存实现等等,由用户提供;所以- -般在应用中都需要实现自己的realm
SessionManager:管理Session生 命周期的组件,而Shiro并不仅仅可以用在Web环境,也可以用在普通的JavaSE环境中
CacheManager: 缓存控制器,来管理如用户,角色,权限等缓存的;因为这些数据基本上很少改变,放到缓存中后可以提高访问的性能;
Cryptography:密码模块,Shiro 提高了一些常见的加密组件用于密码加密, 解密等

Shiro快速开始

准备工作

GitHub资源

  • 创建一个普通maven项目springboot-08-shiro,然后删除src目录,这样的话就可以在这个项目里新建很多model.
  • springboot-08-shiro里新建model hello_shiro
  • 找到文件在这里插入图片描述
    pom.xml中复制
    <dependencies>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
        </dependency>

        <!-- configure logging -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <scope>runtime</scope>
        </dependency>
    </dependencies>
1234567891011121314151617181920212223

更改细节

    <dependencies>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.4.1</version>
        </dependency>

        <!-- configure logging -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
            <version>1.7.21</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.21</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
            <scope>runtime</scope>
        </dependency>
    </dependencies>
123456789101112131415161718192021222324252627

复制在这里插入图片描述
log4j.properties

log4j.rootLogger=INFO, stdout

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n

# General Apache libraries
log4j.logger.org.apache=WARN

# Spring
log4j.logger.org.springframework=WARN

# Default Shiro logging
log4j.logger.org.apache.shiro=INFO

# Disable verbose logging
log4j.logger.org.apache.shiro.util.ThreadContext=WARN
log4j.logger.org.apache.shiro.cache.ehcache.EhCache=WARN
123456789101112131415161718
shiro.ini
[users]
# user 'root' with password 'secret' and the 'admin' role
root = secret, admin
# user 'guest' with the password 'guest' and the 'guest' role
guest = guest, guest
# user 'presidentskroob' with password '12345' ("That's the same combination on
# my luggage!!!" ;)), and role 'president'
presidentskroob = 12345, president
# user 'darkhelmet' with password 'ludicrousspeed' and roles 'darklord' and 'schwartz'
darkhelmet = ludicrousspeed, darklord, schwartz
# user 'lonestarr' with password 'vespa' and roles 'goodguy' and 'schwartz'
lonestarr = vespa, goodguy, schwartz

# -----------------------------------------------------------------------------
# Roles with assigned permissions
# 
# Each line conforms to the format defined in the
# org.apache.shiro.realm.text.TextConfigurationRealm#setRoleDefinitions JavaDoc
# -----------------------------------------------------------------------------
[roles]
# 'admin' role has all permissions, indicated by the wildcard '*'
admin = *
# The 'schwartz' role can do anything (*) with any lightsaber:
schwartz = lightsaber:*
# The 'goodguy' role is allowed to 'drive' (action) the winnebago (type) with
# license plate 'eagle5' (instance specific id)
goodguy = winnebago:drive:eagle5
123456789101112131415161718192021222324252627

然后在这里插入图片描述
Quickstart.java

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.ini.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.lang.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * Simple Quickstart application showing how to use Shiro's API.
 *
 * @since 0.9 RC2
 */
public class Quickstart {

    private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);


    public static void main(String[] args) {

        // The easiest way to create a Shiro SecurityManager with configured
        // realms, users, roles and permissions is to use the simple INI config.
        // We'll do that by using a factory that can ingest a .ini file and
        // return a SecurityManager instance:

        // Use the shiro.ini file at the root of the classpath
        // (file: and url: prefixes load from files and urls respectively):
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        SecurityManager securityManager = factory.getInstance();

        // for this simple example quickstart, make the SecurityManager
        // accessible as a JVM singleton.  Most applications wouldn't do this
        // and instead rely on their container configuration or web.xml for
        // webapps.  That is outside the scope of this simple quickstart, so
        // we'll just do the bare minimum so you can continue to get a feel
        // for things.
        SecurityUtils.setSecurityManager(securityManager);

        // Now that a simple Shiro environment is set up, let's see what you can do:

        // get the currently executing user:
        Subject currentUser = SecurityUtils.getSubject();

        // Do some stuff with a Session (no need for a web or EJB container!!!)
        Session session = currentUser.getSession();
        session.setAttribute("someKey", "aValue");
        String value = (String) session.getAttribute("someKey");
        if (value.equals("aValue")) {
            log.info("Retrieved the correct value! [" + value + "]");
        }

        // let's login the current user so we can check against roles and permissions:
        if (!currentUser.isAuthenticated()) {
            UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
            token.setRememberMe(true);
            try {
                currentUser.login(token);
            } catch (UnknownAccountException uae) {
                log.info("There is no user with username of " + token.getPrincipal());
            } catch (IncorrectCredentialsException ice) {
                log.info("Password for account " + token.getPrincipal() + " was incorrect!");
            } catch (LockedAccountException lae) {
                log.info("The account for username " + token.getPrincipal() + " is locked.  " +
                        "Please contact your administrator to unlock it.");
            }
            // ... catch more exceptions here (maybe custom ones specific to your application?
            catch (AuthenticationException ae) {
                //unexpected condition?  error?
            }
        }

        //say who they are:
        //print their identifying principal (in this case, a username):
        log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");

        //test a role:
        if (currentUser.hasRole("schwartz")) {
            log.info("May the Schwartz be with you!");
        } else {
            log.info("Hello, mere mortal.");
        }

        //test a typed permission (not instance-level)
        if (currentUser.isPermitted("lightsaber:wield")) {
            log.info("You may use a lightsaber ring.  Use it wisely.");
        } else {
            log.info("Sorry, lightsaber rings are for schwartz masters only.");
        }

        //a (very powerful) Instance Level permission:
        if (currentUser.isPermitted("winnebago:drive:eagle5")) {
            log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'.  " +
                    "Here are the keys - have fun!");
        } else {
            log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
        }

        //all done - log out!
        currentUser.logout();

        System.exit(0);
    }
}
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106

启动

2020-11-04 09:13:59,275 INFO [org.apache.shiro.session.mgt.AbstractValidatingSessionManager] - Enabling session validation scheduler... 
2020-11-04 09:14:00,629 INFO [Quickstart] - Retrieved the correct value! [aValue] 
2020-11-04 09:14:00,630 INFO [Quickstart] - User [lonestarr] logged in successfully. 
2020-11-04 09:14:00,631 INFO [Quickstart] - May the Schwartz be with you! 
2020-11-04 09:14:00,631 INFO [Quickstart] - You may use a lightsaber ring.  Use it wisely. 
2020-11-04 09:14:00,632 INFO [Quickstart] - You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'.  Here are the keys - have fun! 

1234567

SpringBoot整合Shiro环境搭建

  • 新建SpringBoot项目,勾选webthymeleaf
  • 保持项目清洁,删除
    在这里插入图片描述
  • templates下新建index.html
<!DOCTYPE html>
<html lang="en"
      xmlns:th="https://www.thymeleaf.org"
      xmlns:shiro="https://www.thymeleaf.org/thymeleaf-extras-shiro">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<h1>首页</h1>

<p th:text="${msg}"></p>

</body>
</html>
12345678910111213141516
  • controller包下新建MyController
package com.huang.controller;

import org.apache.catalina.security.SecurityUtil;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpSession;

@Controller
public class MyController {

    @RequestMapping({"/","/index"})
    public String toIndex(Model model){
        model.addAttribute("msg","hello,shiro");
        return "index";
    }
}
123456789101112131415161718192021222324
  • 测试正常

在这里插入图片描述

让我们继续

Subject用户
SecurityManager管理所有用户
Realm连接数据

GitHub资源

在这里插入图片描述
pom.xml复制

        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring-boot-web-starter</artifactId>
        </dependency>
1234

发现并不好使更换为

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

shiro整合mybatis

导入jar包

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.19</version>
        </dependency>

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

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.12</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.1</version>
        </dependency>
12345678910111213141516171819202122

编写application.yml

spring:
  datasource:
    username: root
    password: jia5211314
    url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
    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
mybatis:
  type-aliases-package: com.huang.pojo
  mapper-locations: classpath:mybatis/mapper/*.xml

123456789101112131415161718192021222324252627282930313233

编写实体类

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.10</version>
            <scope>provided</scope>
        </dependency>
123456
User
package com.huang.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private int id;
    private String name;
    private String pwd;
    private String perms;


}

123456789101112131415161718
mapper`包编写`UserMapper
package com.huang.mapper;

import com.huang.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

import java.util.List;

@Mapper//这个注解表示了这是一个mybatis的mapper类
@Repository
public interface UserMapper {

    List<User> queryUserList();

    User queryUserById(int id);

    int addUser(User user);

    int updateUser(User user);

    int deleteUser(int id);
}

1234567891011121314151617181920212223
resource`包下新建`mybatis`包下健`mapper
UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.huang.mapper.UserMapper">

    <select id="queryUserList" resultType="User">
       select * from user;
    </select>

    <select id="queryUserByName" resultType="User" parameterType="String">
        select * from user where name=#{name}
    </select>

</mapper>
12345678910111213141516

service

UserService接口

package com.huang.service;

import com.huang.pojo.User;

public interface UserService {
    User queryUserByName(String name);
}

12345678
UserServiceImpl
package com.huang.service;

import com.huang.mapper.UserMapper;
import com.huang.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService{
    @Autowired
    UserMapper userMapper;

    @Override
    public User queryUserByName(String name) {
        return userMapper.queryUserByName(name);
    }
}

123456789101112131415161718

ShiroSpringbootApplicationTests中进行测试

package com.huang;

import com.huang.service.UserServiceImpl;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class ShiroSpringbootApplicationTests {
    @Autowired
    UserServiceImpl userService;

    @Test
    void contextLoads() {
        System.out.println(userService.queryUserByName("张三"));
    }

}

12345678910111213141516171819
  • 测试成功

Shiro整合Thymeleaf

导入jar

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

1234567

代码整理

  • 结构目录
    在这里插入图片描述
config
ShiroConfig
package com.huang.config;

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;
import java.util.Map;

@Configuration
public class ShiroConfig {

    //ShiroFilterBean
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){
        ShiroFilterFactoryBean bean=new ShiroFilterFactoryBean();
        //设置安全管理器
        bean.setSecurityManager(defaultWebSecurityManager);

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

        //授权
        filterMap.put("/user/add","perms[user:add]");
        filterMap.put("/user/update","perms[user:update]");

        //filterMap.put("/user/add","authc");
        //filterMap.put("/user/update","authc");

        //设置登陆的请求
        bean.setLoginUrl("/toLogin");

        //设置未授权的请求
        bean.setUnauthorizedUrl("/noauth");

        bean.setFilterChainDefinitionMap(filterMap);

        return bean;

    }

    //DefaultWebSecurityManager
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
        DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();

        //关联UserRealm
        securityManager.setRealm(userRealm);

        return securityManager;
    }


    //创建realm对象
    @Bean
    public UserRealm userRealm(){
        return new UserRealm();
    }

    //整合shiroDialect:用来整合shiro thymeleaf
    @Bean
    public ShiroDialect getShiroDialect(){
        return new ShiroDialect();
    }
}

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677
UserRealm
package com.huang.config;

import com.huang.pojo.User;
import com.huang.service.UserService;
import org.apache.catalina.security.SecurityUtil;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;

public class UserRealm extends AuthorizingRealm {
    @Autowired
    UserService userService;

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

        SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();

        //info.addStringPermission("user:add");
        //拿到当前用户登陆对象
        Subject subject= SecurityUtils.getSubject();
        User currentUser= (User) subject.getPrincipal();//拿到User对象
        info.addStringPermission(currentUser.getPerms());//设置当前用户对象

        return info;
    }

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

        //用户名,密码,数据库中获取

        UsernamePasswordToken userToken=(UsernamePasswordToken) authenticationToken;

        User user=userService.queryUserByName(userToken.getUsername());//获取用户名

        String name=user.getName();
        String password=user.getPwd();
        if(user==null){//说明查无此人
            return null;
        }

        //密码认证,shiro做
        return new SimpleAuthenticationInfo(user,password,"");//放入User对象

    }


}

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859
controller
MyController
package com.huang.controller;

import org.apache.catalina.security.SecurityUtil;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpSession;

@Controller
public class MyController {

    @RequestMapping({"/","/index"})
    public String toIndex(Model model){
        model.addAttribute("msg","hello,shiro");
        return "index";
    }

    @RequestMapping("/user/add")
    public String add(){
        return "user/add";
    }

    @RequestMapping("/user/update")
    public String update(){
        return "user/update";
    }

    @RequestMapping("/toLogin")
    public String toLogin(){
        return "login";
    }

    @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);//设置session
            return "index";
        }catch (UnknownAccountException e){//用户名不存在
            model.addAttribute("msg","用户名错误");
            return "login";
        }catch (IncorrectCredentialsException e){//密码不存在
            model.addAttribute("msg","密码错误");
            return "login";
        }

    }

    @RequestMapping("/noauth")
    @ResponseBody
    public String unauthorized(){
        return "未经授权禁止访问";

    }

}

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
mapper
UserMapper
package com.huang.mapper;

import com.huang.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

import java.util.List;

@Mapper
@Repository
public interface UserMapper {

    User queryUserByName(String name);

    List<User> queryUserList();

    User queryUserById(int id);

    int addUser(User user);

    int updateUser(User user);

    int deleteUser(int id);
}

12345678910111213141516171819202122232425
pojo
User
package com.huang.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private int id;
    private String name;
    private String pwd;
    private String perms;


}

123456789101112131415161718
service
UserService
在这里插入代码片package com.huang.service;

import com.huang.pojo.User;

public interface UserService {
    User queryUserByName(String name);
}

12345678
UserServiceImpl
package com.huang.service;

import com.huang.mapper.UserMapper;
import com.huang.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService{
    @Autowired
    UserMapper userMapper;

    @Override
    public User queryUserByName(String name) {
        return userMapper.queryUserByName(name);
    }
}

123456789101112131415161718
resource
UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.huang.mapper.UserMapper">

    <select id="queryUserList" resultType="User">
       select * from user;
    </select>

    <select id="queryUserByName" resultType="User" parameterType="String">
        select * from user where name=#{name}
    </select>

</mapper>
12345678910111213141516
add.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>添加一个用户</h1>
</body>
</html>


-----------------------------------------------

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>修改一个用户</h1>
</body>
</html>

-----------------------------------------------
<!DOCTYPE html>
<html lang="en"
      xmlns:th="https://www.thymeleaf.org"
      xmlns:shiro="https://www.thymeleaf.org/thymeleaf-extras-shiro">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<h1>首页</h1>

<p th:text="${msg}"></p>

<hr>
<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>

-----------------------------------------------

<!DOCTYPE html>
<html lang="en" xmlns:th="https://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>登陆</h1>
<hr>
<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>
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
application.yml
spring:
  datasource:
    username: root
    password: jia5211314
    url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
    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
mybatis:
  type-aliases-package: com.huang.pojo
  mapper-locations: classpath:mybatis/mapper/*.xml

123456789101112131415161718192021222324252627282930313233
pom.xml
    <dependencies>

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


        <!--
        Subject 用户
        SecurityManager 管理所有用户
        Realm 连接数据
        -->

        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.19</version>
        </dependency>

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

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.12</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.1</version>
        </dependency>

        <!--lombok-->
        <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.10</version>
            <scope>provided</scope>
        </dependency>


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

        <!--thymeleaf模板-->
        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf-spring5</artifactId>
        </dependency>

        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-java8time</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>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

13.swagger

  • 了解Swagger的概念及作用
  • 了解前后端分离
  • 在springboot中集成swagger

1.Swagger简介

前后端分离

Vue+SpringBoot

后端时代:前端只用管理静态页面;html==>后端。模板引擎JSP=>后端才是主力

前后端分离时代

  • 前端 -> 前端控制层、视图层
    • 伪造后端数据,json。已经存在了,不需要后端,前端工程队依旧能够跑起来
  • 后端 -> 后端控制层、服务层、数据访问层
  • 前后端通过API进行交互
  • 前后端相对独立且松耦合

产生的问题

  • 前后端集成联调,前端或者后端无法做到“及时协商,尽早解决”,最终导致问题集中爆发

解决方案

  • 首先定义schema [ 计划的提纲 ],并实时跟踪最新的API,降低集成风险;
  • 早些年:指定word计划文档;
  • 前后端分离:
    • 前端测试后端接口:postman
    • 后端提供接口,需要实时更新最新的消息及改动

2. Swagger

  • 号称世界上最流行的API框架
  • Restful Api 文档在线自动生成器 => API 文档 与API 定义同步更新
  • 直接运行,在线测试API
  • 支持多种语言 (如:Java,PHP等)
  • 官网:https://swagger.io/

3.SpringBoot集成Swagger

SpringBoot集成Swagger => springfox,两个jar包

使用Swagger

要求:jdk 1.8 + 否则swagger2无法运行

步骤:

  1. 新建一个SpringBoot-web项目

  2. 添加Maven依赖(注意:2.9.2版本之前,之后的不行)

    <dependency>
       <groupId>io.springfox</groupId>
       <artifactId>springfox-swagger2</artifactId>
       <version>2.9.2</version>
    </dependency>
    
    <dependency>
       <groupId>io.springfox</groupId>
       <artifactId>springfox-swagger-ui</artifactId>
       <version>2.9.2</version>
    </dependency>
    
    
  3. 编写HelloController,测试确保运行成功!

  4. 要使用Swagger,我们需要编写一个配置类-SwaggerConfig来配置 Swagger

    @Configuration //配置类
    @EnableSwagger2// 开启Swagger2的自动配置
    public class SwaggerConfig {  
    }
    
    
  5. 访问测试 :http://localhost:8080/swagger-ui.html ,可以看到swagger的界面;

    image-20200731132229265

4.配置Swagger

  1. Swagger实例Bean是Docket,所以通过配置Docket实例来配置Swaggger。

    @Bean //配置docket以配置Swagger具体参数
    public Docket docket() {
       return new Docket(DocumentationType.SWAGGER_2);
    }
    1234
    
  2. 可以通过apiInfo()属性配置文档信息

    //配置文档信息
    private ApiInfo apiInfo() {
       Contact contact = new Contact("联系人名字", "http://xxx.xxx.com/联系人访问链接", "联系人邮箱");
       return new ApiInfo(
               "Swagger学习", // 标题
               "学习演示如何配置Swagger", // 描述
               "v1.0", // 版本
               "http://terms.service.url/组织链接", // 组织链接
               contact, // 联系人信息
               "Apach 2.0 许可", // 许可
               "许可链接", // 许可连接
               new ArrayList<>()// 扩展
      );
    }
    
    
  3. Docket 实例关联上 apiInfo()

    @Bean
    public Docket docket() {
       return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo());
    }
    1234
    
  4. 重启项目,访问测试 http://localhost:8080/swagger-ui.html 看下效果;

    image-20200731161851136

5.配置扫描接口

  1. 构建Docket时通过select()方法配置怎么扫描接口。

    @Bean
    public Docket docket() {
       return new Docket(DocumentationType.SWAGGER_2)
          .apiInfo(apiInfo())
          .select()// 通过.select()方法,去配置扫描接口,RequestHandlerSelectors配置如何扫描接口
          .apis(RequestHandlerSelectors.basePackage("nuc.ss.swagger.controller"))
          .build();
    }
    
  2. 重启项目测试,由于我们配置根据包的路径扫描接口,所以我们只能看到一个类

    image-20200731165837391

  3. 除了通过包路径配置扫描接口外,还可以通过配置其他方式扫描接口,这里注释一下所有的配置方式:

    basePackage(final String basePackage) // 根据包路径扫描接口
    any() // 扫描所有,项目中的所有接口都会被扫描到
    none() // 不扫描接口
    // 通过方法上的注解扫描,如withMethodAnnotation(GetMapping.class)只扫描get请求
    withMethodAnnotation(final Class<? extends Annotation> annotation)
    // 通过类上的注解扫描,如.withClassAnnotation(Controller.class)只扫描有controller注解的类中的接口
    withClassAnnotation(final Class<? extends Annotation> annotation)
    
    
  4. 除此之外,我们还可以配置接口扫描过滤:

    @Bean
    public Docket docket() {
       return new Docket(DocumentationType.SWAGGER_2)
          .apiInfo(apiInfo())
          .select()
          .apis(RequestHandlerSelectors.basePackage("com.kuang.swagger.controller"))
           // 配置如何通过path过滤,即这里只扫描请求以/ss开头的接口
          .paths(PathSelectors.ant("/ss/**"))
          .build();
    }
    
    
  5. 这里的可选值还有

    any() // 任何请求都扫描
    none() // 任何请求都不扫描
    regex(final String pathRegex) // 通过正则表达式控制
    ant(final String antPattern) // 通过ant()控制
    
    

6.配置Swagger开关

  1. 通过enable()方法配置是否启用swagger,如果是false,swagger将不能在浏览器中访问了

    @Bean
    public Docket docket() {
       return new Docket(DocumentationType.SWAGGER_2)
          .apiInfo(apiInfo())
          .enable(false) //配置是否启用Swagger,如果是false,在浏览器将无法访问
          .select()
          .apis(RequestHandlerSelectors.basePackage("nuc.ss.swagger.controller"))
          .paths(PathSelectors.ant("/ss/**"))
          .build();
    }
    
    

image-20200731190614381

  1. 如何动态配置当项目处于test、dev环境时显示swagger,处于prod时不显示?

    @Bean
    public Docket docket(Environment environment) {
       // 设置要显示swagger的环境
       Profiles of = Profiles.of("dev", "test");
       // 判断当前是否处于该环境
       // 通过 enable() 接收此参数判断是否要显示
       boolean b = environment.acceptsProfiles(of);
       
       return new Docket(DocumentationType.SWAGGER_2)
          .apiInfo(apiInfo())
          .enable(b)
          .select()
          .apis(RequestHandlerSelectors.basePackage("com.kuang.swagger.controller"))
          .paths(PathSelectors.ant("/ss/**"))
          .build();
    }
    
    
  2. 可以在项目中增加配置文件

    • dev测试环境

      server.port=8081
      1
      

      image-20200731193109826

      项目运行结果

      image-20200731193425090

    • pro测试环境

      server.port=8082
      1
      

      image-20200731194455510

      项目运行结果

      image-20200731194559290

6.配置API分组

  1. 如果没有配置分组,默认是default。通过groupName()方法即可配置分组:

    @Bean
    public Docket docket(Environment environment) {
       return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo())
          .groupName("狂神") // 配置分组
           // 省略配置....
    }
    
  2. 重启项目查看分组

    image-20200731195354714

  3. 如何配置多个分组?配置多个分组只需要配置多个docket即可:

    @Bean
    public Docket docket1(){
       return new Docket(DocumentationType.SWAGGER_2).groupName("group1");
    }
    @Bean
    public Docket docket2(){
       return new Docket(DocumentationType.SWAGGER_2).groupName("group2");
    }
    @Bean
    public Docket docket3(){
       return new Docket(DocumentationType.SWAGGER_2).groupName("group3");
    }
    
    
  4. 重启项目查看即可

    image-20200731195543102

7.实体配置

  1. 新建一个实体类

    //@Api("注释")
    @ApiModel("用户实体")
    public class User {
        @ApiModelProperty("用户名")
        private String username;
        @ApiModelProperty("密码")
        private String password;
    
        public String getUsername() {
            return username;
        }
    
        public void setUsername(String username) {
            this.username = username;
        }
    
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    }
    
    
  2. 只要这个实体在请求接口的返回值上(即使是泛型),都能映射到实体项中:

    @RestController
    public class HelloController {
    
        //   /error默认错误请求
        @GetMapping("/hello")
        public String hello() {
            return "hello";
        }
    
        //只要我们的接口中,返回值中存在实体类,他就会被扫描到Swagger中
        @PostMapping("/user")
        public User user() {
            return new User();
        }
    }
    
    
  3. 重启查看测试

    image-20200731200413725

注:并不是因为@ApiModel这个注解让实体显示在这里了,而是只要出现在接口方法的返回值上的实体都会显示在这里,而@ApiModel和@ApiModelProperty这两个注解只是为实体添加注释的。

  • @ApiModel为类添加注释
  • @ApiModelProperty为类属性添加注释

总结:

  • 我们可以通过Swagger给一些比较难理解的接口或者属性,增加注释信息
  • 接口文档实时更新
  • 可以在线测试

Swagger是一个优秀的工具,几乎所有大公司都有使用它

【注意点】:在正式发布的时候,关闭Swagger!!!

  • 出于安全考虑
  • 而且节省内存

8.用注解

Swagger的所有注解定义在io.swagger.annotations包下

下面列一些经常用到的,未列举出来的可以另行查阅说明:

Swagger注解简单说明
@Api(tags = “xxx模块说明”)作用在模块类上
@ApiOperation(“xxx接口说明”)作用在接口方法上
@ApiModel(“xxxPOJO说明”)作用在模型类上:如VO、BO
@ApiModelProperty(value = “xxx属性说明”,hidden = true)作用在类方法和属性上,hidden设置为true可以隐藏该属性
@ApiParam(“xxx参数说明”)作用在参数、方法和字段上,类似@ApiModelProperty

我们也可以给请求的接口配置一些注释

  1. 在HelloController控制类中的接口添加api接口注释

    @RestController
    public class HelloController {
        ......
        @ApiOperation("Hello控制接口")
        @GetMapping("/hello")
        public String hello2(@ApiParam("用户名") String username) {
            return "hello" + username;
        }
        
        @ApiOperation("get测试")
        @GetMapping("/get")
        public User hello2(@ApiParam("用户") User user) {
            return user;
        }
    }
    

image-20200731201755001

  1. 进行try it out测试

    image-20200731202958255

    测试结果

    image-20200731203034702

总结:

  1. 这样的话,可以给一些比较难理解的属性或者接口,增加一些配置信息,让人更容易阅读!
  2. 相较于传统的Postman或Curl方式测试接口,使用swagger简直就是傻瓜式操作,不需要额外说明文档(写得好本身就是文档)而且更不容易出错,只需要录入数据然后点击Execute,如果再配合自动化框架,可以说基本就不需要人为操作了。
  3. Swagger是个优秀的工具,现在国内已经有很多的中小型互联网公司都在使用它,相较于传统的要先出Word接口文档再测试的方式,显然这样也更符合现在的快速迭代开发行情。当然了,提醒下大家在正式环境要记得关闭Swagger,一来出于安全考虑二来也可以节省运行时内存。

9.拓展:其他皮肤

我们可以导入不同的包实现不同的皮肤定义:

1、默认的 访问 http://localhost:8080/swagger-ui.html

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

image-20200731204929854

2、bootstrap-ui 访问 http://localhost:8080/doc.html

<!-- 引入swagger-bootstrap-ui包 /doc.html-->
<dependency>
   <groupId>com.github.xiaoymin</groupId>
   <artifactId>swagger-bootstrap-ui</artifactId>
   <version>1.9.1</version>
</dependency>
123456

image-20200731205550845

3、Layui-ui 访问 http://localhost:8080/docs.html

<!-- 引入swagger-ui-layer包 /docs.html-->
<dependency>
   <groupId>com.github.caspar-chen</groupId>
   <artifactId>swagger-ui-layer</artifactId>
   <version>1.1.3</version>
</dependency>
123456
  • 我这个测试没成功(Layui-ui)

4、mg-ui 访问 http://localhost:8080/document.html

<!-- 引入swagger-ui-layer包 /document.html-->
<dependency>
   <groupId>com.zyplayer</groupId>
   <artifactId>swagger-mg-ui</artifactId>
   <version>1.0.6</version>
</dependency>
123456

image-20200731205723914

14.任务

异步任务

1、创建一个service包

2、创建一个类AsyncService

异步处理还是非常常用的,比如我们在网站上发送邮件,后台会去发送邮件,此时前台会造成响应不动,直到邮件发送完毕,响应才会成功,所以我们一般会采用多线程的方式去处理这些任务。

编写方法,假装正在处理数据,使用线程设置一些延时,模拟同步等待的情况;

@Service



public class AsyncService {



 



    public void hello(){



        try {



            Thread.sleep(3000);



        } catch (InterruptedException e) {



            e.printStackTrace();



        }



        System.out.println("业务进行中....");



    }



}

3、编写controller包

4、编写AsyncController类

我们去写一个Controller测试一下

@RestController
public class AsyncController {
    @Autowired
    AsyncService asyncService;
    @GetMapping("/hello")
    public String hello(){
        asyncService.hello();
        return "success";
    }
}

5、访问http://localhost:8080/hello进行测试,3秒后出现success,这是同步等待的情况。

问题:我们如果想让用户直接得到消息,就在后台使用多线程的方式进行处理即可,但是每次都需要自己手动去编写多线程的实现的话,太麻烦了,我们只需要用一个简单的办法,在我们的方法上加一个简单的注解即可,如下:

6、给hello方法添加@Async注解;

//告诉Spring这是一个异步方法
@Async
public void hello(){
    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("业务进行中....");
}

SpringBoot就会自己开一个线程池,进行调用!但是要让这个注解生效,我们还需要在主程序上添加一个注解@EnableAsync ,开启异步注解功能;

@EnableAsync //开启异步注解功能
@SpringBootApplication
public class SpringbootTaskApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringbootTaskApplication.class, args);

    }

}

7、重启测试,网页瞬间响应,后台代码依旧执行!

定时任务

项目开发中经常需要执行一些定时任务,比如需要在每天凌晨的时候,分析一次前一天的日志信息,Spring为我们提供了异步执行任务调度的方式,提供了两个接口。

  • TaskExecutor接口
  • TaskScheduler接口

两个注解:

  • @EnableScheduling
  • @Scheduled

cron表达式:

img

img

测试步骤:

1、创建一个ScheduledService

我们里面存在一个hello方法,他需要定时执行,怎么处理呢?

@Service
public class ScheduledService {

    //秒    分    时     日    月    周几
    //0 * * * * MON-FRI
    //注意cron表达式的用法;
    @Scheduled(cron = "0 * * * * 0-7")
    public void hello(){
        System.out.println("hello.....");
    }
}

2、这里写完定时任务之后,我们需要在主程序上增加@EnableScheduling 开启定时任务功能

@EnableAsync //开启异步注解功能
@EnableScheduling //开启基于注解的定时任务
@SpringBootApplication
public class SpringbootTaskApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringbootTaskApplication.class, args);
    }
}

3、我们来详细了解下cron表达式;

http://www.bejson.com/othertools/cron/

4、常用的表达式

10/2 * * * * ?   表示每2秒 执行任务
(10 0/2 * * * ?    表示每2分钟 执行任务
(10 0 2 1 * ?   表示在每月的1日的凌晨2点调整任务
(20 15 10 ? * MON-FRI   表示周一到周五每天上午10:15执行作业
(30 15 10 ? 6L 2002-2006   表示2002-2006年的每个月的最后一个星期五上午10:15执行作
(40 0 10,14,16 * * ?   每天上午10点,下午2点,4点
(50 0/30 9-17 * * ?   朝九晚五工作时间内每半小时
(60 0 12 ? * WED    表示每个星期三中午12点
(70 0 12 * * ?   每天中午12点触发
(80 15 10 ? * *    每天上午10:15触发
(90 15 10 * * ?     每天上午10:15触发
(100 15 10 * * ?    每天上午10:15触发
(110 15 10 * * ? 2005    2005年的每天上午10:15触发
(120 * 14 * * ?     在每天下午2点到下午2:59期间的每1分钟触发
(130 0/5 14 * * ?    在每天下午2点到下午2:55期间的每5分钟触发
(140 0/5 14,18 * * ?     在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发
(150 0-5 14 * * ?    在每天下午2点到下午2:05期间的每1分钟触发
(160 10,44 14 ? 3 WED    每年三月的星期三的下午2:102:44触发
(170 15 10 ? * MON-FRI    周一至周五的上午10:15触发
(180 15 10 15 * ?    每月15日上午10:15触发
(190 15 10 L * ?    每月最后一日的上午10:15触发
(200 15 10 ? * 6L    每月的最后一个星期五上午10:15触发
(210 15 10 ? * 6L 2002-2005   2002年至2005年的每月的最后一个星期五上午10:15触发
(220 15 10 ? * 6#3   每月的第三个星期五上午10:15触发

邮件任务

邮件发送,在我们的日常开发中,也非常的多,Springboot也帮我们做了支持

  • 邮件发送需要引入spring-boot-start-mail
  • SpringBoot 自动配置MailSenderAutoConfiguration
  • 定义MailProperties内容,配置在application.yml中
  • 自动装配JavaMailSender
  • 测试邮件发送

测试:

1、引入pom依赖

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

看它引入的依赖,可以看到 jakarta.mail

<dependency>
    <groupId>com.sun.mail</groupId>
    <artifactId>jakarta.mail</artifactId>
    <version>1.6.4</version>
    <scope>compile</scope>
</dependency>

2、查看自动配置类:MailSenderAutoConfiguration

img

这个类中存在bean,JavaMailSenderImpl

img

然后我们去看下配置文件

@ConfigurationProperties(
    prefix = "spring.mail"
)

public class MailProperties {
    private static final Charset DEFAULT_CHARSET;
    private String host;
    private Integer port;
    private String username;
    private String password;
    private String protocol = "smtp";
    private Charset defaultEncoding;
    private Map<String, String> properties;
    private String jndiName;
}

3、配置文件:

spring.mail.username=1224145956@qq.com
spring.mail.password=wvxqxmbkagxejdce
spring.mail.host=smtp.qq.com
#开启加密授权
spring.mail.properties.mail.smtp.ssl.enable=true

获取授权码:在QQ邮箱中的设置->账户->开启pop3和smtp服务

img

4、Spring单元测试

@Autowired
JavaMailSenderImpl mailSender;
 
@Test
public void contextLoads() {
    //邮件设置1:一个简单的邮件
    SimpleMailMessage message = new SimpleMailMessage();
    message.setSubject("通知-明天来狂神这听课");
    message.setText("今晚7:30开会");
 
    message.setTo("24736743@qq.com");
    message.setFrom("24736743@qq.com");
    mailSender.send(message);
}
 
@Test
public void contextLoads2() throws MessagingException {
    //邮件设置2:一个复杂的邮件
    MimeMessage mimeMessage = mailSender.createMimeMessage();
    MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
 
    helper.setSubject("通知-明天来狂神这听课");
    helper.setText("<p style='color:red'>今天 7:30来开会</p>",true);
 
    //发送附件
    helper.addAttachment("1.jpg",new File(""));
    helper.addAttachment("2.jpg",new File(""));
 
    helper.setTo("24736743@qq.com");
    helper.setFrom("24736743@qq.com");
 
    mailSender.send(mimeMessage);
}

查看邮箱,邮件接收成功!

我们只需要使用Thymeleaf进行前后端结合即可开发自己网站邮件收发功能了!

封装以后

/**
 *
 * @param html
 * @param subject
 * @param text
 * @throws MessagingException
 * @Author:xiao
 */

public void sendMail(Boolean html,String subject,String text) throws MessagingException{
    //邮件设置2:一个复杂的邮件
    MimeMessage mimeMessage = mailSender.createMimeMessage();
    MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, html);

    helper.setSubject(subject);
    helper.setText(text,true);

    //发送附件
    helper.addAttachment("1.jpg",new File("C:\\Users\\Administrator.SC-202001221340\\Pictures\\1.jpg"));
    helper.addAttachment("2.jpg",new File("C:\\Users\\Administrator.SC-202001221340\\Pictures\\1.jpg"));

    helper.setTo("1224145956@qq.com");
    helper.setFrom("1224145956@qq.com");

    mailSender.send(mimeMessage);

}

15.分布式Dubbo+Zookeeper+Springboot

1.什么是分布式系统?

在《分布式系统原理与范型》一书中有如下定义:“分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像单个相关系统”;

分布式系统是由一组通过网络进行通信、为了完成共同的任务而协调工作的计算机节点组成的系统。分布式系统的出现是为了用廉价的、普通的机器完成单个计算机无法完成的计算、存储任务。其目的是利用更多的机器,处理更多的数据

分布式系统(distributed system)是建立在网络之上的软件系统。

首先需要明确的是,只有当单个节点的处理能力无法满足日益增长的计算、存储任务的时候,且硬件的提升(加内存、加磁盘、使用更好的CPU)高昂到得不偿失的时候,应用程序也不能进一步优化的时候,我们才需要考虑分布式系统。因为,分布式系统要解决的问题本身就是和单机系统一样的,而由于分布式系统多节点、通过网络通信的拓扑结构,会引入很多单机系统没有的问题,为了解决这些问题又会引入更多的机制、协议,带来更多的问题。。。

2.Dubbo文档

随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,急需一个治理系统确保架构有条不紊的演进。

在Dubbo的官网文档有这样一张图

img

3.单一应用架构

当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。

img

适用于小型网站,小型管理系统,将所有功能都部署到一个功能里,简单易用。

缺点:

1、性能扩展比较难

2、协同开发问题

3、不利于升级维护

4.垂直应用架构

当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的Web框架(MVC)是关键。

img

通过切分业务来实现各个模块独立部署,降低了维护和部署的难度,团队各司其职更易管理,性能扩展也更方便,更有针对性。

缺点:公用模块无法重复利用,开发性的浪费

5.分布式服务架构

当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的**分布式服务框架(RPC)**是关键。

img

6.流动计算架构

当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)[ Service Oriented Architecture]是关键。

img

什么是RPC

RPC【Remote Procedure Call】是指远程过程调用,是一种进程间通信方式,他是一种技术的思想,而不是规范。它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数,而不用程序员显式编码这个远程调用的细节。即程序员无论是调用本地的还是远程的函数,本质上编写的调用代码基本相同。

也就是说两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。为什么要用RPC呢?就是无法在一个进程内,甚至一个计算机内通过本地调用的方式完成的需求,比如不同的系统间的通讯,甚至不同的组织间的通讯,由于计算能力需要横向扩展,需要在多台机器组成的集群上部署应用。RPC就是要像调用本地的函数一样去调远程函数;

推荐阅读文章:https://www.jianshu.com/p/2accc2840a1b

RPC基本原理

img

步骤解析:

img

RPC两个核心模块:通讯,序列化。

测试环境搭建

7.Dubbo

Apache Dubbo |ˈdʌbəʊ| 是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。

dubbo官网 http://dubbo.apache.org/zh-cn/index.html

1.了解Dubbo的特性

2.查看官方文档

dubbo基本概念

img

服务提供者(Provider):暴露服务的服务提供方,服务提供者在启动时,向注册中心注册自己提供的服务。

服务消费者(Consumer):调用远程服务的服务消费方,服务消费者在启动时,向注册中心订阅自己所需的服务,服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。

注册中心(Registry):注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者

监控中心(Monitor):服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心

调用关系说明

l 服务容器负责启动,加载,运行服务提供者。

l 服务提供者在启动时,向注册中心注册自己提供的服务。

l 服务消费者在启动时,向注册中心订阅自己所需的服务。

l 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。

l 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。

l 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。

8.Dubbo环境搭建

点进dubbo官方文档,推荐我们使用Zookeeper 注册中心

什么是zookeeper呢?可以查看官方文档

9.Window下安装zookeeper

1、下载zookeeper :地址, 我们下载3.4.14 , 最新版!解压zookeeper

2、运行/bin/zkServer.cmd ,初次运行会报错,没有zoo.cfg配置文件;

可能遇到问题:闪退 !

解决方案:编辑zkServer.cmd文件末尾添加pause 。这样运行出错就不会退出,会提示错误信息,方便找到原因。

img

img

3、修改zoo.cfg配置文件

将conf文件夹下面的zoo_sample.cfg复制一份改名为zoo.cfg即可。

注意几个重要位置:

dataDir=./ 临时数据存储的目录(可写相对路径)

clientPort=2181 zookeeper的端口号

修改完成后再次启动zookeeper

img

4、使用zkCli.cmd测试

ls /:列出zookeeper根下保存的所有节点

     [zk: 
     127.0
     .0
     .1:
     2181(CONNECTED) 
     4] ls / 
     [zookeeper]
    
   
 1

create –e /kuangshen 123:创建一个kuangshen节点,值为123

img

get /kuangshen:获取/kuangshen节点的值

img

我们再来查看一下节点

img

10.window下安装dubbo-admin

dubbo本身并不是一个服务软件。它其实就是一个jar包,能够帮你的java程序连接到zookeeper,并利用zookeeper消费、提供服务。

但是为了让用户更好的管理监控众多的dubbo服务,官方提供了一个可视化的监控程序dubbo-admin,不过这个监控即使不装也不影响使用。

我们这里来安装一下:

1、下载dubbo-admin

地址 :https://github.com/apache/dubbo-admin/tree/master

2、解压进入目录

修改 dubbo-admin\src\main\resources \application.properties 指定zookeeper地址

     server.port=
     7001
   
     spring.velocity.cache=
     false
    
    
     spring.velocity.charset=UTF
     -8
     spring.velocity.layout-url=/templates/
     default.vm
     spring.messages.fallback-to-system-locale=
     false
   
     spring.messages.basename=i18n/message
   
     spring.root.password=root
     spring.guest.password=guest  
     dubbo.registry.address=zookeeper:
     //127.0.0.1:2181

3、在项目目录下打包dubbo-admin

mvn clean package -Dmaven.test.skip=true 

第一次打包的过程有点慢,需要耐心等待!直到成功!

img

4、执行 dubbo-admin\target 下的dubbo-admin-0.0.1-SNAPSHOT.jar

java -jar dubbo-admin-0.0.1-SNAPSHOT.jar
 1

【注意:zookeeper的服务一定要打开!】

执行完毕,我们去访问一下 http://localhost:7001/ , 这时候我们需要输入登录账户和密码,我们都是默认的root-root;

登录成功后,查看界面

img

安装完成!

11.SpringBoot + Dubbo + zookeeper

12.框架搭建

1. 启动zookeeper !

2. IDEA创建一个空项目;

3.创建一个模块,实现服务提供者:provider-server , 选择web依赖即可

4.项目创建完毕,我们写一个服务,比如卖票的服务;

编写接口

     package com.kuang.provider.service;
   
     public 
     interface TicketService {
     public 
     String getTicket();   
     }

编写实现类

     package com.kuang.provider.service;    
     public 
     class TicketServiceImpl implements TicketService {  
     @Override        
     public 
     String getTicket() {            
     return 
     "《狂神说Java》";   
         }   
     }

5.创建一个模块,实现服务消费者:consumer-server , 选择web依赖即可

6.项目创建完毕,我们写一个服务,比如用户的服务;

编写service

     package com.kuang.consumer.service;  
     public 
     class UserService {        
     //我们需要去拿去注册中心的服务   
     }

需求:现在我们的用户想使用买票的服务,这要怎么弄呢 ?

15.服务提供者

1、将服务提供者注册到注册中心,我们需要整合Dubbo和zookeeper,所以需要导包

我们从dubbo官网进入github,看下方的帮助文档,找到dubbo-springboot,找到依赖包

     <!-- Dubbo Spring Boot Starter -->
     <dependency>
         <groupId>org.apache.dubbo</groupId>
         <artifactId>dubbo-spring-boot-starter</artifactId> 
         <version> 2.7.3</version>
     </dependency>    

zookeeper的包我们去maven仓库下载,zkclient;

     <!-- https:
     //mvnrepository.com/artifact/com.github.sgroschupf/zkclient --> 
     <dependency>
         <groupId>com.github.sgroschupf</groupId>
         <artifactId>zkclient</artifactId>
         <version> 0.1</version>
         </dependency>

【新版的坑】zookeeper及其依赖包,解决日志冲突,还需要剔除日志依赖;

     <!-- 引入zookeeper -->
     <dependency>
         <groupId>org.apache.curator</groupId>
         <artifactId>curator-framework</artifactId>
         <version> 2.12.0</version>
     </dependency>  
     <dependency>
         <groupId>org.apache.curator</groupId>
         <artifactId>curator-recipes</artifactId>  
         <version>2.12.0</version>
     </dependency>
     <dependency> 
         <groupId>org.apache.zookeeper</groupId> 
         <artifactId>zookeeper</artifactId>   
         <version>3.4.14</version>  
         <!--排除这个slf4j-log4j12-->  
         <exclusions>   
             <exclusion>
                 <groupId>org.slf4j</groupId>
                 <artifactId>slf4j-log4j12</artifactId>
             </exclusion>
         </exclusions>   
     </dependency>

2、在springboot配置文件中配置dubbo相关属性!

     #当前应用名字    
     dubbo.application.name=provider-server   
     #注册中心地址   
     dubbo.registry.address=zookeeper:
     //127.0.0.1:2181   
     #扫描指定包下服务  
     dubbo.scan.base-packages=com.kuang.provider.service

3、在service的实现类中配置服务注解,发布服务!注意导包问题

     import org.apache.dubbo.config.annotation.Service;    
     import org.springframework.stereotype.Component; 
     @Service 
     //将服务发布出去  
     @Component 
     //放在容器中  
     public class TicketServiceImpl implements TicketService {  
         @Override        
     public String getTicket() {           
     return 
     "《狂神说Java》";
 
         }
   }

逻辑理解 :应用启动起来,dubbo就会扫描指定的包下带有@component注解的服务,将它发布在指定的注册中心中!

13.服务消费者

1、导入依赖,和之前的依赖一样;

     <!--dubbo--> 
     <!-- Dubbo Spring Boot Starter -->  
     <dependency>    
         <groupId>org.apache.dubbo</groupId>  
         <artifactId>dubbo-spring-boot-starter</artifactId>   
         <version>2.7.3</version>      
     </dependency>   
     <!--zookeeper-->  
     <!-- https:
     //mvnrepository.com/artifact/com.github.sgroschupf/zkclient -->   
     <dependency>  
         <groupId>com.github.sgroschupf</groupId> 
         <artifactId>zkclient</artifactId>    
         <version> 0.1</version>    
     </dependency>
    
     <!-- 引入zookeeper -->   
     <dependency>  
         <groupId>org.apache.curator</groupId>  
         <artifactId>curator-framework</artifactId> 
         <version>2.12.0</version>  
     </dependency>   
     <dependency>  
         <groupId>org.apache.curator</groupId>  
         <artifactId>curator-recipes</artifactId>  
         <version>2.12.0</version>
     </dependency>  
     <dependency>  
         <groupId>org.apache.zookeeper</groupId>   
         <artifactId>zookeeper</artifactId>  
         <version> 3.4.14</version>   
         <!--排除这个slf4j-log4j12-->   
         <exclusions>
             <exclusion> 
                 <groupId>org.slf4j</groupId>  
                 <artifactId>slf4j-log4j12</artifactId>    
             </exclusion>
         </exclusions>   
     </dependency>

2、配置参数

     #当前应用名字  
     dubbo.application.name=consumer-server  
     #注册中心地址  
     dubbo.registry.address=zookeeper:
     //127.0.0.1:2181

3. 本来正常步骤是需要将服务提供者的接口打包,然后用pom文件导入,我们这里使用简单的方式,直接将服务的接口拿过来,路径必须保证正确,即和服务提供者相同;

img

4. 完善消费者的服务类

     package com.kuang.consumer.service;   
     import com.kuang.provider.service.TicketService;   
     import org.apache.dubbo.config.annotation.Reference;    
     import org.springframework.stereotype.Service;   
     @Service 
     //注入到容器中   
     public class UserService {  
         @Reference 
     //远程引用指定的服务,他会按照全类名进行匹配,看谁给注册中心注册了这个全类名    
         TicketService ticketService;        
         public void bugTicket(){  
             System.out.println( "在注册中心买到"+ticket); 
         }   
     }

5. 测试类编写;

     @RunWith(SpringRunner.
     class)
     @SpringBootTest
     public class ConsumerServerApplicationTests {   
         @Autowired
         UserService userService;   
         @Test         
     public void contextLoads() {   
             userService.bugTicket();
  
         }
  
     }

14.启动测试

1. 开启zookeeper

2. 打开dubbo-admin实现监控【可以不用做】

3. 开启服务者

4. 消费者消费测试,结果:

img

监控中心 :

img

ok , 这就是SpingBoot + dubbo + zookeeper实现分布式开发的应用,其实就是一个服务拆分的思想;

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值