Java秒杀系统:web层详解

网友投稿 227 2022-09-23


Java秒杀系统:web层详解

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

设计Restful接口

根据需求设计前端交互流程。

三个职位:

产品:解读用户需求,搞出需求文档

前端:不同平台的页面展示

后端:存储、展示、处理数据

前端页面流程:

详情页流程逻辑:

标准系统时间从服务器获取。

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

理论

适配器模式(Adapter Pattern),把一个类的接口变换成客户端所期待的另一种接口, Adapter模式使原本因接口不匹配(或者不兼容)而无法在一起工作的两个类能够在一起工作。

SpringMVC的handler(Controller,HttpRequestHandler,Servlet等)有多种实现方式,例如继承Controller的,基于注解控制器方式的,HttpRequestHandler方式的。由于实现方式不一样,调用方式就不确定了。

看HandlerAdapter接口有三个方法:

// 判断该适配器是否支持这个HandlerMethod

boolean 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访问

返回json数据

cookie访问:

项目整合SpringMVC

web.xml下配置springmvc需要加载的配置文件:

seckill-dispatcher

org.springframework.web.servlet.DispatcherServlet

contextConfigLocation

classpath:spring/spring-*.xml

seckill-dispatcher

/

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

使用SpringMVC实现Restful接口

新建文件:

首先是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());

@Autowired

private SecKillService secKillService;

@RequestMapping(value = "/list",method = RequestMethod.GET)

public String list(Model model) {

// list.jsp + model = modelandview

List list = secKillService.getSecKillList();

model.addAttribute("list",list);

return "list";

}

@RequestMapping(value = "/{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 = "/{seckillId}/exposer",

method = RequestMethod.POST,

produces = {"application/json;charset=UTF8"})

@ResponseBody

public 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 = "/{seckillId}/{md5}/execution",

method = RequestMethod.POST,

produces = {"application/json;charset=UTF8"})

public SeckillResult execute(

@PathVariable("seckillId") Long seckillId,

// required = false表示cookie逻辑由我们程序处理,springmvc不要报错

@CookieValue(value = "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 = "/time/now",method = RequestMethod.GET)

@ResponseBody

public SeckillResult time() {

Date now = new Date();

return new SeckillResult(true,now.getTime());

}

}

页面

这里修改数据库为合适的时间来测试我们的代码。

点击后跳转到详情页。

详情页涉及到比较多的交互逻辑,如cookie,秒杀成功失败等等。放到逻辑交互一节来说。

运行时发现jackson版本出现问题,pom.xml修改为:

com.fasterxml.jackson.core

jackson-databind

2.10.2

list.jsp代码为:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>

<%--引入jstl--%>

<%--标签通用头,写在一个具体文件,直接静态包含--%>

<%@include file="common/tag.jsp"%>

<%--静态包含:会合并过来放到这,和当前文件一起作为整个输出--%>

<%@include file="common/head.jsp"%>

<%--页面显示部分--%>

名称

库存

开始时间

结束时间

创建时间

详情页

link

逻辑交互

身份认证

cookie中没有手机号要弹窗,手机号不正确(11位数字)要提示错误:

选择提交之后要能够在cookie中看到:

目前为止detail.jsp:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>

<%--静态包含:会合并过来放到这,和当前文件一起作为整个输出--%>

<%@include file="common/head.jsp"%>

<%----%>

秒杀电话:

Submit

<%--开始写交互逻辑--%>

我们的逻辑主要写在另外的js文件中:

seckill.js

// 存放主要交互逻辑js

// javascript 模块化

var seckill={

// 封装秒杀相关ajax的URL

URL:{

},

// 验证手机号

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 id

var 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('秒杀结束!');

} else if(nowTime

### 秒杀交互

秒杀之前:

![image-20211006202253376](https://img-blog.csdnimg.cn/img_convert/7609c513cb3b64f4e710d879e57c1651.png)

详情页:

image-20211006201149488

点击开始秒杀:

image-20211006202320137

列表页刷新:

![image-20211006202306300](https://img-blog.csdnimg.cn/img_convert/272dac0d7f6d4a2910614551f4580aac.png)

运行时发现controller忘了写`@ResponseBody`了,这里返回的不是jsp是json,需要加上。

```java

@ResponseBody

public SeckillResult execute(

@PathVariable("seckillId") Long seckillId,

// required = false表示cookie逻辑由我们程序处理,springmvc不要报错

@CookieValue(value = "killPhone",required = false) Long userPhone,

@PathVariable("md5") String md5)

在seckill.js中,补全秒杀逻辑:

// 封装秒杀相关ajax的URL

URL:{

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);

}

})

},

秒杀成功后再次进行秒杀则不成功:

输出:

在库存不够时也返回秒杀结束:

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

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注我们的更多内容!


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:华为防火墙和路由器之间运行OSPF协议配置案例(可跟做)(华为配置ospf路由协议命令)
下一篇:1.BFD简介 2.静态路由调用BFD 3.OSPF调用BFD 4.VRRP调用BFD​
相关文章

 发表评论

暂时没有评论,来抢沙发吧~