spring boot2.0+shiro+mybatis多資料來源+druid連線池專案整合
阿新 • • 發佈:2018-12-09
關於整合
網上關於springboot2.0和shiro+myabtis整合的案例很少,大神的教程也是用jpa編寫,jpa很方便,但是還有很多人用mybatis,加之剛學習完mybatis多資料來源整合和druid連線池監控配置,所以算是階段性記錄。
專案目錄
POM檔案
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>mybatisdemo</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>mybatisdemo</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.4.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.10</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> </dependency> <dependency> <groupId> org.springframework.boot </groupId> <artifactId> spring-boot-configuration-processor </artifactId> <optional> true </optional> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
application.yml檔案
server: port: 8080 spring: datasource: druid: test1: #配置監控統計攔截的filters,去掉後監控介面SQL無法進行統計,'wall'用於防火牆 filters: stat,wall driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true username: root password: root #初始化大小 initial-size: 1 #最小連線數 min-idle: 1 #最大連線數 max-active: 20 #獲取連線等待超時時間 max-wait: 60000 #間隔多久才進行一次檢測,檢測需要關閉的空閒連線,單位毫秒 time-between-eviction-runs-millis: 60000 #一個連線在池中最小生存的時間,單位是毫秒 min-evictable-idle-time-millis: 30000 #測試語句是否執行正確 validation-query: SELECT 'x' #指明連線是否被空閒連接回收器(如果有)進行檢驗.如果檢測失敗,則連線將被從池中去除. test-while-idle: true #借出連線時不要測試,否則很影響效能 test-on-borrow: false test-on-return: false #開啟PSCache,並指定每個連線上PSCache的大小。oracle設為true,mysql設為false。分庫分表較多推薦設定為false pool-prepared-statements: false #與Oracle資料庫PSCache有關,再druid下可以設定的比較高 max-pool-prepared-statement-per-connection-size: 20 #資料來源2 test2: filters: stat,wall driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/mytest2?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true username: root password: root initial-size: 1 min-idle: 1 max-active: 20 max-wait: 60000 time-between-eviction-runs-millis: 60000 min-evictable-idle-time-millis: 30000 validation-query: SELECT 'x' test-while-idle: true test-on-borrow: false test-on-return: false pool-prepared-statements: false max-pool-prepared-statement-per-connection-size: 20 thymeleaf: cache: false mode: LEGACYHTML5
資料來源1配置
package com.example.mybatisdemo.config; import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.SqlSessionTemplate; import org.mybatis.spring.annotation.MapperScan; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.jdbc.datasource.DataSourceTransactionManager; @Configuration @MapperScan(basePackages = "com.example.mybatisdemo.dao1",sqlSessionTemplateRef = "test1SqlSessionTemplate") public class DataSource1Config { @Bean(name = "test1DataSource") @ConfigurationProperties(prefix = "spring.datasource.druid.test1") @Primary public DruidDataSource test1DataSource(){ return DruidDataSourceBuilder.create().build(); } @Bean(name = "test1SqlSessionFactory") @Primary public SqlSessionFactory test1sqlSessionFactory(@Qualifier("test1DataSource") DruidDataSource druidDataSource) throws Exception{ SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(druidDataSource); bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml")); return bean.getObject(); } @Bean(name = "test1TransactionManager") @Primary public DataSourceTransactionManager test1TransactionManager(@Qualifier("test1DataSource")DruidDataSource druidDataSource){ return new DataSourceTransactionManager(druidDataSource); } @Bean(name = "test1SqlSessionTemplate") @Primary public SqlSessionTemplate test1SqlSessionTemplate(@Qualifier("test1SqlSessionFactory")SqlSessionFactory sqlSessionFactory){ return new SqlSessionTemplate(sqlSessionFactory); } }
資料來源2配置
package com.example.mybatisdemo.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
@Configuration
@MapperScan(basePackages = "com.example.mybatisdemo.dao2",sqlSessionTemplateRef = "test2SqlSessionTemplate")
public class DataSource2Config {
@Bean(name = "test2DataSource")
@ConfigurationProperties(prefix = "spring.datasource.druid.test2")
// @Primary
public DruidDataSource test2DataSource(){
return DruidDataSourceBuilder.create().build();
}
@Bean(name = "test2SqlSessionFactory")
// @Primary
public SqlSessionFactory test2sqlSessionFactory(@Qualifier("test2DataSource") DruidDataSource druidDataSource) throws Exception{
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(druidDataSource);
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));
return bean.getObject();
}
@Bean(name = "test2TransactionManager")
// @Primary
public DataSourceTransactionManager test2TransactionManager(@Qualifier("test2DataSource")DruidDataSource druidDataSource){
return new DataSourceTransactionManager(druidDataSource);
}
@Bean(name = "test2SqlSessionTemplate")
// @Primary
public SqlSessionTemplate test2SqlSessionTemplate(@Qualifier("test2SqlSessionFactory")SqlSessionFactory sqlSessionFactory){
return new SqlSessionTemplate(sqlSessionFactory);
}
}
Druid連線池監控配置
package com.example.mybatisdemo.config;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class DruidConfig {
/**
* 註冊servlet資訊,配置監控圖
*
*/
@Bean
@ConditionalOnMissingBean
public ServletRegistrationBean druidServlet(){
ServletRegistrationBean servletRegistrationBean =
new ServletRegistrationBean(new StatViewServlet(),"/druid/*");
//白名單
servletRegistrationBean.addInitParameter("allow","192.168.6.195");
//IP黑名單(存在共同時,deny優先於allow) : 如果滿足deny的話提示:Sorry, you are not permitted to view this page.
servletRegistrationBean.addInitParameter("deny","192.168.6.73");
//用於登陸的賬號密碼
servletRegistrationBean.addInitParameter("loginUsername","admin");
servletRegistrationBean.addInitParameter("loginPassword","admin");
//是否能重置資料
servletRegistrationBean.addInitParameter("resetEnable","true");
return servletRegistrationBean;
}
/**
*
* 註冊filter資訊,用於攔截
*/
@Bean
@ConditionalOnMissingBean
public FilterRegistrationBean filterRegistrationBean(){
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(new WebStatFilter());
filterRegistrationBean.addUrlPatterns("/*");
filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
return filterRegistrationBean;
}
}
關於RABC角色訪問控制
RBAC 是基於角色的訪問控制(Role-Based Access Control )在 RBAC 中,許可權與角色相關聯,使用者通過成為適當角色的成員而得到這些角色的許可權。這就極大地簡化了許可權的管理。這樣管理都是層級相互依賴的,許可權賦予給角色,而把角色又賦予使用者,這樣的許可權設計很清楚,管理起來很方便。
建表過程
CREATE TABLE `sys_permission` (
`id` int(11) NOT NULL,
`available` bit(1) DEFAULT NULL,
`name` varchar(255) DEFAULT NULL,
`parent_id` bigint(20) DEFAULT NULL,
`parent_ids` varchar(255) DEFAULT NULL,
`permission` varchar(255) DEFAULT NULL,
`resource_type` enum('menu','button') DEFAULT NULL,
`url` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4
CREATE TABLE `sys_role` (
`id` int(11) NOT NULL,
`available` bit(1) DEFAULT NULL,
`description` varchar(255) DEFAULT NULL,
`role` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4
CREATE TABLE `user_info` (
`uid` int(11) NOT NULL,
`name` varchar(255) DEFAULT NULL,
`password` varchar(255) DEFAULT NULL,
`salt` varchar(255) DEFAULT NULL,
`state` tinyint(4) NOT NULL,
`username` varchar(255) DEFAULT NULL,
PRIMARY KEY (`uid`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4
CREATE TABLE `sys_role_permission` (
`permission_id` int(11) NOT NULL,
`role_id` int(11) NOT NULL,
KEY `FK9q28ewrhntqeipl1t04kh1be7` (`role_id`),
KEY `FKomxrs8a388bknvhjokh440waq` (`permission_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4
CREATE TABLE `sys_user_role` (
`role_id` int(11) NOT NULL,
`uid` int(11) NOT NULL,
KEY `FKgkmyslkrfeyn9ukmolvek8b8f` (`uid`),
KEY `FKhh52n8vd4ny9ff4x9fb8v65qx` (`role_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4
插入測試資料
INSERT INTO `user_info` (`uid`,`username`,`name`,`password`,`salt`,`state`) VALUES ('1', 'admin', '管理員', 'd3c59d25033dbf980d29554025c23a75', '8d78869f470951332959580424d4bf4f', 0);
INSERT INTO `sys_permission` (`id`,`available`,`name`,`parent_id`,`parent_ids`,`permission`,`resource_type`,`url`) VALUES (1,0,'使用者管理',0,'0/','userInfo:view','menu','userInfo/userList');
INSERT INTO `sys_permission` (`id`,`available`,`name`,`parent_id`,`parent_ids`,`permission`,`resource_type`,`url`) VALUES (2,0,'使用者新增',1,'0/1','userInfo:add','button','userInfo/userAdd');
INSERT INTO `sys_permission` (`id`,`available`,`name`,`parent_id`,`parent_ids`,`permission`,`resource_type`,`url`) VALUES (3,0,'使用者刪除',1,'0/1','userInfo:del','button','userInfo/userDel');
INSERT INTO `sys_role` (`id`,`available`,`description`,`role`) VALUES (1,0,'管理員','admin');
INSERT INTO `sys_role` (`id`,`available`,`description`,`role`) VALUES (2,0,'VIP會員','vip');
INSERT INTO `sys_role` (`id`,`available`,`description`,`role`) VALUES (3,1,'test','test');
INSERT INTO `sys_role_permission` VALUES ('1', '1');
INSERT INTO `sys_role_permission` (`permission_id`,`role_id`) VALUES (1,1);
INSERT INTO `sys_role_permission` (`permission_id`,`role_id`) VALUES (2,1);
INSERT INTO `sys_role_permission` (`permission_id`,`role_id`) VALUES (3,2);
INSERT INTO `sys_user_role` (`role_id`,`uid`) VALUES (1,1);
實體類(getter setter方法自行實現)
package com.example.mybatisdemo.domain;
public class Permission {
private Integer id;
private Boolean available;
private String name;
private Long parent_id;
private String parent_ids;
private String permission;
private String resource_type;
private String url;}
package com.example.mybatisdemo.domain;
public class Role {
private Integer id;
private Boolean available;
private String description;
private String role;
}
package com.example.mybatisdemo.domain;
public class UserInfo {
private Integer uid;
private String name;
private String username;
private String password;
private String salt;
private byte state;
//密碼加鹽
public String getCredentialsSalt(){
return this.username+this.salt;
}
}
鹽值由資料庫中的salt和賬號組合而成
DAO層
因為多資料來源配置,dao層有兩個並分開使用,可以一個設計資料一個設計使用者驗證,這裡只實現使用者驗證放dao1中
package com.example.mybatisdemo.dao1;
import com.example.mybatisdemo.domain.Permission;
import com.example.mybatisdemo.domain.Role;
import com.example.mybatisdemo.domain.UserInfo;
import java.util.List;
public interface UserInfoDao {
UserInfo selectByUsername(String username);
List<Integer> selectRoleidByUid(Integer uid);
Role selectRoleById(Integer id);
List<Integer> selectPermissionidByRoleid(Integer roleid);
Permission selectPermissionById(Integer permissionid);
}
Mapper實現
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.example.mybatisdemo.dao1.UserInfoDao">
<sql id="BASE_COLUMN">
uid,name,password,salt,state,username
</sql>
<sql id="BASE_TABLE">
user_info
</sql>
<sql id="USERNAME">
username
</sql>
<sql id="ROLEID">
role_id
</sql>
<sql id="USERROLE">
sys_user_role
</sql>
<sql id="ROLE_COLUMN">
id,available,description,role
</sql>
<sql id="ROLETABLE">
sys_role
</sql>
<sql id="PERMISSIONID">
permission_id
</sql>
<sql id="ROLEPERMISSION">
sys_role_permission
</sql>
<sql id="PERMISSIONCOLUNMN">
id,available,name,parent_id,parent_ids,permission,resource_type,url
</sql>
<sql id="PERMISSIONTABLE">
sys_permission
</sql>
<select id="selectByUsername" resultType="com.example.mybatisdemo.domain.UserInfo" parameterType="java.lang.String">
select
<include refid="BASE_COLUMN"/>
FROM
<include refid="BASE_TABLE"/>
WHERE
<include refid="USERNAME"/>
<trim prefix="=">
#{username,jdbcType=VARCHAR}
</trim>
</select>
<select id="selectRoleidByUid" resultType="java.lang.Integer" parameterType="java.lang.Integer">
SELECT
<include refid="ROLEID"/>
FROM
<include refid="USERROLE"/>
WHERE uid
<trim prefix="=">
#{uid,jdbcType=INTEGER}
</trim>
</select>
<select id="selectRoleById" resultType="com.example.mybatisdemo.domain.Role" >
SELECT
<include refid="ROLE_COLUMN"/>
FROM
<include refid="ROLETABLE"/>
WHERE id
<trim prefix="=">
#{id, jdbcType=INTEGER}
</trim>
</select>
<select id="selectPermissionidByRoleid" resultType="java.lang.Integer" parameterType="java.lang.Integer">
SELECT
<include refid="PERMISSIONID"/>
FROM
<include refid="ROLEPERMISSION"/>
where
<include refid="ROLEID"/>
<trim prefix="=">
#{roleid, jdbcType=INTEGER}
</trim>
</select>
<select id="selectPermissionById" resultType="com.example.mybatisdemo.domain.Permission">
SELECT
<include refid="PERMISSIONCOLUNMN"/>
FROM
<include refid="PERMISSIONTABLE"/>
WHERE id
<trim prefix="=">
#{id, jdbcType=INTEGER}
</trim>
</select>
</mapper>
Service層
package com.example.mybatisdemo.service;
import com.example.mybatisdemo.domain.Permission;
import com.example.mybatisdemo.domain.Role;
import com.example.mybatisdemo.domain.UserInfo;
import java.util.List;
public interface UserInfoService {
UserInfo findByUsername(String username);
Role findRoleById(Integer id);
List<Integer> findRoleidByUid(Integer uid);
List<Integer> findPermissionidByRoleid(Integer roleid);
Permission findPermissionById(Integer permissionid);
}
Service層實現
package com.example.mybatisdemo.service.impl;
import com.example.mybatisdemo.dao1.UserInfoDao;
import com.example.mybatisdemo.domain.Permission;
import com.example.mybatisdemo.domain.Role;
import com.example.mybatisdemo.domain.UserInfo;
import com.example.mybatisdemo.service.UserInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service(value = "userInfoService")
public class UserInfoServiceImpl implements UserInfoService {
@Autowired
private UserInfoDao userInfoDao;
@Override
public UserInfo findByUsername(String username) {
return userInfoDao.selectByUsername(username);
}
@Override
public Role findRoleById(Integer id) {
return userInfoDao.selectRoleById(id);
}
@Override
public List<Integer> findRoleidByUid(Integer uid) {
return userInfoDao.selectRoleidByUid(uid);
}
@Override
public List<Integer> findPermissionidByRoleid(Integer roleid) {
return userInfoDao.selectPermissionidByRoleid(roleid);
}
@Override
public Permission findPermissionById(Integer permissionid) {
return userInfoDao.selectPermissionById(permissionid);
}
}
Web層
package com.example.mybatisdemo.web;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
@Controller
public class HomeController {
@RequestMapping({"/","/index"})
public String index(){
return "/index";
}
@RequestMapping("/login")
public String login(HttpServletRequest request, Map<String,Object> map) throws Exception{
System.out.println("登入頁面");
// 登入失敗從request中獲取shiro處理的異常資訊。
// shiroLoginFailure:就是shiro異常類的全類名.
String exception = (String) request.getAttribute("shiroLoginFailure");
System.out.println("exception="+exception);
String msg = "";
if (exception!=null){
if (UnknownAccountException.class.getName().equals(exception)){
System.out.println("賬號不存在");
msg = "賬號不存在";
} else if (IncorrectCredentialsException.class.getName().equals(exception)){
System.out.println("密碼不正確");
} else if ("kaptchaValidateFailed".equals(exception)){
System.out.println("驗證碼不正確");
} else {
msg ="else "+exception;
System.out.println("else "+exception);
}
}
map.put("msg",msg);
return "login";
}
@RequestMapping("/403")
public String unauthorizaRole(){
System.out.println("沒有許可權");
return "/403";
}
@RequestMapping("/registered")
public String registered(){
return "/registered";
}
}
package com.example.mybatisdemo.web;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/nofilter")
public class NoFiltercController {
@RequestMapping("/nofilter")
public String nofilter(){
return "static/nofilter";
}
}
在/nofilter下的所有連結都可以匿名訪問
package com.example.mybatisdemo.web;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/userInfo")
public class UserInfoController {
@RequestMapping("/userList")
@RequiresPermissions("userInfo:view")
public String userInfo(){
return "userInfo";
}
@RequestMapping("/userAdd")
@RequiresPermissions("userInfo:add")
public String userInfoAdd(){
return "userInfoAdd";
}
@RequestMapping("/userDel")
@RequiresPermissions("UserInfo:del")
public String userInfoDel(){
return "userInfoDel";
}
}
/userinfo下的所有連線都需要驗證登陸後實現
關於頁面
除了login.html之外都很簡單,貼出login.html和index.html,其他的網頁都可以仿照index.html實現
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Login</title>
</head>
<body>
錯誤資訊:<h4 th:text="${msg}"></h4>
<form action="" method="post">
<p>賬號:<input type="text" name="username" value="admin"/></p>
<p>密碼:<input type="text" name="password" value="123456"/></p>
<p><input type="submit" value="登入"/>
<button onclick="window.location.href='/registered'" type="button">註冊
</button>
</p>
</form>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>index</title>
</head>
<body>
<h1>index</h1>
</body>
</html>
測試
3、修改admin不同的許可權進行測試
阿里雲druid真的很好用