Java秒杀实战 (三)秒杀基本功能开发
转自:https://blog.csdn.net/qq_41305266/article/details/80991687
一、两次MD5
1. 用户端: PASS = MD5( 明文 + 固定 Salt)
2. 服务端: PASS = MD5( 用户输入 + 随机 Salt)
通过两次MD5,可以增大http明文传输过程或数据库被盗后,黑客通过彩虹表等手段反推出明文密码的难度(有一定作用,但不能保证绝对安全)。
pom文件添加依赖
<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>
添加md5加密工具类
package com.wings.seckill.util;
import org.apache.commons.codec.digest.DigestUtils;
public class Md5Util {
private static final String SALT = "1a2b3c4d";
public static String md5(String src){
		return DigestUtils.md5Hex(src);
	}
public static String inputPass2FormPass(String inputPass){
		String src = "" + SALT.charAt(0) + SALT.charAt(2)+ inputPass + SALT.charAt(5)+ SALT.charAt(4);
		return md5(src);
	}
public static String formPass2DbPass(String formPass, String salt){
		String src = "" + salt.charAt(0) + salt.charAt(2)+ formPass + salt.charAt(5)+ salt.charAt(4);
		return md5(src);
	}
public static String inputPass2DbPass(String inputPass, String salt){
		String formPass = inputPass2FormPass(inputPass);
		String dbPass = formPass2DbPass(formPass, salt);
		return dbPass;
	}
public static void main(String[] args) {
		String inputPass = "13632481101";
		String salt = "mysalt";
		String formPass = inputPass2FormPass(inputPass);
		String dbPass1 = formPass2DbPass(formPass, salt);
		String dbPass2 = inputPass2DbPass(inputPass, salt);
		System.out.println(formPass);
		System.out.println(dbPass1);
		System.out.println(dbPass2);
	}
}
新增用户表seckill_user
CREATE TABLE `seckill`.`Untitled`  (
  `id` bigint(20) NOT NULL COMMENT '用户ID,电话号码',
  `nickname` varchar(255) NOT NULL,
  `password` varchar(32) DEFAULT null COMMENT 'MD5(MD5(pass明文+固定salt)+salt)',
  `salt` varchar(10) DEFAULT NULL,
  `head` varchar(128) DEFAULT NULL COMMENT '头像,云存储的id',
  `register_date` datetime(0) DEFAULT NULL COMMENT '注册时间',
  `last_login_date` datetime(0) COMMENT '上次登录时间',
  `login_count` int(11) DEFAULT 0 COMMENT '登录次数',
  PRIMARY KEY (`id`)
);
添加电话号码校验工具类
package com.wings.seckill.util;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.alibaba.druid.util.StringUtils;
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();
	}
public static void main(String[] args) {
		boolean result1 = isMobile("13632481101");
		boolean result2 = isMobile("1363248110");
		System.out.println(result1);
		System.out.println(result2);
	}
}
SeckillUser domain、dao、service、controller
package com.wings.seckill.domain;
import java.util.Date;
public class SeckillUser {
	private Long id;
	private String nickname;
	private String password;
	private String salt;
	private String head;
	private Date registerDate;
	private Date lastLoginDate;
	private Integer loginCount;
	public Long getId() {
		return id;
	}
	public void setId(Long id) {
		this.id = id;
	}
	public String getNickname() {
		return nickname;
	}
	public void setNickname(String nickname) {
		this.nickname = nickname;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
	public String getSalt() {
		return salt;
	}
	public void setSalt(String salt) {
		this.salt = salt;
	}
	public String getHead() {
		return head;
	}
	public void setHead(String head) {
		this.head = head;
	}
	public Date getRegisterDate() {
		return registerDate;
	}
	public void setRegisterDate(Date registerDate) {
		this.registerDate = registerDate;
	}
	public Date getLastLoginDate() {
		return lastLoginDate;
	}
	public void setLastLoginDate(Date lastLoginDate) {
		this.lastLoginDate = lastLoginDate;
	}
	public Integer getLoginCount() {
		return loginCount;
	}
	public void setLoginCount(Integer loginCount) {
		this.loginCount = loginCount;
	}
}
package com.wings.seckill.dao;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import com.wings.seckill.domain.SeckillUser;
@Mapper
public interface SeckillUserDao {
@Select("select * from seckill_user where id = #{id}")
	public SeckillUser getById(long id);
}
package com.wings.seckill.service;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.wings.seckill.dao.SeckillUserDao;
import com.wings.seckill.domain.SeckillUser;
import com.wings.seckill.result.CodeMsg;
import com.wings.seckill.result.Result;
import com.wings.seckill.util.Md5Util;
import com.wings.seckill.util.ValidatorUtil;
import com.wings.seckill.vo.LoginVo;
@Service
public class SeckillUserService {
@Autowired
	SeckillUserDao seckillUserDao;
public SeckillUser getById(long id){
		return seckillUserDao.getById(id);
	}
public CodeMsg login(LoginVo loginVo){
		if(loginVo == null){
			return CodeMsg.SERVER_ERROR;
		}
		String mobile = loginVo.getMobile();
		String password = loginVo.getPassword();
if(StringUtils.isEmpty(mobile)){
			return CodeMsg.MOBILE_EMPTY;
		}
		if(StringUtils.isEmpty(password)){
			return CodeMsg.PASSWORD_EMPTY;
		}
if(!ValidatorUtil.isMobile(mobile)){
			return CodeMsg.MOBILE_ERROR;
		}
SeckillUser user = seckillUserDao.getById(Long.parseLong(loginVo.getMobile()));
		if(user == null){
			return CodeMsg.MOBILE_NOT_EXIST;
		}
String salt = user.getSalt();
		String dbPass = user.getPassword();
		String md5Pass = Md5Util.formPass2DbPass(password, salt);
		if(!dbPass.equals(md5Pass)){
			return CodeMsg.PASSWORD_ERROR;
		}
return CodeMsg.SUCCESS;
	}
}
package com.imooc.miaosha.controller;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.imooc.miaosha.redis.RedisService;
import com.imooc.miaosha.result.Result;
import com.imooc.miaosha.service.MiaoshaUserService;
import com.imooc.miaosha.vo.LoginVo;
@Controller
@RequestMapping("/login")
public class LoginController {
private static Logger log = LoggerFactory.getLogger(LoginController.class);
@Autowired
	MiaoshaUserService userService;
@Autowired
	RedisService redisService;
@RequestMapping("/to_login")
    public String toLogin() {
        return "login";
    }
@RequestMapping("/do_login")
    @ResponseBody
    public Result<Boolean> doLogin(HttpServletResponse response, @Valid LoginVo loginVo) {
    	log.info(loginVo.toString());
    	//登录
    	userService.login(response, loginVo);
    	return Result.success(true);
    }
}
视图层增加如下文件
登陆页面:login.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>登录</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<!-- 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/do_login",
	    type: "POST",
	    data:{
	    	mobile:$("#mobile").val(),
	    	password: password
	    },
	    success:function(data){
	    	layer.closeAll();
	    	if(data.code == 0){
	    		layer.msg("成功");
	    	}else{
	    		layer.msg(data.msg);
	    	}
	    },
	    error:function(){
	    	layer.closeAll();
	    }
	});
}
</script>
</html>
全局自定义 common.js
//展示loading
function g_showLoading(){
	var idx = layer.msg('处理中...', {icon: 16,shade: [0.5, '#f5f5f5'],scrollbar: false,offset: '0px', time:100000}) ;  
	return idx;
}
//salt
var g_passsword_salt="1a2b3c4d"
(二)JSR303参数校验 + 全局异常处理器
service中的方法入参有许多参数的判断代码,为了简化,可以利用JSR303参数校验
pom文件引入依赖
<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-validation</artifactId>
		</dependency>
自定义参数校验注解
package com.wings.seckill.validator;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import org.apache.commons.lang3.StringUtils;
import com.wings.seckill.util.ValidatorUtil;
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);
	}
}
package com.wings.seckill.validator;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = IsMobileValidator.class )
public @interface IsMobile {
boolean required() default true;
String message() default "手机号码格式不正确";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
使用该自定义注解:
package com.wings.seckill.vo;
import javax.validation.constraints.NotNull;
import org.hibernate.validator.constraints.Length;
import com.wings.seckill.validator.IsMobile;
public class LoginVo {
@NotNull
	@IsMobile
	private String mobile;
	@NotNull
	@Length(min = 32)
	private String password;
public String getMobile() {
		return mobile;
	}
	public void setMobile(String mobile) {
		this.mobile = mobile;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
	@Override
	public String toString() {
		return "LoginVo [mobile=" + mobile + ", password=" + password + "]";
	}
}
在对应方法中加入@Valid 开启参数校验
@RequestMapping("/do_login")
	@ResponseBody
	public Result<CodeMsg> doLogin(@Valid LoginVo loginVo) {
		log.info(loginVo.toString());
		seckillUserService.login(loginVo);
		return Result.success(CodeMsg.SUCCESS);
	}
service的login方法简化如下:
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();
		/*
		if(StringUtils.isEmpty(mobile)){
			return CodeMsg.MOBILE_EMPTY;
		}
		if(StringUtils.isEmpty(password)){
			return CodeMsg.PASSWORD_EMPTY;
		}
if(!ValidatorUtil.isMobile(mobile)){
			return CodeMsg.MOBILE_ERROR;
		}
		*/
		SeckillUser user = seckillUserDao.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.formPass2DbPass(password, salt);
		if(!dbPass.equals(md5Pass)){
			//return CodeMsg.PASSWORD_ERROR;
			throw new GlobalException(CodeMsg.PASSWORD_ERROR);
		}
return true;
	}
定义全局异常及异常处理器
package com.wings.seckill.exception;
import com.wings.seckill.result.CodeMsg;
public class GlobalException extends RuntimeException{
private static final long serialVersionUID = 31665074385012932L;
	private CodeMsg cm;
public GlobalException(CodeMsg cm){
		this.cm = cm;
	}
	public CodeMsg getCm() {
		return cm;
	}
}
package com.wings.seckill.exception;
import javax.servlet.http.HttpServletRequest;
import org.springframework.validation.BindException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import com.wings.seckill.result.CodeMsg;
import com.wings.seckill.result.Result;
@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.fillMsg(message));
		} else {
			return Result.error(CodeMsg.SERVER_ERROR);
		}
	}
}
(三)分布式session
添加uuid工具类
package com.wings.seckill.util;
import java.util.UUID;
public class UUIDUtil {
public static String uuid(){
		return UUID.randomUUID().toString().replace("-", "");
	}
}
SeckillUserService的login方法添加以下代码
String token = UUIDUtil.uuid();
		redisService.set(SeckillUserKey.token, token, user);
		Cookie cookie = new Cookie(COOKIE_TOKEN_NAME, token);
		cookie.setMaxAge(SeckillUserKey.token.expireSeconds());
		cookie.setPath("/");
response.addCookie(cookie);
SeckillUserService添加通过token获取user对象方法
public SeckillUser getByToke(String token) {
		if(StringUtils.isEmpty(token)){
			return null;
		}
return redisService.get(SeckillUserKey.token, token, SeckillUser.class);
	}
登录成功后跳转到商品列表,对应的Controller及页面如下
package com.wings.seckill.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import com.wings.seckill.domain.SeckillUser;
import com.wings.seckill.service.SeckillUserService;
@Controller
@RequestMapping("/goods")
public class GoodsController {
private static Logger log = LoggerFactory.getLogger(GoodsController.class);
@Autowired
	private SeckillUserService seckillUserService;
@RequestMapping("/to_list")
	public String toList(Model model, 
			@CookieValue(name = SeckillUserService.COOKIE_TOKEN_NAME, required = false) String cookieToken,
			// @RequestParam 是为了兼容默写手机端会把cookie信息放入请求参数中
			@RequestParam(name = SeckillUserService.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;
		SeckillUser seckillUser = seckillUserService.getByToke(token);
		model.addAttribute("user", seckillUser);
		return "goods_list";
	}
}
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>商品列表</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<p th:text="'hello:'+${user.nickname}" ></p>
</body>
</html>
(四)优化
像User这种,几乎每个Controller方法都要使用的对象,如果每个Controller的方法都通过以上方式获取,代码将相当臃肿。这时,可以使用SpringMVC中WebMvcConfigurerAdapter的addArgumentResolvers,向需要user对象的方法进行入参注入。
package com.wings.seckill.config;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter{
@Autowired
	private UserArgumentResolver userArgumentResolver;
@Override
	public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
		argumentResolvers.add(userArgumentResolver);
	}
}
package com.wings.seckill.config;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import com.wings.seckill.domain.SeckillUser;
import com.wings.seckill.service.SeckillUserService;
@Service
public class UserArgumentResolver implements HandlerMethodArgumentResolver{
@Autowired
	private SeckillUserService seckillUserService;
@Override
	public boolean supportsParameter(MethodParameter parameter) {
		Class<?> clazz = parameter.getParameterType();
		return clazz == SeckillUser.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(SeckillUserService.COOKIE_TOKEN_NAME);
		String cookieToken = getCookieValue(request, SeckillUserService.COOKIE_TOKEN_NAME);
if(StringUtils.isEmpty(cookieToken) && StringUtils.isEmpty(paramToken)){
			return null;
		}
String token = StringUtils.isEmpty(paramToken) ? cookieToken : paramToken;
		return seckillUserService.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;
	}
}
package com.wings.seckill.service;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.wings.seckill.dao.SeckillUserDao;
import com.wings.seckill.domain.SeckillUser;
import com.wings.seckill.exception.GlobalException;
import com.wings.seckill.redis.RedisService;
import com.wings.seckill.redis.SeckillUserKey;
import com.wings.seckill.result.CodeMsg;
import com.wings.seckill.util.Md5Util;
import com.wings.seckill.util.UUIDUtil;
import com.wings.seckill.vo.LoginVo;
@Service
public class SeckillUserService {
public static final String COOKIE_TOKEN_NAME = "token";
@Autowired
	SeckillUserDao seckillUserDao;
@Autowired
	RedisService redisService;
public SeckillUser getById(long id){
		return seckillUserDao.getById(id);
	}
public boolean login(HttpServletResponse response, LoginVo loginVo){
		if(loginVo == null){
			throw new GlobalException(CodeMsg.SERVER_ERROR);
		}
		String mobile = loginVo.getMobile();
		String password = loginVo.getPassword();
SeckillUser user = seckillUserDao.getById(Long.parseLong(mobile));
		if(user == null){
			throw new GlobalException(CodeMsg.MOBILE_NOT_EXIST);
		}
String salt = user.getSalt();
		String dbPass = user.getPassword();
		String md5Pass = Md5Util.formPass2DbPass(password, salt);
		if(!dbPass.equals(md5Pass)){
			throw new GlobalException(CodeMsg.PASSWORD_ERROR);
		}
String token = UUIDUtil.uuid();
		addCookie(token, response, user);
		return true;
	}
public SeckillUser getByToke(String token, HttpServletResponse response) {
		if(StringUtils.isEmpty(token)){
			return null;
		}
// 延长有效期
		SeckillUser user = redisService.get(SeckillUserKey.token, token, SeckillUser.class);
		if(user != null){
			addCookie(token, response, user);
		}
		return user;
	}
private void addCookie(String token, HttpServletResponse response, SeckillUser user){
		redisService.set(SeckillUserKey.token, token, user);
		Cookie cookie = new Cookie(COOKIE_TOKEN_NAME, token);
		cookie.setMaxAge(SeckillUserKey.token.expireSeconds());
		cookie.setPath("/");
		response.addCookie(cookie);
	}
}
这时Controller将会相当简洁
package com.wings.seckill.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import com.wings.seckill.domain.SeckillUser;
@Controller
@RequestMapping("/goods")
public class GoodsController {
@RequestMapping("/to_list")
	public String toList(Model model, SeckillUser seckillUser) {
		model.addAttribute("user", seckillUser);
		return "goods_list";
	}
}
 
--------------------- 
作者:插上小翅膀的程序猿Wings 
来源:CSDN 
原文:https://blog.csdn.net/qq_41305266/article/details/80991687 
版权声明:本文为博主原创文章,转载请附上博文链接!
Java秒杀实战 (三)秒杀基本功能开发的更多相关文章
- WEB开发框架系列教程 (三)页面功能开发(2)
		
上一节介绍了,基础资料币别信息的开发,只通过辅助开发工具,创建及资料表,填写 表名,程序就完全好了. 最后也说到,可能我们也会面对另外一些基础资料信息的维护,但是不是简单到只有代码 和名称,可能还有另 ...
 - EBS OAF开发中的Java 实体对象(Entity Object)验证功能补充
		
EBS OAF开发中的Java 实体对象(Entity Object)验证功能补充 (版权声明,本人原创或者翻译的文章如需转载,如转载用于个人学习,请注明出处:否则请与本人联系,违者必究) EO理论上 ...
 - 20135231 JAVA实验报告三:敏捷开发与XP实践
		
---恢复内容开始--- JAVA实验报告三:敏捷开发与XP实践 20135231 何佳 实验内容 1. XP基础 2. XP核心实践 3. 相关工具 实验要求 1.没有Linux基础的同学建议先学习 ...
 - 《Java 8实战》读书笔记系列——第三部分:高效Java 8编程(四):使用新的日期时间API
		
https://www.lilu.org.cn/https://www.lilu.org.cn/ 第十二章:新的日期时间API 在Java 8之前,我们常用的日期时间API是java.util.Dat ...
 - Java进阶(三)多线程开发关键技术
		
原创文章,同步发自作者个人博客,转载请务必以超链接形式在文章开头处注明出处http://www.jasongj.com/java/multi_thread/. sleep和wait到底什么区别 其实这 ...
 - “全栈2019”Java第三章:安装开发工具IntelliJ IDEA
		
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...
 - go语言实战教程之 后台管理页面统计功能开发(1)
		
本节内容我们将学习开发实现后台管理平台页面统计功能开发的功能接口,本章节内容将涉及到多种请求路由的方式. 功能介绍 后台管理平台不仅是功能管理平台,同时还是数据管理平台.从数据管理平台角度来说,在管理 ...
 - FastAPI(七十)实战开发《在线课程学习系统》接口开发--留言功能开发
		
在之前的文章:FastAPI(六十九)实战开发<在线课程学习系统>接口开发--修改密码,这次分享留言功能开发 我们能梳理下对应的逻辑 1.校验用户是否登录 2.校验留言的用户是否存在 3. ...
 - go语言实战教程之 后台管理页面统计功能开发(2)
		
上节内容介绍了后台管理页面统计功能开发(1),从功能介绍,到接口请求分析和归类,最后是代码设计.经过上节内容的介绍,已经将业务逻辑和开发逻辑解释清楚,本节内容侧重于编程代码实现具体的功能. 当日增长数 ...
 - 小D课堂-SpringBoot 2.x微信支付在线教育网站项目实战_5-2.微信扫一扫功能开发前期准备
		
笔记 2.微信扫一扫功能开发前期准备 简介:讲解微信扫一扫功能相关开发流程和资料准备 1.微信开放平台介绍(申请里面的网站应用需要企业资料) ...
 
随机推荐
- [Java读书笔记] Effective Java(Third Edition) 第 5 章 泛型
			
第 26 条:请不要使用原生态类型 声明中具有一个或多个类型参数的类或者接口,就是泛型(generic). 例如List接口只有单个类型参数E, 表示列表的元素类型.这个接口全称List<E&g ...
 - Eclipse添加Android library 错误的原因
			
这两天把项目从本地转移到GIT上,本来我的Workspace是在D盘,现在因为感觉D盘不够用,就把GIT到的项目放到E盘了 按照以往的用法,GIT下来以后再往属性里添加依赖库就OK了,但是这次怎么也无 ...
 - eclipse快捷键整理
			
编辑 Ctrl+1 快速修复(最经典的快捷键,就不用多说了,可以解决很多问题,比如import类.try catch包围等)Ctrl+Shift+F 格式化当前代码Ctrl+Shift+M 添加类的i ...
 - JAVA 基础编程练习题20 【程序 20 求前 20 项之和】
			
20 [程序 20 求前 20 项之和] 题目:有一分数序列:2/1,3/2,5/3,8/5,13/8,21/13...求出这个数列的前 20 项之和. 程序分析:请抓住分子与分母的变化规律. pac ...
 - 为Django添加图片验证码
			
可直接复制到Django项目中使用 # author:sunshine from django.http import HttpResponse from PIL import Image, Imag ...
 - Vue学习笔记(二)
			
1.引入组件需要注意的事项说明和步骤: 第一步,引入对应的组件: import Hello2 from './components/Hello2' 第二步: 需要注册该组件才可以使用 <scri ...
 - 图像欧拉数计算 matlab实现
			
EUL = C - H 其中EUL表示欧拉数 C表示对象数 H表示孔洞数 欧拉数常用来识别数字: 识别数字 8 ,8 的欧拉数为 -1 ,不同于0,1,2,3,4,5,6,7,9 close all ...
 - centos(linux)-jdk配置
			
1.清理系统默认自带的jdk 在安装centos时,可能系统会默认安装了例如openjdk等,需要先手动卸载 先执行:rpm -qa | grep jdk (查看已经自带的jdk): 卸载命名:sud ...
 - Matlab求微分方程的符号解1
			
一.常微分方程的求解 例1. 例2. 例3. 通常我们使用syms 和dsolve来求解: first: second:表示 third:如果有必要 功能函数diff可以完成一元或多元函数任意阶数的微 ...
 - spring boot跨域问题的简便解决方案
			
刚学spring boot的时候被跨域问题拦住好久,最终好不容易从网上抄了别人的极端代码才解决. 但是前些天看一同事的代码时,发现spring boot中用注解就可以解决. 在controller上添 ...