这里的包名默认会在后面自动拼接项目名,但这是没有必要的,这会让包结构中多了一个以项目名为名的子包,因此可以手动删除拼接的项目名.
在SpringBoot中不需要了SpringMVC的繁琐的配置,项目创建出来就可以运行,运行的入口是SpringbootApplication
类中的main
方法,这是SpringBoot自动生成的,以后的各层代码都需要在这个类的同级目录中创建才能被该类扫描到.
可以直接应用注解创建各种层,如下面的controller层
@RestController
public class MyController {
@RequestMapping("hello")
public String hello() {
return "hello";
}
}
代码是跟SpringMVC相同的,只是缺少了配置.
只需要在resources
目录下创建banner.txt
文件,并将自己喜欢的banner复制进去即可.
这样,以后启动SpringBoot的banner就会是自己的了.
server:
port: 8082
# 普通键值对
name: fisher
# 对象
user:
name: fisher
age: 17
# 对象的行内写法
user2: {name: fisher, age: 17}
# 数组
hobite:
- SpringBoot
- Mybatis
- SpringMVC
# 数组的行内写法
hobite2: [SpringBoot, Mybatis, SpringMVC]
SpringBoot官方推荐使用yaml文件作为配置文件,因为yaml更加轻巧
yaml可以表示普通键值对/对象/数组
yaml的功能十分强大,甚至可以直接给实体类赋值
在yaml中写类的赋值语句,在类中通过@ConfigurationProperties(prefix = "")
注解直接导入yaml中的类,就可以通过yaml对类复制,具体代码如下
实体类User.java
@Component //注册
@ConfigurationProperties(prefix = "user")//指定配置文件中的类
public class User {
private String name;
private int age;
private boolean isMan;
private List<String> hobby;
private Map<String, String> map;
private Dog dog;//这个Dog也是一个实体类.里面有name和age属性
}
配置文件application.yaml
user:
name: Fisher
age: 17
isMan: true
hobby: [Java, Java, Java]
map: {kay1: hello, kay2: word}
dog:
name: Tom
age: 3
yaml中类的属性与实体类中的属性一一对应就可以通过这种方式给类赋值.
yaml还支持松散绑定,也就是在给类赋值的时候,yaml写is-man与isMan是相同的
JSR303校验是用来校验类属性赋值数据的合法性
常用校验如下
Bean Validation 中内置的 constraint
Hibernate Validator 附加的 constraint
JSR303校验的使用
@Validated //数据校验
public class Dog {
@Email
private String name;
private int age;
}
JRS校验的使用很简单,只需要在该类注解
@Validated
,并在字段中注解需要的校验即可,如这时,该属性赋值时就要遵守JRS校验,否则程序就会报错
多环境配置
在yaml中可以配置多个环境,通过用
---
分割,通过默认环境的spring:profiles:active
选择所用的配置
server:
port: 8081
spring:
profiles:
active: dev #所选环境名
---
server:
port: 8082
spring:
profiles: dev #该环境名
---
server:
port: 8083
spring:
profiles: test
Springboot访问静态资源有两种方式
localhost:8080/webjars...
访问localhost:8080/...
即可直接访问
SpringBoot首页存放在静态资源目录下的idnex.html
,也就是在public/resources/static这三个目录下的任意一个.
导入依赖
<dependency>
<groupId>org.thymeleafgroupId>
<artifactId>thymeleaf-spring5artifactId>
dependency>
<dependency>
<groupId>org.thymeleaf.extrasgroupId>
<artifactId>thymeleaf-extras-java8timeartifactId>
dependency>
thymeleaf的视图解析器路径为
public static final String DEFAULT_PREFIX = "classpath:/templates/";
public static final String DEFAULT_SUFFIX = ".html";
因此需要把资源文件放在/templates
目录下
Controller层代码
@Controller
public class MyController {
@RequestMapping("/hello")
public String hello(Model model) {
model.addAttribute("text", "word");
return "hello"; //跳转到classpath:/templates/hello.html中
}
}
资源文件hello.html
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
hello
<div th:text="${text}">div>
body>
html>
使用thymeleaf需要在头文件中导入
xmlns:th="http://www.thymeleaf.org
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<div th:text="${text}">div>
<div th:utext="${text}">div>
<div th:each="user:${users}" th:text="${user}">div>
body>
html>
th:text
文字转义,比如,如果text的值为那他会原样输出这个字符串
word
th:utext
文字不转义,上面的字符串会输出成word标题
th:each="user:${users}"
遍历,遍历的结果存放在user中
url:@{}
国际化提取信息:#{}
首页由于需要走thymeleaf模板而不能放在静态资源目录中,只能放在tmplates目录中,因此要在地址栏中访问到首页就需要自定义一个视图解析器,当使用url访问/
或/index.html
就跳转到index.html
自定义配置文件的类需要由@Configration注解,并且实现WebMvcConfigurer接口,代码如下
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
//自定义视图解析器
@Override
public void addViewControllers(ViewControllerRegistry registry) {
//访问'/'时跳转到index,这里index走了SpringBoot的视图解析器
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
}
}
配置文件
其中
login.properties
为默认语言,login_en_US.properties
为英文配置,login_zh_CN.properties
为中文配置
在配置文件中配置国际化配置文件的位置
application.properties
中
# 国际化配置文件的位置
spring.messages.basename=i18n.login
页面资源文件中引用国际化配置文件
就是在需要国际化的文字部分使用#{国际化文件字段名},例如
<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign inh1>
注意,要使用#{}
自定义语言解析类,即实现LocaleResolver
接口,该接口位于org.springframework.web.servlet
包下
MyLocaleResolver.java
中
public class MyLocaleResolver implements LocaleResolver {
//解析语言请求
@Override
public Locale resolveLocale(HttpServletRequest request) {
//获取请求中的语言
String lang = request.getParameter("lang");
Locale locale = Locale.getDefault();//取一个默认的Locale,如果没有参数就用这个默认的
// 有lang参数时进行国际化
if (!StringUtils.isEmpty(lang)) {
String[] split = lang.split("_");
locale = new Locale(split[0], split[1]);
}
return locale;
}
@Override
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
}
}
注册自定义的国际化类
在MyMvcConfig
类中
// 在容器中注册自定义国际化组件
@Bean
public LocaleResolver localeResolver() {
return new MyLocaleResolver();
}
现在,只要请求中包含lang
参数,解析类就会解析,并根据lang的参数内容显示不同的语言.
拦截器类需要实现HandlerInterceptor
接口
LoginHandlerInterceptor
类中
public class LoginHandlerInterceptor implements HandlerInterceptor {
//请求前拦截
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
Object username = request.getSession().getAttribute("username");
//没有登录
if (username == null) {
request.getRequestDispatcher("/index.html").forward(request, response);
return false;
}
return true;
}
}
拦截器配置
MyMvcConfig
类中
//配置自定义拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginHandlerInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/", "/index.html", "/login", "/css/**", "/img/**", "/js/**");
}
addInterceptor
: 添加拦截器
addPathPatterns
方法: 添加拦截的资源
excludePathPatterns
方法: 添加不拦截的资源
产生一个模块
th:fragment="frag_name"
定义模块名,实例代码如下
使用模块
th:replace='~{模块文件::模块名}'
,实例代码如下
<div th:replace="~{commons/commons::sidebar(active='main.html')}">div>
需要用
~{}
应用模板
()
放了传递的参数,在调用模板时会传递给模板
thymeleaf中的#dates.format(data, fromat)
方法可以自定义输出的时间格式,实例代码如下
<td th:text="${#dates.format(emp.getBirth(), 'yyyy-MM-dd')}">td>
application.properties
中
# 修改时间日期格式
spring.mvc.date-format=yyyy-MM-dd
配置之后,在输入框输入的时间格式是定义的时间格式,SpringBoot才会识别并转换成时间类
SpringBoot默认的时间格式是
yyyy/MM/dd
thymeleaf的参数不能直接通过${}
的方式取值,
而直接拼接字符串传递RestFull风格的参数IDEA会划红线的,尽管实际运行时没有什么问题
官方推荐下面的方式传递参数
th:href="@{/updataEmp/{id} (id=${emp.getId()})}
将错误页面放在templates/error
目录下,以错误类型命名,当发生页面错误时会自动跳转到对应错误类型的页面
(HttpSession session)
session.invalidate();//清空session
配置数据源
在application.yaml
中
# 配置数据源
spring:
datasource:
username: root
password: root
url: jdbc:mysql://localhost:3306?useSSL=false&useUnicode=TRUE&characterEncoding=utf8&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
@SpringBootTest
class Springboot03DataApplicationTests {
@Autowired
DataSource dataSource;
@Test
void contextLoads() throws SQLException {
String sql = "select * from mybatis.user";
Connection connection = dataSource.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement(sql);
ResultSet resultSet = preparedStatement.executeQuery();
while (resultSet.next()) {
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
String passwd = resultSet.getString("passwd");
System.out.println(id + name + passwd);
}
connection.close();
}
}
使用SpringBoot原生JDBC操作跟原生JDBC操作区别不大
只是SpringBoot内置了
DataSource实例
,使用它可以自动加载配置文件的数据源配置.
SpringBoot中内置了许多xxxTemplate
,这些是已经配置好的模板Bean,需要是直接拿来使用,其中就包括JDBC的CRUD操作.
使用JDBCTemplate进行CRUD操作
@RestController
public class JDBCController {
@Autowired
JdbcTemplate jdbcTemplate;
@RequestMapping("/userList")
public List<Map<String, Object>> userList() {
String sql = "select * from mybatis.user";
List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
return maps;
}
@RequestMapping("/addList")
public String addList() {
String sql = "insert into mybatis.user (id, name, passwd) values (?, ?, ?)";
jdbcTemplate.update(sql, 100, "borzj", "890809");
return "Success";
}
}
Druid 是阿里巴巴开源平台上一个数据库连接池实现,结合了 C3P0、DBCP 等 DB 池的优点,同时加入了日志监控。
Druid 可以很好的监控 DB 池连接和 SQL 的执行情况,天生就是针对监控 而生的 DB 连接池。
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.1.22version>
dependency>
application.yaml
指定数据源并注入其属性
# 配置数据源
spring:
datasource:
username: root
password: root
url: jdbc:mysql://localhost:3306?useSSL=false&useUnicode=TRUE&characterEncoding=utf8&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
# 选择数据源
type: com.alibaba.druid.pool.DruidDataSource
#Spring Boot 默认是不注入这些属性值的,需要自己绑定
#druid 数据源专有配置
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
#配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
#如果允许时报错 java.lang.ClassNotFoundException: org.apache.log4j.Priority
#则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
配置Druid
注入数据源并设置属性值
@Bean
@ConfigurationProperties(prefix = "spring.datasource") //指定属性信息位置
public DataSource druidDataSource() {
return new DruidDataSource();
}
设置后台监控
@Bean
//后台监控
public ServletRegistrationBean statViewServlet() {
ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
// 设置初始化参数
Map<String, String> initParameters = new HashMap<>();
// 登录用户名
initParameters.put("loginUsername", "admin");
// 登录密码
initParameters.put("loginPassword", "root");
// 允许谁能访问,第二个参数传递允许访问的IP地址,""表示谁都可以访问
initParameters.put("allow", "");
bean.setInitParameters(initParameters);
return bean;
}
设置过滤器
@Bean
//过滤器
public FilterRegistrationBean webStatFilter() {
FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<>();
bean.setFilter(new WebStatFilter());
Map<String, String> initParameters = new HashMap<>();
//exclusions 设置不进行统计的资源
initParameters.put("exclusions", "*.js,*.css,/druid/*");
bean.setInitParameters(initParameters);
return bean;
}
导入依赖
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.1.2version>
dependency>
写实体类和Mapper接口
UserMapper
接口
@Repository
@Mapper
public interface UserMapper {
List<User> getUsers();
User getUserByID(@Param("id") int id);
int addUser(User user);
int deleteUser(@Param("id") int id);
int updateUser(User user);
}
@Mapper
会自动扫描Mapper的包并创建Mapper实现类
实现Mapper接口
UserMApper.xml
配置Mybatis
application.yaml
中
# 整合Mybatis
mybatis:
type-aliases-package: pero.fisher.pojo
mapper-locations: classpath:mybatis/mapper/*.xml
这一步就是配置Mybatis的类型别名和注册所有的Mapper.xml
调用实现类进行CRUD
@RestController
public class UserController {
@Autowired
private UserMapper userMapper;
@RequestMapping("/userList")
public List<User> userList() {
List<User> users = userMapper.getUsers();
return users;
}
}
SpringSecurity是面向AOP编程,使用它时直接编写配置类即可
导入依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
SpringSecurity配置类SecurityConfig
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// 授权
@Override
protected void configure(HttpSecurity http) throws Exception {
//配置首页所有人可以访问,功能页对应权限的人可以访问
http.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/level1/**").hasRole("vip1")
.antMatchers("/level2/**").hasRole("vip2")
.antMatchers("/level3/**").hasRole("vip3");
//开启登录页面,没有权限返回登录页面
http.formLogin();
}
// 认证
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("fisher").password(new BCryptPasswordEncoder().encode("fisher")).roles("vip1", "vip2")
.and()
.withUser("root").password(new BCryptPasswordEncoder().encode("root")).roles("vip1", "vip2", "vip3");
}
}
@EnableWebSecurity
打开SpringSecurity配置类需要继承
WebSecurityConfigurerAdapter
类配置授权时重写
configure(HttpSecurity http)
方法,授权就是控制每种角色的访问权限配置认证时重写
configure(AuthenticationManagerBuilder auth)
方法,认证就是将设置某个用户的用户名/密码/该用户属于什么角色
SpringSecurity 5.0+内置了许多加密方法,这些方法都是以
PasswordEncoding
结尾的,并且在SpringBoot 2.2.+版本后Security管理的密码必须进行加密,否则访问页面时报找不到PasswordEncoder的错
在配置类的configure(HttpSecurity http)
方法中开启注销页面
// 关闭csrf功能
http.csrf().disable();
//注销页面
http.logout().logoutSuccessUrl("/");
这个注销页面的路径在
/logout
中,当使用get请求这个页面时,SpringBoot默认开启的csrf会禁止访问,因为get请求不安全,所以要想通过get访问到注销页面,需要
http.csrf().disable();
关闭csrf
导入依赖
<dependency>
<groupId>org.thymeleaf.extrasgroupId>
<artifactId>thymeleaf-extras-springsecurity4artifactId>
<version>3.0.4.RELEASEversion>
dependency>
这个包让thymeleaf与SpringSecurity结合, 在thymeleaf中使用Security的属性和方法,利用这个可以惊醒权限控制,不同的用户有不同权限,显示不同内容,
这个功能似乎只在SpringBoot2.0.9以下的版本支持
引入xmls
<html xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
html>
几个方法
- sec:authorize="isAuthenticated() 判断用户是否登录
- sec:authorize="hasRole(‘vip1’) 判断登录的用户是否属于某角色
- sec:authentication=“name” 取登录用户的用户名
http.formLogin()
中
loginPage()
方法指定登录页面loginProcessingUrl()
方法指定验证界面usernameParameter()
方法指定用户名元素名passwordParameter()
方法指定密码元素名自定义登录界面有两种思路
指定登录界面与验证界面,自定义登录界面跳转到验证界面
configure(HttpSecurity http)
方法中
http.formLogin().loginPage("/toLogin").loginProcessingUrl("/login");
登录界面
<form action="/login" method="post">
<input type="submit" />
form>
指定登录界面,在自定义登录界面中跳转到登录界面
configure(HttpSecurity http)
方法中
http.formLogin().loginPage("/toLogin");
登录界面
<form action="/toLogin" method="post">
<input type="submit" />
form>
SpringSecurity默认的用户名元素名是username,密码元素名是password,如果自定义登录界面的元素名不同,可以自定用户名和密码的元素名,如下
//登录页面
http.formLogin().loginPage("/toLogin").usernameParameter("username").passwordParameter("password");
configure(HttpSecurity http)
方法内
//记住我
http.rememberMe();
即可在登录页面中开启记住我功能
自定义登录界面时,可以使用rememberMeParameter()
方法向SpringSecurity指定记住我的元素名,如下
//记住我
http.rememberMe().rememberMeParameter("remember");
Shiro的三大对象
Shiro内置方法
Subject currentUser = SecurityUtils.getSubject();
Session session = currentUser.getSession();
currentUser.isAuthenticated();
currentUser.getPrincipal();
currentUser.hasRole("roleName");
currentUser.isPermitted("user:root");
currentUser.logout();
依赖
<dependency>
<groupId>org.apache.shirogroupId>
<artifactId>shiro-springartifactId>
<version>1.5.2version>
dependency>
shiro配置类ShiroConfig
@Configuration
public class ShiroConfig {
// ShiroFilterFactoryBean对象
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
// 设置安全管理器
bean.setSecurityManager(defaultWebSecurityManager);
return bean;
}
// SecurityManager对象
@Bean(name="securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 关联Realm
securityManager.setRealm(userRealm);
return securityManager;
}
// Realm对象
@Bean
public UserRealm userRealm() {
return new UserRealm();
}
}
这个类注册Shiro的三个对象
其中Realm对象是自己创建的,这个类中写授权和认证规则,下面写出自定义的Realm类
UserRealm
UserRealm
//自定义Realm
public class UserRealm extends AuthorizingRealm {
// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("====>执行授权");
return null;
}
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("====>执行认证");
return null;
}
}
自定义Realm类需要继承
AuthorizingRealm
类
Shiro内置过滤器
下面是为shiro设置过滤器的方法
ShiroConfig
的getShiroFilterFactoryBean
方法中
// ShiroFilterFactoryBean对象
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
// 设置安全管理器
bean.setSecurityManager(defaultWebSecurityManager);
// 添加shiro过滤器
Map<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/user/*", "authc");// 第二个参数为内置过滤器类型
bean.setFilterChainDefinitionMap(filterMap);
//设置登录页面
bean.setLoginUrl("/toLogin");
return bean;
}
核心代码是
setFilterChainDefinitionMap()
方法设置过滤器
setLoginUrl()
方法设置登录界面,当没有通过认证时就会跳转到登录界面
获取页面输入的帐号密码,并封装登录数据,执行登录方法
@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";
}
}
在自定义Realm的认证方法中判断用户名和密码是否正确
在UserRealm
类中
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("====>执行认证");
// 假装从数据库中取的用户名和密码
String username = "root";
String password = "root";
//获取token
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
// 判断用户名是否正常
if (!token.getUsername().equals(username)) {
return null;//返回null时 会自动抛出UnknownAccountException异常
}
//进行密码认证, 只需要返回一个SimpleAuthenticationInfo,由shiro进行密码认证,
return new SimpleAuthenticationInfo("", password, "");
}
在配置类ShiroConfig
的getShiroFilterFactoryBean()
方法中设置页面的访问权限
和未授权页面
// ShiroFilterFactoryBean对象
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
// 设置安全管理器
bean.setSecurityManager(defaultWebSecurityManager);
// 添加shiro过滤器
Map<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/user/add", "perms[user:add]");//只有拥有user:add的用户才能访问,否则未授权
filterMap.put("/user/*", "authc");// 第二个参数为内置过滤器类型
bean.setFilterChainDefinitionMap(filterMap);
//设置登录页面
bean.setLoginUrl("/toLogin");
//设置未授权页面
bean.setUnauthorizedUrl("/uoauth");
return bean;
}
授权的核心就是设置FilterChainDefinitionMap时,使用
perms
过滤器设置某页面的访问权限,只有拥有该权限的用户才可以访问设置未授权页面使用
setUnauthorizedUrl()
方法,当某用户访问了未经授权的页面时会跳转到该页面
用户授权
思路是先在数据库中标记不同用户的权限,在访问时直接查询数据库中用户的权限授权
授权是在自定义Realm类的doGetAuthorizationInfo(PrincipalCollection principalCollection)
方法中完成,而能够查询到用户的方法是doGetAuthenticationInfo(AuthenticationToken authenticationToken)
,因此需要将用户实从后者传递给前者.代码如下
//自定义Realm
public class UserRealm extends AuthorizingRealm {
@Autowired
UserServiceImpl userService;
// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("====>执行授权");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
Subject subject = SecurityUtils.getSubject();
// 获取认证方法传来的principal
User user = (User) subject.getPrincipal();
// 添加该用户的Perm权限
info.addStringPermission(user.getPerms());
return info;
}
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("====>执行认证");
//获取token
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
//请求数据库
User user = userService.getUserByName(token.getUsername());
if (user == null) {
return null;
}
//进行密码认证, 只需要返回一个SimpleAuthenticationInfo,由shiro进行密码认证,
// 第一个参数可以传递对象
return new SimpleAuthenticationInfo(user, user.getPasswd(), "");
}
}
认证方法也就是这个类的第二个方法中
SimpleAuthenticationInfo(Object principal, Object credentials, String realmName)
类的第一个参数可以传递对象,然后在授权方法中通过subject.getPrincipal()
方法就可以获取到传递过来的对象.
依赖
<dependency>
<groupId>com.github.theborakompanionigroupId>
<artifactId>thymeleaf-extras-shiroartifactId>
<version>2.0.0version>
dependency>
注册ShiroDialect
在Shiro的配置类ShiroConfig
中
// 整合ShiroDialect,也就是Shiro与Thymeleaf的整合
@Bean
public ShiroDialect getShiroDialect() {
return new ShiroDialect();
}
现在,就可以在前端代码中使用Shiro的方法了
Shiro的头是这个xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro"
<body>
<h1>首页h1>
<p th:text="${msg}">p>
<hr/>
<div shiro:hasPermission="user:add">
<a th:href="@{/user/add}">添加a>
div>
<a th:href="@{/user/update}">更新a>
body>
hasPermission
方法判断用户是否有某种权限
Shiro还有自己的session可以通过下面的方法设置session的值
Subject subject = SecurityUtils.getSubject();
Session session = subject.getSession();
session.setAttribute("loginUser", user);
然后在前端可以取出来
<div th:if="session.loginUser != null">
<p>已登录p>
div>
Swagger号称世界上最流行的API框架
依赖
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger-uiartifactId>
<version>2.9.2version>
dependency>
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger2artifactId>
<version>2.9.2version>
dependency>
编写配置类
@Configuration
@EnableSwagger2 //开启swagger
public class SwaggerConfig {
}
在配置类中配置一个Docket实例
@Configuration
@EnableSwagger2 //开启swagger
public class SwaggerConfig {
//配置swagger的Docket实例
@Bean
public Docket docket() {
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo());
}
private ApiInfo apiInfo() {
//作者信息
Contact contact = new Contact("Fisher", "https://borzj.github.io", "fisher4@gmail");
return new ApiInfo("Fisher的Swagger",
"这是我的第一个Swagger",
"1.0",
"https://borzj.github.io",
contact,
"Apache 2.0",
"http://www.apache.org/licenses/LICENSE-2.0",
new ArrayList<VendorExtension>());
}
}
通过
Docket().apiInfo()
自定义Swagger信息
//配置swagger的Docket实例
@Bean
public Docket docket(Environment environment) {
// 设置要判断的环境
Profiles profiles = Profiles.of("dev");
// 判断当前环境是不是设置的指定环境,如果是,返回true
boolean flag = environment.acceptsProfiles(profiles);
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.enable(flag) //控制Swagger启动
.select().apis(RequestHandlerSelectors.basePackage("pero.fisher.controller")).build();// 设置要扫描的接口
}
Docket.select().apis(RequestHandlerSelectors.basePackage("pero.fisher.controller")).build()
设置扫描的接口,也就是访问什么时接收扫描
environment.acceptsProfiles(profiles)
判断现在的环境是不是指定环境
Docket.enable()
参数为true开启swagger,参数为false关闭
设置分组
Docket(DocumentationType.SWAGGER_2).groupName("Borjigin");
设置多个分组时就写多个返回Docket的方法,每个Docket添加到不同的分组
@Bean
public Docket docket1() {
return new Docket(DocumentationType.SWAGGER_2).groupName("Borjigin");
}
@ApiModel("用户实体类")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
@ApiModelProperty("用户名")
private String username;
@ApiModelProperty("密码")
private String password;
}
@ApiModel("")
或@Api("")
为实体类名注释
@ApiModelProperty("")
为实体类属性名注释
下面让Swagger扫描到这个类就可以在Swagger中显示它了
Controller
类中
@PostMapping("/user")
@ResponseBody
// 接口中返回值为实体类就会被Swagger扫描到
public User user() {
return new User();
}
在Controller类中,只要方法的返回值是实体类,并且设置一个访问路径让他可以访问,那这个返回的实体类就可以被Swagger扫描到
@ApiOperation("hello控制类,显示hello")
@GetMapping("/hello")
@ResponseBody
public String hello(@ApiParam("用户名") String username) {
return "hello";
}
@ApiOperation("")
给一个方法注释
@ApiParam("")
给一个参数注释
有时候某个方法运行的十分漫长,为了不影响页面显示,提升用户体验,会将这种方法创建一个线程独立运行
在方法中标注这是一个异步方法
@Service
public class AsyncService {
// 注解这是一个异步方法
@Async
public void hello() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("传输成功");
}
}
@Async
注解会向SpringBoot声明这是一个异步方法,SpringBoot会为这个方法创建一个线程
开启异步功能
在Application
类中
// 开启异步功能
@EnableAsync
@SpringBootApplication
public class Springboot07TaskApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot07TaskApplication.class, args);
}
}
@EnableAsync
注解开启异步功能
现在,调用这个方法就会异步运行.
依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-mailartifactId>
dependency>
配置文件application.yaml
spring:
mail:
username: [email protected]
password: tkxbimgbzkrehecf
host: smtp.qq.com
# 开启加密验证
properties.mail.smtp.ssl.enbale: true
加密验证只有qq邮箱需要开启
@Autowired
JavaMailSenderImpl mailSender;
@Test
void contextLoads() {
SimpleMailMessage message = new SimpleMailMessage();
//设置邮件主题
message.setSubject("hello,我在用SpringBoot给你发邮件");
//设置文本内容
message.setText("我正在学习SpringBoot的邮件任务,很简单哦");
//设置收信人
message.setTo("[email protected]");
//设置发信人
message.setFrom("[email protected]");
//发送邮件
mailSender.send(message);
}
multipart:是否支持多文件
@Autowired
JavaMailSenderImpl mailSender;
@Test
void contextLoads2() throws MessagingException {
// 创建一个复杂邮件
MimeMessage mimeMessage = mailSender.createMimeMessage();
//利用Helper进行文件组装
//multipart:是否支持多文件
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
helper.setSubject("这是SpringBoot发送的一个复杂文件");
// html:是否解析html文本
helper.setText("hello 我是红色的文本
", true);
//发送附件
helper.addAttachment("1.jpg", new File("/home/fisher/pictures/head.jpg"));
helper.addAttachment("2.jpg", new File("/home/fisher/pictures/head.jpg"));
helper.setTo("[email protected]");
helper.setFrom("[email protected]");
mailSender.send(mimeMessage);
}
定时任务就是在指定时间执行的任务,SpringBoot中使用两个注解就可以进行创建定时任务
@EnableScheduling
开启执行任务功能,与开启异步任务的注解相同,该注解也是放在Application类上的
@Scheduled(cron)
设置执行的时间
@Service
public class ScheduledService {
@Scheduled(cron = "0/1 * * * * ?")
public void hello() {
System.out.println("hello");
}
}
@Scheduled
注解的方法是可以被SpringBoot扫描到的方法才可以,因为这种方法需要执行多次,而不是执行一次就结束了
(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触发
Dubbo是一款高性能 轻量级的开源Java RPC(远程方法调度)框架,提供了三大核心功能: 面向接口的远程方法调用/容错和负载均衡,以及服务自动注册和发现.
ZooKeeper是一个分布式的开源的分布式应用程序协调服务,目标是封装好复杂易出错的关键服务,提供服务注册与发布功能
ZooKeeper的下载: ZooKeeper下载位置
下载完是一个压缩文件,解压缩放在自己喜欢的目录中
在刚才的配置文件zoo.cfg
中修改dataDir
为刚才创建的目录data
Dubbo-admin是一个监控后台,可以监控ZooKeeper注册中心的哪些任务被注册,哪些任务被消费等
下载Dubbo-admin
下载的是一个ZIP,这是一个SpringBoot项目,将其解压并进行Maven打包
打包方式: 在要打包的根目录(也就是dubbo-admin-master目录)中,执行mvn clean package -Dmaven.test.skip=true
打包完成后,在dubbo-admin-master/dubbo-admin/
目录下,会出现一个target
目录,执行这个目录中的dubbo-admin-0.0.1-SNAPSHOT.jar
包,就可开启Dubbo-admin,前提是ZooKeeper是开启的
这个项目的默认端口是7001
账户: root
依赖
<dependency>
<groupId>org.apache.dubbogroupId>
<artifactId>dubbo-spring-boot-starterartifactId>
<version>2.7.6version>
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.6.0version>
<exclusions>
<exclusion>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-log4j12artifactId>
exclusion>
exclusions>
dependency>
提供者配置
server.port=8001
# 服务应用的名字
dubbo.application.name=private-server
# 注册中心地址
dubbo.registry.address=zookeeper://127.0.0.1:2181
# 需要被注册的服务
dubbo.scan.base-packages=pero.fisher.service
向ZooKeeper注册服务
import org.apache.dubbo.config.annotation.Service;
@Service
public class TicketServiceImpl implements TicketService {
@Override
public String getTicket() {
return "获得一张票";
}
}
@Service
通过dubbo注册了这个接口注意: @Service的包一定是
dubbo
的包
依赖
调用的依赖和注册的依赖是相同的
消费者配置
server.port=8222
dubbo.application.name=my-consumer-server
dubbo.registry.address=zookeeper://127.0.0.1:2181
server.port=8222
dubbo.application.name=my-consumer-server
dubbo.registry.address=zookeeper://127.0.0.1:2181
消费者只需要配置名字和ZooKeeper的地址就可以,不需要注册服务
消费
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Reference
private TicketService ticketService;
public void buyTicket() {
System.out.println(ticketService.getTicket());
}
}
使用
@Reference
注解取出注册中心的接口这个接口是在消费者中没有的,因此消费者需要在创建与提供者相同的包和类