Java Web 项目学习(二) 开发登录、退出功能
登录
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是否存在,可以判断是否已经邓璐成功。