feat(api): 全链路后端 + 数据库设计 + E2E 测试(65 项断言全通过)
数据库: - db/schema_phase1.sql 16 张业务表(类目/服务/地址/师傅/订单/流水/取证/报价/评价/券/钱包/提现/结算/消息) - db/seed_phase1.sql 15 类目 + 32 服务项目 + 新人券(幂等) 后端 API (application/api/controller): - Base/Auth/Home/Address/Order/Master/Review - app/common/library/OrderStatus 11 状态机 + 流转矩阵 - application/api/common.php PHP 8.5 兼容垫片(E_DEPRECATED 不再升级为异常) 测试: - tests/e2e_flow.js 用户下单→支付→师傅抢单→履约→验收→结算→评价 全链路 65 项断言 - 含异常用例:越权/重复操作/非法状态流转/2h 未联系自动回流
这个提交包含在:
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
|
||||
namespace app\api\controller;
|
||||
|
||||
use app\common\controller\Api;
|
||||
use app\common\library\OrderStatus;
|
||||
use think\Db;
|
||||
|
||||
/**
|
||||
* 业务接口基类
|
||||
* 统一处理:登录用户注入 / 师傅档案 / 订单归属校验 / 状态流转 / 通知
|
||||
*/
|
||||
class Base extends Api
|
||||
{
|
||||
protected $noNeedLogin = [];
|
||||
protected $noNeedRight = '*';
|
||||
|
||||
/** @var mixed 当前登录用户 */
|
||||
protected $user = null;
|
||||
|
||||
/** @var array|false|null 师傅档案:null未查询 / false未入驻 / array已入驻 */
|
||||
protected $master = null;
|
||||
|
||||
protected function _initialize()
|
||||
{
|
||||
parent::_initialize();
|
||||
$this->user = $this->auth->getUser();
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前用户ID
|
||||
*/
|
||||
protected function uid()
|
||||
{
|
||||
return (int)$this->auth->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* 师傅档案
|
||||
*/
|
||||
protected function masterInfo($reload = false)
|
||||
{
|
||||
if ($this->master === null || $reload) {
|
||||
$row = Db::name('master')->where('user_id', $this->uid())->find();
|
||||
$this->master = $row ? $row : false;
|
||||
}
|
||||
return $this->master;
|
||||
}
|
||||
|
||||
/**
|
||||
* 要求是已通过审核的师傅
|
||||
*/
|
||||
protected function requireMaster()
|
||||
{
|
||||
$master = $this->masterInfo();
|
||||
if (!$master) {
|
||||
$this->error('请先完成师傅入驻', ['need_apply' => 1], 2001);
|
||||
}
|
||||
if ($master['verify_status'] === 'pending') {
|
||||
$this->error('师傅资质审核中,请耐心等待', ['verify_status' => 'pending'], 2002);
|
||||
}
|
||||
if ($master['verify_status'] === 'reject') {
|
||||
$this->error('资质未通过:' . $master['audit_msg'], ['verify_status' => 'reject'], 2003);
|
||||
}
|
||||
return $master;
|
||||
}
|
||||
|
||||
/**
|
||||
* 取本人订单(越权拦截)
|
||||
* @param int $orderId
|
||||
* @param string $side user|master
|
||||
*/
|
||||
protected function ownOrder($orderId, $side = 'user')
|
||||
{
|
||||
$orderId = (int)$orderId;
|
||||
if ($orderId <= 0) {
|
||||
$this->error('订单参数错误');
|
||||
}
|
||||
$order = Db::name('order')->where('id', $orderId)->find();
|
||||
if (!$order) {
|
||||
$this->error('订单不存在');
|
||||
}
|
||||
if ($side === 'user' && (int)$order['user_id'] !== $this->uid()) {
|
||||
$this->error('无权操作该订单', null, 403);
|
||||
}
|
||||
if ($side === 'master') {
|
||||
$master = $this->requireMaster();
|
||||
if ((int)$order['master_id'] !== (int)$master['id']) {
|
||||
$this->error('无权操作该订单', null, 403);
|
||||
}
|
||||
}
|
||||
return $order;
|
||||
}
|
||||
|
||||
/**
|
||||
* 订单状态流转:校验 → 更新 → 写流水
|
||||
*/
|
||||
protected function transit(array $order, $to, $remark = '', $extra = [], $operatorType = 'system', $operatorId = 0)
|
||||
{
|
||||
$from = (int)$order['status'];
|
||||
if (!OrderStatus::can($from, $to)) {
|
||||
$this->error('订单状态「' . OrderStatus::label($from) . '」不允许变更为「' . OrderStatus::label($to) . '」');
|
||||
}
|
||||
$data = array_merge($extra, [
|
||||
'status' => (int)$to,
|
||||
'updatetime' => time(),
|
||||
]);
|
||||
Db::name('order')->where('id', (int)$order['id'])->update($data);
|
||||
$this->addLog((int)$order['id'], (int)$to, $remark, $operatorType, $operatorId);
|
||||
return array_merge($order, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 写订单流水
|
||||
*/
|
||||
protected function addLog($orderId, $status, $remark = '', $operatorType = 'system', $operatorId = 0)
|
||||
{
|
||||
Db::name('order_log')->insert([
|
||||
'order_id' => (int)$orderId,
|
||||
'status' => (int)$status,
|
||||
'remark' => mb_substr((string)$remark, 0, 255),
|
||||
'operator_type' => $operatorType,
|
||||
'operator_id' => (int)$operatorId,
|
||||
'createtime' => time(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息通知
|
||||
*/
|
||||
protected function notify($receiveType, $receiveId, $title, $content, $type = 'order', $orderId = 0)
|
||||
{
|
||||
if (!$receiveId) {
|
||||
return;
|
||||
}
|
||||
Db::name('message')->insert([
|
||||
'receive_type' => $receiveType,
|
||||
'receive_id' => (int)$receiveId,
|
||||
'type' => $type,
|
||||
'title' => mb_substr((string)$title, 0, 100),
|
||||
'content' => mb_substr((string)$content, 0, 500),
|
||||
'order_id' => (int)$orderId,
|
||||
'is_read' => 0,
|
||||
'createtime' => time(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成订单号
|
||||
*/
|
||||
protected function makeOrderNo()
|
||||
{
|
||||
return date('YmdHis') . str_pad((string)mt_rand(0, 999999), 6, '0', STR_PAD_LEFT);
|
||||
}
|
||||
}
|
||||
在新工单中引用
屏蔽一个用户