Java Practical learning of seckill system —— Implement user login

<> User login

Implementation of user login steps :

<>1. Design of database

The field of database design is mainly the user's mobile number , Nickname? , password ,salt, head portrait , Registration time , Last login time , Number of logins , Details are as follows :
CREATE TABLE `miaosha_user` ( `id` bigint(20) NOT NULL COMMENT ' user ID, phone number ',
`nickname` varchar(255) NOT NULL, `password` varchar(32) DEFAULT NULL COMMENT
'MD5(MD5(pass Plaintext + fixed salt) salt)', `salt` varchar(10) DEFAULT NULL, `head`
varchar(128) DEFAULT NULL COMMENT ' head portrait , Cloud storage ID', `register_date` datetime DEFAULT
NULL COMMENT ' Registration time ', `last_login_date` datetime DEFAULT NULL COMMENT ' Login time on ',
`login_count` int(11) DEFAULT '0' COMMENT ' Number of logins ', PRIMARY KEY (`id`) )
<>2. twice MD5

Mainly for safety , Prevent password disclosure ; for the first time MD5 Is to prevent the user's clear text password from being transmitted on the network , Get the password from someone else's bag ; Second time MD5 To prevent password reverse cracking after database theft , Make sure the password doesn't leak .

General implementation process : After the user enters the login information and submits the login , It will be implemented for the first time in the front desk MD5 encryption , Then it will store the attached machine in the database salt Take it out and splice it with the password for the second time MD5 encryption , After that, we will judge whether it is the second time in the database MD5 Same password .

Introduce dependency :
<dependency> <groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId> </dependency> <dependency>
<groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId>
<version>3.6</version> </dependency>
Create toolkit write Md5 Tools (Md5Util):
for the first time MD5( Plaintext +salt): Use clear text +salt To ensure the security of password ,salt It is fixed and convenient for server operation , The code is as follows :
private static final String SALT = "1a2b3c4d"; public static String md5(String
src){ return DigestUtils.md5Hex(src); } // for the first time MD5 encryption : Plaintext +salt Mixed splicing of public static
String inputPassToFormPass(String inputPass){ String src = "" + SALT.charAt(0)
+ SALT.charAt(2)+ inputPass + SALT.charAt(5)+ SALT.charAt(4); return
md5(src);// Such as clear text password 123456 After this encryption , The result of being intercepted and interpreted by others will be 12123456c3 }
The second time MD5( User input + random salt): Use user input password + Random salt,salt Is written to the database , The code is as follows :
// The second time MDS encryption : User input password + random salt public static String formPassToDbPass(String
formPass, String salt){ String src = "" + salt.charAt(0) + salt.charAt(2)+
formPass + salt.charAt(5)+ salt.charAt(4); return
md5(src);// When the password is interpreted after the database is stolen, the real password is not encrypted in clear text once }
Put plaintext twice directly MD5 Store data :
// Directly convert user password to password in database public static String inputPassToDbPass(String inputPass,
String salt){ String formPass = inputPassToFormPass(inputPass); String dbPass =
formPassToDbPass(formPass, salt); return dbPass; }
Specific implementation :
stay Controller Created in package LoginController class , There are two main methods :
to_login( adopt thymeleat Template back to src/main/resources/templates/login.html Display login interface )
do_login( Responsible for parameter comparison of submitted data );

login.html Main code :
<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); // for the first time Md5 encryption $.ajax({
url: "/login/do_login", // adopt ajax Submit to do_login method , There are two parameters mobile And password( The value is Md5 Encrypted )
type: "POST", data:{ mobile:$("#mobile").val(), password: password },
success:function(data){ layer.closeAll(); if(data.code == 0){ layer.msg(" success ");
window.location.href="/goods/to_list"; }else{ layer.msg(data.msg); } },
error:function(){ layer.closeAll(); } }); } </script>
do_login() The parameter comparison in is mainly to judge whether the mobile number and password are not empty (StringUtils.isEmpty() Method judgment )
Whether the mobile number format is correct ( establish ValidatorUtil Class to judge ) The code details are as follows :
do_login Parameter verification code in :
if(StringUtils.isEmpty(mobile)){ return CodeMsg.MOBILE_EMPTY; }
if(StringUtils.isEmpty(password)){ return CodeMsg.PASSWORD_EMPTY; }
if(!ValidatorUtil.isMobile(mobile)){ return CodeMsg.MOBILE_ERROR; }
Verify phone format ValidatorUtil class :
public class ValidatorUtil { private static Pattern MOBILE_PATTERN =
Pattern.compile("1\\d{10}"); public static boolean isMobile(String mobile){
if(StringUtils.isEmpty(mobile)){ return false; } Matcher matcher =
MOBILE_PATTERN.matcher(mobile); return matcher.matches(); } }
stay MiaoshaUserService Determine whether the mobile phone number is empty and verify the password
stay LoginVo Create corresponding member variable , stay MiaoshaUserDao Chinese writing Mapper By way of annotation sql Statement to check mobile

<>3. JSR303 Parameter checksum global exception handler

Optimize code , The first optimization will LoginController in do_login() Passed the parameter verification of JSR303 Parameter verification is realized by annotation . The second optimization is to define the global exception handler to display the exception information to the user friendly and modify the business logic method so that it can return to express the meaning of the business method .

Introduce dependency :
<dependency> <groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-validation</artifactId> </dependency>
To implement login , Precede the parameter to be verified with @valid Annotation is
@RequestMapping("/do_login") @ResponseBody public Result<CodeMsg>
doLogin(@Valid LoginVo loginVo) {;
miaoshaUserService.login(loginVo); return Result.success(CodeMsg.SUCCESS);
Then annotate the variables to be verified. The verification is not empty , Verify length and phone number format
public class LoginVo { @NotNull @IsMobile private String mobile; @NotNull
@Length(min=32) private String password;
To verify the phone number format, you need to customize a verifier @IsMobile( Reference note @NotNull), establish IsMobile and IsMobileValidator class , The code is as follows :

IsMobile class :
@Retention(RUNTIME) @Documented @Constraint(validatedBy =
IsMobileValidator.class ) public @interface IsMobile { boolean required()
default true; String message() default " Incorrect format of mobile number "; Class<?>[] groups() default
{ }; Class<? extends Payload>[] payload() default { }; }
IsMobileValidator class :
public class IsMobileValidator implements ConstraintValidator<IsMobile,
String> { private boolean required; @Override public void initialize(IsMobile
isMobile) { required = isMobile.required(); } @Override public boolean
isValid(String value, ConstraintValidatorContext context) { if (!required &&
StringUtils.isEmpty(value)) { return true; } return
ValidatorUtil.isMobile(value); } }

In order to display exception information in browser page friendly , definition GlobalExceptionHandler. class , add to exceptionHandler method , adopt @ExceptionHandler(value
= Exception.class)
Block all exceptions , Method body first intercepts binding exception , Return specific errors and CodeMsg Back after splicing , Splicing calls the next CodeMsg In class fillArgs method , Other exceptions return system errors .
The code is as follows :
@ControllerAdvice @ResponseBody public class GlobalExceptionHandler {
@ExceptionHandler(value = Exception.class) public Result<String>
handleException(HttpServletRequest request, Exception ex){
ex.printStackTrace(); if(ex instanceof GlobalException){ GlobalException gex =
(GlobalException)ex; return Result.error(gex.getCm()); } else if(ex instanceof
BindException){ BindException bex = (BindException)ex; String message =
bex.getAllErrors().get(0).getDefaultMessage(); return
Result.error(CodeMsg.BIND_ERROR.fillArgs(message)); } else { return
Result.error(CodeMsg.SERVER_ERROR); } } }

stay MiaoshaUserServic Class login The return value of the method is CodeMsg type , But you should return methods that express the meaning of business methods , It shouldn't be CodeMsg type . You can define a global exception class by GlobalException
Further optimization , Throw out the abnormality directly , Give it to the exception handler .

stay GlobalException Encapsulation in class CondeMsg Type variable , For instantiation when throwing ; stay GlobalExceptionHandler Class modification exceptionHandler method , Add processing GlobalException Abnormal logic .
public class GlobalException extends RuntimeException{ private static final
long serialVersionUID = 1L; private CodeMsg cm; public GlobalException(CodeMsg
cm){ = cm; } public CodeMsg getCm() { return cm; } public static long
getSerialVersionUID() { return serialVersionUID; } public void setCm(CodeMsg
cm) { = cm; } }

stay MiaoshaUserService Modify in class login Method return type is boolean, Errors in the method body are directly instantiated GlobalException Class throws the exception out ;LoginController Modified in doLogin method , Optimize login logic .
Optimized MiaoshaUserService:
public boolean login(LoginVo loginVo){ if(loginVo == null){ //return
CodeMsg.SERVER_ERROR; throw new GlobalException(CodeMsg.SERVER_ERROR); } String
mobile = loginVo.getMobile(); String password = loginVo.getPassword();
MiaoshaUser user = miaoshaUserDao.getById(Long.parseLong(mobile)); if(user ==
null){ //return CodeMsg.MOBILE_NOT_EXIST; throw new
GlobalException(CodeMsg.MOBILE_NOT_EXIST); } String salt = user.getSalt();
String dbPass = user.getPassword(); String md5Pass =
Md5Util.formPassToDbPass(password, salt); if(!dbPass.equals(md5Pass)){ //return
CodeMsg.PASSWORD_ERROR; throw new GlobalException(CodeMsg.PASSWORD_ERROR); }
return true;
Optimized login logic :
@Controller @RequestMapping("/login") public class LoginController { private
static Logger log = LoggerFactory.getLogger(LoginController.class); @Autowired
UserService userService; @Autowired MiaoshaUserService miaoshaUserService;
@RequestMapping("/to_login") public String toLogin(){ return "login"; }
@RequestMapping("/do_login") @ResponseBody public Result<Boolean>
doLogin(HttpServletResponse response, @Valid LoginVo loginVo) {; // Sign in miaoshaUserService.login(response,loginVo);
return Result.success(true); }
<>4. Distributed Session

Distributed Session Implementation is to session Put in a single cache , adopt redis To manage .
thinking : After successful login , adopt UUIDUtil Build for user token Identify users , write to cookie in , Pass to client , The client uploads this every time
token, Server side according to token Get user information .

Define tool class uuid:
public class UUIDUtil { public static String uuid(){ return
UUID.randomUUID().toString().replace("-", ""); } }
stay MiaoshaUserService Of login Method :
// generate cookie String token = UUIDUtil.uuid();
redisSevice.set(MiaoshaUserKey.token, token, user); Cookie cookie = new
Cookie(COOKIE_TOKEN_NAME, token);
cookie.setMaxAge(MiaoshaUserKey.token.expireSecconds()); cookie.setPath("/");
response.addCookie(cookie); return true;
stay MiaoshaUserService add to getByToke() obtain user object , adopt addCookie() Extension of validity
public MiaoshaUser getByToke(String token,HttpServletResponse response) {
if(StringUtils.isEmpty(token)){ return null; } // Extension of validity MiaoshaUser user =
redisSevice.get(MiaoshaUserKey.token, token, MiaoshaUser.class); if(user !=
null){ addCookie(token, response, user); } return user; } //
// Realization of extended validity period , Regenerate a new to the cache cookie private void addCookie(String token,
HttpServletResponse response, MiaoshaUser user){
redisSevice.set(MiaoshaUserKey.token, token, user); Cookie cookie = new
Cookie(COOKIE_TOKEN_NAME, token);
cookie.setMaxAge(MiaoshaUserKey.token.expireSecconds()); cookie.setPath("/");
response.addCookie(cookie); }
Jump to product list after login , Corresponding Controller( According to upload cookie To get user information ) as follows :
@Controller @RequestMapping("/goods") public class GoodsController { private
static Logger log = LoggerFactory.getLogger(GoodsController.class); @Autowired
private MiaoshaUserService miaoshaUserService; @RequestMapping("/to_list")
public String toList(Model model, @CookieValue(name =
MiaoshaUserService.COOKIE_TOKEN_NAME, required = false) String cookieToken, //
@RequestParam It's for compatibility. The mobile terminal will cookie Put information in request parameters @RequestParam(name =
MiaoshaUserService.COOKIE_TOKEN_NAME, required = false) String paramToken) {
if(StringUtils.isEmpty(cookieToken) && StringUtils.isEmpty(paramToken)){ return
"/login/to_login"; } String token = StringUtils.isEmpty(paramToken) ?
cookieToken : paramToken; MiaoshaUser miaoshaUser =
miaoshaUserService.getByToke(token); model.addAttribute("user", miaoshaUser);
return "goods_list"; } }
Code optimization :

After login , The user's product information needs to be GoodsController Get Uploaded cookie, Carry out parameter verification , Optimize this part , stay GoodsController Parameter passed in directly user object , Available through WebMvcConfigurerAdapter Of addArgumentResolvers Implement .

WebConfig Class inheritance WebMvcConfigurerAdapter( rewrite addArgumentResolvers()):
package com.example.demo.config; import
org.springframework.beans.factory.annotation.Autowired; import
org.springframework.context.annotation.Configuration; import; import
import java.util.List; @Configuration public class WebConfig extends
WebMvcConfigurerAdapter{ @Autowired private UserArgumentResolver
userArgumentResolver; @Override public void
addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(userArgumentResolver); } }
UserArgumentResolver class ( Implementation interface HandlerMethodArgumentResolver, analysis user object ):
@Service public class UserArgumentResolver implements
HandlerMethodArgumentResolver{ @Autowired private MiaoshaUserService
miaoshaUserService; @Override public boolean supportsParameter(MethodParameter
parameter) { Class<?> clazz = parameter.getParameterType(); return clazz ==
MiaoshaUser.class; } @Override public Object resolveArgument(MethodParameter
parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception { HttpServletRequest
request = webRequest.getNativeRequest(HttpServletRequest.class);
HttpServletResponse response =
webRequest.getNativeResponse(HttpServletResponse.class); String paramToken =
request.getParameter(miaoshaUserService.COOKIE_TOKEN_NAME); String cookieToken
= getCookieValue(request, miaoshaUserService.COOKIE_TOKEN_NAME);
if(StringUtils.isEmpty(cookieToken) && StringUtils.isEmpty(paramToken)){ return
null; } String token = StringUtils.isEmpty(paramToken) ? cookieToken :
paramToken; return miaoshaUserService.getByToke(token, response); } private
String getCookieValue(HttpServletRequest request, String cookieName) { Cookie[]
cookies = request.getCookies(); if(cookies != null){ for(Cookie cookie :
cookies){ if(cookie.getName().equals(cookieName)){ return cookie.getValue(); }
} } return null; } }
After optimization GoodsController:
@Controller @RequestMapping("/goods") public class GoodsController { private
static Logger log = LoggerFactory.getLogger(GoodsController.class); @Autowired
private MiaoshaUserService miaoshaUserService; @Autowired RedisSevice
redisSevice; @RequestMapping("/to_list") public String toLogin(Model
model,MiaoshaUser user){ model.addAttribute("user", user); return "goods_list";
} }

©2019-2020 Toolsou All rights reserved,
org.postgresql.util.PSQLException Processing records 【JAVA】【 Huawei campus recruitment written examination - Software 】2020-09-09el-ui:select Get value Three methods of value transfer between non parent and child components python read , write in txt Text content [AndroidO] [RK3399] -- GPIO Drive and control mode be based on STM32 Design of infrared obstacle avoidance car ( There is a code )( Essence )2020 year 7 month 21 day ASP.NET Core Use of global filters ( Essence 2020 year 6 month 2 Daily update ) TypeScript Function explanation Dijkstra Algorithmic Python realization - Shortest path problem