第二部分:完善基础秒杀功能
这一部分只是完成1.0版的基础秒杀功能,把功能大致完成,为后面的优化做准备。
主要完成两个功能:
- 查询商品列表及查询某一商品的详情;(GoodsController)
- 具体的秒杀行为。(MiaoshaController)
数据库设计
- 商品总表
- 秒杀商品汇总表
- 订单详情表
- 秒杀订单汇总表
对应的实体:
- 商品
public class Goods {
private Long id;
private String goodsName;
private String goodsTitle;
private String goodsImg;
private String goodsDetail;
private Double goodsPrice;
private Integer goodsStock;
//Getter and Setter...
}
- 秒杀商品
public class MiaoshaGoods {
private Long id;
private Long goodsId;
private Integer stockCount;//秒杀库存量
private Date startDate; //秒杀起始时间
private Date endDate; //秒杀结束时间
//Getter and Setter...
}
- 订单详情
public class OrderInfo {
private Long id;
private Long userId;
private Long goodsId;
private Long deliveryAddrId;
private String goodsName;
private Integer goodsCount;
private Double goodsPrice;
private Integer orderChannel; //安卓手机 or Iphone or 网页...
private Integer status; //已完成 or 待完成 or 已退货...
private Date createDate;
private Date payDate;
//Getter and Setter...
}
- 秒杀订单汇总
public class MiaoshaOrder {
private Long id;
private Long userId;
private Long orderId;
private Long goodsId;
//Getter and Setter...
}
查询商品列表以及商品详情
- Controller层:
- 查看商品列表的流程是:先得到token查询到用户,若没有token则返回登陆页面;然后查看商品列表;
- 查看商品详情的流程是:先得到token查询到用户,若没有token则返回登陆页面;然后查看商品详情(包括秒杀状态,倒计时等)。
@Controller
@RequestMapping("/goods")
public class GoodsController {
@Autowired
MiaoshaUserService userService;
@Autowired
RedisService redisService;
@Autowired
GoodsService goodsService;
@RequestMapping("/to_list")
public String list(Model model, HttpServletResponse response,
@CookieValue(value = MiaoshaUserService.COOKI_NAME_TOKEN,required = false) String cookieToken,
@RequestParam(value = MiaoshaUserService.COOKI_NAME_TOKEN,required = false) String paramToken) {
if (StringUtils.isEmpty(cookieToken) && StringUtils.isEmpty(paramToken)) {
return "login";
}
String token = StringUtils.isEmpty(paramToken)?cookieToken:paramToken;
MiaoshaUser user = userService.getByToken(response,token);//从token中读用户信息
model.addAttribute("user", user); //将user对象和goods_list.html页面中的user“关联”
//查询商品列表
List<GoodsVo> goodsList = goodsService.listGoodsVo();
model.addAttribute("goodsList", goodsList);
return "goods_list"; //返回goods_list.html
}
@RequestMapping("/to_detail/{goodsId}")
public String detail(Model model, @PathVariable("goodsId") long goodsId, HttpServletResponse response,
@CookieValue(value = MiaoshaUserService.COOKI_NAME_TOKEN,required = false) String cookieToken,
@RequestParam(value = MiaoshaUserService.COOKI_NAME_TOKEN,required = false) String paramToken) {
if (StringUtils.isEmpty(cookieToken) && StringUtils.isEmpty(paramToken)) {
return "login";
}
String token = StringUtils.isEmpty(paramToken)?cookieToken:paramToken;
MiaoshaUser user = userService.getByToken(response,token);//从token中读用户信息
model.addAttribute("user", user);
GoodsVo goods = goodsService.getGoodsVoByGoodsId(goodsId);
model.addAttribute("goods", goods);
long startAt = goods.getStartDate().getTime();//转换成毫秒值
long endAt = goods.getEndDate().getTime();
long now = System.currentTimeMillis();
int miaoshaStatus = 0;
int remainSeconds = 0;
if (now < startAt) {//秒杀还没开始,倒计时
miaoshaStatus = 0;
remainSeconds = (int) ((startAt - now) / 1000);
} else if (now > endAt) {//秒杀已经结束
miaoshaStatus = 2;
remainSeconds = -1;
} else {//秒杀进行中
miaoshaStatus = 1;
remainSeconds = 0;
}
model.addAttribute("miaoshaStatus", miaoshaStatus);
model.addAttribute("remainSeconds", remainSeconds);
return "goods_detail";
}
}
- Service层
@Service
public class GoodsService {
@Autowired
GoodsDao goodsDao;
public List<GoodsVo> listGoodsVo() {
return goodsDao.listGoodsVo();
}
public GoodsVo getGoodsVoByGoodsId(long goodsId) {
return goodsDao.getGoodsVoByGoodsId(goodsId);
}
public void reduceStock(GoodsVo goods) {
MiaoshaGoods g = new MiaoshaGoods();
g.setGoodsId(goods.getId());
goodsDao.reduceStock(g);
}
}
这里涉及到了GoodsVo,一并贴出来。
public class GoodsVo extends Goods {
private Double miaoshaPrice;
private Integer stockCount;
private Date startDate;
private Date endDate;
//Getter and Setter...
}
- Dao层
@Repository
@Mapper
public interface GoodsDao {
//查商品列表
@Select("select g.*,mg.stock_count, mg.start_date, mg.end_date,mg.miaosha_price from miaosha_goods mg left join goods g on mg.goods_id = g.id")
public List<GoodsVo> listGoodsVo();
//查商品
@Select("select g.*,mg.stock_count, mg.start_date, mg.end_date,mg.miaosha_price from miaosha_goods mg left join goods g on mg.goods_id = g.id where g.id = #{goodsId}")
public GoodsVo getGoodsVoByGoodsId(@Param("goodsId") long goodsId);
@Update("update miaosha_goods set stock_count = stock_count - 1 where goods_id = #{goodsId}")
public int reduceStock(MiaoshaGoods g);
}
以上就完成了查询商品列表和商品详情的功能啦~
秒杀功能
具体流程是:
- 根据token值在Redis中读取用户,token为空则返回登陆页面;
- 判断商品库存是否大于0;
- 判断是否已经秒杀过了(判断有无秒杀订单);
- 事务完成:减库存、创建商品订单、写入秒杀总单中。
- Controller层
@Controller
@RequestMapping("/miaosha")
public class MiaoshaController {
@Autowired
MiaoshaUserService userService;
@Autowired
RedisService redisService;
@Autowired
GoodsService goodsService;
@Autowired
OrderService orderService;
@Autowired
MiaoshaService miaoshaService;
@RequestMapping("/do_miaosha")
public String list(Model model, HttpServletResponse response, @RequestParam("goodsId") long goodsId,
@CookieValue(value = MiaoshaUserService.COOKI_NAME_TOKEN,required = false) String cookieToken,
@RequestParam(value = MiaoshaUserService.COOKI_NAME_TOKEN,required = false) String paramToken) {
if (StringUtils.isEmpty(cookieToken) && StringUtils.isEmpty(paramToken)) {
return "login";
}
String token = StringUtils.isEmpty(paramToken)?cookieToken:paramToken;
MiaoshaUser user = userService.getByToken(response,token);//从token中读用户信息
model.addAttribute("user", user);
if (user == null) {
return "login";
}
//判断库存
GoodsVo goods = goodsService.getGoodsVoByGoodsId(goodsId);
int stock = goods.getStockCount();
if (stock <= 0) {
model.addAttribute("errmsg", CodeMsg.MIAO_SHA_OVER.getMsg());
return "miaosha_fail";
}
//判断是否已经秒杀过了
MiaoshaOrder order = orderService.getMiaoshaOrderByUserIdGoodsId(user.getId(), goodsId);
if (order != null) {
model.addAttribute("errmsg", CodeMsg.REPEATE_MIAOSHA.getMsg());
return "miaosha_fail";
}
//3步:减库存 下订单 写入秒杀订单,事务中完成
OrderInfo orderInfo = miaoshaService.miaosha(user, goods);
model.addAttribute("orderInfo", orderInfo);
model.addAttribute("goods", goods);
return "order_detail";
}
}
- Service层
涉及到了两个Service层的类:miaoshaService 和 orderService。(GoodsService上面已经有了)
@Service
public class MiaoshaService {
@Autowired
GoodsService goodsService;
@Autowired
OrderService orderService;
@Transactional
public OrderInfo miaosha(MiaoshaUser user, GoodsVo goods) {
//减库存
goodsService.reduceStock(goods);
//创建商品订单并写入秒杀订单
return orderService.createOrder(user, goods);
}
}
@Service
public class OrderService {
@Autowired
OrderDao orderDao;
public MiaoshaOrder getMiaoshaOrderByUserIdGoodsId(long userId, long goodsId) {
return orderDao.getMiaoshaOrderByUserIdGoodsId(userId, goodsId);
}
@Transactional
public OrderInfo createOrder(MiaoshaUser user, GoodsVo goods) {
OrderInfo orderInfo = new OrderInfo();
orderInfo.setCreateDate(new Date());
orderInfo.setDeliveryAddrId(0L);
orderInfo.setGoodsCount(1);
orderInfo.setGoodsId(goods.getId());
orderInfo.setGoodsName(goods.getGoodsName());
orderInfo.setGoodsPrice(goods.getMiaoshaPrice());
orderInfo.setOrderChannel(1);
orderInfo.setStatus(0);
orderInfo.setUserId(user.getId());
long orderId = orderDao.insert(orderInfo);
MiaoshaOrder miaoshaOrder = new MiaoshaOrder();
miaoshaOrder.setGoodsId(goods.getId());
miaoshaOrder.setOrderId(orderId);
miaoshaOrder.setUserId(user.getId());
orderDao.insertMiaoshaOrder(miaoshaOrder);
return orderInfo;
}
}
- Dao层
@Repository
@Mapper
public interface OrderDao {
@Select("select * from miaosha_order where user_id=#{userId} and goods_id=#{goodsId}")
public MiaoshaOrder getMiaoshaOrderByUserIdGoodsId(@Param("userId") long userId, @Param("goodsId") long goodsId);
@Insert("insert into order_info(user_id, goods_id, goods_name, goods_count, goods_price, order_channel, status, create_date)values("
+ "#{userId}, #{goodsId}, #{goodsName}, #{goodsCount}, #{goodsPrice}, #{orderChannel},#{status},#{createDate} )")
@SelectKey(keyColumn = "id", keyProperty = "id", resultType = long.class, before = false, statement = "select last_insert_id()")//把上次插入的id返回
public long insert(OrderInfo orderInfo);
@Insert("insert into miaosha_order (user_id, goods_id, order_id)values(#{userId}, #{goodsId}, #{orderId})")
public int insertMiaoshaOrder(MiaoshaOrder miaoshaOrder);
}
到此为止,就把基本功能实现了,后面就不断完善功能啦~