1、保存订单商品,2、验证参数是否合法
This commit is contained in:
@@ -39,12 +39,12 @@ public class OrderConfigProperties {
|
||||
* 测试手机号列表
|
||||
*/
|
||||
private List<String> phoneNumbers;
|
||||
|
||||
|
||||
/**
|
||||
* 测试支付金额
|
||||
*/
|
||||
private BigDecimal testPayAmount = new BigDecimal("0.01");
|
||||
|
||||
|
||||
/**
|
||||
* 是否启用测试模式
|
||||
*/
|
||||
@@ -57,22 +57,22 @@ public class OrderConfigProperties {
|
||||
* 租户ID
|
||||
*/
|
||||
private Integer tenantId;
|
||||
|
||||
|
||||
/**
|
||||
* 租户名称
|
||||
*/
|
||||
private String tenantName;
|
||||
|
||||
|
||||
/**
|
||||
* 最小金额限制
|
||||
*/
|
||||
private BigDecimal minAmount;
|
||||
|
||||
|
||||
/**
|
||||
* 金额限制提示信息
|
||||
*/
|
||||
private String minAmountMessage;
|
||||
|
||||
|
||||
/**
|
||||
* 是否启用
|
||||
*/
|
||||
@@ -81,16 +81,22 @@ public class OrderConfigProperties {
|
||||
|
||||
@Data
|
||||
public static class DefaultConfig {
|
||||
|
||||
/**
|
||||
* 默认标题
|
||||
*/
|
||||
private String defaultTitle = "订单标题";
|
||||
|
||||
/**
|
||||
* 默认备注
|
||||
*/
|
||||
private String defaultComments = "暂无";
|
||||
|
||||
|
||||
/**
|
||||
* 最小订单金额
|
||||
*/
|
||||
private BigDecimal minOrderAmount = BigDecimal.ZERO;
|
||||
|
||||
|
||||
/**
|
||||
* 订单超时时间(分钟)
|
||||
*/
|
||||
@@ -101,8 +107,8 @@ public class OrderConfigProperties {
|
||||
* 检查是否为测试账号
|
||||
*/
|
||||
public boolean isTestAccount(String phone) {
|
||||
return testAccount.isEnabled() &&
|
||||
testAccount.getPhoneNumbers() != null &&
|
||||
return testAccount.isEnabled() &&
|
||||
testAccount.getPhoneNumbers() != null &&
|
||||
testAccount.getPhoneNumbers().contains(phone);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package com.gxwebsoft.shop.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.*;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 订单创建请求DTO
|
||||
@@ -23,6 +24,10 @@ public class OrderCreateRequest {
|
||||
@Max(value = 2, message = "订单类型值无效")
|
||||
private Integer type;
|
||||
|
||||
@Size(max = 60, message = "备注长度不能超过60个字符")
|
||||
@Schema(description = "订单标题")
|
||||
private String title;
|
||||
|
||||
@Schema(description = "快递/自提")
|
||||
private Integer deliveryType;
|
||||
|
||||
@@ -44,9 +49,15 @@ public class OrderCreateRequest {
|
||||
@Schema(description = "使用的会员卡id")
|
||||
private String cardId;
|
||||
|
||||
@Schema(description = "关联收货地址")
|
||||
private Integer addressId;
|
||||
|
||||
@Schema(description = "收货地址")
|
||||
private String address;
|
||||
|
||||
@Schema(description = "收货人姓名")
|
||||
private String realName;
|
||||
|
||||
@Schema(description = "地址纬度")
|
||||
private String addressLat;
|
||||
|
||||
@@ -129,4 +140,28 @@ public class OrderCreateRequest {
|
||||
@Schema(description = "租户id")
|
||||
@NotNull(message = "租户ID不能为空")
|
||||
private Integer tenantId;
|
||||
|
||||
@Schema(description = "订单商品列表")
|
||||
@Valid
|
||||
@NotEmpty(message = "订单商品列表不能为空")
|
||||
private List<OrderGoodsItem> goodsItems;
|
||||
|
||||
/**
|
||||
* 订单商品项
|
||||
*/
|
||||
@Data
|
||||
@Schema(name = "OrderGoodsItem", description = "订单商品项")
|
||||
public static class OrderGoodsItem {
|
||||
@Schema(description = "商品ID", required = true)
|
||||
@NotNull(message = "商品ID不能为空")
|
||||
private Integer goodsId;
|
||||
|
||||
@Schema(description = "商品数量", required = true)
|
||||
@NotNull(message = "商品数量不能为空")
|
||||
@Min(value = 1, message = "商品数量必须大于0")
|
||||
private Integer quantity;
|
||||
|
||||
@Schema(description = "支付类型")
|
||||
private Integer payType;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,9 @@ public class ShopOrder implements Serializable {
|
||||
@Schema(description = "订单类型,0商城订单 1预定订单/外卖 2会员卡")
|
||||
private Integer type;
|
||||
|
||||
@Schema(description = "订单标题")
|
||||
private String title;
|
||||
|
||||
@Schema(description = "快递/自提")
|
||||
private Integer deliveryType;
|
||||
|
||||
@@ -218,7 +221,6 @@ public class ShopOrder implements Serializable {
|
||||
private String nickname;
|
||||
|
||||
@Schema(description = "真实姓名")
|
||||
@TableField(exist = false)
|
||||
private String realName;
|
||||
|
||||
@Schema(description = "手机号码")
|
||||
|
||||
@@ -5,14 +5,19 @@ import com.gxwebsoft.common.core.exception.BusinessException;
|
||||
import com.gxwebsoft.common.system.entity.User;
|
||||
import com.gxwebsoft.shop.config.OrderConfigProperties;
|
||||
import com.gxwebsoft.shop.dto.OrderCreateRequest;
|
||||
import com.gxwebsoft.shop.entity.ShopGoods;
|
||||
import com.gxwebsoft.shop.entity.ShopOrder;
|
||||
import com.gxwebsoft.shop.entity.ShopOrderGoods;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@@ -29,6 +34,12 @@ public class OrderBusinessService {
|
||||
@Resource
|
||||
private ShopOrderService shopOrderService;
|
||||
|
||||
@Resource
|
||||
private ShopOrderGoodsService shopOrderGoodsService;
|
||||
|
||||
@Resource
|
||||
private ShopGoodsService shopGoodsService;
|
||||
|
||||
@Resource
|
||||
private OrderConfigProperties orderConfig;
|
||||
|
||||
@@ -41,6 +52,7 @@ public class OrderBusinessService {
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Map<String, String> createOrder(OrderCreateRequest request, User loginUser) {
|
||||
|
||||
// 1. 参数校验
|
||||
validateOrderRequest(request, loginUser);
|
||||
|
||||
@@ -56,7 +68,10 @@ public class OrderBusinessService {
|
||||
throw new BusinessException("订单保存失败");
|
||||
}
|
||||
|
||||
// 5. 创建微信支付订单
|
||||
// 5. 保存订单商品
|
||||
saveOrderGoods(request, shopOrder);
|
||||
|
||||
// 6. 创建微信支付订单
|
||||
try {
|
||||
return shopOrderService.createWxOrder(shopOrder);
|
||||
} catch (Exception e) {
|
||||
@@ -73,19 +88,92 @@ public class OrderBusinessService {
|
||||
throw new BusinessException("用户未登录");
|
||||
}
|
||||
|
||||
if (request.getTotalPrice() == null || request.getTotalPrice().compareTo(BigDecimal.ZERO) <= 0) {
|
||||
// 验证商品信息并计算总金额
|
||||
BigDecimal calculatedTotal = validateAndCalculateTotal(request);
|
||||
|
||||
if (calculatedTotal.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
throw new BusinessException("商品金额不能为0");
|
||||
}
|
||||
|
||||
// 检查前端传入的总金额是否正确(允许小的误差,比如0.01)
|
||||
if (request.getTotalPrice() != null &&
|
||||
request.getTotalPrice().subtract(calculatedTotal).abs().compareTo(new BigDecimal("0.01")) > 0) {
|
||||
log.warn("订单金额计算不一致,前端传入:{},后台计算:{}", request.getTotalPrice(), calculatedTotal);
|
||||
throw new BusinessException("订单金额计算错误,请刷新重试");
|
||||
}
|
||||
|
||||
// 使用后台计算的金额
|
||||
request.setTotalPrice(calculatedTotal);
|
||||
|
||||
// 检查租户特殊规则
|
||||
OrderConfigProperties.TenantRule tenantRule = orderConfig.getTenantRule(request.getTenantId());
|
||||
if (tenantRule != null && tenantRule.getMinAmount() != null) {
|
||||
if (request.getTotalPrice().compareTo(tenantRule.getMinAmount()) < 0) {
|
||||
if (calculatedTotal.compareTo(tenantRule.getMinAmount()) < 0) {
|
||||
throw new BusinessException(tenantRule.getMinAmountMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证商品信息并计算总金额
|
||||
*/
|
||||
private BigDecimal validateAndCalculateTotal(OrderCreateRequest request) {
|
||||
if (CollectionUtils.isEmpty(request.getGoodsItems())) {
|
||||
throw new BusinessException("订单商品列表不能为空");
|
||||
}
|
||||
|
||||
BigDecimal total = BigDecimal.ZERO;
|
||||
|
||||
for (OrderCreateRequest.OrderGoodsItem item : request.getGoodsItems()) {
|
||||
// 验证商品ID
|
||||
if (item.getGoodsId() == null) {
|
||||
throw new BusinessException("商品ID不能为空");
|
||||
}
|
||||
|
||||
// 验证购买数量
|
||||
if (item.getQuantity() == null || item.getQuantity() <= 0) {
|
||||
throw new BusinessException("商品购买数量必须大于0");
|
||||
}
|
||||
|
||||
// 获取商品信息
|
||||
ShopGoods goods = shopGoodsService.getById(item.getGoodsId());
|
||||
if (goods == null) {
|
||||
throw new BusinessException("商品不存在,商品ID:" + item.getGoodsId());
|
||||
}
|
||||
|
||||
// 验证商品状态
|
||||
if (goods.getStatus() == null || goods.getStatus() != 0) {
|
||||
throw new BusinessException("商品已下架:" + goods.getName());
|
||||
}
|
||||
|
||||
// 验证商品价格
|
||||
if (goods.getPrice() == null || goods.getPrice().compareTo(BigDecimal.ZERO) <= 0) {
|
||||
throw new BusinessException("商品价格异常:" + goods.getName());
|
||||
}
|
||||
|
||||
// 验证库存(如果商品有库存管理)
|
||||
if (goods.getStock() != null && goods.getStock() < item.getQuantity()) {
|
||||
throw new BusinessException("商品库存不足:" + goods.getName() + ",当前库存:" + goods.getStock());
|
||||
}
|
||||
|
||||
// 验证购买数量限制
|
||||
if (goods.getCanBuyNumber() != null && goods.getCanBuyNumber() > 0 &&
|
||||
item.getQuantity() > goods.getCanBuyNumber()) {
|
||||
throw new BusinessException("商品购买数量超过限制:" + goods.getName() + ",最大购买数量:" + goods.getCanBuyNumber());
|
||||
}
|
||||
|
||||
// 计算商品小计
|
||||
BigDecimal itemTotal = goods.getPrice().multiply(new BigDecimal(item.getQuantity()));
|
||||
total = total.add(itemTotal);
|
||||
|
||||
log.debug("商品验证通过 - ID:{},名称:{},单价:{},数量:{},小计:{}",
|
||||
goods.getGoodsId(), goods.getName(), goods.getPrice(), item.getQuantity(), itemTotal);
|
||||
}
|
||||
|
||||
log.info("订单商品验证完成,总金额:{}", total);
|
||||
return total;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建订单对象
|
||||
*/
|
||||
@@ -148,6 +236,79 @@ public class OrderBusinessService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存订单商品
|
||||
*/
|
||||
private void saveOrderGoods(OrderCreateRequest request, ShopOrder shopOrder) {
|
||||
if (CollectionUtils.isEmpty(request.getGoodsItems())) {
|
||||
log.warn("订单商品列表为空,订单号:{}", shopOrder.getOrderNo());
|
||||
return;
|
||||
}
|
||||
|
||||
List<ShopOrderGoods> orderGoodsList = new ArrayList<>();
|
||||
for (OrderCreateRequest.OrderGoodsItem item : request.getGoodsItems()) {
|
||||
// 重新获取商品信息(确保数据一致性)
|
||||
ShopGoods goods = shopGoodsService.getById(item.getGoodsId());
|
||||
if (goods == null) {
|
||||
throw new BusinessException("商品不存在,商品ID:" + item.getGoodsId());
|
||||
}
|
||||
|
||||
// 再次验证商品状态(防止并发问题)
|
||||
if (goods.getStatus() == null || goods.getStatus() != 0) {
|
||||
throw new BusinessException("商品已下架:" + goods.getName());
|
||||
}
|
||||
|
||||
ShopOrderGoods orderGoods = new ShopOrderGoods();
|
||||
|
||||
// 设置订单关联信息
|
||||
orderGoods.setOrderId(shopOrder.getOrderId());
|
||||
orderGoods.setOrderCode(shopOrder.getOrderNo());
|
||||
|
||||
// 设置商户信息
|
||||
orderGoods.setMerchantId(shopOrder.getMerchantId());
|
||||
orderGoods.setMerchantName(shopOrder.getMerchantName());
|
||||
|
||||
// 设置商品信息(使用后台查询的真实数据)
|
||||
orderGoods.setGoodsId(item.getGoodsId());
|
||||
orderGoods.setGoodsName(goods.getName());
|
||||
orderGoods.setImage(goods.getImage());
|
||||
orderGoods.setPrice(goods.getPrice()); // 使用后台查询的价格
|
||||
orderGoods.setTotalNum(item.getQuantity());
|
||||
|
||||
// 计算商品小计(用于日志记录)
|
||||
BigDecimal itemTotal = goods.getPrice().multiply(new BigDecimal(item.getQuantity()));
|
||||
|
||||
// 设置商品规格信息(如果有的话)
|
||||
if (goods.getCode() != null) {
|
||||
orderGoods.setSpec(goods.getCode()); // 使用商品编码作为规格
|
||||
}
|
||||
|
||||
// 设置支付相关信息
|
||||
orderGoods.setPayStatus(0); // 0 未付款
|
||||
orderGoods.setOrderStatus(0); // 0 未使用
|
||||
orderGoods.setIsFree(false); // 默认收费
|
||||
orderGoods.setVersion(0); // 当前版本
|
||||
|
||||
// 设置其他信息
|
||||
orderGoods.setComments(request.getComments());
|
||||
orderGoods.setUserId(shopOrder.getUserId());
|
||||
orderGoods.setTenantId(shopOrder.getTenantId());
|
||||
|
||||
orderGoodsList.add(orderGoods);
|
||||
|
||||
log.debug("准备保存订单商品 - 商品ID:{},名称:{},单价:{},数量:{},小计:{}",
|
||||
goods.getGoodsId(), goods.getName(), goods.getPrice(), item.getQuantity(), itemTotal);
|
||||
}
|
||||
|
||||
// 批量保存订单商品
|
||||
boolean saved = shopOrderGoodsService.saveBatch(orderGoodsList);
|
||||
if (!saved) {
|
||||
throw new BusinessException("保存订单商品失败");
|
||||
}
|
||||
|
||||
log.info("成功保存订单商品,订单号:{},商品数量:{}", shopOrder.getOrderNo(), orderGoodsList.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为测试账号
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user