记一次Spring Cloud Session微服务间传递丢失问题定位(续)

 回想一下,Spring cloud微服务框架曾使用两年之久,为什么以前没有这种情况发生呢?

   仔细梳理了以前使用的场景,用户在请求业务服务之前,必须先进行系统登录,在用户登录校验请求的时候,创建系统Session而且这种登录校验过程中不涉及跨服务使用Session的情况,在用户登录校验通过以后,用户再请求业务时其实Session已经创建好了,不涉及Session创建,故没有触发上面的情况,的确实际业务使用中很少涉及这种情况,也就让人觉得使用很顺利,一切OK的错觉。

    填补这个坑,想到了如下两种方式:


  •  创建Session与使用Session不放到一次请求中

        也就是业务上规避一下,如果请求发现Session没有创建,说明用户可能没有登录过,可以创建Session后,将其请求重定向到Home页面或登录页面,这样下次业务数据请求时就可以直接使用Session了。这也是以前使用中没有注意到这个问题原因。

  •   重新实现CookieId获取策略

        Spring boot在请求进来时对Request进行了包装,而在获取Session id时目前Spring boot支持两种,一种Cookie方式,也就是通常默认使用的方式;还有一种是header模式,通过request.getHeader()获取Session ID。

       Spring boot定义了Session id获取的接口org.springframework.session.web.http.HttpSessionIdResolver,默认的两个实现类为:

  1.     org.springframework.session.web.http.CookieHttpSessionIdResolver

            该接口为默认的Spring boot获取Session方法,从Cookie中获取相应的Session id。 

     2.    org.springframework.session.web.http.HeaderHttpSessionIdResolver

           该接口通过从Request Header中获取session id,对其感兴趣的话可以查询Spring boot源码进行查看详细内容。

     既然Spring boot提供了这个接口,那我们就可以自定义实现一个HttpSessionIdResolver接口,来获取自定义的Session id,下面我们应该怎么实现这个接口呢?

     Spring boot默认使用CookieHttpSessionIdResolver进行操作,这样我们就有了参考,因为该类为Final类,不能够继承,故需要将其源码Copy到自定义类中,然后修改其id获取方式,那我们应该以什么方法获取到Session id呢?

     我们可以将Header与Cookie两种结合方式来获取,即在Session创建的服务类中,将Session id设置到request header中,而在使用Session的服务类中,定义CustomHttpSessionIdResolver类,先从request header中获取session id,如果获取不到,再从cookie中获取,这样保证了Session id任何时候都能获取到,然后通过Session id也能获取到正确的Session对象了。

      具体实现如下:

      ZUUL服务中Filter中如果发现创建了Session则将Session id设置到request header中.

@Component
public class LoginFilter extends ZuulFilter {

	private final static Logger LOG = LoggerFactory.getLogger(LoginFilter.class);
	
	private final static  String SESSION_ID = "SESSIONID";
	
	@Override
	public Object run() throws ZuulException {
		Double rand = Math.random() * 100;
		int randInt = rand.intValue();
		RequestContext ctx = RequestContext.getCurrentContext();
		HttpServletRequest request = ctx.getRequest();
		HttpSession session = request.getSession();
		if(!request.isRequestedSessionIdValid()) {
			ctx.addZuulRequestHeader(SESSION_ID, session.getId());
		}
		session.setAttribute("test", randInt);
		LOG.info("Session id:{} test:{}",session.getId(),randInt);
		return null;
	}

	@Override
	public boolean shouldFilter() {
		return true;
	}

	@Override
	public int filterOrder() {
		return 0;
	}

	@Override
	public String filterType() {
		return "pre";
	}

}

    业务服务中,定制获取Cookie的类,如下:

   

package com.king.business;

import java.util.Collections;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringUtils;
import org.springframework.session.web.http.CookieHttpSessionIdResolver;
import org.springframework.session.web.http.CookieSerializer;
import org.springframework.session.web.http.DefaultCookieSerializer;
import org.springframework.session.web.http.HttpSessionIdResolver;
import org.springframework.session.web.http.CookieSerializer.CookieValue;
import org.springframework.stereotype.Component;
/**
 * 
 * @author Administrator
 *
 */
@Component
public class CustomHttpSessionIdResolver implements HttpSessionIdResolver {

	private static final String WRITTEN_SESSION_ID_ATTR = CookieHttpSessionIdResolver.class
			.getName().concat(".WRITTEN_SESSION_ID_ATTR");

	private final static  String SESSION_ID = "SESSIONID";
	private CookieSerializer cookieSerializer = new DefaultCookieSerializer();

	@Override
	public List<String> resolveSessionIds(HttpServletRequest request) {
		String sessionId = request.getHeader(SESSION_ID);
		if(!StringUtils.isEmpty(sessionId)) {
			return Collections.singletonList(sessionId);
		}
		return this.cookieSerializer.readCookieValues(request);
	}

	@Override
	public void setSessionId(HttpServletRequest request, HttpServletResponse response,
			String sessionId) {
		if (sessionId.equals(request.getAttribute(WRITTEN_SESSION_ID_ATTR))) {
			return;
		}
		request.setAttribute(WRITTEN_SESSION_ID_ATTR, sessionId);
		this.cookieSerializer
				.writeCookieValue(new CookieValue(request, response, sessionId));
	}

	@Override
	public void expireSession(HttpServletRequest request, HttpServletResponse response) {
		this.cookieSerializer.writeCookieValue(new CookieValue(request, response, ""));
	}

	/**
	 * Sets the {@link CookieSerializer} to be used.
	 *
	 * @param cookieSerializer the cookieSerializer to set. Cannot be null.
	 */
	public void setCookieSerializer(CookieSerializer cookieSerializer) {
		if (cookieSerializer == null) {
			throw new IllegalArgumentException("cookieSerializer cannot be null");
		}
		this.cookieSerializer = cookieSerializer;
	}

}
     这样通过配合完美的解决了Spring cloud单次服务Session传递问题。


猜你喜欢

转载自blog.csdn.net/weixin_36171229/article/details/80954716