Laravel 服務提供者業務使用例項

ligkwww發表於2019-11-26

laravel服務提供者使用例項

前言

之前在網上看到過很多「laravel容器」、「服務提供者」相關的文章,但大部分只是介紹框架內容器的組成和使用方式,貌似關於實際的業務應用很少,這樣下來時間長了,可能僅僅看過原始碼過段時間就忘記了,正好最近公司的專案里正好要做一些功能的調整,所以就應用在業務中,加深自己理解的同時也深刻體會到框架帶來的方便。

業務背景

專案裡有一個視訊轉碼的功能,登入會員可以上傳任意視訊至平臺,之前使用的第三方服務是七牛,最近要改成阿里雲,我們要做的就是利用容器來自定義呼叫第三方SDK。

一開始說做這個功能時,第一時間考慮是直接改業務程式碼。後來想萬一哪天又要重新用七牛的怎麼辦,所以還是需要做成可配置的。事實證明這個決定是正確的,過了沒兩個月公司果然又用七牛的服務了,這個需求一提,老夫心中暗喜,幸虧提前做了準備,否則又要浪費大把時間來改程式碼了,就好比已經上路多年的老司機,突然要讓你從科目一再重新考一遍駕照,你說費勁麼?也不費勁,但就是需要去做重複工作。

開始動工吧

改動之前

在正式開始之前,我覺得有必要稍微花一點點時間看一下改動之前的呼叫方式,以便可以全面的瞭解一下,如果不感興趣的可以直接跳過~


public function transcodeVideo(){
    //這裡對引數內容進行了簡化,關注流程就好:)
    $url = $this->url;  
    //呼叫七牛的轉碼服務
    return FileService::getInstance()->transcodeVideo($url,callback?mid=".$this->id);
}

//呼叫七牛的轉碼服務,一開始是考慮直接改這個方法,當然這麼做也確實可以。
public function transcodeVideo($video,$callback_url){
    Log::info(__CLASS__.'transcodeVideo param:',compact('video','callback_url'));
    $auth = new Auth(env('QINIU_ACCESSKEY'), env('QINIU_SECRETKEY'));
    $c = new \Qiniu\Processing\PersistentFop($auth);  
    $ret = $c->execute(env('QINIU_BUCKET'),$video,'avthumb/mp4/vcodec/libx264/s/720x720/autoscale/1|saveas/'.\Qiniu\base64_urlSafeEncode(env('QINIU_BUCKET').':'.md5($video).'.mp4'),'video1',$callback_url,true);
    Log::info(__CLASS__.'.transcodeVideo ret:',$ret);
    if(is_array($ret) && count($ret) > 0) {
        return true;
    }else{
        return false;
    }
}

其實有一個問題就是,因為專案肯定不止一個人在用到這個功能,所以呼叫方也是五花八門的,不全域性搜尋一下,你都不知道有多少個人在使用這個功能,所以我們做一個統一入口是很有必要的。

改動之後

首先我們註冊一個視訊轉碼的服務提供者,具體怎麼建立大家可以參考官方文件,有很詳細的介紹,這裡就不再多說了。

public function register()
{
    //如果要換其他的服務方,直接把匿名函式中的類改一下就好了
    $this->app->singleton(VideoTranscodeService::class, function($app){
        return new \Aliyun\VideoTranscodeService();
    });
}

那麼我們看到了,如果要實現這個服務,我們需要構建的基礎程式碼結構:

1.定義抽象父類: VideoTranscodeService

2.定義七牛實現: Qiniu\VideoTranscodeService

3.定義阿里雲實現: Aliyun\VideoTranscodeService

下面附上實現程式碼,有相關業務需求的同學也可以參考一下。

抽象父類:

abstract class VideoTranscodeService{

    protected $sourceUrl = '';
    protected $extra = [];
    protected $scene = '';

    /**
     * 提交轉碼作業
     * @param string $scene         場景
     * @param string $sourceUrl     原始檔地址
     * @param array $extra          額外資料
     * @return mixed
     * @throws BusinessException
     */
    public function videoTranscode($scene, $sourceUrl = '', $extra = [])
    {
        Log::info(__CLASS__.'.videoTranscode param:', compact('scene', 'sourceUrl','extra'));

        if(!$scene) {
            Log::warning(__CLASS__.'.videoTranscode loss scene');
            throw new BusinessException('缺少場景',Error::INVALID_PARAM);
        }

        if(!$sourceUrl) {
            Log::warning(__CLASS__.'.videoTranscode loss sourceUrl');
            throw new BusinessException('缺少原始檔地址',Error::INVALID_PARAM);
        }

        $this->scene = $scene;
        $this->sourceUrl = $sourceUrl;
        $this->extra = $extra;

        return call_user_func([$this, $this->scene]);
    }

    /**
     * @param $func
     * @param $arg
     * @throws BusinessException
     */
    public function __call($func = '', $arg = []){
        Log::warning(__CLASS__ . $func . ' function not Exists');
        throw new BusinessException('訪問不存在的方法', Error::INVALID_PARAM);
    }

    /**
     * 根據原始檔地址,生成轉碼後地址
     * @param string $sourceUrl
     * @return mixed
     */
    abstract public function makeCompressUrl($sourceUrl);

    /**
     * 根據視訊地址,生成封面圖地址
     * @param $videoUrl
     * @return mixed
     */
    abstract public function makeCoverImg($videoUrl);

}

七牛轉碼服務:

class VideoTranscodeService extends  TranscodeService{

    /**
     * 視訊轉碼
     * @param string $sourceUrl
     * @param array $extra
     * @return bool
     * @throws BusinessException
     */
    protected function interaction(){
        Log::info(__CLASS__.'.interaction param:', [$this->sourceUrl, $this->extra]);

        $callbackUrl = $this->makeCallbackUrl($this->extra['id']);

        return $this->submitTranscodeJob($callbackUrl);
    }

    /**
     * 提交轉碼作業
     * @param $video
     * @param $callbackUrl
     * @return bool
     */
    protected function submitTranscodeJob($callbackUrl = ''){
        //原始檔
        $video = basename($this->sourceUrl);

        //認證
        $auth = new Auth(env('QINIU_ACCESSKEY'), env('QINIU_SECRETKEY'));
        $c = new \Qiniu\Processing\PersistentFop($auth);

        //輸出(轉碼)地址
        $output = VideoTranscodeConf::$videoPath[$this->scene]['output'].md5($video).'.mp4';
        $saveAs = \Qiniu\base64_urlSafeEncode(env('QINIU_BUCKET').':'.$output);
        $fops = 'avthumb/mp4/vcodec/libx264/s/720x720/autoscale/1|saveas/'. $saveAs;

        //提交轉碼作業
        $ret = $c->execute(env('QINIU_BUCKET'),$video,$fops,'video1',$callbackUrl,true);
        Log::info(__CLASS__.'.submitTranscodeJob ret:',$ret);

        if (is_array($ret) && count($ret) > 0) {
            return true;
        }else{
            return false;
        }
    }

    //生成七牛回撥地址
    protected function makeCallbackUrl($extra){
        $data = $this->scene.'#'.$extra;
        return route('qiniuCallback',['data' => $data]);
    }

    /**
     * 根據原始檔地址,生成轉碼後地址
     * @param string $sourceUrl
     * @return mixed|string
     * @throws BusinessException
     */
    public function makeCompressUrl($sourceUrl = ''){
        if(!$sourceUrl) {
            Log::warning(__CLASS__.'.makeCompressUrl sourceName Not Exists');
            throw new BusinessException('原始檔地址錯誤',Error::INVALID_PARAM);
        }
        return env('QINIU_HOST')."/".md5(basename($sourceUrl)).".mp4";
    }

    /**
     * 根據視訊地址,生成封面圖地址
     * @param $videoUrl
     * @return string
     */
    public function makeCoverImg($videoUrl){
        return $videoUrl.'?vframe/jpg/offset/0';
    }
}

阿里雲轉碼服務:

class VideoTranscodeService extends TranscodeService{

    /**
     *
     * VideoTranscodeService constructor.
     * @throws ClientException
     */
    public function __construct(){
        AlibabaCloud::accessKeyClient(AliyunConf::VIDEO_ACCESS_KEY, AliyunConf::VIDEO_ACCESS_KEY_SECRET)
            ->regionId(env('ALIYUN_VIDEO_REGION'))
            ->asGlobalClient();
    }

    /**
     * 視訊轉碼
     * @param string $sourceUrl         輸入視訊地址 
     * @param array $extra              額外配置引數: id、 width(視訊寬度)
     * @return bool                     請求結果,僅代表請求是否成功,不代表最終轉碼結果
     * @throws BusinessException
     * @throws ClientException
     * @throws ServerException
     */
    protected function interaction(){
        Log::info(__CLASS__.'.interaction param:', [$this->sourceUrl,$this->extra]);

        $ossLocation = env('ALIYUN_VIDEO_BUCKET_LOCATION');
        $ossBucket = env('ALIYUN_VIDEO_BUCKET_NAME');
        $userData = 'interaction#'.$this->extra['id'];
        $templateId = $this->getTemplate($this->extra['width']);

        $input = [
            'Location' => $ossLocation,
            'Bucket' => $ossBucket,
            'Object' => urlencode($this->makeInputObject())
        ];

        $outputs = [[
            'OutputObject' => urlencode($this->makeOutputObject()),
            'Container' => ['Format' => AliyunConf::VIDEO_TRANSCODE_FORMAT],
            'TemplateId' => $templateId,
            'UserData' => $userData
        ]];

        return $this->submitTranscodeJob($input, $outputs, $ossLocation, $ossBucket);
    }

    /**
     * 提交轉碼作業
     * @param $input
     * @param $outputs
     * @param string $ossLocation
     * @param string $ossBucket
     * @return bool
     * @throws ClientException
     * @throws ServerException
     */
    protected function submitTranscodeJob($input, $outputs, $ossLocation = '', $ossBucket = ''){
        $result = AlibabaCloud::mts()
            ->v20140618()
            ->submitJobs()
            ->setAcceptFormat('JSON')
            ->withInput(json_encode($input))
            ->withOutputs(json_encode($outputs))
            ->withOutputBucket($ossBucket)
            ->withOutputLocation($ossLocation)
            ->withPipelineId(env('ALIYUN_VIDEO_TRANSCODE_PIPELINE'))
            ->request();

        return $result->isSuccess();
    }

    /**
     * 根據原始檔名稱,生成轉碼後地址(主要用作於類外直接呼叫)
     * @param string $sourceUrl     轉碼前原始檔名稱  
     * @return string               轉碼後URL地址    
     * @throws BusinessException
     */
    public function makeCompressUrl($sourceUrl = ''){
        if(!$sourceUrl) {
            Log::warning(__CLASS__.'.makeCompressUrl sourceName Not Exists');
            throw new BusinessException('原始檔地址錯誤',Error::INVALID_PARAM);
        }
        $inputFile = basename($sourceUrl);
        $outputObject = $this->makeOutputObject($inputFile);
        return env('ALIYUN_VIDEO_BUCKET_URL').$outputObject;
    }

    /**
     * 根據視訊地址,生成封面圖地址
     * @param $videoUrl
     * @return string
     */
    public function makeCoverImg($videoUrl){
        return $videoUrl.'?x-oss-process=video/snapshot,t_0,w_0,h_0,m_fast';
    }

    /**
     * 生成輸出檔案bucket地址
     * @param $inputFile string 輸入檔案 -用於類外部直接呼叫makeCompressUrl時生成的路徑
     * @return string
     * @throws BusinessException
     */
    protected function makeOutputObject($inputFile = ''){
        if (isset(VideoTranscodeConf::$videoPath[$this->scene]['output'])) {
            if (!$inputFile) {
                $inputFile = basename($this->sourceUrl);
            }

            return VideoTranscodeConf::$videoPath[$this->scene]['output'].md5($inputFile).'.'. AliyunConf::VIDEO_TRANSCODE_FORMAT;
        }else{
            Log::error(__CLASS__.'.makeOutputObject outputpath not config');
            throw new BusinessException('缺少輸出配置檔案', Error::INVALID_PARAM);
        }
    }

    /**
     * 生成輸入檔案bucket地址
     * @return string
     * @throws BusinessException
     */
    protected function makeInputObject(){
        if (isset(VideoTranscodeConf::$videoPath[$this->scene]['input'])) {
            return VideoTranscodeConf::$videoPath[$this->scene]['input'].basename($this->sourceUrl);
        } else {
            Log::error(__CLASS__.'.makeInputObject inputpath not config');
            throw new BusinessException('缺少輸入配置檔案', Error::INVALID_PARAM);
        }
    }

    /**
     * 根據寬度定義轉碼模板
     * @param int $width
     * @return string
     */
    protected function getTemplate($width = 0){
        if($width > 1280) {
            $templateId = AliyunConf::VIDEO_TRANSCODE_HD;   //高清
        }else{
            $templateId = AliyunConf::VIDEO_TRANSCODE_SD;   //標清
        }
        return $templateId;
    }
}

這樣我們的服務就寫好了,在業務程式碼中只需要呼叫下面程式碼即可完成視訊的轉碼,而不需要關注第三方的服務到底是誰。(程式碼中隱藏了部分引數配置資訊,但不影響整體流程)

app(VideoTranscodeService::class)->videoTranscode('interaction', $this->url, $extra);

結尾

至此我們的視訊轉碼服務就已經完成,即便是後期再增加其他的第三方SDK,也僅僅是增加一個實現就可以了,業務程式碼完全不需要改動。目前來看效果還不錯,其實掌握這些框架或者模式相關的東西,不僅是要充實自己的理論知識,同時也是在面對需求調整時可以儘量小的改動程式碼來完成功能的實現,減少程式碼的耦合,減輕日後維護的困擾,所以我們在學習過後還是儘可能的用起來,不要僅僅停留在看過xxx,聽說過xxx。

相關文章