第四章 交易模块开发
4.1 交易模型管理–交易模型创建
1. 创建用户下单模型OrderModel
//用户下单的交易模型
public class OrderModel {
//交易单号,例如2019052100001212,使用string类型
private String id;
//购买的用户id
private Integer userId;
//购买的商品id
private Integer itemId;
//购买时商品的单价
private BigDecimal itemPrice;
//购买数量
private Integer amount;
//购买总金额
private BigDecimal orderPrice;
}
2. 数据库创建order_info表
名 | 类型 | 说明 |
---|---|---|
id | String | 交易号 |
userId | Integer | 购买用户id |
itemId | Integer | 购买商品id |
itemPrice | BigDecimal | 购买商品单价 |
amount | Integer | 购买数量 |
orderPrice | BigDecimal | 购买总金额 |
3. mybatis generator生成数据库映射
mybatis generator生成order_info表及OrderDO
运行 mvn mybatis-generator:generate
<table tableName="order_info" domainObjectName="OrderDO"
enableCountByExample="false"
enableUpdateByExample="false"
enableDeleteByExample="false"
enableSelectByExample="false"
selectByExampleQueryId="false" ></table>
4.2 交易模型管理-交易下单
1.OrderService接口部分
实现createOrder函数,通过用户id,商品id以及购买数量创建订单
OrderModel createOrder(Integer userId, Integer itemId, Integer amount) throws BusinessException;
2.OrderServiceImpl实现类
用户下单的流程主要包括:
- 校验下单状态,包括下单商品是否存在,用户是否合法,购买数量是否正确
- 减少库存量(落单减库存)
- 订单入库
- 返回前端
1. 校验下单状态
校验下单商品是否存在是通过商品id在itemService查询itemModel,校验用户是否合法是用userId在userService里查找userModel,校验购买数量我们规定一个用户最少购买1件最多不超过100
//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, "数量信息不存在");
}
2. 落单减库存
减少库存量有两种方式:落单减库存以及支付减库存。
对应的就是在用户下单成功还是支付成功后减少库存,支付成功后减库存因为支付时未加库存锁,所以会出现支付成功但因库存不够退单情况。而落单减库存则可能出现恶意刷单的情况。这里我们采取落单减库存的方式。
ItemStockMapper增加对item_stock数据库减操作
ItemStockMapper.xml
<update id="decreaseStock">
update item_stock
set stock = stock-#{amount}
where item_id = #{item_id} and stock>=#{amount}
</update>
ItemStockMapper
int decreaseStock(@Param("itemId") Integer itemId, @Param("amount") Integer amount);
ItemService接口
//库存扣减
boolean decreaseStock(Integer itemId,Integer amount) throws BusinessException;
ItemServiceImpl实现类
@Override
@Transactional
public boolean decreaseStock(Integer itemId, Integer amount) throws BusinessException {
int affectedRow = itemStockDOMapper.decreaseStock(itemId, amount);
if (affectedRow > 0) {
//更新库存成功
return true;
} else {
//更新库存失败
return false;
}
}
3. 生成交易订单号
交易订单号有16类,比如"2020031100000100",前8位为时间信息,年月日,中间6位为自增序列,最后2位为分库分表位,我们暂时不考虑
前8位我们用LocalDateTime类获取当前时间并转化为ISO_DATE类型的字符串
//订单号有16位
StringBuilder stringBuilder = new StringBuilder();
//前8位为时间信息,年月日
LocalDateTime now = LocalDateTime.now();
//2020-03-08
String nowDate = now.format(DateTimeFormatter.ISO_DATE).replace("-","");
stringBuilder.append(nowDate);
中间6位为自增序列,初始值为0,我们新建一个sequence_info的数据库,每次访问要加锁,表示一个下单数
sequ_info表
名 | 类型 | 说明 |
---|---|---|
name | String | 订单表 |
current_value | int | 当前订单数 |
step | int | 下单数加1 |
修改mybatis-generator生成SequenceDO,SequenceDOMapper
运行 mvn mybatis-generator:generate
<table tableName="sequence_info" domainObjectName="SequenceDO"
enableCountByExample="false"
enableUpdateByExample="false"
enableDeleteByExample="false"
enableSelectByExample="false"
selectByExampleQueryId="false" ></table>
修改SequenceDOMapper.xml,自增时用for update添加行锁
<select id="getSequenceByName" parameterType="java.lang.String" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from sequence_info
where name = #{name,jdbcType=VARCHAR} for update
</select>
生成订单号
private String generateOrderNo(){
//订单号有16位
StringBuilder stringBuilder = new StringBuilder();
//前8位为时间信息,年月日
LocalDateTime now = LocalDateTime.now();
//2020-03-08
String nowDate = now.format(DateTimeFormatter.ISO_DATE).replace("-","");
stringBuilder.append(nowDate);
//中间6位为自增序列
//新建sequence_info表,初始值为0,实现每次获取加一
//获取当前sequence
int sequence = 0;
SequenceDO sequenceDO = sequenceDOMapper.getSequenceByName("order_info");
sequence = sequenceDO.getCurrentValue();
sequenceDO.setCurrentValue(sequenceDO.getCurrentValue() + sequenceDO.getStep());
sequenceDOMapper.updateByPrimaryKeySelective(sequenceDO);
//拼足中间6位
String sequenceStr = String.valueOf(sequence);
for(int i = 0; i< 6-sequenceStr.length();i++) {
stringBuilder.append(0);
}
stringBuilder.append(sequenceStr);
//最后2位为分库分表位,暂时写死
stringBuilder.append("00");
return stringBuilder.toString();
4. 销量增加
itemDOMapper.xml
<update id="increaseSales">
update item
set sales = sales+ #{amount}
where id = #{id,jdbcType=INTEGER}
</update>
itemDOMapper
int increaseSales(@Param("id") Integer id, @Param("amount") Integer amount);
ItemServiceImpl
@Override
@Transactional
public void increaseSales(Integer itemId, Integer amount) throws BusinessException {
itemDOMapper.increaseSales(itemId,amount);
}
5. 最终的OrderServiceImpl类
注解@Transactional的作用保证事务的一致
下单函数createOrder中用到了@Transactional(propagation = Propagation.REQUIRED)表示执行代码后,不管成功与否,直接提交事务。不管该方法是否在事务中,都会开启一个新的事务,为了保证订单号的唯一性,防止下单失败后订单号的回滚
@Override
@Transactional(propagation = Propagation.REQUIRED) //propagation.REQUIRED表示执行代码后不管成功与否,直接提交事务
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);
orderModel.setPromoId(promoId);
//是秒杀活动,价格为秒杀价格,否则为平销价格
if(promoId != null) {
orderModel.setItemPrice(itemModel.getPromoModel().getPromoItemPrice());
}else {
orderModel.setItemPrice(itemModel.getPrice());
}
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;
}
3. Controller层创建下单
//封装下单请求
@RequestMapping(value = "/createorder", method = {RequestMethod.POST}, consumes = {CONTENT_TYPE_FORMED})
@ResponseBody
public CommonReturnType createOrder(@RequestParam(name = "itemId") Integer itemId,
@RequestParam(name = "amount") Integer amount) 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, amount);
return CommonReturnType.create(null);
}
4. 前端设计
____________________________________________________________【系列笔记已经更新完毕】
- SpringBoot构建电商秒杀项目(一) 基础项目的搭建
- SpringBoot构建电商秒杀项目(二) 用户模块开发
- SpringBoot构建电商秒杀项目(三) 商品模块开发
- SpringBoot构建电商秒杀项目(四)交易模块开发
- SpringBoot构建电商秒杀项目(五)秒杀模块开发
后续还有进阶课程 聚焦Java性能优化 打造亿级流量秒杀系统
欢迎来我的博客skiron.xyz来玩啊