登錄
DAO 層
-
構建實體
數據庫中有表login_ticket。因此構建實體LoginTicket在entity包下。屬性一一對應。get。set。toString方法public class LoginTicket { private int id; private int userId; private String ticket; private int status; private Date expired; get.... set.... toString... }
-
構建Mapper接口(LoginTicketMapper)
@Mapper public interface LoginTicketMapper { int insertLoginTicket(LoginTicket loginTicket); LoginTicket selectByTicket(String ticket); int updataStatus(String ticket, int status); }
-
配置mapper接口
有兩種實現方式,-
通過resources下mapper下的新建對應xml文件配置。(前面一直使用這種方式)
-
同過直接在mapper接口寫注解的方式實現。
@Mapper public interface LoginTicketMapper { @Insert({ "insert into login_ticket(user_id, ticket, status, expired) ", "values(#{userId},#{ticket},#{status},#{expired}) " }) @Options(useGeneratedKeys = true,keyProperty = "id") int insertLoginTicket(LoginTicket loginTicket); @Select({ "select id, user_id, ticket, status, expired ", "from login_ticket ", "where ticket= #{ticket}" }) LoginTicket selectByTicket(String ticket); @Update({"update login_ticket set status=#{status} ", "where ticket= #{ticket} " }) int updataStatus(String ticket, int status); }
- 使用注解 @Update({}) 小括號內大括號
- 引號內寫SQL語句, 變量同xml一樣采用 #{} 表示。可以拼接,用逗號隔開方便閱讀。最好在末尾加空格方便拼接。
- 同樣可以加if條件語言。但需要用在"<script>""</script>"內部。 同時需要注意引號需要采用轉義字符。
@Update({ "<script>", "update login_ticket set status=#{status} ", "where ticket= #{ticket} ", "<if test=\"ticket!=null\"> ", "and 1=1", "</if> ", "</script> " })
-
-
測試
因為沒有提示,所以比較容易寫錯。推薦寫完之后做個測試。在繼續往下做。@Autowired private LoginTicketMapper loginTicketMapper; @Test public void testInsertLoginTicket(){ LoginTicket loginTicket = new LoginTicket(); loginTicket.setUserId(0001); loginTicket.setStatus(0); loginTicket.setTicket("aabc"); loginTicket.setExpired(new Date(System.currentTimeMillis()+1000 *60 *10)); loginTicketMapper.insertLoginTicket(loginTicket); } @Test public void testSelectLoginTicket(){ LoginTicket ticket =loginTicketMapper.selectByTicket("aabc"); System.out.println(ticket); loginTicketMapper.updataStatus("aabc",1); ticket =loginTicketMapper.selectByTicket("aabc"); System.out.println(ticket); }
業務層Service
(與注冊的邏輯相同。判空,判合法性,執行....)同樣也是針對用戶行為,因此寫在UserService中。
這里需要注意! 驗證碼的判斷在Controller進行就可以,不需要放在Service中。可以這么理解,需要與數據庫中存的信息做對比或者往數據庫中存得數據在Service中。其他可以在Controller中處理。

@Autowired private LoginTicketMapper loginTicketMapper; public Map<String,Object> login(String username, String password, int expiredSeconds){ Map<String,Object> map = new HashMap<>(); //空值處理 if(StringUtils.isBlank(username)){ map.put("usernameMsg","賬號不能為空!"); return map; } if(StringUtils.isBlank(password)){ map.put("passwordMsg","密碼不能為空!"); return map; } //合法性處理 User user = userMapper.selectByName(username); if(user == null){ map.put("usernameMsg","該賬號不存在!"); return map; } if(user.getStatus() ==0){ map.put("usernameMsg","該賬號未激活!"); return map; } //驗證密碼 String salt = user.getSalt(); String psw = CommunityUtil.md5(password + salt); if(!psw.equals(user.getPassword())){ map.put("passwordMsg","密碼不正確!"); return map; } //生成登錄憑證 LoginTicket loginTicket = new LoginTicket(); loginTicket.setUserId(user.getId()); loginTicket.setTicket(CommunityUtil.generateUUID()); loginTicket.setStatus(0); loginTicket.setExpired(new Date(System.currentTimeMillis() + expiredSeconds * 1000 )); loginTicketMapper.insertLoginTicket(loginTicket); //如果登錄成功了,要把憑證放進去,最終要將他發給客戶端。(只需要放loginTicket.ticket字符串就可) map.put("ticket",loginTicket.getTicket()); return map; }
Controller
-
LoginController
- 對於 @RequestMapping(path = "/login", method = RequestMethod.POST) path可以相同,但是path相同的情況下method不能相同。
- 登錄界面參數比較多。需要model用來返回頁面信息,需要傳遞給客戶端cookie通過Response攜帶,驗證碼的信息需要從Session中取得。記住我標簽對勾與否也需區分。因此, public String login(String username, String password, String code, boolean rememberme, Model model, HttpSession session, HttpServletResponse response)
- 檢查驗證碼時忽略大小寫,同時注意在session中取得的值也不能為空。
- 設置過期時間常量在CommunityConstant中定義。在LoginController implements CommunityConstant 使用
- 聲明一個固定的值到變量中。@Value("${}") 代碼中其值在配置文件application.properties中
- 重定向采用 return "redirect:/login";
- 對於實體參數,SpringMVC會自動把實體參數裝入model中。Java自帶的普通參數類型例如String則不會自動裝入model中。因此有兩種方式獲取到:1通過人為主動的裝入model中。2這些對象是存在於request對象中的。可以從request中取值。(對后面修改html文件,訪問還未終止,因此可以從request中取值)
-
@Value("${server.servlet.context-path}") private String contextPath; /** * 給瀏覽器返回一個html,這個html包含一個圖片的路徑。瀏覽器依據路徑再次訪問服務器獲得圖片。 */ @RequestMapping(path = "/login", method = RequestMethod.GET) public String getLoginPage(){ return "site/login"; } /** * 登錄。 */ @RequestMapping(path = "/login", method = RequestMethod.POST) public String login(String username, String password, String code, boolean rememberme, Model model, HttpSession session, HttpServletResponse response){ //檢查驗證碼 String kaptcha =(String) session.getAttribute("kaptcha"); if (StringUtils.isBlank(kaptcha) || StringUtils.isBlank(code) || !kaptcha.equalsIgnoreCase(code)){ model.addAttribute("codeMag","驗證碼不正確"); return "/site/login"; } //檢查賬號、密碼 int expriedSeconds = rememberme ? REMEMBER_EXPIRED_SECONDS : DEFAULT_EXPIRED_SECONDS; Map<String ,Object> map = userService.login(username, password,expriedSeconds); if (map.containsKey("ticket")){ //成功登錄,給客戶端發一個帶ticket的cookie Cookie cookie = new Cookie("ticket",map.get("ticket").toString()); cookie.setPath(contextPath); cookie.setMaxAge(expriedSeconds); response.addCookie(cookie); return "redirect:/index"; }else{ //登錄失敗,把錯誤信息帶給登錄界面 model.addAttribute("usernameMsg",map.get("usernameMsg")); model.addAttribute("passwordMsg",map.get("passwordMsg")); return "/site/login"; } }
-
修改對應的HTML
- 提交的方式內容 <form class="mt-5" method="post" th:action="@{/login}">
- 給每一個賦name,與controller方法中的一致(用戶名,密碼、驗證碼同理)記住我的input也需要name ,rememberme
<input type="text" class="form-control is-invalid" id="username" name="username" placeholder="請輸入您的賬號!" required>
- 回到頁面對錯誤的值有一個顯示
- th:value="${param.username}" 相當於request.gerParameters(username)。
<input type="text" class="form-control is-invalid" th:value="${param.username}" id="username" name="username" placeholder="請輸入您的賬號!" required>
密碼同理。驗證碼就不需要給默認值了。對於記住我。是通過check來決定的。因此,
<input type="checkbox" id="remember-me" name="rememberme" th:checked="{param.rememberme}">
- 錯誤提示(修改顯示內容text,與是否顯示——動態class) 賬號、密碼、驗證碼
<input type="text" th:class="|form-control ${usernameMsg==null?'':'is-invalid'}|" th:value="${param.username}" id="username" name="username" placeholder="請輸入您的賬號!" required> <div class="invalid-feedback" text="${usernameMsg}"> 該賬號不存在! </div>
- 各種錯誤
- 出錯基本都是咋HTML頁面。錯誤基本都是忘記加${}符號,忘記加 th: 忘記寫name=“” 再就是拼寫錯誤,這種的。寫的時候還是要仔細一點,找錯太費勁了。
退出
將登錄憑證轉為失效狀態。
Seivice
userService
/** * 登出 */ public void logout(String ticket){ loginTicketMapper.updataStatus(ticket,1); }
Controller
- 重定向采用 return "redirect:/login";
/** * 登出 */ @RequestMapping(path = "/logout",method = RequestMethod.GET) public String logout(@CookieValue("ticket") String ticket){ userService.logout(ticket); return "redirect:/login"; //重定向默認get方法對應的。 }
- 修改對應的HTML文件。因為在頭部,所有的都是復用的index的header,去index修改
<a class="dropdown-item text-center" th:href="@{/logout}">退出登錄</a>
與LogController中RequestMapping聲明的path=“logout”對應
關於如何查看是否登錄登出成功
- 在數據庫中的login_ticket 查看狀態0,1
- 在客戶端瀏覽器中查看cookie是否存在,可以判斷是否已經鄧璐成功。