仿牛客学习论坛之登录部分

用户登录部分

本部分代码来源于牛客网官方的仿牛客论坛项目,仅供个人学习和探讨使用。

文章目录

  • 用户登录部分
      • 1 创建数据库
      • 2 编写实体类
      • 3 编写dao层
      • 4 测试dao层代码
      • 5 在util包中设置登录凭证时间
      • 6 编写service业务层
      • 7 编写controller层
      • 8 编写前端代码

1 创建数据库

DROP TABLE IF EXISTS `login_ticket`;
SET character_set_client = utf8mb4 ;
CREATE TABLE `login_ticket` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NOT NULL,
  `ticket` varchar(45) NOT NULL,
  `status` int(11) DEFAULT '0' COMMENT '0-有效; 1-无效;',
  `expired` timestamp NOT NULL,
  PRIMARY KEY (`id`),
  KEY `index_ticket` (`ticket`(20))
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

相关参数说明:
id:标识唯一的登录凭证,自增
user_id:外键,通过这个值找到对应的用户
ticket:登录凭证,生成随机的字符串,唯一
status:登录状态,0-有效,1-无效
expired:登录到期时间

2 编写实体类

public class LoginTicket {
    private int id;
    private int userId;
    private String ticket; // 登录凭证
    private int status;
    private Date expired;
	// 省略 getter setter 和 toString 方法 
}

3 编写dao层

编写sql语句。

复制代码记得更改包名。

package com.zcq.community.dao;

import com.zcq.community.entity.LoginTicket;
import org.apache.ibatis.annotations.*;

@Mapper
public interface LoginTicketMapper {

    // 使用sql语句注解会比较直观,但是复杂的sql语句推荐使用xml
    @Insert({
            "insert into login_ticket(user_id, ticket, status, expired) ",
            "values(#{userId}, #{ticket}, #{status}, #{expired}) "
    })
    @Options(useGeneratedKeys = true, keyProperty = "id") // 是否启用主键,将主键注入到哪个属性中
    int insertLoginTicket(LoginTicket loginTicket);

    /**
     * 利用核心数据ticket在服务端找到用户信息进行查询
     * @param ticket
     * @return
     */
    @Select({
            "select id, user_id, ticket, status, expired ",
            "from login_ticket where ticket = #{ticket} "
    })
    LoginTicket selectByTicket(String ticket);

    /**
     * 修改登录状态, 退出登录时用
     * @param ticket
     * @param status
     * @return
     */
    @Update({
            "update login_ticket set status = #{status} where ticket = #{ticket} ",
    })
    int updateStatus(String ticket, int status);

    // 通过注解方式也可以嵌套动态sql语句,比如if标签
        /*""*/

}

4 测试dao层代码

由于注解sql语句的方式IDEA无法进行识别,如果出现语法错误会导致后续无法进行。

package com.zcq.community.dao;

import com.zcq.community.CommunityApplication;
import com.zcq.community.entity.LoginTicket;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.Date;

@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration(classes = CommunityApplication.class)
public class LoginMapperTests {

    @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);
    }
}

5 在util包中设置登录凭证时间

package com.zcq.community.util;

public interface CommunityConstant {

    // 默认状态的登录凭证超时时间
    int DEFAULT_EXPIRED_SECONDS = 3600 * 12;

    // 记住状态下的登录凭证超时时间
    int REMEMBER_EXPIRED_SECONDS = 3600 * 24 * 100;
    
}

6 编写service业务层

放入 userService 中是因为登录操作与用户相关。

package com.zcq.community.service;

import com.zcq.community.dao.LoginTicketMapper;
import com.zcq.community.dao.UserMapper;
import com.zcq.community.entity.LoginTicket;
import com.zcq.community.entity.User;
import com.zcq.community.util.CommunityConstant;
import com.zcq.community.util.CommunityUtil;
import com.zcq.community.util.MailClient;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;

@Service
public class UserService implements CommunityConstant {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private LoginTicketMapper loginTicketMapper;

	// 此处省略了userService的其他代码

    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;
    }
}

7 编写controller层

package com.zcq.community.controller;

import com.google.code.kaptcha.Producer;
import com.zcq.community.service.UserService;
import com.zcq.community.util.CommunityConstant;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.Map;

@Controller
public class LoginController implements CommunityConstant {

    @Autowired
    private UserService userService;

    @Value("${server.servlet.context-path}")
    private String contextPath;

    // 说明:特殊对象, 比如 user, springmvc 会将值装到 model 层里, 龚前端页面获取
    // 其余参数可以从 request 中取值, 或者存到 model 层里, 从 model 层里取值
    // 这里选用 post 请求的原因是我们需要传入一个表单
    @RequestMapping(path = "/login", method = RequestMethod.POST)
    public String login(String username, String password, String code, boolean rememberme,
                        Model model, HttpSession session, HttpServletResponse response) {
        // 1 判断验证码正确与否
        String kaptcha = (String) session.getAttribute("kaptcha");
        // 后台的验证码为空 或 用户传入的验证码为空 或 忽略大小写后的值不等
        if(StringUtils.isBlank(kaptcha) || StringUtils.isBlank(code) || !kaptcha.equalsIgnoreCase(code)) {
            model.addAttribute("codeMsg", "验证码不正确");
            return "/site/login";
        }
        // 2 检查账号,密码
        int expiredSeconds = rememberme ? REMEMBER_EXPIRED_SECONDS : DEFAULT_EXPIRED_SECONDS;
        Map<String, Object> map = userService.login(username, password, expiredSeconds);
        if(map.containsKey("ticket")) {
            Cookie cookie = new Cookie("ticket", map.get("ticket").toString());
            cookie.setPath(contextPath);
            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";
        }

    }

}

8 编写前端代码

说明:html 代码中的设计部分已删除,此处指展示和后端业务交互的部分。

DOCTYPE html>
<html lang="en" xmlns="https://www.thymeleaf.org">
	<head>
		<meta charset="UTF-8">
		<title>title>
	head>
	<body>
		<form method="post" th:action="@{/login}">
			<div>
				<label>账号:label>
				<div>
					
					
					<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" th:text="${usernameMsg}">
						该账号不存在
					div>
				div>
			div>
			
			<div>
				<label for="password">密码:label>
				<div>
					<input type="password" th:class="|form-control ${passwordMsg!=null?'is-invalid':''}|"
						   th:value="${param.password}"
						   id="password" name="password" placeholder="请输入您的密码" required>
					<div th:text="${passwordMsg}">
						密码长度不能小于8位!
					div>							
				div>
			div>
			
			<div>
				<label for="verifycode">验证码:label>
				<div>
					<input type="text" th:class="|form-control ${codeMsg!=null?'is-invalid':''}|"
						   id="verifycode" name="code" placeholder="请输入验证码 不区分大小写">
					<div th:text="${codeMsg}">
						验证码不正确!
					div>
				div>
				<div>
					<img th:src="@{/kaptcha}">
					<a href="javascript:refresh_kaptcha();">刷新验证码a>
				div>
			div>
			<div>
				<div>
					<input type="checkbox" id="remember-me" name="rememberme"
						   th:checked="${param.rememberme}"> 
					<label for="remember-me">记住我label>
					<a href="forget.html">忘记密码?a>
				div>
			div>			
			<div class="form-group row mt-4">
				<button type="submit">立即登录button>
			div>
		form>
	body>
html>

代码编写完毕,可以进行测试了。

你可能感兴趣的:(java,spring,boot,spring)