Spring boot求生之路[2]—登录功能

前期准备

创建LoginController

@Controller
@RequestMapping("/login")
@Slf4j
public class LoginController {
    @Autowired
    private IUserService userService;

    @RequestMapping("/toLogin")    // 跳转到登录页面
    public String toLogin() {
        return "login";
    }
}

准备登录页面(前端)

静态资源放在static包下,页面放在templates包下
新建登录页面login.html

<!DOCTYPE html>
<html lang="en"
      xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
    <!-- jquery -->
    <script type="text/javascript" th:src="@{/js/jquery.min.js}"></script>
    <!-- bootstrap -->
    <link rel="stylesheet" type="text/css" th:href="@{/bootstrap/css/bootstrap.min.css}"/>
    <script type="text/javascript" th:src="@{/bootstrap/js/bootstrap.min.js}"></script>
    <!-- jquery-validator -->
    <script type="text/javascript" th:src="@{/jquery-validation/jquery.validate.min.js}"></script>
    <script type="text/javascript" th:src="@{/jquery-validation/localization/messages_zh.min.js}"></script>
    <!-- layer -->
    <script type="text/javascript" th:src="@{/layer/layer.js}"></script>
    <!-- md5.js -->
    <script type="text/javascript" th:src="@{/js/md5.min.js}"></script>
    <!-- common.js -->
    <script type="text/javascript" th:src="@{/js/common.js}"></script>
</head>
<body>
<form name="loginForm" id="loginForm" method="post" style="width:50%; margin:0 auto">
    <h2 style="text-align:center; margin-bottom: 20px">用户登录</h2>
    <div class="form-group">
        <div class="row">
            <label class="form-label col-md-4">请输入手机号码</label>
            <div class="col-md-5">
                <input id="mobile" name="mobile" class="form-control" type="text" placeholder="手机号码" required="true"
                       minlength="11" maxlength="11"/>
            </div>
            <div class="col-md-1">
            </div>
        </div>
    </div>
    <div class="form-group">
        <div class="row">
            <label class="form-label col-md-4">请输入密码</label>
            <div class="col-md-5">
                <input id="password" name="password" class="form-control" type="password" placeholder="密码"
                       required="true" minlength="6" maxlength="16"/>
            </div>
        </div>
    </div>
    <div class="row">
        <div class="col-md-5">
            <button class="btn btn-primary btn-block" type="reset" onclick="reset()">重置</button>
        </div>
        <div class="col-md-5">
            <button class="btn btn-primary btn-block" type="submit" onclick="login()">登录</button>
        </div>
    </div>
</form>
</body>
<script>
    function login() {
        $("#loginForm").validate({
            submitHandler: function (form) {
                doLogin();
            }
        });
    }
    function doLogin() {
        g_showLoading();
        var inputPass = $("#password").val();
        var salt = g_passsword_salt;
        var str = "" + salt.charAt(0) + salt.charAt(2) + inputPass + salt.charAt(5) + salt.charAt(4);
        var password = md5(str);
        $.ajax({
            url: "/login/doLogin",
            type: "POST",
            data: {
                mobile: $("#mobile").val(),
                password: password
            },
            success: function (data) {
                layer.closeAll();
                if (data.code == 200) {
                    layer.msg("成功");
                    window.location.href="/goods/toList";
                } else {
                    layer.msg(data.message);
                }
            },
            error: function () {
                layer.closeAll();
            }
        });
    }
</script>
</html>

其中,用户提交登录后,$("#loginForm").validate() 会执行登录校验,检查用户提交的手机号、密码是否符合规范。function doLogin() 部分会对用户端输入的明文密码执行第一次盐值加密。登录成功后会跳转到 /goods/toList路径下。

创建公共返回对象类

在vo包下创建RespBeanEnum(公共返回对象枚举)、RespBean(公共返回对象)

  1. RespBeanEnum ——包含通用和功能模块专用状态码
@Getter
@ToString
@AllArgsConstructor
public enum RespBeanEnum {
    // 通用
    SUCCESS(200, "SUCCESS"),
    ERROR(500, "服务端异常"),

    // 登录模块专用
    LOGIN_ERROR(500210, "用户名或密码不正确"),
    MOBILE_ERROR(500211, "手机号格式不正确"),
    BIND_ERROR(500212, "参数校验异常");

    private final Integer code;
    private final String message;
}
  1. RespBean ——包含成功和失败的返回结果
@Data
@NoArgsConstructor
@AllArgsConstructor
public class RespBean {
    private long code;
    private String message;
    private Object obj;
    public static RespBean success() {
        return new RespBean(RespBeanEnum.SUCCESS.getCode(), RespBeanEnum.SUCCESS.getMessage(), null);
    }

    public static RespBean success(Object obj) {
        return new RespBean(RespBeanEnum.SUCCESS.getCode(), RespBeanEnum.SUCCESS.getMessage(), obj);
    }

    public static RespBean error(RespBeanEnum respBeanEnum) {
        return new RespBean(respBeanEnum.getCode(), respBeanEnum.getMessage(), null);
    }

    public static RespBean error(RespBeanEnum respBeanEnum, Object obj) {
        return new RespBean(respBeanEnum.getCode(), respBeanEnum.getMessage(), obj);
    }
}

这里error使用RespBeanEnum respBeanEnum参数而success不使用的原因:成功只有唯一的状态码200,而失败有多种具体的状态码。

功能实现

跳转登录页

启动项目,访问http://localhost:8080/login/toLogin,调用@RequestMapping("/toLogin"),跳转到登录页面。
在这里插入图片描述
输入手机号13012345678,密码111111,后台可接收该参数,以及加密过一次的密码。
在这里插入图片描述

创建登录参数类

在vo包下创建LoginVo,接收从前端传来的参数—mobile、password。

@Data
public class LoginVo {
    @NotNull
    @IsMobile(required = true)    // 自定义注解
    private String mobile;

    @NotNull
    @Length(min = 32)
    private String password;
}

创建登录方法

LoginController类中创建doLogin方法

    @RequestMapping("/doLogin")    // 执行登录操作
    @ResponseBody
    public RespBean doLogin(@Valid LoginVo loginVo, HttpServletRequest request, HttpServletResponse response) {    // @Valid对参数校验
        log.info("{}", loginVo);
        return userService.doLogin(loginVo, request, response);
    }

编写登录逻辑

loginController中注入service层接口信息

@Autowired
    private IUserService userService;

service层的IUserService接口中定义方法

public interface IUserService extends IService<User> {
    RespBean doLogin(LoginVo loginVo, HttpServletRequest request, HttpServletResponse response);
}

创建实现类UserServiceImpl

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
    @Autowired
    private UserMapper userMapper;

    @Override
    public RespBean doLogin(LoginVo loginVo, HttpServletRequest request, HttpServletResponse response) {
        String mobile = loginVo.getMobile();
        String password = loginVo.getPassword();
        // 查询数据库 根据手机号查询用户是否存在
        User user = userMapper.selectById(mobile);    
        if (user == null)    // 判断用户是否存在,不存在则抛出异常
            throw new GlobalException(RespBeanEnum.LOGIN_ERROR);
        // 检查密码,密码错误则抛出异常,backToDB为第二次加密,将加密后的结果与数据库中的密码比较
        if (!MD5util.backToDB(password, user.getSalt()).equals(user.getPassword()))  
            throw new GlobalException(RespBeanEnum.LOGIN_ERROR);
        // 生成cookie
        String ticket = UUIDUtil.uuid();
        request.getSession().setAttribute(ticket, user);
        CookieUtil.setCookie(request, response, "userTicket", ticket);
        return RespBean.success();   // 没有以上问题则成功
    }
}

参数校验

在utils包下创建ValidatorUtil

public class ValidatorUtil {
    // 手机号正则表达
    private static final Pattern mobile_pattern = Pattern.compile("[1]([3-9])[0-9]{9}$");  
    public static boolean isMobile(String mobile) {   // 判断手机号是否符合规范
        if (StringUtils.isEmpty(mobile))   // 手机号为空
            return false;
        Matcher matcher = mobile_pattern.matcher(mobile);
        return matcher.matches();
    }
}

导入validation依赖。

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>

LoginController类中的doLogin方法参数添加注解@Valid LoginVo loginVo
validator包下创建自定义注解IsMobile

@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = {IsMobileValidator.class})
public @interface IsMobile {
    boolean required() default true;   // 手机号必填
    String message() default "手机号格式错误";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

@Constraint(validatedBy = {IsMobileValidator.class})表示自定义校验规则
vo包下创建IsMobileValidator

public class IsMobileValidator implements ConstraintValidator<IsMobile, String> {
    private boolean required = false;   // 是否必填
    @Override
    public void initialize(IsMobile constraintAnnotation) {
        required = constraintAnnotation.required();
    }
    @Override
    public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
        if (required)    // 必填时检验手机号格式
            return ValidatorUtil.isMobile(value);
        else if (StringUtils.isEmpty(value))   // 非必填,未填时直接放行
            return true;
        else
            return ValidatorUtil.isMobile(value);  // 非必填,填了需要检验
    }
}

创建完成后在vo包下的LoginVo类中的mobile字段上添加自定义注解@IsMobile(required = true),表示手机号必填,收到前端的参数时校验。

自定义异常

exception包下定义通用异常GlobalException

@Data
@NoArgsConstructor
@AllArgsConstructor
public class GlobalException extends RuntimeException{
    private RespBeanEnum respBeanEnum;
}

exception包下定义处理异常方法GlobalExceptionHandler

@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(Exception.class)
    public RespBean ExceptionHandler(Exception e) {
        if (e instanceof GlobalException) {
            GlobalException ex = (GlobalException) e;
            return RespBean.error(ex.getRespBeanEnum());
        } else if (e instanceof BindException) {
            BindException ex = (BindException) e;
            RespBean respBean = RespBean.error(RespBeanEnum.BIND_ERROR);
            respBean.setMessage("参数校验异常:" + ex.getBindingResult().getAllErrors().get(0).getDefaultMessage());
            return respBean;
        }
        return RespBean.error(RespBeanEnum.ERROR);
    }
}

若抛出的异常为通用异常,则强制转换为GlobalException,获取通用错误码。
在这里插入图片描述

若抛出的异常为绑定异常,则强制转换为BindException,获取登录模块错误码。
在这里插入图片描述

完善功能

获取用户信息

判断用户是否登录成功
utils包下创建CookieUtil工具类,用于生成Cookie

public final class CookieUtil {
    /**
     * 得到Cookie的值, 不编码
     *
     * @param request
     * @param cookieName
     * @return
     */
    public static String getCookieValue(HttpServletRequest request, String cookieName) {
        return getCookieValue(request, cookieName, false);
    }
    /**
     * 得到Cookie的值,
     *
     * @param request
     * @param cookieName
     * @return
     */
    public static String getCookieValue(HttpServletRequest request, String cookieName, boolean isDecoder) {
        Cookie[] cookieList = request.getCookies();
        if (cookieList == null || cookieName == null)
            return null;
        String retValue = null;
        try {
            for (int i = 0; i < cookieList.length; i++) {
                if (cookieList[i].getName().equals(cookieName)) {
                    if (isDecoder)
                        retValue = URLDecoder.decode(cookieList[i].getValue(), "UTF-8");
                    else
                        retValue = cookieList[i].getValue();
                    break;
                }
            }
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return retValue;
    }
    /**
     * 得到Cookie的值,
     *
     * @param request
     * @param cookieName
     * @return
     */
    public static String getCookieValue(HttpServletRequest request, String cookieName, String encodeString) {
        Cookie[] cookieList = request.getCookies();
        if (cookieList == null || cookieName == null)
            return null;
        String retValue = null;
        try {
            for (int i = 0; i < cookieList.length; i++) {
                if (cookieList[i].getName().equals(cookieName)) {
                    retValue = URLDecoder.decode(cookieList[i].getValue(), encodeString);
                    break;
                }
            }
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return retValue;
    }
    /**
     * 设置Cookie的值 不设置生效时间默认浏览器关闭即失效,也不编码
     */
    public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
                                 String cookieValue) {
        setCookie(request, response, cookieName, cookieValue, -1);
    }
    /**
     * 设置Cookie的值 在指定时间内生效,但不编码
     */
    public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
                                 String cookieValue, int cookieMaxage) {
        setCookie(request, response, cookieName, cookieValue, cookieMaxage, false);
    }
    /**
     * 设置Cookie的值 不设置生效时间,但编码
     */
    public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
                                 String cookieValue, boolean isEncode) {
        setCookie(request, response, cookieName, cookieValue, -1, isEncode);
    }
    /**
     * 设置Cookie的值 在指定时间内生效, 编码参数
     */
    public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
                                 String cookieValue, int cookieMaxage, boolean isEncode) {
        doSetCookie(request, response, cookieName, cookieValue, cookieMaxage, isEncode);
    }
    /**
     * 设置Cookie的值 在指定时间内生效, 编码参数(指定编码)
     */
    public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
                                 String cookieValue, int cookieMaxage, String encodeString) {
        doSetCookie(request, response, cookieName, cookieValue, cookieMaxage, encodeString);
    }
    /**
     * 删除Cookie带cookie域名
     */
    public static void deleteCookie(HttpServletRequest request, HttpServletResponse response,
                                    String cookieName) {
        doSetCookie(request, response, cookieName, "", -1, false);
    }
    /**
     * 设置Cookie的值,并使其在指定时间内生效
     *
     * @param cookieMaxage cookie生效的最大秒数
     */
    private static final void doSetCookie(HttpServletRequest request, HttpServletResponse response,
                                          String cookieName, String cookieValue, int cookieMaxage, boolean isEncode) {
        try {
            if (cookieValue == null)
                cookieValue = "";
            else if (isEncode)
                cookieValue = URLEncoder.encode(cookieValue, "utf-8");
            Cookie cookie = new Cookie(cookieName, cookieValue);
            if (cookieMaxage > 0)
                cookie.setMaxAge(cookieMaxage);
            if (null != request) {// 设置域名的cookie
                String domainName = getDomainName(request);
                System.out.println(domainName);
                if (!"localhost".equals(domainName))
                    cookie.setDomain(domainName);
            }
            cookie.setPath("/");
            response.addCookie(cookie);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * 设置Cookie的值,并使其在指定时间内生效
     *
     * @param cookieMaxage cookie生效的最大秒数
     */
    private static final void doSetCookie(HttpServletRequest request, HttpServletResponse response,
                                          String cookieName, String cookieValue, int cookieMaxage, String encodeString) {
        try {
            if (cookieValue == null)
                cookieValue = "";
            else
                cookieValue = URLEncoder.encode(cookieValue, encodeString);
            Cookie cookie = new Cookie(cookieName, cookieValue);
            if (cookieMaxage > 0)
                cookie.setMaxAge(cookieMaxage);
            if (null != request) {// 设置域名的cookie
                String domainName = getDomainName(request);
                System.out.println(domainName);
                if (!"localhost".equals(domainName))
                    cookie.setDomain(domainName);
            }
            cookie.setPath("/");
            response.addCookie(cookie);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * 得到cookie的域名
     */
    private static final String getDomainName(HttpServletRequest request) {
        String domainName = null;
        // 通过request对象获取访问的url地址
        String serverName = request.getRequestURL().toString();
        if (serverName == null || serverName.equals(""))
            domainName = "";
        else {
            // 将url地下转换为小写
            serverName = serverName.toLowerCase();
            // 如果url地址是以http://开头  将http://截取
            if (serverName.startsWith("http://"))
                serverName = serverName.substring(7);
            int end = serverName.length();
            // 判断url地址是否包含"/"
            if (serverName.contains("/"))
                //得到第一个"/"出现的位置
                end = serverName.indexOf("/");
            // 截取
            serverName = serverName.substring(0, end);
            // 根据"."进行分割
            final String[] domains = serverName.split("\\.");
            int len = domains.length;
            if (len > 3)
                // www.xxx.com.cn
                domainName = domains[len - 3] + "." + domains[len - 2] + "." + domains[len - 1];
            else if (len <= 3 && len > 1)
                // xxx.com or xxx.cn
                domainName = domains[len - 2] + "." + domains[len - 1];
            else
                domainName = serverName;
        }
        if (domainName != null && domainName.indexOf(":") > 0) {
            String[] ary = domainName.split("\\:");
            domainName = ary[0];
        }
        return domainName;
    }

utils包下创建UUIDUtil工具类,用于生成UUID

public class UUIDUtil {
    public static String uuid() {
        return UUID.randomUUID().toString().replace("-", "");
    }
}

UserServiceImpl类的doLogin方法中添加以下代码,将uuid和当前用户对象user存在Session中,生成当前用户的Cookie

        // 生成cookie
        String ticket = UUIDUtil.uuid();
        request.getSession().setAttribute(ticket, user);
        CookieUtil.setCookie(request, response, "userTicket", ticket);

接收用户信息

login.html中添加

if (data.code == 200) {
                    layer.msg("成功");
                    window.location.href="/goods/toList";
                } else {
                    layer.msg(data.message);
                }

controller包下创建GoodsController,执行页面跳转,并将参数传递到前端。

@Controller
@RequestMapping("/goods")
public class GoodsController {
    @RequestMapping("/toList")                              // userTicket 为自定义的cookie
    public String toList(HttpSession session, Model model, @CookieValue("userTicket") String ticket) {
        if (StringUtils.isEmpty(ticket))
            return "login";
        User user = (User) session.getAttribute(ticket);
        if (user == null)
            return "login";
        model.addAttribute("user", user);   // 将user传递到前端页面
        return "goodsList";
    }
}

登录校验成功后将访问http://localhost:8080/goods/toList
创建新页面goodsList.html,添加代码<p th:text="'hello:'+${user.nickname}"></p>展示用户信息。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值