<dependency>
<groupId>joda-timegroupId>
<artifactId>joda-timeartifactId>
<version>2.9.1version>
dependency>
DROP TABLE IF EXISTS `promo`;
CREATE TABLE `promo` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`promo_name` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '',
`start_date` datetime NOT NULL DEFAULT '2018-12-31 23:59:59' COMMENT '默认为当前时间',
`item_id` int(11) NOT NULL DEFAULT '0',
`promo_item_price` double(10,2) NOT NULL DEFAULT '0.00',
`end_date` datetime NOT NULL DEFAULT '2099-12-31 23:59:59',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
DROP TABLE IF EXISTS `order_info`;
CREATE TABLE `order_info` (
`id` varchar(32) COLLATE utf8_unicode_ci NOT NULL,
`user_id` int(11) NOT NULL DEFAULT '0',
`item_id` int(11) NOT NULL DEFAULT '0',
`item_price` double(10,2) NOT NULL DEFAULT '0.00',
`amount` int(11) NOT NULL DEFAULT '0',
`order_price` double(10,2) NOT NULL DEFAULT '0.00',
`promo_id` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
import org.joda.time.DateTime;
import java.math.BigDecimal;
public class PromoModel {
private Integer id;
//秒杀活动状态:1表示还未开始,2表示正在进行,3表示已结束
private Integer status;
//秒杀活动名称
private String promoName;
//秒杀活动的开始时间
private DateTime startDate;
//秒杀活动的结束时间
private DateTime endDate;
//秒杀活动的适用商品
private Integer itemId;
//秒杀活动的商品价格
private BigDecimal promoItemPrice;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public String getPromoName() {
return promoName;
}
public void setPromoName(String promoName) {
this.promoName = promoName;
}
public DateTime getStartDate() {
return startDate;
}
public void setStartDate(DateTime startDate) {
this.startDate = startDate;
}
public DateTime getEndDate() {
return endDate;
}
public void setEndDate(DateTime endDate) {
this.endDate = endDate;
}
public Integer getItemId() {
return itemId;
}
public void setItemId(Integer itemId) {
this.itemId = itemId;
}
public BigDecimal getPromoItemPrice() {
return promoItemPrice;
}
public void setPromoItemPrice(BigDecimal promoItemPrice) {
this.promoItemPrice = promoItemPrice;
}
}
mybatis-generator.xml
<table tableName="promo" domainObjectName="PromoDO"
enableCountByExample="false"
enableUpdateByExample="false"
enableDeleteByExample="false"
enableSelectByExample="false"
selectByExampleQueryId="false" >table>
重新生成一次
public interface PromoService {
//根据itemId获取即将进行的或正在进行的秒杀活动
PromoModel getPromoByItemId(Integer itemId);
}
@Service
public class PromoServiceImpl implements PromoService {
@Autowired
private PromoDOMapper promoDOMapper;
@Override
public PromoModel getPromoByItemId(Integer itemId) {
//获取商品对应的秒杀信息
PromoDO promoDO = promoDOMapper.selectByItemId(itemId);
//dataobject->model
PromoModel promoModel = convertFromDataObject(promoDO);
if (promoModel == null) {
return null;
}
//判断当前时间是否秒杀活动即将开始或正在进行
DateTime now = new DateTime();
if (promoModel.getStartDate().isAfterNow()) {
promoModel.setStatus(1);
} else if (promoModel.getEndDate().isBeforeNow()) {
promoModel.setStatus(3);
} else {
promoModel.setStatus(2);
}
return promoModel;
}
private PromoModel convertFromDataObject(PromoDO promoDO) {
if (promoDO == null) {
return null;
}
PromoModel promoModel = new PromoModel();
BeanUtils.copyProperties(promoDO, promoModel);
promoModel.setPromoItemPrice(new BigDecimal(promoDO.getPromoItemPrice()));
promoModel.setStartDate(new DateTime(promoDO.getStartDate()));
promoModel.setEndDate(new DateTime(promoDO.getEndDate()));
return promoModel;
}
}
PromoDO selectByItemId(Integer itemId);
<mapper namespace="com.miaoshaproject.dao.PromoDOMapper">
<resultMap id="BaseResultMap" type="com.miaoshaproject.dataobject.PromoDO">
<id column="id" jdbcType="INTEGER" property="id" />
<result column="promo_name" jdbcType="VARCHAR" property="promoName" />
<result column="start_date" jdbcType="TIMESTAMP" property="startDate" />
<result column="item_id" jdbcType="INTEGER" property="itemId" />
<result column="promo_item_price" jdbcType="DOUBLE" property="promoItemPrice" />
<result column="end_date" jdbcType="TIMESTAMP" property="endDate" />
resultMap>
<sql id="Base_Column_List">
id, promo_name, start_date, item_id, promo_item_price, end_date
sql>
<select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from promo
where id = #{id,jdbcType=INTEGER}
select>
<select id="selectByItemId" parameterType="java.lang.Integer" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from promo
where item_id = #{itemId,jdbcType=INTEGER}
select>
<delete id="deleteByPrimaryKey" parameterType="java.lang.Integer">
delete from promo
where id = #{id,jdbcType=INTEGER}
delete>
<insert id="insert" parameterType="com.miaoshaproject.dataobject.PromoDO">
insert into promo (id, promo_name, start_date,
item_id, promo_item_price, end_date
)
values (#{id,jdbcType=INTEGER}, #{promoName,jdbcType=VARCHAR}, #{startDate,jdbcType=TIMESTAMP},
#{itemId,jdbcType=INTEGER}, #{promoItemPrice,jdbcType=DOUBLE}, #{endDate,jdbcType=TIMESTAMP}
)
insert>
<insert id="insertSelective" parameterType="com.miaoshaproject.dataobject.PromoDO">
insert into promo
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="id != null">
id,
if>
<if test="promoName != null">
promo_name,
if>
<if test="startDate != null">
start_date,
if>
<if test="itemId != null">
item_id,
if>
<if test="promoItemPrice != null">
promo_item_price,
if>
<if test="endDate != null">
end_date,
if>
trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="id != null">
#{id,jdbcType=INTEGER},
if>
<if test="promoName != null">
#{promoName,jdbcType=VARCHAR},
if>
<if test="startDate != null">
#{startDate,jdbcType=TIMESTAMP},
if>
<if test="itemId != null">
#{itemId,jdbcType=INTEGER},
if>
<if test="promoItemPrice != null">
#{promoItemPrice,jdbcType=DOUBLE},
if>
<if test="endDate != null">
#{endDate,jdbcType=TIMESTAMP},
if>
trim>
insert>
<update id="updateByPrimaryKeySelective" parameterType="com.miaoshaproject.dataobject.PromoDO">
update promo
<set>
<if test="promoName != null">
promo_name = #{promoName,jdbcType=VARCHAR},
if>
<if test="startDate != null">
start_date = #{startDate,jdbcType=TIMESTAMP},
if>
<if test="itemId != null">
item_id = #{itemId,jdbcType=INTEGER},
if>
<if test="promoItemPrice != null">
promo_item_price = #{promoItemPrice,jdbcType=DOUBLE},
if>
<if test="endDate != null">
end_date = #{endDate,jdbcType=TIMESTAMP},
if>
set>
where id = #{id,jdbcType=INTEGER}
update>
<update id="updateByPrimaryKey" parameterType="com.miaoshaproject.dataobject.PromoDO">
update promo
set promo_name = #{promoName,jdbcType=VARCHAR},
start_date = #{startDate,jdbcType=TIMESTAMP},
item_id = #{itemId,jdbcType=INTEGER},
promo_item_price = #{promoItemPrice,jdbcType=DOUBLE},
end_date = #{endDate,jdbcType=TIMESTAMP}
where id = #{id,jdbcType=INTEGER}
update>
mapper>
//用于前端展示
public class ItemVO {
private Integer id;
//商品名称
private String title;
//商品价格
private BigDecimal price;
//商品的库存
private Integer stock;
//商品的描述
private String description;
//商品的销量
private Integer sales;
//商品描述图片的url
private String imgUrl;
//商品是否在秒杀活动中,以及对应的状态:0表示没有秒杀活动,1表示秒杀活动等待开始,2表示进行中
private Integer promoStatus;
//秒杀活动价格
private BigDecimal promoPrice;
//秒杀活动id
private Integer promoId;
//秒杀活动开始时间
private String startDate;
//get set 方法
}
@Controller("/item")
@RequestMapping("/item")
//跨域请求中,不能做到session共享
@CrossOrigin(origins = "http://localhost:63343",allowCredentials = "true",allowedHeaders="*")
public class ItemController extends BaseController{
@Autowired
private ItemService itemService;
//创建商品的controller
@RequestMapping(value = "/create", method = {
RequestMethod.POST}, consumes = {
CONTENT_TYPE_FORMED})
@ResponseBody
public CommonReturnType createItem(@RequestParam(name = "title") String title,
@RequestParam(name = "description") String description,
@RequestParam(name = "price") BigDecimal price,
@RequestParam(name = "stock") Integer stock,
@RequestParam(name = "imgUrl") String imgUrl) throws BusinessException {
//封装service请求用来创建商品
ItemModel itemModel = new ItemModel();
itemModel.setTitle(title);
itemModel.setDescription(description);
itemModel.setPrice(price);
itemModel.setStock(stock);
itemModel.setImgUrl(imgUrl);
ItemModel itemModelForReturn = itemService.createItem(itemModel);
ItemVO itemVO = convertVOFromModel(itemModelForReturn);
return CommonReturnType.create(itemVO);
}
//商品详情页面浏览--采用get,对服务端不产生任何变化操作
@RequestMapping(value = "/get", method = {
RequestMethod.GET})
@ResponseBody
public CommonReturnType getItem(@RequestParam(name = "id") Integer id) {
ItemModel itemModel = itemService.getItemById(id);
ItemVO itemVO = this.convertVOFromModel(itemModel);
return CommonReturnType.create(itemVO);
}
//商品列表页面浏览
@RequestMapping(value = "/list", method = {
RequestMethod.GET})
@ResponseBody
public CommonReturnType listItem() {
List<ItemModel> itemModelList = itemService.listItem();
//使用stream API将list内的itemModel转化为itemVO;
List<ItemVO> itemVOList = itemModelList.stream().map(itemModel -> {
ItemVO itemVO = this.convertVOFromModel(itemModel);
return itemVO;
}).collect(Collectors.toList());
return CommonReturnType.create(itemVOList);
}
private ItemVO convertVOFromModel(ItemModel itemModel) {
if (itemModel == null) {
return null;
}
ItemVO itemVO = new ItemVO();
BeanUtils.copyProperties(itemModel, itemVO);
if (itemModel.getPromoModel()!=null){
//有正在进行或即将进行的秒杀活动 itemVO.setPromoStatus(itemModel.getPromoModel().getStatus()); itemVO.setPromoId(itemModel.getPromoModel().getId()); itemVO.setStartDate(itemModel.getPromoModel().getStartDate().toString(DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss"))); itemVO.setPromoPrice(itemModel.getPromoModel().getPromoItemPrice());
}else {
itemVO.setPromoStatus(0);
}
return itemVO;
}
}
public class ItemModel {
private Integer id;
//商品名称
@NotBlank(message = "商品名称不能为空")
private String title;
//商品价格
@NotNull(message = "商品价格不能为空")
@Min(value = 0,message = "商品价格必须大于0")
private BigDecimal price;
//商品的库存
@NotNull(message = "库存不能不填")
private Integer stock;
//商品的描述
@NotBlank(message = "商品描述信息不能为空")
private String description;
//商品的销量
private Integer sales;
//商品描述图片的url
@NotBlank(message = "商品图片信息不能为空")
private String imgUrl;
//使用聚合模型,如果promoModel不为空,则表示其拥有还未结束的秒杀活动
private PromoModel promoModel;
//get set方法
public class PromoModel {
private Integer id;
//秒杀活动状态:1表示还未开始,2表示正在进行,3表示已结束
private Integer status;
//秒杀活动名称
private String promoName;
//秒杀活动的开始时间
private DateTime startDate;
//秒杀活动的结束时间
private DateTime endDate;
//秒杀活动的适用商品
private Integer itemId;
//秒杀活动的商品价格
private BigDecimal promoItemPrice;
}
public interface OrderService {
//1、通过前端URL上传过来秒杀活动ID,然后下单接口内校验对应id是否属于对应商品且活动已经开始 >推荐使用第一种
//2、直接在下单接口内判断对应的商品是否存在秒杀活动,若存在进行中的则以秒杀价格下单
OrderModel createOrder(Integer userId, Integer itemId, Integer promoId, Integer amount) throws BusinessException;
}
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private ItemService itemService;
@Autowired
private UserService userService;
@Autowired
private OrderDOMapper orderDOMapper;
@Autowired
private SequenceDOMapper sequenceDOMapper;
@Override
@Transactional
public OrderModel createOrder(Integer userId, Integer itemId, Integer promoId, Integer amount) throws BusinessException {
//1.校验下单状态,下单的商品是否存在,用户是否合法,购买数量是否正确
ItemModel itemModel = itemService.getItemById(itemId);
if (itemModel == null) {
throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR, "商品信息不存在");
}
UserModel userModel = userService.getUserById(userId);
if (userModel == null) {
throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR, "用户信息不存在");
}
if (amount <= 0 || amount > 99) {
throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR, "数量信息不存在");
}
//校验活动信息
if (promoId!=null){
//(1)校验对应活动是否存在这个适用商品
if (promoId.intValue() != itemModel.getPromoModel().getId()){
throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR, "活动信息不正确");
//(2)校验活动是否正在进行中
}else if (itemModel.getPromoModel().getStatus().intValue()!=2){
throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR, "活动还未开始");
}
}
//2.落单减库存
boolean result = itemService.decreaseStock(itemId, amount);
if (!result) {
throw new BusinessException(EmBusinessError.STOCK_NOT_ENOUGH);
}
//3.订单入库
OrderModel orderModel = new OrderModel();
orderModel.setUserId(userId);
orderModel.setItemId(itemId);
orderModel.setAmount(amount);
if (promoId!=null){
orderModel.setItemPrice(itemModel.getPromoModel().getPromoItemPrice());
}else {
orderModel.setItemPrice(itemModel.getPrice());
}
// orderModel.setOrderPrice(itemModel.getPrice().multiply(BigDecimal.valueOf(amount)));
orderModel.setPromoId(promoId);
// orderModel.setOrderPrice(itemModel.getPrice().multiply(new BigDecimal(amount))); orderModel.setOrderPrice(orderModel.getItemPrice().multiply(new BigDecimal(amount)));
//生成交易流水号
orderModel.setId(generateOrderNo());
OrderDO orderDO = convertFromOrderModel(orderModel);
orderDOMapper.insertSelective(orderDO);
//加上商品的销量
itemService.increaseSales(itemId, amount);
//4.返回前端
return orderModel;
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
//不管该方法是否在事务中,都会开启一个新的事务,不管外部事务是否成功
//最终都会提交掉该事务,为了保证订单号的唯一性,防止下单失败后订单号的回滚
String generateOrderNo() {
//订单有16位
StringBuilder stringBuilder = new StringBuilder();
//前8位为时间信息,年月日
LocalDateTime now = LocalDateTime.now();
String nowDate = now.format(DateTimeFormatter.ISO_DATE).replace("-", "");
stringBuilder.append(nowDate);
//中间6位为自增序列
//获取当前sequence
int sequence = 0;
SequenceDO sequenceDO = sequenceDOMapper.getSequenceByName("order_info");
sequence = sequenceDO.getCurrentValue();
sequenceDO.setCurrentValue(sequenceDO.getCurrentValue() + sequenceDO.getStep());
sequenceDOMapper.updateByPrimaryKeySelective(sequenceDO);
//拼接
String sequenceStr = String.valueOf(sequence);
for (int i = 0; i < 6 - sequenceStr.length(); i++) {
stringBuilder.append(0);
}
stringBuilder.append(sequenceStr);
//最后两位为分库分表位,暂时不考虑
stringBuilder.append("00");
return stringBuilder.toString();
}
private OrderDO convertFromOrderModel(OrderModel orderModel) {
if (orderModel == null) {
return null;
}
OrderDO orderDO = new OrderDO();
BeanUtils.copyProperties(orderModel, orderDO);
orderDO.setItemPrice(orderModel.getItemPrice().doubleValue());
orderDO.setOrderPrice(orderModel.getOrderPrice().doubleValue());
return orderDO;
}
}
@Controller("order")
@RequestMapping("/order")
@CrossOrigin(origins = "http://localhost:63343",allowCredentials = "true",allowedHeaders="*")
public class OrderController extends BaseController{
@Autowired
private OrderService orderService;
@Autowired(required = false)
// @Autowired
private HttpServletRequest httpServletRequest;
//封装下单请求
@RequestMapping(value = "/createorder", method = {
RequestMethod.POST}, consumes = {
CONTENT_TYPE_FORMED})
@ResponseBody
public CommonReturnType createOrder(@RequestParam(name = "itemId") Integer itemId,
@RequestParam(name = "amount") Integer amount,
@RequestParam(name = "promoId" ,required = false) Integer promoId) throws BusinessException {
//获取用户登录信息
Boolean isLogin = (Boolean) httpServletRequest.getSession().getAttribute("IS_LOGIN");
if (isLogin == null || !isLogin.booleanValue()) {
throw new BusinessException(EmBusinessError.USER_NOT_LOGIN, "用户还未登录,不能下单");
}
UserModel userModel = (UserModel) httpServletRequest.getSession().getAttribute("LOGIN_USER");
OrderModel orderModel = orderService.createOrder(userModel.getId(),itemId,promoId,amount);
return CommonReturnType.create(null);
}
}
DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link href="static/assets/global/plugins/bootstrap/css/bootstrap.min.css" rel="stylesheet" type="text/css"/>
<link href="static/assets/global/css/components.css" rel="stylesheet" type="text/css"/>
<link href="static/assets/admin/pages/css/login.css" rel="stylesheet" type="text/css"/>
<script src="static/assets/global/plugins/jquery-1.11.0.min.js" type="text/javascript">script>
head>
<body class="login">
<div class="content">
<h3 class="form-title">商品详情h3>
<div id="promoStartDateContainer" class="form-group">
<label style="color:blue" id="promoStatus" class="control-label">秒杀开始时间label>
<div>
<label style="color:red" class="control-label" id="promoStartDate" />
div>
div>
<div class="form-group">
<div>
<label class="control-label" id="title" />
div>
div>
<div class="form-group">
<div>
<img style="width:200px;height:auto;" id="imgUrl">
div>
div>
<div class="form-group">
<label class="control-label">商品描述label>
<div>
<label class="control-label" id="description" />
div>
div>
<div id="normalPriceContainer" class="form-group">
<label class="control-label">商品价格label>
<div>
<label class="control-label" id="price" />
div>
div>
<div id="promoPriceContainer" class="form-group">
<label style="color:red" class="control-label">秒杀价格label>
<div>
<label style="color:red" class="control-label" id="promoPrice" />
div>
div>
<div class="form-group">
<label class="control-label">商品库存label>
<div>
<label class="control-label" id="stock" />
div>
div>
<div class="form-group">
<label class="control-label">商品销量label>
<div>
<label class="control-label" id="sales" />
div>
div>
<div class="form-actions">
<button class="btn blue" id="createOrder" type="submit">
立即购买
button>
div>
div>
body>
<script>
var g_itemVO = {
};
$(document).ready(function() {
// 获取商品详情
$.ajax({
type: "GET",
url: "http://localhost:8090/item/get",
data: {
"id": getParam("id"),
},
xhrFields:{
withCredentials:true
},
success: function(data) {
if (data.status == "success") {
g_itemVO = data.data;
reloadDom();
setInterval(reloadDom, 1000);
} else {
alert("获取信息失败,原因为" + data.data.errMsg);
}
},
error: function(data) {
alert("获取信息失败,原因为" + data.responseText);
}
});
$("#createOrder").on("click", function() {
$.ajax({
type: "POST",
url: "http://localhost:8090/order/createorder",
contentType: "application/x-www-form-urlencoded",
data: {
"itemId": g_itemVO.id,
"promoId": g_itemVO.promoId,
"amount": 1,
},
xhrFields:{
withCredentials:true
},
success: function(data) {
if (data.status == "success") {
alert("下单成功");
window.location.reload();
} else {
// alert("下单失败,原因为" + data.data.errMsg + data.data.errCode);
alert("下单失败,原因为" + data.data.errMsg);
if (data.data.errCode == 20003) {
window.location.href="login.html";
}
}
},
error: function(data) {
alert("下单失败,原因为" + data.responseText);
}
});
});
});
function reloadDom() {
$("#title").text(g_itemVO.title);
$("#imgUrl").attr("src", g_itemVO.imgUrl);
$("#description").text(g_itemVO.description);
$("#price").text(g_itemVO.price);
$("#stock").text(g_itemVO.stock);
$("#sales").text(g_itemVO.sales);
if (g_itemVO.promoStatus == 1) {
// 秒杀活动还未开始
console.log(g_itemVO.startDate);
var startTime = g_itemVO.startDate.replace(new RegExp("-", "gm"), "/");
startTime = (new Date(startTime)).getTime();
var nowTime = Date.parse(new Date());
var delta = (startTime - nowTime) / 1000;
if (delta <= 0) {
// 活动开始了
g_itemVO.promoStatus = 2;
reloadDom();
}
$("#promoStartDate").text("秒杀活动将于:"+g_itemVO.startDate+" 开始售卖 倒计时:"+delta+" 秒");
$("#promoPrice").text(g_itemVO.promoPrice);
$("#createOrder").attr("disabled", true);
} else if (g_itemVO.promoStatus == 2) {
// 秒杀活动进行中
$("#promoStartDate").text("秒杀活动进行中");
$("#promoPrice").text(g_itemVO.promoPrice);
$("#createOrder").attr("disabled", false);
$("#normalPriceContainer").hide();
}
}
function getParam(paramName) {
paramValue = "", isFound = !1;
if (this.location.search.indexOf("?") == 0 && this.location.search.indexOf("=") > 1) {
arrSource = unescape(this.location.search).substring(1, this.location.search.length).split("&"), i = 0;
while (i < arrSource.length && !isFound)
arrSource[i].indexOf("=") > 0 && arrSource[i].split("=")[0].toLowerCase() == paramName.toLowerCase() && (paramValue = arrSource[i].split("=")[1], isFound = !0), i++
}
return paramValue == "" && (paramValue = null), paramValue
}
script>
html>