这里写目录标题
1. 创建项目
idea创建springboot项目非常简单,根据创建指引完成即可。
1.create new project
2.选择Spring Initializr初始化,选择sdk和default;
3.自定义group、artifact、name等信息,默认使用maven类型的java8版本;
4.idea默认的springboot版本为2.2.6,我们搭建的是springboot web项目,所以选择web下的spring web;
此外还需连接数据库,选择mysql和mybatis框架;
一路next下去即可。
2. 项目结构
2.1 唯一主入口
默认在包的同级路径下生成一个XXXApplication.java,内部是一个main方法。
在类上使用==@SpringBootApplication注解==,标识该类为唯一主入口!
程序启动方式:执行main方法。
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
2.2 resource
- static:默认的存放css、js、image等静态资源的文件夹;
- thymeleaf:默认存放html等页面的文件夹;
- application.proprities(.yml):属性配置文件,.properties和.yml只是书写格式不同;
2.3 pom.xml
在创建项目时选择了spring web、mybatis和mysql,idea就根据我们选的这些模块初试化好了相关依赖。依赖如下:
<?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>
<!-- 默认的springboot版本:2.2.6 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- spring web模块 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- mybatis框架 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.2</version>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- test测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3. 基础配置
3.1 指定Tomcat端口号
application.yml
#指定内置Tomcat端口号
server:
port: 9090
3.2 整合mybatis
mybatis作为持久层框架,传统的SSM整合时需要:
application.xml
1. xml文件配置数据源;
2. xml文件配置事务并开启事务注解;
3. 对mapper接口和xml路径无要求;
4. 实体类设置别名,xml文件要映射;
与springboot整合:
5. application.yml配置;
6. 直接使用@Transactional;
7. springboot默认有要求,但是可以更改;
3.2.1 数据源
- SSM:xml文件配置数据源;
- springboot:application.yml配置;
application.yml
注意冒号后面要空一格
#mysql数据库
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://localhost:3306/db_mis?&characterEncoding=utf8&useSSL=false&allowMultiQueries=true
username: root
password: root
多环境配置
application.yml
spring:
# 运行环境 dev:开发环境|test:测试环境|prod:生产环境
profiles:
active: dev
application-dev.yml
server:
port: 5555
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://10.20.61.55:3306/netseal_4_data?&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&serverTimezone=UTC
username: root
password: root
mvc:
pathmatch:
use-suffix-pattern: true
3.2.3 起别名
指定type-aliases-package,就会自动扫描映射别名,别名就是类名首字母小写。
application.yml
#指定model的位置,自定义扫描映射别名
mybatis:
type-aliases-package: cn.com.gs.library.model
3.2.4 xml映射
springboot默认mapper接口和mapper.xml需要目录结构一致,才能映射:
- 可以放在一起,但是不好管理(不推荐);
- 可以将mapper.xml放在resource目录下
- 保持和接口路径一致,若包路径很长,也不利于管理(不推荐);
- 无需保持接口路径一致,需要在application.yml中指定xml的位置(推荐);
application.yml
#指定mapper.xml的位置
mybatis:
mapper-locations: classpath:/mapper/*.xml
3.2.5 mapper代理
- 方式一:在每一个mapper接口类上使用@Mapper注解;
- 方式二:在程序主入口类上使用@MapperScan注解,指定value值为mapper接口路径;
package cn.com.gs.library;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @MapperScan将指定包中的所有接口都标注为DAO层接口,相当于在每一个接口上写@Mapper
*/
@SpringBootApplication
@MapperScan(value = "cn.com.gs.library.dao")
public class LibraryApplication {
/**
* springboot项目程序主入口
* 启动main方法相当于启动Tomcat
* @param args
*/
public static void main(String[] args) {
SpringApplication.run(LibraryApplication.class, args);
}
}
3.3 mvc配置
springboot之前的版本认为bookList和bookList.do是同一个请求,从哪一个版本开始就不这样认为了。
我们还是拦截*.do的请求,需要开启use-suffix-pattern,使用后缀匹配功能,springboot就会认为是同一个请求了。
application.yml
spring:
mvc:
pathmatch:
use-suffix-pattern: true
4. 整合其他技术
整合技术就不再使用上述基础demo了,将会使用已经搭建好的libraryMIS图书管理系统。
4.1 Thymeleaf
pom.xml
<!-- 使用thymeleaf引擎 -->
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-thymeleaf -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
<!--热部署,修改代码无需重启,同时yml关闭thymeleaf缓存-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<!-- optional=true, 依赖不会传递, 该项目依赖devtools; 之后依赖boot项目的项目如果想要使用devtools, 需要重新引入 -->
<optional>true</optional>
</dependency>
第一步:在标签内引入thymeleaf标签库:xmlns:th=“http://www.thymeleaf.org”
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>用户登录</title>
</head>
<body>
</body>
</html>
第二步:使用thymeleaf标签
含义 | 标签 |
---|---|
取值 | th:text="${msg}" |
4.1.1 引用公共样式文件
目录结构:
|—templates
|—common
|—resource.html
|—main.html
resource.html:相当于是一个通用模板,可以接收传进来的非公共css和script进行渲染。
使用th:fragment定义一个方法,接收title(必须)等信息;
使用th:block和th:replace进行渲染。
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head th:fragment="common_resource(title,links,scripts)">
<meta charset="UTF-8">
<title th:replace="${title}">Title</title>
<link rel="stylesheet" type="text/css" href="/static/h-ui/css/H-ui.min.css" />
<link rel="stylesheet" type="text/css" href="/static/h-ui.admin/css/H-ui.admin.css" />
<script type="text/javascript" src="/static/lib/jquery/1.9.1/jquery.min.js"></script>
<script type="text/javascript" src="/static/lib/layer/2.4/layer.js"></script>
<!--/_footer 作为公共模版分离出去-->
<th:block th:replace="${links}"></th:block>
<th:block th:replace="${scripts}"></th:block>
</head>
<body>
</body>
</html>
main.html
在head标签内使用th:replace,
common/resource:指公共模板;
common_resource(传参):~{::title}指将本页面main.html的title值传给模板;语法: ~{::标签}
参数没有要传的值就不写:例如 ~{}
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="common/resource :: common_resource(~{::title},~{},~{::script})">
<!--[if lt IE 9]>
<script type="text/javascript" src="/lib/html5shiv.js"></script>
<script type="text/javascript" src="/lib/respond.min.js"></script>
<![endif]-->
<!--[if IE 6]>
<script type="text/javascript" src="/lib/DD_belatedPNG_0.0.8a-min.js" ></script>
<script>DD_belatedPNG.fix('*');</script>
<![endif]-->
</head>
4.2 shiro
SSM框架整合shiro时,是使用xml配置的方式实现bean注入;在springboot中,没有了xml文件,需要以自定义类+注解的方式完成注入。
- 自定义shiro的配置类ShiroConfig.java,使用@Configuration注解标识;
- 注入所需的SecurityManager、自定义的SysUserRealm、ShiroFilterFactoryBean;
pom.xml
<properties>
<shiro.version>1.3.1</shiro.version>
</properties>
<dependencies>
<!--shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-all</artifactId>
<version>${shiro.version}</version>
</dependency>
</dependencies>
ShiroConfig.java
package cn.com.gs.library.realm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.mgt.SecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
/**
* 自定义的认证方法
*/
@Bean
public SysUserRealm sysUserRealm(){
SysUserRealm sysUserRealm = new SysUserRealm();
return sysUserRealm;
}
/**
* 配置securityManager管理器
* 指定认证器
* 指定缓存器
* @return
*/
@Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
defaultWebSecurityManager.setRealm(sysUserRealm());
return defaultWebSecurityManager;
}
/**
* 开启shiro 注解支持
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
/**
* 配置shiro过滤器
* 给ShiroFilterFactoryBean指定管理器
* 配置过滤器链
* @param securityManager
* @return
*/
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){
System.out.println("shiroFilter过滤器链");
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 指定管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 配置过滤器
shiroFilterFactoryBean.setLoginUrl("/sysUser/toLogin.do");
shiroFilterFactoryBean.setSuccessUrl("/sysUser/toMain.do");
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/sysUser/toLogin.do", "anon");
filterChainDefinitionMap.put("/sysUser/toShiroLogin.do", "anon");
filterChainDefinitionMap.put("/sysUser/login.do", "anon");
filterChainDefinitionMap.put("/sysUser/timeout.do", "anon");
filterChainDefinitionMap.put("/sysUser/toRefuse.do", "anon");
filterChainDefinitionMap.put("/static/**", "anon");
filterChainDefinitionMap.put("/2/**", "anon");
filterChainDefinitionMap.put("/bootstrap/**", "anon");
filterChainDefinitionMap.put("/css/**", "anon");
filterChainDefinitionMap.put("/images/**", "anon");
filterChainDefinitionMap.put("/img/**", "anon");
filterChainDefinitionMap.put("/js/**", "anon");
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
}
SysUserRealm.java
package cn.com.gs.library.realm;
import javax.annotation.Resource;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.authz.permission.PermissionResolver;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import cn.com.gs.library.common.util.StringUtil;
import cn.com.gs.library.dao.ButtonDao;
import cn.com.gs.library.dao.MenuDao;
import cn.com.gs.library.dao.RoleDao;
import cn.com.gs.library.dao.sysUser.UserDao;
import cn.com.gs.library.model.Button;
import cn.com.gs.library.model.Menu;
import cn.com.gs.library.model.Role;
import cn.com.gs.library.model.User;
public class SysUserRealm extends AuthorizingRealm{
@Resource
private UserDao userDao;
@Resource
private RoleDao roleDao;
@Resource
private MenuDao menuDao;
@Resource
private ButtonDao buttonDao;
protected String getRealmName(){
return "sysUserRealm";
}
/**
* 认证
*
* */
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("------认证:doGetAuthenticationInfo--------");
//1.从token中获取用户输入的用户名
String account = token.getPrincipal().toString();
System.out.println("输入的账户名:"+account);
//2.根据用户名去查询数据库,得到账密信息(要求用户名唯一)
User user = userDao.getUserByAccount(account);
if(user == null){
return null;
}
/*
* 3.将信息封装给SimpleAuthenticationInfo:
* Object principal, 身份信息:可以传入查询出的对象,也可以只传入用户名(传入对象subject就可以获取到)
* Object credentials, 密码信息:传入数据库查询出的密码信息
* String realmName Realm的名字
*
* shiro内部将会比对token的密码和传入的数据库密码是否相等,返回SimpleAuthenticationInfo对象
* Subject就会得到该SimpleAuthenticationInfo信息,
* 最后在controller的登录方法里,由subject判断是否认证通过
* 通过:获取对象存入session
* */
//TODO 密码加密的情况,学习盐值
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), getRealmName());
return info;
}
/**
* 授权
* 前台发请求,先走shiro的过滤器链(包含自定义过滤器)
* 前台进入页面后,每个被shiro标记的按钮都会发请求来验证权限,效率很低
* 请整合redis缓存
*
* */
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
Integer menuId = null;
Integer buttonId = null;
Menu menu = null;
Button button = null;
System.out.println("-----------授权方法------------");
//1.获取已登录的个人身份信息(认证时传入SimpleAuthenticationInfo的个人信息)
User user = (User)principal.getPrimaryPrincipal();
//TODO 已集成ehcache 学习整合Redis,先查缓存,缓存中没有再查数据库
//2.获取该用户的已授权按钮信息(菜单级别暂时用NetSeal的写法)
Role role = roleDao.getRoleById(user.getRoleId());
//TODO 已实现按钮用shiro标签的实现,基于url的暂未实现
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//3.将菜单的code信息(shiro要求的授权规则)传给SimpleAuthorizationInfo
String menuIds = role.getMenuId();
String[] menuIdArr = menuIds.split(",");
for(String tempId : menuIdArr){
tempId = tempId.trim();
menuId = Integer.parseInt(tempId);
menu = menuDao.getMenuById(menuId);
if(StringUtil.isNotBlank(menu.getMenuCode())) {
info.addStringPermission(menu.getMenuCode());
}
//添加url,在自定义过滤器中控制访问
info.addStringPermission(menu.getUrl());
}
//4.将按钮的code信息(shiro要求的授权规则)传给SimpleAuthorizationInfo
String buttonIds = role.getButtonId();
String[] buttonIdArr = buttonIds.split(",");
for(String tempId : buttonIdArr){
tempId = tempId.trim();
buttonId = Integer.parseInt(tempId);
button = buttonDao.getButtonById(buttonId);
info.addStringPermission(button.getButtonCode());
}
//添加url,在自定义过滤器中控制访问
info.addStringPermission(button.getUrl());
return info;
}
}
4.2.1 Thymeleaf + shiro
- 第一步:添加依赖;
pom.xml
<!--shiro+thymeleaf-->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
- 第二步:修改ShiroConfig;
ShiroConfig.java
/**
* 前台的shiro标签才能生效,注意thymeleaf-extras-shiro版本问题:2.0.0可用
* @return
*/
@Bean
public ShiroDialect shiroDialect(){
return new ShiroDialect();
}
4.2.2 配置ehcache缓存
在shiro-all的jar包中,已经包含了ehcache相关类。
ShiroConfig.java
/**
* 配置ehcache缓存
*/
@Bean
public EhCacheManager ehcacheManager(){
EhCacheManager ehCacheManager = new EhCacheManager();
return ehCacheManager;
}
@Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
defaultWebSecurityManager.setRealm(sysUserRealm());
defaultWebSecurityManager.setCacheManager(ehcacheManager());
return defaultWebSecurityManager;
}
5 bug解决
5.1 There are test failures.
There are test failures.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<testFailureIgnore>true</testFailureIgnore>
</configuration>
</plugin>