前期准备
创建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>
<script type="text/javascript" th:src="@{/js/jquery.min.js}"></script>
<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>
<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>
<script type="text/javascript" th:src="@{/layer/layer.js}"></script>
<script type="text/javascript" th:src="@{/js/md5.min.js}"></script>
<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(公共返回对象)
- 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;
}
- 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) {
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);
if (!MD5util.backToDB(password, user.getSalt()).equals(user.getPassword()))
throw new GlobalException(RespBeanEnum.LOGIN_ERROR);
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 {
public static String getCookieValue(HttpServletRequest request, String cookieName) {
return getCookieValue(request, cookieName, false);
}
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;
}
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;
}
public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
String cookieValue) {
setCookie(request, response, cookieName, cookieValue, -1);
}
public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
String cookieValue, int cookieMaxage) {
setCookie(request, response, cookieName, cookieValue, cookieMaxage, false);
}
public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
String cookieValue, boolean isEncode) {
setCookie(request, response, cookieName, cookieValue, -1, isEncode);
}
public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
String cookieValue, int cookieMaxage, boolean isEncode) {
doSetCookie(request, response, cookieName, cookieValue, cookieMaxage, isEncode);
}
public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
String cookieValue, int cookieMaxage, String encodeString) {
doSetCookie(request, response, cookieName, cookieValue, cookieMaxage, encodeString);
}
public static void deleteCookie(HttpServletRequest request, HttpServletResponse response,
String cookieName) {
doSetCookie(request, response, cookieName, "", -1, false);
}
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) {
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();
}
}
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) {
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();
}
}
private static final String getDomainName(HttpServletRequest request) {
String domainName = null;
String serverName = request.getRequestURL().toString();
if (serverName == null || serverName.equals(""))
domainName = "";
else {
serverName = serverName.toLowerCase();
if (serverName.startsWith("http://"))
serverName = serverName.substring(7);
int end = serverName.length();
if (serverName.contains("/"))
end = serverName.indexOf("/");
serverName = serverName.substring(0, end);
final String[] domains = serverName.split("\\.");
int len = domains.length;
if (len > 3)
domainName = domains[len - 3] + "." + domains[len - 2] + "." + domains[len - 1];
else if (len <= 3 && len > 1)
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。
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")
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);
return "goodsList";
}
}
登录校验成功后将访问http://localhost:8080/goods/toList 。 创建新页面goodsList.html,添加代码<p th:text="'hello:'+${user.nickname}"></p> 展示用户信息。
|