标签搜索

PHP面向对象中的接口在实际工作中的使用场景

basil
2023-10-04 / 52 阅读

什么是PHP面向对象的接口

在PHP中,面向对象的接口是用来定义类的行为规范的一种机制,接口可以定义一个类应该实现哪些方法,但不需要具体实现这些方法。

使用场景(以单据导出功能为例)

UML类图

UML类图

实现原理

在日常业务功能开发过程中,单据导出是一个很常用的功能,为了能够顺利的导出数据,主要考虑数据量太大导致内存溢出和响应超时的问题, 因此一般采用异步的方式,虽然每个单据导出的字段和内容不一样,但是可以抽象出共有的地方,异步导出功能共有的地方一般包括开始导出任务、执行导出任务、任务成功回调以及任务失败回调这些方法,这些共有的方法可以用接口来定义,每个单据类可以实现这个接口来补充具体的导出逻辑,然后异步任务根据这个接口依次执行这些方法,如上图所示,定义ExportTaskInterface接口,接口包含startExportTaskdoExportTaskexportTaskFailedHandletaskFinishHandle方法,定义Order类并实现ExportTaskInterface接口,定义ExportExcelJob类对Order实例导出数据相关方法进行调用。

实现步骤

定义ExportTaskInterface接口
<?php


namespace app\contract\excel;


use think\queue\Job;

interface ExportTaskInterface
{
    /**
     * 开始导出任务
     * @param array $params 创建任务的参数
     * @return mixed
     */
    public function startExportTask(array $params) : array;

    /**
     * 导出Excel
     * @param array $params
     * @return mixed
     */
    public function doExportTask(array $params) : array;

    /**
     * 任务失败时的处理方法
     * @param \Exception $e
     * @param array $params
     * @param Job $job
     * @return mixed
     */
    public function exportTaskFailedHandle(\Exception $e, array $params, Job $job) : array;

    /**
     * 任务完成时的处理方法
     * @param array $exportResult
     * @param Job $job
     * @return mixed
     */
    public function taskFinishHandle(array $exportResult, Job $job) : array;
}
定义Order类,并实现ExportTaskInterface接口
<?php
namespace app\admin\service;

use app\admin\model\Region;
use app\common\MyLog;
use think\Db;
use think\facade\Env;
use think\facade\Config;
use app\admin\model\GoodsBrand as GoodsBrandModel;
use app\admin\model\Order;
use app\admin\model\OrderDelivery as OrderDeliveryModel;
use app\admin\model\OrderGoods;
use app\admin\model\OrderPackGoods;
use app\admin\model\Products;
use app\contract\excel\ExportTaskInterface;
use app\facade\model\OrderChange;
use app\library\enum\AsyncTaskEnum;
use app\library\enum\ErpEnum;
use app\models\AsyncTask;
use app\models\OrderChangeGoods;
use app\models\OrderChangePackGoods;
use app\service\common\XlsWriterService;
use app\service\QueueService;
use app\admin\logic\Erp as ErpLogic;
use app\library\exception\{ServiceLogicException};
use app\admin\model\OrderPackGoods as OrderPackGoodsModel;
use app\facade\model\OrderGoods as OrderGoodsModelFacade;
use app\facade\model\Order as OrderModelFacade;
use app\facade\model\Goods as GoodsModelFacade;
use app\facade\model\OrderChange as OrderChangeModelFacade;
use app\facade\model\UserAccountLog as UserAccountLogModelFacade;
use app\facade\model\OrderDelivery as OrderDeliveryModelFacade;
use app\facade\model\UserBill as UserBillModelFacade;
use app\facade\model\DiyPack as DiyPackModel;
use app\facade\model\DiySolution as DiySolutionModel;
use app\admin\model\Order as AdminOrderModel;
use app\api\model\Order as ApiOrderModel;
use app\facade\service\client\ActivityService;
use app\plogic\OrderLogic;
use excel\Excel;
use think\queue\Job;
use app\common\BaseResult;
use app\common\basic\Base;
use app\library\enum\ErrorCodeEnum;

class Order implements ExportTaskInterface
{

    /**
     * 开始导出任务
     * @param array $params
     * @return array
     */
    public function startExportTask(array $params): array
    {

        $name = '订货单数据_' . date("YmdHis");
        $data = [
            'user_id' => $params['admin_id'],
            'name' => $name,
            'task_type' => AsyncTaskEnum::TASK_TYPE_EXCEL_EXPORT,
            'identification' => AsyncTaskEnum::IDENTIFICATION_EXCEL_EXPORT,
            'task_params' => json_encode($params),
            'out_file_name' => $name . '.xlsx',
        ];
        $taskId = AsyncTask::createTask($data);
        $params['task_id'] = $taskId;
        $params['handler'] = self::class;
        QueueService::push('excel_export', $params);
        return ['msg' => '已创建导出任务,请在下载中心查看', 'task_id' => $taskId];
    }

    /**
     * 进行导出任务
     * @param array $params
     * @return array
     * @throws \Pxlswrite\DataFormatException
     */
    public function doExportTask(array $params): array
    {
        $orderModel=new Order();
        $data= $orderModel->get_order($params['where']);
        if (empty($data)){
            throw new ServiceLogicException('没有可导出的数据');
        }
        $now = time();
        $filename = "订货单数据{$params['admin_id']}_{$now}.xlsx";
        $field = [
            'order_sn'              =>  ['name' =>'订单编号'],
            'erp_sn'                =>  ['name' =>'ERP订单编号'],
            'user_name'             =>  ['name' =>'客户名称'],
            'brand_name'            =>  ['name' =>'品牌'],
            'order_status'          =>  ['name' =>'订单状态'],
            'pay_status'            =>  ['name' =>'付款状态'],
            'pay_name'              =>  ['name' =>'支付方式'],
            'is_special_approval'   =>  ['name' =>'是否特殊审批单'],
            'prepare'               =>  ['name' =>'是否备货单'],
            'goods_amount'          =>  ['name' =>'商品总金额'],
            'order_amount'          =>  ['name' =>'订单金额'],
            'money_paid'            =>  ['name' =>'已付款金额'],
            'cash_money'            =>  ['name' =>'抵扣券抵扣金额'],
            'add_time'              =>  ['name' =>'下单时间'],
            'pay_time'              =>  ['name' =>'付款时间'],
            'address'               =>  ['name' =>'详细地址'],
            'tel'                   =>  ['name' =>'电话号码'],
            'mobile'                =>  ['name' =>'手机号码'],
            'consignee'             =>  ['name' =>'收货人姓名'],
            'close_reason'          =>  ['name' =>'订单关闭原因'],
            'remark'                =>  ['name' =>'订单备注'],
            'action_user'           =>  ['name' =>'最后操作人'],
            'email'                 =>  ['name' =>'邮箱'],
            'service_name'          =>  ['name' =>'销售内勤'],
            'pay_type'              =>  ['name' =>'结款方式'],
        ];

        $fileObj = XlsWriterService::getInstance()
            ->constMemory($filename, null, false)
            ->field($field);
        $orderStatusFields = ['0'=>'未确认','1'=>'已确认','2'=>'已取消','3'=>'无效','4'=>'退货','5'=>'已分单','6'=>'部分分单','7'=>'已关闭'];
        $payStatusFields = ['0'=>'未付款','1'=>'付款中','2'=>'已付款','3'=>'部分付款'];
        $page = 1;
        while (true){
            $fields = ['order_id','is_valet','is_from_quotation','order_total_amount','od.user_id','order_sn','pay_status','money_paid','cash_money','surplus_money','company_name','od.add_time','od.pay_time','order_amount','goods_amount','order_status','shipping_status','action_user','od.remark','od.pay_type','od.consignee','od.tel','od.mobile','pm.pay_name','ui.user_rank','brand_id','order_type','order_from','od.erp_sn','is_special_approval','prepare','od.address','od.close_reason','od.email','ui.service_name','od.integral','od.extension_code','od.is_unite_promotion', 'od.province', 'od.city', 'od.district'];
            $data= $orderModel->get_order($params['where'],$fields,[],'list',true,$page);

            $data=$data->toArray();
            if (empty($data['data'])){
                break;
            }
            $orderListData = $data['data'];
            $order_ids = array_column($orderListData, 'order_id');
            $brand_ids = array_column($orderListData, 'brand_id');
            $order_goods = OrderService::getInstance()->getOrderGoodsList($order_ids);
            // 订单商品品牌
            $brands = GoodsBrandModel::where('brand_id', 'in', array_unique($brand_ids))->column('brand_id,brand_name');
            // 发货单金额
            $orderDeliveryAmount = OrderDeliveryModel::where([['order_id','in',$order_ids],['status','in',[0,2,3,4,5]]])->group('order_id')->column('order_id,sum(order_amount)');
            $order_change = OrderChange::where(['order_id'=>$order_ids,'status'=>1])->group('order_id')->column('change_id','order_id');
            $items = [];

            foreach ($orderListData as &$value)
            {
                $value['pay_name'] = $value['surplus_money'] > 0 ? '余额支付' : $value['pay_name'];
                // 关闭可退回订单金额
                $can_return_amount = $value['money_paid'] == 0 && $value['surplus_money'] > 0 ? $value['surplus_money'] : $value['money_paid'];
                $delivery_amount = $orderDeliveryAmount[$value['order_id']] ?? 0;
                if ($delivery_amount > 0) {
                    $can_return_amount = $can_return_amount - $delivery_amount;
                }
                if ($value['pay_type'] == 1) {
                    $can_return_amount = 0;
                }
                $value['can_return_amount'] = number_format($can_return_amount, 2);
                $value['brand'] = isset($brands[$value['brand_id']]) ? ['brand_id'=>$value['brand_id'],'brand_name'=>$brands[$value['brand_id']]] : [];
                if(isset($order_goods[$value['order_id']]))
                {
                    $value['children'] = $order_goods[$value['order_id']];
                    foreach ($value['children'] as &$vc)
                    {
                        if ($vc['goods_type'] == 1) {
                            $vc['product_sn'] = $vc['goods_sn'];
                        }
                    }
                    unset($vc);
                }
                $value['add_time'] =date('Y-m-d H:i',$value['add_time']);
                $value['pay_time'] = $value['pay_time'] ? date('Y-m-d H:i',$value['pay_time']) : '';
                //是否有变更中的订单
                $value['is_changing'] = isset($order_change[$value['order_id']]) ? 1 : 0;
                $value['operation'] = false;
                $region = Region::where('region_id', 'in', [$value['province'], $value['city'], $value['district']])
                    ->column('region_name', 'region_id') ?: [];
                $province = $region[$value['province']] ?? '';
                $city = $region[$value['city']] ?? '';
                $district = $region[$value['district']] ?? '';
                $value['address'] = $province . $city . $district . $value['address'];
            }
            unset($value);
            $exportData = [];
            foreach ($orderListData as $key => $value)
            {
                $exportData[$key]['order_sn']                   = $value['order_sn'];
                $exportData[$key]['erp_sn']                     = $value['erp_sn'];
                $exportData[$key]['user_name']                  = $value['company_name'];
                $exportData[$key]['brand_name']                 = $value['brand']['brand_name'] ?? '';
                $exportData[$key]['order_status']               = $orderStatusFields[$value['order_status']];
                $exportData[$key]['pay_status']                 = $payStatusFields[$value['pay_status']];
                $exportData[$key]['pay_name']                   = $value['pay_name'];
                $exportData[$key]['is_special_approval']        = $value['is_special_approval'] ? '是' : '否';
                $exportData[$key]['prepare']                    = $value['prepare'] ? '是' : '否';
                $exportData[$key]['goods_amount']               = $value['goods_amount'];
                $exportData[$key]['order_amount']               = $value['goods_amount'];
                $exportData[$key]['money_paid']                 = $value['money_paid'];
                $exportData[$key]['cash_money']                 = $value['cash_money'];
                $exportData[$key]['add_time']                   = $value['add_time'];
                $exportData[$key]['pay_time']                   = $value['pay_time'];
                $exportData[$key]['address']                    = $value['address'];
                $exportData[$key]['tel']                        = $value['tel'];
                $exportData[$key]['mobile']                     = $value['mobile'];
                $exportData[$key]['consignee']                  = $value['consignee'];
                $exportData[$key]['close_reason']               = $value['close_reason'];
                $exportData[$key]['remark']                     = $value['remark'];
                $exportData[$key]['action_user']                = $value['action_user'];
                $exportData[$key]['email']                      = $value['email'];
                $exportData[$key]['service_name']               = $value['service_name'];
                $exportData[$key]['pay_type']                   = $value['pay_type'] ? '月结' : '现结';
            }

            foreach ($exportData as $val) {
                $item = [];
                foreach ($field as $key => $value) {
                    $item[$key] = $val[$key] ?? '';
                }
                $items[] = array_values($item);
            }
            unset($exportData);
            !empty($items) && $fileObj->data($items);
            $page++;
        }
        $filePath = $fileObj->output();
        return ['file_path' => $filePath, 'task_id' => $params['task_id']];
    }

    public function exportTaskFailedHandle(\Exception $e, array $params, Job $job): array
    {
        AsyncTask::where('id', $params['task_id'])->update([
            'status' => AsyncTaskEnum::TASK_STATUS_FAIL,
            'exception_msg' => $e->getMessage()
        ]);
        $job->delete();
        return [];
    }

    public function taskFinishHandle(array $exportResult, Job $job): array
    {
        AsyncTask::where('id', $exportResult['task_id'])->update([
            'status' => AsyncTaskEnum::TASK_STATUS_FINISH,
            'download_url' => $exportResult['file_path']
        ]);
        $job->delete();
        return [];
    }
}
定义ExportExcelJob
<?php


namespace app\job\jobs;


use app\common\MyLog;
use app\contract\excel\ExportTaskInterface;
use app\library\exception\ServiceLogicException;
use think\queue\Job;

class ExportExcelJob
{
    public function fire(Job $job, $data)
    {
        if (!isset($data['handler'])){
            $job->delete();
            throw new ServiceLogicException('缺少处理类');
        }
        try {
            $this->handleJob(new $data['handler'], $data, $job);
        }catch (\Exception $e){
            var_dump($e->getFile() . $e->getLine() . $e->getMessage());
            MyLog::errorLog('error', $e->getFile() . $e->getLine() . $e->getMessage(), 'export_excel_task');
            $job->delete();
        }

    }

    protected function handleJob(ExportTaskInterface $exportTask, $data, Job $job)
    {
        MyLog::infoLog('export_excel_task', '接收到Excel导出任务:' . json_encode($data), 'export_excel_task');
        try {
            $result = $exportTask->doExportTask($data);
            $exportTask->taskFinishHandle($result, $job);
            MyLog::infoLog('export_excel_task_res', '执行成功:' . json_encode($result), 'export_excel_task');
        }catch (\Exception $e){
            MyLog::infoLog('export_excel_task_err', $e->getFile() . $e->getLine() . $e->getMessage(), 'export_excel_task');

            $exportTask->exportTaskFailedHandle($e, $data, $job);
        }
    }

    public function fail(Job $job, $data)
    {
        $job->delete();
    }
}
调用顺序

用户点击导出按钮调用Order实例的startExportTask方法,startExportTask方法把当前类以及相关参数传递到ExportExcelJob实例,通过ExportExcelJob实例调用handleJob方法进行导出操作,具体实现逻辑请参考这篇文章 使用thinkphp、think-queue、redis、supervisor搭建异步任务中心

0