【电商项目】---订单模块
前言
订单模块开发:创建订单,取消订单
复杂订单状态设计
聚合支付中心
提交订单-支付方式
@ApiOperation(value = “用户下单”, notes = “用户下单”, httpMethod = “POST”)
@PostMapping("/create")
public ZCWJSONResult create(
@RequestBody SubmitOrderBO submitOrderBO,
HttpServletRequest request,
HttpServletResponse response) {
if (submitOrderBO.getPayMethod() != PayMethod.WEIXIN.type
&& submitOrderBO.getPayMethod() != PayMethod.ALIPAY.type ) {
return ZCWJSONResult.errorMsg("支付方式不支持!");
}
// System.out.println(submitOrderBO.toString());
// 1. 创建订单
OrderVO orderVO = orderService.createOrder(submitOrderBO);
String orderId = orderVO.getOrderId();
// 2. 创建订单以后,移除购物车中已结算(已提交)的商品
/** * 1001 * 2002 -> 用户购买 * 3003 -> 用户购买 * 4004 */
// TODO 整合redis之后,完善购物车中的已结算商品清除,并且同步到前端的cookie
// CookieUtils.setCookie(request, response, FOODIE_SHOPCART, “”, true);
// 3. 向支付中心发送当前订单,用于保存支付中心的订单数据
MerchantOrdersVO merchantOrdersVO = orderVO.getMerchantOrdersVO();
merchantOrdersVO.setReturnUrl(payReturnUrl);
// 为了方便测试购买,所以所有的支付金额都统一改为1分钱
merchantOrdersVO.setAmount(1);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.add("imoocUserId","imooc");
headers.add("password","imooc");
HttpEntity<MerchantOrdersVO> entity =
new HttpEntity<>(merchantOrdersVO, headers);
ResponseEntity<ZCWJSONResult> responseEntity =
restTemplate.postForEntity(paymentUrl,
entity,
ZCWJSONResult.class);
ZCWJSONResult paymentResult = responseEntity.getBody();
if (paymentResult.getStatus() != 200) {
logger.error("发送错误:{}", paymentResult.getMsg());
return ZCWJSONResult.errorMsg("支付中心订单创建失败,请联系管理员!");
}
return ZCWJSONResult.ok(orderId);
}
/* 用于创建订单的BO对象 */
public class SubmitOrderBO {private String userId;
private String itemSpecIds;
private String addressId;
private Integer payMethod;
private String leftMsg;
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getItemSpecIds() {
return itemSpecIds;
}
public void setItemSpecIds(String itemSpecIds) {
this.itemSpecIds = itemSpecIds;
}
public String getAddressId() {
return addressId;
}
public void setAddressId(String addressId) {
this.addressId = addressId;
}
public Integer getPayMethod() {
return payMethod;
}
public void setPayMethod(Integer payMethod) {
this.payMethod = payMethod;
}
public String getLeftMsg() {
return leftMsg;
}
public void setLeftMsg(String leftMsg) {
this.leftMsg = leftMsg;
}
@Override
public String toString() {
return "SubmitOrderBO{" +
"userId='" + userId + '\'' +
", itemSpecIds='" + itemSpecIds + '\'' +
", addressId='" + addressId + '\'' +
", payMethod=" + payMethod +
", leftMsg='" + leftMsg + '\'' +
'}';
}
}
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.ObjectMapper;/* @Title: ZCWJSONResult.java @Package com.zcw.utils @Description: 自定义响应数据结构 本类可提供给 H5/ios/安卓/公众号/小程序 使用 前端接受此类数据(json object)后,可自行根据业务去实现相关功能 200:表示成功 500:表示错误,错误信息在msg字段中 501:bean验证错误,不管多少个错误都以map形式返回 502:拦截器拦截到用户token出错 555:异常抛出信息 556: 用户qq校验异常 @Copyright: Copyright (c) 2020 @Company: www.zcw.com @author Zhaocunwei @version V1.0 */
public class ZCWJSONResult {// 定义jackson对象
private static final ObjectMapper MAPPER = new ObjectMapper();
// 响应业务状态
private Integer status;
// 响应消息
private String msg;
// 响应中的数据
private Object data;
@JsonIgnore
private String ok; // 不使用
public static ZCWJSONResult build(Integer status, String msg, Object data) {
return new ZCWJSONResult(status, msg, data);
}
public static ZCWJSONResult build(Integer status, String msg, Object data, String ok) {
return new ZCWJSONResult(status, msg, data, ok);
}
public static ZCWJSONResult ok(Object data) {
return new ZCWJSONResult(data);
}
public static ZCWJSONResult ok() {
return new ZCWJSONResult(null);
}
public static ZCWJSONResult errorMsg(String msg) {
return new ZCWJSONResult(500, msg, null);
}
public static ZCWJSONResult errorMap(Object data) {
return new ZCWJSONResult(501, "error", data);
}
public static ZCWJSONResult errorTokenMsg(String msg) {
return new ZCWJSONResult(502, msg, null);
}
public static ZCWJSONResult errorException(String msg) {
return new ZCWJSONResult(555, msg, null);
}
public static ZCWJSONResult errorUserQQ(String msg) {
return new ZCWJSONResult(556, msg, null);
}
public ZCWJSONResult() {
}
public ZCWJSONResult(Integer status, String msg, Object data) {
this.status = status;
this.msg = msg;
this.data = data;
}
public ZCWJSONResult(Integer status, String msg, Object data, String ok) {
this.status = status;
this.msg = msg;
this.data = data;
this.ok = ok;
}
public ZCWJSONResult(Object data) {
this.status = 200;
this.msg = "OK";
this.data = data;
}
public Boolean isOK() {
return this.status == 200;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public String getOk() {
return ok;
}
public void setOk(String ok) {
this.ok = ok;
}
}
/* @Description: 支付方式 枚举 */
public enum PayMethod {WEIXIN(1, "微信"),
ALIPAY(2, "支付宝");
public final Integer type;
public final String value;
PayMethod(Integer type, String value){
this.type = type;
this.value = value;
}
}
创建订单-填充新订单数据
public interface OrderService {
/** * 用于创建订单相关信息 * @param submitOrderBO */
public OrderVO createOrder(SubmitOrderBO submitOrderBO);
/** * 修改订单状态 * @param orderId * @param orderStatus */
public void updateOrderStatus(String orderId, Integer orderStatus);
/** * 查询订单状态 * @param orderId * @return */
public OrderStatus queryOrderStatusInfo(String orderId);
/** * 关闭超时未支付订单 */
public void closeOrder();
}
impl
@Service
public class OrderServiceImpl implements OrderService {@Autowired
private OrdersMapper ordersMapper;
@Autowired
private OrderItemsMapper orderItemsMapper;
@Autowired
private OrderStatusMapper orderStatusMapper;
@Autowired
private AddressService addressService;
@Autowired
private ItemService itemService;
@Autowired
private Sid sid;
@Transactional(propagation = Propagation.REQUIRED)
@Override
public OrderVO createOrder(SubmitOrderBO submitOrderBO) {
String userId = submitOrderBO.getUserId();
String addressId = submitOrderBO.getAddressId();
String itemSpecIds = submitOrderBO.getItemSpecIds();
Integer payMethod = submitOrderBO.getPayMethod();
String leftMsg = submitOrderBO.getLeftMsg();
// 包邮费用设置为0
Integer postAmount = 0;
String orderId = sid.nextShort();
UserAddress address = addressService.queryUserAddres(userId, addressId);
// 1. 新订单数据保存
Orders newOrder = new Orders();
newOrder.setId(orderId);
newOrder.setUserId(userId);
newOrder.setReceiverName(address.getReceiver());
newOrder.setReceiverMobile(address.getMobile());
newOrder.setReceiverAddress(address.getProvince() + " "
+ address.getCity() + " "
+ address.getDistrict() + " "
+ address.getDetail());
// newOrder.setTotalAmount();
// newOrder.setRealPayAmount();newOrder.setPostAmount(postAmount);
newOrder.setPayMethod(payMethod);
newOrder.setLeftMsg(leftMsg);
newOrder.setIsComment(YesOrNo.NO.type);
newOrder.setIsDelete(YesOrNo.NO.type);
newOrder.setCreatedTime(new Date());
newOrder.setUpdatedTime(new Date());
// 2. 循环根据itemSpecIds保存订单商品信息表
String itemSpecIdArr[] = itemSpecIds.split(",");
Integer totalAmount = 0; // 商品原价累计
Integer realPayAmount = 0; // 优惠后的实际支付价格累计
for (String itemSpecId : itemSpecIdArr) {
// TODO 整合redis后,商品购买的数量重新从redis的购物车中获取
int buyCounts = 1;
// 2.1 根据规格id,查询规格的具体信息,主要获取价格
ItemsSpec itemSpec = itemService.queryItemSpecById(itemSpecId);
totalAmount += itemSpec.getPriceNormal() * buyCounts;
realPayAmount += itemSpec.getPriceDiscount() * buyCounts;
// 2.2 根据商品id,获得商品信息以及商品图片
String itemId = itemSpec.getItemId();
Items item = itemService.queryItemById(itemId);
String imgUrl = itemService.queryItemMainImgById(itemId);
// 2.3 循环保存子订单数据到数据库
String subOrderId = sid.nextShort();
OrderItems subOrderItem = new OrderItems();
subOrderItem.setId(subOrderId);
subOrderItem.setOrderId(orderId);
subOrderItem.setItemId(itemId);
subOrderItem.setItemName(item.getItemName());
subOrderItem.setItemImg(imgUrl);
subOrderItem.setBuyCounts(buyCounts);
subOrderItem.setItemSpecId(itemSpecId);
subOrderItem.setItemSpecName(itemSpec.getName());
subOrderItem.setPrice(itemSpec.getPriceDiscount());
orderItemsMapper.insert(subOrderItem);
// 2.4 在用户提交订单以后,规格表中需要扣除库存
itemService.decreaseItemSpecStock(itemSpecId, buyCounts);
}
newOrder.setTotalAmount(totalAmount);
newOrder.setRealPayAmount(realPayAmount);
ordersMapper.insert(newOrder);
// 3. 保存订单状态表
OrderStatus waitPayOrderStatus = new OrderStatus();
waitPayOrderStatus.setOrderId(orderId);
waitPayOrderStatus.setOrderStatus(OrderStatusEnum.WAIT_PAY.type);
waitPayOrderStatus.setCreatedTime(new Date());
orderStatusMapper.insert(waitPayOrderStatus);
// 4. 构建商户订单,用于传给支付中心
MerchantOrdersVO merchantOrdersVO = new MerchantOrdersVO();
merchantOrdersVO.setMerchantOrderId(orderId);
merchantOrdersVO.setMerchantUserId(userId);
merchantOrdersVO.setAmount(realPayAmount + postAmount);
merchantOrdersVO.setPayMethod(payMethod);
// 5. 构建自定义订单vo
OrderVO orderVO = new OrderVO();
orderVO.setOrderId(orderId);
orderVO.setMerchantOrdersVO(merchantOrdersVO);
return orderVO;
}
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void updateOrderStatus(String orderId, Integer orderStatus) {
OrderStatus paidStatus = new OrderStatus();
paidStatus.setOrderId(orderId);
paidStatus.setOrderStatus(orderStatus);
paidStatus.setPayTime(new Date());
orderStatusMapper.updateByPrimaryKeySelective(paidStatus);
}
@Transactional(propagation = Propagation.SUPPORTS)
@Override
public OrderStatus queryOrderStatusInfo(String orderId) {
return orderStatusMapper.selectByPrimaryKey(orderId);
}
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void closeOrder() {
// 查询所有未付款订单,判断时间是否超时(1天),超时则关闭交易
OrderStatus queryOrder = new OrderStatus();
queryOrder.setOrderStatus(OrderStatusEnum.WAIT_PAY.type);
List<OrderStatus> list = orderStatusMapper.select(queryOrder);
for (OrderStatus os : list) {
// 获得订单创建时间
Date createdTime = os.getCreatedTime();
// 和当前时间进行对比
int days = DateUtil.daysBetween(createdTime, new Date());
if (days >= 1) {
// 超过1天,关闭订单
doCloseOrder(os.getOrderId());
}
}
}
@Transactional(propagation = Propagation.REQUIRED)
void doCloseOrder(String orderId) {
OrderStatus close = new OrderStatus();
close.setOrderId(orderId);
close.setOrderStatus(OrderStatusEnum.CLOSE.type);
close.setCloseTime(new Date());
orderStatusMapper.updateByPrimaryKeySelective(close);
}
}
/** * @Desc: 是否 枚举 */
public enum YesOrNo {
NO(0, "否"),
YES(1, "是");
public final Integer type;
public final String value;
YesOrNo(Integer type, String value) {
this.type = type;
this.value = value;
}
}
/** * @Description: 订单状态 枚举 */
public enum OrderStatusEnum {
WAIT_PAY(10, "待付款"),
WAIT_DELIVER(20, "已付款,待发货"),
WAIT_RECEIVE(30, "已发货,待收货"),
SUCCESS(40, "交易成功"),
CLOSE(50, "交易关闭");
public final Integer type;
public final String value;
OrderStatusEnum(Integer type, String value){
this.type = type;
this.value = value;
}
}
商品信息
public interface ItemService {
/** * 根据商品ID查询详情 * @param itemId * @return */
public Items queryItemById(String itemId);
/** * 根据商品id查询商品图片列表 * @param itemId * @return */
public List<ItemsImg> queryItemImgList(String itemId);
/** * 根据商品id查询商品规格 * @param itemId * @return */
public List<ItemsSpec> queryItemSpecList(String itemId);
/** * 根据商品id查询商品参数 * @param itemId * @return */
public ItemsParam queryItemParam(String itemId);
/** * 根据商品id查询商品的评价等级数量 * @param itemId */
public CommentLevelCountsVO queryCommentCounts(String itemId);
/** * 根据商品id查询商品的评价(分页) * @param itemId * @param level * @return */
public PagedGridResult queryPagedComments(String itemId, Integer level,
Integer page, Integer pageSize);
/** * 搜索商品列表 * @param keywords * @param sort * @param page * @param pageSize * @return */
public PagedGridResult searhItems(String keywords, String sort,
Integer page, Integer pageSize);
/** * 根据分类id搜索商品列表 * @param catId * @param sort * @param page * @param pageSize * @return */
public PagedGridResult searhItems(Integer catId, String sort,
Integer page, Integer pageSize);
/** * 根据规格ids查询最新的购物车中商品数据(用于刷新渲染购物车中的商品数据) * @param specIds * @return */
public List<ShopcartVO> queryItemsBySpecIds(String specIds);
/** * 根据商品规格id获取规格对象的具体信息 * @param specId * @return */
public ItemsSpec queryItemSpecById(String specId);
/** * 根据商品id获得商品图片主图url * @param itemId * @return */
public String queryItemMainImgById(String itemId);
/** * 减少库存 * @param specId * @param buyCounts */
public void decreaseItemSpecStock(String specId, int buyCounts);
@Service
public class ItemServiceImpl implements ItemService {
@Autowired
private ItemsMapper itemsMapper;
@Autowired
private ItemsImgMapper itemsImgMapper;
@Autowired
private ItemsSpecMapper itemsSpecMapper;
@Autowired
private ItemsParamMapper itemsParamMapper;
@Autowired
private ItemsCommentsMapper itemsCommentsMapper;
@Autowired
private ItemsMapperCustom itemsMapperCustom;
@Transactional(propagation = Propagation.SUPPORTS)
@Override
public Items queryItemById(String itemId) {
return itemsMapper.selectByPrimaryKey(itemId);
}
@Transactional(propagation = Propagation.SUPPORTS)
@Override
public List<ItemsImg> queryItemImgList(String itemId) {
Example itemsImgExp = new Example(ItemsImg.class);
Example.Criteria criteria = itemsImgExp.createCriteria();
criteria.andEqualTo("itemId", itemId);
return itemsImgMapper.selectByExample(itemsImgExp);
}
@Transactional(propagation = Propagation.SUPPORTS)
@Override
public List<ItemsSpec> queryItemSpecList(String itemId) {
Example itemsSpecExp = new Example(ItemsSpec.class);
Example.Criteria criteria = itemsSpecExp.createCriteria();
criteria.andEqualTo("itemId", itemId);
return itemsSpecMapper.selectByExample(itemsSpecExp);
}
@Transactional(propagation = Propagation.SUPPORTS)
@Override
public ItemsParam queryItemParam(String itemId) {
Example itemsParamExp = new Example(ItemsParam.class);
Example.Criteria criteria = itemsParamExp.createCriteria();
criteria.andEqualTo("itemId", itemId);
return itemsParamMapper.selectOneByExample(itemsParamExp);
}
@Transactional(propagation = Propagation.SUPPORTS)
@Override
public CommentLevelCountsVO queryCommentCounts(String itemId) {
Integer goodCounts = getCommentCounts(itemId, CommentLevel.GOOD.type);
Integer normalCounts = getCommentCounts(itemId, CommentLevel.NORMAL.type);
Integer badCounts = getCommentCounts(itemId, CommentLevel.BAD.type);
Integer totalCounts = goodCounts + normalCounts + badCounts;
CommentLevelCountsVO countsVO = new CommentLevelCountsVO();
countsVO.setTotalCounts(totalCounts);
countsVO.setGoodCounts(goodCounts);
countsVO.setNormalCounts(normalCounts);
countsVO.setBadCounts(badCounts);
return countsVO;
}
@Transactional(propagation = Propagation.SUPPORTS)
Integer getCommentCounts(String itemId, Integer level) {
ItemsComments condition = new ItemsComments();
condition.setItemId(itemId);
if (level != null) {
condition.setCommentLevel(level);
}
return itemsCommentsMapper.selectCount(condition);
}
@Transactional(propagation = Propagation.SUPPORTS)
@Override
public PagedGridResult queryPagedComments(String itemId,
Integer level,
Integer page,
Integer pageSize) {
Map<String, Object> map = new HashMap<>();
map.put("itemId", itemId);
map.put("level", level);
// mybatis-pagehelper
/** * page: 第几页 * pageSize: 每页显示条数 */
PageHelper.startPage(page, pageSize);
List<ItemCommentVO> list = itemsMapperCustom.queryItemComments(map);
for (ItemCommentVO vo : list) {
vo.setNickname(DesensitizationUtil.commonDisplay(vo.getNickname()));
}
return setterPagedGrid(list, page);
}
private PagedGridResult setterPagedGrid(List<?> list, Integer page) {
PageInfo<?> pageList = new PageInfo<>(list);
PagedGridResult grid = new PagedGridResult();
grid.setPage(page);
grid.setRows(list);
grid.setTotal(pageList.getPages());
grid.setRecords(pageList.getTotal());
return grid;
}
@Transactional(propagation = Propagation.SUPPORTS)
@Override
public PagedGridResult searhItems(String keywords, String sort, Integer page, Integer pageSize) {
Map<String, Object> map = new HashMap<>();
map.put("keywords", keywords);
map.put("sort", sort);
PageHelper.startPage(page, pageSize);
List<SearchItemsVO> list = itemsMapperCustom.searchItems(map);
return setterPagedGrid(list, page);
}
@Transactional(propagation = Propagation.SUPPORTS)
@Override
public PagedGridResult searhItems(Integer catId, String sort, Integer page, Integer pageSize) {
Map<String, Object> map = new HashMap<>();
map.put("catId", catId);
map.put("sort", sort);
PageHelper.startPage(page, pageSize);
List<SearchItemsVO> list = itemsMapperCustom.searchItemsByThirdCat(map);
return setterPagedGrid(list, page);
}
@Transactional(propagation = Propagation.SUPPORTS)
@Override
public List<ShopcartVO> queryItemsBySpecIds(String specIds) {
String ids[] = specIds.split(",");
List<String> specIdsList = new ArrayList<>();
Collections.addAll(specIdsList, ids);
return itemsMapperCustom.queryItemsBySpecIds(specIdsList);
}
@Transactional(propagation = Propagation.SUPPORTS)
@Override
public ItemsSpec queryItemSpecById(String specId) {
return itemsSpecMapper.selectByPrimaryKey(specId);
}
@Transactional(propagation = Propagation.SUPPORTS)
@Override
public String queryItemMainImgById(String itemId) {
ItemsImg itemsImg = new ItemsImg();
itemsImg.setItemId(itemId);
itemsImg.setIsMain(YesOrNo.YES.type);
ItemsImg result = itemsImgMapper.selectOne(itemsImg);
return result != null ? result.getUrl() : "";
}
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void decreaseItemSpecStock(String specId, int buyCounts) {
// synchronized 不推荐使用,集群下无用,性能低下
// 锁数据库: 不推荐,导致数据库性能低下
// 分布式锁 zookeeper redis
// lockUtil.getLock(); -- 加锁
// 1. 查询库存
// int stock = 10;
// 2. 判断库存,是否能够减少到0以下
// if (stock - buyCounts < 0) {
// 提示用户库存不够
// 10 - 3 -3 - 5 = -1
// }
// lockUtil.unLock(); -- 解锁
int result = itemsMapperCustom.decreaseItemSpecStock(specId, buyCounts);
if (result != 1) {
throw new RuntimeException("订单创建失败,原因:库存不足!");
}
}
}
封装分页对象
import java.util.List;
/** * * @Title: PagedGridResult.java * @Package com.zcw.utils * @Description: 用来返回分页Grid的数据格式 * Copyright: Copyright (c) 2020 */
public class PagedGridResult {
private int page; // 当前页数
private int total; // 总页数
private long records; // 总记录数
private List<?> rows; // 每行显示的内容
public int getPage() {
return page;
}
public void setPage(int page) {
this.page = page;
}
public int getTotal() {
return total;
}
public void setTotal(int total) {
this.total = total;
}
public long getRecords() {
return records;
}
public void setRecords(long records) {
this.records = records;
}
public List<?> getRows() {
return rows;
}
public void setRows(List<?> rows) {
this.rows = rows;
}
}
此语句执行时,会触发MySQL的乐观锁
update
items_spec
set
stock = stock - #{ pendingCounts}
where
id = #{ specId}
and
stock >= #{ pendingCounts}
public int decreaseItemSpecStock(@Param(“specId”) String specId,
@Param("pendingCounts") int pendingCounts);
还没有评论,来说两句吧...