【狼人杀plus全记录】SpringBoot结合Redis实现微信小程序登录态维护

原创 2018年02月08日 15:19:07

前文:微信小程序API文档中,有一个关于wx.login()的登录态维护的时序图,这篇文章就是记录如何实现的。

准备工作

首先,再开始这篇文章内容之前,我们已经拿到了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接口中无法获取流的问题

版权声明:转载请注明我的个人微信平台 暴沸

微信小程序中做用户登录与登录态维护的实现详解

微信小程序的运行环境不是在浏览器下运行的。所以不能以cookie来维护登录态。下面这篇文章主要给大家介绍了微信小程序中如何做用户登录与登录态维护的相关资料,文中介绍的非常详细,需要的朋友可以参考学习。...
  • sunshao904
  • sunshao904
  • 2017年10月18日 20:00
  • 617

【狼人杀plus全记录】没有公网IP照样完美解决微信小程序本地测试问题,超简单方法!

前文:在开发微信小程序后台的时候,我们需要使用域名进行跳转访问,按照传统的思路我们的域名只能填写一个公网IP,然而多数情况下我们并没有公网IP 方法非常简单,有两种思路: 第一种,将域名定向到...
  • baofeidyz
  • baofeidyz
  • 2018年01月30日 16:21
  • 124

微信小程序维护登录态与获取用户信息_0

前言、   微信小程序的运行环境不是在浏览器下运行的。所以不能以cookie来维护登录态。下面我就来说说我根据官方给出的方法来写出的维护登录态的方法吧。 一、登录态维护 官方的文档地址:http...
  • woshishui123aa
  • woshishui123aa
  • 2017年01月27日 20:53
  • 7676

微信小程序中用户登录和登录态维护

让用户登录,标识用户和获取用户信息,以用户为核心提供服务,是大部分小程序都会做的事情。我们今天就来了解下在小程序中,如何做用户登录,以及如何去维护这个登录后的会话(Session)状态。在微信小程序中...
  • qq_35228658
  • qq_35228658
  • 2017年02月13日 09:55
  • 4105

Asp.Net编程中,用户状态维护的几种方法 ---(转自Pierce)

  下面对维持用户状态作简单的描述,如果要深入描述,写一本专业书籍也不过分,其中的使用技巧,需要在实践中获取。 Asp.Net 编程依赖Http协议,而Http协议本身是无状态的,如果要维护用户的状态...
  • louisp
  • louisp
  • 2006年01月15日 23:54
  • 942

微信小程序--后端管理平台9页面Springboot接受表单实体

实体类一定要有无参构造器,不然会报错,无法初始化。 后台接受前台表单: @PostMapping(value = "/academy/addPost") @Re...
  • sinat_24048051
  • sinat_24048051
  • 2017年09月15日 09:57
  • 865

微信小程序开发——1.环境搭建

一:安装java环境 1、将下载好的jdk包通过ssh上传到centos操作系统  2、进行解压 tar -vxzf jdk-8u45-linux-x64.tar.g 3、编辑系统环境变量 vi...
  • sinat_24048051
  • sinat_24048051
  • 2017年07月28日 21:54
  • 299

java 微信小程序登录 验证登录并获取用户信息

public class WeChatAppLoginReq { private String code; private String rawData; private String ...
  • zhibo112
  • zhibo112
  • 2017年09月12日 16:48
  • 525

(狼人杀)游戏研究-Android

游戏中的功能研究:游戏的通信/充值/广告/分享 通信:环信 融云 SurfaceView 与 GLSurfaceView效率!   两款游戏引擎,一个是cocos2dx,另一款是andengine,其...
  • ShareUs
  • ShareUs
  • 2017年05月18日 22:51
  • 950

小程序登录态维持

这几天一直在搞小程序,对于其登录态的维持和后台运行机制一直有些困惑。花了很多时间完成了,这里可以给新手指条路,也给自己做个笔记。分为下面几个方面来讲常规Web项目登录态维持小程序的特殊点如何在小程序中...
  • Rafireman
  • Rafireman
  • 2018年01月18日 09:50
  • 239
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:【狼人杀plus全记录】SpringBoot结合Redis实现微信小程序登录态维护
举报原因:
原因补充:

(最多只允许输入30个字)