Accept Merge Request #25: (feature/dgg -> develop)

Merge Request: 小程序相关api代码

Created By: @大狗哥
Accepted By: @大狗哥
URL: https://g-bcrc3009.coding.net/p/allocatr/d/allocatr/git/merge/25
This commit is contained in:
大狗哥 2025-04-21 23:13:51 +08:00 committed by Coding
commit 3dea4ba1b7
32 changed files with 1822 additions and 3 deletions

View File

@ -9,3 +9,7 @@ username = root
password = root password = root
hostport = 3306 hostport = 3306
prefix = fa_ prefix = fa_
[mini_program]
app_id =
secret =

View File

@ -0,0 +1,52 @@
<?php
namespace app\api\library;
use think\Log;
class ApiException extends \RuntimeException
{
private $statusCode;
private $headers;
private $errorCode;
public function __construct($message, $code = 0, \Exception $previous = null, array $headers = [], $statusCode = 200)
{
if ($code instanceof \Exception) {
Log::error([
$message,
$code->getMessage(),
$code->getFile(),
$code->getLine(),
]);
$code = 0;
}
if ($code === null) {
$code = 0;
}
$this->statusCode = $statusCode;
$this->headers = $headers;
$this->errorCode = $code;
parent::__construct($message, $statusCode, $previous);
}
public function getStatusCode()
{
return $this->statusCode;
}
public function getHeaders()
{
return $this->headers;
}
public function getErrorCode()
{
return $this->errorCode;
}
}

View File

@ -13,6 +13,11 @@ class ExceptionHandle extends Handle
public function render(Exception $e) public function render(Exception $e)
{ {
// API 异常
if ($e instanceof ApiException) {
return json(['code' => $e->getErrorCode(), 'msg' => $e->getMessage(), 'time' => time(), 'data' => null], $e->getStatusCode());
}
// 在生产环境下返回code信息 // 在生产环境下返回code信息
if (!\think\Config::get('app_debug')) { if (!\think\Config::get('app_debug')) {
$statuscode = $code = 500; $statuscode = $code = 500;

View File

@ -3,6 +3,7 @@
namespace app\common\controller; namespace app\common\controller;
use app\common\library\Auth; use app\common\library\Auth;
use app\services\BaseService;
use think\Config; use think\Config;
use think\exception\HttpResponseException; use think\exception\HttpResponseException;
use think\exception\ValidateException; use think\exception\ValidateException;
@ -17,7 +18,7 @@ use think\Validate;
/** /**
* API控制器基类 * API控制器基类
*/ */
class Api class Api extends BaseService
{ {
/** /**

View File

@ -0,0 +1,319 @@
<?php
namespace app\common\controller;
use app\api\library\ApiException;
use app\common\library\Auth;
use app\common\library\Token;
use app\services\BaseService;
use fast\Random;
use think\exception\HttpResponseException;
use think\exception\ValidateException;
use think\Loader;
use think\Request;
use think\Response;
use think\Validate;
class WorkerApi extends BaseService
{
/**
* @var Request Request 实例
*/
protected $request;
/**
* 默认响应输出类型,支持json/xml
* @var string
*/
protected $responseType = 'json';
/*
* 授权用户
*/
public $user = [];
//Token默认有效时长
protected $keeptime = 2592000;
/**
* 无需登录的方法,同时也就不需要鉴权了
* @var array
*/
protected $noNeedLogin = [];
/**
* @var bool 是否批量验证
*/
protected $batchValidate = false;
/**
* @var bool 验证失败是否抛出异常
*/
protected $failException = false;
protected $token = null;
protected $allowFields = [
'id',
'name',
'tel',
'status',
'area_id',
'lng',
'lat',
'deposit_amount',
'star',
'create_time',
];
/**
* 构造方法
* @access public
* @param Request $request Request 对象
*/
public function __construct(Request $request = null)
{
$this->request = is_null($request) ? Request::instance() : $request;
// 控制器初始化
$this->_initialize();
}
/**
* 初始化操作
* @access protected
*/
protected function _initialize()
{
//跨域请求检测
check_cors_request();
// 检测IP是否允许
check_ip_allowed();
//移除HTML标签
$this->request->filter('trim,strip_tags,htmlspecialchars');
// token
$token = $this->request->server('HTTP_TOKEN', $this->request->request('token', \think\Cookie::get('token')));
if (!$this->match($this->noNeedLogin)) {
$this->init($token);
} else {
if ($token) {
$this->init($token);
}
}
return true;
}
/**
* 检测当前控制器和方法是否匹配传递的数组
*
* @param array $arr 需要验证权限的数组
* @return boolean
*/
public function match($arr = [])
{
$request = Request::instance();
$arr = is_array($arr) ? $arr : explode(',', $arr);
if (!$arr) {
return false;
}
$arr = array_map('strtolower', $arr);
// 是否存在
if (in_array(strtolower($request->action()), $arr) || in_array('*', $arr)) {
return true;
}
// 没找到匹配
return false;
}
/**
* 操作成功返回的数据
* @param string $msg 提示信息
* @param mixed $data 要返回的数据
* @param int $code 错误码默认为1
* @param string $type 输出类型
* @param array $header 发送的 Header 信息
*/
protected function success($msg = '', $data = null, $code = 1, $type = null, array $header = [])
{
$this->result($msg, $data, $code, $type, $header);
}
/**
* 操作失败返回的数据
* @param string $msg 提示信息
* @param mixed $data 要返回的数据
* @param int $code 错误码默认为0
* @param string $type 输出类型
* @param array $header 发送的 Header 信息
*/
protected function error($msg = '', $data = null, $code = 0, $type = null, array $header = [])
{
$this->result($msg, $data, $code, $type, $header);
}
/**
* 返回封装后的 API 数据到客户端
* @access protected
* @param mixed $msg 提示信息
* @param mixed $data 要返回的数据
* @param int $code 错误码默认为0
* @param string $type 输出类型支持json/xml/jsonp
* @param array $header 发送的 Header 信息
* @return void
* @throws HttpResponseException
*/
protected function result($msg, $data = null, $code = 0, $type = null, array $header = [])
{
$result = [
'code' => $code,
'msg' => $msg,
'time' => Request::instance()->server('REQUEST_TIME'),
'data' => $data,
];
// 如果未设置类型则自动判断
$type = $type ? $type : ($this->request->param(config('var_jsonp_handler')) ? 'jsonp' : $this->responseType);
if (isset($header['statuscode'])) {
$code = $header['statuscode'];
unset($header['statuscode']);
} else {
//未设置状态码,根据code值判断
$code = $code >= 1000 || $code < 200 ? 200 : $code;
}
$response = Response::create($result, $type, $code)->header($header);
throw new HttpResponseException($response);
}
/**
* 师傅登录
* @param int $id
* @return bool
*/
public function workerLogin(int $id): bool
{
$user = $this->getWorkerModel()->find($id);
$this->tryLogin($user);
$user = $user->toArray();
$user['token'] = $this->getTokenByUserId($user['id']);
$this->user = $user;
return true;
}
public function workerLogout(): bool
{
if ($this->token) {
Token::delete($this->token);
}
return true;
}
/**
* 验证数据
* @access protected
* @param array $data 数据
* @param string|array $validate 验证器名或者验证规则数组
* @param array $message 提示信息
* @param bool $batch 是否批量验证
* @param mixed $callback 回调方法(闭包)
* @return array|string|true
* @throws ValidateException
*/
public function validate($data, $validate, $messageHeader = '', $message = [], $batch = false, $callback = null)
{
if (is_array($validate)) {
$v = Loader::validate();
$v->rule($validate);
} else {
// 支持场景
if (strpos($validate, '.')) {
list($validate, $scene) = explode('.', $validate);
}
$v = Loader::validate($validate);
!empty($scene) && $v->scene($scene);
}
// 批量验证
if ($batch || $this->batchValidate) {
$v->batch(true);
}
// 设置错误信息
if (is_array($message)) {
$v->message($message);
}
// 使用回调验证
if ($callback && is_callable($callback)) {
call_user_func_array($callback, [$v, &$data]);
}
if (!$v->check($data)) {
$errorMessage = $v->getError();
if ($messageHeader) {
$errorMessage = "$messageHeader$errorMessage";
}
if ($this->failException) {
throw new ValidateException($errorMessage);
}
throw new ApiException($errorMessage);
}
return true;
}
private function init($token)
{
if ($this->user) {
return true;
}
$data = Token::get($token);
if (!$data) {
throw new ApiException('登录已失效', 401);
}
$this->token = $token;
$user = $this->getWorkerModel()
->field($this->allowFields)
->where('id', $data['user_id'])
->find();
$this->tryLogin($user);
$this->user = $user->toArray();
return true;
}
private function tryLogin($user)
{
if (!$user) {
throw new ApiException('用户不存在,请联系平台');
}
if ($user->status === 0) {
throw new ApiException('当前账号不可用');
}
return true;
}
protected function getTokenByUserId($userId)
{
$token = Random::uuid();
Token::set($token, $userId, $this->keeptime);
return $token;
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace app\common\model;
use think\Model;
class Abnormal extends Model
{
protected $visible = [
];
protected $hidden = [
];
}

View File

@ -0,0 +1,21 @@
<?php
namespace app\common\model;
use think\Model;
class Order extends Model
{
protected $visible = [
];
protected $hidden = [
];
public function area()
{
return $this->belongsTo(\app\admin\model\Area::class,'area_id', 'area_code');
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace app\common\model;
use think\Model;
class OrderAbnormal extends Model
{
protected $visible = [
];
protected $hidden = [
];
protected $autoWriteTimestamp = 'datetime';
protected $dateFormat = 'Y-m-d H:i:s';
}

View File

@ -0,0 +1,61 @@
<?php
namespace app\common\model;
use think\Model;
class OrderDispatch extends Model
{
protected $visible = [
];
protected $hidden = [
];
public function orderInfo()
{
return $this->belongsTo(Order::class,'order_id', 'id');
}
public function getArriveImagesAttr($val)
{
$images = explode(',', $val);
foreach ($images as $k => $v) {
$images[$k] = cdnurl($v, true);
}
return $images;
}
public function getImagesAttr($val)
{
$images = explode(',', $val);
foreach ($images as $k => $v) {
$images[$k] = cdnurl($v, true);
}
return $images;
}
public function getImageAttr($val)
{
return cdnurl($val, true);
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace app\common\model;
use think\Model;
class Worker extends Model
{
protected $visible = [
];
protected $hidden = [
];
}

View File

@ -0,0 +1,18 @@
<?php
namespace app\common\model;
use think\Model;
class WorkerVendor extends Model
{
protected $visible = [
];
protected $hidden = [
];
}

View File

@ -312,10 +312,14 @@ return [
//插件纯净模式插件启用后是否删除插件目录的application、public和assets文件夹 //插件纯净模式插件启用后是否删除插件目录的application、public和assets文件夹
'addon_pure_mode' => true, 'addon_pure_mode' => true,
//允许跨域的域名,多个以,分隔 //允许跨域的域名,多个以,分隔
'cors_request_domain' => 'localhost,127.0.0.1', 'cors_request_domain' => 'localhost,127.0.0.1,*',
//版本号 //版本号
'version' => '1.5.3.20250217', 'version' => '1.5.3.20250217',
//API接口地址 //API接口地址
'api_url' => 'https://api.fastadmin.net', 'api_url' => 'https://api.fastadmin.net',
], ],
'mini_program' => [
'app_id' => Env::get('mini_program.app_id', ''),
'secret' => Env::get('mini_program.secret', ''),
]
]; ];

View File

@ -9,7 +9,7 @@ return [
/** /**
* CDN地址 * CDN地址
*/ */
'cdnurl' => '', 'cdnurl' => get_addon_config('alioss')['cdnurl'],
/** /**
* 文件保存格式 * 文件保存格式
*/ */

View File

@ -0,0 +1,18 @@
<?php
namespace app\services;
class AbnormalService extends BaseService
{
public function findAll()
{
return $this->getAbnormalModel()
->where('type', 1)
->order('sort', 'desc')
->field([
'id',
'title',
])
->select();
}
}

View File

@ -0,0 +1,148 @@
<?php
namespace app\services;
use app\api\library\ApiException;
use app\common\controller\WorkerApi;
use app\common\Logic\OrderLogic;
use app\common\model\Worker;
use think\Log;
use app\common\model\WorkerVendor;
use app\common\model\OrderDispatch;
use app\common\model\Order;
use app\common\model\Abnormal;
use app\common\model\OrderAbnormal;
//{%add use model%}
class BaseService
{
protected function apiError($msg, $code = 0, $data = [])
{
if (!empty($data)) {
array_unshift($data, ['msg' => $msg]);
$log = [
'请求参数' => $_REQUEST,
'结果' => $data,
];
Log::log($log);
}
throw new ApiException($msg, $code);
}
/**
* @return WorkerApi
*/
protected function getWorkerApi()
{
return app(WorkerApi::class);
}
/**
* @return WorkerService
*/
protected function getWorkerService()
{
return app(WorkerService::class);
}
/**
* @return Worker
*/
protected function getWorkerModel()
{
return app(Worker::class, true);
}
/**
* @return WorkerVendorService
*/
protected function getWorkerVendorService()
{
return app(WorkerVendorService::class);
}
/**
* @return WorkerVendor
*/
protected function getWorkerVendorModel()
{
return app(WorkerVendor::class, true);
}
/**
* @return OrderDispatchService
*/
protected function getOrderDispatchService()
{
return app(OrderDispatchService::class);
}
/**
* @return OrderDispatch
*/
protected function getOrderDispatchModel()
{
return app(OrderDispatch::class, true);
}
/**
* @return OrderService
*/
protected function getOrderService()
{
return app(OrderService::class);
}
/**
* @return Order
*/
protected function getOrderModel()
{
return app(Order::class, true);
}
/**
* @return OrderLogic
*/
protected function getOrderLogic()
{
return app(OrderLogic::class);
}
/**
* @return AbnormalService
*/
protected function getAbnormalService()
{
return app(AbnormalService::class);
}
/**
* @return Abnormal
*/
protected function getAbnormalModel()
{
return app(Abnormal::class, true);
}
/**
* @return OrderAbnormalService
*/
protected function getOrderAbnormalService()
{
return app(OrderAbnormalService::class);
}
/**
* @return OrderAbnormal
*/
protected function getOrderAbnormalModel()
{
return app(OrderAbnormal::class, true);
}
//{%add function code%}
}

View File

@ -0,0 +1,83 @@
<?php
namespace app\services;
class OrderAbnormalService extends BaseService
{
/**
* 上报异常
* @param int $workerId
* @param array $params
* @return bool
*/
public function create(int $workerId, array $params)
{
$data = $this->getOrderAbnormal($workerId, $params['order_id']);
if ($data) {
$this->apiError('您已上报过异常,请勿重复提交');
}
$abnormal = $this->getAbnormalModel()->find($params['abnormal_id']);
if (!$abnormal) {
$this->apiError('异常原因不存在,请重新选择');
}
$worker = $this->getWorkerModel()->find($workerId);
$model = $this->getOrderAbnormalModel();
$model->order_id = $params['order_id'];
$model->status = 0;
$model->abnormal_id = $params['abnormal_id'];
$model->abnormal_title = $abnormal->title;
$model->detail = $params['detail'];
$model->type = 2;
$model->admin_user = $worker->name;
$model->admin_id = $workerId;
$model->save();
return true;
}
/**
* 获取订单异常详情
* @param int $workerId
* @param int $orderId
*/
public function getOrderAbnormal(int $workerId, int $orderId)
{
return $this->getOrderAbnormalModel()
->where('order_id', $orderId)
->where('admin_id', $workerId)
->where('type', 2)
->field([
'id',
'order_id',
'abnormal_id',
'abnormal_title',
'detail',
'create_time',
])
->find();
}
}

View File

@ -0,0 +1,395 @@
<?php
namespace app\services;
use app\admin\model\Order;
use app\admin\model\OrderDispatch;
use app\api\library\ApiException;
use think\Db;
use think\Hook;
class OrderDispatchService extends BaseService
{
public function dispatchList(int $workerId, int $pageSize)
{
return $this->getOrderDispatchModel()
->with(['orderInfo' => function ($query) {
$query->with(['area' => function ($query) {
$query->field('id,area_code,merge_name');
}])->field('id,order_no,item_id,item_title,receive_type,address,lng,lat,plan_time,online_amount,discount_amount,area_id');
}])
->where('status', OrderDispatch::STATUS_TOGET)
->where('worker_id', $workerId)
->field(['id', 'order_id', 'status', 'remark', 'create_time'])
->order('id desc')
->paginate($pageSize);
}
/**
* @param int $workerId 师傅id
* @param string $type 类型ongoing=进行中,today=当日,tomorrow=昨日,all=全部订单
* @param int $pageSize
*/
public function workbenchOrderList(int $workerId, string $type, int $pageSize)
{
$model = $this->getWorkbenchOrderModel($workerId, $type);
$result = $model
->field(['id', 'order_id', 'status', 'remark', 'create_time', 'plan_time'])
->order('id desc')
->paginate($pageSize);
return $result;
}
/**
* 统计工作台订单
* @param int $workerId
* @return array
*/
public function countWorkbenchOrder(int $workerId)
{
return [
'ongoing' => $this->getWorkbenchOrderModel($workerId, 'ongoing')->count(),
'today' => $this->getWorkbenchOrderModel($workerId, 'today')->count(),
'tomorrow' => $this->getWorkbenchOrderModel($workerId, 'tomorrow')->count(),
'all' => $this->getWorkbenchOrderModel($workerId, 'all')->count(),
];
}
/**
* 获取工作台订单模型
* @param int $workerId
* @param string $type
*/
private function getWorkbenchOrderModel(int $workerId, string $type)
{
$model = $this->getOrderDispatchModel()
->with(['orderInfo' => function ($query) {
$query->with(['area' => function ($query) {
$query->field('id,area_code,merge_name');
}])->field('id,order_no,item_id,item_title,receive_type,address,lng,lat,plan_time,online_amount,discount_amount,area_id,customer,tel');
}])
->where('worker_id', $workerId);
$status = [
OrderDispatch::STATUS_GOTIT,
OrderDispatch::STATUS_PLANIT,
OrderDispatch::STATUS_CLOCK,
];
switch ($type) {
case 'ongoing':
//所有已接单未完成的订单
$model->whereIn('status', $status);
break;
case 'today':
$model->whereIn('status', $status);
$model->where('plan_time', '>=', date('Y-m-d 00:00:00'));
$model->where('plan_time', '<=', date('Y-m-d 23:59:59'));
break;
case 'tomorrow':
$model->whereIn('status', $status);
$model->where('plan_time', '>=', date('Y-m-d 00:00:00', strtotime('+1 day')));
$model->where('plan_time', '<=', date('Y-m-d 23:59:59', strtotime('+1 day')));
break;
case 'all':
$status[] = OrderDispatch::STATUS_REFUSED;
$status[] = OrderDispatch::STATUS_FINISH;
$model->whereIn('status', $status);
break;
}
return $model;
}
/**
* 师傅接单/拒接
* @param int $workerId 师傅id
* @param array $params 请求参数
* @return true
*/
public function orderConfirm(int $workerId, array $params)
{
$orderDispatchId = $params['order_dispatch_id'];
$type = $params['type'];
$orderDispatch = $this->getOrderDispatchModel()
->where('worker_id', $workerId)
->where('id', $orderDispatchId)
->find();
if (!$orderDispatch) {
$this->apiError('订单不存在');
}
if ($orderDispatch->status !== OrderDispatch::STATUS_TOGET) {
$this->apiError('该订单已被接单');
}
//接单或拒接
$orderDispatchStatus = $type == 'accept' ? OrderDispatch::STATUS_GOTIT : OrderDispatch::STATUS_REFUSED;
Db::startTrans();
try {
//接单
$orderDispatch->status = $orderDispatchStatus;
//拒接原因
if ($type == 'reject') {
if (empty($params['reject_reason'])) {
$this->apiError('请输入拒接原因');
}
$orderDispatch->reject_reason = $params['reject_reason'];
}
$orderDispatch->save();
$orderDispatchChangeParams = [
'dispatch' => $orderDispatch,
'remark' => $type == 'accept' ? '师傅接单' : '师傅拒接',
];
Hook::listen('order_dispatch_change', $orderDispatchChangeParams);
//拒接,更新订单状态
if ($type == 'reject') {
$order = $this->getOrderModel()->find($orderDispatch->order_id);
$order->status = Order::STATUS_DISPATCHING;
$order->save();
$orderChangeParams['order'] = $order;
$orderChangeParams['role'] = 2;
$orderChangeParams['auth'] = $this->getWorkerModel()->find($orderDispatch->worker_id);
$orderChangeParams['remark'] = "任务被师傅拒接[OrderDispatchId{$orderDispatch->id},原因为:{$params['reject_reason']},订单状态回退";
Hook::listen('order_change', $orderChangeParams);
}
Db::commit();
} catch (ApiException $e) {
Db::rollback();
$this->apiError($e->getMessage());
} catch (\Exception $e) {
Db::rollback();
$this->apiError('操作失败', $e, [
'msg' => '师傅接单或拒接操作失败',
'workerId' => $workerId,
'orderDispatchId' => $orderDispatchId,
'type' => $type,
]);
}
return true;
}
/**
* 派单详情
* @param int $workerId 师傅id
* @param int $orderDispatchId 派单id
*/
public function dispatchInfo(int $workerId, int $orderDispatchId)
{
$orderFields = [
'id',
'order_no',
'item_id',
'item_title',
'receive_type',
'address',
'lng',
'lat',
'plan_time',
'online_amount',
'discount_amount',
'area_id',
'customer',
'tel',
'remark',
'detail',
];
$orderDispatchFields = [
'id',
'order_id',
'status',
'remark',
'worker_remark',
'create_time',
'total',
'online_total',
'is_receipt',
'plan_time',
'reject_reason',
'arrive_images',
'arrive_time',
'images',
'image',
'finish_time',
'offline_total_type',
];
$res = $this->getOrderDispatchModel()
->with(['orderInfo' => function ($query) use ($orderFields) {
$query->with(['area' => function ($query) {
$query->field('id,area_code,merge_name');
}])->field($orderFields);
}])
->where('id', $orderDispatchId)
->where('worker_id', $workerId)
->field($orderDispatchFields)
->find();
if (!$res) {
$this->apiError('订单不存在');
}
return $res;
}
/**
* 提交预约上门时间
* @param int $workerId 师傅id
* @param int $orderDispatchId 派单id
* @param string $planTime 预约时间
* @return true
*/
public function appointmentTime(int $workerId, int $orderDispatchId, string $planTime)
{
$orderDispatch = $this->getOrderDispatchInfo($workerId, $orderDispatchId);
$orderDispatch->status = OrderDispatch::STATUS_PLANIT;
$orderDispatch->plan_time = $planTime;
$orderDispatch->save();
$orderDispatchChangeParams = [
'dispatch' => $orderDispatch,
'remark' => '师傅已和客户预约,预约时间:' . $planTime,
];
Hook::listen('order_dispatch_change', $orderDispatchChangeParams);
return true;
}
/**
* 完成上门
* @param int $workerId 师傅id
* @param int $orderDispatchId 派单id
* @param string $images 上门图片
* @return true
*/
public function arrivedOnSite(int $workerId, int $orderDispatchId, string $images)
{
$time = datetime(time());
$orderDispatch = $this->getOrderDispatchInfo($workerId, $orderDispatchId);
$orderDispatch->status = OrderDispatch::STATUS_CLOCK;
$orderDispatch->arrive_images = $this->removeStrCdnUrl($images);
$orderDispatch->arrive_time = $time;
$orderDispatch->save();
//派单状态变更
$orderDispatchChangeParams = [
'dispatch' => $orderDispatch,
'remark' => '师傅已上门,上门时间:' . $time,
];
Hook::listen('order_dispatch_change', $orderDispatchChangeParams);
return true;
}
/**
* 移除字符串中的 cdnUrl
* @param string $str
* @return string
*/
private function removeStrCdnUrl(string $str): string
{
$cdnUrl = cdnurl('', true);
return str_replace($cdnUrl, '', $str);
}
/**
* 获取订单信息
* @param int $workerId 师傅id
* @param int $orderDispatchId 派单id
*/
private function getOrderDispatchInfo(int $workerId, int $orderDispatchId) {
$res = $this->getOrderDispatchModel()
->where('id', $orderDispatchId)
->where('worker_id', $workerId)
->find();
if (!$res) {
$this->apiError('工单不存在');
}
return $res;
}
/**
* @param int $workerId 师傅id
* @param array $params 请求参数
* @return bool
*/
public function completeService(int $workerId, array $params)
{
Db::startTrans();
try {
$time = datetime(time());
$orderDispatch = $this->getOrderDispatchInfo($workerId, $params['order_dispatch_id']);
$orderDispatch->status = OrderDispatch::STATUS_FINISH;
$orderDispatch->images = $this->removeStrCdnUrl($params['complete_images']);
$orderDispatch->image = $this->removeStrCdnUrl($params['payment_image']);
$orderDispatch->offline_total_type = $params['offline_total_type'];
$orderDispatch->finish_time = $time;
//线下尾款
if ($params['final_payment_method'] == 1) {
$orderDispatch->total = $params['amount'];
}
//线上尾款
if ($params['final_payment_method'] == 2) {
$orderDispatch->online_total = $params['amount'];
}
$orderDispatch->save();
//派单状态变更
$orderDispatchChangeParams = [
'dispatch' => $orderDispatch,
'remark' => '师傅已完成服务,完成时间:' . $time,
];
Hook::listen('order_dispatch_change', $orderDispatchChangeParams);
//修改订单状态
$orderDispatchInfo = OrderDispatch::get($params['order_dispatch_id']);
$roleInfo = ['role' => 2, 'auth' => $this->getWorkerModel()->find($workerId), 'remark' => '师傅完成服务'];
$this->getOrderLogic()->dispachFinishAfter($orderDispatchInfo, $roleInfo);
Db::commit();
} catch (\Exception $e) {
Db::rollback();
$this->apiError('操作失败', $e);
}
return true;
}
/**
* 保存师傅备注
* @param int $workerId 师傅id
* @param int $orderDispatchId 订单派单id
* @param string $workerRemark 备注信息
* @return true
*/
public function saveWorkerRemark(int $workerId, int $orderDispatchId, string $workerRemark)
{
$orderDispatch = $this->getOrderDispatchInfo($workerId, $orderDispatchId);
$orderDispatch->worker_remark = $workerRemark;
$orderDispatch->save();
return true;
}
}

View File

@ -0,0 +1,9 @@
<?php
namespace app\services;
use app\admin\model\Order;
class OrderService extends BaseService
{
}

View File

@ -0,0 +1,140 @@
<?php
namespace app\services;
use app\common\library\Token;
use EasyWeChat\Factory;
use EasyWeChat\Kernel\Exceptions\InvalidConfigException;
use EasyWeChat\MiniProgram\Application;
class WorkerService extends BaseService
{
/**
* 绑定手机号
* @param string $code
* @param string $vendorToken
*/
public function bindPhoneNumber(string $code, string $vendorToken)
{
$phone = $this->getPhoneNumber($code);
$worker = $this->getWorkerService()->getByTel($phone);
$this->tryBindPhoneNumber($worker);
$tokenData = Token::get($vendorToken);
if (!$tokenData) {
$this->apiError('vendor_token 无效');
}
$workerVendor = $this->getWorkerVendorModel()->find($tokenData['user_id']);
if ($workerVendor->worker_id) {
$this->apiError('绑定失败,该微信已绑定手机号');
}
//绑定手机号
$workerVendor->worker_id = $worker->id;
$workerVendor->save();
Token::delete($vendorToken);
return $worker->id;
}
/**
* 微信登录
* @param string $code
* @return array
*/
public function login(string $code)
{
$app = $this->getMiniProgramApp();
try {
$info = $app->auth->session($code);
} catch (InvalidConfigException $e) {
$this->apiError('登录失败', $e);
}
if (isset($info['errcode']) && $info['errcode'] !== 0) {
$this->apiError('登录失败', 0, $info);
}
$workerVendor = $this->getWorkerVendorService()->getVendorByOpenid($info['openid']);
//创建
if (empty($workerVendor)) {
try {
$this->getWorkerVendorService()->createWechatMpVendor($info['openid']);
} catch (\Exception $e) {
$this->apiError('登录失败', $e, $info);
}
$workerVendor = $this->getWorkerVendorService()->getVendorByOpenid($info['openid']);
}
return $workerVendor->toArray();
}
/**
* 获取小程序 App
* @return Application
*/
private function getMiniProgramApp(): Application
{
$config = [
'app_id' => config('mini_program.app_id'),
'secret' => config('mini_program.secret'),
];
return Factory::miniProgram($config);
}
/**
* 解密微信手机号
* @param string $code
* @return mixed
*/
public function getPhoneNumber(string $code)
{
//getPhoneNumber 方法通过魔术方法 __call 获取
$phoneInfo = $this->getMiniProgramApp()->getPhoneNumber($code);
if (empty($phoneInfo)) {
$this->apiError('获取手机号失败', 0, $phoneInfo);
}
if ($phoneInfo['errcode'] !== 0) {
$this->apiError('获取手机号失败', 0, $phoneInfo);
}
return $phoneInfo['phone_info']['phoneNumber'];
}
/**
* 通过手机号查询师傅信息
* @param string $phone
*/
private function getByTel(string $phone)
{
return $this->getWorkerModel()->where('tel', $phone)->find();
}
/**
* 尝试绑定手机号
* @param $worker
* @return true
*/
private function tryBindPhoneNumber($worker)
{
if (empty($worker)) {
$this->apiError('绑定失败,该手机号未注册');
}
if ($worker->status === 0) {
$this->apiError('绑定失败,您的账号不可用');
}
$workerVendor = $this->getWorkerVendorService()->getByWorkerIdAndPlatform($worker->id, 'WechatMp');
if ($workerVendor) {
$this->apiError('绑定失败,当前手机号已被其他微信绑定');
}
return true;
}
}

View File

@ -0,0 +1,43 @@
<?php
namespace app\services;
class WorkerVendorService extends BaseService
{
/**
* 通过 openid 获取账户
* @param string $openid
*/
public function getVendorByOpenid(string $openid)
{
return $this->getWorkerVendorModel()->where(['openid' => $openid])->find();
}
/**
* 创建微信小程序账户
* @param string $openid
*/
public function createWechatMpVendor(string $openid)
{
$vendor = $this->getWorkerVendorModel();
$vendor->vendor = 'wechat';
$vendor->platform = 'WechatMp';
$vendor->unionid = '';
$vendor->openid = $openid;
$vendor->worker_id = 0;
$vendor->create_time = datetime(time());
$vendor->save();
return $vendor->id;
}
/**
* 通过师傅id和平台查询师傅账号表信息
* @param int $workerId
* @param string $platform
*/
public function getByWorkerIdAndPlatform(int $workerId, string $platform)
{
return $this->getWorkerVendorModel()->where(['worker_id' => $workerId, 'platform' => $platform])->find();
}
}

View File

@ -0,0 +1,6 @@
<?php
//配置文件
return [
'exception_handle' => '\\app\\api\\library\\ExceptionHandle',
];

View File

@ -0,0 +1,16 @@
<?php
namespace app\worker\controller;
use app\common\controller\WorkerApi;
class Abnormal extends WorkerApi
{
protected $noNeedLogin = [];
public function index()
{
$res = $this->getAbnormalService()->findAll();
$this->success('操作成功', $res);
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace app\worker\controller;
use app\common\controller\WorkerApi;
class Common extends WorkerApi
{
public function ossParams()
{
$name = $this->request->post('name');
if (empty($name)) {
$this->error('文件名必填');
}
$md5 = md5($name);
$auth = new \addons\alioss\library\Auth();
$params = $auth->params($name, $md5);
$params['OSSAccessKeyId'] = $params['id'];
$params['success_action_status'] = 200;
$params['cdnurl'] = cdnurl('', true);
$this->success('获取成功', $params);
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace app\worker\controller;
use app\common\controller\WorkerApi;
class Order extends WorkerApi
{
protected $noNeedLogin = [];
}

View File

@ -0,0 +1,55 @@
<?php
namespace app\worker\controller;
use app\common\controller\WorkerApi;
class OrderAbnormal extends WorkerApi
{
protected $noNeedLogin = [];
public function create()
{
$params = $this->request->request();
$validate = $this->validate($params, \app\worker\validate\OrderAbnormal::class . '.create');
if ($validate !== true) {
$this->error($validate);
}
$res = $this->getOrderAbnormalService()->create($this->user['id'], $params);
$this->success('操作成功', $res);
}
public function info()
{
$params = $this->request->request();
$validate = $this->validate($params, \app\worker\validate\OrderAbnormal::class . '.info');
if ($validate !== true) {
$this->error($validate);
}
$res = $this->getOrderAbnormalService()->getOrderAbnormal($this->user['id'], $params['order_id']);
$this->success('操作成功', $res ?: []);
}
}

View File

@ -0,0 +1,154 @@
<?php
namespace app\worker\controller;
use app\common\controller\WorkerApi;
class OrderDispatch extends WorkerApi
{
protected $noNeedLogin = [];
/**
* 待接单列表
* @return void
*/
public function index()
{
$res = $this->getOrderDispatchService()->dispatchList($this->user['id'], $this->request->request('page_size', 20));
$this->success('获取成功', $res);
}
/**
* 派单详情
* @return void
*/
public function info()
{
$params = $this->request->request();
$validate = $this->validate($params, \app\worker\validate\OrderDispatch::class . '.info');
if ($validate !== true) {
$this->error($validate);
}
$res = $this->getOrderDispatchService()->dispatchInfo($this->user['id'], $this->request->request('order_dispatch_id'));
$this->success('获取成功', $res);
}
/**
* 工作台订单列表
* @return void
*/
public function workbenchOrderList()
{
$params = $this->request->request();
$validate = $this->validate($params, \app\worker\validate\OrderDispatch::class . '.workbenchOrderList');
if ($validate !== true) {
$this->error($validate);
}
$pageSize = $this->request->request('page_size', 20);
$type = $this->request->request('workbench_type');
$res = $this->getOrderDispatchService()->workbenchOrderList($this->user['id'], $type, $pageSize);
$this->success('获取成功', $res);
}
/**
* 统计工作台订单
*/
public function countWorkbenchOrder()
{
$res = $this->getOrderDispatchService()->countWorkbenchOrder($this->user['id']);
$this->success('获取成功', $res);
}
/**
* 接单/拒接
* @return void
*/
public function orderConfirm()
{
$params = $this->request->request();
$validate = $this->validate($params, \app\worker\validate\OrderDispatch::class . '.orderConfirm');
if ($validate !== true) {
$this->error($validate);
}
$res = $this->getOrderDispatchService()->orderConfirm($this->user['id'], $params);
$this->success('操作成功', $res);
}
/**
* 提交预约上门时间
* @return void
*/
public function appointmentTime()
{
$params = $this->request->request();
$validate = $this->validate($params, \app\worker\validate\OrderDispatch::class . '.appointmentTime');
if ($validate !== true) {
$this->error($validate);
}
$res = $this->getOrderDispatchService()->appointmentTime($this->user['id'], $params['order_dispatch_id'], $params['plan_time']);
$this->success('操作成功', $res);
}
/**
* 提交上门信息
* @return void
*/
public function arrivedOnSite()
{
$params = $this->request->request();
$validate = $this->validate($params, \app\worker\validate\OrderDispatch::class . '.arrivedOnSite');
if ($validate !== true) {
$this->error($validate);
}
$res = $this->getOrderDispatchService()->arrivedOnSite($this->user['id'], $params['order_dispatch_id'], $params['images']);
$this->success('操作成功', $res);
}
public function completeService()
{
$params = $this->request->request();
$validate = $this->validate($params, \app\worker\validate\OrderDispatch::class . '.completeService');
if ($validate !== true) {
$this->error($validate);
}
if ($params['final_payment_method'] == 1 && empty($params['offline_total_type'])) {
$this->error('线下尾款需选择尾款收款方');
}
$res = $this->getOrderDispatchService()->completeService($this->user['id'], $params);
$this->success('操作成功', $res);
}
/**
* 保存师傅备注
* @return void
*/
public function saveWorkerRemark()
{
$params = $this->request->request();
$validate = $this->validate($params, \app\worker\validate\OrderDispatch::class . '.saveWorkerRemark');
if ($validate !== true) {
$this->error($validate);
}
$res = $this->getOrderDispatchService()->saveWorkerRemark($this->user['id'], $params['order_dispatch_id'], $params['worker_remark']);
$this->success('操作成功', $res);
}
}

View File

@ -0,0 +1,80 @@
<?php
namespace app\worker\controller;
use app\common\controller\WorkerApi;
class Worker extends WorkerApi
{
protected $noNeedLogin = ['login', 'bindPhoneNumber', 'guestLogin'];
/**
* 微信登录
* @return void
*/
public function login()
{
$params = $this->request->request();
$validate = $this->validate($params, \app\worker\validate\Worker::class . '.login');
if ($validate !== true) {
$this->error($validate);
}
$workerVendor = $this->getWorkerService()->login($params['code']);
//存在师傅id直接登录
if ($workerVendor['worker_id']) {
$this->workerLogin($workerVendor['worker_id']);
$this->success('登录成功', $this->user);
}
$this->error('请绑定手机号', ['vendor_token' => $this->getTokenByUserId($workerVendor['id'])]);
}
/**
* 绑定手机号
* @return void
*/
public function bindPhoneNumber()
{
$params = $this->request->request();
$validate = $this->validate($params, \app\worker\validate\Worker::class . '.bindPhoneNumber');
if ($validate !== true) {
$this->error($validate);
}
$workerId = $this->getWorkerService()->bindPhoneNumber($params['code'], $params['vendor_token']);
$this->workerLogin($workerId);
$this->success('绑定成功', $this->user);
}
/**
* 游客登录
* @return void
*/
public function guestLogin()
{
$this->workerLogin(9);
$this->success('登录成功', $this->user);
}
/**
* 退出登录
* @return void
*/
public function logout()
{
$this->workerLogout();
$this->success('操作成功');
}
/**
* 获取当前登录师傅信息
* @return void
*/
public function show()
{
$this->success('操作成功', $this->user);
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace app\worker\controller;
use app\common\controller\WorkerApi;
class WorkerVendor extends WorkerApi
{
protected $noNeedLogin = ['*'];
}

View File

@ -0,0 +1,25 @@
<?php
namespace app\worker\validate;
use think\Validate;
class OrderAbnormal extends Validate
{
protected $rule = [
'abnormal_id|异常原因' => 'require|number',
'order_id|订单id' => 'require|number',
'detail|异常详情' => 'require|max:200',
];
protected $message = [
'abnormal_id.require' => '请选择异常原因',
'detail.require' => '异常说明不能为空',
'detail.max' => '异常说明不能超过 200 个字',
];
protected $scene = [
'create' => ['abnormal_id', 'order_id', 'detail'],
'info' => ['order_id'],
];
}

View File

@ -0,0 +1,38 @@
<?php
namespace app\worker\validate;
use think\Validate;
class OrderDispatch extends Validate
{
protected $rule = [
'type|确认类型' => 'require|in:accept,reject',
'order_dispatch_id|订单派单id' => 'require|number',
'workbench_type|工作台类型' => 'require|in:ongoing,today,tomorrow,all',
'plan_time|预约时间' => 'require|date',
'images|上门图片' => 'require|max:3000',
'complete_images|完成图片' => 'require|max:3000',
'final_payment_method|收款方式' => 'require|in:1,2',
'amount|收款金额' => 'require|number|between:0,10000000',
'payment_image|收款图片' => 'require|max:255',
'offline_total_type|尾款收款方' => 'in:0,1,2',
'reject_reason|拒接原因' => 'max:100',
'worker_remark|备注信息' => 'max:500',
];
protected $message = [
];
protected $scene = [
'orderConfirm' => ['type', 'order_dispatch_id', 'reject_reason'],
'workbenchOrderList' => ['workbench_type'],
'info' => ['order_dispatch_id'],
'appointmentTime' => ['order_dispatch_id', 'plan_time'],
'arrivedOnSite' => ['order_dispatch_id', 'images'],
'completeService' => ['order_dispatch_id', 'complete_images', 'offline_total_type', 'amount', 'payment_image', 'offline_total_type'],
'saveWorkerRemark' => ['order_dispatch_id', 'worker_remark'],
];
}

View File

@ -0,0 +1,22 @@
<?php
namespace app\worker\validate;
use think\Validate;
class Worker extends Validate
{
protected $rule = [
'code' => 'require|max:128',
'vendor_token' => 'require|max:128',
];
protected $message = [
];
protected $scene = [
'login' => ['code'],
'bindPhoneNumber' => ['code', 'vendor_token'],
];
}

View File

@ -31,6 +31,7 @@
"ext-bcmath": "*", "ext-bcmath": "*",
"txthinking/mailer": "^2.0", "txthinking/mailer": "^2.0",
"symfony/var-dumper": "^6.4", "symfony/var-dumper": "^6.4",
"gjc/thinkphp5-container": "^1.0.1",
"alibabacloud/dyvmsapi-20170525": "^3.2", "alibabacloud/dyvmsapi-20170525": "^3.2",
"nesbot/carbon": "^3.8" "nesbot/carbon": "^3.8"
}, },