1.商品详情页最终目的是为了完成用户下单
一个普通用户的下单过程
先考虑交易模型,而不是数据库
建立OrderModel.java 用来解决用户下单的交易模型
import java.math.BigDecimal;
//用户下单的交易模型
public class OrderModel {
//订单号 有属性
private String id;
//购买用户的id
private Integer userId;
//购买的商品id
private Integer itemId;
//购买商品的单价
private BigDecimal itemPrice;
//购买数量
private Integer amount;
//购买金额
private BigDecimal orderAmount;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public Integer getItemId() {
return itemId;
}
public void setItemId(Integer itemId) {
this.itemId = itemId;
}
public Integer getAmount() {
return amount;
}
public void setAmount(Integer amount) {
this.amount = amount;
}
public BigDecimal getOrderAmount() {
return orderAmount;
}
public void setOrderAmount(BigDecimal orderAmount) {
this.orderAmount = orderAmount;
}
public BigDecimal getItemPrice() {
return itemPrice;
}
public void setItemPrice(BigDecimal itemPrice) {
this.itemPrice = itemPrice;
}
}
一个订单就是交易模型的一部分
id 类型为String :一般企业的订单编号有一定的属性 比如说2018年21日 下单 +编号
购买总金额=商品数量×购买商品的单价 但是商品单价在变化
购买之后 商品单价发生变化 但是买过后的总金额不变
2.对应数据库的建立
3.在mybatis-generator中添加表并生成对应的Mapper、Dao文件
<table tableName="item_stock" domainObjectName="ItemStockDO"
enableCountByExample="false"
enableUpdateByExample="false"
enableDeleteByExample="false"
enableSelectByExample="false"
selectByExampleQueryId="false"></table>
4.创建一个OrderService接口 用来处理订单
import com.miaoshaproject.service.model.OrderModel;
public interface OrderService {
OrderModel createOrder(Integer userId, Integer itemId, Integer amount);
}
5.创建实现OrderService的OrderServiceImpl
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private SequenceDOMapper sequenceDOMapper;
@Autowired
private ItemService itemService;
@Autowired
private UserService userService;
@Autowired
private OrderDOMapper orderDOMapper;
@Override
//保证创建订单在同一个事务当中
@Transactional
public OrderModel createOrder(Integer userId, Integer itemId, 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,"用户信息不存在");
}
//下单数量假设为不能小于0 不能大于99
if(amount <=0 || amount >99){
throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR,"购买的数量信息不正确");
}
// 2.落单减库存:在调用订单的createorder下单之前,先把amount数量的库存锁定给这个用户使用,若库存不够就为下单失败。下单成功则锁定库存的操作必定成功
// 支付减库存(未使用)
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);
orderModel.setItemPrice(itemModel.getPrice());
orderModel.setOrderPrice(itemModel.getPrice().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)
private String generateOrderNo(){
//订单号有16位
StringBuilder stringBuilder = new StringBuilder();
//前八位为时间信息 年月日
//获取今天日期
LocalDateTime now = LocalDateTime.now();
String nowDate= now.format(DateTimeFormatter.ISO_DATE).replace("-","");
stringBuilder.append(nowDate);
//中间六位为自增序列
//获取当前sequence
int sequence = 0;
SequenceDO sequenceDO = sequenceDOMapper.getSequenceByName("order_info");
sequence =sequenceDO.getCurrentValue();
sequenceDO.setCurrentValue(sequenceDO.getCurrentValue() + sequenceDO.getStep());
sequenceDOMapper.updateByPrimaryKeySelective(sequenceDO);
//拼接对应的sequence 凑够六位
String sequenceStr = String.valueOf(sequence);
for(int i =0; i < 6-sequenceStr.length();i++){
stringBuilder.append(0);
}
stringBuilder.append(sequenceStr);
//最后两位为分库分表为1-99 暂时写死
stringBuilder.append("00");
return stringBuilder.toString();
}
//将Model转为为一个dataobject的方式
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;
}
}
6.ItemService实现库存扣减
//库存扣减
boolean decreaseStock(Integer itemId, Integer amount) throws BusinessException;
7.ItemServiceImpl实现库存扣减
@Override
@Transactional
public boolean decreaseStock(Integer itemId, Integer amount) throws BusinessException {
//影响的条目数 affectedRow
int affectedRow = itemStockDOMapper.decreaseStock(itemId, amount);
if (affectedRow > 0) {
//更新库存成功
return true;
} else {
//更新库存失败
return false;
}
}
8.ItemStockDOMapper.xml中添加减去库存的方法
当商品id对应的这个商品的库存 大于等于 要扣减的数量的时候 执行操作
<update id="decreaseStock">
update item_stock
set stock = stock-#{amount}
where item_id = #{itemId} and stock >= #{amount}
</update>
10.ItemStockDOMapper中添加语句
int decreaseStock(@Param("itemId") Integer itemId, @Param("amount") Integer amount);
9.保证冻结操作的原则性 对item_stock加锁
如对item_id =1的商品加锁 看减完后的库存是否大于0
对item表 没有任何影响
10.建一张通用的表sequence_info
初始化一些sequence 初始值0 获取一次的sequence 就加上一定的步长
在mybatis-generator中添加对应的信息
<table tableName="sequence_info" domainObjectName="SequenceDO"
enableCountByExample="false"
enableUpdateByExample="false"
enableDeleteByExample="false"
enableSelectByExample="false"
selectByExampleQueryId="false"></table>
11.在SequenceDOMapper.xml中添加语句
<select id="getSequenceByName" resultMap="BaseResultMap" parameterType="java.lang.String" >
select
<include refid="Base_Column_List" />
from sequence_info
where name = #{name,jdbcType=VARCHAR} for update
</select>
12.在SequenceDOMapper.java中添加方法
SequenceDO getSequenceByName(String name);
13.修改getitem.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>
<title>Title</title>
</head>
<body class="login">
<div class="content">
<h3 class="form-title">商品详情</h3>
<div class="form-group">
<label class="control-label">商品名</label>
<div>
<label class="control-label" id="title" />
</div>
</div>
<div class="form-group">
<label class="control-label">商品描述</label>
<div>
<label class="control-label" id="description" />
</div>
</div>
<div class="form-group">
<label class="control-label">商品价格</label>
<div>
<label class="control-label" id="price" />
</div>
</div>
<div class="form-group">
<label class="control-label">图片</label>
<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="stock" />
</div>
</div>
<div class="form-group">
<label class="control-label">商品销量</label>
<div>
<label class="control-label" id="sales" />
</div>
<div class="form-actions">
<button class="btn blue" id="createorder" type="submit">
下单
</button>
</div>
</div>
</div>
</body>
<script>
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
}
var g_itemVO = {};
jQuery(document).ready(function () {
$("#createorder").on("click",function () {
$.ajax({
type:"POST",
contentType:"application/x-www-form-urlencoded",
url:"http://localhost:8090/order/createorder",
data:{
"itemId":g_itemVO.id,
"amount":1,
},
//允许跨域请求
xhrFields:{withCredentials:true},
success:function (data) {
if (data.status=="success") {
alert("下单成功");
window.location.reload();
}else {
alert("下单失败,原因为" + data.data.errMsg);
if(data.data.errCode == 20003){
window.location.href="login.html"
}
}
},
error:function (data) {
alert("下单失败,原因为"+data.responseText);
}
});
})
//获取商品详情
$.ajax({
type:"GET",
contentType:"application/x-www-form-urlencoded",
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();
}else {
alert("获取信息失败,原因为" + data.data.errMsg);
}
},
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);
}
</script>
</html>
14.创建OrderController
⚠ 运行了一下 报错
解决:springboot 启动报错Field XXX required a bean of type XXX that could not be found._Julycaka的博客-CSDN博客没有在OrderServiceImpl 加@Service
15.在ItemService中
//商品销量增加
void increaseSales(Integer itemId,Integer amount) throws BusinessException;
16.在ItemServiceImpl中
@Override
@Transactional
public void increaseSales(Integer itemId, Integer amount) throws BusinessException {
itemDOMapper.increaseSales(itemId,amount);
}
17.在ItemDOMapper.xml中添加sql语句
<update id="increaseSales">
update item
set sales = sales+ #{amount}
where id = #{id,jdbcType=INTEGER}
</update>
18.ItemDOMapper.java
int increaseSales(@Param("id") Integer id, @Param("amount") Integer amount);
最后运行结果为