Java秒杀系统:web层详解

目录

  • 设计Restful接口
  • SpringMVC
    • 项目整合SpringMVC
  • 使用SpringMVC实现Restful接口
    • 逻辑交互
      • 身份认证
      • 计时面板
    • 总结

      设计Restful接口 根据需求设计前端交互流程。
      三个职位:
      • 产品:解读用户需求,搞出需求文档
      • 前端:不同平台的页面展示
      • 后端:存储、展示、处理数据
      前端页面流程:
      Java秒杀系统:web层详解
      文章图片

      详情页流程逻辑:
      Java秒杀系统:web层详解
      文章图片

      标准系统时间从服务器获取。
      Restful:一种优雅的URI表述方式、资源的状态和状态转移。
      Restful规范:
      • GET 查询操作
      • POST 添加/修改操作(非幂等)
      • PUT 修改操作(幂等,没有太严格区分)
      • DELETE 删除操作
      URL设计:
      /模块/资源/{标示}/集合/.../user/{uid}/friends -> 好友列表/user/{uid}/followers -> 关注者列表

      秒杀API的URL设计
      GET /seckill/list 秒杀列表GET /seckill/{id}/detail 详情页GET /seckill/time/now 系统时间POST /seckill/{id}/exposer 暴露秒杀POST /seckill/{id}/{md5}/execution 执行秒杀

      下一步就是如何实现这些URL接口。

      SpringMVC 理论
      Java秒杀系统:web层详解
      文章图片

      适配器模式(Adapter Pattern),把一个类的接口变换成客户端所期待的另一种接口, Adapter模式使原本因接口不匹配(或者不兼容)而无法在一起工作的两个类能够在一起工作。
      SpringMVC的handlerControllerHttpRequestHandlerServlet等)有多种实现方式,例如继承Controller的,基于注解控制器方式的,HttpRequestHandler方式的。由于实现方式不一样,调用方式就不确定了。
      看HandlerAdapter接口有三个方法:
      // 判断该适配器是否支持这个HandlerMethodboolean supports(Object handler); // 用来执行控制器处理函数,获取ModelAndView 。就是根据该适配器调用规则执行handler方法。ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception; long getLastModified(HttpServletRequest request, Object handler);

      问流程如上图,用户访问一个请求,首先经过DispatcherServlet转发。利用HandlerMapping得到想要的HandlerExecutionChain(里面包含handler和一堆拦截器)。然后利用handler,得到HandlerAdapter,遍历所有注入的HandlerAdapter,依次使用supports方法寻找适合这个handler的适配器子类。最后通过这个获取的适配器子类运用handle方法调用控制器函数,返回ModelAndView。
      注解映射技巧
      • 支持标准的URL
      • ?和*和**等字符,如/usr/*/creation会匹配/usr/AAA/creation和/usr/BBB/creation等。/usr/**/creation会匹配/usr/creation和/usr/AAA/BBB/creation等URL。带{xxx}占位符的URL。
      • 如/usr/{userid}匹配/usr/123、/usr/abc等URL.
      请求方法细节处理
      • 请求参数绑定
      • 请求方式限制
      • 请求转发和重定向
      • 数据模型赋值
      • 返回json数据
      • cookie访问
      Java秒杀系统:web层详解
      文章图片

      返回json数据
      Java秒杀系统:web层详解
      文章图片

      cookie访问:
      Java秒杀系统:web层详解
      文章图片


      项目整合SpringMVC
      web.xml下配置springmvc需要加载的配置文件:
      seckill-dispatcherorg.springframework.web.servlet.DispatcherServletspring -> springmvc-->contextConfigLocationclasspath:spring/spring-*.xmlseckill-dispatcher/

      在resources文件夹下的spring文件夹添加spring-web.xml文件:


      使用SpringMVC实现Restful接口 新建文件:
      Java秒杀系统:web层详解
      文章图片

      首先是SeckillResult.java,这个保存controller的返回结果,做一个封装。
      // 所有ajax请求返回类型,封装json结果public class SeckillResult {private boolean success; //是否执行成功private T data; // 携带数据private String error; // 错误信息// getter setter contructor}

      在Seckillcontroller.java中,实现了我们之前定义的几个URL:
      GET /seckill/list 秒杀列表GET /seckill/{id}/detail 详情页GET /seckill/time/now 系统时间POST /seckill/{id}/exposer 暴露秒杀POST /seckill/{id}/{md5}/execution 执行秒杀

      具体代码如下:
      @Controller // @Service @Component放入spring容器@RequestMapping("/seckill") // url:模块/资源/{id}/细分public class SeckillController {private final Logger logger = LoggerFactory.getLogger(this.getClass()); @Autowiredprivate SecKillService secKillService; @RequestMapping(value = "https://www.it610.com/list",method = RequestMethod.GET)public String list(Model model) {// list.jsp + model = modelandviewList list = secKillService.getSecKillList(); model.addAttribute("list",list); return "list"; }@RequestMapping(value = "https://www.it610.com/{seckillId}/detail", method = RequestMethod.GET)public String detail(@PathVariable("seckillId") Long seckillId, Model model) {if (seckillId == null) {// 0. 不存在就重定向到list// 1. 重定向访问服务器两次// 2. 重定向可以重定义到任意资源路径。// 3. 重定向会产生一个新的request,不能共享request域信息与请求参数return "redrict:/seckill/list"; }SecKill secKill = secKillService.getById(seckillId); if (secKill == null) {// 0. 为了展示效果用forward// 1. 转发只访问服务器一次。// 2. 转发只能转发到自己的web应用内// 3. 转发相当于服务器跳转,相当于方法调用,在执行当前文件的过程中转向执行目标文件,//两个文件(当前文件和目标文件)属于同一次请求,前后页 共用一个request,可以通//过此来传递一些数据或者session信息return "forward:/seckill/list"; }model.addAttribute("seckill",secKill); return "detail"; }// ajax json@RequestMapping(value = "https://www.it610.com/{seckillId}/exposer",method = RequestMethod.POST,produces = {"application/json; charset=UTF8"})@ResponseBodypublic SeckillResult exposer(Long seckillId) {SeckillResult result; try {Exposer exposer = secKillService.exportSecKillUrl(seckillId); result = new SeckillResult(true,exposer); } catch (Exception e) {logger.error(e.getMessage(),e); result = new SeckillResult<>(false,e.getMessage()); }return result; }@RequestMapping(value = "https://www.it610.com/{seckillId}/{md5}/execution",method = RequestMethod.POST,produces = {"application/json; charset=UTF8"})public SeckillResult execute(@PathVariable("seckillId") Long seckillId,// required = false表示cookie逻辑由我们程序处理,springmvc不要报错@CookieValue(value = "https://www.it610.com/article/killPhone",required = false) Long userPhone,@PathVariable("md5") String md5) {if (userPhone == null) {return new SeckillResult(false, "未注册"); }SeckillResult result; try {SeckillExecution execution = secKillService.executeSeckill(seckillId, userPhone, md5); result = new SeckillResult(true, execution); return result; } catch (SeckillCloseException e) { // 秒杀关闭SeckillExecution execution = new SeckillExecution(seckillId, SecKillStatEnum.END); return new SeckillResult(false,execution); } catch (RepeatKillException e) { // 重复秒杀SeckillExecution execution = new SeckillExecution(seckillId, SecKillStatEnum.REPEAT_KILL); return new SeckillResult(false,execution); } catch (Exception e) {// 不是重复秒杀或秒杀结束,就返回内部错误logger.error(e.getMessage(), e); SeckillExecution execution = new SeckillExecution(seckillId, SecKillStatEnum.INNER_ERROR); return new SeckillResult(false,execution); }}@RequestMapping(value = "https://www.it610.com/time/now",method = RequestMethod.GET)@ResponseBodypublic SeckillResult time() {Date now = new Date(); return new SeckillResult(true,now.getTime()); }}

      页面
      Java秒杀系统:web层详解
      文章图片

      这里修改数据库为合适的时间来测试我们的代码。
      点击后跳转到详情页。
      Java秒杀系统:web层详解
      文章图片

      详情页涉及到比较多的交互逻辑,如cookie,秒杀成功失败等等。放到逻辑交互一节来说。
      运行时发现jackson版本出现问题,pom.xml修改为:
      com.fasterxml.jackson.corejackson-databind2.10.2

      list.jsp代码为:
      Bootstrap 模板 - 锐客网 秒杀列表
      名称库存开始时间结束时间创建时间详情页
      ${sk.name}${sk.number}link

      Java秒杀系统:web层详解
      文章图片


      逻辑交互
      身份认证
      cookie中没有手机号要弹窗,手机号不正确(11位数字)要提示错误:
      Java秒杀系统:web层详解
      文章图片

      选择提交之后要能够在cookie中看到:
      Java秒杀系统:web层详解
      文章图片

      目前为止detail.jsp:
      秒杀详情页 - 锐客网 ${seckill.name}秒杀电话:

      我们的逻辑主要写在另外的js文件中:
      seckill.js
      // 存放主要交互逻辑js// javascript 模块化var seckill={// 封装秒杀相关ajax的URLURL:{},// 验证手机号validatePhone: function (phone) {if(phone && phone.length==11 && !isNaN(phone)) {return true; } else {return false; }},// 详情页秒杀逻辑detail: {// 详情页初始化init: function (params) {// 手机验证和登录,计时交互// 规划交互流程// 在cookie中查找手机号var killPhone = $.cookie('killPhone'); var startTime = params['startTime']; var endTime = params['endTime']; var seckillId = params['seckillId']; // 验证手机号if(!seckill.validatePhone(killPhone)) {// 绑定手机号,获取弹窗输入手机号的div idvar killPhoneModal = $('#killPhoneModal'); killPhoneModal.modal({show: true, //显示弹出层backdrop: 'static',//禁止位置关闭keyboard: false, //关闭键盘事件}); $('#killPhoneBtn').click(function () {var inputPhone = $('#killphoneKey').val(); // 输入格式什么的ok了就刷新页面if(seckill.validatePhone(inputPhone)) {// 将电话写入cookie$.cookie('killPhone',inputPhone,{expires:7,path:'/seckill'}); window.location.reload(); } else {// 更好的方式是把字符串写入字典再用$('#killphoneMessage').hide().html('').show(500); }}); }// 已经登录}}}


      计时面板
      在登录完成后,处理计时操作:
      // 已经登录// 计时交互$.get(seckill.URL.now(),{},function (result) {if(result && result['success']) {var nowTime = result['data']; // 写到函数里处理seckill.countdown(seckillId,nowTime,startTime,endTime); } else {console.log('result: '+result); }});

      在countdown函数里,有三个判断,未开始、已经开始、结束。
      URL:{now: function () {return '/seckill/time/now'; }}, handleSeckill: function () {// 处理秒杀逻辑}, countdown: function (seckillId,nowTime,startTime,endTime) {var seckillBox = $('#seckillBox'); if(nowTime>endTime) {seckillBox.html('秒杀结束!'); } elseif(nowTime ### 秒杀交互 秒杀之前: ![image-20211006202253376](https://img-blog.csdnimg.cn/img_convert/7609c513cb3b64f4e710d879e57c1651.png) 详情页: Java秒杀系统:web层详解
      文章图片
      点击开始秒杀: Java秒杀系统:web层详解
      文章图片
      列表页刷新: ![image-20211006202306300](https://img-blog.csdnimg.cn/img_convert/272dac0d7f6d4a2910614551f4580aac.png) 运行时发现controller忘了写`@ResponseBody`了,这里返回的不是jsp是json,需要加上。 ```java@ResponseBodypublic SeckillResult execute(@PathVariable("seckillId") Long seckillId,// required = false表示cookie逻辑由我们程序处理,springmvc不要报错@CookieValue(value = "https://www.it610.com/article/killPhone",required = false) Long userPhone,@PathVariable("md5") String md5)

      在seckill.js中,补全秒杀逻辑:
      // 封装秒杀相关ajax的URLURL:{now: function () {return '/seckill/time/now'; },exposer: function(seckillId) {return '/seckill/'+seckillId+'/exposer'; },execution: function (seckillId,md5) {return '/seckill/'+seckillId+'/'+md5+'/execution'; }}, // id和显示计时的那个模块handleSeckill: function (seckillId,node) {// 处理秒杀逻辑// 在计时的地方显示一个秒杀按钮node.hide().html(''); // 获取秒杀地址$.post(seckill.URL.exposer(),{seckillId},function (result) {if(result && result['success']) {var exposer = result['data']; if(exposer['exposed']) {// 如果开启了秒杀// 获取秒杀地址var md5 = exposer['md5']; var killUrl = seckill.URL.execution(seckillId,md5); console.log("killurl: "+killUrl); // click永远绑定,one只绑定一次$('#killBtn').one('click',function () {// 执行秒杀请求操作// 先禁用按钮$(this).addClass('disabled'); // 发送秒杀请求$.post(killUrl,{},function (result) {if(result) {var killResult = result['data']; var state = killResult['state']; var stateInfo = killResult['stateInfo']; // 显示秒杀结果if(result['success']) {node.html(''+stateInfo+''); } else {node.html(''+stateInfo+''); } }console.log(result); })}); node.show(); } else {// 未开始秒杀,这里是因为本机显示时间和服务器时间不一致// 可能浏览器认为开始了,服务器其实还没开始var now = exposer['now']; var start = exposer['start']; var end = exposer['end']; // 重新进入倒计时逻辑seckill.countdown(seckillId,now,start,end); }} else {console.log('result='+result); }})},

      秒杀成功后再次进行秒杀则不成功:
      Java秒杀系统:web层详解
      文章图片

      输出:
      Java秒杀系统:web层详解
      文章图片

      在库存不够时也返回秒杀结束:
      Java秒杀系统:web层详解
      文章图片

      Java秒杀系统:web层详解
      文章图片

      至此,功能方面已经实现了,后面还剩下优化部分。

      总结 【Java秒杀系统:web层详解】本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注脚本之家的更多内容!

        推荐阅读