SpringBoot的完整学习

springBoot

  1. 配置如何编写 yaml
  2. 自动装配原理
  3. 集成web开发:业务的核心
  4. 集成数据库:Druid
  5. 分布式开发:Dubbo+zookeeper
  6. swagger:接口文档
  7. 任务调度
  8. SpringSecunity Shiro

1、微服务

微服务架构打破之前的all in one 的架构方式,把每个功能元素对出来。

好处:

  1. 节省 了调用资源
  2. 每个功能元素的服务都是一个可替代、可独立升级的软件代码

1.1 HelloWorld

可以从Spring Initializr上下载一个项目,耶可以从IDEA中创建springboot项目!

要在Application的同级目录下建包,不然不会生效!

在这里插入图片描述

在pom.xml里有一个web依赖:用来启动tomcat

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

springboot所有的依赖都是以spring-boot-starter开头!

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.7.1</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.liu</groupId>
	<artifactId>helloWorld</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>helloWorld</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<java.version>1.8</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

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

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

  • 如上所示:主要分为四部分
  • 项目元数据信息:maven项目的基本元素:gav,name,description等
  • parent:继承spring-boot-starter-parent的依赖管理、控制版本和打包等
  • dependencies:项目具体依赖,这里包含了web依赖用于实现http接口(包含了springmvc)
  • build:构建配置部分:默认使用spring-boot-maven-plugin,配合spring-boot-starter-parent就可以把springboot应用打包成jar来运行。

1.2 打包成jar包

在maven的lifecycle里有一个package可以打包成jar包!

在这里插入图片描述

在target项目下可以得到生成的jar包!在这里插入图片描述

1.3 IDEA创建springboot项目

第一步:新建项目

在这里插入图片描述

第二步:选择spring-web项目

在这里插入图片描述

1.4 更改项目的端口号

在这里插入图片描述

1.5 更改banner

在resources目录下新建banner.txt文件

放入你要生成的banner图示:

在这里插入图片描述

2、原理

2.1、自动配置

pom.xml

  • spring-boot-dependencies:核心依赖在父工程中

  • 依赖不需要配置版本号是因为有版本仓库

启动器

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
</dependency>
  • 启动器:就是springBoot的启动场景

  • 要使用什么功能就找到对应的启动器就可以了start

主程序

@SpringBootApplication : 标注这个类是一个springboot的应用
    
@SpringBootConfiguration:springboot的配置类,启动类下的所有资源被导入
    @Configuration:配置
         @Component:组件
@EnableAutoConfiguration:自动配置
    @AutoConfigurationPackage:自动配置包
         @Import({Registrar.class}):导入`包注册`
    @Import({AutoConfigurationImportSelector.class}) :自动导入选择器
//获取所有的配置
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);

//获取候选的配置的方法
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    List<String> configurations = new ArrayList(SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()));
    ImportCandidates.load(AutoConfiguration.class, this.getBeanClassLoader()).forEach(configurations::add);
    Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you are using a custom packaging, make sure that file is correct.");
    return configurations;
}
META-INF/spring.factories:自动配置的核心文件

在这里插入图片描述

2.2 底层原理:

在这里插入图片描述

getAutoConfigurationEntry:获取自动配置的实体
getCandidateConfigurations:获取候选的配置
    protected Class<?> getAnnotationClass() {
        return EnableAutoConfiguration.class;
    } 
List<String> getCandidateConfigurations 所有的加载的配置
loadSpringFactories:项目资源:"META-INF/spring.factories"
                     系统资源
                     从这些资源中遍历所有的element并封装成properties供我们使用

2.3 结论:

  • springboot所有的自动配置都在启动类中被扫描并加载:spring.factories所有的自动配置类都在这里,但是不一定生效,要判断条件是否成立,只要导入了对应的start,就有对应的启动器了,有了启动器,自动装配就生效,然后配置成功!
  1. springboot在启动的时候,从类路径下META-INF/spring.factories获取指定的值
  2. 将这些自动配置的类导入容器,自动配置就会生效,进行自动配置
  3. 整合javaEE,解决方案和自动配置的东西都在spring-boot-autoconfigure-2.2.0.RELEASE.jar包下
  4. 它会把所有需要导入的组件以类名的方式返回,这些组件就会被添加到容器中
  5. 容器中也会存在非常多的xxxAutoConfiguration的文件(@Bean),就是这些类给容器中导入了这个场景需要的所有组件并自动配置

2.4 Run

@SpringBootApplication
public class Springboot01HelloworldApplication {

    //该方法返回一个configurableApplicationContext对象
    //参数一:应用入口的类   参数类:命令行传参
    public static void main(String[] args) {
        SpringApplication.run(Springboot01HelloworldApplication.class, args);
    }

}

2.5 springApplication

这个类主要做了以下几件事:

  1. 推断应用的类型是普通的项目还是web项目
  2. 查找并加载所有可用初始化器,设置到initializers属性中
  3. 找出所有的应用程序监听器,设置到listeners属性中,获取上下文,处理bean
  4. 推断并设置main方法的定义类,找到运行的主类

查看构造器

    this.sources = new LinkedHashSet();
    this.bannerMode = Mode.CONSOLE;
    this.logStartupInfo = true;
    this.addCommandLineProperties = true;
    this.addConversionService = true;
    this.headless = true;
    this.registerShutdownHook = true;
    this.additionalProfiles = Collections.emptySet();
    this.isCustomEnvironment = false;
    this.lazyInitialization = false;
    this.applicationContextFactory = ApplicationContextFactory.DEFAULT;
    this.applicationStartup = ApplicationStartup.DEFAULT;
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    this.bootstrapRegistryInitializers = new ArrayList(this.getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
    this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
    this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = this.deduceMainApplicationClass();

3、springboot配置

3.1 配置文件

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

  • application.properties
    • 语法结构:key = value
  • application.yml
    • 语法结构:key: 空格 value
  • 配置文件的作用:修改springboot自动配置的默认值,因为springboot在底层给我们自动配置好了

3.2 yaml

1.格式

server:
  port: 8080

2.标记语言

  • 以前的配置文件大多数使用xml来配置,比如一个简单的端口配置
  • xml配置:
<server>
   <port>8081</port>
</server>
# 普通的key-value
name: liuxiang

# 对象
student:
  name: liuxiang
  age: 3

#行内写法
student1: {name: liuxiang,age: 3}

#数组
pets:
  - cat
  - dog
  - pig

pets1: [cat,dog,pig]

3.3 给实体类赋值

1.原生的赋值:需要给每一个属性赋值,麻烦

  @Value("旺财")
    private String name;
    @Value("3")
    private  Integer age;

2.用yaml赋值

person:
  name: liuxiang${random.uuid}
  age: ${random.int}
  happy: true
  birth: 1997/12/12
  maps: {k1: v1,k2: v2}
  list:
    - code
    - music
    - boy
  dog:
    name: ${person.hello:hello}_旺财 #前面值如果不存在就选择后面的值
    age: 3

在这里插入图片描述

springboot测试:

package com.liu;

import com.liu.pojo.Dog;
import com.liu.pojo.Person;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class Springboot02ConfigApplicationTests {
    @Autowired
    private Person person;

    @Test
    void contextLoads() {
        System.out.println(person);
    }
}

结果:注入成功!

Person{name='liuxiang', age=18, happy=true, birth=Fri Dec 12 00:00:00 CST 1997, maps={k1=v1, k2=v2}, list=[code, music, boy], dog=Dog{name='旺财', age=3}}

实体类:

package com.liu.pojo;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

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

@Component
@ConfigurationProperties(prefix = "person")
/* 
@ConfigurationProperties作用:
将配置文件中配置的每一个属性的值映射到这个组件中
告诉springboot将本类中的所有属性和配置文件中相关的配置进行绑定
参数prefix = "person":将配置文件中的person下面的所有属性一一对应

只有这个组件是容器中的组件才能用@ConfigurationProperties
*/
public class Person {

    private String name;
    private Integer age;
    private Boolean happy;
    private Date birth;
    private Map<String,Object> maps;
    private List<Object> list;
    private Dog dog;

    public Person() {
    }

    public Person(String name, Integer age, Boolean happy, Date birth, Map<String, Object> maps, List<Object> list, Dog dog) {
        this.name = name;
        this.age = age;
        this.happy = happy;
        this.birth = birth;
        this.maps = maps;
        this.list = list;
        this.dog = dog;
    }

  • 松散绑定:比如yaml中写的last-name,和lastName是一样的,-后面跟着的字母默认是大写的
  • JSR303数据校验,可以在字段增加一层过滤器验证,可以保证数据的合法性
  • 复杂类型封装,yml中可以封装对象,使用@value就不支持

3.4 JSR303校验

@Validated //数据校验

参考配置:

@Null 限制只能为null
@NotNull 限制必须不为null
@AssertFalse 限制必须为false
@AssertTrue 限制必须为true
@DecimalMax(value) 限制必须为一个不大于指定值的数字
@DecimalMin(value) 限制必须为一个不小于指定值的数字
@Digits(integer,fraction) 限制必须为一个小数,且整数部分的位数不能超过integer,小数部分的位数不能超过fraction
@Future 限制必须是一个将来的日期
@Max(value) 限制必须为一个不大于指定值的数字
@Min(value) 限制必须为一个不小于指定值的数字
@Past 限制必须是一个过去的日期
@Pattern(value) 限制必须符合指定的正则表达式
@Size(max,min) 限制字符长度必须在min到max之间
@Past 验证注解的元素值(日期类型)比当前时间早
@NotEmpty 验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0)
@NotBlank 验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的空格
@Email 验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式

4、多环境配置及配置文件地址

配置文件存放地址:

  1. file:./config/ : 项目目录下的config下的优先级最高
  2. file:./ :优先级其次
  3. classpath:/config/:优先级第三
  4. classpath:/:优先级最低

在这里插入图片描述

application.properties文件:

# springboot的多环境配置:可以选择激活哪一个配置文件
spring.profiles.active=dev

application-dev.properties 线下环境:

server.port=8082

application-test.properties 测试环境:

server.port=8081

yaml配置实现:(推荐)

server:
  port: 8081
spring:
  profiles:
    active: dev
---
server:
  port: 8082
spring:
  profiles:dev

---
server:
  port: 8083
spring:
  profiles:dev

5、自动配置再理解

在这里插入图片描述

一定要满足条件才会生效,导入相关依赖,找到对应的start启动器即可!

配置文件yaml和spring.factories的关系:

  • 都是通过ConfigurationProperties(prefix = “xxx”)来实现
  • xxxAutoConfiguration:有默认值,都有一个xxxProperties的文件和配置文件绑定,就可以使用自定义的配置了
  • 这样配置文件就可以动态的修改spring的内容

5.1 自动装配的原理

  1. springboot启动会加载大量的自动配置类
  2. 看我们需要的功能有没有再springboot默认写好的自动配置类当中
  3. 再看这个自动配置类中到底配置了哪些组件(要用的在其中就不需要再配置了)
  4. 给容器中自动配置类添加组件时,会从properties类中获取某些属性,我们只需要在配置文件中指定这些属性的值即可
  5. xxxAutoConfiguration:自动配置类;给容器中添加组件
  6. xxxProperties:封装配置文件中相关属性

可以在yaml文件下通过debug=true来查看,哪些自动配置类生效了,一部分生效,一部分不生效,选择最好的

debug: true

6、springBoot Web开发

要解决的问题:

  • 导入静态资源
  • 首页
  • jsp,模板引擎Thymeleaf
  • 装配扩展springmvc
  • 增删改查
  • 拦截器

6.1、静态资源导入

在这里插入图片描述

这四个文件c夹下的所有的资源都可以被访问到,例如:

在这里插入图片描述

优先级:

resources>static>public

  • 在springboot还可以通过webjars访问:localhost:8080/webjars/

6.2 thymeleaf模板引擎

  • jsp就是一个模板引擎

  • 前端页面变量报红添加注释

  • <!--suppress ThymeleafVariablesResolveInspection -->
    

引入thymeleaf:

  1. Thymeleaf

  2. GitHub - thymeleaf/thymeleaf: Thymeleaf is a modern server-side Java template engine for both web and standalone environments.

  3. Spring Boot Reference Guide spring导入start文档

依赖jar包:

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

结论:

需要使用thymeleaf,只需要将html放在templates下,在controller下设置跳转页面路径即可

  • 用thymeleaf需要导入头文件约束 xmlns:th=“http://www.thymeleaf.org”

  • 超连接,文本等等需要放在th下

  • 常用命名空间:

  • <xmlns:th="http://www.thymeleaf.org">
    <xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
    <xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
    
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<!--用thymeleaf需要导入头文件约束 xmlns:th="http://www.thymeleaf.org"-->
<!--所有的html元素都可以被thymeleaf替代,用th:元素名-->
<div th:text="${msg}"></div>
<div th:utext="${msg}"></div> <!--转义-->

<hr>
<!--遍历数组-->
<h3 th:each="user:${users}" th:text="${user}"></h3>

</body>
</html>

变量表达式:

  • 变量:${}:与EL表达式一样

  • 消息:#{}

  • URL:@{}

  • Fragment:~{}:片段表达式

  • 文本:‘’

  • 数字:1,2

  • 布尔值:true

  • Null:null

文本操作:

  • string:+

数学运算:

  • +,-,*,/,%

其他操作:

  • 等于操作:==,!=

  • 比较:>,<,<=,>=

  • 三元运算符:(if) ? (then) ? (else)

6.3、装配扩展springMVC

29. Developing Web Applications (spring.io)

If you want to keep Spring Boot MVC features and you want to add additional MVC configuration (interceptors, formatters, view controllers, and other features), you can add your own @Configuration class of type WebMvcConfigurer but without @EnableWebMvc. If you wish to provide custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or ExceptionHandlerExceptionResolver, you can declare a WebMvcRegistrationsAdapter instance to provide such components.

视图解析器的源码:

  • 在springboot2.7.1版本中,将此部分改成了判断是否有下一个视图,有就添加
  • 采用了迭代器而不是之前的全部遍历
  • 获取候选的,再选择最好的视图
public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport implements ViewResolver, Ordered, InitializingBean{}
private List<View> getCandidateViews(String viewName, Locale locale, List<MediaType> requestedMediaTypes) throws Exception {
List<View> candidateViews = new ArrayList();
if (this.viewResolvers != null) {
    Assert.state(this.contentNegotiationManager != null, "No ContentNegotiationManager set");
    Iterator var5 = this.viewResolvers.iterator();

    while(var5.hasNext()) {
        ViewResolver viewResolver = (ViewResolver)var5.next();
        View view = viewResolver.resolveViewName(viewName, locale);
        if (view != null) {
            candidateViews.add(view);
        }

        Iterator var8 = requestedMediaTypes.iterator();

自定义视图解析器:继承视图解析器的接口并注册到bean中会自动装配

package com.liu.config;


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.Locale;

//全面扩展mvc
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
    //ViewResolver 实现了视图解析器接口的类,可以把它看作视图解析器
    @Bean
    public ViewResolver myViewResolver(){
        return new MyViewResolver();
    }

    //自定义一个自己的视图解析器MyViewResolver
    public static class MyViewResolver implements ViewResolver{
        @Override
        public View resolveViewName(String viewName, Locale locale) throws Exception {
            return null;
        }
    }
}

格式Formatter

public String getDateFormat() {
        return this.format.getDate();
    }

可以再yml文件中自己配置日期格式!

  • springboot再自动配置很多组件的时候,先看容器中有没有用户自己配置的(如果有用户自己配置的@bean),就用用户的,没有就用自动配置的,比如视图解析器,将用户配置的和自己默认的组合起来作为候选的,再选择最好的。

视图跳转

//扩展springMVC,官方这样操作
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
    //视图跳转
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/liu").setViewName("test");
    }
}

@EnableWebMvc注解:自动配置的时候不能加这个注解

@EnableWebMvc //导入了这个类DelegatingWebMvcConfiguration  作用:从容器中获取所有的webmvc config

原因:在WebMvcAutoConfiguration这个类中有三个条件满足才生效,而在EnableWebMvc这个注解当中导入了这个类DelegatingWebMvcConfiguration,不满足自动装配的条件了

@ConditionalOnWebApplication(
    type = Type.SERVLET
)
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})
@ConditionalOnMissingBean({WebMvcConfigurationSupport.class})

总结:

  • 在springboot中,有许多的xxxConfiguration配置类帮助进行扩展配置,看见这个就是改变了原始的配置,要注意扩展了什么功能!

7、员工管理系统

  • 关闭模板引擎的缓存
#关闭模板引擎的缓存
spring.thymeleaf.cache=false

7.1 首页配置

  • 所有的静态资源都需要用thymeleaf接管,链接用@{}

7.2 国际化(中英文切换)

要确保全部是UTF-8格式!

在这里插入图片描述

可视化配置:

在这里插入图片描述

页面的每个原始都需要这么配置:

在这里插入图片描述

在yml文件中配置真实的配置文件地址:

#真实配置文件
spring.messages.basename=i18n.login

国际化消息用:#{}取值

在这里插入图片描述

7.2.1 自定义国际化组件

前端的两个跳转链接:

<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>
package com.liu.config;

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

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

public class MyLocaleResolver implements LocaleResolver {

    //解析请求
    @Override
    public Locale resolveLocale(HttpServletRequest request) {
        //获取请求中的语言参数 带l都走这个请求
        String language = request.getParameter("l");

        Locale locale = Locale.getDefault();//默认的,如果没有就使用默认的

        //如果请求的链接携带了地区化的参数
        if (!StringUtils.isEmpty(language)){
            //zh_CN 分割键值对
            String[] split = language.split("_");
            //国家地区
            locale = new Locale(split[0], split[1]);
        }
        return locale;
    }

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

    }
}

要将这个写的类注册到spring中:@Bean

package com.liu.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

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

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

步骤:

  1. 需要配置i18n文件
  2. 如果需要在项目进行按钮自动切换,需要自定义一个组件LocaleResolver
  3. 将组件配置到spring容器中@Bean
  4. #{}

7.3 登录功能

index.html

<p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>
  • 判断条件格式:

  • ${#strings.isEmpty(name)}
    ${#strings.arrayIsEmpty(nameArr)}
    ${# strings.listIsEmpty(nameList)}
    ${# strings.setIsEmpty(nameSet)}
    

后端controller层:

  • 接收前端name属性传过来的值,用@RequestParam保证路径不出错!
  • main.html在myMvcConfig文件中设置了访问到dashboard.html页面!
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

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

    //自定义的国际化组件
    @Bean
    public LocaleResolver localeResolver(){
        return new MyLocaleResolver();
    }
}
@Controller
public class LoginController {

    @RequestMapping("/user/login")
    public String login(@RequestParam("username") String username, @RequestParam("password") String password, Model model, HttpSession session){
        if (!StringUtils.isEmpty(username) && "123456".equals(password)){

            //登陆成功,session
            session.setAttribute("loginUser",username);
            return "redirect:/main.html";
        }else {
            model.addAttribute("msg","用户名或者密码错误!");
            return "index";
        }
    }

}

7.4 拦截器

package com.liu.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 {
    //return true 放行 反之不放行
    @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;
        }

    }

}
  • 到spring中注册Bean,也就是带@Configuration注解,继承了WebMvcConfigurer的类
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

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

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

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

7.5 前端侧边栏

实现代码的复用:用thymeleaf模板进行片段的插入

将公共部分的代码提取到common文件下:

<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0" th:fragment="topbar">
<nav class="col-md-2 d-none d-md-block bg-light sidebar" th:fragment="sidebar">

dashboard.html:侧边栏

<div th:replace="~{/common/commons::sidebar}"></div>

list.html:在同样的位置进行插入片段!用~{}方式

<div th:replace="~{/common/commons::sidebar}"></div>

dashboard.html:导航栏

<div th:replace="~{/common/commons::topbar}"></div>

list.html:在同样的位置进行插入片段!用~{}方式

<div th:replace="~{/common/commons::topbar}"></div>
  • fragment inclusion的两种方式
    • th:insert
    • th:replace

7.6 提取公共页面

commons.html

<a th:class="${active=='main.html'?'nav-link active':'nav-link'}" th:href="@{/index.html}">
<a th:class="${active=='list.html'?'nav-link active':'nav-link'}" th:href="@{/emps}">

dashboard.html

<div th:replace="~{/common/commons::sidebar(active='main.html')}"></div>

list.html

<div th:replace="~{/common/commons::sidebar(active='list.html')}"></div>

thymeleaf前端变量报红的解决办法:在html页面的头部加上下面这个注释

<!--suppress ThymeleafVariablesResolveInspection -->

7.7 展示数据库数据

<thead>
<tr>
    <th>id</th>
    <th>lastName</th>
    <th>email</th>
    <th>gender</th>
    <th>department</th>
    <th>birth</th>
    <th>操作</th>
</tr>
</thead>
<tbody>
<tr th:each="emp:${emps}">
    <td th:text="${emp.getId()}"></td>
    <td>[[${emp.getLastName()}]]</td>
    <td th:text="${emp.getEmail()}"></td>
    <td th:text="${emp.getGender()==0?'':''}"></td>
    <td th:text="${emp.department.getDepartmentName()}"></td>
    <td th:text="${#dates.format(epm.getBirth(),'yyyy-MM-dd HH:mm:ss')}"></td>
    <td>
    <a class="btn btn-sm btn-primary" th:href="@{'/emp/'+${emp.getId()}}">编辑</a>
    <a class="btn btn-sm btn-danger">删除</a>
    </td>
</tr>
</tbody>

日期转换:去参考thymeleaf官方文档

  • 地址:Tutorial: Using Thymeleaf

  • ${#dates.format(date, 'dd/MMM/yyyy HH:mm')}
    ${#dates.arrayFormat(datesArray, 'dd/MMM/yyyy HH:mm')}
    ${#dates.listFormat(datesList, 'dd/MMM/yyyy HH:mm')}
    ${#dates.setFormat(datesSet, 'dd/MMM/yyyy HH:mm')}
    

7.8 添加员工

  1. 按钮提交
  2. 跳转到添加页面
  3. 添加员工成功
  4. 返回首页

问题报错:

Failed to convert property value of type ‘java.lang.String’ to required type ‘java.util.Date’ for property ‘birth’;

解决:

在pojo实体类的属性上添加注解:

@DateTimeFormat(pattern = "yyyy-MM-dd")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")

或者在配置文件中修改:

spring.mvc.date-format=yyyy-MM-dd
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<form th:action="@{emp}" method="post">
    <div class="form-group">
        <label>LastName</label>
        <input type="text" name="lastName" class="form-control" placeholder="name">
    </div>
    <div class="form-group">
        <label>Email</label>
        <input type="email" name="email" class="form-control" placeholder="email">
    </div>
    <div class="form-group">
        <label>Gender</label>
        <div class="form-check form-check-inline">
            <input class="form-check-input" type="radio" name="gender" value="1">
            <label class="form-check-label"></label>
        </div>
        <div class="form-check form-check-inline">
            <input class="form-check-input" type="radio" name="gender" value="0">
            <label class="form-check-label"></label>
        </div>
    </div>
    <div class="form-group">
        <label>department</label>
        <select class="form-control" name="department.id">
            <option th:each="dept:${departments}" th:text="${dept.getDepartmentName()}" th:value="${dept.getId()}"></option>
        </select>
    </div>
    <div class="form-group">
        <label>Birth</label>
        <input type="text" name="birth" class="form-control" placeholder="2022-07-13">
    </div>

    <button type="submit" class="btn btn-default btn-success">添加</button>
</form>
</main>
@GetMapping("/emp")
    public String toAddPage(Model model){
        //查出所有部门的信息
        Collection<Department> departments = departmentDao.getDepartments();
        model.addAttribute("departments",departments);
        return "emp/add";
    }

    @PostMapping("/emp")
    public String addEmp(Employee employee){
        //添加的操作
        System.out.println("add=>"+employee);
        employeeDao.add(employee);//保存员工信息
        return "redirect:/emps";
    }

7.9 修改员工信息

list.html页面

<td>
    <a class="btn btn-sm btn-primary" th:href="@{'/emp/'+${emp.getId()}}">编辑</a>
</td>
  • 因为有自增id,所以要隐藏,不然一修改就自动新增了!
    <main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
    <form th:action="@{updateEmp}" method="post">
    <input type="hidden" name="${id}" th:value="${emp.getId()}">
    <div class="form-group">
        <label>LastName</label>
        <input th:value="${emp.getLastName()}" type="text" name="lastName" class="form-control" placeholder="name">
    </div>
    <div class="form-group">
        <label>Email</label>
        <input th:value="${emp.getEmail()}" type="email" name="email" class="form-control" placeholder="email">
    </div>
    <div class="form-group">
        <label>Gender</label>
        <div class="form-check form-check-inline">
            <input th:checked="${emp.getGender()==1}" class="form-check-input" type="radio" name="gender" value="1">
            <label class="form-check-label"></label>
        </div>
        <div class="form-check form-check-inline">
            <input th:checked="${emp.getGender()==0}" class="form-check-input" type="radio" name="gender" value="0">
            <label class="form-check-label"></label>
        </div>
    </div>
    <div class="form-group">
        <label>department</label>
        <select class="form-control" name="department.id">
            <option th:selected="${dept.getId()==emp.department.getId()}"
                    th:each="dept:${departments}" th:text="${dept.getDepartmentName()}"
                    th:value="${dept.getId()}"></option>
        </select>
    </div>
    <div class="form-group">
        <label>Birth</label>
        <input th:value="${#dates.format(emp.getBirth(),'yyyy-MM-dd')}" type="text" name="birth" class="form-control" placeholder="2022-07-13">
    </div>

    <button type="submit" class="btn btn-default btn-success">修改</button>
    </form>
    </main>
//跳转到修改页面
    @GetMapping("/emp/{id}") //restful风格
    public String tuUpdateEmp(@PathVariable("id")Integer id,Model model){
        //查出原来的数据
        Employee employeeById = employeeDao.getEmployeeById(id);
        model.addAttribute("emp",employeeById);

        //查询部门所有信息
        Collection<Department> departments = departmentDao.getDepartments();
        model.addAttribute("departments",departments);

        return "emp/update";
    }

    @PostMapping("/updateEmp")
    public String updateEmp(Employee employee){
        employeeDao.add(employee);//修改员工信息
        return "redirect:/emps";
    }

7.10 删除员工

index.html

<a class="btn btn-sm btn-danger" th:href="@{'/delete/'+${emp.getId()}}">删除</a>
 //删除员工信息
    @RequestMapping("/delete/{id}")
    public String deleteById(@PathVariable("id")Integer id){
        employeeDao.deleteEmployeeById(id);
        return "redirect:/emps";
    }

8、Data

对于数据访问层,无论是SQL还是NOsql,springboot底层都是采用springData的方式进行统一处理。

springData官网:https://spring.io/projects/spring-data

数据库相关的启动器:官方文档:Spring Boot Reference Guide

8.1、JDBC

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
  • Could not autowire. No beans of ‘DataSource’ type found问题解决办法:

  • @Autowired(required = false)
    

配置文件:

  • DataSourceProperties
  • 对于的DataSourceAutoConfiguration

8.2 SpringBoot封装JDBC

spring:
datasource:
username: root
password: 123456789
url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver #数据源HikariDataSource
package com.liu.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(required = false)
    JdbcTemplate jdbcTemplate;

    //查询数据库的所有信息
    //无实体类时用万能的map查询数据
    @GetMapping("/userList")
    public List<Map<String,Object>> userList(){
        String sql = "select * from mybatis.user";
        List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
        return maps;
    }

    @GetMapping("/addUser")
    public String addUser(){
        String sql = "insert into mybatis.user(id,name,pwd) values (5,'小明','12345678')";
        int i = jdbcTemplate.update(sql);
        return "addOK!";
    }

    @GetMapping("/updateUser/{id}")
    public String updateUser(@PathVariable("id")int id){
        String sql = "update mybatis.user set name=?,pwd=? where id="+id;

        //封装数据
        Object[] objects = new Object[2];
        objects[0] = "小华";    //修改的名字
        objects[1] = "zxczxc"; //密码
        jdbcTemplate.update(sql,objects);

        return "updateOK";
    }

    @GetMapping("/deleteUser/{id}")
    public String deleteUser(@PathVariable("id")int id){
        String sql = "delete from mybatis.user where id=?";
        jdbcTemplate.update(sql,id);
        return "deleteOK!";
    }

}

8.3 数据源Druid

  • 阿里巴巴平台的数据库连接池实现,结合C3P0,DBCP,PROXOOL等DB池的有点,同时加入日志监控。

  • 天生针对监控而生的DB连接池

  • HikariDataSource是速度最快的数据源

依赖包:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.21</version>
</dependency>
<!--log4j的依赖包-->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

yaml配置中修改数据源的使用:只用加type即可

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

    initial-size: 5
    min-idle: 5
    max-active: 20
    max-wait: 60000
    time-between-eviction-runs-millis: 60000
    min-evictable-idle-time-millis: 300000
    validation-query: SELECT 1 FROM DUAL
    test-while-idle: true
    test-on-borrow: false
    test-on-return: false
    pool-prepared-statements: true
    filters: stat,wall,log4j
    max-pool-prepared-statement-per-connection-size: 20
    use-global-data-source-stat: true
    connect-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

测试数据源:

@SpringBootTest
class Springboot04DataApplicationTests {
    @Autowired(required = false)
    DataSource dataSource;
    @Test
    void contextLoads() throws SQLException {
        System.out.println(dataSource.getClass());
        //获得数据库连接
        Connection connection = dataSource.getConnection();
        System.out.println(connection);
        connection.close();
    }
}

结果:

在这里插入图片描述

强大之处:自动配置

建立一个自动配置的文件

  • 松散绑定:不用注意大小写,下划线什么的
@Configuration
public class DruidConfig {

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

与yaml配置文件绑定:只需要再前缀处加入数据源的名称和@Bean注解

@ConfigurationProperties(prefix = "spring.datasource")
@Bean

8.3.1 后台监控

  • 因为springboot内置了servlet容器,所以没有web.xml,替代方法:ServletRegistrationBean
//后台监控
//因为springboot内置了servlet容器,所以没有web.xml,替代方法:ServletRegistrationBean
    @Bean
    public ServletRegistrationBean StatViewServlet(){
        ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");

        //后台需要登录,账号密码配置
        Map<String,String> initParameters = new HashMap<>();

        //增加配置
        initParameters.put("loginUsername","admin");//登录的key是固定的loginUsername  loginPassword
        initParameters.put("loginPassword","123456");

        //允许谁可以访问
        initParameters.put("allow","");

        //静止谁访问
        initParameters.put("liuxiang","192.168.11.123");

        bean.setInitParameters(initParameters);//设置初始参数
        return bean;

        @Bean
    public FilterRegistrationBean webStatFilter() {
        FilterRegistrationBean bean = new FilterRegistrationBean();
        bean.setFilter(new WebStatFilter());
        Map<String, String> map = new HashMap<>();
        map.put("exclusions", "*.js*,*.css,/druid/*");
        bean.setInitParameters(map);
        return bean;
    }
    }

访问druid monitor自动跳转到login,html页面

输入用户名和密码进入后台监控:

在这里插入图片描述

出现filter下的数据即可监控统计sql了

在这里插入图片描述

当发起后台sql请求时,可以看到监控统计:

在这里插入图片描述

9、整合mybatis

导入依赖包:

不是springboot官方的,不是以spring开头

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

以前是写一个mapper和一个对应的mapper.xml,现在统一放在resource目录下

在这里插入图片描述

配置文件下:

#整合mybatis
#别名
mybatis.type-aliases-package=com.liu.pojo 
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml

UserMapper

@org.apache.ibatis.annotations.Mapper
@Repository
public interface Mapper {

    List<User> queryUserList();

    User queryUserById(int id);

    int updateUser(User user);

    int addUser(User user);

    int deleteUser(int id);

}

UserMapper.xml

<?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.liu.mapper.Mapper">

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

    <select id="queryUserById" resultType="User">
        select *
        from mybatis.user
        where user.id=#{id};
    </select>

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

    <insert id="addUser" parameterType="User">
        insert into mybatis.user(id, name, pwd)
        values (#{id},#{name},#{pwd});
    </insert>
</mapper>

controller层

@RestController
public class UserController {

    @Autowired(required = false)
    private UserMapper userMapper;

    @GetMapping("/queryUserList")
    public List<User> queryUserList(){
        List<User> userList = userMapper.queryUserList();
        return userList;
    }
    @GetMapping("/queryUserById/{id}")
    public User queryUserById(@PathVariable("id") int id){
        User user = userMapper.queryUserById(id);
        return user;
    }

    @GetMapping("/updateUser")
    public String updateUser(){
        userMapper.updateUser(new User(2,"hyt","3423213"));
        return "OK";
    }

    @GetMapping("/addUser")
    public String addUser(){
        userMapper.addUser(new User(4,"dsajda","esdfsdf"));
        return "OK";
    }

    @GetMapping("/deleteUser")
    public String deleteUser(){
        userMapper.deleteUser(4);
        return "ok";
    }
}

10、SpringSecurity(安全)

依赖包

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

认证授权和权限分级

  • 要用数据库连接,在认证部分需要用JDBC连接,而不是在内存中,用Autowired注解自动注入数据源
package com.liu.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;

//AOP思想
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //首页所有人可以访问,功能页只有对应有权限的人才能访问
        //授权
        http.authorizeHttpRequests().antMatchers("/").permitAll()
        .antMatchers("/level1/**").hasRole("vip1")
        .antMatchers("/level2/**").hasRole("vip2")
        .antMatchers("/level3/**").hasRole("vip3");

        //没有权限默认会登录页面
        http.formLogin();
        //注销
        http.logout();
    }

    //认证
    //密码编码:passwordEncoder
    //在spring Security 5.0+ 新增很多加密方法
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
                .withUser("liuxiang")
                .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");
    }
}

controller层

@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;
    }

}

更改前端页面的图标icon

推荐一个网站:https://semantic-ui.com/elements/icon.html

如下所示前端代码即可更改样式:

<a class="item" th:href="@{/toLogin}">
    <i class="hand point right icon"></i> 登录
</a>
<!--注销-->
<a class="item" th:href="@{/logout}">
    <i class="share square icon"></i> 注销
</a>

thymeleaf和springsecurity的整合

导依赖包

<!-- https://mvnrepository.com/artifact/org.thymeleaf.extras/thymeleaf-extras-springsecurity4 -->
<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity4</artifactId>
    <version>3.0.4.RELEASE</version>
</dependency>

导入命名空间:

<html lang="en" xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/extras/spring-security">"
  • springboot版本太高,无法显示!

11、Shiro

  • Apache Shiro是Java的安全(权限)框架
  • 下载地址:http://shiro.apache.org/

功能:

  • Authentication:认证
  • Authorization:授权
  • session management:session管理
  • cryptograhy:加密
  • web support:web支持
  • 缓存

11.1、HelloWorld

1.导入依赖

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

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
            <version>1.7.21</version>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.21</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
    </dependencies>

2.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

3.Quickstart


import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.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) {
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        SecurityManager securityManager = factory.getInstance();
        SecurityUtils.setSecurityManager(securityManager);

        // get the currently executing user:
        //获取当前用户对象subject
        Subject currentUser = SecurityUtils.getSubject();

        // 通过当前用户拿到session
        Session session = currentUser.getSession();
        session.setAttribute("someKey", "aValue");
        String value = (String) session.getAttribute("someKey");
        if (value.equals("aValue")) {
            log.info("Subject =>session! [" + 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);
    }
}

4.log4j.properties

#输出日志文件到console和file目的地
log4j.rootLogger=DEBUG,console,file

# 控制台(console)
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.Target=System.out
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%c]-%m%n

# 文件输出的相关设置(file)
log4j.appender.file=org.apache.log4j.RollingFileAppender
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.File=D:/logs/log.log4j
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p] [%d{yy-MM-dd}][%c]%m%n

#日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG

总结方法:

 //1.获取当前用户对象subject
Subject currentUser = SecurityUtils.getSubject();
// 2.通过当前用户拿到session
Session session = currentUser.getSession();
// 3.判断当前的用户是否被认证
if (!currentUser.isAuthenticated()){UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");}
// 4.获得当前用户的认证
currentUser.getPrincipal()
// 5.拥有什么角色
if (currentUser.hasRole("schwartz")){}
// 6.获取什么权限
 if (currentUser.isPermitted("lightsaber:wield")) {}
//7.注销
currentUser.logout();

SpringBoot中集成Shiro

三大对象:(面试必问)

  1. subject:用户
  2. securityManager:管理所有用户
  3. realm:连接数据

导入依赖:

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

index.html

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

<h1>首页</h1>

<div th:if="${session.loginUser==null}">
    <a th:href="@{toLogin}">登录</a>
</div>
<p th:text="${msg}"></p>
<hr>
<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>

连接数据部分:授权和认证UserRealm

public class UserRealm extends AuthorizingRealm {
    @Autowired
    UserService userService;

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

        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 token) throws AuthenticationException {
        System.out.println("执行了=>认证doGetAuthenticationInfo");

        UsernamePasswordToken userToken = (UsernamePasswordToken) token;
        //用户名,密码:连接数据库
        User user = userService.queryUserByName(userToken.getUsername());
        if (user==null){
            return null;//抛去用户名不存在的异常
        }
        Subject currentSubject = SecurityUtils.getSubject();
        Session session = currentSubject.getSession();
        session.setAttribute("loginUser",user);
        //密码认证:加密,MD5
        return new SimpleAuthenticationInfo(user,user.getPwd(),"");
    }
}

Controller层

@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){
        //获取当前用户
        Subject subject = SecurityUtils.getSubject();
        //封装用户的登录数据
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        //登录
        try {
            subject.login(token);
            return "index";
        } catch (UnknownAccountException e){
            model.addAttribute("msg","用户名错误");
            return "login";
        } catch (IncorrectCredentialsException e){
            model.addAttribute("msg","密码错误");
            return "login";
        }
    }

    @RequestMapping("/unauth")
    @ResponseBody
    public String unAuthor(){
        return "未经授权不予登录!";
    }

}

shiroConfig

@Configuration
public class ShiroConfig {

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

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

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

        //拦截请求
        filterChainDefinitionMap.put("/user/*","authc");
        bean.setFilterChainDefinitionMap(filterChainDefinitionMap);

        //设置登录的请求
        bean.setLoginUrl("/toLogin");
        //设置未被认证的请求
        bean.setUnauthorizedUrl("/unauth");


        return bean;
    }

    //2.DefaultWebSecurityManager
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //关联Realm
        securityManager.setRealm(userRealm);
        return securityManager;
    }

    //1.创建Realm对象,需要自定义
    @Bean
    public UserRealm userRealm(){
        return new UserRealm();
    }

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

}

连接数据库,用druid数据源,整合mybatis,整合shiro

导入依赖

<dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.8</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.0</version>
        </dependency>
		<dependency>
            <groupId>com.github.theborakompanioni</groupId>
            <artifactId>thymeleaf-extras-shiro</artifactId>
            <version>2.0.0</version>
        </dependency>

编写配置文件:yaml

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

    initial-size: 5
    min-idle: 5
    max-active: 20
    max-wait: 60000
    time-between-eviction-runs-millis: 60000
    min-evictable-idle-time-millis: 300000
    validation-query: SELECT 1 FROM DUAL
    test-while-idle: true
    test-on-borrow: false
    test-on-return: false
    pool-prepared-statements: true
    filters: stat,wall,log4j
    max-pool-prepared-statement-per-connection-size: 20
    use-global-data-source-stat: true
    connect-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
    
mybatis:
  type-aliases-package: com.liu.pojo
  mapper-locations: classpath:mapper/*.xml

User

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private int id;
    private String pwd;
    private String name;
    private String perms;//权限表
}

UserMapper接口

@Mapper
@Repository
public interface UserMapper {
    public User queryUserByName(String name);
}

UserMapper.xml

<?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.liu.mapper.UserMapper">

    <select id="queryUserByName" resultType="User">
        select *
        from mybatis.user
        where user.name=#{name};
    </select>

</mapper>

service接口

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

service实现类

@Service
public class UserServiceImpl implements UserService{

    //调mapper层
    @Autowired
    UserMapper userMapper;

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

12、Swagger

官网:API Documentation & Design Tools for Teams | Swagger

前后端分离:

  • 前端测试后端接口:postman
  • 后端提供接口

api框架:

  • RestFul API文档在线自动生成工具—api文档与api定义同步更新
  • 直接运行,可以在线测试api接口

在项目中使用swagger需要springbox:

  • swagger2
  • ui

12.1、springBoot集成Swagger

依赖包

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

配置swagger:

@Configuration
@EnableSwagger2 //开启swagger2
public class SwaggerConfig {
}

产生的问题:Failed to start bean ‘documentationPluginsBootstrapper’; nested exception is java.lang.NullPointerException

因为版本不兼容

解决:

  • 在springboot的配置文件中配置如下:因为高版本后的路径匹配修改了

  • spring:
      mvc:
        pathmatch:
          matching-strategy: ant_path_matcher
    
  • 降低springboot版本到2.5.6

测试运行:Swagger UI

12.2、配置swagger

  • Swagger的bean实例 Docket
@Configuration
@EnableSwagger2 //开启swagger2
public class SwaggerConfig {

    @Bean
    public Docket docket(){
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo());
    }

    //配置swagger信息=apiInfo
    private ApiInfo apiInfo(){
        //作者信息
        Contact contact = new Contact("liuxiang", "http://localhost:8080/", "971223772@qq.com");
        return new ApiInfo("刘想的swagger日记",
                "牛牛的Java学习之旅",
                "1.0",
                "http://localhost:8080/",
                contact,
                "Apache 2.0",
                "http://www.apache.org/licenses/LICENSE-2.0",
                new ArrayList());
    }

}

Swagger配置扫描接口

 @Bean
    public Docket docket(){
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                //basePackage:指定要扫描的包
                //any():扫描全部
                //none():不扫描
                //withClassAnnotation():扫描类上的注解
                //withMethodAnnotation():扫描方法上的注解
                .apis(RequestHandlerSelectors.basePackage("com.liu.swagger.controller"))
                //paths():过滤路径
                .paths(PathSelectors.ant("/liu/**"))
                .build();
    }

配置是否启动swagger

@Bean
public Docket docket(){
    return new Docket(DocumentationType.SWAGGER_2)
            .apiInfo(apiInfo())
            .enable(false) //是否启动swagger
            .select()
          .apis(RequestHandlerSelectors.basePackage("com.liu.swagger.controller"))
            .build();
}

测试题

如果想要在生产环境下开启swagger,在测试环境下不开启

方法:

  • 通过设置两套模式application-dev.properties和application-prod.properties,分别配置不同的端口号,在application.properties中选择开启哪套环境

  • spring.profiles.active=dev
    
  • 在swaggerConfig中配置:

  • @Bean
    public Docket docket(Environment environment){
    
    //设置要显示的swagger环境
    Profiles profiles = Profiles.of("dev", "test");
    //通过environment.acceptsProfiles判断是否处在设定的环境下
    boolean flag = environment.acceptsProfiles(profiles);
    
    return new Docket(DocumentationType.SWAGGER_2)
            .apiInfo(apiInfo())
            .enable(flag)
            .select()
            .apis(RequestHandlerSelectors.basePackage("com.liu.swagger.controller"))
            .build();
    }
    

配置API文档分组

配置多个分组,多个Docket即可!

@Bean
    public Docket docket1(){
        return new Docket(DocumentationType.SWAGGER_2).groupName("A");
    }
    @Bean
    public Docket docket2(){ return new Docket(DocumentationType.SWAGGER_2).groupName("B"); }
    @Bean
    public Docket docket3(){
        return new Docket(DocumentationType.SWAGGER_2).groupName("C");
    }

在这里插入图片描述

实体类配置

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

}

controller

//只要接口中的返回值存在实体类,就会被扫描到swagger中
@PostMapping(value = "/user")
    public User user(){
        return new User();
    }

其他的注解

@ApiOperation("hello控制类") //用在方法上
    @GetMapping(value = "/hello")
    public String hello(@ApiParam("用户名") String username){
        return "hello"+username;
    }

结果:有中文了

在这里插入图片描述

13、任务

13.1 异步任务

在方法上加注解@Async

@Service
public class AsyncService {

    //告诉spring这是一个异步的方法
    @Async
    public void hello(){
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("数据正在处理中");
    }
}

在main方法上加注解@EnableAsync开启异步功能

@EnableAsync
@SpringBootApplication
public class Springboot10TestApplication {
    public static void main(String[] args) {
        SpringApplication.run(Springboot10TestApplication.class, args);
    }
}

就可以一瞬间响应,无需等待!

13.2 邮件任务

导入依赖

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

一个简单的邮件

1.先配置相关信息,先在qq邮件设置里将POP3/SMTP服务开启

spring.mail.username=971223772@qq.com
spring.mail.password=zyvegqhkctuobeif
spring.mail.host=smtp.qq.com
#开启加密验证
spring.mail.properties.mail.smtp,ssl.enable=true

2.测试

@SpringBootTest
class Springboot10TestApplicationTests {

    @Autowired(required = false)
    JavaMailSenderImpl mailSender;

    @Test
    void contextLoads() {
        SimpleMailMessage mailMessage = new SimpleMailMessage();

        mailMessage.setSubject("牛牛的Java学习之旅!");
        mailMessage.setText("加油继续努力!");
        mailMessage.setTo("971223772@qq.com");
        mailMessage.setFrom("971223772@qq.com");

        mailSender.send(mailMessage);

    }

}

一个复杂的邮件发送

@SpringBootTest
class Springboot10TestApplicationTests {

    @Autowired(required = false)
    JavaMailSenderImpl mailSender;

    @Test
    void contextLoads() throws MessagingException {

        MimeMessage mimeMessage = mailSender.createMimeMessage();
        //组件
        MimeMessageHelper messageHelper = new MimeMessageHelper(mimeMessage,true);
        //设置主题
        messageHelper.setSubject("牛牛的Java的学习");
        messageHelper.setText("<p style='color:red'>继续加油学习!冲鸭!</p>",true);
        //附件 绝对地址
        messageHelper.addAttachment("1.jpg",new File("C:\\Users\\liuxiang\\Desktop\\1.jpg"));
        messageHelper.setTo("971223772@qq.com");
        messageHelper.setFrom("971223772@qq.com");
        mailSender.send(mimeMessage);
    }
}

封装成工具类

 //封装成工具类
    /**
     *
     * @param html
     * @param subject
     * @param text
     * @param fileName
     * @param fileUrl
     * @param ReceiveAddress
     * @param sendAddress
     * @throws MessagingException
     * @Author liuxiang
     */
    public void sendMail(Boolean html,String subject,String text,String fileName,String fileUrl,String ReceiveAddress,String sendAddress) throws MessagingException{

        MimeMessage mimeMessage = mailSender.createMimeMessage();
        //组件
        MimeMessageHelper messageHelper = new MimeMessageHelper(mimeMessage,html);
        //设置主题
        messageHelper.setSubject(subject);
        messageHelper.setText(text,html);
        //附件 绝对地址
        messageHelper.addAttachment(fileName,new File(fileUrl));
        messageHelper.setTo(ReceiveAddress);
        messageHelper.setFrom(sendAddress);
        mailSender.send(mimeMessage);
    }

13.3 定时任务

  1. 在main线程开启定时功能的注解
@EnableAsync
@EnableScheduling //开始定时功能的注解
@SpringBootApplication
public class Springboot10TestApplication {
    public static void main(String[] args) {
        SpringApplication.run(Springboot10TestApplication.class, args);
    }
}
  1. 使用 @Scheduled(cron = “0 * * * * 0-7”)注解和cron表达式
@Service
public class ScheduleService {

    //在一个特定的事件执行这个方法
    //cron表达式 任务调度
    //秒 分 时 日 月 周几
    @Scheduled(cron = "0 * * * * 0-7")
    public void hello(){
        System.out.println("hello");
    }
}

文件上传和下载

springMVC中没有装配MultipartResolver,所以默认情况下不能处理文件上传工作,如果想要使用文件上传功能,需要在上下文中配置MultipartResolver。

前端表单要求:必须将表单的method设置为POST,并将enctype设置为multipart/form-data,只有在这种情况下,浏览器才会把用户选择的文件以二进制数据发送给服务器。

  • application/x-www=form-urlencoded:默认方式,只处理表单域中的value属性值,采用这种编码方式的表单会将表单域中的值处理成URL编码方式。
  • multipart/form-data:这种编码方式会以二进制流的方式来处理表单数据,这种编码丰富会把文件域指定文件的内容也封装到请求参数中,不会对字符编码
  • text/plain:除了把空格转换为"+"号外,其他字符不作编码处理,适用 直接通过表单发送右键
<form action="${pageContext.request.contextPath}/upload" enctype="multipart/form-data" method="post">
    <input type="file" name="file">
    <input type="submit">
  </form>

后端导入文件上传的jar包:commons-fileupload

<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.3</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>

在resources目录下的springmvc-servlet.xml配置:

<!--文件上传配置-->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <!--请求的编码格式,必须和jsp的pageEncoding属性一致,默认是ISO-8859-1-->
        <property name="defaultEncoding" value="utf-8"/>
        <!--上传文件大小上限,单位为字节(10485760=10M)-->
        <property name="maxUploadSize" value="10485760"/>
        <property name="maxInMemorySize" value="40960"/>
    </bean>

controller层

package com.liu.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.commons.CommonsMultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;

@RestController
public class FileController {

    //@RequestMapping("file") 将name=file控件得到的文件封装成CommonsMultipartFile对象
    //批量上传CommonsMultipartFile则为数组即可
    @RequestMapping("/upload")
    public String fileUpLoad(@RequestParam("file")CommonsMultipartFile file, HttpServletRequest request) throws IOException {

        //获取文件名:file.getOriginalFilename();
        String uploadFileName = file.getOriginalFilename();

        //如果文件名为空,直接回到首页
        if ("".equals(uploadFileName)){
            return "redirect:/index.jsp";
        }
        System.out.println("上传文件名:"+uploadFileName);

        //上传路径保存设置
        String path = request.getServletContext().getRealPath("/upload");
        //如果路径不存在,创建一个
        File realPath = new File(path);
        if (!realPath.exists()){
            realPath.mkdir();
        }
        System.out.println("上传文件保存地址:"+realPath);

        InputStream is = file.getInputStream();//文件输入流
        FileOutputStream os = new FileOutputStream(new File(realPath, uploadFileName));//输出流

        //读取写出
        int len = 0;
        byte[] buffer = new byte[1024];
        while ((len=is.read(buffer))!=-1){
            os.write(buffer,0,len);
            os.flush();
        }
        os.close();
        is.close();

        return "redirect:/index.jsp";
    }
}

方法二:

  //采用file.TransferTo来保存上传的文件
    @RequestMapping("/upload2")
    public String fileUpLoad2(@RequestParam("file")CommonsMultipartFile file, HttpServletRequest request) throws IOException {

        //上传路径保存设置
        String path = request.getServletContext().getRealPath("/upload");
        //如果路径不存在,创建一个
        File realPath = new File(path);
        if (!realPath.exists()) {
            realPath.mkdir();
        }
        System.out.println("上传文件保存地址:"+realPath);

        //通过CommonsMultipartFile的方法直接写文件
        file.transferTo(new File(realPath +"/" + file.getOriginalFilename()));

        return "redirect:/index.jsp";
    }

文件下载

  1. 设置response响应头
  2. 读取文件InputStream
  3. 写出文件OutputStream
  4. 执行操作
  5. 关闭流
//下载图片方法
    @RequestMapping("/downLoad")
    public String downLoads(HttpServletResponse response,HttpServletRequest request) throws IOException {
        //要下载的图片
        String path = request.getServletContext().getRealPath("/upload");
        String fileName = "基础语法.jpg";

        //1.设置response响应头
        response.reset();//设置页面不缓存,清空buffer
        response.setCharacterEncoding("utf-8");
        response.setContentType("multipart/form-data");//二进制传输数据
        //设置响应头
        response.setHeader("Content-Disposition",
                "attachment;fileName="+ URLEncoder.encode(fileName,"UTF-8"));
        File file = new File(path, fileName);
        //2.读取文件--输入流
        InputStream is = new FileInputStream(file);
        //3.写出文件-输出流
        OutputStream fos = response.getOutputStream();

        byte[] buffer = new byte[1024];
        int index = 0;
        while ((index = is.read(buffer))!=-1){
            fos.write(buffer,0,index);
            fos.flush();
        }

        fos.close();
        is.close();

        return null;
    }
 <a href="${pageContext.request.contextPath}/downLoad">下载图片</a>

14、分布式Dubbo+Zookeeper+SpringBoot

分布式理论:

分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像单个相关系统

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

dubbo官方文档:文档 | Apache Dubbo

ORM单一应用架构:

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

缺点:

  • 性能扩展难
  • 协同开发问题
  • 不利于维护升级

垂直应用架构:

当访问量逐渐变大,单一应用增加机器带来的加速度越来越小,将应用拆分不相干的几个应用,以提升效率,MVC架构是关键

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

分布式服务架构:

将核心业务提取出来作为独立的服务,组件形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求,RPC分布式服务框架是关键。

流动计算架构:

小服务资源的浪费等问题产生,需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。提高机器利用率的资源调度和治理中心(SOA)是关键

什么是RPC

RPC:Remote Procedure Call 远程过程调用

  • 允许程序调用另外一个地址空间的过程或函数,而不用程序员显示编码这个远程调用的细节。

  • 核心:通讯、序列化

在这里插入图片描述

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

Consumer:o调用远程服务的服务消费方,服务消费者在启动时向注册中心订阅自己所需的服务

Registry:注册中心返回服务提供者地址列表给消费者

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

Dubbo和zookeeper的安装

zookeeper下载地址:

http://archive.apache.org/dist/zookeeper/zookeeper-3.4.14/zookeeper-3.4.14.tar.gz

管理员身份进入cmd命令:

在这里插入图片描述

可能遇到的问题:闪退

解决:

  • 将conf文件夹下面的zoo_sample.cfg复制一份改名未zoo.cfg即可
  • zookeeper的端口号:clientPort=2181

dubbo-admin下载

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

端口号:7001

在项目目录下打包dubbo-admin

D:\Environment\dubbo-admin-master>mvn clean package -Dmaven.test.skip=true

出现build success即可

在这里插入图片描述

cmd命令下执行jar包:

java -jar dubbo-admin-server-0.3.0.jar

账户密码是 root-root

服务注册

  • 不用上述的下载也可以,通过导入jar包即可
<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo-spring-boot-starter</artifactId>
    <version>3.0.9</version>
</dependency>
<!--zookeeper-->
<dependency>
    <groupId>com.101tec</groupId>
    <artifactId>zkclient</artifactId>
    <version>0.2</version>
</dependency>

需要排除日志,不然会起冲突,报错:

<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>
        <exclusions>
            <exclusion>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-log4j12</artifactId>
            </exclusion>
        </exclusions>
    </dependency>

注册中心:

server.port=8001

#服务应用名字
dubbo.application.name=privider-server
#注册中心地址
dubbo.registry.address=zookeeper://127.0.0.1:2181
#哪些服务要被注册
dubbo.scan.base-packages=com.liu.service

service层:

@DubboService
@Component  //使用了dubbo后不要使用service
public class TicketServiceImpl implements TicketService{
    @Override
    public String getTicket() {
        return "llx";
    }
}

消费者配置:

server.port=8002

#消费者应用名字
dubbo.application.name=consumer-server

#注册中心的地址
dubbo.registry.address=zookeeper://127.0.0.1.2181

service

@Service //是spring的注解service
public class UserService {
    //想拿到provider-service的票,去注册中心拿到服务
    @Reference //定义路径相同的的接口名 引用 从远程注入服务
    TicketService ticketService;
    public void buyTicket(){
        String ticket = ticketService.getTicket();
        System.out.println(ticket);
    }
}

复利用,开发性的浪费

分布式服务架构:

将核心业务提取出来作为独立的服务,组件形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求,RPC分布式服务框架是关键。

流动计算架构:

小服务资源的浪费等问题产生,需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。提高机器利用率的资源调度和治理中心(SOA)是关键

什么是RPC

RPC:Remote Procedure Call 远程过程调用

  • 允许程序调用另外一个地址空间的过程或函数,而不用程序员显示编码这个远程调用的细节。

  • 核心:通讯、序列化

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

Consumer:o调用远程服务的服务消费方,服务消费者在启动时向注册中心订阅自己所需的服务

Registry:注册中心返回服务提供者地址列表给消费者

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

Dubbo和zookeeper的安装

zookeeper下载地址:

http://archive.apache.org/dist/zookeeper/zookeeper-3.4.14/zookeeper-3.4.14.tar.gz

管理员身份进入cmd命令:

[外链图片转存中…(img-oR9kCsl1-1658244095119)]

可能遇到的问题:闪退

解决:

  • 将conf文件夹下面的zoo_sample.cfg复制一份改名未zoo.cfg即可
  • zookeeper的端口号:clientPort=2181

dubbo-admin下载

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

端口号:7001

在项目目录下打包dubbo-admin

D:\Environment\dubbo-admin-master>mvn clean package -Dmaven.test.skip=true

出现build success即可

[外链图片转存中…(img-Qmxxx9gy-1658244095119)]

cmd命令下执行jar包:

java -jar dubbo-admin-server-0.3.0.jar

账户密码是 root-root

服务注册

  • 不用上述的下载也可以,通过导入jar包即可
<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo-spring-boot-starter</artifactId>
    <version>3.0.9</version>
</dependency>
<!--zookeeper-->
<dependency>
    <groupId>com.101tec</groupId>
    <artifactId>zkclient</artifactId>
    <version>0.2</version>
</dependency>

需要排除日志,不然会起冲突,报错:

<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>
        <exclusions>
            <exclusion>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-log4j12</artifactId>
            </exclusion>
        </exclusions>
    </dependency>

注册中心:

server.port=8001

#服务应用名字
dubbo.application.name=privider-server
#注册中心地址
dubbo.registry.address=zookeeper://127.0.0.1:2181
#哪些服务要被注册
dubbo.scan.base-packages=com.liu.service

service层:

@DubboService
@Component  //使用了dubbo后不要使用service
public class TicketServiceImpl implements TicketService{
    @Override
    public String getTicket() {
        return "llx";
    }
}

消费者配置:

server.port=8002

#消费者应用名字
dubbo.application.name=consumer-server

#注册中心的地址
dubbo.registry.address=zookeeper://127.0.0.1.2181

service

@Service //是spring的注解service
public class UserService {
    //想拿到provider-service的票,去注册中心拿到服务
    @Reference //定义路径相同的的接口名 引用 从远程注入服务
    TicketService ticketService;
    public void buyTicket(){
        String ticket = ticketService.getTicket();
        System.out.println(ticket);
    }
}
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当前课程中博客项目的实战源码是我在 GitHub上开源项目 My-Blog,目前已有 3000 多个 star:本课程是一个 Spring Boot 技术栈的实战类课程,课程共分为 3 大部分,前面两个部分为基础环境准备和相关概念介绍,第三个部分是 Spring Boot 个人博客项目功能的讲解,通过本课程的学习,不仅仅让你掌握基本的 Spring Boot 开发能力以及 Spring Boot 项目的大部分开发使用场景,同时帮你提前甄别和处理掉将要遇到的技术难点,认真学完这个课程后,你将会对 Spring Boot 有更加深入而全面的了解,同时你也会得到一个大家都在使用的博客系统源码,你可以根据自己的需求和想法进行改造,也可以直接使用它来作为自己的个人网站,这个课程一定会给你带来巨大的收获。作者寄语本课程录制于 2020 年,代码基于 Spring Boot 2.x 版本。到目前为止,Spring Boot 技术栈也有一些版本升级,比如 Spring Boot 2.7 发版、Spring Boot 3.x 版本发布正式版本。对于这些情况,笔者会在本课程实战项目的开源仓库中创建不同的代码分支,保持实战项目的源码更新,保证读者朋友们不会学习过气的知识点。课程特色 课程内容紧贴 Spring Boot 技术栈,涵盖大部分 Spring Boot 使用场景。开发教程详细完整、文档资源齐全、实验过程循序渐进简单明了。实践项目页面美观且实用,交互效果完美。包含从零搭建项目、以及完整的后台管理系统和博客展示系统两个系统的功能开发流程。技术栈新颖且知识点丰富,学习后可以提升大家对于知识的理解和掌握,对于提升你的市场竞争力有一定的帮助。实战项目预览    

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值