【狼人杀plus全记录】SpringBoot结合Redis实现微信小程序登录态维护
2018-02-08 15:19
721 查看
前文:微信小程序API文档中,有一个关于wx.login()的登录态维护的时序图,这篇文章就是记录如何实现的。
其次,关于为什么要这样做的原因。
以往我们在网站应用的开发中,我们会在用户登录后,将用户名或者是唯一标识放在服务器端的session对象中,当用户在session会话允许时间内再次访问的时候,我们的servlet会从http请求报文中拿到session id,然后替我们找到该用户的session对象以及session对象中我们存入的用户名或者是唯一标识。然后我们就可以通过这个信息获取到这个用户的相关信息,从而完成业务逻辑。
但是微信小程序并没有H5中的session会话机制,因此我们不能像平时的网站应用那样在服务器端的session对象中存储用户名或者是唯一标识来区别谁是谁。所以我们需要自己去实现一个session。
选择Redis的原因主要有两点:
第一个是高效;
第二个是Redis数据库可以设置数据的有效期,超过时间则会自动删除。
注:SpringBoot在2017年8月的时候就抛弃了spring-boot-starter-redis转而使用spring-boot-starter-data-redis 并且相关资料全部在SpringData下而不是SpringBoot下,如果需要查看官方文档的朋友可以注意一下。
这样弄好就可以了,SpringBoot会为我们自动帮我们把参数注入到 StringRedisTemplate 对象中,我们只需要使用 @Autowired 注解取出被Spring注入的对象即可。
就这样,我们完成了对Redis数据库的操作
先看代码:
实现逻辑非常简单:
第一,实现Filter接口;
第二,重载三个方法;
第三,我们在doFilter()方法中添加我们自己的逻辑代码;
第四,添加 @Component 注解,这样SpringBoot在启动的时候就会把这个类的对象注入到容器中了;
第五,添加 @WebFilter(urlPatterns = “/*”) 注解,urlPatterns参数标识需要处理的url,这里是匹配所有,建议大家根据实际情况而定,比如/wechat/* 等。
不同点就在于以往我们是在session对象中取用户名或者是唯一标识,现在我们只能在request对象中去拿信息。
由于微信小程序与后台的交互主要是使用json格式,所以我的方案是将mySessionKey放在json数据中传输(当然也可以通过修改http的header完成,但是我并不知道应该如何在微信端操作,就不讲这个了),json格式如下:
但是这里就会发生一个问题,由于json数据不同于以往我们的get或者post表单请求,如果你抓包的话,你会看到在http报文的response header中,有一个属性变了:
所以我们没有办法通过request.getParameter() 或者是 request.getAttribute() 方法拿到json参数。
我们只能通过流来拿到json数据,方法如下:
这里就不分析代码块了,具体思路就是通过这个工具类,将在过滤器中的request对象传入,然后从request中拿到我们想要的json数据。
至此,实际上我们已经完成了在过滤器拿到我们微信小程序发回来的json数据,But 你如果实操就会发现,这样子做了以后,我们的Controller拿不到数据了,永远都是null。具体的原因也非常的简单,就是流getInputStream()只能被读取一次,所以当你在过滤器中拿到了数据以后,你在Controller中也就拿不到了。
所以我们需要重写getInputStream()方法:
至此我们顺利完成了整个登录态的维护demo,当然这里面还涉及到一个过滤器获取到mySessionKey以后如何从Redis数据库中去获取openID与sessionKey的问题,但这个并不是难点,也就不再赘叙了。
最后重新贴一下实现了获取json数据的过滤器代码:
平时很少写这么长的文章,如果有看不懂的地方欢迎baofeidyz@foxmail.com
参考文章:解决在Filter中读取Request中的流后,后续controller或restful接口中无法获取流的问题
准备工作
首先,再开始这篇文章内容之前,我们已经拿到了openid与session_key这两个参数,但是这两个参数的命名不符合java的驼峰命名法,所以后面我会将openid称为openID,session_key称为sessionKey。其次,关于为什么要这样做的原因。
以往我们在网站应用的开发中,我们会在用户登录后,将用户名或者是唯一标识放在服务器端的session对象中,当用户在session会话允许时间内再次访问的时候,我们的servlet会从http请求报文中拿到session id,然后替我们找到该用户的session对象以及session对象中我们存入的用户名或者是唯一标识。然后我们就可以通过这个信息获取到这个用户的相关信息,从而完成业务逻辑。
但是微信小程序并没有H5中的session会话机制,因此我们不能像平时的网站应用那样在服务器端的session对象中存储用户名或者是唯一标识来区别谁是谁。所以我们需要自己去实现一个session。
具体逻辑
我们生成自己生成一个key,我称之为mySessionKey,然后用key-value键值对的形式,将mySessionKey作为key,openID与sessionKey作为value。然后我们在每一次微信小程序用户发送请求的时候带上mySessionKey,服务器端则通过mySessionKey来读取存储在服务器端的value,也就是openID与sessionKey。选择Redis的原因主要有两点:
第一个是高效;
第二个是Redis数据库可以设置数据的有效期,超过时间则会自动删除。
具体实现
第一步,导入spring boot 对 redis 的支持
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <version>1.5.10.RELEASE</version> </dependency>
注:SpringBoot在2017年8月的时候就抛弃了spring-boot-starter-redis转而使用spring-boot-starter-data-redis 并且相关资料全部在SpringData下而不是SpringBoot下,如果需要查看官方文档的朋友可以注意一下。
第二步,添加Redis数据库配置
SpringBoot支持 application.yml 和 application.properties 两种文件类型,这里我使用的是 application.yml 的文件类型spring: redis: host: localhost #改成自己的IP地址,如果是本地就localhost port: 6379 #默认Redis数据库端口 password: password #Redis数据库的密码,需要在Redis配置文件中配置好,如果没有密码就删掉 database: 0 pool: max-active: 8 max-wait: -1 max-idle: 500 min-idle: 0 timeout: 0
这样弄好就可以了,SpringBoot会为我们自动帮我们把参数注入到 StringRedisTemplate 对象中,我们只需要使用 @Autowired 注解取出被Spring注入的对象即可。
第三步,完成一个对Redis操作的DAO类
先看代码:package com.baofeidyz.langrenshaplusbackground2.dao.impl; import com.baofeidyz.langrenshaplusbackground2.dao.RedisDao; import com.baofeidyz.langrenshaplusbackground2.pojo.entity.UserDO; import com.google.gson.Gson; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.ValueOperations; import org.springframework.stereotype.Repository; import java.util.concurrent.TimeUnit; /** * @author: 暴沸 * @email: baofeidyz@foxmail.com * @data: 2018/2/6 10:09 * @desc: Redis数据库操作实例化对象 */ @Repository public class RedisDaoImpl implements RedisDao{ // 通过Autowired注解拿到SpringBoot帮我们注入好参数的StringRedisTemplate 对象 @Autowired private StringRedisTemplate redisTemplate; // 传入参数解析: // key -> 自己生成的mySessionKey // openID -> 从微信服务器端获取到的openid // sessionKey -> 从微信服务器端获取到的session_key // l -> 在Redis数据库中保存的时长 // timeUnit -> 在Redis数据库中保存的时长单位,TimeUnit是一个枚举类,建议点开查看一下源码就可以理解了。我 public void set(String key, String openID, String sessionKey, Long l, TimeUnit timeUnit){ //准备redis数据操作对象(String类型) ValueOperations<String,String> ops = redisTemplate.opsForValue(); //UserDO 封装了微信用户的相关属性,比如openID 和 sessionKey //准备user封装数据 UserDO user = new UserDO(); user.setOpenID(openID); user.setSessionKey(sessionKey); //这里使用的是Google的Gson包,个人喜好,可以随意更改,主要目的就是将user对象转换为JSON字符串,便于存放在Redis数据库中 //准备Gson对象封装json数据 Gson gson = new Gson(); // 将json数据存入redis数据库中 ops.set(key,gson.toJson(user),l,timeUnit); } //传入参数解析: //key -> 我自己生成的mySessionKey,用于获取对应的value,也就是被转换为json字符串的user对象 //这里参数不写mySessionKey而写key是因为这个DAO类并不设计业务逻辑,仅是单纯的对Redis数据库操作 public UserDO get(String key){ //准备redis数据操作对象(String类型) ValueOperations<String,String> ops = redisTemplate.opsForValue(); //准备user对象封装数据 UserDO user = new UserDO(); //准备gson对象封装json数据 Gson gson = new Gson(); //从redis数据库中获取json字符串 String str = ops.get(key); //使用gson对象方法序列化json字符串为user对象 user = gson.fromJson(str, UserDO.class); return user; } }
就这样,我们完成了对Redis数据库的操作
第四步,实现会话过滤器
实现了mySessionKey换openID与sessionKey以后,我们还需要设置一个会话过滤器。实现的逻辑也是类似于我们网站应用,但是也有所不同。不同的点在后面再细说,我们先实现一个会话过滤器:先看代码:
package com.baofeidyz.langrenshaplusbackground2.filter; import com.baofeidyz.langrenshaplusbackground2.util.BodyReaderHttpServletRequestWrapperUtil; import com.baofeidyz.langrenshaplusbackground2.util.RequestJsonUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import java.io.IOException; @Component @WebFilter(urlPatterns = "/*") public class MiniFilter implements Filter { @Autowired RequestJsonUtil requestJsonUtil; @Override public void init(FilterConfig filterConfig) throws ServletException { System.out.println("init"); } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { //TODO 在这里写具体的过滤逻辑 filterChain.doFilter(servletRequest,servletResponse); } @Override public void destroy() { System.out.println("destory"); } }
实现逻辑非常简单:
第一,实现Filter接口;
第二,重载三个方法;
第三,我们在doFilter()方法中添加我们自己的逻辑代码;
第四,添加 @Component 注解,这样SpringBoot在启动的时候就会把这个类的对象注入到容器中了;
第五,添加 @WebFilter(urlPatterns = “/*”) 注解,urlPatterns参数标识需要处理的url,这里是匹配所有,建议大家根据实际情况而定,比如/wechat/* 等。
不同点就在于以往我们是在session对象中取用户名或者是唯一标识,现在我们只能在request对象中去拿信息。
由于微信小程序与后台的交互主要是使用json格式,所以我的方案是将mySessionKey放在json数据中传输(当然也可以通过修改http的header完成,但是我并不知道应该如何在微信端操作,就不讲这个了),json格式如下:
{ code: null, mySessionKey: "9fafe6cf8da4463a9c69cc60773fd505", msg: null, data: null }
但是这里就会发生一个问题,由于json数据不同于以往我们的get或者post表单请求,如果你抓包的话,你会看到在http报文的response header中,有一个属性变了:
Content-Type:application/json;charset=UTF-8
所以我们没有办法通过request.getParameter() 或者是 request.getAttribute() 方法拿到json参数。
我们只能通过流来拿到json数据,方法如下:
package com.baofeidyz.langrenshaplusbackground2.util; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.io.UnsupportedEncodingException; @Component public class RequestJsonUtil { public String getJsonStr(HttpServletRequest request){ String jsonStr = null; String httpMethod = request.getMethod(); if ("".equals(httpMethod)){ return null; }else if ("POST".equals(httpMethod)){ try { jsonStr = this.getJsonStrByPOSTRquest(request); } catch (IOException e) { e.printStackTrace(); } }else if ("GET".equals(httpMethod)){ try { jsonStr = this.getJsonStrByGETRquest(request); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } return jsonStr; } private String getJsonStrByGETRquest(HttpServletRequest request) throws UnsupportedEncodingException { String json = null; String httpMethod = request.getMethod(); if ("GET".equals(httpMethod)) { json = new String(request.getQueryString().getBytes("iso-8859-1"), "utf-8").replaceAll("%22", "\""); } return json; } private String getJsonStrByPOSTRquest(HttpServletRequest request) throws IOException { String json = null; String httpMethod = request.getMethod(); if ("POST".equals(httpMethod)){ //判断是否为空 int contentLength = request.getContentLength(); if (contentLength < 0) { return null; } byte buffer[] = new byte[contentLength]; for (int i = 0; i < contentLength; ) { int readlen = request.getInputStream().read(buffer, i, contentLength - i); if (readlen == -1) { break; } i += readlen; } String charEncoding = request.getCharacterEncoding(); if (charEncoding == null) { charEncoding = "UTF-8"; } json = new String(buffer, charEncoding); } return json; } }
这里就不分析代码块了,具体思路就是通过这个工具类,将在过滤器中的request对象传入,然后从request中拿到我们想要的json数据。
至此,实际上我们已经完成了在过滤器拿到我们微信小程序发回来的json数据,But 你如果实操就会发现,这样子做了以后,我们的Controller拿不到数据了,永远都是null。具体的原因也非常的简单,就是流getInputStream()只能被读取一次,所以当你在过滤器中拿到了数据以后,你在Controller中也就拿不到了。
所以我们需要重写getInputStream()方法:
package com.baofeidyz.langrenshaplusbackground2.util; import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import java.io.*; import java.nio.charset.Charset; //继承HttpServletRequestWrapper类 public class BodyReaderHttpServletRequestWrapperUtil extends HttpServletRequestWrapper{ //私有化一个byte数据,用于存放我们读取出来的数据 private final byte[] body; //修改构造函数,调用私有化的getBodyByte()方法读取我们的json数据,当然是以byte数据的方式 public BodyReaderHttpServletRequestWrapperUtil(HttpServletRequest request) { super(request); body = this.getBodyByte(request); } /** * @author: 暴沸 * @email: baofeidyz@foxmail.com * @data: 2018/2/7 16:57 * @desc: 重写getInputStream()将获取的数据重新写入以保证Controller可以正常运行 */ @Override // 重写getInputStream()方法,主要逻辑就是将在构造方法中读取的byte数组,重新写入,并生成一个新的ServletInputStream()流,以此方式让我们的Controller可以顺利获取到数据 public ServletInputStream getInputStream() throws IOException { final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body); return new ServletInputStream() { @Override public boolean isFinished() { return false; } @Override public boolean isReady() { return false; } @Override public void setReadListener(ReadListener readListener) { } @Override public int read() throws IOException { return byteArrayInputStream.read(); } }; } /** * @author: 暴沸 * @email: baofeidyz@foxmail.com * @data: 2018/2/7 16:56 * @desc: 获取request对象传入的json数据并返回byte数组用于后面重新写入流 */ private byte[] getBodyByte(ServletRequest request){ StringBuffer stringBuffer = new StringBuffer(); InputStream inputStream = null; BufferedReader bufferedReader = null; try{ inputStream = request.getInputStream(); bufferedReader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8"))); String line = null; while ((line = bufferedReader.readLine()) != null) { stringBuffer.append(line); } } catch (IOException e) { e.printStackTrace(); } finally { if (null != inputStream) { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } if(null != bufferedReader){ try { bufferedReader.close(); } catch (IOException e) { e.printStackTrace(); } } } return stringBuffer.toString().getBytes(Charset.forName("UTF-8")); } }
至此我们顺利完成了整个登录态的维护demo,当然这里面还涉及到一个过滤器获取到mySessionKey以后如何从Redis数据库中去获取openID与sessionKey的问题,但这个并不是难点,也就不再赘叙了。
最后重新贴一下实现了获取json数据的过滤器代码:
package com.baofeidyz.langrenshaplusbackground2.filter; import com.baofeidyz.langrenshaplusbackground2.util.BodyReaderHttpServletRequestWrapperUtil; import com.baofeidyz.langrenshaplusbackground2.util.RequestJsonUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import java.io.IOException; @Component @WebFilter(urlPatterns = "/*") public class MiniFilter implements Filter { @Autowired RequestJsonUtil requestJsonUtil; @Override public void init(FilterConfig filterConfig) throws ServletException { System.out.println("init"); } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { //通过这行代码拿到request对象,然后传入filterChain.doFilter()方法中 HttpServletRequestWrapper httpServletRequestWrapper = new BodyReaderHttpServletRequestWrapperUtil((HttpServletRequest) servletRequest); String json = requestJsonUtil.getJsonStr(httpServletRequestWrapper); //这里我们就拿到了我们想要的json数据,然后我们通过mySessionKey去获取Redis数据库中的数据就可以拿到该用户的相关数据了。具体逻辑就根据大家的实际情况而定 System.out.println("json: "+json); filterChain.doFilter(httpServletRequestWrapper,servletResponse); } @Override public void destroy() { System.out.println("destory"); } }
平时很少写这么长的文章,如果有看不懂的地方欢迎baofeidyz@foxmail.com
参考文章:解决在Filter中读取Request中的流后,后续controller或restful接口中无法获取流的问题
相关文章推荐
- 微信小程序结合后台数据管理实现商品数据的动态展示、维护
- SpringBoot 整合redis实现缓存 记录@CachePut值为1
- java实现微信小程序登录态维护的示例代码
- 微信小程序中做用户登录与登录态维护的实现详解
- spring boot结合spring security实现注册后自动登录
- 微信小程序中做用户登录与登录态维护的实现详解
- SpringBoot+Maven项目实战(6):整合Log4j和Aop,实现简单的日志记录
- spring boot + redis 实现session共享
- (35)Spring Boot集成Redis实现缓存机制【从零开始学Spring Boot】
- spring boot + redis 实现session共享
- spring结合redis如何实现数据的缓存
- Spring Boot 结合shiro做第三方登录验证
- Spring Boot / Spring MVC 入门实践 (四) :需求记录网站的实现
- spring boot + redis 实现session共享
- 35. Spring Boot集成Redis实现缓存机制【从零开始学Spring Boot】
- springboot整合redis,实现session共享
- spring boot + redis 实现session共享
- springboot结合redis使用CachingConfigurerSupport方法不能被继承
- 进击的java(8):springmvc+redis实现登录与拦截器
- eayui结合Spring Boot实现客户端分页