从零搭建基于SpringBoot的秒杀系统(三):首页、详情页编写
在上一篇博客中,我们已经搭好了系统的主要架构,目前已经可以跑通这个项目,接下来我们就可以把注意力都集中在代码上。本次需要创建的代码目录如下:
一、创建实体类
在entity包中创建和数据库字段对应的实体类,一共有四个实体类
package com.sdxb.secondkill.entity;
import lombok.Data;
import java.util.Date;
@Data
public class Item {
private Integer id;
private String name;
private String code;
private Long stock;
private Date purchaseTime;
private Integer isActive;
private Date createTime;
private Date updateTime;
}
package com.sdxb.secondkill.entity;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.util.Date;
@Data
public class ItemKill {
private Integer id;
private Integer itemId;
private Integer total;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
private Date startTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
private Date endTime;
private Byte isActive;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
private Date createTime;
private String itemName;
//采用服务器时间控制是否可以进行抢购
private Integer canKill;
}
package com.sdxb.secondkill.entity;
import lombok.Data;
import lombok.ToString;
import java.util.Date;
@Data
@ToString
public class ItemKillSuccess {
private String code;
private Integer itemId;
private Integer killId;
private String userId;
private Byte status;
private Date createTime;
private Integer diffTime;
}
package com.sdxb.secondkill.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import java.util.Date;
@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Integer id;
private String userName;
private String password;
private String phone;
private String email;
private Byte isActive;
private Date createTime;
private Date updateTime;
}
在正式开始业务前先创建一个BaseController,用于处理统一的错误:在controller文件夹中创建BaseController:
package com.sdxb.seckill.server.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
@RequestMapping("/base")
public class BaseController {
@RequestMapping(value = "/error",method = RequestMethod.GET)
public String error(){
return "error";
}
}
我打算在主页展示所有商品的信息,如果该商品的余量大于0并且在抢购时间内,就展示出抢购按钮。如果不符合上面的条件,则展示此商品无法被抢购的提示。首先在controller文件夹中新建ItemController
package com.sdxb.secondkill.controller;
import com.sdxb.secondkill.entity.ItemKill;
import com.sdxb.secondkill.service.Impl.ItemServiceImpl;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import java.util.List;
@Controller
public class ItemController {
private static final Logger log= LoggerFactory.getLogger(ItemController.class);
@Autowired
private ItemServiceImpl itemServiceImpl;
//获取商品列表
@RequestMapping(value = "/item",method = RequestMethod.GET)
public String list(Model model){
try{
List<ItemKill> itemKills =itemServiceImpl.getKillItems();
model.addAttribute("itemkills",itemKills);
}catch (Exception e){
log.error("获取商品列表异常",e.fillInStackTrace());
return "redirect:/base/error";
}
return "item";
}
@RequestMapping(value = "/detail/{id}",method = RequestMethod.GET)
public String detail(@PathVariable Integer id, Model model){
if (id==null||id<0){
return "redirect:/base/error";
}
try{
ItemKill itemKill=itemServiceImpl.getKillDetail(id);
model.addAttribute("itemkill",itemKill);
}catch (Exception e){
log.error("获取详情发生异常:id={}"+id);
}
return "detail";
}
}
在itemController中,自动注入ItemServiceImpl ,所有的业务都在Service中处理。在service文件夹中编写itemService接口和itemServiceImpl实现类:
package com.sdxb.secondkill.service;
import com.sdxb.secondkill.entity.ItemKill
import java.util.List;
public interface ItemService {
List<ItemKill> getKillItems();
ItemKill getKillDetail(Integer id) throws Exception;
}
ItemServiceImpl:这一段代码实现的是获取秒杀商品的列表和获取秒杀商品的详情
@Service
public class ItemServiceImpl implements ItemService {
@Autowired
private ItemKillMapper itemKillMapper;
//获取待秒杀商品的列表
@Override
public List<ItemKill> getKillItems() {
List<ItemKill> list = itemKillMapper.selectAll();
return list;
}
//获取秒杀详情
@Override
public ItemKill getKillDetail(Integer id) throws Exception {
ItemKill itemKill=itemKillMapper.selectByid(id);
if (itemKill==null){
throw new Exception("秒杀详情记录不存在");
}
return itemKill;
}
}
在上述的代码中自动注入ItemKillMapper,用来操作数据库,这里采用注解的方式:在mapper下新建ItemKillMapper
package com.sdxb.secondkill.mapper;
import com.sdxb.secondkill.entity.ItemKill;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import java.util.List;
@Mapper
public interface ItemKillMapper {
@Select("select \n" +
"a.*,\n" +
"b.name as itemName,\n" +
"(\n" +
"\tcase when(now() BETWEEN a.start_time and a.end_time and a.total>0)\n" +
"\t\tthen 1\n" +
"\telse 0\n" +
"\tend\n" +
")as cankill\n" +
"from item_kill as a left join item as b\n" +
"on a.item_id = b.id\n" +
"where a.is_active=1;")
List<ItemKill> selectAll();
@Select("select \n" +
"a.*,\n" +
"b.name as itemName,\n" +
"(\n" +
"\tcase when(now() BETWEEN a.start_time and a.end_time and a.total>0)\n" +
"\t\tthen 1\n" +
"\telse 0\n" +
"\tend\n" +
")as cankill\n" +
"from item_kill as a left join item as b\n" +
"on a.item_id = b.id\n" +
"where a.is_active=1 and a.id=#{id};")
ItemKill selectByid(Integer id);
}
讲解一下两段sql,第一段sql的目的是筛选此时服务器时间在start_time到end_time并且总量大于0的商品,如果可以抢购另cankill字段为1,否则为0
select a.*,b.name as itemName,
(
case when(now() BETWEEN a.start_time and a.end_time and a.total>0)
then 1
else 0
end
)as cankill
from item_kill as a left join item as b
on a.item_id=b.id
where a.is_active=1
前端页面不是本次项目的重点,基于BootStrap编写,Bootstrap的依赖直接使用CDN获取,不需要下载相关的css和js代码:因为到这里为止用户登陆还未做,因此默认用户id为10,后续会做修改
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"><!--引入thymeleaf-->
<html lang="en">
<head>
<meta charset="UTF-8">
<title>商品秒杀列表</title>
<!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<!-- 可选的 Bootstrap 主题文件(一般不用引入) -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
</head>
<body>
<div>
<table class="table table-hover">
<thead>
<tr>
<th>产品名称</th>
<th>剩余数量</th>
<th>抢购开始时间</th>
<th>抢购结束时间</th>
<th>详情</th>
</tr>
</thead>
<tbody>
<tr th:each="itemkill:${itemkills}">
<td th:text="${itemkill.itemName}"></td>
<td th:text="${itemkill.total}"></td>
<td th:text="${#dates.format(itemkill.startTime,'yyyy-MM-dd HH:mm:ss')}" th:pattern="${'yyyy-MM-dd HH:mm:ss'}"></td>
<td th:text="${#dates.format(itemkill.endTime,'yyyy-MM-dd HH:mm:ss')}" th:pattern="${'yyyy-MM-dd HH:mm:ss'}"></td>
<td th:if="${itemkill.canKill} eq 1">
<a th:href="@{'/detail/'+${itemkill.id}}">
详情
</a>
</td>
<td th:if="${itemkill.canKill} eq 0">
未在时间段内或容量为0
</td>
</tr>
</tbody>
</table>
</div>
<script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script>
<!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
</body>
</html>
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"><!--引入thymeleaf-->
<html lang="en">
<head>
<meta charset="UTF-8">
<title>商品秒杀列表</title>
<!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<!-- 可选的 Bootstrap 主题文件(一般不用引入) -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
</head>
<body>
<div>
<table class="table table-hover">
<thead>
<tr>
<th>产品名称</th>
<th>剩余数量</th>
<th>抢购开始时间</th>
<th>抢购结束时间</th>
<th>详情</th>
</tr>
</thead>
<tbody>
<tr>
<td th:text="${itemkill.itemName}"></td>
<td th:text="${itemkill.total}"></td>
<td th:text="${#dates.format(itemkill.startTime,'yyyy-MM-dd HH:mm:ss')}" th:pattern="${'yyyy-MM-dd HH:mm:ss'}"></td>
<td th:text="${#dates.format(itemkill.endTime,'yyyy-MM-dd HH:mm:ss')}" th:pattern="${'yyyy-MM-dd HH:mm:ss'}"></td>
<td th:if="${itemkill.canKill} eq 1">
<button th:onclick="|executekill(${itemkill.id})|">抢购</button>
</td>
<td th:if="${itemkill.canKill} eq 0">
未在时间段内或容量为0
</td>
</tr>
</tbody>
</table>
</div>
<script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script>
<!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
<script type="text/javascript">
function executekill(killid) {
$.ajax({
type:"POST",
url:"http://localhost:8080/kill/execute",
contentType:"application/json;charset=utf-8",
data:JSON.stringify({"killid":killid,"userid":10}),
dataType:"json",
success:function (res) {
if (res.code==0){
window.location.href="http://localhost:8080/kill/execute/success"
}else {
window.location.href="http://localhost:8080/kill/execute/fail"
}
},
error:function (msg) {
alert("数据提交失败");
}
})
}
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>异常</title>
</head>
<body>
<h1>出现了异常</h1>
</body>
</html>
运行项目,输入http://localhost:8080/item,可以看到首页展示如下:
只有当剩余数量和抢购在时间内才会展示详情,点击详情:
可以看到具体的信息,当点击抢购后完成抢购。这个功能将在下一节介绍。
到目前为止的代码放在https://github.com/OliverLiy/SecondKill/tree/version2.0中
我搭建了一个微信公众号《Java鱼仔》,如果你对本项目有任何疑问,欢迎在公众号中联系我,我会尽自己所能为大家解答。