这是我参与11月更文挑战的第16天,活动详情查看:2021最后一次更文挑战
一、订单详情管理
1.1 创建订单控制器
运行命令php artisan make:controller Web/OrderController
创建订单控制器: 写入订单管理相关的方法:
<?php
namespace App\Http\Controllers\Web;
use App\Http\Controllers\BaseController;
use App\Models\Cart;
use App\Models\Order;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class OrderController extends BaseController
{
/**
* 订单列表
*/
public function index(Request $request) {
// 状态查询
$status = $request->query('status') ;
// 根据标题
$title = $request->query('title');
$order = Order::where('user_id', auth('api')->id())
->when($status, function ($query) use ($status) {
$query->where('status', $status);
})
->when($status, function ($query) use ($title) {
$query->whereHas('goods', function ($query) use($title) {
$query->where('title', 'like', "%{$title}%");
}); // 向关联加入自定义约束
})
->paginate(3);
return $this->response->paginator($order, new OrderTransformer());
}
/**
* 预览订单
*/
public function preview()
{
// 地址数据
// TODO 暂时模拟地址数据
$address = [
['id' => 1, 'name' => '张三', 'address' => '厦门', 'phone' => '123123'],
['id' => 2, 'name' => '李四', 'address' => '厦门', 'phone' => '123123'],
['id' => 3, 'name' => '王武', 'address' => '厦门', 'phone' => '123123'],
];
// 购物车数据
$carts = Cart::where('user_id', auth('api')->id())
->where('is_checked', 1)
->with('goods:id,cove,title')
->get();
// 返回数据
return $this->response->array([
'address' => $address,
'carts' => $carts,
]);
}
/**
* 提交订单
*/
public function store(Request $request)
{
// 验证地址字段
$request->validate([
'address_id' => 'required' // TODO 地址要存在才行 exists:addresses,id
], [
'address_id.required' => ['收获地址不能为空']
]);
// 处理插入的数据
$user_id = auth('api')->id();
$order_no = date('YmdHis') . rand(100000, 999999);
$amount = 0;
$cartsQuery = Cart::where('user_id', $user_id)
->where('is_checked', 1);
$carts = $cartsQuery->with('goods:id,price')->get();
// 要插入的订单详情的数据
$insertData = [];
foreach ($carts as $key => $cart) {
// 如果有商品库存不足,提示用户重新选择
if ($cart->goods->stock < $cart->num) {
return $this->response->errorBadRequest($cart->goods->title.' 库存不足,请重新选择商品!');
}
$insertData[] = [
'goods_id' => $cart->goods_id,
'price' => $cart->goods->price,
'num' => $cart->num
];
$amount += $cart->goods->price * $cart->num;
}
try {
DB::beginTransaction(); // 开启事务
// 生成订单
$order = Order::create([
'user_id' => $user_id,
'order_no' => $order_no,
'address_id' => $request->input('address_id'),
'amount' => $amount
]);
// 生成订单的详情
$order->orderDetails()->createMany($insertData);
// 删除已经结算的购物车数据
$cartsQuery->delete();
// 减去商品对应的库存量
foreach($carts as $cart) {
Good::where('id', $cart->goods_id)->decrement('stock', $cart->num);
}
DB::commit(); // 没有异常才提交
return $this->response->created();
} catch (\Exception $e) {
DB::rollBack(); // 出现异常 事务 数据回滚
throw $e; // 抛出异常
}
}
// 订单详情
public function show(Order $order) {
return $this->response->item($order, new OrderTransformer());
}
/**
* 确认收货
*/
public function confirm(Order $order) {
if ($order->status != 3) {
return $this->response->errorBadRequest('订单状态异常!');
}
try {
DB::beginTransaction();
$order->status = 4;
$order->save();
$orderDetails = $order->orderDetails;
// 增加订单下所有商品的销量
foreach($orderDetails as $orderDetail) {
// 更新商品销量
Good::where('id', $orderDetail->goods_id)->increment('sales', $orderDetail->num);
}
DB::commit();
} catch (\Exception $e) {
DB::rollBack();
throw $e;
}
return $this->response->noContent();
}
}
复制代码
1、我们这边暂时还没做地址管理api,所以地址暂时用模拟 2、由于创建订单涉及到多表操作,万一一步操作错了,可能会产生未知的bug,所以我们这边使用事务来进行异常捕获并抛出。
由于往订单表中插入数据,所以我们还要往订单表中写入可批量赋值的字段: 由于也往订单详情表中插入了数据,所以也需要往订单详情表中插入可批量的字段:
接着在订单模型中写入远程一对多,这样我们就可以跳过中间订单详情表,直接通过订单模型获取到商品数据:
/**
* 订单远程一对多,关联商品
*/
public function goods() {
return $this->hasManyThrough(
Good::class, // 最终关联的模型
OrderDetails::class, // 中间模型
'order_id', // 中间模型和本模型关联的外键
'id', // 最终关联模型的外键
'id', // 本模型和中间模型关联的健
'goods_id' // 中间表和最终模型关联到一个键
);
}
复制代码
增加订单OrderTransformer.php
的商品数据:
/**
* 商品数据
*/
public function includeGoods(Order $order) {
return $this->collection($order->goods, new GoodTransformer());
}
复制代码
1、任务调度订单过时未付取消
由于下单了就会减少库存,为了防止有些人恶意下单,减库存,所以我们可以做个任务调度,做个订单多久失效。 输入命令:crontab -e
,输入1回车: 在编辑器中输入
* * * * * /home/vagrant/code/shopProjectApi/artisan schedule:run >> /dev/null 2>&1
。你的项目的绝对路径。 然后controller + x
输入y
保存退出。 然后在 App\Console\Kernel
中输入:
// 定时检测订单状态,超过10分钟未支付的,作废
// 真是项目一般会采用长链接,因为可以实时长链接,时间一到,直接作废,而任务调度的话不会那么精确的跟前端显示一样的时间
// 任务调度的话 在一定的时间帮我们去执行任务 像备份数据...
$schedule->call(function () {
// info('hellp');
$orders = Order::where('status', 1)
->where('created_at', '<', date('Y-m-d H:i:s', time()-600))
->with('orderDetails.goods') // 拿到关联的商品
->get();
// 循环订单,修改订单状态,还原商品库存
try {
DB::beginTransaction(); // 开启门面
foreach($orders as $order) {
$order->status = 5;
$order->save();
// 还原商品库存
foreach($order->orderDetails as $details) {
$details->goods->increment('stock', $details->num);
}
}
Db::commit();
} catch (\Exception $e) {
DB::rollBack();
Log::error($e);
}
})->everyMinute();
复制代码
这样在订单过期时,将会把订单状态设置成5,并且商品数量将回滚:
1.2 创建订单路由
/**
* 订单
*/
// 订单预览页
$api->get('order/preview', [OrderController::class, 'preview']);
// 提交订单
$api->post('order', [OrderController::class, 'store']);
// 订单详情
$api->get('order/{order}', [OrderController::class, 'show']);
// 订单列表
$api->get('orders', [OrderController::class, 'index']);
// 确认收货
$api->patch('orders/{order}/confirm', [OrderController::class, 'confirm']);
复制代码
1.3 测试效果
测试的时候我们发现报错了,原因是我们之前在建订单表时,将订单单号设置成了integer
所以我们来修改下这个字段的类型。 首先运行命令
composer require doctrine/dbal
。 接着运行:
php artisan make:migration modify_order_no_column_in_orders_table --table=orders
创建修改该字段的迁移文件: 接着写入:
$table->string('order_no')->comment('订单单号')->change();
好的类型就修改好了。
接下来测试:
可以看到生成了订单,并且购物车中数据也没了。
在学习的php的路上,如果你觉得本文对你有所帮助的话,那就请关注点赞评论三连吧,谢谢,你的肯定是我写博的另一个支持。