SpringBoot 是基于 Spring 的开发 SpringBoot 本身不提供Spring 框架的核心特性以及扩展性功能 只是用于快速开发一代基于Spring 框架的应用程序 他不是用来替代Spring的解决方法, 而是和Spring紧密结合用于提升Spring开发体验的
Spring Boot 约定大于配置 默认帮我们进行了很多设置 多数SpringBoot 应用只需要很少的Spring配置
同时集成了大量的常用第三方库配置(Redis,MongoDB,Jpa,RabbitMQ,Quartz …) Spring Boot 中这些第三方库几乎可以零配置
SpringBoot 其实不是什么新的框架 它默认配置了很多框架的使用方法 就像maven整合了所有的jar包 SpringBoot 整合了所有的框架
优点:
微服务是一种架构风格 它要求我们在开发一个应用的时候 这个应用必须构建成一系列小服务的组合 可以通过http 的方式进行互通
将一个应用中的所有应用服务都封装在一个应用中
all in one 的架构方式 我们把所有的功能单元放在一个应用里面 然后我们把整个应用部署到服务器上 如果负 载能力不行 我们将整个应用进行水平复制 进行扩展 然后在负载均衡
微服务架构 就是打破之前 all in one 的架构方式 把每个功能元素独立出来 把独立出来的功能元素的动态组合 需要的功能元素才拿来组合 需要多一些时可以整合多个功能元素 所以微服务架构是对功能元素进行复制 而没有对整个应用进行复制
好处:
怎么样构建微服务:
在 官网 直接下载后 导入 idea 开发
Spring 官网:
项目结构:
HelloworldApplication :程序的主入口 不能修改 所有的包都要创建在 它的同级目录下 不然无法访问
helloController
@RestController
public class helloController {
@RequestMapping("/hello")
public String Hello(){
//调用业务接收前端的参数
return "hello world!!!";
}
}
运行:
项目元数据:创建时输入的 Project Metadata 部分 也就是Maven 项目的基本元素 包括:groupid artifactld version name description 等等
parent:继承 spring-boot-starter-parent
的依赖管理 控制版本与打包等内容
dependencies:项目具体依赖 这里包括 spring-boot-strater-web
用于实现 HTTP 接口 (该接口包含 Spring MVC )
spring-boot-starter-test
:用于编写单元测试的依赖包
build:构建配置部分 默认使用 spring-boot-maven-plugin
配合 spring-boot-starter-parent
就可以把 Spring Boot 应用打包成 JAR 运行。
使用idea 创建一个springboot 项目
helloController
//SpringBoot 完成一件事: 自动装配
@RestController
@RequestMapping("/hello")
public class helloController {
@ResponseBody
@RequestMapping("/hello")
public String Hello(){
//调用业务接收前端的参数
return "hello world!!!";
}
}
application.properties
# 更改项目访问的端口号
server.port=8081
banner.txt
[{+?]
~++++{.
!{{{{[}}{{{{{_. .{++++{ {{}{
{++++++{++++++?}]++?{{_++{ -{++++}
{+rr+r\+{+++++{$$ +++++++[{<<+++++{
}++++++{+++++++{<{}+{$k {+++++}}{^
}{++[{++++++++++++++{ _++++++++{,
{++++++++++++++++++]}?+++++++++++{
{{++++++++++++++++++++++++++++++}
{{+++++++++++++++++++++++_++++{
{+++++++++++++++++++}}}}}}}++I
{+++++++++++++++++++[}}}}}}}_+{
[]++vY+++++++++++++++}}}}}}}}++{
{++++X$X|+++++++++++++}}}}}[+++{
{_++++X%$$qXXv\|XXX)++++++++++-^
. :{++++++XX$$$$$qXv++++++++++++{
l-++++++++++++++++++++++++++{
I{++++++++++++++++++++++++{
C0{}++++++++++++++++++-{0C
COOOOO0{{[+++++++++_{{0OOO0C
COOOOOOOOOOOOOOUCOOOOOOOOOOOOn<<<"
"< COOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOC
{QOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOL '
COOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOY"< <
COOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOC<<<<<<`
'COOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOC
[COOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOC
]CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
<< ^<^
<< ^<^
<< ^<^
$$$$$$$$> O$$$$$$$z
pom.xml
<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.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.5.5version>
<relativePath/>
parent>
<groupId>com.zhanggroupId>
<artifactId>SpringBoot-study-01artifactId>
<version>0.0.1-SNAPSHOTversion>
<name>SpringBoot-study-01name>
<description>SpringBoot-study-01description>
<properties>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
<repositories>
<repository>
<id>aliyunmavenid>
<url>https://maven.aliyun.com/repository/publicurl>
<releases>
<enabled>trueenabled>
releases>
<snapshots>
<enabled>falseenabled>
snapshots>
repository>
repositories>
<pluginRepositories>
<pluginRepository>
<id>nexus-aliyunid>
<name>Nexus-aliyunname>
<url>https://maven.aliyun.com/repository/publicurl>
<snapshots>
<enabled>falseenabled>
snapshots>
<releases>
<enabled>trueenabled>
releases>
pluginRepository>
pluginRepositories>
project>
运行:
自动配置
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.5.5version>
<relativePath/>
parent>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
spring-boot-starter-web
就会帮我们导入 web 所需要的所有环境starter
// @SpringBootApplication :标注这个类是一个 springboot 的应用 程序
@SpringBootApplication
public class SpringBootStudy01Application {
// 将spring boot 应用启动
public static void main(String[] args) {
SpringApplication.run(SpringBootStudy01Application.class, args);
}
}
注解:
@SpringBootConfiguration : springboot 的配置
@Configuration :spring 配置类
@Component: 说明也是一个spring的组件
@EnableAutoConfiguration : 自动配置
@AutoConfigurationPackage : 自动配置包
@Import(AutoConfigurationPackages.Registrar.class) :自动配置 包注册
@Import(AutoConfigurationImportSelector.class) : 自动配置导入选择
SpringApplication.run:
该方法·主要分为两部分 一部分是 springApplication 的实例 二是run方法 的执行
SpringApplication:
配置文件:使用一个 全局的配置文件
配置文件的作用:
修改springboot 的默认配置
标记语言:以前的配置文件 大多数是用xml 来配置
xml 配置
<server>
<port>8081port>
server>
yaml 配置:
server:
prot: 8081
替代 application.properties
# 对空格要求十分严格
# 可以注入到配置类中
# 普通的 k-v
name: xiaotao
# 对象
student:
name: xiaotao
age: 18
# 行内写法
student1: {name: xiaotao,age: 18}
# 数组
pets:
- cat
- dog
- pig
pers: [cat,dog,pig]
实体类:
Dog
@Component
@Data
public class Dog {
@Value("旺财")
private String name;
@Value("3")
private Integer age;
}
Person
@Component
@Data
@ConfigurationProperties(prefix = "person")
public class Person {
private String name;
private Integer age;
private Boolean happy;
private Date birth;
private Map<String,Object> map;
private List<Object> list;
private Dog dog;
}
application.yaml
# 通过 yaml 给实体类赋值
person:
name: xiaotao
age: 18
happy: true
birth: 2021/10/31
map: {k1: v1,k2: v2}
list:
- code
- music
- girl
dog:
name: 旺财
age: 2
测试
@SpringBootTest
class SpringBootStudy01ApplicationTests {
@Autowired
private Person person;
@Test
void contextLoads() {
System.out.println(person);
}
}
pom.xml
添加依赖即可解决
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-configuration-processorartifactId>
<optional>trueoptional>
dependency>
运行:
# 通过 yaml 给实体类赋值
person:
name: xiaotao${random.uuid} # 产生一个随机的 uuid
age: ${random.int}
happy: true
birth: 2021/10/31
map: {k1: v1,k2: v2}
list:
- code
- music
- girl
dog:
name: ${person.hello:hello}_旺财 # 如果 person.hello 存在则取 对应值 否则 取 hello
age: 2
在字段增加一层过滤器验证 可以保证数据的合法性
Bean Validation 中内置的 constraint
Hibernate Validator 附加的 constraint
application.properties
application.yaml
使用yaml 一个配置文件即可以
通过 ---
即可区分不同配置环境
server:
port: 8081
spring:
profiles:
active: dev
---
server:
port: 8082
spring:
profiles: dev
---
server:
port: 8083
spring:
profiles: test
SpringBoot 启动会加载大量的自动配置类
我们看我们需要的功能有没有在SpringBoot 默认写好的自动配置类中
看这个自动配置类中到底配置了哪些组件· 只要我们要用到的组件存在其中 我们就不需要手动配置了
给容器中自动配置类添加组件的时候 会从·properties类中获取属性 我们只需要在配置文件中指定这些属性的值即可
xxxAutoConfigurartion: 自动配置类 给容器添加组件
xxxProperties : 封装配置文件中相关属性
debug = true # 开启日志 查看哪些自动配置类生效 哪些没有生效
自动配置
SpringBoot 到底帮我们配置了什么? 我们能不能修改? 能修改什么? 能不能扩展?
要解决的问题:
https://www.webjars.org/
方式一: 以 maven 的方式引入静态资源 (不推荐)
<dependency>
<groupId>org.webjars.npmgroupId>
<artifactId>jqueryartifactId>
<version>3.6.0version>
dependency>
方式二: 放在 classpath:static classpath:public classpath:resources 文件夹下面
在 templates 目录下的所有页面 只能通过controller 来跳转
需要模板引擎的支持 thymeleaf
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>
引入命名空间
<html lang="en" xmlns:th="www.thymeleaf.arg">
所有的静态资源都需要使用 thylemeaf 接管
url : @{}
@Bean
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8vNNGdtH-1690941414317)(http://qiniu.xiaotao.cloud/QQ截图20211105001121.png)]
无论是 sql(关系型数据库) 还是 nosql(非关系型数据库)springboot 都是采用 SpringData 的方式进行统一管理
JDBCController
@RestController
public class JDBCController {
@Autowired
JdbcTemplate jdbcTemplate;
@GetMapping("/userList")
public List<Map<String,Object>> userList(){
String sql = "select * from stuinfo";
List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
return maps;
}
@GetMapping("/addUser")
public String addUser(){
String sql = "insert into stuinfo values('T123100','小灰灰','女',19,'重庆')";
int i = jdbcTemplate.update(sql);
return "addUser";
}
@GetMapping("/update")
public String update(){
String sql = "update stuinfo set stu_name = '小涛',stu_sex = '女',stu_age = 18,stu_Address = '重庆' where stu_id = 'T123005';";
int i = jdbcTemplate.update(sql);
return "update";
}
@GetMapping("/del/{id}")
public String del(@PathVariable("id") String id){
String sql = "delete from stuinfo where stu_id = ?";
int i = jdbcTemplate.update(sql,id);
return "del";
}
}
application.yaml
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/mybatis?useUniocde=true&characterEncoding=utf-8&serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource # 指定使用 Druid 数据源
#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
DriverConfig
@Configuration
public class DriverConfig {
/**
* 使自定义配置 druid 生效
* @return
*/
@ConfigurationProperties(prefix = "spring.datasource")
@Bean
public DataSource druidDataSource(){
return new DruidDataSource();
}
// 后台监控 运行访问 http://localhost:8080/druid
@Bean
public ServletRegistrationBean statViewServlet(){
ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
// 后台需要有人登录 账号 密码 配置
HashMap<String,String> initParameters = new HashMap<>();
// 增加配置
// 登录的 key 是固定的 loginUsername loginPassword
initParameters.put("loginUsername","admin");
initParameters.put("loginPassword","123456");
// 允许谁能访问 值为空 代表 任何人都可以
initParameters.put("allow","");
// 禁止谁访问
//initParameters.put("xiaotao","192.168.42.233");
bean.setInitParameters(initParameters); // 设置初始化参数
return bean;
}
}
访问: http://localhost:8080/druid
执行一次查询
监控到
//filter
public FilterRegistrationBean webStatFilter(){
FilterRegistrationBean bean = new FilterRegistrationBean();
HashMap<String,String> initParameters = new HashMap<>();
initParameters.put("exclusions","*.js,*.css,/druid/*"); // 这些不统计
bean.setInitParameters(initParameters);
bean.setFilter(new WebStatFilter());
return bean;
}
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>1.3.2version>
dependency>
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zhang.mapper.StuInfoMapper">
<select id="queryStuList" resultType="StuInfo">
select * from stuinfo
select>
mapper>
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 整合mybatis
mybatis.type-aliases-package=com.zhang.pojo
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml
两个都是用于安全的框架 很像 除了类不一样
认证 授权
springSecurity 是针对 spring 项目的安全框架 样式 SpringBoot底层安全模块默认的选形 他可以实现强大的web 安全控制 外面只需要 引入
进行少量的配置 即可实现强大的web安全管理
SpringSecurity 的两个主要目标是“认证” 和 “授权”(访问控制)
认证(Authentication)
授权(Authorization)
导入包:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
SecurityConfig
/**
* 权限 管理 通过 AOP 横切
*/
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 授权
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
// 首页所有人可以访问 功能页只有对应有权限的人才能访问
// 链式编程
// antMatchers("/"):代表哪些页面
// permitAll():所有人都可以访问
// hasRole("vip1") : 指定人可以访问
http.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/level1/**").hasRole("vip1")
.antMatchers("/level2/**").hasRole("vip2")
.antMatchers("/level3/**").hasRole("vip3");
// 没有权限默认会到登录页面 需要开启登录的页面 会走默认的页面 http://localhost:8080/login
// loginPage("/toLogin") : 定制自己的登录页面
// 框架默认使用的是 username password 要使用 不同的可以加上下面的属性
http.formLogin().loginPage("/toLogin").usernameParameter("user").passwordParameter("pwd").loginProcessingUrl("/login");
// 开启注销功能
// 他会有防止网站攻击: 只能使用 post 请求
// 不加需要 post 请求 才能注销
http.csrf().disable(); // 关闭 csrf 功能 可以使用 get 请求 (解决不能注销问题)
http.logout().logoutSuccessUrl("/");
//开启记住我 cookie 默认保存两周 自定义接收前端参数
http.rememberMe().rememberMeParameter("remember");
}
/**
* 认证
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 从内存中获取
// 需要进行密码加密
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("xiaotao").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2")
.and()
.withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3")
.and()
.withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1");
// 也可以从数据库中获取
// auth.jdbcAuthentication().withUser("").password("").roles("");
}
}
RouterController
@Controller
public class RouterController {
@RequestMapping({"/","/index"})
public String index(){
return "index";
}
@RequestMapping("/toLogin")
public String login(){
return "views/login";
}
@RequestMapping("/level1/{id}")
public String level1(@PathVariable("id") Integer id){
return "views/level1/"+id;
}
@RequestMapping("/level2/{id}")
public String level2(@PathVariable("id") Integer id){
return "views/level2/"+id;
}
@RequestMapping("/level3/{id}")
public String level3(@PathVariable("id") Integer id){
return "views/level3/"+id;
}
}
实现有什么权限看到那个页面:
<dependency>
<groupId>org.thymeleaf.extrasgroupId>
<artifactId>thymeleaf-extras-springsecurity4artifactId>
<version>3.0.4.RELEASEversion>
dependency>
<div class="right menu">
<div sec:authorize="!isAuthenticated()">
<a class="item" th:href="@{/toLogin}">
<i class="address card icon">i> 登录
a>
div>
<div sec:authorize="isAuthenticated()">
<a class="item" >
用户名:<span sec:authentication="name">span>
a>
<a class="item" th:href="@{/logout}">
<i class="sign-out icon">i> 注销
a>
div>
div>
<div class="column" sec:authorize="hasRole('vip1')">
<div class="ui raised segment">
<div class="ui">
<div class="content">
<h5 class="content">Level 1h5>
<hr>
<div><a th:href="@{/level1/1}"><i class="bullhorn icon">i> Level-1-1a>div>
<div><a th:href="@{/level1/2}"><i class="bullhorn icon">i> Level-1-2a>div>
<div><a th:href="@{/level1/3}"><i class="bullhorn icon">i> Level-1-3a>div>
div>
div>
div>
div>
subject: 用户
SecurityManager:管理所有用户
Realm:连接数据
<dependency>
<groupId>org.apache.shirogroupId>
<artifactId>shiro-coreartifactId>
<version>1.8.0version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>jcl-over-slf4jartifactId>
<version>1.7.21version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-log4j12artifactId>
<version>1.7.21version>
dependency>
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.17version>
dependency>
log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n
# General Apache libraries
log4j.logger.org.apache=WARN
# Spring
log4j.logger.org.springframework=WARN
# Default Shiro logging
log4j.logger.org.apache.shiro=INFO
# Disable verbose logging
log4j.logger.org.apache.shiro.util.ThreadContext=WARN
log4j.logger.org.apache.shiro.cache.ehcache.EhCache=WARN
[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
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);
// 获取当前的用户对象 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 + "]");
}
// 测试当前用户是否被认证
if (!currentUser.isAuthenticated()) {
// 通过当前 用户名和密码 设置一个 令牌(token)
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?
}
}
//获取当前用户的信息
log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");
//判断当前用户是否具有该权限
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!");
}
//注销
currentUser.logout();
// 结束系统
System.exit(0);
}
}
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>org.apache.shirogroupId>
<artifactId>shiro-springartifactId>
<version>1.7.1version>
dependency>
<dependency>
<groupId>com.github.theborakompanionigroupId>
<artifactId>thymeleaf-extras-shiroartifactId>
<version>2.0.0version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.2.8version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.22version>
dependency>
<dependency>
<groupId>com.att.innogroupId>
<artifactId>log4jartifactId>
<version>1.2.13version>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>1.3.2version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
IndexController
@Controller
public class IndexController {
@RequestMapping({"/","/index","index.html"})
public String index(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 uae) { // 用户名不存在
model.addAttribute("msg","用户名错误");
return "login";
} catch (IncorrectCredentialsException ice) { // 密码错误
model.addAttribute("msg","密码错误");
return "login";
} catch (LockedAccountException lae) { // 账户被锁定
model.addAttribute("msg","账户被锁定");
return "login";
}
}
/**
* 未授权页面
* @return
*/
@RequestMapping("/unauth")
@ResponseBody
public String unauthorized(){
return "没有授权不能访问此页面";
}
}
ShiroConfig
@Configuration
public class ShiroConfig {
// 第三步 ShiroFilterFactoryBean
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
// 设置安全管理器
factoryBean.setSecurityManager(defaultWebSecurityManager);
// 添加shiro 的内置过滤器
/**
* anon: 无需认证就可以访问
* authc:必须认证了才能访问
* user:必须拥有记住我 才能实现
* perms:拥有对某个资源的权限才能访问
* role:拥有某个角色权限才能访问
*/
Map<String, String> filterMap = new LinkedHashMap<>();
// 授权
filterMap.put("/user/add","perms[user:add]");
filterMap.put("/user/update","perms[user:update]");
// 拦截
/*filterMap.put("/user/add","authc");
filterMap.put("/user/update","authc");*/
// 可以使用通配符
filterMap.put("/user/*","authc");
factoryBean.setFilterChainDefinitionMap(filterMap);
// 设置登录页面
factoryBean.setLoginUrl("/toLogin");
// 设置未授权页面
factoryBean.setUnauthorizedUrl("/unauth");
return factoryBean;
}
// 第二步 DefaultWebSecurityManager
@Bean(name = "securityManager") //@Qualifier("userRealm") userRealm: 为下面的方法名 或 name 都可以
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 关联 realm
securityManager.setRealm(userRealm);
return securityManager;
}
// 第一步 创建 realm 需要自定义类
@Bean(name = "userRealm") // 或 @Bean(name = "userRealm")
public UserRealm userRealm(){
return new UserRealm();
}
// 整合shiro thymeleaf
@Bean
public ShiroDialect getShiroDialect(){
return new ShiroDialect();
}
}
UserRealm
/**
* 自定义 realm
*/
public class UserRealm extends AuthorizingRealm {
@Autowired
UserService userService;
/**
* 授权
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了 ==》 授权 doGetAuthorizationInfo");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//获取到当前登录的对象
Subject subject = SecurityUtils.getSubject();
User currentUSer = (User) subject.getPrincipal();
//设置当前用户的权限
info.addStringPermission(currentUSer.getPerms());
return info;
}
/**
* 认证
* @return
* @throws AuthenticationException
*/
@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; //抛出异常
}
// 把当前登录用户存入session
Subject currentSubject = SecurityUtils.getSubject();
currentSubject.getSession().setAttribute("loginUser",user);
// 密码认证 shiro 会自己实现
return new SimpleAuthenticationInfo(user,user.getPwd(),""); //这里必须存入 user 上面才能获取
}
}
UserMapper
@Mapper
@Repository
public interface UserMapper {
User queryUserByName(String name);
}
index.html
DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.arg"
xmlns:shiro="http://www.thymeleaf.arg/thymeleaf-extras-shiro">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<h1>首页h1>
<p th:if="${session.loginUser==null}">
<a th:href="@{/toLogin}">登录a>
p>
<h3 th:text="${msg}">h3>
<hr/>
<div shiro:hasPermission="user:add">
<a th:href="@{/user/add}">adda>
div>
<div shiro:hasPermission="user:update">
<a th:href="@{/user/update}">updatea>
div>
body>
html>
application.yaml
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/mybatis?useUniocde=true&characterEncoding=utf-8&serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource # 指定使用 Druid 数据源
#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
学习目标:
前后端分离:
Vue + SpringBoot
后端时代:前端只用管理静态页面 html ===》后端 模板引擎 jsp
前后端分离时代:
后端:控制层 服务层 数据访问层
前端:控制层 试图访问层
伪后端数据 json
前后端如何交互 ====》 API
前后端相对独立 松耦合
前后端甚至可以部署在不同的服务器上
问题:
前后端继承联调 前后端人员无法做到“及时协商 今早解决” 最后导致问题爆发
解决方案:
首先制定 schema 【计划提纲】 实时更新最新API 降低集成风险
在项目中使用Swagger需要springfox
导入依赖
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger2artifactId>
<version>2.9.2version>
dependency>
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger-uiartifactId>
<version>2.9.2version>
dependency>
Config 配置:
@Configuration
@EnableSwagger2 // 开启 swagger2
public class SwaggerConfig {
}
测试运行: swagger-ui.html
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xNLsWfZ2-1690941414320)(http://qiniu.xiaotao.cloud/QQ截图
.png)]
@Configuration
@EnableSwagger2 // 开启 swagger2
public class SwaggerConfig {
// 配置了 Swagger的Docket 的bean 实例
@Bean
public Docket docket(){
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo());
}
// 配置 swagger 信息 apiInfo
public ApiInfo apiInfo(){
// 作者信息
Contact contact = new Contact("小涛","https://www.xiaotao.cloud/","[email protected]");
return new ApiInfo(
"xiaotao 的 SwaggerApi ",
"即使再小的帆也能远航!",
"1.0",
"https://www.xiaotao.cloud/",
contact,
"Apache 2.0",
"http://www.apache.org/licenses/LICENSE-2.0",
new ArrayList()
);
}
}
// 配置了 Swagger的Docket 的bean 实例
@Bean
public Docket docket(){
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
// RequestHandlerSelectors: 配置要扫描接口的方式
// .basePackage("com.zhang.controller"): 指定要扫描的包
// .any(): 扫描全部
// .none(): 不扫描
// .withClassAnnotation(RestController.class): 扫描类上的注解 参数是一个注解的反射对象
// .withMethodAnnotation(GetMapping.class): 扫描方法上的注解
.apis(RequestHandlerSelectors.basePackage("com.zhang.controller"))
// paths: 过滤什么路径
.paths(PathSelectors.ant("/zhang/**"))
.build();
}
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.enable(true) // 默认为 true 改为false 则不能使用
.select()
.apis(RequestHandlerSelectors.basePackage("com.zhang.controller"))
.build();
@Bean
public Docket docket(Environment environment){
// 实现在开发的时候使用swagger 扫描 上线时不使用swagger
//设置要显示的环境
Profiles profiles = Profiles.of("dev", "test");
// 通过 environment.acceptsProfiles 判断是否处于自己设定的环境中
boolean flag = environment.acceptsProfiles(profiles);
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.enable(flag) // flag : true false
.select()
.apis(RequestHandlerSelectors.basePackage("com.zhang.controller"))
.build();
}
配置Api分组:
.groupName("xiaotao")
配置 多个分组:
@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
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello(){
return "hello word";
}
// 只要接口中 返回值存在 实体类 他就会被 扫描到 swagger 中
@PostMapping("/user")
public User user(){
return new User();
}
//Operation 接口
@ApiOperation("Hello 控制类")
@GetMapping("/hello2")
public String hello2(@ApiParam("用户名") String username){
return "hello"+username;
}
@ApiOperation("post 测试控制类")
@PostMapping("/postt")
public User postt(@ApiParam("用户") User user){
return user;
}
}
总结:
@Service
public class AsyncService {
// 告诉 spring 这是一个 异步请求
@Async
public void hello(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("数据正在处理.....");
}
}
@RestController
public class AsyncController {
@Autowired
private AsyncService asyncService;
@RequestMapping("/hello")
public String hello(){
asyncService.hello(); // 不使用异步注解 停止 3秒
return "ok";
}
}
主启动类
@EnableAsync // 开启异步请求注解
@SpringBootApplication
public class SpringBootTask08Application {
public static void main(String[] args) {
SpringApplication.run(SpringBootTask08Application.class, args);
}
}
TaskScheduler 任务调度者
TaskExecutor 任务执行者
@EnableScheduling 开启定时功能的注解
@Scheduled 什么时候执行
主启动类
@EnableAsync // 开启异步请求的注解
@EnableScheduling // 开启定时功能的注解
@SpringBootApplication
public class SpringBootTask08Application {
public static void main(String[] args) {
SpringApplication.run(SpringBootTask08Application.class, args);
}
}
@Service
public class ScheduledService {
/**
* 在特定的时间执行
* cron : 表达式
* 秒 分 时 日 月 周几
*/
@Scheduled(cron = "0 * * * * 0-7")
public void hello(){
System.out.println("hello 我出来啦!");
}
}
常用表达式例子
(1)0/2 * * * * ? 表示每2秒 执行任务
(1)0 0/2 * * * ? 表示每2分钟 执行任务
(1)0 0 2 1 * ? 表示在每月的1日的凌晨2点调整任务
(2)0 15 10 ? * MON-FRI 表示周一到周五每天上午10:15执行作业
(3)0 15 10 ? 6L 2002-2006 表示2002-2006年的每个月的最后一个星期五上午10:15执行作
(4)0 0 10,14,16 * * ? 每天上午10点,下午2点,4点
(5)0 0/30 9-17 * * ? 朝九晚五工作时间内每半小时
(6)0 0 12 ? * WED 表示每个星期三中午12点
(7)0 0 12 * * ? 每天中午12点触发
(8)0 15 10 ? * * 每天上午10:15触发
(9)0 15 10 * * ? 每天上午10:15触发
(10)0 15 10 * * ? 每天上午10:15触发
(11)0 15 10 * * ? 2005 2005年的每天上午10:15触发
(12)0 * 14 * * ? 在每天下午2点到下午2:59期间的每1分钟触发
(13)0 0/5 14 * * ? 在每天下午2点到下午2:55期间的每5分钟触发
(14)0 0/5 14,18 * * ? 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发
(15)0 0-5 14 * * ? 在每天下午2点到下午2:05期间的每1分钟触发
(16)0 10,44 14 ? 3 WED 每年三月的星期三的下午2:10和2:44触发
(17)0 15 10 ? * MON-FRI 周一至周五的上午10:15触发
(18)0 15 10 15 * ? 每月15日上午10:15触发
(19)0 15 10 L * ? 每月最后一日的上午10:15触发
(20)0 15 10 ? * 6L 每月的最后一个星期五上午10:15触发
(21)0 15 10 ? * 6L 2002-2005 2002年至2005年的每月的最后一个星期五上午10:15触发
(22)0 15 10 ? * 6#3 每月的第三个星期五上午10:15触发
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-mailartifactId>
dependency>
[email protected]
spring.mail.password=ktdihwxprzqveaad
spring.mail.host=smtp.qq.com
# 开启加密验证 (qq才需要)
spring.mail.properties.mail.smtp.ssl.enable=true
@Autowired
JavaMailSenderImpl mailSender;
/**
* 简单的邮件
*/
@Test
void contextLoads() {
SimpleMailMessage mailMessage = new SimpleMailMessage();
mailMessage.setSubject("hello xiaotao");
mailMessage.setText("嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿");
mailMessage.setTo("[email protected]");
mailMessage.setFrom("[email protected]");
mailSender.send(mailMessage);
}
/**
* 复杂的邮件
*/
@Test
void contextLoads2() throws MessagingException {
MimeMessage mimeMessage = mailSender.createMimeMessage();
// 组装
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage,true);
//正文
helper.setSubject("hello xiaotao");
helper.setText("嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿嘿
",true);
// 附件
helper.addAttachment("1.jpg",new File("D:\\01.jpg"));
helper.addAttachment("2.jpg",new File("D:\\01.jpg"));
helper.setTo("[email protected]");
helper.setFrom("[email protected]");
mailSender.send(mimeMessage);
}
什么是分布式系统:
在 《分布式系统原理与泛型》一书中有如下定义:“分布式系统是若干独立计算机的集合 这些计算机对于用户来说就像单个相关系统”
分布式系统(distributed system)是建立在网络之上的软件系统。正是因为软件的特性,所以分布式系统具有高度的内聚性和透明性。因此,网络和分布式系统之间的区别更多的在于高层软件(特别是操作系统),而不是硬件
利用更多的机器 处理更多的数据
只有当单个机器无法处理的时候我们才会考虑使用分布式
详细概念:
https://mp.weixin.qq.com/s/sKu9-vH7NEpUd8tbxLRLVQ
Zookeeper : 注册中心
下载:http://archive.apache.org/dist/zookeeper/
管理员身份
Dubbo : 一个jar包
下载dubbo-admin
地址 :https://github.com/apache/dubbo-admin/tree/master-0.2.0
先要启动 (管理员)
zkServer.cmd
:打开 zookeeper 服务
去cmd 运行(管理员身份)
java -jar dubbo-admin-0.0.1-SNAPSHOT.jar
访问:
dubbo-admin : 是一个监控管理后台 查看我们注册了那些服务· 那些服务被消费了
新建项目
服务提供者
导入依赖:
<dependency>
<groupId>org.apache.dubbogroupId>
<artifactId>dubbo-spring-boot-starterartifactId>
<version>2.7.3version>
dependency>
<dependency>
<groupId>com.github.sgroschupfgroupId>
<artifactId>zkclientartifactId>
<version>0.1version>
dependency>
<dependency>
<groupId>org.apache.curatorgroupId>
<artifactId>curator-frameworkartifactId>
<version>2.12.0version>
dependency>
<dependency>
<groupId>org.apache.curatorgroupId>
<artifactId>curator-recipesartifactId>
<version>2.12.0version>
dependency>
<dependency>
<groupId>org.apache.zookeepergroupId>
<artifactId>zookeeperartifactId>
<version>3.4.14version>
<exclusions>
<exclusion>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-log4j12artifactId>
exclusion>
exclusions>
dependency>
application.properties
server.port=8081
#当前应用名字
dubbo.application.name=provider-server
#注册中心地址
dubbo.registry.address=zookeeper://127.0.0.1:2181
#扫描指定包下服务
dubbo.scan.base-packages=com.zhang.service
TicketServiceImpl
package com.zhang.service;
import org.apache.dubbo.config.annotation.Service;
import org.springframework.stereotype.Component;
@Service // dubbo :下的 在项目一启动就自动注到注册中心
@Component //使用了dubbo 后尽量不要用 service 注解 会和上一个冲突
public class TicketServiceImpl implements TicketService{
@Override
public String getTicket() {
return "小涛 你好呀!!";
}
}
先要启动 (管理员)
zkServer.cmd
:打开 zookeeper 服务
去cmd 运行(管理员身份)
java -jar dubbo-admin-0.0.1-SNAPSHOT.jar
启动项目:
provider-server
服务消费者
导入依赖:(同上)
<dependency>
<groupId>org.apache.dubbogroupId>
<artifactId>dubbo-spring-boot-starterartifactId>
<version>2.7.3version>
dependency>
<dependency>
<groupId>com.github.sgroschupfgroupId>
<artifactId>zkclientartifactId>
<version>0.1version>
dependency>
<dependency>
<groupId>org.apache.curatorgroupId>
<artifactId>curator-frameworkartifactId>
<version>2.12.0version>
dependency>
<dependency>
<groupId>org.apache.curatorgroupId>
<artifactId>curator-recipesartifactId>
<version>2.12.0version>
dependency>
<dependency>
<groupId>org.apache.zookeepergroupId>
<artifactId>zookeeperartifactId>
<version>3.4.14version>
<exclusions>
<exclusion>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-log4j12artifactId>
exclusion>
exclusions>
dependency>
application.properties
server.port=8082
#当前应用名字
dubbo.application.name=consumer-server
#注册中心地址
dubbo.registry.address=zookeeper://127.0.0.1:2181
UserService
package com.zhang.service;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.stereotype.Service;
@Service // 放到spring 容器中
public class UserService {
// 获取到 provider-server 提供的票 需要去注册中心那到服务
/**
* 两种方式:
* 1. pom 坐标
* 2. 定义路径相同的接口名
*/
// 这里使用的是 路径相同的接口名
@Reference // 引用
TicketService ticketService;
public void buyTicket(){
String ticket = ticketService.getTicket();
System.out.println("在注册中心拿到票===> "+ticket);
}
}
TicketService
public interface TicketService {
public String getTicket();
}
测试:
@SpringBootTest
class ConsumerServerApplicationTests {
@Autowired
UserService userService;
@Test
void contextLoads() {
userService.buyTicket();
}
}
启动项目