之前和几个朋友在开发网站时用到了redis数据库,这里分享干货给大家,废话不多说直接上代码
我们用到redis的地方是在验证登录时的一个小demo,思路是每个用户登录到网站时给这个用互提供一个唯一的校验码ticket,之后存到redis中在后续访问网站相关内容时要取出ticket进行验证方可继续访问
生成ticket
- entity层
- Ticket.java
private Integer userId;//用户id
private String userName;//用户名
private String token;//ticket编号
private long createTime;//创建时间
private long expire;//有效期
public String toString() {
// token+createTime+expire+userId+userName
String str = token + "-" + createTime + "-" + expire + "-" + userId;
//利用Base64算法处理
String base64Str = Base64Utils.encodeToString(str.getBytes());
return base64Str;
}
...get,set方法自己生成吧
这里用到了Base64算法封装ticket,便于验证时的比较
service层
TicketManager.java//管理ticket的TickManager类,有创建ticket的方法和检验ticket的方法
@Component public class TicketManager { @Autowired private RedisTemplate<Object, Object> redis; public Ticket create(User user, int hour) { Ticket ticket = new Ticket(); ticket.setUserId(user.getId()); ticket.setUserName(user.getName()); ticket.setCreateTime(System.currentTimeMillis()); ticket.setExpire(hour * 3600 * 1000);// 有效时长 ticket.setToken(UUID.randomUUID().toString()); // 将ticket存入redis,方便将来验证 // redis.opsForValue().set("ticket_"+user.getId(), t); redis.opsForHash().put("tickers", user.getId(), ticket); // 测试取userId Ticket t = (Ticket) redis.opsForHash().get("tickers", user.getId()); System.out.println("从redis中读取=>:"+t); return ticket; } public int checkTicket(int userId, String ticket) { Ticket t = (Ticket) redis.opsForHash().get("tickers", userId); System.out.println("|--------------------------------------------------------------------------------------------------|"); System.out.println("|从redis中读取=>:"+t.toString()+"|"); System.out.println("|--------------------------------------------------------------------------------------------------|"); System.out.println("|从request带来=>:"+ticket.toString()+"|"); System.out.println("|--------------------------------------------------------------------------------------------------|"); System.out.println("|"+t.toString().equals(ticket)+"|"); System.out.println("|--------------------------------------------------------------------------------------------------|"); // 检测ticket是否匹配 if (t != null && t.toString().equals(ticket)) { // 匹配成功 long currentTime = System.currentTimeMillis(); long totalTime = t.getCreateTime() + t.getExpire(); // 检测是否失效 if (currentTime < totalTime) { // 在有效期内 0 return yixueConstant.SUCCESS; } else { // 过期 1 return yixueConstant.ERROR1; } } // 不匹配 -1 return yixueConstant.ERROR; } }
这里用到了Java访问Redis的方法因为网站是用SpringBoot开发的,boot也提供了访问redis的包
spring-boot-starter-data-redis
这里我提供boot访问redis的方法:
需要添加SpringData-redis包,它提供一个RedisTemplate对象使用。
引入spring-boot-starter-data-redis工具包(Maven项目中的pom.xml中添加)
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.1.RELEASE</version> </parent> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> </dependencies>
在application.properties配置redis连接参数(resources文件夹下的application.properties配置文件)
spring.redis.host=localhost spring.redis.port=6379
注入使用RedisTemplate对象(这是测试类,测试能否存储到redis中和能否取出)
@RunWith(SpringRunner.class) @SpringBootTest(classes={MyBootApplication.class}) public class TestRedisTemplate { @Autowired private RedisTemplate<Object, Object> redis; @Test public void test1(){ redis.opsForValue().set("msg1", "redis"); String value = (String)redis.opsForValue().get("msg1"); System.out.println(value); } }
接着我们在service接口添加方法,这个方法是检验ticket是否合法和是否超时
/** * 检查凭证是否合法或超时功能的实现 * @param userId 用户唯一ID * @param ticket 用户登录后唯一凭证 * @return */ public ReturnResult checkTicket(int userId,String ticket);
在实现类serviceImp中注入ticketManager对象
@Autowired private TicketManager ticketManager;
在实现类serviceImp中有用户登录的方法和检验ticket的方法,我们需要在用户登录方法中调用create方法来实现用户每次登录时都创建一个唯一的ticket作为凭证,用户如果要想访问网站其他内容必须要经过ticket的校验,这一点我们后面用拦截器来实现
/** * 登录功能实现 */ @Override public ReturnResult checkUser(String name, String password) { ReturnResult result = new ReturnResult(); // 检验参数是否合法 if (StringUtils.isEmpty(name) || StringUtils.isEmpty(password)) { // 用yixueConstant常量类替换 // (yixueConstant.ERROR -> 参数错误 // yixueConstant.PARAM_ERROR->"参数不合法") result.setStatus(yixueConstant.ERROR); result.setMsg(yixueConstant.PARAM_ERROR); return result; } // 检查账户是否存在 User user = userDao.selectByName(name); if (user == null) { // 不存在 result.setStatus(yixueConstant.ERROR1); result.setMsg(yixueConstant.LOGIN_NAME_ERROR); return result; } // 检验密码是否正确 String salt = user.getSalt(); String md5Password = PasswordUtil.md5(password + salt); if (!user.getPassword().equals(md5Password)) { result.setStatus(yixueConstant.ERROR2); result.setMsg(yixueConstant.LOGIN_PASSWORD_ERROR); return result; } // 账户和密码都正确 result.setStatus(yixueConstant.SUCCESS); result.setMsg(yixueConstant.LOGIN_SUCCESS); //---关键代码---- // 若登录成功即(账户和密码都正确),则创建一个ticket返回,有效期2小时 Ticket ticket = ticketManager.create(user,2); Map<String, Object> map = new HashMap<>(); map.put("userId", ticket.getUserId()); //这里我们把ticket的toString方法的返回值放进map中了,也就是base64算法封装后的ticket map.put("ticket", ticket.toString()); //---关键代码---- result.setData(map); //返回的result是返回给浏览器,浏览器把ticket保存下来,继续后续的业务校验工作 return result; } /** * 检查凭证是否合法或超时功能 * */ @Override public ReturnResult checkTicket(int userId, String ticket) { ReturnResult result = new ReturnResult(); int num = ticketManager.checkTicket(userId, ticket); if (num == yixueConstant.SUCCESS){ // 凭证在有效期内 result.setStatus(num); result.setMsg(yixueConstant.TICKETS_SUCCESS); return result; }else if(num == yixueConstant.ERROR1){ //凭证过时 result.setStatus(num); result.setMsg(yixueConstant.TICKETS_ERROR_OVERTIME); return result; } //凭证不匹配 result.setStatus(num); result.setMsg(yixueConstant.TICKETS_ERROR); return result; }
returnResult 对象是 ReturnResult类的对象,是为了给浏览器返回一个状态码,状态信息,和数据内容,我们用ajax来取出传来的data信息进行前端的展示
private int status; private String msg; private Object data;
- Controller层中注入service并添加方法,这个方法也就是我们后面如果有请求被拦截,都会调用这个方法来进行校验的
@Autowired
private UserService userService;
@PostMapping("/user/ticket")
public ReturnResult ticket(
@RequestParam(name = "userId", required = false) int userId,
@RequestParam(name = "ticket", required = false) String ticket) {
ReturnResult result = userService.checkTicket(userId,ticket);
return result;
}
- 之后我们如果想拦截用户访问其他模块的请求,就在相应的模块中添加拦截器,这里以访问视频模块为例,在视频模块中添加intercepter拦截器
public class CheckInterceptor implements HandlerInterceptor {
//发请求 restTemplate
//专门调rest服务的对象
@Autowired
private RestTemplate restTemplate;
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
//截取请求中的userId,ticket
String userId = request.getParameter("userId");
String ticket = request.getParameter("ticket");
// 将userId,ticket发给/user/ticket检测
//返回结果是ReturnResult对象
if(userId!=null&&ticket!=null){
String url = "http://localhost:7001/user/ticket";
MultiValueMap<String, Object> params = new LinkedMultiValueMap<>();
params.set("userId", userId);
params.set("ticket", ticket);
ReturnResult result = restTemplate.postForObject(url, params, ReturnResult.class);
if(result.getStatus()==yixueConstant.SUCCESS){
//校验正确,放行
return true;
}
}
//校验失败,拦截请求,给用户返回一个json结果
response.setContentType("text/html;Charset=UTF-8");
PrintWriter out = response.getWriter();
out.print("{\"status\":-2,\"msg\":\"令牌凭证无效\"}");
out.flush();
return false;
}
}
- 有了拦截器,我们还要把拦截器加入配置,使其生效
@Component
public class CheckInterceptorConfiguration implements WebMvcConfigurer {
@Autowired
private CheckInterceptor checkInterceptor;
public void addInterceptors(InterceptorRegistry registry) {
//添加要拦截的请求地址url,在我们访问视频模块时,需要先拦截请求,在拦截器中验证,如果返回结果为true则放行
String[] urls = {"/course/chapter"};
registry.addInterceptor(checkInterceptor).addPathPatterns(urls);
}
}
到这里大致就结束了,其实用到redis的地方就是springBoot访问redis的包,和访问redis的几行代码,这里稍微总结一下:
@Autowired
private RedisTemplate<Object, Object> redis;
注入并使用RedisTemplate对象
- 存到redis中
`redis.opsForHash().put("tickers", user.getId(), ticket);`
- 取出
`redis.opsForHash().get("tickers", user.getId());`
也就是我们在用户登录是要create一个ticket存入redis中,然后在checkTicket时我们从redis取出ticket做校验,如果相同再去校验是否超时达到单点登录的功能
这里附一个redis的教程供大家参考