用户登录模块Java后端实现

 

目录

🧡Util工具

🧡Dao层(数据访问层:CRUD操作)

🧡Sevice层(业务逻辑层:条件判断)

🧡Controller层(控制层:接收请求、响应结果)


💟这里是CS大白话专场,让枯燥的学习变得有趣!

💟没有对象不要怕,我们new一个出来,每天对ta说不尽情话!

💟好记性不如烂键盘,自己总结不如收藏别人!

🧡Util工具

public interface CommunityConstant {
    
    // 默认的登录凭证超时时间 (12小时)
    int DEFAULT_EXPIRED_SECONDS = 3600 * 12;

    // “记住我”状态下凭证超时时间 (100天)
    int REMEMBER_EXPIRED_SECONDS = 3600 * 24 * 100;
}
public class CommunityUtil {

    /**
     * 生成随机字符串
     * @return
     */
    public static String generateUUID() {
        // 去除生成的随机字符串中的 ”-“
        return UUID.randomUUID().toString().replaceAll("-", "");
    }

    /**
     * md5 加密
     * @param key 要加密的字符串
     * @return
     */
    public static String md5(String key) {
        if (StringUtils.isBlank(key)) {
           return null;
        }
        return DigestUtils.md5DigestAsHex(key.getBytes());
    }

}
public class RedisKeyUtil {

    private static final String SPLIT = ":";
    private static final String PREFIX_KAPTCHA = "kaptcha"; // 验证码
    private static final String PREFIX_TICKET = "ticket"; // 登录凭证

     /**
     * 登录验证码(指定这个验证码是针对哪个用户的)
     * @param owner 用户进入登录页面的时候,由于此时用户还未登录,无法通过 id 标识用户
     *              随机生成一个字符串,短暂的存入 cookie,使用这个字符串来标识这个用户
     * @return
     */
    public static String getKaptchaKey(String owner) {
        return PREFIX_KAPTCHA + SPLIT + owner;
    }

    /**
     * 登录凭证
     * @param ticket
     * @return
     */
    public static String getTicketKey(String ticket) {
        return PREFIX_TICKET + SPLIT + ticket;
    }

}

🧡Dao层(数据访问层:CRUD操作)

创建com.jodie.xxx.dao.UserMapper:

@Mapper
public interface UserMapper {
    /**
     * 根据 username 查询用户
     * @param username
     * @return
     */
    User selectByName(String username);
}

在resources/mappers下创建user-mapper.xml写sql查询语句: 

<mapper namespace="com.jodie.xxx.dao.UserMapper">
    <sql id = "selectFields">
        id, username, password, salt, email, type, status, activation_code, header_url, create_time
    </sql>
    <!--根据 Username 查询用户信息-->
    <select id="selectByName" resultType="User">
        select <include refid="selectFields"></include>
        from user
        where username = #{username}
    </select>
</mapper>

🧡Sevice层(业务逻辑层:条件判断)

@Service
public class UserService implements CommunityConstant {

    @Autowired
    private UserMapper userMapper;

     /**
     * 用户登录(为用户创建凭证)
     * @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包含用户id、登录凭证字符串ticket、是否有效、过期时间
        LoginTicket loginTicket = new LoginTicket();
        loginTicket.setUserId(user.getId());
        loginTicket.setTicket(CommunityUtil.generateUUID()); // 随机凭证
        loginTicket.setStatus(0); // 设置凭证状态为有效(当用户登出的时候,设置凭证状态为无效)
        loginTicket.setExpired(new Date(System.currentTimeMillis() + expiredSeconds * 1000)); // 设置凭证到期时间

        // 将登录凭证存入 redis
        String redisKey = RedisKeyUtil.getTicketKey(loginTicket.getTicket());
        // 自动把loginTicket序列化成一个json字符串
        redisTemplate.opsForValue().set(redisKey, loginTicket);

        map.put("ticket", loginTicket.getTicket());

        return map;
    }
}

🧡Controller层(控制层:接收请求、响应结果)

🍠先随机生成当前页面暂时的用户id→cookie

🍠随机生成验证码→redis

🍠输入账号密码验证码,账号密码正确后,比较redis中的验证码与输入的code

🍠验证码正确,登录成功,生成登录凭证ticket→redis/cookie

下次用户想免输入登录时,直接判断cookie中是否含有该用户ticket,有则直接登录,否则重新输入用户名和密码。

@Controller
public class LoginController implements CommunityConstant {

    private static final Logger logger = LoggerFactory.getLogger(LoginController.class);

    @Autowired
    private Producer kaptchaProducer;

    @Autowired
    private RedisTemplate redisTemplate;

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

    /**
     * 生成验证码, 并存入 Redis
     * @param response
     */
    @GetMapping("/kaptcha")
    public void getKaptcha(HttpServletResponse response) {
        // 生成验证码
        String text = kaptchaProducer.createText(); // 生成随机字符
        System.out.println("验证码:" + text);
        BufferedImage image = kaptchaProducer.createImage(text); // 生成图片
        
        // 验证码的归属者
        String kaptchaOwner = CommunityUtil.generateUUID(); // 登录前随机生成用户暂时的id
        Cookie cookie = new Cookie("kaptchaOwner", kaptchaOwner); //将暂时id存入cookie中
        cookie.setMaxAge(60);
        cookie.setPath(contextPath);
        response.addCookie(cookie);
        // 将id对应的验证码存入 redis
        String redisKey = RedisKeyUtil.getKaptchaKey(kaptchaOwner);
        redisTemplate.opsForValue().set(redisKey, text, 60, TimeUnit.SECONDS);

        // 将图片输出给浏览器
        response.setContentType("image/png");
        try {
            ServletOutputStream os = response.getOutputStream();
            ImageIO.write(image, "png", os);
        } catch (IOException e) {
            logger.error("响应验证码失败", e.getMessage());
        }
    }

    /**
     * 用户登录
     * @param username 用户名
     * @param password 密码
     * @param code 验证码
     * @param rememberMe 是否记住我(点击记住我后,凭证的有效期延长)
     * @param model
     * @param kaptchaOwner 通过@CookieValue注解获取cookie中的暂时的id
     * @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, HttpServletResponse response,
                        @CookieValue("kaptchaOwner") String kaptchaOwner) {
        // 检查redis中id对应的验证码
        String kaptcha = null;
        if (StringUtils.isNotBlank(kaptchaOwner)) {
            String redisKey = RedisKeyUtil.getKaptchaKey(kaptchaOwner);
            kaptcha = (String) redisTemplate.opsForValue().get(redisKey);
        }
       // 判断redis中的验证码是否和输入的验证码code一致
        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); //cookie存在时间
            response.addCookie(cookie); // 服务器返回给浏览器cookie以便下次判断
            return "redirect:/index"; // 重定向
        }
        else {
            model.addAttribute("usernameMsg", map.get("usernameMsg"));
            model.addAttribute("passwordMsg", map.get("passwordMsg"));
            return "/site/login";
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值