1 登录判断
用户在未登录状态下可以查看商品列别以及秒杀商品详情,但不可以在未登录状态进行秒杀商品的操作,当用户点击开始秒杀时,进行登陆验证
<!DOCTYPE html>
<head><title>商品详情</title><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /><script type="text/javascript" src="/js/jquery.min.js"></script><link rel="stylesheet" type="text/css" href="/bootstrap/css/bootstrap.min.css" /><!-- bootstrap --><script type="text/javascript" src="/bootstrap/js/bootstrap.min.js"></script><script type="text/javascript" src="/jquery-validation/jquery.validate.min.js"></script> <!-- jquery-validator --><script type="text/javascript" src="/jquery-validation/localization/messages_zh.min.js"></script><script type="text/javascript" src="/layer/layer.js"></script><!-- layer --><script type="text/javascript" src="/js/md5.min.js"></script><!-- md5.js --><script type="text/javascript" src="/js/common.js"></script><!-- common.js --><script type="text/javascript" src="/js/socket.js"></script><!-- common.js -->
</head>
<body>
<div class="panel panel-default"><div class="panel-heading">秒杀商品详情</div><div class="panel-body"><div id="userTip" style="display: none"><span> 您还没有登录,请<a href="/login.html">登陆</a>后再操作<br/></span></div><span>没有收货地址的提示。。。</span></div><table class="table"><tr><td>商品名称</td><td colspan="3" id="goodName"></td></tr><tr><td>商品图片</td><td colspan="3"><img id="goodImg" width="200" height="200" /></td></tr><tr><td>秒杀开始时间</td><td id="startDate"></td><td id="seckillTip"></td><td><img id="verifyCodeImg" width="80" height="32" onclick="initVerifyCodeImg()" style="display: none"><input id="verifyCode" style="display: none"><button class="btn btn-primary btn-block" type="button" id="buyButton" onclick="">立即秒杀</button></td></tr><tr><td>商品原价</td><td colspan="3" id="goodPrice"></td></tr><tr><td>秒杀价</td><td colspan="3" id="seckillPrice"></td></tr><tr><td>库存数量</td><td colspan="3" id="stockCount"></td></tr></table>
</div>
<script type="text/javascript">var seckillId;$(function () {seckillId = getQueryString("seckillId");initGood();initUser();});function initGood(){$(function () {$.ajax({url: "http://localhost:9000/seckill/seckillGood/find?seckillId="+seckillId,type: "get",xhrFields: {withCredentials: true}, //启用cookiesuccess:function (data) {if(data.code==200){//填充表格中的数据renderGood(data.data);}else{layer.msg(data.msg)}}});});}function renderGood(good) {$("#goodName").html(good.goodName);$("#goodImg").prop("src",good.goodImg);$("#startDate").html(good.startDate);$("#goodPrice").html(good.goodPrice);$("#stockCount").html(good.stockCount);$("#seckillPrice").html(good.seckillPrice);//调用时间renderDate(good.startDate,good.endDate);}//定义秒杀的三个阶段var timer; //计时器//距离抢购开始还有多久var remainStartSeconds;//距离结束还有多久var remainEndSeconds;function renderDate(sDate,eDate) {var startTime = new Date(sDate); // 2023-11-25 16:00var endTime = new Date(eDate); // 2023-11-25 18:00var now = new Date(); // 2023-11-25 14:37remainStartSeconds=parseInt((startTime.getTime()-now.getTime())/1000);//秒remainEndSeconds=parseInt((endTime.getTime()-now.getTime())/1000);//秒timer=window.setInterval(showSeckillTip,1000);}function showSeckillTip() {remainStartSeconds--;remainEndSeconds--;if(remainStartSeconds>0){$("#seckillTip").html("距离本场秒杀开始还有"+remainStartSeconds+"秒");//禁用按钮$("#buyButton").prop("disabled",true);}else{if(remainEndSeconds>0){//秒杀中$("#seckillTip").html("秒杀进行中....");//禁用按钮$("#buyButton").prop("disabled",false);}else{$("#seckillTip").html("秒杀结束了");//禁用按钮$("#buyButton").prop("disabled",true);window.clearInterval(timer);//取消计时器}}}function initUser(){$(function () {$.ajax({url: "http://localhost:9000/member/token/getCurrent",type: "get",xhrFields: {withCredentials: true}, //启用cookiesuccess:function (data) {if(data.code==200){//填充表格中的数据renderUser(data.data);}else{layer.msg(data.msg)}}});});}var user;function renderUser(u){if(u){user=u;}else{//没有数据$("#userTip").show();}}</script>
</body>
</html>
1.1 编写前端登录判断
1.2 编写后端登录判断方法
1 编写sevice接口及其实现类方法
sevice接口
public User getUserByToken(String token);
实现类
/*** 根据token查询用户* @param token* @return*/@Overridepublic User getUserByToken(String token) {return myRedisTemplate.get(MemberServerKeyPrefix.USER_TOKEN, token, User.class);}
2 编写controller层方法
/*** 获取当前用户* @param token 利用cookie中存储的token来判断用户* @return*/@GetMapping("/getCurrent")public Result<User> getCurrent(@CookieValue(value = CookieUtil.TOKEN_COOKIE_NAME, required = false) String token){User user = userService.getUserByToken(token);return Result.success(user);}
3 测试
2 后端登陆判断方法的优化
2.1 调整项目结构
1 添加依赖
2 将member-server下MemberServerKeyPrefix类移动至member-api下
3 将member-server下CookieUtil类移动至member-api下
4 编写getCookie方法
/*** 获取cookie* @param request* @param tokenCookieName* @return*/public static String getCookie(HttpServletRequest request, String tokenCookieName) {Cookie[] cookies = request.getCookies();if (cookies != null && cookies.length > 0){for (Cookie cookie : cookies) {//找到cookieif (cookie.getName().equals(tokenCookieName)){return cookie.getValue();}}}return null;}
2.2 编写自定义SpringMVC参数解析器
public class UserMethodArgumentResolver implements HandlerMethodArgumentResolver {@Autowiredprivate MyRedisTemplate myRedisTemplate;/*** 判断参数类型是否为User* @param methodParameter* @return*/@Overridepublic boolean supportsParameter(MethodParameter methodParameter) {return methodParameter.getParameterType() == User.class &&methodParameter.getParameterAnnotation(RedisValue.class) != null;}/*** 自定义参数解析器* @param parameter* @param mavContainer* @param webRequest* @param binderFactory* @return* @throws Exception*/@Overridepublic Object resolveArgument(MethodParameter parameter,ModelAndViewContainer mavContainer,NativeWebRequest webRequest,WebDataBinderFactory binderFactory) throws Exception {//获取请求对象HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);//获取cookieString token = CookieUtil.getCookie(request, CookieUtil.TOKEN_COOKIE_NAME);if (StringUtils.isEmpty(token)){return null;}return myRedisTemplate.get(MemberServerKeyPrefix.USER_TOKEN, token, User.class);}}
2.3 编写配置类
@Configuration
public class WebConfig implements WebMvcConfigurer {//注入参数解析器@Autowiredprivate UserMethodArgumentResolver userMethodArgumentResolver;/*** 添加参数解析器* @param resolvers*/@Overridepublic void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {resolvers.add(userMethodArgumentResolver);}@Beanpublic UserMethodArgumentResolver userMethodArgumentResolver() {return new UserMethodArgumentResolver();}
}
2.4 编写自定义注解
@Target(ElementType.PARAMETER) //定义该注解应用于方法参数上
@Retention(RetentionPolicy.RUNTIME) //不仅保存在class中,JVM加载时也存在
public @interface RedisValue {}
2.5 编写登陆判断方法
@RequestMapping("/getCurrent")public Result<User> getCurrent(@RedisValue User user){return Result.success(user);}
3 秒杀实现
实现流程:
-
- 判断用户是否登录
-
- 判断场次是否正常
-
- 根据秒杀的场次查出当前场次对应的秒杀商品
-
- 秒杀时间的问题 处于秒杀中才能抢购
-
- 判断用户是否已经参与过当前场次的抢购
-
- 判断库存是否足够
-
- 秒杀的原子性 【 做三件事 】
- 7.1 商品的库存 -1 t_seckill_goods(生成抢购订单)
- 7.2 创建商品的订单表 t_order_info(参与抢购的商品表)
- 7.3 创建秒杀订单 t_seckill_order(防止用户重复抢购的表)
3.1 编写前端秒杀动作
3.2 调整项目结构
1 添加依赖
2 添加参数解析器的配置类
将member-server下的参数解析器的配置类复制到seckill-server下