SpringBoot基础

配置文件

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

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

YAML配置文件是官方推荐的配置方式:YAML语法

基础语法:

k:(空格) v   

以此来表示一对键值对(空格不能省略);以空格的缩进来控制层级关系,只要是左边对齐的一列数据都是同一个层级的。

注意 :属性和值的大小写都是十分敏感的。

值的写法

字面量:普通的值 [ 数字,布尔值,字符串 ]

k: v

字面量直接写在后面就可以 , 字符串默认不用加上双引号或者单引号;

“” 双引号,不会转义字符串里面的特殊字符 , 特殊字符会作为本身想表示的意思;

对象、Map(键值对)

k: 
    v1:
    v2:

在下一行来写对象的属性和值得关系,注意缩进;比如:

student:
    name: lizeyu
    age: 22			//年龄与name在同一垂直线,若有空格默认是name的下一级别属性

行内写法

student: {name: 李泽玉,age: 22}

数组( List、set )

用 - 值表示数组中的一个元素,比如:

people:
 - lizeyu
 - houlinkai
 - lixiangwei

行内写法

people: [lizeyu,houlinkai,lixiangwei]

修改SpringBoot的默认端口号

配置文件中添加,端口号的参数,就可以切换端口;

server:
 port: 8888

我们来看个功能对比图

img

  • cp只需要写一次即可 , value则需要每个字段都添加
  • 松散绑定:这个什么意思呢? 比如我的yml中写的last-name,这个和lastName是一样的, - 后面跟着的字母默认是大写的。这就是松散绑定
  • JSR303数据校验 , 这个就是我们可以在字段是增加一层过滤器验证 , 可以保证数据的合法性
  • 复杂类型封装,yml中可以封装对象 , 使用@value就不支持

结论:

  • 配置yml和配置properties都可以获取到值 , 强烈推荐 yml
  • 如果我们在某个业务中,只需要获取配置文件中的某个值,可以使用一下 @value
  • 如果说,我们专门编写了一个JavaBean来和配置文件进行映射,就直接使用@configurationProperties,不要犹豫!

JSR303数据校验

spring-boot中可以用@validated来校验数据,如果数据异常则会统一抛出异常,方便异常中心统一处理。我们这里来写个注解让我们的name只能支持Email格式

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

    //@Value("${person.name}")
    @Email //name必须是邮箱格式
    private String name;
}

在这里插入图片描述

Bean Validation 中的 constraint

表 1. Bean Validation 中内置的 constraint
Constraint详细信息
@Null被注释的元素必须为 null
@NotNull被注释的元素必须不为 null
@AssertTrue被注释的元素必须为 true
@AssertFalse被注释的元素必须为 false
@Min(value)被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value)被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin(value)被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value)被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Size(max, min)被注释的元素的大小必须在指定的范围内
@Digits (integer, fraction)被注释的元素必须是一个数字,其值必须在可接受的范围内
@Past被注释的元素必须是一个过去的日期
@Future被注释的元素必须是一个将来的日期
@Pattern(value)被注释的元素必须符合指定的正则表达式
表 2. Hibernate Validator 附加的 constraint
Constraint详细信息
@Email被注释的元素必须是电子邮箱地址
@Length被注释的字符串的大小必须在指定的范围内
@NotEmpty被注释的字符串的必须非空
@Range被注释的元素必须在合适的范围内

静态资源映射规则

首先,我们搭建一个普通的SpringBoot项目,回顾一下HelloWorld程序!【演示】

那我们要引入我们小实验的测试资源,我们项目中有许多的静态资源,比如,css,js等文件,这个SpringBoot怎么处理呢?

如果我们是一个web应用,我们的main下会有一个webapp,我们以前都是将所有的页面导在这里面的,对吧!

但是我们现在的pom呢,打包方式是为jar的方式,那么这种方式SpringBoot能不能来给我们写页面呢?当然是可以的,但是SpringBoot对于静态资源放置的位置,是有规定的!

我们先来聊聊这个静态资源映射规则;

SpringBoot中,SpringMVC的web配置都在 WebMvcAutoConfiguration 这个配置里面,我们可以去看看 WebMvcAutoConfigurationAdapter 中有很多配置方法;

比如:addResourceHandlers

  public void addResourceHandlers(ResourceHandlerRegistry registry) {
            if (!this.resourceProperties.isAddMappings()) {
                logger.debug("Default resource handling disabled");
            } else {
                Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
                CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
                if (!registry.hasMappingForPattern("/webjars/**")) {
                    this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{"/webjars/**"}).addResourceLocations(new String[]{"classpath:/META-INF/resources/webjars/"}).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl));
                }

                String staticPathPattern = this.mvcProperties.getStaticPathPattern();
                if (!registry.hasMappingForPattern(staticPathPattern)) {
                    this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{staticPathPattern}).addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations())).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl));
                }

            }
        }

读一下源代码:比如所有的 /webjars/** , 都需要去 classpath:/META-INF/resources/webjars/ 找对应的资源,那什么是webjars呢?

webjars本质就是以jar包的方式引入我们的静态资源 , 我们以前要导入一个静态资源文件,直接导入即可。使用SpringBoot需要使用webjars,我们可以去搜索一下

网站:https://www.webjars.org/ 【网站带看,并引入jQuery测试】

要使用jQuery,我们只要要引入jQuery对应版本的pom依赖即可!【导入完毕,查看webjars目录结构,并访问Jquery.js文件】

导入依赖:Maven

        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>jquery</artifactId>
            <version>3.3.1</version>
        </dependency>

那我们项目中要是使用自己的静态资源该怎么导入呢?我们看下一行代码;

我们去找staticPathPattern发现第二种映射规则 : /** , 访问当前的项目任意资源,它会去找 resourceProperties 这个类,我们可以点进去看一下,

@ConfigurationProperties(
    prefix = "spring.resources",
    ignoreUnknownFields = false
)
public class ResourceProperties {
    private static final String[] CLASSPATH_RESOURCE_LOCATIONS = new String[]{"classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"};

resourceProperties 可以设置和我们静态资源有关的参数,this.staticLocations = CLASSPATH_RESOURCE_LOCATIONS; 这里面指向了它会去寻找资源的文件夹;即上面数组的内容。再向下看一个方法;它还会在根路径下去寻找资源!

    private String[] appendSlashIfNecessary(String[] staticLocations) {
        String[] normalized = new String[staticLocations.length];

        for(int i = 0; i < staticLocations.length; ++i) {
            String location = staticLocations[i];
            normalized[i] = location.endsWith("/") ? location : location + "/";
        }

        return normalized;
    }

所以得出结论:

"classpath:/META-INF/resources/", 
"classpath:/resources/",
 "classpath:/static/", 
"classpath:/public/",
"/" :当前项目的根目录

我们可以在resources根目录下新建对应的文件夹,都可以存放我们的静态文件;

比如我们访问 localhost:8080/1.js, 他就会去这些文件夹中寻找对应的静态资源文件;

静态资源文件夹说完后,我们继续看源码!可以看到一个欢迎页的映射,就是我们的首页!

        @Bean
        public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext) {
            return new WelcomePageHandlerMapping(new TemplateAvailabilityProviders(applicationContext), applicationContext, this.getWelcomePage(), this.mvcProperties.getStaticPathPattern());
        }

点进去继续看

        private Optional<Resource> getWelcomePage() {
            String[] locations = getResourceLocations(this.resourceProperties.getStaticLocations());
            return Arrays.stream(locations).map(this::getIndexHtml).filter(this::isReadable).findFirst();
        }

        private Resource getIndexHtml(String location) {
            return this.resourceLoader.getResource(location + "index.html");
        }

欢迎页,静态资源文件夹下的所有index.html页面;被 /** 映射。

比如我访问 localhost:8080/ ,就会找静态资源文件夹下的 index.html 【测试一下】

模板引擎

前端交给我们的页面,是html页面。如果是我们以前开发,我们需要把他们转成jsp页面,jsp好处就是当我们查出一些数据转发到JSP页面以后,我们可以用jsp轻松实现数据的显示,及交互等。jsp支持非常强大的功能,包括能写Java代码,但是呢,我们现在的这种情况,SpringBoot这个项目首先是以jar的方式,不是war,像第二,我们用的还是嵌入式的Tomcat,所以呢,他现在默认是不支持jsp的。

那不支持jsp,如果我们直接用纯静态页面的方式,那给我们开发会带来非常大的麻烦,那怎么办呢,SpringBoot推荐你可以来使用模板引擎。

那么这模板引擎,我们其实大家听到很多,其实jsp就是一个模板引擎,还有以用的比较多的freemarker,包括SpringBoot给我们推荐的Thymeleaf,模板引擎有非常多,但再多的模板引擎,他们的思想都是一样的,什么样一个思想呢我们来看一下这张图。

img

模板引擎的作用就是我们来写一个页面模板,比如有些值呢,是动态的,我们写一些表达式。而这些值,从哪来呢,我们来组装一些数据,我们把这些数据找到。然后把这个模板和这个数据交给我们模板引擎,模板引擎按照我们这个数据帮你把这表达式解析、填充到我们指定的位置,然后把这个数据最终生成一个我们想要的内容给我们写出去,这就是我们这个模板引擎,不管是jsp还是其他模板引擎,都是这个思想。只不过呢,就是说不同模板引擎之间,他们可能这个语法有点不一样。其他的我就不介绍了,我主要来介绍一下SpringBoot给我们推荐的Thymeleaf模板引擎,这模板引擎呢,是一个高级语言的模板引擎,他的这个语法更简单。而且呢,功能更强大。

我们呢,就来看一下这个模板引擎,那既然要看这个模板引擎。首先,我们来看SpringBoot里边怎么用。

第一步:引入thymeleaf , 怎么引入呢,对于springboot来说,什么事情不都是一个start的事情嘛,我们去在项目中引入一下。给大家三个网址:

1、Thymeleaf 官网:https://www.thymeleaf.org/

2、Thymeleaf 在Github 的主页:https://github.com/thymeleaf/thymeleaf

3、Spring官方文档:“https://docs.spring.io/spring-boot/docs/2.1.6.RELEASE/reference/htmlsingle/#using-boot-starter” , 找到我们对应的版本

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

maven会自动下载jar包,我们可以去看下下载的东西;

img

compile group: 'org.thymeleaf', name: 'thymeleaf-spring5'
compile group: 'org.thymeleaf.extras', name: 'thymeleaf-extras-java8time'

使用Thymeleaf

前面呢,我们已经引入了Thymeleaf,那这个要怎么使用呢?
我们首先得按照SpringBoot的自动配置原理看一下我们这个Thymeleaf的自动配置规则,在按照那个规则,我们进行使用。
我们去找一下Thymeleaf的自动配置类;

img

这个包下有我们的thymeleaf,看我们的配置类,我们选择部分

@ConfigurationProperties(
    prefix = "spring.thymeleaf"
)
public class ThymeleafProperties {
    private static final Charset DEFAULT_ENCODING;
    public static final String DEFAULT_PREFIX = "classpath:/templates/";
    public static final String DEFAULT_SUFFIX = ".html";
    private boolean checkTemplate = true;
    private boolean checkTemplateLocation = true;
    private String prefix = "classpath:/templates/";
    private String suffix = ".html";
    private String mode = "HTML";
    private Charset encoding;
}

我们可以在其中看到默认的前缀和后缀!我们只需要把我们的html页面放在类路径下的templates下,thymeleaf就可以帮我们自动渲染了。

我们可以去测试一下 , 写一个Controller,跳转到一个指定页面,这个指定页面需要在 类路径下的模板目录下 【演示】

使用thymeleaf什么都不需要配置,只需要将他放在指定的文件夹下即可!

Thymeleaf语法

要学习语法,还是参考官网文档最为准确,我们找到对应的版本看一下;

Thymeleaf 官网:https://www.thymeleaf.org/ , 简单看一下官网!我们去下载Thymeleaf的官方文档!

我们做个最简单的练习 : 我们需要查出一些数据,在页面中展示

我们去在controller编写一个请求,放进去一些数据;

    @RequestMapping("/success")
    public String success(Model model){
        //存入数据
        model.addAttribute("msg","Hello,Thymeleaf");
        //classpath:/templates/success.html
        return "success";
    }

我们要使用thymeleaf,需要在html文件中导入命名空间的约束,方便提示。我们可以去官方文档的#3中看一下命名空间拿来过来

 xmlns:th="http://www.thymeleaf.org"

我们去编写下前端页面

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>南风吹进西电</title>
</head>
<body>
<h1>首页</h1>

<!--th:text就是将div中的内容设置为它指定的值,和之前学习的Vue一样-->
<div th:text="${msg}"></div>
</body>
</html>

Thymeleaf语法

Selection Variable Expressions: *{...}:选择表达式:和${}在功能上是一样;
   补充:配合 th:object="${session.user}:

   <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>
    
Message Expressions: #{...}:获取国际化内容
Link URL Expressions: @{...}:定义URL;
            @{/order/process(execId=${execId},execType='FAST')}

Fragment Expressions: ~{...}:片段引用表达式
            <div th:insert="~{commons :: main}">...</div>
            
Literals(字面量)
      Text literals: 'one text' , 'Another one!' ,…
      Number literals: 0 , 34 , 3.0 , 12.3 ,…
      Boolean literals: true , false
      Null literal: null
      Literal tokens: one , sometext , main ,…
Text operations:(文本操作)
    String concatenation: +
    Literal substitutions: |The name is ${name}|
Arithmetic operations:(数学运算)
    Binary operators: + , - , * , / , %
    Minus sign (unary operator): -
Boolean operations:(布尔运算)
    Binary operators: and , or
    Boolean negation (unary operator): ! , not
Comparisons and equality:(比较运算)
    Comparators: > , < , >= , <= ( gt , lt , ge , le )
    Equality operators: == , != ( eq , ne )
Conditional operators:条件运算(三元运算符)
    If-then: (if) ? (then)
    If-then-else: (if) ? (then) : (else)
    Default: (value) ?: (defaultvalue)
Special tokens:
    No-Operation: _ 

前端基础

准备工作

本页面在bootstrap example网站获取,可自行选择下载!地址:http://www.bootstrapmb.com/item/6989
导入完毕这些之后,我们还需要导入我们的前端页面,及静态资源文件!

  • css,js等放在static文件夹下
  • html放在templates文件夹下

准备工作:OK!!!

首页实现

要求一:默认访问首页

方式一:写一个controller实现!

    //会解析到templates目录下的index.html页面
    @RequestMapping({"/","/index.html"})
    public String index(){
        return "index";
    }

方式二:自己编写MVC的扩展配置

@Configuration
public class MyMVCConfig implements WebMvcConfigurer {
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        //param1:路径,param2:名称
        registry.addViewController("/").setViewName("index");
        registry.addViewController("/index.html").setViewName("index");
    }
}

解决了这个问题,我们还需要解决一个资源导入的问题;

为了保证资源导入稳定,我们建议在所有资源导入时候使用 th:去替换原有的资源路径!

<!--“/”默认就是statics目录-->
<link th:href="@{/css/style.css}" rel="stylesheet" />

</head>
<body>

<script th:src="@{/js/anime.min.js}"></script>

页面国际化

**第一步 :**编写国际化配置文件,抽取页面需要显示的国际化页面消息。我们可以去登录页面查看一下

先在IDEA中统一设置properties的编码问题!

img

我们在resources资源文件下新建一个i18n目录,新建Resource Bundle
在这里插入图片描述
弹出如下页面:我们添加一个中文&英文的;

img

这样就OJBK了

img

接下来,我们就来编写配置,我们可以看到idea下面有另外一个视图;

img

这个视图我们点击 + 号就可以直接添加属性了;我们新建一个login.tip,可以看到边上有三个文件框可以输入

img

我们添加一下首页的内容!

在这里插入图片描述

默认

login.btn=提交
login.email=邮箱
login.password=密码
login.tip=登录

英文

login.btn=Submit
login.email=Email
login.password=Password
login.tip=Sign in

中文

login.btn=提交
login.email=邮箱
login.password=密码
login.tip=登录

OK,搞定!

第二步 :我们去看一下SpringBoot对国际化的自动配置!

这里又涉及到一个类: MessageSourceAutoConfiguration ,里面有一个方法,这里发现SpringBoot已经自动配置好了管理我们国际化资源文件的组件 ResourceBundleMessageSource;

public class MessageSourceAutoConfiguration {
    private static final Resource[] NO_RESOURCES = new Resource[0];

    public MessageSourceAutoConfiguration() {
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.messages") //我们的配置文件可以直接放在类路径下叫: messages.properties, 就可以进行国际化操作了
    public MessageSourceProperties messageSourceProperties() {
        return new MessageSourceProperties();
    }

    @Bean
    public MessageSource messageSource(MessageSourceProperties properties) {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        if (StringUtils.hasText(properties.getBasename())) {
        //设置国际化文件的基础名(去掉语言国家代码的)
            messageSource.setBasenames(StringUtils.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(properties.getBasename())));
        }

        if (properties.getEncoding() != null) {
            messageSource.setDefaultEncoding(properties.getEncoding().name());
        }

        messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale());
        Duration cacheDuration = properties.getCacheDuration();
        if (cacheDuration != null) {
            messageSource.setCacheMillis(cacheDuration.toMillis());
        }

        messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat());
        messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage());
        return messageSource;
    }
}

我们真实 的情况是放在了i18n目录下,所以我们要去配置这个messages的路径;

spring.messages.basename=i18n.login

第三步 : 去页面获取国际化的值;

查看Thymeleaf的文档,找到message取值操作为: #{…}。

我们去页面测试下;

img

其余同理!IDEA还有提示,非常智能的!

我们可以去打开项目,访问一下,发现已经自动识别为中文的了!

在这里插入图片描述

但是我们想要更好!可以根据按钮自动切换中文英文!

在Spring中有一个国际化的Locale (区域信息对象);里面有一个叫做LocaleResolver (获取区域信息对象)的解析器

我们去自己写一个自己的LocaleResolver,可以在链接上携带区域信息!

修改一下前端页面的跳转连接;

//这里传入参数不需要使用 ?  使用 (key=value)
<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>

我们去写一个处理的组件类

public class MyLocalResolver implements LocaleResolver{
    @Override
    public Locale resolveLocale(HttpServletRequest request) {
        String language = request.getParameter("l");
        Locale locale = Locale.getDefault(); //如果没有就使用默认的
        if (!StringUtils.isEmpty(language)) {
            String[] split = language.split("_");
            locale = new Locale(split[0], split[1]);
        }

        return locale;
    }

    @Override
    public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {

    }
}

为了让我们的区域化信息能够生效,我们需要再配置一下这个组件!在我们自己的MvcConofig下添加bean;

    @Bean
    public LocaleResolver localeResolver(){
        return new MyLocaleResolver();
    }

我们重启项目,来访问一下,发现点击按钮可以实现成功切换!
在这里插入图片描述

index.html

<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<title>简洁又炫酷的登录表单</title>

<link th:href="@{/css/style.css}" rel="stylesheet" />

</head>
<body>

<script th:src="@{/js/anime.min.js}"></script>

<div class="page">
    <div class="container">
        <div class="left">
            <div class="login">[[#{login.tip}]]</div>
            <div class="eula">欢迎使用!</div>
        </div>
        <div class="right">
            <svg viewBox="0 0 320 300">
        <defs>
          <linearGradient
                          inkscape:collect="always"
                          id="linearGradient"
                          x1="13"
                          y1="193.49992"
                          x2="307"
                          y2="193.49992"
                          gradientUnits="userSpaceOnUse">
            <stop
                  style="stop-color:#ff00ff;"
                  offset="0"
                  id="stop876" />
            <stop
                  style="stop-color:#ff0000;"
                  offset="1"
                  id="stop878" />
          </linearGradient>
        </defs>
        <path d="m 40,120.00016 239.99984,-3.2e-4 c 0,0 24.99263,0.79932 25.00016,35.00016 0.008,34.20084 -25.00016,35 -25.00016,35 h -239.99984 c 0,-0.0205 -25,4.01348 -25,38.5 0,34.48652 25,38.5 25,38.5 h 215 c 0,0 20,-0.99604 20,-25 0,-24.00396 -20,-25 -20,-25 h -190 c 0,0 -20,1.71033 -20,25 0,24.00396 20,25 20,25 h 168.57143" />
      </svg>
            <div class="form">
                <label for="email" th:text="#{login.email}"></label>
                <input type="email" id="email">
                <label for="password" th:text="#{login.password}">密码</label>
                <input type="password" id="password">
                <!--<button type="submit">[[#{login.btn}]]</button>-->
                <input type="submit" th:value="#{login.btn}">
                <a th:href="@{/index.html(l='zh_CN')}">中文</a>
                <a th:href="@{/index.html(l='en_US')}">English</a>
            </div>
        </div>
    </div>
</div>

<script>
var current = null;
document.querySelector('#email').addEventListener('focus', function(e) {
  if (current) current.pause();
  current = anime({
    targets: 'path',
    strokeDashoffset: {
      value: 0,
      duration: 700,
      easing: 'easeOutQuart'
    },
    strokeDasharray: {
      value: '240 1386',
      duration: 700,
      easing: 'easeOutQuart'
    }
  });
});
document.querySelector('#password').addEventListener('focus', function(e) {
  if (current) current.pause();
  current = anime({
    targets: 'path',
    strokeDashoffset: {
      value: -336,
      duration: 700,
      easing: 'easeOutQuart'
    },
    strokeDasharray: {
      value: '240 1386',
      duration: 700,
      easing: 'easeOutQuart'
    }
  });
});
document.querySelector('#submit').addEventListener('focus', function(e) {
  if (current) current.pause();
  current = anime({
    targets: 'path',
    strokeDashoffset: {
      value: -730,
      duration: 700,
      easing: 'easeOutQuart'
    },
    strokeDasharray: {
      value: '530 1386',
      duration: 700,
      easing: 'easeOutQuart'
    }
  });
});</script>

</body>
</html>

登录 + 拦截器

我们这里就先不连接数据库了,输入任意用户名都可以登录成功!

声明一个之前没有提到的问题:

templates下的页面只能通过Controller跳转实现,而static下的页面是能直接被外界访问的,就能正常访问了。

我们把登录页面的表单提交地址写一个controller!

<form class="form" th:action="@{/user/login}" method="post">

去编写对应的controller

@Controller
public class LoginController{
    @RequestMapping("/user/login")
    public String login(@RequestParam("email") String email, @RequestParam("password") String password, Model model) {

        if (!StringUtils.isEmpty(email) && "111111".equals(password)) {
            model.addAttribute("loginUser",email);
            return "redirect:/main.html";
        }else {
            model.addAttribute("msg", "用户名或者密码错误");
            return "index";
        }

    }
}

页面存在缓存,所以我们需要禁用模板引擎的缓存

#禁用模板缓存
spring:
  thymeleaf:
    cache: false

模板引擎修改后,想要实时生效!页面修改完毕后,IDEA小技巧 : Ctrl + F9 重新编译!

OK ,测试登录成功!如果模板出现500错误,参考处理连接:https://blog.csdn.net/fengzyf/article/details/83341479

登录成功,发现了静态资源导入问题!

登录失败的话,我们需要将后台信息输出到前台,可以在首页标题下面加上判断!

<!--判断是否显示,使用if, ${}可以使用工具类,可以看thymeleaf的中文文档-->
<p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>

刷新测试 :

在这里插入图片描述

我们再添加一个视图控制映射,在我们的自己的MyMvcConfig中**:**

registry.addViewController("/main.html").setViewName("main");

将 Controller 的代码改为重定向;

//登录成功!防止表单重复提交,我们重定向
return "redirect:/main.html";

重定向成功之后!我们解决了之前资源没有加载进来的问题!后台主页正常显示!

但是又发现新的问题,我们可以直接登录到后台主页,不用登录也可以实现!

怎么处理这个问题呢?我们可以使用拦截器机制,实现登录检查!

我们先自定义一个拦截器

public class LoginHandleInterceptor 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;
        }

        return true;
    }
}

然后将拦截器注册到我们的SpringMVC配置类当中!

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

我们然后在后台主页,获取用户登录的信息

<!--后台主页显示登录用户的信息-->
[[${session.loginUser}]]

然后我们登录测试

在这里插入图片描述

整合Mybatis

新建一个项目
在这里插入图片描述

在这里插入图片描述

导入依赖

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

自定义数据源 DruidDataSource

DRUID 简介

Druid 是阿里巴巴开源平台上一个数据库连接池实现,结合了 C3P0、DBCP、PROXOOL 等 DB 池的优点,同时加入了日志监控。

Druid 可以很好的监控 DB 池连接和 SQL 的执行情况,天生就是针对监控而生的 DB 连接池。

Spring Boot 2.0 以上默认使用 Hikari 数据源,可以说 Hikari 与 Driud 都是当前 Java Web 上最优秀的数据源,我们来重点介绍 Spring Boot 如何集成 Druid 数据源,如何实现数据库监控。

com.alibaba.druid.pool.DruidDataSource 基本配置参数如下:

配置缺省值说明
name配置这个属性的意义在于,如果存在多个数据源,监控的时候可以通过名字来区分开来。 如果没有配置,将会生成一个名字,格式是:“DataSource-” + System.identityHashCode(this). 另外配置此属性至少在1.0.5版本中是不起作用的,强行设置name会出错 详情-点此处
url连接数据库的url,不同数据库不一样。例如: mysql : jdbc:mysql://10.20.153.104:3306/druid2 oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto
username连接数据库的用户名
password连接数据库的密码。如果你不希望密码直接写在配置文件中,可以使用ConfigFilter。详细看这里:https://github.com/alibaba/druid/wiki/%E4%BD%BF%E7%94%A8ConfigFilter
driverClassName根据url自动识别这一项可配可不配,如果不配置druid会根据url自动识别dbType,然后选择相应的driverClassName
initialSize0初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时
maxActive8最大连接池数量
maxIdle8已经不再使用,配置了也没效果
minIdle最小连接池数量
maxWait获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。
poolPreparedStatementsfalse是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
maxOpenPreparedStatements-1要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100
validationQuery用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。
validationQueryTimeout单位:秒,检测连接是否有效的超时时间。底层调用jdbc Statement对象的void setQueryTimeout(int seconds)方法
testOnBorrowtrue申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
testOnReturnfalse归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
testWhileIdlefalse建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
timeBetweenEvictionRunsMillis1分钟(1.0.14)有两个含义: 1) Destroy线程会检测连接的间隔时间,如果连接空闲时间大于等于minEvictableIdleTimeMillis则关闭物理连接 2) testWhileIdle的判断依据,详细看testWhileIdle属性的说明
numTestsPerEvictionRun不再使用,一个DruidDataSource只支持一个EvictionRun
minEvictableIdleTimeMillis30分钟(1.0.14)连接保持空闲而不被驱逐的最长时间
connectionInitSqls物理连接初始化的时候执行的sql
exceptionSorter根据dbType自动识别当数据库抛出一些不可恢复的异常时,抛弃连接
filters属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有: 监控统计用的filter:stat 日志用的filter:log4j 防御sql注入的filter:wall
proxyFilters类型是List<com.alibaba.druid.filter.Filter>,如果同时配置了filters和proxyFilters,是组合关系,并非替换关系

引入数据源

第一步需要在应用的 pom.xml 文件中添加上 Druid 数据源依赖,而这个依赖可以从 Maven 仓库官网 Maven Repository 中获取

<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.12</version>
</dependency>

现在我们去切换数据源;

之前已经说过 Spring Boot 2.0 以上默认使用 com.zaxxer.hikari.HikariDataSource 数据源,但可以 通过 spring.datasource.type 指定数据源。

切换成功!既然切换成功,就可以设置数据源连接初始化大小、最大连接数、等待时间、最小连接数 等设置项;

我们可以配置一些参数来测试一下;

spring:
  datasource:
    username: root
    password: 111111
    url: jdbc:mysql://localhost:3306/mybatis?useSSl=true&useUnicode=true&characterEncoding=UTF-8&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

log4j日志依赖

        <!-- https://mvnrepository.com/artifact/log4j/log4j -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>

现在需要程序员自己为 com.alibaba.druid.pool.DruidDataSource 绑定全局配置文件中的参数,再添加到容器中,而不再使用 Spring Boot 的自动生成了;我们需要 自己添加 DruidDataSource 组件到容器中,并绑定属性;

package com.microsoft.springbootmybatis.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

@Configuration
public class DruidConfig {

    /*
       将自定义的 Druid数据源添加到容器中,不再让 Spring Boot 自动创建
       绑定全局配置文件中的 druid 数据源属性到 com.alibaba.druid.pool.DruidDataSource从而让它们生效
       @ConfigurationProperties(prefix = "spring.datasource"):作用就是将 全局配置文件中
       前缀为 spring.datasource的属性值注入到 com.alibaba.druid.pool.DruidDataSource 的同名参数中
     */
    @ConfigurationProperties(prefix = "spring.datasource")
    @Bean
    public DataSource druidDataSource() {
        return new DruidDataSource();
    }
}

去测试类中测试一下;看是否成功!

public class SpringbootDemoDataApplicationTests {

    //注入数据源
    @Autowired
    DataSource dataSource;

    @Test
    public void contextLoads() throws SQLException {
        //看一下默认数据源
        System.out.println(dataSource.getClass());
        //获得连接
        Connection connection =   dataSource.getConnection();
        System.out.println(connection);

        DruidDataSource druidDataSource = (DruidDataSource) dataSource;
        System.out.println("druidDataSource 数据源最大连接数:" + druidDataSource.getMaxActive());
        System.out.println("druidDataSource 数据源初始化连接数:" + druidDataSource.getInitialSize());

        //关闭连接
        connection.close();
    }

}

输出结果 :可见配置参数已经生效

在这里插入图片描述

配置 Druid 数据源监控

Druid 数据源具有监控的功能,并提供了一个 web 界面方便用户查看,类似安装 路由器 时,人家也提供了一个默认的 web 页面。

所以第一步需要设置 Druid 的后台管理页面,比如 登录账号、密码 等;配置后台管理;

配置完毕后,我们可以选择访问 : http://localhost:8080/druid/login.html

img

进入之后

img

配置 Druid web 监控 filter

这个过滤器的作用就是统计 web 应用请求中所有的数据库信息,比如 发出的 sql 语句,sql 执行的时间、请求次数、请求的 url 地址、以及seesion 监控、数据库表的访问次数 等等。

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

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

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

配置完毕后,我们可以启动来进行测试!

我们发送一条sql语句,然后来看一下后台的消息;

img

测试OK!

SpringBoot 整合mybatis

1. 导入mybatis所需要的依赖

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

2,创建实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private int id;
    private String username;
    private String password;
}

3.配置Mapper接口类

@Mapper
@Repository
public interface UserMapper {
    List<User> queryUserList();

    User queryUserById(int id);

    int addUser(User user);

    int deleteUser(int id);

    int updateUser(User user);
}

4.Mapper映射文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.microsoft.springbootmybatis.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,username,password) values (#{id},#{username},#{password})
    </insert>

    <update id="updateUser" parameterType="User">
    update mybatis.user set username=#{username},pwd=#{password} where id = #{id}
    </update>

    <delete id="deleteUser" parameterType="int">
    delete from mybatis.user where id = #{id}
    </delete>
</mapper>

5.SpringBoot 整合!

以前 MyBatis 未与 spring 整合时,配置数据源、事务、连接数据库的账号、密码等都是在 myBatis 核心配置文件中进行的
myBatis 与 spring 整合后,配置数据源、事务、连接数据库的账号、密码等就交由 spring 管理。因此,在这里我们即使不使用mybatis配置文件也完全ok!
既然已经提供了 myBatis 的映射配置文件,自然要告诉 spring boot 这些文件的位置

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

已经说过 spring boot 官方并没有提供 myBaits 的启动器,是 myBatis 官方提供的开发包来适配的 spring boot,从 pom.xml 文件中的依赖包名也能看出来,并非是以 spring-boot 开头的;

同理上面全局配置文件中的这两行配置也是以 mybatis 开头 而非 spring 开头也充分说明这些都是 myBatis 官方提供的

6.编写controller

@RestController
public class UserController {

    @Autowired
    private UserMapper userMapper;

    //选择全部用户
    @GetMapping("/selectUser")
    public String selectUser(){
        List<User> users = userMapper.selectUser();
        for (User user : users) {
            System.out.println(user);
        }

        return "ok";
    }
    //根据id选择用户
    @GetMapping("/selectUserById")
    public String selectUserById(){
        User user = userMapper.selectUserById(1);
        System.out.println(user);
        return "ok";
    }
    //添加一个用户
    @GetMapping("/addUser")
    public String addUser(){
        userMapper.addUser(new User(5,"阿毛","456789"));
        return "ok";
    }
    //修改一个用户
    @GetMapping("/updateUser")
    public String updateUser(){
        userMapper.updateUser(new User(5,"阿毛","421319"));
        return "ok";
    }
    //根据id删除用户
    @GetMapping("/deleteUser")
    public String deleteUser(){
        userMapper.deleteUser(5);
        return "ok";
    }

}

整合Swagger

参考我的博客:https://blog.csdn.net/qq_42295733/article/details/104112555

邮件任务

导包

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

邮件配置

spring:
  mail:
    default-encoding: UTF-8
    protocol: smtp
    host: smtp.163.com
    username: ***@163.com
    password: ***  #邮箱密钥,并非密码
    properties:
      mail:
        smtp:
          auth: true
          starttls:
            enable: true
            required: true

邮件发送实体

@Data
@NoArgsConstructor
@Builder
@AllArgsConstructor
public class SendEmail implements Serializable {

    /**
     * 邮件接收方,可多人
     */
    private String[] tos;
    /**
     * 邮件主题
     */
    private String subject;
    /**
     * 邮件内容
     */
    private String content;
    /**
     * 邮件发送方
     */
    @Value("${spring.mail.username}")
    private String from;
}

发送业务类

@Service
@Slf4j
public class EmailService {

    @Value("${spring.mail.username}")
    private String from;
    @Autowired
    private JavaMailSenderImpl mailSender;


    /**
     * 发送文本邮件
     * @param email
     * @return
     */
    public Boolean sendEmail(SendEmail email){
        MimeMessage mailMessage = mailSender.createMimeMessage();
        MimeMessageHelper mimeMessageHelper;
        try {
            mimeMessageHelper = new MimeMessageHelper(mailMessage,true,"UTF-8");
            mimeMessageHelper.setText(email.getContent());
            mimeMessageHelper.setSubject(email.getSubject());
            mimeMessageHelper.setFrom(from);
            mimeMessageHelper.setTo(email.getTos());
            mailSender.send(mailMessage);
            log.info("发送邮件成功");
        }catch (MessagingException e){
            log.error("发送邮件发生异常", e);
            return false;
        }
        return true;
    }
}

测试:封装好SendEmail,配置文件完成,直接调用sendMail函数即可

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

nan feng

打赏一杯咖啡吧

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值