avatar

目录
bootdo已实现(1)

整合mybatis

pom

依赖

xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<!--mybatis 分页插件 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.5</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>

逆向工程插件

xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<!--mybatis 逆向工程-->
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.5</version>
<configuration>
<configurationFile>src/main/resources/generatorConfig.xml</configurationFile>
<verbose>true</verbose>
<overwrite>true</overwrite>
</configuration>
<executions>
<execution>
<id>Generate MyBatis Artifacts</id>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.5</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
<scope>runtime</scope>
</dependency>
</dependencies>
</plugin>

yml

yaml
1
2
3
4
5
6
7
8
9
10
11
12
spring:
datasource: # ======数据源=========
url: jdbc:mysql://10.211.55.6:3306/mybootdo?useUnicode=true&characterEncoding=utf8
username: machine
password: 4869
driver-class-name: com.mysql.jdbc.Driver

mybatis:
mapper-locations: mybatis/**/*Mapper.xml # Mapper扫描
type-aliases-package: machine.mybootdo.**.domain # 别名
configuration:
map-underscore-to-camel-case: true # 字段映射用驼峰命名(user_id > userId)

事务

Code
1
2
1. 在springboot启动类上加 @EnableTransactionManagement
2. 在service类或者方法加 @Transactional

测试

测试controller

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package machine.mybootdo;

import machine.mybootdo.common.config.ApplicationContextRegister;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import javax.servlet.Filter;

@RunWith(SpringRunner.class)
@SpringBootTest
public class ControllerTests {


private MockMvc mockMvc; //模拟MVC对象

@Autowired
private WebApplicationContext wac;

@Before
public void setup(){
//加入过滤器
Filter filter = ApplicationContextRegister.getBean("shiroFilterFactoryBean");
//MockMvcBuilders使用构建MockMvc对象 (项目拦截器有效)
mockMvc = MockMvcBuilders.webAppContextSetup(wac).addFilter(filter).build();
//单个类 拦截器无效
//mockMvc = MockMvcBuilders.standaloneSteup(userController).build();
}

@Test
public void test01() throws Exception{
//模拟post
MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders
.post("/login")
.param("username", "admin");
//.param("password", "123");

String str = mockMvc.perform(requestBuilder)
.andReturn().getResponse()
.getContentAsString();

System.out.println(str);
System.out.println("======================");
}
}

开发准备

Code
1
2
3
4
5
6
7
1. 逆向工程:
mapper需要改包名
2. dao需要加@Mapper注释
3. service的接口加 @Service,实现类也加 @Service
4. controller要加controller
5. service实现类要考虑事务 @Transactional
@Transactional(readOnly = true,rollbackFor = Exception.class)

API管理

/index:跳转管理页面;加载菜单${menus};

1. 登录(shiro认证)

流程:

Code
1
2
subject.login(token);
//此方法请求认证,shiro会调用[自定义realm]验证合法性,若非法,就报错(抱realm里抛出的错误),若合法就正常执行

pom

Code
1
2
3
4
5
6
<!--shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>

sys_user

sql
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
-- ----------------------------
-- sys_user
-- ----------------------------
CREATE TABLE `sys_user` (
`user_id` bigint(20) NOT NULL AUTO_INCREMENT,
`username` varchar(50) DEFAULT NULL COMMENT '用户名',
`name` varchar(100) DEFAULT NULL,
`password` varchar(50) DEFAULT NULL COMMENT '密码',
`dept_id` bigint(20) DEFAULT NULL,
`email` varchar(100) DEFAULT NULL COMMENT '邮箱',
`mobile` varchar(100) DEFAULT NULL COMMENT '手机号',
`status` tinyint(255) DEFAULT NULL COMMENT '状态 0:禁用,1:正常',
`user_id_create` bigint(255) DEFAULT NULL COMMENT '创建用户id',
`gmt_create` datetime DEFAULT NULL COMMENT '创建时间',
`gmt_modified` datetime DEFAULT NULL COMMENT '修改时间',
`sex` bigint(32) DEFAULT NULL COMMENT '性别',
`birth` datetime DEFAULT NULL COMMENT '出身日期',
`pic_id` bigint(32) DEFAULT NULL,
`live_address` varchar(500) DEFAULT NULL COMMENT '现居住地',
`hobby` varchar(255) DEFAULT NULL COMMENT '爱好',
`province` varchar(255) DEFAULT NULL COMMENT '省份',
`city` varchar(255) DEFAULT NULL COMMENT '所在城市',
`district` varchar(255) DEFAULT NULL COMMENT '所在地区',
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=138 DEFAULT CHARSET=utf8;

INSERT INTO `sys_user` VALUES ('1', 'admin', '超级管理员', '27bd386e70f280e24c2f4f2a549b82cf', '6', 'admin@example.com', '17699999999', '1', '1', '2017-08-15 21:40:39', '2017-08-15 21:41:00', '96', '2017-12-14 00:00:00', '138', 'ccc', '121;', '北京市', '北京市市辖区', '东城区');
INSERT INTO `sys_user` VALUES ('2', 'test', '临时用户', '6cf3bb3deba2aadbd41ec9a22511084e', '6', 'test@bootdo.com', null, '1', '1', '2017-08-14 13:43:05', '2017-08-14 21:15:36', null, null, null, null, null, null, null, null);

dao mapper domain

方法:list

UserDO UserDao UserMapper

ShiroConfig

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package machine.mybootdo.system.config;

import machine.mybootdo.system.shiro.UserRealm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;

@Configuration //相当于spring核心配置文件
public class ShiroConfig {
/*
用户自定义realm域,校验数据合法性
*/
@Bean
UserRealm userRealm() {
UserRealm userRealm = new UserRealm();
return userRealm;
}

@Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//设置realm
securityManager.setRealm(userRealm());
return securityManager;
}

@Bean
ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
shiroFilterFactoryBean.setLoginUrl("/login");
shiroFilterFactoryBean.setSuccessUrl("/index");
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/css/**", "anon");//静态资源可以匿名访问
filterChainDefinitionMap.put("/js/**", "anon");
filterChainDefinitionMap.put("/fonts/**", "anon");
filterChainDefinitionMap.put("/img/**", "anon");
filterChainDefinitionMap.put("/docs/**", "anon");
filterChainDefinitionMap.put("/", "anon");//首页
filterChainDefinitionMap.put("/**", "authc");//其他资源必须认证才能访问
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
}

UserRealm

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package machine.mybootdo.system.shiro;

import com.alibaba.fastjson.JSON;
import machine.mybootdo.common.config.ApplicationContextRegister;
import machine.mybootdo.system.dao.UserDao;
import machine.mybootdo.system.domain.UserDO;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class UserRealm extends AuthorizingRealm{
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}

//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

String username = (String) token.getPrincipal();
//报错:java.lang.ClassCastException
//String password = (String) token.getCredentials();
String password = new String((char[])token.getCredentials());

//校验数据,抛异常
// 获取容器bean,因为只有该方法需要userDao ,不需要声明为private
UserDao userDao = ApplicationContextRegister.getBean(UserDao.class);
Map<String, Object> map = new HashMap<>();

map.put("username",username);
List<UserDO> list = userDao.list(map);

//错误:账号不存在
if (list==null || list.size()==0){
throw new UnknownAccountException("账号或密码不正确");
}

UserDO user = list.get(0);

//错误:密码错误
if(!password.equals(user.getPassword())){
throw new IncorrectCredentialsException("账号或密码不正确");
}

//错误:账号锁定
if (user.getStatus() == 0) {
throw new LockedAccountException("账号已被锁定,请联系管理员");
}
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username, password, getName());
return info;
}
}

utils

ApplicationContextRegister

getbean方法

R

表单提交 状态返回

controller

LoginController.ajaxLogin

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@PostMapping("/login")
@ResponseBody
public R ajaxLogin(String username, String password){

/*
--输入校验
*/

SecurityManager securityManager = ApplicationContextRegister.getBean(SecurityManager.class);
Subject subject = SecurityUtils.getSubject();
password = MD5Utils.encrypt(username,password);//密码加密
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
try{
subject.login(token);//会调用UserRealm进行校验,校验失败就抛出异常
return R.ok();
}catch (AuthenticationException e){
return R.error(e.getMessage());
}

}

ui

ajax post提交 参数:username password

2. 加载菜单列表(用户角色菜单)

根据用户id加载用户可操作的菜单列表

Code
1
2
3
4
5
sys_user	
sys_role
sys_menu
sys_user_role
sys_role_menu
sql
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
-- ----------------------------
-- sys_role
-- ----------------------------
CREATE TABLE `sys_role` (
`role_id` bigint(20) NOT NULL AUTO_INCREMENT,
`role_name` varchar(100) DEFAULT NULL COMMENT '角色名称',
`role_sign` varchar(100) DEFAULT NULL COMMENT '角色标识',
`remark` varchar(100) DEFAULT NULL COMMENT '备注',
`user_id_create` bigint(255) DEFAULT NULL COMMENT '创建用户id',
`gmt_create` datetime DEFAULT NULL COMMENT '创建时间',
`gmt_modified` datetime DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`role_id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='角色';

INSERT INTO `sys_role` VALUES ('1', '超级用户角色', 'admin', '拥有最高权限', '2', '2017-08-12 00:43:52', '2017-08-12 19:14:59');

-- ----------------------------
-- sys_menu
-- ----------------------------
CREATE TABLE `sys_menu` (
`menu_id` bigint(20) NOT NULL AUTO_INCREMENT,
`parent_id` bigint(20) DEFAULT NULL COMMENT '父菜单ID,一级菜单为0',
`name` varchar(50) DEFAULT NULL COMMENT '菜单名称',
`url` varchar(200) DEFAULT NULL COMMENT '菜单URL',
`perms` varchar(500) DEFAULT NULL COMMENT '授权(多个用逗号分隔,如:user:list,user:create)',
`type` int(11) DEFAULT NULL COMMENT '类型 0:目录 1:菜单 2:按钮',
`icon` varchar(50) DEFAULT NULL COMMENT '菜单图标',
`order_num` int(11) DEFAULT NULL COMMENT '排序',
`gmt_create` datetime DEFAULT NULL COMMENT '创建时间',
`gmt_modified` datetime DEFAULT NULL COMMENT '修改时间',
PRIMARY KEY (`menu_id`)
) ENGINE=InnoDB AUTO_INCREMENT=102 DEFAULT CHARSET=utf8 COMMENT='菜单管理';

INSERT INTO `sys_menu` VALUES ('3', '0', '系统管理', null, null, '0', 'fa fa-desktop', '1', '2017-08-09 23:06:55', '2017-08-14 14:13:43');
INSERT INTO `sys_menu` VALUES ('6', '3', '用户管理', 'sys/user/', 'sys:user:user', '1', 'fa fa-user', '0', '2017-08-10 14:12:11', null);

-- ----------------------------
-- sys_user_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`user_id` bigint(20) DEFAULT NULL COMMENT '用户ID',
`role_id` bigint(20) DEFAULT NULL COMMENT '角色ID',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=134 DEFAULT CHARSET=utf8 COMMENT='用户与角色对应关系';

INSERT INTO `sys_user_role` VALUES ('1', '1', '1');
INSERT INTO `sys_user_role` VALUES ('2', '2', '1');

-- ----------------------------
-- sys_role_menu
-- ----------------------------
CREATE TABLE `sys_role_menu` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`role_id` bigint(20) DEFAULT NULL COMMENT '角色ID',
`menu_id` bigint(20) DEFAULT NULL COMMENT '菜单ID',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2988 DEFAULT CHARSET=utf8 COMMENT='角色与菜单对应关系';

INSERT INTO `sys_role_menu` VALUES ('1', '1', '6');
INSERT INTO `sys_role_menu` VALUES ('2', '1', '3');

dao mapper domain

方法:listMenuByUserId

MenuDO

MenuDao

Code
1
List<MenuDO> listMenuByUserId(Long id);

MenuMapper.xml

Code
1
2
3
4
5
6
7
8
9
10
11
<select id="listMenuByUserId" resultType="machine.mybootdo.system.domain.MenuDO">
select distinct
m.menu_id , parent_id, name, url,
perms,`type`,icon,order_num,gmt_create, gmt_modified
from sys_menu m
left join sys_role_menu rm on m.menu_id = rm.menu_id
left join sys_user_role ur on rm.role_id =ur.role_id
where ur.user_id = #{id}
and m.type in(0,1)
order by m.order_num
</select>

说明:

Code
1
2
-- 以上sql:列出某个用户所拥有的全部菜单操作
-- 一个用户有多个角色 ,不同的角色有重复的菜单 ;left join后一个用户就会有多个重复菜单,因此要distinct

service

@Transactional(readOnly = true,rollbackFor = Exception.class)

MenuService

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Override
public List<Tree<MenuDO>> listMenuTree(Long id) {

List<Tree<MenuDO>> trees = new ArrayList<>();
List<MenuDO> menuDOs = menuDao.listMenuByUserId(id);
for(MenuDO sysMenuDO : menuDOs){
Tree<MenuDO> tree = new Tree<MenuDO>();
tree.setId(sysMenuDO.getMenuId().toString());
tree.setText(sysMenuDO.getName());
tree.setParentId(sysMenuDO.getParentId().toString());
Map<String,Object> attributes = new HashMap<>();
attributes.put("url",sysMenuDO.getUrl());
attributes.put("icon",sysMenuDO.getIcon());
tree.setAttributes(attributes);
trees.add(tree);
}
//完善Tree里的字段(添加父子关系)
List<Tree<MenuDO>> list = BuildTree.buildList(trees, "0");
return list;
}

controller

LoginController extends BaseController

Code
1
2
3
4
5
6
@GetMapping("/index")
public String index(Model model){
List<Tree<MenuDO>> menus = menuService.listMenuTree(getUserId());//查询该用户可操作的菜单列表
model.addAttribute("menus",menus);
return "index_v1";
}

utils

fastjson

Code
1
2
3
4
5
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.31</version>
</dependency>

Tree

菜单树

BuildTree

buildList方法 构建菜单树

ShiroUtils

BaseController

ui

Code
1
2
3
4
5
6
7
8
9
10
11
12
<li th:each="menu : ${menus}">
<a href="#">
<i class="fa fa-home" th:class="${menu.attributes.icon}"></i>
<span class="nav-label" th:text="${menu.text}">一级菜单</span> <span class="fa arrow"></span>
</a>
<ul class="nav nav-second-level">
<li th:each="cmenu : ${menu.children}">
<a class="J_menuItem" href="index_v1.html"
th:text="${cmenu.text}" th:href="${cmenu.attributes.url}">二级菜单</a>
</li>
</ul>
</li>
sql
1
2
3
4
5
6
7
8
9
select distinct
m.menu_id , parent_id, name, url,
perms,`type`,icon,order_num,gmt_create, gmt_modified
from sys_menu m
left join sys_role_menu rm on m.menu_id = rm.menu_id
left join sys_user_role ur on rm.role_id =ur.role_id
where ur.user_id = #{id}
and m.type in(0,1)
order by m.order_num

3. shiro+缓存

给shiro配置缓存,用户操作时会多次查询认证信息,只需要首次查数据库,之后都从缓存取,减少数据库查询次数,提高效率

前置知识:

springboot+redis+jedis操作

pom

xml
1
2
3
4
5
6
7
8
9
10
<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>

配置:

yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
spring:
redis:
host: 10.211.55.6
port: 6379
password:
# 连接超时时间(毫秒)
timeout: 10000
jedis:
pool:
# 连接池中的最大空闲连接
max-idle: 8
# 连接池中的最小空闲连接
min-idle: 10
# 连接池最大连接数(使用负值表示没有限制)
max-active: 100
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1

操作类 RedisManager

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
public class RedisManager {
@Value("${spring.redis.host}")
private String host = "127.0.0.1";
@Value("${spring.redis.port}")
private int port = 6379;
// 缓存过期时间,0表示不过期
private int expire = 0;
@Value("${spring.redis.timeout}")
private int timeout = 0;
@Value("${spring.redis.password}")
private String password = "";
private static JedisPool jedisPool = null;

/**
* 初始化方法:创建jedisPool
*/
public void init() {
if (jedisPool == null) {
if (password != null && !"".equals(password)) {
jedisPool = new JedisPool(new JedisPoolConfig(), host, port, timeout, password);
} else if (timeout != 0) {
jedisPool = new JedisPool(new JedisPoolConfig(), host, port, timeout);
} else {
jedisPool = new JedisPool(new JedisPoolConfig(), host, port);
}

}
}

/**
* get value from redis
*
* @param key
* @return
*/
public byte[] get(byte[] key) {
byte[] value = null;
Jedis jedis = jedisPool.getResource();
try {
value = jedis.get(key);
} finally {
if (jedis != null) {
jedis.close();
}
}
return value;
}
/**
* set
*
* @param key
* @param value
* @return
*/
public byte[] set(byte[] key, byte[] value) {
Jedis jedis = jedisPool.getResource();
try {
jedis.set(key, value);
if (this.expire != 0) {
jedis.expire(key, this.expire);
}
} finally {
if (jedis != null) {
jedis.close();
}
}
return value;
}

/**
* set
*
* @param key
* @param value
* @param expire
* @return
*/
public byte[] set(byte[] key, byte[] value, int expire) {
Jedis jedis = jedisPool.getResource();
try {
jedis.set(key, value);
if (expire != 0) {
jedis.expire(key, expire);
}
} finally {
if (jedis != null) {
jedis.close();
}
}
return value;
}

/**
* del
*
* @param key
*/
public void del(byte[] key) {
Jedis jedis = jedisPool.getResource();
try {
jedis.del(key);
} finally {
if (jedis != null) {
jedis.close();
}
}
}

/**
* flush
*/
public void flushDB() {
Jedis jedis = jedisPool.getResource();
try {
jedis.flushDB();
} finally {
if (jedis != null) {
jedis.close();
}
}
}

/**
* size
*/
public Long dbSize() {
Long dbSize = 0L;
Jedis jedis = jedisPool.getResource();
try {
dbSize = jedis.dbSize();
} finally {
if (jedis != null) {
jedis.close();
}
}
return dbSize;
}

/**
* keys
*
* @param regex
* @return
*/
public Set<byte[]> keys(String pattern) {
Set<byte[]> keys = null;
Jedis jedis = jedisPool.getResource();
try {
keys = jedis.keys(pattern.getBytes());
} finally {
if (jedis != null) {
jedis.close();
}
}
return keys;
}
}

springboot+日志slf4j

Code
1
2
3
spring-boot-starter-logging依赖了slf4j-api和log4j-api

SLF4J的全称是Simple Logging Facade for Java,即简单日志门面。SLF4J并不是具体的日志框架,而是作为一个简单门面服务于各类日志框架,如java.util.logging, logback和log4j。

使用

java
1
2
3
4
5
6
7
8
9
10
11
12
13
private static final Logger logger = LoggerFactory.getLogger(CommonTest.class);

@Test
public void test02(){
//{}表示通配符
logger.info("Current Time: {}", System.currentTimeMillis());
logger.info("Current Time: " + System.currentTimeMillis());
logger.trace("trace log");
logger.warn("warn log");
logger.debug("debug log");
logger.info("info log");
logger.error("error log");
}

java序列化

概念:

Code
1
2
3
序列化 (Serialization)将对象的状态信息转换为可以存储或传输的形式的过程(一个对象可以被表示为一个字节序列)。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。

java对象实现Serializable接口表示该对象可以序列化

将对象序列化为字节序列

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
package machine.mybootdo.common.redis.shiro;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;

/**
* @description: 对象序列化
*/
public class SerializeUtils {

private static Logger logger = LoggerFactory.getLogger(SerializeUtils.class);
/**
* 序列化
* @param object
* @return
*/
public static byte[] serialize(Object object) {

byte[] result = null;

if (object == null) {
return new byte[0];
}
try {
ByteArrayOutputStream byteStream = new ByteArrayOutputStream(128);
try {
if (!(object instanceof Serializable)) {
throw new IllegalArgumentException(SerializeUtils.class.getSimpleName() + " requires a Serializable payload " +
"but received an object of type [" + object.getClass().getName() + "]");
}
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteStream);
objectOutputStream.writeObject(object);
objectOutputStream.flush();
result = byteStream.toByteArray();
}
catch (Throwable ex) {
throw new Exception("Failed to serialize", ex);
}
} catch (Exception ex) {
logger.error("Failed to serialize",ex);
}
return result;
}

/**
* 反序列化
* @param bytes
* @return
*/
public static Object deserialize(byte[] bytes) {

Object result = null;

if (isEmpty(bytes)) {
return null;
}

try {
ByteArrayInputStream byteStream = new ByteArrayInputStream(bytes);
try {
ObjectInputStream objectInputStream = new ObjectInputStream(byteStream);
try {
result = objectInputStream.readObject();
}
catch (ClassNotFoundException ex) {
throw new Exception("Failed to deserialize object type", ex);
}
}
catch (Throwable ex) {
throw new Exception("Failed to deserialize", ex);
}
} catch (Exception e) {
logger.error("Failed to deserialize",e);
}
return result;
}

public static boolean isEmpty(byte[] data) {
return (data == null || data.length == 0);
}
}

法1.内置ehcache缓存

pom

xml
1
2
3
4
5
<dependency>  
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-core</artifactId>
<version>2.6.11</version>
</dependency>

缓存配置 ehcache.xml

xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<diskStore path="java.io.tmpdir/Tmp_EhCache" />
<defaultCache eternal="false" maxElementsInMemory="1000"
overflowToDisk="false" diskPersistent="false" timeToIdleSeconds="0"
timeToLiveSeconds="600" memoryStoreEvictionPolicy="LRU" />
<cache name="role" eternal="false" maxElementsInMemory="10000"
overflowToDisk="false" diskPersistent="false" timeToIdleSeconds="0"
timeToLiveSeconds="0" memoryStoreEvictionPolicy="LFU" />
</ehcache>

<!--diskStore:
磁盘存储:将缓存中暂时不使用的对象,转移到硬盘,类似于Windows系统的虚拟内存
path:指定在硬盘上存储对象的路径
user.home:用户的家目录。
user.dir:用户的当前工作目录。
java.io.tmpdir:Java临时目录。
-->

<!--defaultCache:
默认的缓存配置信息,如果不加特殊说明,则所有对象按照此配置项处理
maxElementsInMemory:设置了缓存的上限,最多存储多少个记录对象
eternal:代表对象是否永不过期
timeToIdleSeconds:最大的发呆时间
timeToLiveSeconds:最大的存活时间
overflowToDisk:是否允许对象被写入到磁盘
-->

<!--cache配置:
以下属性是必须的:
name :cache的标识符,在一个CacheManager中必须唯一。
maxElementsInMemory : 在内存中缓存的element的最大数目。
maxElementsOnDisk:在磁盘上缓存的element的最大数目,默认值为0,表示不限制。
overflowToDisk : 设定当内存缓存溢出的时候是否将过期的element缓存到磁盘上。
eternal:设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断。
以下属性是可选的:
diskPersistent : 在VM重启的时候是否持久化磁盘缓存,默认是false。
timeToIdleSeconds: 对象空闲时间,指对象在多长时间没有被访问就会失效。只对eternal为false的有效。默认值0,表示一直可以访问
timeToLiveSeconds: 对象存活时间,指对象从创建到失效所需要的时间。只对eternal为false的有效。默认值0,表示一直可以访问。
memoryStoreEvictionPolicy : 当内存缓存达到最大,有新的element加入的时候,移除缓存中element的策略。默认是LRU,可选的有LFU和FIFO
-->

<!--置换算法:
LRU:LRU是Least Recently Used 的缩写 LRU缓存把最近最少使用的数据移除,让给最新读取的数据。而往往最常读取的,也是读取次数最多的,所以,利用LRU缓存,我们能够提高系统的performance(性能)
LFU:LFU是最近最不常用页面置换算法(Least Frequently Used),也就是淘汰一定时期内被访问次数最少的页!
FIFO:FIFO(First In First Out ,先进先出)算法是根据先进先出原理来淘汰数据的,实现上是最简单的一种,具体算法如下:
1. 新访问的数据插入FIFO队列尾部,数据在FIFO队列中顺序移动;
2. 淘汰FIFO队列头部的数据;
-->

shiro配置

java
1
2
3
4
5
6
7
//ehCacheManager
@Bean
public EhCacheManager ehCacheManager() {
EhCacheManager em = new EhCacheManager();
em.setCacheManagerConfigFile("classpath:config/ehcache.xml");
return em;
}

再:securityManager.setCacheManager(ehCacheManager());

法2.自定义redis缓存

RedisCache

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
package machine.mybootdo.common.redis.shiro;

import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.util.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;

/**
* @description: 自定义shiro的redis缓存,将数据存到redis中
*/
public class RedisCache<K,V> implements Cache<K,V> {

private Logger logger = LoggerFactory.getLogger(this.getClass());
/**
* The wrapped Jedis instance.
*/
private RedisManager cache;

/**
* The Redis key prefix for the sessions
*/
private String keyPrefix = "shiro_redis_session:";

public String getKeyPrefix() {
return keyPrefix;
}

public void setKeyPrefix(String keyPrefix) {
this.keyPrefix = keyPrefix;
}


/**
* 通过一个JedisManager实例构造RedisCache
*/
public RedisCache(RedisManager cache){
if (cache == null) {
throw new IllegalArgumentException("Cache argument cannot be null.");
}
this.cache = cache;
}

/**
* Constructs a cache instance with the specified
* Redis manager and using a custom key prefix.
* @param cache The cache manager instance
* @param prefix The Redis key prefix
*/
public RedisCache(RedisManager cache,
String prefix){

this( cache );

// set the prefix
this.keyPrefix = prefix;
}

/**
* 获得byte[]型的key
* @param key
* @return
*/
private byte[] getByteKey(K key){
if(key instanceof String){
String preKey = this.keyPrefix + key;
return preKey.getBytes();
}else{
return SerializeUtils.serialize(key);
}
}




@Override
public V get(K key) throws CacheException {
logger.debug("根据key从Redis中获取对象 key [" + key + "]");
try {
if (key == null) {
return null;
}else{
byte[] rawValue = cache.get(getByteKey(key));
@SuppressWarnings("unchecked")
V value = (V)SerializeUtils.deserialize(rawValue);
return value;
}
} catch (Throwable t) {
throw new CacheException(t);
}
}

@Override
public V put(K key, V value) throws CacheException {
logger.debug("根据key从存储 key [" + key + "]");
try {
cache.set(getByteKey(key), SerializeUtils.serialize(value));
return value;
} catch (Throwable t) {
throw new CacheException(t);
}
}

@Override
public V remove(K key) throws CacheException {
logger.debug("从redis中删除 key [" + key + "]");
try {
V previous = get(key);
cache.del(getByteKey(key));
return previous;
} catch (Throwable t) {
throw new CacheException(t);
}
}

@Override
public void clear() throws CacheException {
logger.debug("从redis中删除所有元素");
try {
cache.flushDB();
} catch (Throwable t) {
throw new CacheException(t);
}
}

@Override
public int size() {
try {
Long longSize = new Long(cache.dbSize());
return longSize.intValue();
} catch (Throwable t) {
throw new CacheException(t);
}
}

//获取匹配前缀的所有key
@Override
public Set<K> keys() {
try {
Set<byte[]> keys = cache.keys(this.keyPrefix + "*");
if (CollectionUtils.isEmpty(keys)) {
return Collections.emptySet();
}else{
Set<K> newKeys = new HashSet<K>();
for(byte[] key:keys){
newKeys.add((K)key);
}
return newKeys;
}
} catch (Throwable t) {
throw new CacheException(t);
}
}

//获取keys对应的value集合
@Override
public Collection<V> values() {
try {
Set<byte[]> keys = cache.keys(this.keyPrefix + "*");
if (!CollectionUtils.isEmpty(keys)) {
List<V> values = new ArrayList<V>(keys.size());
for (byte[] key : keys) {
@SuppressWarnings("unchecked")
V value = get((K)key);
if (value != null) {
values.add(value);
}
}
return Collections.unmodifiableList(values);
} else {
return Collections.emptyList();
}
} catch (Throwable t) {
throw new CacheException(t);
}
}
}

RedisCacheManager

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
package machine.mybootdo.common.redis.shiro;

import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.cache.CacheManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
* @description: 自定义shiro的redis缓存管理器
*/
public class RedisCacheManager implements CacheManager {
private static final Logger logger = LoggerFactory
.getLogger(RedisCacheManager.class);


// fast lookup by name map
private final ConcurrentMap<String, Cache> caches = new ConcurrentHashMap<String, Cache>();

private RedisManager redisManager;

/**
* The Redis key prefix for caches
*/
private String keyPrefix = "shiro_redis_cache:";

public String getKeyPrefix() {
return keyPrefix;
}

public void setKeyPrefix(String keyPrefix) {
this.keyPrefix = keyPrefix;
}


//给自定义缓存管理器注入自定义redis缓存
@Override
public <K, V> Cache<K, V> getCache(String name) throws CacheException {
logger.debug("获取名称为: " + name + " 的RedisCache实例");

Cache c = caches.get(name);
if (c == null) {
// initialize the Redis manager instance
redisManager.init();

// create a new cache instance
c = new RedisCache<K, V>(redisManager, keyPrefix);

// add it to the cache collection
caches.put(name, c);
}

return c;
}

public RedisManager getRedisManager() {
return redisManager;
}

public void setRedisManager(RedisManager redisManager) {
this.redisManager = redisManager;
}
}

shiro配置

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.password}")
private String password;
@Value("${spring.redis.port}")
private int port;
@Value("${spring.redis.timeout}")
private int timeout;
@Value("${cacheType}")
private String cacheType;

//自定义cacheManager,redis实现
public RedisCacheManager redisCacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
return redisCacheManager;
}
//封装了jedis操作
public RedisManager redisManager() {
RedisManager redisManager = new RedisManager();
redisManager.setHost(host);
redisManager.setPort(port);
redisManager.setExpire(1800);// 配置缓存过期时间
redisManager.setTimeout(timeout);
redisManager.setPassword(password);
return redisManager;
}

//securityManager
@Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//设置realm.
securityManager.setRealm(userRealm());
// 自定义缓存实现 使用redis
if(Constant.CACHE_TYPE_REDIS.equals(cacheType)){
securityManager.setCacheManager(redisCacheManager());
}else {
securityManager.setCacheManager(ehCacheManager());
}
//SessionManager
return securityManager;
}

Constant

java
1
2
3
4
public class Constant {
//缓存方式
public static String CACHE_TYPE_REDIS ="redis";
}

yml

yaml
1
2
#配置缓存和session存储方式,默认ehcache,可选redis
cacheType: ehcache

4. shiro+授权

Code
1
前提就是在Realm的授权方法中查询出权限并返回List<String>形式

userRealm

java
1
2
3
4
5
6
7
8
9
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) {
Long userId = ShiroUtils.getUserId();
MenuService menuService = ApplicationContextRegister.getBean(MenuService.class);
Set<String> perms = menuService.listPerms(userId);
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.setStringPermissions(perms);
return info;
}

基于角色的访问权限控制

根据用户id列出该用户拥有的权限(菜单)列表

MenuMapper.xml

xml
1
2
3
4
5
6
7
<select id="listUserPerms" resultType="string">
select distinct perms
from sys_menu m
left join sys_role_menu rm on m.menu_id=rm.menu_id
left join sys_user_role ur on rm.role_id=ur.role_id
where ur.user_id=#{id}
</select>

MenuDao.java

Code
1
List<String> listUserPerms(Long id);

MenuServiceImpl

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Override
public Set<String> listPerms(Long userId) {
List<String> perms = menuMapper.listUserPerms(userId);
Set<String> permsSet = new HashSet<>();
for (String perm : perms) {
if (StringUtils.isNotBlank(perm)) {
permsSet.addAll(Arrays.asList(perm.trim().split(",")));
}
}
return permsSet;
}
/*
boolean addAll(Collection<? extends E> c)
将集合c的所有元素全部加入到set集合里

public static <T> List<T> asList(T... a)
可将数组a转化为list类型

perm.trim().split(",")
menu表的perms字段 如果要写多种权限,则用" , "分隔

为什么返回set?
realm需要Set<String>
*/

StringUtils

Code
1
2
3
4
5
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.7</version>
</dependency>

方法1:使用xml配置方法(不采用)

applicationContext-shiro.xml配置

方法2:使用注解方法

开启aop支持(否则shiro注解无法生效)

Code
1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

shiro注解支持

java
1
2
3
4
5
6
7
8
9
/**
* 开启shiro aop注解支持.
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}

使用shiro注解

java
1
2
3
4
5
@RequiresPermissions("sys:user:user")
@GetMapping("")
String user() {
return prefix + "/user";
}

执行流程

Code
1
当调用controller的一个方法,由于该 方法加了@RequiresPermissions("item:query") ,shiro调用realm获取数据库中的权限信息,看"item:query"是否在权限数据中存在,如果不存在就拒绝访问,如果存在就授权通过。

测试

Code
1
2
3
ok-授权缓存
ok-访问非法权限会发生什么
会抛出异常AuthorizationException

建立一个异常处理器处理非法授权异常403

common.exception.BDExceptionHandler

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@RestControllerAdvice
public class BDExceptionHandler {
private Logger logger = LoggerFactory.getLogger(getClass());

/*
@ControllerAdvic:控制器增强,即把@ControllerAdvice注解内部使用@ExceptionHandler、@InitBinder、@ModelAttribute注解的方法应用到所有的 @RequestMapping注解的方法。
*/


/*
应用到所有@RequestMapping注解的方法,在其抛出AuthorizationException异常时执行
*/
@ExceptionHandler(AuthorizationException.class)
public Object handleAuthorizationException(AuthorizationException e, HttpServletRequest request) {
logger.error(e.getMessage(), e);
if (HttpServletUtils.jsAjax(request)) {
return R.error(403, "未授权");
}
return new ModelAndView("error/403");
}

}

common.utils.HttpServletUtils

java
1
2
3
4
5
6
7
8
9
10
public class HttpServletUtils {
public static boolean jsAjax(HttpServletRequest req){
//判断是否为ajax请求,默认不是
boolean isAjaxRequest = false;
if(!StringUtils.isBlank(req.getHeader("x-requested-with")) && req.getHeader("x-requested-with").equals("XMLHttpRequest")){
isAjaxRequest = true;
}
return isAjaxRequest;
}
}
文章作者: Machine
文章链接: https://machine4869.gitee.io/2018/07/20/15328488619832/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 哑舍
打赏
  • 微信
    微信
  • 支付宝
    支付宝

评论