SpringBoot实现Java高并发秒杀系统之Web层开发(三)

简介: SpringBoot实现Java高并发秒杀系统之Web层开发(三) 接着上一篇文章:SpringBoot实现Java高并发之Service层开发,今天我们开始讲SpringBoot实现Java高并发秒杀系统之Web层开发。

SpringBoot实现Java高并发秒杀系统之Web层开发(三)

接着上一篇文章:SpringBoot实现Java高并发之Service层开发,今天我们开始讲SpringBoot实现Java高并发秒杀系统之Web层开发。

Web层即Controller层,当然我们所说的都是在基于Spring框架的系统上而言的,传统的SSH项目中,与页面进行交互的是struts框架,但struts框架很繁琐,后来就被SpringMVC给顶替了,SpringMVC框架在与页面的交互上提供了更加便捷的方式,MVC的设计模式也是当前非常流行的一种设计模式。这次我们针对秒杀系统讲解一下秒杀系统需要和页面交互的操作和数据都涉及哪些?

本项目的源码请参看:springboot-seckill 如果觉得不错可以star一下哦(#^.^#)

本项目一共分为四个模块来讲解,具体的开发教程请看我的博客文章:

  • SpringBoot实现Java高并发秒杀系统之DAO层开发(一)

  • SpringBoot实现Java高并发秒杀系统之Service层开发(二)

  • SpringBoot实现Java高并发秒杀系统之Web层开发(三)

  • SpringBoot实现Java高并发秒杀系统之并发优化(四)

首先如果你对SpringBoot项目还是不清楚的话,我依然推荐你看一下我的这个项目:优雅的入门SpringBoot2.x,整合Mybatis实现CRUD

前端交互流程设计

编写Controller就是要搞清楚:1.页面需要什么数据?2.页面将返回给Controller什么数据?3.Controller应该返回给页面什么数据?

带着这些问题我们看一下秒杀详情页流程逻辑(不再讲基本的findById和findAll()方法):

因为整个秒杀系统中最核心的业务就是:1.减库存;2.查询订单明细。我们看一下Controller层的源码:

@Controller
@RequestMapping("/seckill")
public class SeckillController {

@Autowired
private SeckillService seckillService;

private final Logger logger = LoggerFactory.getLogger(this.getClass());

@ResponseBody
@RequestMapping("/findAll")
public List<Seckill> findAll() {
return seckillService.findAll();
}

@ResponseBody
@RequestMapping("/findById")
public Seckill findById(@RequestParam("id") Long id) {
return seckillService.findById(id);
}

@RequestMapping("/{seckillId}/detail")
public String detail(@PathVariable("seckillId") Long seckillId, Model model) {
if (seckillId == null) {
return "page/seckill";
}
Seckill seckill = seckillService.findById(seckillId);
model.addAttribute("seckill", seckill);
if (seckill == null) {
return "page/seckill";
}
return "page/seckill_detail";
}

@ResponseBody
@RequestMapping(value = "/{seckillId}/exposer",
method = RequestMethod.POST, produces = {"application/json;charset=UTF-8"})
public SeckillResult<Exposer> exposer(@PathVariable("seckillId") Long seckillId) {
SeckillResult<Exposer> result;
try {
Exposer exposer = seckillService.exportSeckillUrl(seckillId);
result = new SeckillResult<Exposer>(true, exposer);
} catch (Exception e) {
logger.error(e.getMessage(), e);
result = new SeckillResult<Exposer>(false, e.getMessage());
}
return result;
}

@RequestMapping(value = "/{seckillId}/{md5}/execution",
method = RequestMethod.POST,
produces = {"application/json;charset=UTF-8"})
@ResponseBody
public SeckillResult<SeckillExecution> execute(@PathVariable("seckillId") Long seckillId,
@PathVariable("md5") String md5,
@RequestParam("money") BigDecimal money,
@CookieValue(value = "killPhone", required = false) Long userPhone) {
if (userPhone == null) {
return new SeckillResult<SeckillExecution>(false, "未注册");
}
try {
SeckillExecution execution = seckillService.executeSeckill(seckillId, money, userPhone, md5);
return new SeckillResult<SeckillExecution>(true, execution);
} catch (RepeatKillException e) {
SeckillExecution seckillExecution = new SeckillExecution(seckillId, SeckillStatEnum.REPEAT_KILL);
return new SeckillResult<SeckillExecution>(true, seckillExecution);
} catch (SeckillCloseException e) {
SeckillExecution seckillExecution = new SeckillExecution(seckillId, SeckillStatEnum.END);
return new SeckillResult<SeckillExecution>(true, seckillExecution);
} catch (SeckillException e) {
SeckillExecution seckillExecution = new SeckillExecution(seckillId, SeckillStatEnum.INNER_ERROR);
return new SeckillResult<SeckillExecution>(true, seckillExecution);
}
}

@ResponseBody
@GetMapping(value = "/time/now")
public SeckillResult<Long> time() {
Date now = new Date();
return new SeckillResult(true, now.getTime());
}
}

下面我以问答的形式讲解一下Controller层方法的定义:

1.@ResponseBody和@RestController注解分别有什么作用?

  • @ResponseBody注解标识的方法,Spring会将此方法return的数据转换成JSON格式且不会被Spring视图解析器所扫描到,也就是此方法永不可能返回一个视图页面。且这个注解只能用在方法体上,不能用在类上。

  • @RestController注解标识的类,Spring会将其下的所有方法return的数据都转换成JSON格式且不会被Spring视图解析器扫描到,也就是此类下面的所有方法都不可能返回一个视图页面。且这个注解只能用在类上,不能用在方法体上。

2.@RequestMapping中{xx}的语法是什么?@PathVariable注解的用处是什么?

Spring框架很早就支持开发REST资源。也是就是现在我们定义的RESTful URL,在Spring框架上支持的尤为完美,我们可以在Controller中定义这样一个URL映射地址:/{id}/detail,他是合理的RESTful URL定义方式。

这种URL的特点:URL地址由动态的数据拼接组成的,而不是将所有的资源全部映射到一个路径下,比如:/article/detail。

这种URL结构的优势:我们能很容易从URL地址上判断出该地址所展示的页面是什么?比如:/1/detail就可能表示ID为1的文章的详情页,看起来设计的很清晰。

这种URL如何进行交互:我们定义了/{id}/detail这样一个URL映射地址,其对应的映射方法上就应该添加@PathVariable注解标识,如:@PathVariable("id") Long idSpring就能装配前端传递的URL中指定位置的数据并赋值给id这个参数。比如前端调用后端接口:localhost:8080/seckill/1/detail,后端存在一个映射方法:@RequestMapping("/{id}/detail"),这样就能刚好匹配上这个URL映射地址。

所以我们看一下秒杀系统的RESTful URL设计:

3.为什么要单独写一个接口用来获取当前系统时间?

由于我们开发的系统肯定不是给自己用的,我们的用户可能处于不同的时区,他们的当前系统时间也是不同的,所以我们写一个通用的时间规范:就是当前服务器的时间。

4.SeckillResult是什么?

在前面我们将Service层系统开发的时候就手动创建了很多类来封装一些通用的结果信息。而对于Controller层也会返回很多结果数据,比如传入的URL中id值为null,那么就没必要继续向下请求,而是直接给页面返回false信息。

于是我们创建:SeckillResult.java

public class SeckillResult<T> {

private boolean success;

private T data;

private String error;

public SeckillResult(boolean success, T data) {
this.success = success;
this.data = data;
}

public SeckillResult(boolean success, String error) {
this.success = success;
this.error = error;
}
}

泛型T表示可以代表不同类型的对象。这是泛型类应用很广泛的一个特性,我们调用SeckillResult类,将其中的T用什么替换那么T就表示这个替换的对象类型。

页面设计

用了哪些技术?

  1. HTML页面,用Bootstrap绘制。
  2. Thymeleaf模板引擎渲染HTML页面,使得HTML页面拥有类似JSP页面一样功能。
  3. JS方面使用原生的JQuery。

我们来看一下前端的页面设计:

本项目使用Cookie存储用户手机号的方式模拟用户登录功能,实际上没有与后端交互的操作。如果用户没有登录就打开了商品详情页会直接弹出一个手机号登录框提醒用户登录,且没有登录时无法关闭登录框的。

具体的源码请看:GitHub

思考

在从JSP页面转换到HTML页面的时候我常会遇到这么一个问题:前端如何取出来后端查询到的数据?

在之前我们写的JSP页面中,可以通过将后端查询到的数据放进request,session域对象中,JSP页面可以直接调用Java中域对象的数据,甚至可以通过EL表达式(${})来直接获取参数,但是这种方法有一个弊端:Controller必须是返回一个视图,这样才能在此视图中获取存进域对象中的数据。

而我们现在都开始用HTML页面,也无法从域对象中取出数据该怎么办呢?我这里提供两个思路:

  • 1.像本项目中一样,前端使用Thymeleaf模板引擎渲染页面,那么Thymeleaf内置很多方法如同JSP页面的EL表达式。Thymeleaf在HTML中取出域对象数据使用:<span th:text="${xx}">;在JS中取出域对象数据:var v = [[${xx}]](当然都必须是在HTML页面中,在外部JS文件中是得不到数据的)。

  • 2.使用原生js提供的location对象,我们先看一下URL的组成结构:

详细介绍请看:博文

举个栗子

function QueryUrl(name){
var reg = new RegExp("(^|&)"+ name +"=([^&]*)(&|$)");
var r = window.location.search.substr(1).match(reg);
if(r!=null)return unescape(r[2]); return null;
}

// 调用方法
alert(QueryUrl("参数名1"));


相关文章
|
12月前
|
算法 Java Go
【GoGin】(1)上手Go Gin 基于Go语言开发的Web框架,本文介绍了各种路由的配置信息;包含各场景下请求参数的基本传入接收
gin 框架中采用的路优酷是基于httprouter做的是一个高性能的 HTTP 请求路由器,适用于 Go 语言。它的设计目标是提供高效的路由匹配和低内存占用,特别适合需要高性能和简单路由的应用场景。
792 4
|
安全 Java API
Java Web 在线商城项目最新技术实操指南帮助开发者高效完成商城项目开发
本项目基于Spring Boot 3.2与Vue 3构建现代化在线商城,涵盖技术选型、核心功能实现、安全控制与容器化部署,助开发者掌握最新Java Web全栈开发实践。
928 1
|
存储 前端开发 Java
【JAVA】Java 项目实战之 Java Web 在线商城项目开发实战指南
本文介绍基于Java Web的在线商城技术方案与实现,涵盖三层架构设计、MySQL数据库建模及核心功能开发。通过Spring MVC + MyBatis + Thymeleaf实现商品展示、购物车等模块,提供完整代码示例,助力掌握Java Web项目实战技能。(238字)
1350 0
|
缓存 JavaScript 前端开发
鸿蒙5开发宝藏案例分享---Web开发优化案例分享
本文深入解读鸿蒙官方文档中的 `ArkWeb` 性能优化技巧,从预启动进程到预渲染,涵盖预下载、预连接、预取POST等八大优化策略。通过代码示例详解如何提升Web页面加载速度,助你打造流畅的HarmonyOS应用体验。内容实用,按需选用,让H5页面快到飞起!
|
JavaScript 前端开发 API
鸿蒙5开发宝藏案例分享---Web加载时延优化解析
本文深入解析了鸿蒙开发中Web加载完成时延的优化技巧,结合官方案例与实际代码,助你提升性能。核心内容包括:使用DevEco Profiler和DevTools定位瓶颈、四大优化方向(资源合并、接口预取、图片懒加载、任务拆解)及高频手段总结。同时提供性能优化黄金准则,如首屏资源控制在300KB内、关键接口响应≤200ms等,帮助开发者实现丝般流畅体验。
|
前端开发 JavaScript Shell
鸿蒙5开发宝藏案例分享---Web页面内点击响应时延分析
本文为鸿蒙开发者整理了Web性能优化的实战案例解析,结合官方文档深度扩展。内容涵盖点击响应时延核心指标(≤100ms)、性能分析工具链(如DevTools时间线、ArkUI Trace抓取)以及高频优化场景,包括递归函数优化、网络请求阻塞解决方案和setTimeout滥用问题等。同时提供进阶技巧,如首帧加速、透明动画陷阱规避及Web组件初始化加速,并通过优化前后Trace对比展示成果。最后总结了快速定位问题的方法与开发建议,助力开发者提升Web应用性能。
|
JavaScript Java 微服务
现代化 Java Web 在线商城项目技术方案与实战开发流程及核心功能实现详解
本项目基于Spring Boot 3与Vue 3构建现代化在线商城系统,采用微服务架构,整合Spring Cloud、Redis、MySQL等技术,涵盖用户认证、商品管理、购物车功能,并支持Docker容器化部署与Kubernetes编排。提供完整CI/CD流程,助力高效开发与扩展。
1256 64
|
数据采集 存储 弹性计算
高并发Java爬虫的瓶颈分析与动态线程优化方案
高并发Java爬虫的瓶颈分析与动态线程优化方案
|
前端开发 Java 数据库
Java 项目实战从入门到精通 :Java Web 在线商城项目开发指南
本文介绍了一个基于Java Web的在线商城项目,涵盖技术方案与应用实例。项目采用Spring、Spring MVC和MyBatis框架,结合MySQL数据库,实现商品展示、购物车、用户注册登录等核心功能。通过Spring Boot快速搭建项目结构,使用JPA进行数据持久化,并通过Thymeleaf模板展示页面。项目结构清晰,适合Java Web初学者学习与拓展。
755 1
|
缓存 NoSQL Java
Java Web 从入门到精通之苍穹外卖项目实战技巧
本项目为JavaWeb综合实战案例——苍穹外卖系统,涵盖Spring Boot 3、Spring Cloud Alibaba、Vue 3等主流技术栈,涉及用户认证、订单处理、Redis缓存、分布式事务、系统监控及Docker部署等核心功能,助你掌握企业级项目开发全流程。
1368 0