Echo/docs/50-开发登录登出功能.md
2021-01-19 13:01:26 +08:00

226 lines
6.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 开发登录、登出功能
---
登录:
- 验证账号、密码、验证码
- 成功时,生成登录凭证并将其状态设置为有效,发放给客户端(为什么不用 session而是存放在数据库表中因为 session 会给服务器增加压力,且不利于分布式部署)
- 失败时,跳转回登录页
登出:
- 将登录凭证修改为失效状态
- 跳转至网站首页
## DAO
```java
@Mapper
public interface LoginTicketMapper {
/**
* 插入一条 LoginTicket
* @param loginTicket
* @return
*/
int insertLoginTicket(LoginTicket loginTicket);
/**
* 根据 ticket 查询 LoginTicket
* @param ticket
* @return
*/
LoginTicket selectByTicket(String ticket);
/**
* 更新凭证状态0有效1无效
* @param ticket
* @param status
* @return
*/
int updateStatus(String ticket, int status);
}
```
对应的 `mapper.xml`
```xml
<sql id = "insertFields">
user_id, ticket, status, expired
</sql>
<sql id = "selectFields">
id, user_id, ticket, status, expired
</sql>
<!--根据 ticket 查询凭证信息-->
<select id="selectByTicket" resultType="LoginTicket">
select <include refid="selectFields"></include>
from login_ticket
where ticket = #{ticket}
</select>
<!--插入凭证信息-->
<insert id="insertLoginTicket" parameterType="LoginTicket" keyProperty="id">
insert into login_ticket (<include refid="insertFields"></include>)
values(#{userId}, #{ticket}, #{status}, #{expired})
</insert>
<!--根据 ticket 修改凭证状态-->
<update id="updateStatus">
update login_ticket set status = #{status} where ticket = #{ticket}
</update>
```
## Service
```java
/**
* 用户登录(为用户创建凭证)
* @param username
* @param password
* @param expiredSeconds 多少秒后凭证过期
* @return Map<String, Object> 返回错误提示消息以及 ticket(凭证)
*/
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;
}
// 验证密码
password = CommunityUtil.md5(password + user.getSalt());
if (!user.getPassword().equals(password)) {
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);
map.put("ticket", loginTicket.getTicket());
return map;
}
/**
* 用户退出(将凭证状态设为无效)
* @param ticket
*/
public void logout(String ticket) {
loginTicketMapper.updateStatus(ticket, 1);
}
```
## Controller
```java
/**
* 用户登录
* @param username 用户名
* @param password 密码
* @param code 验证码
* @param rememberMe 是否记住我(点击记住我后,凭证的有效期延长)
* @param model
* @param session 从 session 中取出验证码
* @param response
* @return
*/
@PostMapping("/login")
public String login(@RequestParam("username") String username,
@RequestParam("password") String password,
@RequestParam("code") String code,
@RequestParam(value = "rememberMe", required = false) 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("codeMsg", "验证码错误");
return "/site/login";
}
// 凭证过期时间(是否记住我)
int expiredSeconds = rememberMe ? REMEMBER_EXPIRED_SECONDS : DEFAULT_EXPIRED_SECONDS;
// 验证用户名和密码
Map<String, Object> map = userService.login(username, password, expiredSeconds);
if (map.containsKey("ticket")) {
// 账号和密码均正确,则服务端会生成 ticket浏览器通过 cookie 存储 ticket
Cookie cookie = new Cookie("ticket", map.get("ticket").toString());
cookie.setPath(contextPath); // cookie 有效范围
cookie.setMaxAge(expiredSeconds);
response.addCookie(cookie);
return "redirect:/index";
}
else {
model.addAttribute("usernameMsg", map.get("usernameMsg"));
model.addAttribute("passwordMsg", map.get("passwordMsg"));
return "/site/login";
}
}
/**
* 用户登出
* @param ticket 设置凭证状态为无效
* @return
*/
@GetMapping("/logout")
public String logout(@CookieValue("ticket") String ticket) {
userService.logout(ticket);
return "redirect:/login";
}
```
```java
@RequestParam(value = "rememberMe", required = false) boolean rememberMe
```
表示前端没有传来 rememberMe 参数也可,因为用户是可能不勾选记住我的
## 前端
```html
<form class="mt-5" method="post" th:action="@{/login}">
<input type="text" th:class="|form-control ${usernameMsg != null ? 'is-invalid' : ''}|"
th:value="${param.username}"
name="username" required>
<div class="invalid-feedback" th:text="${usernameMsg}"></div>
```