1、保存订单商品,2、验证参数是否合法

This commit is contained in:
2025-07-30 01:32:10 +08:00
parent 1bda2263c0
commit d3904420a9
10 changed files with 1041 additions and 15 deletions

View File

@@ -0,0 +1,215 @@
# 订单商品保存功能使用指南
## 功能概述
本功能为下单接口添加了保存订单商品的能力,支持在创建订单时同时保存多个商品项到订单商品表中。
## 前端数据结构
根据您提供的前端数据结构,系统现在支持以下格式的订单创建请求:
```json
{
"goodsItems": [
{
"goodsId": 10018,
"quantity": 1,
"payType": 1
}
],
"addressId": 10832,
"comments": "科技小王子大米年卡套餐2.5kg",
"deliveryType": 0,
"type": 0,
"totalPrice": 99.00,
"payPrice": 99.00,
"totalNum": 1,
"payType": 1,
"tenantId": 1
}
```
## 代码修改说明
### 1. OrderCreateRequest DTO 修改
`OrderCreateRequest` 类中添加了 `goodsItems` 字段:
```java
@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;
}
```
### 2. OrderBusinessService 修改
`OrderBusinessService` 中添加了保存订单商品的逻辑:
```java
// 5. 保存订单商品
saveOrderGoods(request, shopOrder);
/**
* 保存订单商品
*/
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());
}
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());
// 设置支付相关信息
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);
}
// 批量保存订单商品
boolean saved = shopOrderGoodsService.saveBatch(orderGoodsList);
if (!saved) {
throw new BusinessException("保存订单商品失败");
}
log.info("成功保存订单商品,订单号:{},商品数量:{}", shopOrder.getOrderNo(), orderGoodsList.size());
}
```
## 功能特性
### 1. 数据验证
- 商品ID不能为空
- 商品数量必须大于0
- 订单商品列表不能为空
### 2. 商品信息自动填充
- 自动从商品表获取商品名称、图片、价格等信息
- 验证商品是否存在
### 3. 状态管理
- 默认设置为未付款状态payStatus = 0
- 默认设置为未使用状态orderStatus = 0
- 默认设置为收费商品isFree = false
### 4. 事务支持
- 整个订单创建过程在同一个事务中
- 如果保存订单商品失败,整个订单创建会回滚
## 使用示例
### 单商品订单
```json
{
"goodsItems": [
{
"goodsId": 10018,
"quantity": 1,
"payType": 1
}
],
"type": 0,
"totalPrice": 99.00,
"payPrice": 99.00,
"totalNum": 1,
"payType": 1,
"tenantId": 1,
"comments": "科技小王子大米年卡套餐2.5kg"
}
```
### 多商品订单
```json
{
"goodsItems": [
{
"goodsId": 10018,
"quantity": 1,
"payType": 1
},
{
"goodsId": 10019,
"quantity": 2,
"payType": 1
}
],
"type": 0,
"totalPrice": 297.00,
"payPrice": 297.00,
"totalNum": 3,
"payType": 1,
"tenantId": 1,
"comments": "多商品订单"
}
```
## 测试建议
1. **单元测试**: 已创建 `OrderBusinessServiceTest` 测试类,包含单商品和多商品订单的测试用例
2. **集成测试**: 建议使用 Postman 或类似工具测试完整的订单创建流程
3. **数据验证**: 测试各种边界情况如商品不存在、数量为0等
## 注意事项
1. 确保商品表中存在对应的商品记录
2. 前端需要正确计算订单总金额和总数量
3. 支付类型字段在订单商品表中暂未使用,但保留了接口兼容性
4. 建议在生产环境部署前进行充分测试
## 后续优化建议
1. 添加库存检查和扣减逻辑
2. 支持商品规格SKU选择
3. 添加商品价格变动检查
4. 支持优惠券和折扣计算

View File

@@ -0,0 +1,192 @@
# 订单商品验证功能指南
## 概述
本文档介绍了订单创建时商品信息后台验证的实现方案。该方案确保了订单数据的安全性和一致性,防止前端数据被篡改。
## 主要特性
### 1. 商品信息后台验证
- ✅ 商品存在性验证
- ✅ 商品状态验证(上架/下架)
- ✅ 商品价格验证
- ✅ 库存数量验证
- ✅ 购买数量限制验证
- ✅ 订单总金额重新计算
### 2. 数据安全保障
- ✅ 防止前端价格篡改
- ✅ 使用后台查询的真实商品信息
- ✅ 订单金额一致性检查
- ✅ 详细的错误提示信息
## 实现细节
### 核心方法
#### 1. validateOrderRequest()
```java
private void validateOrderRequest(OrderCreateRequest request, User loginUser) {
// 1. 用户登录验证
if (loginUser == null) {
throw new BusinessException("用户未登录");
}
// 2. 验证商品信息并计算总金额
BigDecimal calculatedTotal = validateAndCalculateTotal(request);
// 3. 检查金额一致性
if (request.getTotalPrice() != null &&
request.getTotalPrice().subtract(calculatedTotal).abs().compareTo(new BigDecimal("0.01")) > 0) {
throw new BusinessException("订单金额计算错误,请刷新重试");
}
// 4. 使用后台计算的金额
request.setTotalPrice(calculatedTotal);
// 5. 检查租户特殊规则
// ...
}
```
#### 2. validateAndCalculateTotal()
```java
private BigDecimal validateAndCalculateTotal(OrderCreateRequest request) {
BigDecimal total = BigDecimal.ZERO;
for (OrderCreateRequest.OrderGoodsItem item : request.getGoodsItems()) {
// 获取商品信息
ShopGoods goods = shopGoodsService.getById(item.getGoodsId());
// 验证商品存在性
if (goods == null) {
throw new BusinessException("商品不存在商品ID" + item.getGoodsId());
}
// 验证商品状态
if (goods.getStatus() != 0) {
throw new BusinessException("商品已下架:" + goods.getName());
}
// 验证库存
if (goods.getStock() != null && goods.getStock() < item.getQuantity()) {
throw new BusinessException("商品库存不足:" + goods.getName());
}
// 验证购买数量限制
if (goods.getCanBuyNumber() != null && item.getQuantity() > goods.getCanBuyNumber()) {
throw new BusinessException("商品购买数量超过限制:" + goods.getName());
}
// 计算小计
BigDecimal itemTotal = goods.getPrice().multiply(new BigDecimal(item.getQuantity()));
total = total.add(itemTotal);
}
return total;
}
```
### 订单商品保存优化
```java
private void saveOrderGoods(OrderCreateRequest request, ShopOrder shopOrder) {
for (OrderCreateRequest.OrderGoodsItem item : request.getGoodsItems()) {
// 重新获取商品信息(确保数据一致性)
ShopGoods goods = shopGoodsService.getById(item.getGoodsId());
// 再次验证商品状态(防止并发问题)
if (goods.getStatus() != 0) {
throw new BusinessException("商品已下架:" + goods.getName());
}
ShopOrderGoods orderGoods = new ShopOrderGoods();
// 使用后台查询的真实数据
orderGoods.setGoodsId(item.getGoodsId());
orderGoods.setGoodsName(goods.getName());
orderGoods.setPrice(goods.getPrice()); // 使用后台价格
orderGoods.setTotalNum(item.getQuantity());
// 设置其他信息...
}
}
```
## 验证流程
```mermaid
graph TD
A[前端提交订单] --> B[validateOrderRequest]
B --> C[validateAndCalculateTotal]
C --> D[验证商品存在性]
D --> E[验证商品状态]
E --> F[验证库存数量]
F --> G[验证购买限制]
G --> H[计算总金额]
H --> I[检查金额一致性]
I --> J[保存订单]
J --> K[saveOrderGoods]
K --> L[再次验证商品状态]
L --> M[使用后台数据保存]
```
## 错误处理
### 常见错误信息
| 错误码 | 错误信息 | 说明 |
|--------|----------|------|
| 1 | 用户未登录 | 用户身份验证失败 |
| 1 | 商品不存在商品IDxxx | 商品ID无效或已删除 |
| 1 | 商品已下架xxx | 商品状态不是上架状态 |
| 1 | 商品价格异常xxx | 商品价格为空或小于等于0 |
| 1 | 商品库存不足xxx当前库存xxx | 购买数量超过可用库存 |
| 1 | 商品购买数量超过限制xxx最大购买数量xxx | 超过单次购买限制 |
| 1 | 订单金额计算错误,请刷新重试 | 前端金额与后台计算不一致 |
| 1 | 商品金额不能为0 | 计算后的总金额为0或负数 |
## 测试用例
项目包含完整的单元测试,覆盖以下场景:
1. ✅ 正常订单创建
2. ✅ 商品不存在
3. ✅ 商品已下架
4. ✅ 库存不足
5. ✅ 超过购买限制
6. ✅ 多商品金额计算
7. ✅ 金额不一致检测
运行测试:
```bash
mvn test -Dtest=OrderValidationTest
```
## 最佳实践
### 1. 安全性
- 始终使用后台查询的商品信息
- 不信任前端传入的价格数据
- 在保存前再次验证商品状态
### 2. 性能优化
- 批量查询商品信息(如果需要)
- 缓存商品基础信息(可选)
- 合理的错误提示,避免过多数据库查询
### 3. 用户体验
- 提供清晰的错误提示信息
- 支持金额小误差容忍0.01元)
- 及时更新前端商品状态
## 总结
通过后台验证商品信息的方案,我们实现了:
1. **数据安全性**:防止价格篡改,确保订单金额准确
2. **业务完整性**:完整的商品状态、库存、限制验证
3. **系统稳定性**:详细的错误处理和日志记录
4. **可维护性**:清晰的代码结构和完整的测试覆盖
这种方案比前端传递商品信息更安全可靠,是电商系统的最佳实践。