diff --git a/.env.sample b/.env.sample index ccd0f29..214e5d6 100755 --- a/.env.sample +++ b/.env.sample @@ -9,3 +9,7 @@ username = root password = root hostport = 3306 prefix = fa_ + +[mini_program] +app_id = +secret = diff --git a/application/api/library/ApiException.php b/application/api/library/ApiException.php new file mode 100644 index 0000000..1950fda --- /dev/null +++ b/application/api/library/ApiException.php @@ -0,0 +1,52 @@ +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; + } +} diff --git a/application/api/library/ExceptionHandle.php b/application/api/library/ExceptionHandle.php index 852f7ef..7c11118 100755 --- a/application/api/library/ExceptionHandle.php +++ b/application/api/library/ExceptionHandle.php @@ -13,6 +13,11 @@ class ExceptionHandle extends Handle 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信息 if (!\think\Config::get('app_debug')) { $statuscode = $code = 500; diff --git a/application/common/controller/Api.php b/application/common/controller/Api.php index f685ca2..1ca0c4e 100755 --- a/application/common/controller/Api.php +++ b/application/common/controller/Api.php @@ -3,6 +3,7 @@ namespace app\common\controller; use app\common\library\Auth; +use app\services\BaseService; use think\Config; use think\exception\HttpResponseException; use think\exception\ValidateException; @@ -17,7 +18,7 @@ use think\Validate; /** * API控制器基类 */ -class Api +class Api extends BaseService { /** diff --git a/application/common/controller/WorkerApi.php b/application/common/controller/WorkerApi.php new file mode 100644 index 0000000..939f90f --- /dev/null +++ b/application/common/controller/WorkerApi.php @@ -0,0 +1,319 @@ +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; + } +} diff --git a/application/common/model/Abnormal.php b/application/common/model/Abnormal.php new file mode 100644 index 0000000..af459df --- /dev/null +++ b/application/common/model/Abnormal.php @@ -0,0 +1,18 @@ +belongsTo(\app\admin\model\Area::class,'area_id', 'area_code'); + } +} diff --git a/application/common/model/OrderAbnormal.php b/application/common/model/OrderAbnormal.php new file mode 100644 index 0000000..62e67ba --- /dev/null +++ b/application/common/model/OrderAbnormal.php @@ -0,0 +1,21 @@ +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); + } +} + + + + + + + + + + + + + + + diff --git a/application/common/model/Worker.php b/application/common/model/Worker.php new file mode 100644 index 0000000..fad9738 --- /dev/null +++ b/application/common/model/Worker.php @@ -0,0 +1,17 @@ + true, //允许跨域的域名,多个以,分隔 - 'cors_request_domain' => 'localhost,127.0.0.1', + 'cors_request_domain' => 'localhost,127.0.0.1,*', //版本号 'version' => '1.5.3.20250217', //API接口地址 'api_url' => 'https://api.fastadmin.net', ], + 'mini_program' => [ + 'app_id' => Env::get('mini_program.app_id', ''), + 'secret' => Env::get('mini_program.secret', ''), + ] ]; diff --git a/application/extra/upload.php b/application/extra/upload.php index 24357af..ede454f 100755 --- a/application/extra/upload.php +++ b/application/extra/upload.php @@ -9,7 +9,7 @@ return [ /** * CDN地址 */ - 'cdnurl' => '', + 'cdnurl' => get_addon_config('alioss')['cdnurl'], /** * 文件保存格式 */ diff --git a/application/services/AbnormalService.php b/application/services/AbnormalService.php new file mode 100644 index 0000000..c4835af --- /dev/null +++ b/application/services/AbnormalService.php @@ -0,0 +1,18 @@ +getAbnormalModel() + ->where('type', 1) + ->order('sort', 'desc') + ->field([ + 'id', + 'title', + ]) + ->select(); + } +} diff --git a/application/services/BaseService.php b/application/services/BaseService.php new file mode 100644 index 0000000..2fdb5cd --- /dev/null +++ b/application/services/BaseService.php @@ -0,0 +1,148 @@ + $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%} +} diff --git a/application/services/OrderAbnormalService.php b/application/services/OrderAbnormalService.php new file mode 100644 index 0000000..bf953eb --- /dev/null +++ b/application/services/OrderAbnormalService.php @@ -0,0 +1,83 @@ +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(); + } +} + + + + + + + + + + + + + + + + + + + + + diff --git a/application/services/OrderDispatchService.php b/application/services/OrderDispatchService.php new file mode 100644 index 0000000..7a9431e --- /dev/null +++ b/application/services/OrderDispatchService.php @@ -0,0 +1,395 @@ +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; + } +} + + + + + + + + + + + + diff --git a/application/services/OrderService.php b/application/services/OrderService.php new file mode 100644 index 0000000..03b52cb --- /dev/null +++ b/application/services/OrderService.php @@ -0,0 +1,9 @@ +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; + } +} diff --git a/application/services/WorkerVendorService.php b/application/services/WorkerVendorService.php new file mode 100644 index 0000000..602f88d --- /dev/null +++ b/application/services/WorkerVendorService.php @@ -0,0 +1,43 @@ +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(); + } +} diff --git a/application/worker/config.php b/application/worker/config.php new file mode 100644 index 0000000..0844157 --- /dev/null +++ b/application/worker/config.php @@ -0,0 +1,6 @@ + '\\app\\api\\library\\ExceptionHandle', +]; diff --git a/application/worker/controller/Abnormal.php b/application/worker/controller/Abnormal.php new file mode 100644 index 0000000..3409469 --- /dev/null +++ b/application/worker/controller/Abnormal.php @@ -0,0 +1,16 @@ +getAbnormalService()->findAll(); + $this->success('操作成功', $res); + } +} diff --git a/application/worker/controller/Common.php b/application/worker/controller/Common.php new file mode 100644 index 0000000..1ffdfd5 --- /dev/null +++ b/application/worker/controller/Common.php @@ -0,0 +1,25 @@ +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); + } +} \ No newline at end of file diff --git a/application/worker/controller/Order.php b/application/worker/controller/Order.php new file mode 100644 index 0000000..3976d33 --- /dev/null +++ b/application/worker/controller/Order.php @@ -0,0 +1,10 @@ +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 ?: []); + } +} + + + + + + + + + + + + + + + + + + + + + diff --git a/application/worker/controller/OrderDispatch.php b/application/worker/controller/OrderDispatch.php new file mode 100644 index 0000000..6f07222 --- /dev/null +++ b/application/worker/controller/OrderDispatch.php @@ -0,0 +1,154 @@ +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); + } +} + + + + + + + + + + + diff --git a/application/worker/controller/Worker.php b/application/worker/controller/Worker.php new file mode 100644 index 0000000..38eecc0 --- /dev/null +++ b/application/worker/controller/Worker.php @@ -0,0 +1,80 @@ +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); + } +} diff --git a/application/worker/controller/WorkerVendor.php b/application/worker/controller/WorkerVendor.php new file mode 100644 index 0000000..806a0cb --- /dev/null +++ b/application/worker/controller/WorkerVendor.php @@ -0,0 +1,10 @@ + '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'], + ]; +} diff --git a/application/worker/validate/OrderDispatch.php b/application/worker/validate/OrderDispatch.php new file mode 100644 index 0000000..0e01e33 --- /dev/null +++ b/application/worker/validate/OrderDispatch.php @@ -0,0 +1,38 @@ + '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'], + ]; +} diff --git a/application/worker/validate/Worker.php b/application/worker/validate/Worker.php new file mode 100644 index 0000000..43c1b06 --- /dev/null +++ b/application/worker/validate/Worker.php @@ -0,0 +1,22 @@ + 'require|max:128', + 'vendor_token' => 'require|max:128', + ]; + + protected $message = [ + + ]; + + protected $scene = [ + 'login' => ['code'], + 'bindPhoneNumber' => ['code', 'vendor_token'], + ]; +} diff --git a/composer.json b/composer.json index bdab9f9..3b6e6c0 100755 --- a/composer.json +++ b/composer.json @@ -31,6 +31,7 @@ "ext-bcmath": "*", "txthinking/mailer": "^2.0", "symfony/var-dumper": "^6.4", + "gjc/thinkphp5-container": "^1.0.1", "alibabacloud/dyvmsapi-20170525": "^3.2", "nesbot/carbon": "^3.8" },