項目介紹:
整個項目采用分布式的架構設計,包括登錄系統、搜索系統(沒做)、購物車系統、訂單系統、支付系統等。整個項目采用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 }
攔截器重置有效期:

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 }
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>
問題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加密后更新到數據庫中。