最近在做springboot的一个项目,用到缓存做登录验证(这块也可用Redis),学到了一些新知识,想和大家分享一下。
一、创建springboot项目
个人认为创建spring boot项目最快的地方就是访问spring.io官网,从官网创建,里面的一些基础配置都给我们配置好了,直接添加即可。
- 首先我们通过QUICKSTART进入
- 点击start.spring.io,进入springboot的搭建界面,进行配置
- 选择成功以后直接下载,下载直接解压,导入maven项目(在这里我用的IDEA工具)
- pom.xml中导入依赖内容如下:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Druid数据源 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<!--mybatis-plus完成项目构建所需模板,真实项目不需要使用-->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
</dependency>
<!--commons -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.6</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.70</version>
</dependency>
<!-- activeMq end -->
<!--热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-messaging</artifactId>
</dependency>
<!--js客户端的静态资源-->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>webjars-locator-core</artifactId>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>sockjs-client</artifactId>
<version>1.0.2</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>stomp-websocket</artifactId>
<version>2.3.3</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>bootstrap</artifactId>
<version>3.3.7</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.3.3.RELEASE</version>
</dependency>
</dependencies>
二、创建数据库表,导入数据库配置,反向生成代码
- 我设计了一个t_user表,包括id(主键自增) , use_name(登录名) , password(密码)
- 将数据库连接信息以及基础配置加进项目的配置文件中
spring.datasource.druid.url=jdbc:mysql://localhost:3306/byte_easy?useUnicode=true&characterEncoding=utf-8&serverTimezone=CTT
spring.datasource.druid.username=root
spring.datasource.druid.password=root
spring.datasource.druid.driver-class-name=com.mysql.cj.jdbc.Driver
# \u521D\u59CB\u5316\u65F6\u5EFA\u7ACB\u7269\u7406\u8FDE\u63A5\u7684\u4E2A\u6570
spring.datasource.druid.initial-size=5
# \u6700\u5927\u8FDE\u63A5\u6C60\u6570\u91CF
spring.datasource.druid.max-active=30
# \u6700\u5C0F\u8FDE\u63A5\u6C60\u6570\u91CF
spring.datasource.druid.min-idle=5
# \u83B7\u53D6\u8FDE\u63A5\u65F6\u6700\u5927\u7B49\u5F85\u65F6\u95F4\uFF0C\u5355\u4F4D\u6BEB\u79D2
spring.datasource.druid.max-wait=60000
# \u914D\u7F6E\u95F4\u9694\u591A\u4E45\u624D\u8FDB\u884C\u4E00\u6B21\u68C0\u6D4B\uFF0C\u68C0\u6D4B\u9700\u8981\u5173\u95ED\u7684\u7A7A\u95F2\u8FDE\u63A5\uFF0C\u5355\u4F4D\u662F\u6BEB\u79D2
spring.datasource.druid.time-between-eviction-runs-millis=60000
# \u8FDE\u63A5\u4FDD\u6301\u7A7A\u95F2\u800C\u4E0D\u88AB\u9A71\u9010\u7684\u6700\u5C0F\u65F6\u95F4
spring.datasource.druid.min-evictable-idle-time-millis=300000
# \u7528\u6765\u68C0\u6D4B\u8FDE\u63A5\u662F\u5426\u6709\u6548\u7684sql\uFF0C\u8981\u6C42\u662F\u4E00\u4E2A\u67E5\u8BE2\u8BED\u53E5
spring.datasource.druid.validation-query=SELECT 1 FROM DUAL
# \u5EFA\u8BAE\u914D\u7F6E\u4E3Atrue\uFF0C\u4E0D\u5F71\u54CD\u6027\u80FD\uFF0C\u5E76\u4E14\u4FDD\u8BC1\u5B89\u5168\u6027\u3002\u7533\u8BF7\u8FDE\u63A5\u7684\u65F6\u5019\u68C0\u6D4B\uFF0C\u5982\u679C\u7A7A\u95F2\u65F6\u95F4\u5927\u4E8EtimeBetweenEvictionRunsMillis\uFF0C\u6267\u884CvalidationQuery\u68C0\u6D4B\u8FDE\u63A5\u662F\u5426\u6709\u6548\u3002
spring.datasource.druid.test-while-idle=true
# \u7533\u8BF7\u8FDE\u63A5\u65F6\u6267\u884CvalidationQuery\u68C0\u6D4B\u8FDE\u63A5\u662F\u5426\u6709\u6548\uFF0C\u505A\u4E86\u8FD9\u4E2A\u914D\u7F6E\u4F1A\u964D\u4F4E\u6027\u80FD\u3002
spring.datasource.druid.test-on-borrow=false
# \u5F52\u8FD8\u8FDE\u63A5\u65F6\u6267\u884CvalidationQuery\u68C0\u6D4B\u8FDE\u63A5\u662F\u5426\u6709\u6548\uFF0C\u505A\u4E86\u8FD9\u4E2A\u914D\u7F6E\u4F1A\u964D\u4F4E\u6027\u80FD\u3002
spring.datasource.druid.test-on-return=false
# \u662F\u5426\u7F13\u5B58preparedStatement\uFF0C\u4E5F\u5C31\u662FPSCache\u3002PSCache\u5BF9\u652F\u6301\u6E38\u6807\u7684\u6570\u636E\u5E93\u6027\u80FD\u63D0\u5347\u5DE8\u5927\uFF0C\u6BD4\u5982\u8BF4oracle\u3002\u5728mysql\u4E0B\u5EFA\u8BAE\u5173\u95ED\u3002
spring.datasource.druid.pool-prepared-statements=true
# \u8981\u542F\u7528PSCache\uFF0C\u5FC5\u987B\u914D\u7F6E\u5927\u4E8E0\uFF0C\u5F53\u5927\u4E8E0\u65F6\uFF0CpoolPreparedStatements\u81EA\u52A8\u89E6\u53D1\u4FEE\u6539\u4E3Atrue\u3002
spring.datasource.druid.max-pool-prepared-statement-per-connection-size=50
# \u914D\u7F6E\u76D1\u63A7\u7EDF\u8BA1\u62E6\u622A\u7684filters\uFF0C\u53BB\u6389\u540E\u76D1\u63A7\u754C\u9762sql\u65E0\u6CD5\u7EDF\u8BA1
spring.datasource.druid.filters=stat,wall
# \u901A\u8FC7connectProperties\u5C5E\u6027\u6765\u6253\u5F00mergeSql\u529F\u80FD\uFF1B\u6162SQL\u8BB0\u5F55
spring.datasource.druid.connection-properties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
# \u5408\u5E76\u591A\u4E2ADruidDataSource\u7684\u76D1\u63A7\u6570\u636E
spring.datasource.druid.use-global-data-source-stat=true
# druid\u8FDE\u63A5\u6C60\u76D1\u63A7
spring.datasource.druid.stat-view-servlet.login-username=admin
spring.datasource.druid.stat-view-servlet.login-password=123456
# \u6392\u9664\u4E00\u4E9B\u9759\u6001\u8D44\u6E90\uFF0C\u4EE5\u63D0\u9AD8\u6548\u7387
spring.datasource.druid.web-stat-filter.exclusions=*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*
- 我在项目继承了mybatis-plus,利用mybatis plus 的代码生成器来生成代码 。Mybatis-Plus官网地址.
(直接运行main方法)
三、配置缓存
- 在resourses目录下创建一个cache的文件夹,导入配置文件
cache.xml内容如下:
<?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">
<!-- 指定一个文件目录,当EHCache把数据写到硬盘上时,将把数据写到这个文件目录下 -->
<diskStore path = "java.io.tmpdir"/>
<!-- 默认的管理策略 -->
<defaultCache
name = "serviceCacheToken"
eternal = "false"
maxElementsInMemory = "10000"
overflowToDisk = "true"
diskPersistent = "false"
timeToIdleSeconds = "7200"
timeToLiveSeconds = "7200"
diskExpiryThreadIntervalSeconds = "120"
memoryStoreEvictionPolicy = "LRU"/>
</ehcache>
- 在config包下导入CacheConfig.java(key生成策略),内容如下
package com.mbyte.easy.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @ClassName: CacheConfig
* @Description: 缓存配置
* @Author: zte
* @Date: 2019-02-14 18:12
* @Version 1.0
**/
@Configuration
public class CacheConfig {
private final static Logger logger = LoggerFactory.getLogger(CacheConfig.class);
/**
* @Title: classKey
* @Description: 以【类名】为缓存的key值 一般用于【selectALL】查询
* @Author: zte
* @Date: 2019-03-01 15:42
* @return: org.springframework.cache.interceptor.KeyGenerator
* @throws:
*/
@Bean
public KeyGenerator classKey() {
return (target, method, params) -> {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getName());
logger.info("缓存classKey-key:"+sb.toString());
return sb.toString();
};
}
/**
* @Title: classMethodParamsKey
* @Description: 以【类名+方法名+参数】为缓存的key值
* @Author: zte
* @Date: 2019-03-01 15:43
* @return: org.springframework.cache.interceptor.KeyGenerator
* @throws:
*/
@Bean
public KeyGenerator classMethodParamsKey() {
return (target, method, params) -> {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getName());
sb.append(method.getName());
for (Object obj : params) {
sb.append(obj == null ? "" : obj.toString());
}
logger.info("缓存classMethodParamsKey-key:"+sb.toString());
return sb.toString();
};
}
}
- 主方法添加 开启缓存注解(必须)
四、实现登录接口
代码如下
package com.mbyte.easy.rest.login;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.mbyte.easy.common.controller.BaseController;
import com.mbyte.easy.user.entity.User;
import com.mbyte.easy.user.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.security.crypto.bcrypt.BCrypt;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* @author zte
* @title: LoginController
* @projectName easy
* @description: 用户登录接口
* @date 2020/7/27 10:22 下午
*/
@RestController
@RequestMapping("/rest/login")
public class LoginController extends BaseController {
@Autowired
private IUserService userService;
@Resource
private CacheManager cacheManager;
/**
* 登录验证
* @param username 用户名
* @param password 密码
* @return
*/
@GetMapping
public Map<String, Object> index(@RequestParam(value = "username") String username, @RequestParam(value = "password") String password){
Map<String , Object> map = new HashMap<>();
User user = userService.getOne(new LambdaQueryWrapper<User>().eq(User::getUserName , username));
if(user != null && user.getPassword().equals(password)){
Cache cache = cacheManager.getCache("serviceCacheToken");//serviceCacheToken,需要与配置文件xml里面的name保持一致
String uuid = UUID.randomUUID().toString().replaceAll("-", "");
//放入缓存
cache.put(uuid, user);
map.put("uuid",uuid);
map.put("msg","success");
return map;
}
map.put("msg","用户名或密码不正确");
return map;
}
/**
* 根据token加载用户信息
* @param token 令牌
* @return
*/
@GetMapping("/loadUser")
public Map<String, Object> loadUser(@RequestParam(value = "token") String token){
Cache cache = cacheManager.getCache("serviceCacheToken");
Map<String , Object> map = new HashMap<>();
User user = cache.get(token, User.class);
if(user != null){
map.put("user",user);
map.put("msg","success");
return map;
}
map.put("msg","令牌已失效!");
return map;
}
}
- 登录接口(index),传入两个参数(用户名和密码),然后根据用户名到数据库去匹配,如果不存在,直接返回错误信息,如果存在继续匹配密码,如果密码也正确,创建一个缓存:
Cache cache = cacheManager.getCache("serviceCacheToken");
//serviceCacheToken,需要与配置文件xml里面的name保持一致
生成一个UUID,作为key值,user对象作为value,存入缓存(类似于Map):
String uuid = UUID.randomUUID().toString().replaceAll("-", "");
//放入缓存
cache.put(uuid, user);
然后将这个uuid返回回去,作为用户的身份标识,就是我们说的令牌。
- 根据身份标识加载用户信息接口:传入用户身份标识,然后我们根据身份标识去匹配,匹配成功,将缓存的value,也就是我们存的user对象返回回去,如果匹配失败,则返回错误信息。
User user = cache.get(token, User.class);
五、postman测试接口
-
首先我们现在数据库加载一条数据
-
验证失败,提示信息
-
验证成功,返回用户身份标识,登录接口测试完毕。
-
然后我们根据返回的uuid测试加载信息接口
我们可以看到,根据身份标识返回我们的用户信息 -
到这里,缓存应用就讲解完毕了。有问题欢迎评论区留言。