Java秒杀系统:web层详解
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需要加载的配置文件:
在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
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
SeckillResult
try {
Exposer exposer = secKillService.exportSecKillUrl(seckillId);
result = new SeckillResult
} 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
@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
}
SeckillResult
try {
SeckillExecution execution = secKillService.executeSeckill(seckillId, userPhone, md5);
result = new SeckillResult
return result;
} catch (SeckillCloseException e) { // 秒杀关闭
SeckillExecution execution = new SeckillExecution(seckillId, SecKillStatEnum.END);
return new SeckillResult
} catch (RepeatKillException e) { // 重复秒杀
SeckillExecution execution = new SeckillExecution(seckillId, SecKillStatEnum.REPEAT_KILL);
return new SeckillResult
} catch (Exception e) {
// 不是重复秒杀或秒杀结束,就返回内部错误
logger.error(e.getMessage(), e);
SeckillExecution execution = new SeckillExecution(seckillId, SecKillStatEnum.INNER_ERROR);
return new SeckillResult
}
}
@RequestMapping(value = "/time/now",method = RequestMethod.GET)
@ResponseBody
public SeckillResult
Date now = new Date();
return new SeckillResult
}
}
页面
这里修改数据库为合适的时间来测试我们的代码。
点击后跳转到详情页。
详情页涉及到比较多的交互逻辑,如cookie,秒杀成功失败等等。放到逻辑交互一节来说。
运行时发现jackson版本出现问题,pom.xml修改为:
list.jsp代码为:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%--引入jstl--%>
<%--标签通用头,写在一个具体文件,直接静态包含--%>
<%@include file="common/tag.jsp"%>
<%--静态包含:会合并过来放到这,和当前文件一起作为整个输出--%>
<%@include file="common/head.jsp"%>
<%--页面显示部分--%>
名称 库存 开始时间 结束时间 创建时间 详情页
link