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

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

2018.04.13更新:这种方案不好,我后面有时间会重新写,主要错误点是在于应该把mySessionKey放在HTTP协议的header中,而且微信小程序的wx.request()方法也是支持的,我的小程序已经更新了,但是没有更新博客。

前文:微信小程序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接口中无法获取流的问题

版权声明:转载请注明我的个人微信平台 暴沸 https://blog.csdn.net/baofeidyz/article/details/79287090

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

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

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

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

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

前言、   微信小程序的运行环境不是在浏览器下运行的。所以不能以cookie来维护登录态。下面我就来说说我根据官方给出的方法来写出的维护登录态的方法吧。 一、登录态维护 官方的文档地...
  • qq_16399991
  • qq_16399991
  • 2017年05月31日 21:56
  • 1834

【小程序合集】哪个游戏可以陪你度过周末?

极乐商店整理了24款微信益智小游戏,这个周末可以让TA好好陪你了!1、功夫拼图创新的多人协作玩法,拼图一加一,我拼一张你拼一张,挥洒创意,总能让我们忍俊不禁。朋友一起才好玩~2、狼人杀小法官『狼人杀小...
  • qq_38530880
  • qq_38530880
  • 2017年08月09日 14:41
  • 1174

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

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

小程序登录态维持

这几天一直在搞小程序,对于其登录态的维持和后台运行机制一直有些困惑。花了很多时间完成了,这里可以给新手指条路,也给自己做个笔记。分为下面几个方面来讲常规Web项目登录态维持小程序的特殊点如何在小程序中...
  • Rafireman
  • Rafireman
  • 2018年01月18日 09:50
  • 812

一个新的项目:狼人杀(六)

狼人杀的运营进入平稳期。 最初的兴奋过后,多了一些惫懒和麻木。现在的日常,就是时不时发布一些小的更新,补一些过去的坑,看看用户的评价,感觉不过如此。大概是最近繁琐的工作内容消耗了我的耐心。平心而论,...
  • xdx3000
  • xdx3000
  • 2017年04月06日 17:35
  • 332

android狼人杀源码,桌面源码,猎豹快切源码

Android精选源码 android实现狼人杀app源码 android实现精心打造的Android基础框架源码 android热门电影的客户端源码 ...
  • ld11620967
  • ld11620967
  • 2017年10月25日 11:45
  • 420

还不知道聚会玩什么?群play小程序一起来看看吧

什么?聚会不知道该玩些什么?朋友间距离远没法聚到一起?最近火起来的名为“群play”的小程序或许可以帮上你的大忙。 这款小程序主要分为三个板块,分别是无聊木鱼、剪刀石头布还有聚会少不了。其实这款小程...
  • weixiaocheng123
  • weixiaocheng123
  • 2017年12月11日 11:54
  • 235

一个新的项目:狼人杀

最近从和菜头的文章中得到了一些启发。他开发的姨妈日记,和他朋友开发的高尔夫计分,这些简单的小程序,在简单的推广后也获得了不错的反响。这给了我对小程序的一些信心和想法。之前总想着做游戏一步到位的想法,可...
  • xdx3000
  • xdx3000
  • 2017年03月06日 09:12
  • 1052
收藏助手
不良信息举报
您举报文章:【狼人杀plus全记录】SpringBoot结合Redis实现微信小程序登录态维护
举报原因:
原因补充:

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