开发登录、退出功能
- 访问登录页面
- 点击顶部区域内的链接,打开登录页面
- 登录
- 验证账号、密码、验证码。
- 成功时,生成登录凭证,发放给客户端。
- 失败时,跳转回登录页。
- 退出
- 将登录凭证修改为失效状态。-跳转至网站首页。
- 将登录凭证修改为失效状态。-跳转至网站首页。
- 创建登录凭证实体类
package com.nowcoder.community.entity;
import java.util.Date;
public class LoginTicket {
private int id;
private int userId;
private String ticket;
private int status;
private Date expired;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public String getTicket() {
return ticket;
}
public void setTicket(String ticket) {
this.ticket = ticket;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public Date getExpired() {
return expired;
}
public void setExpired(Date expired) {
this.expired = expired;
}
@Override
public String toString() {
return "LoginTicket{" +
"id=" + id +
", userId=" + userId +
", ticket='" + ticket + '\'' +
", status=" + status +
", expired=" + expired +
'}';
}
}
- 实现数据访问层dao
package com.nowcoder.community.dao;
import com.nowcoder.community.entity.LoginTicket;
public interface LoginTicketMapper {
//增加凭证数据,login_ticket中有ticket要发给客户端浏览器保存,
//其他数据,服务端存,
//客户端用cockie存入数据后,再次访问服务器,会将cockie发送给服务端,利用ticket查到整条数据,知道用户,用户id等信息
int insertLoginTicket(LoginTicket loginTicket);
LoginTicket selectByTicket(String ticket);
//修改状态
int updateStatus(String ticket,int status);
}
- 编写实现类,
之前用的是mapper中的xml配置文件实现,还有一种方式可以 以注解的方式实现,
package com.nowcoder.community.dao;
import com.nowcoder.community.entity.LoginTicket;
import org.apache.ibatis.annotations.*;
@Mapper
public interface LoginTicketMapper {
//增加凭证数据,login_ticket中有ticket要发给客户端浏览器保存,
//其他数据,服务端存,
//客户端用cockie存入数据后,再次访问服务器,会将cockie发送给服务端,利用ticket查到整条数据,知道用户,用户id等信息
@Insert({
"insert into login_ticket (user_id,ticket,status,expired) ",
"values(#{userId},#{ticket},#{status},#{expired})"
})//把多个字符串拼成一个sql,注意每写一个字符串,后面加一个“) ”;缺点是:若较为复杂的sql,阅读起来不太方便
@Options(useGeneratedKeys = true,keyProperty = "id")//将自动生成的主键注入到loginTicket中。
int insertLoginTicket(LoginTicket loginTicket);
@Select({
"select id,user_id,ticket,status,expired ",
"from login_ticket where ticket=#{ticket} "
})
LoginTicket selectByTicket(String ticket);
//修改状态,还可以写动态sql,但一定要在script中引用
@Update({
"<script>",
"update login_ticket set status=#{status} where ticket=#{ticket}",
"<if test=\"ticket!=null\">",
" and 1=1",
"</if>",
"</script>"
})
int updateStatus(String ticket,int status);
}
但这个内容出错概率大,所以需要测试
//注入
@Autowired
private LoginTicketMapper loginTicketMapper;
@Test
public void testInsertLoginTicket() {
LoginTicket loginTicket = new LoginTicket();
loginTicket.setUserId(101);
loginTicket.setTicket("abc");
loginTicket.setStatus(0);
loginTicket.setExpired(new Date(System.currentTimeMillis() + 1000 * 60 * 10));
loginTicketMapper.insertLoginTicket(loginTicket);
}
@Test
public void testSelectLoginTicket() {
LoginTicket loginTicket = loginTicketMapper.selectByTicket("abc");
System.out.println(loginTicket);
loginTicketMapper.updateStatus("abc", 1);
loginTicket = loginTicketMapper.selectByTicket("abc");
System.out.println(loginTicket);
}
- 开发业务层,支持登录的业务
@Autowired
private LoginTicketMapper loginTicketMapper;
public Map<String, Object> login(String username, String password, long expiredSeconds) {
//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;
}
public void logout(String ticket) {
loginTicketMapper.updateStatus(ticket, 1);
}
public LoginTicket findLoginTicket(String ticket) {
return loginTicketMapper.selectByTicket(ticket);
}
public int updateHeader(int userId, String headerUrl) {
return userMapper.updateHeader(userId, headerUrl);
}
- 编写表现层的代码(LoginController)
@Value("${server.servlet.context-path}")//注入固定的值
private String contextPath;
@RequestMapping(path = "/login", method = RequestMethod.POST)//请求名路径可以重复,但请求方法不能重复
public String login(String username,String password,String code,boolean rememberme,
Model model,HttpSession session,HttpServletResponse response){
//session存对比的验证码,HttpServletResponse取cookie存在客户端
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;//CommunityConstant中的属性
Map<String, Object> map = userService.login(username, password, expiredSeconds);
if (map.containsKey("ticket")) {
Cookie cookie = new Cookie("ticket", map.get("ticket").toString());//给客户端发送一个cockie,存储
cookie.setPath(contextPath);//凭证有效的路径范围,整个项目,不要写死,写成一个参数。
cookie.setMaxAge(expiredSeconds);
response.addCookie(cookie);//cockie发送给页面
return "redirect:/index";//重定向到首页
} else {//错误时,回到页面,将用户的错误信息带回页面
model.addAttribute("usernameMsg", map.get("usernameMsg"));
model.addAttribute("passwordMsg", map.get("passwordMsg"));
return "/site/login";
}
}
- 设置登录页面login.html
发送请求时需要这样的设置,但要注意,名字要与controller中的一样才能识别。
错误信息的展示
- 人为加到model中
- 参数是存在request对象中的,可以通过request.getParameter也能得到这些参数
- 退出登录
- 将凭证改为失效状态;
- 跳转到首页登录页面
在UserService中添加
public void logout(String ticket) {
loginTicketMapper.updateStatus(ticket, 1);//1表示无效
}
LoginController中添加
@RequestMapping(path = "/logout", method = RequestMethod.GET)
public String logout(@CookieValue("ticket") String ticket) {
userService.logout(ticket);
return "redirect:/login";
}
配置链接
在Index.html中配置
重启刷新即可;