公司接了一个小程序的活。本来后台想用的是session存储登录状态。后来发现登录存进去的sessionId和取时候的sessionId不一样,导入无法取到登录状态,百度了一下才知道,原来是小程序端不支持存储cookie,后来想到了在微信登录授权后,把openId加密(token),当做key,openId当做value存储到redis当中。然后小程序端的Storage存一个token,每次访问接口时带着这个参数访问。
这是小程序官网的图,介绍的还是挺详细的.
小程序登录流程图
该图中,“小程序”指的就是我们使用小程序框架写的代码部分,“第三方服务器”一般就是我们自己的后台服务程序,“微信服务器”是微信官方的API服务器。
微信小程序端的代码我就不贴了,说下流程吧:
1.微信授权,带着code访问后台接口。
2.根据返回的token(加密后的openId)存储到storage,表头中。
3.每次访问后台接口都带着token,这里可能会有一个失效问题,后面我会说到。
/**
* @param code 微信开放平台重定向这里,携带的code码
* @author xph
*/
@ResponseBody
@RequestMapping("/WXServlet")
public HashMap WXServlet(String code){
// 根据code请求access_token,获取唯一标识openId
Map map = CodeUtils.get_access_token(code);
String openid = map.get("openid").toString();
//加密
String md5openId = StringUtil.MD5(openid);
//用户入库
HashMap hashMap = userService.wxLogin(openid);
//查询token是否存在
String token = redisDao.queryUserToken(md5openId);
if(StringUtils.isNotBlank(token)) {
//如果存在,重新生成
redisDao.deleteUserToken(md5openId);
}
//重新生成openid
redisDao.saveUserTokenToRedisDb(md5openId, openid);
hashMap.put("token", md5openId);
InitialPicDto pic = initialPicService.getPic();
hashMap.put("pic", pic);
return hashMap;
}
Redis我这里就不讲了,既然要用,就肯定会用。
既然用到了token,那就需要一个拦截器,在每次请求接口之前判断是否存在此openId,
public class CustomeInterceptor implements HandlerInterceptor {
private final Logger logger =LoggerFactory.getLogger(CustomeInterceptor.class);
@Autowired
private RedisDao redisdao;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
PrintWriter out = null ;
logger.info("request请求地址path:"+request.getServletPath());
//从请求头获取token
String token = request.getHeader(CommonConstants.TOKEN);
//查询是否存在token
String openid = redisdao.queryUserToken(token);
if(StringUtils.isBlank(openid)) {
out =response.getWriter();
JSONObject jsonObject = new JSONObject();
jsonObject.put("code",ErrorCode.LOGIN_ERROR);
jsonObject.put("Error", "登录失败");
out.write(jsonObject.toJSONString());
out.flush();
out.close();
return false;
}
redisdao.expireEndTime(token);
return true;
}
注意我上面提到的失效问题,因为我redis存的失效时间为两个小时,和微信的失效时间保持一致。但是有个问题是小程序端有个方法会自动判断是否失效,可能用户两个小时候内一直在使用,不会失效,但是我redis存的token会失效。这就会导致一个连接问题。
所以我的解决方案是:在每次访问接口前,判断是否存在此token,若存在,则刷新token的失效时间(两个小时)。
也就是上面的redisdao.expireEndTime(token);
//设置过期时间
public void expireEndTime(String key) {
this.stringRedisTemplate.expire(key, 2, TimeUnit.HOURS);
}
1.小程序端发起请求并携带主要参数(code)
2.java后台接到/login请求后,根据code去调用微信接口获取用户唯一标识openid和sessionKey
3.根据openid查询mysql数据库,判断该用户是否存在,如果不存在将用户非敏感信息和其他初始化数据存入到数据库中,如果已存在,不操作
4.根据openid查询redis数据库,判断openid对应的skey是否存在,如果存在则删除原来老的skey以及对应的openid和sessionKey.
5.通过md5生成唯一的skey,用openid做键,skey做值,存入到redis中
6.将微信小程序需要的数据封装到map中,返回给小程序端