电商项目面试总结

项目介绍:

整个项目采用分布式的架构设计,包括登录系统、搜索系统(没做)、购物车系统、订单系统、支付系统等。整个项目采用nginx+tomcat来部署,nginx主要用来做反向代理和负载均衡。主要用redis来做登录信息缓存,mysql做数据库。自己参与了登录系统的开发,包括注册、单点登录等功能模块。

问题1:为什么要选用redis?

由于每个系统都单独部署运行一个单独的tomcat,所以,不能将用户的登录信息保存到session中(多个tomcat的session不共享),所以选用redis来缓存登录信息,当用户登录时,将用户登录信息保存到redis中,并生成一个token保存到cookie中(不太确定是否是这么实现的?)

问题2:单点登录系统的基本流程?

  当用户点击登录按钮的时候,用户输入用户名和密码,检验用户名是否在数据库中存在,然后用户名密码是否正确。这里的密码是用了spring的MD5加密技术。当全部成功后,将sessionId(也可以生成一个UUID)写入cookie中供前端调用,写入浏览器的cookie中,然后存入到redis中(key是sessionId,value是用户信息),并设置有效期。

  这里的cookie是设置了共同的name,所以不论是什么系统进行登录,前端页面都会存有这个name的cookie,也就实现了所有子系统都可以访问到cookie。

  当用户登录其他子系统时,先从cookie中获取token信息(也就是sessionId),根据token信息获取用户信息。用户每次与网站的交互,比如查看产品,则刷新一次redis的时间,重新设置有效期,这个效果是通过拦截器来实现的。

  拦截器的拦截,在springMVC.xml中设置拦截的名称。

登录流程代码:先写cookie再写redis.

 1     @RequestMapping(value = "login.do", method = RequestMethod.POST)
 2     @ResponseBody
 3     public ServerResponse<User> login(String username, String password, HttpSession session, HttpServletResponse httpServletResponse) {
 4         ServerResponse<User> response = iUserService.login(username, password);
 5         if(response.isSuccess()) {
 6             CookieUtil.writeLoginToken(httpServletResponse, session.getId());
 7             //sessionId是key,用户的登录信息是value,存在redis中
 8             //sessionId是tomcat中自动生成的,针对当前项目,每启动一次,sessionId就变一次
 9             //设置session超时时间
10             RedisShardedPoolUtil.setEx(session.getId(), JsonUtil.obj2String(response.getData()), Const.RedisCacheExtime.REDIS_SESSION_EXTIME);
11 
12         }
13         return response;
14     }
View Code

 拦截器重置有效期:

 1 public class SessionExpireFilter implements Filter {
 2 
 3     @Override
 4     public void init(FilterConfig filterConfig) throws ServletException {
 5 
 6     }
 7 
 8     @Override
 9     public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
10         HttpServletRequest httpServletRequest = (HttpServletRequest)servletRequest;
11         String loginToken = CookieUtil.readLoginToken(httpServletRequest);
12         if(StringUtils.isNotEmpty(loginToken)) {
13             //判断loginToken是否为空或者""
14             //如果不为空的话,符合条件,继续拿user信息
15             String userJsonStr = RedisShardedPoolUtil.get(loginToken);
16             User user = JsonUtil.string2Obj(userJsonStr, User.class);
17             if(user != null) {
18                 //如果user不为空,则重置session的时间,即调用expire命令
19                 RedisShardedPoolUtil.expire(loginToken, Const.RedisCacheExtime.REDIS_SESSION_EXTIME);
20             }
21         }
22         filterChain.doFilter(servletRequest, servletResponse);
23     }
24 
25     @Override
26     public void destroy() {
27 
28     }
29 }
View Code

web.xml部署拦截器:

1     <filter>
2         <filter-name>sessionExpireFilter</filter-name>
3         <filter-class>com.mall.controller.common.SessionExpireFilter</filter-class>
4     </filter>
5     <filter-mapping>
6         <filter-name>sessionExpireFilter</filter-name>
7         <url-pattern>*.do</url-pattern>
8     </filter-mapping>
View Code

问题3:单点登录之跨域问题?

  将cookie存在一个公共的站点的页面上就可以了,这里我们管那个站点叫主站S。

  环境1:a.xxx.com需要跟b.xxx.com实现跨域,这种比较简单,只需要设置cookie的域名关联域就可以了 cookie.Domain = "xxx.com",这样两个域名间的cookie就可以互相访问,实现跨域。【项目中使用的这种环境】

  环境2:a.aaa.com需要跟b.bbb.com实现跨域,这种不同域名的情况下,想要实现就必须换种方式.

  在这里我将引入第三者,s.sss.com这个站点,就是某个浏览器同时打开了这3个站点,我们访问A站点,先判断自身是否登录,如果session为空,就重定向到S站点,判断S站点上面是否有cookie,如果S站点上面也没有cookie,则由S站点重定向到A站点的登录页.

  这样我们就实现了第一步,S站做的的就是隐藏在幕后,子站先判断自己是否存在session,如果不存在,就重定向到主站S上面去验证.

  第二步,验证登录信息合法性.这里我引入token(令牌),网上有很多资料,描述token的传递,工作方式是这样,A登录成功,保存自身的session,重定向到S,S在自己站点保存一个session跟cookie,session保存token对象{tokenID,userName,startTime,endTime},cookie保存tokenID,tokenID是一个Guid,把token对象缓存在集合里面,另起一个线程,根据endTime(过期时间)来定期清理集合列表,重定向到A的时候再将tokenID传递过去,拿到tokenID后,进入验证环节,S站有提供一个接口,根据tokenID获取token对象,如果获取到对象,且没有失效,则tokenID合法,跳入index页面.情况2,A登录,直接打开B,这时候B自身没有session,会主动请求主站,主站会返回cookieID(S站存在客户端的cookie),这个时候再走验证环节,如果通过,则B根据token对象创建自身的session,再跳入index。

问题4:忘记密码找回密码的流程?

  在注册的时候会设置找回密码的问题和答案,在非登录状态忘记密码后,需要通过回答问题和答案找回密码。根据用户名找到用户设置的问题,然后回答完问题后,生成一个UUID,缓存在redis中设置有效期,并返回这个UUID。然后前端将这个UUID和新密码传入重置密码的函数,将传入的UUID和之前缓存在redis中的UUID进行比较,如果相同,则对新密码进行md5加密后更新到数据库中。

原文地址:https://www.cnblogs.com/cing/p/9128615.html