【PHP庫】phpseclib

2022-08-08 06:04:34

需求場景說明

對接的三方商家需要進行檔案傳輸,並且對方提供的方式是 sftp 的伺服器賬號,我們需根據他們提供的目錄進行下載和上傳指定檔案。

安裝

composer require phpseclib/phpseclib:~3.0

使用sftp功能

1.新建並設定config/sftp.php檔案


return [
    'sftp' => [
        'host'   => env('SFTP_HOST', '127.0.0.1'),
        'port'   => env('SFTP_PORT', 22),
        'user' => env('SFTP_USE', null),
        'password' => env('SFTP_PASSWORD', null),
    ],
];

2.設定.env檔案

SFTP_HOST=127.0.0.1
SFTP_PORT=22
SFTP_USE=user
SFTP_PASSWORD=password

3.封裝 app/Utils/SftpHelper.php呼叫庫檔案,通過單例可設定不同的 sftp 伺服器

namespace App\Utils;

use phpseclib3\Net\SFTP;

class SftpHelper
{
    private static $instance = [];

    public static function getInstance($key='sftp')
    {
        if (!isset(self::$instance[$key])) {
            $config = ConfigHelper::getInstance()->read('sftp.'.$key);
            self::$instance[$key] = new SFTP($config['host'], $config['port']);
            self::$instance[$key]->login($config['user'], $config['password']);
        }

        return self::$instance[$key];
    }
}

4.使用方法說明

  • nlist:獲取指定目錄下的檔案列表,包括子目錄,(預設不會遞迴子目錄下的檔案)
  • is_readable: 判斷檔案是否有讀許可權
  • chmod:修改檔案/目錄許可權,預設不遞迴
  • get:獲取檔案,預設獲取檔案內容。
  • is_dir:是否存在該目錄
  • mkdir:建立目錄
  • rename: 將檔案重新命名
  • put:上傳檔案

5.存取 sftp 伺服器並下載檔案到本地

5.1 讀取指定伺服器下的檔案,並回圈處理每個檔案

5.2 下載遠端檔案到當前伺服器的指定位置,並建立待處理檔案記錄表

說明:建立檔案處理表可使檔案讀取邏輯失敗時,可重複處理,並且不需要多次存取 sftp 伺服器,進行邏輯解耦

5.3 建立檔案記錄資料後將伺服器上的檔案移到歸檔目錄,避免重複讀取

// 連線sftp伺服器並登入
$sftp = SftpHelper::getInstance('sftp');
// 獲取目錄下的檔案列表(不遞迴)
$file_list = $sftp->nlist($remote_dir);

// 迴圈檔案列表,獲取處理資料
foreach ($file_list as $file_name) {
    // 跳過不處理的目錄
    if (in_array($file_name, ['.', '..', 'Archive'])) {
        continue;
    }

    // 拼接完整的伺服器檔案路徑
    $remote_file = $remote_dir.$file_name;

    // 設定本地儲存的目錄
    $save_path = env('FILE_PATH', '/data/storage/sftp/')."{$file_type}/";
    File::exists($save_path) or (File::makeDirectory($save_path, 0777, true) && @chmod($save_path, 0777));

    // 完整的本地路徑
    $local_file = $save_path. $file_name;
    // 拉取sftp檔案到本地目錄
    if (!file_exists($local_file)) {
        if (!$sftp->is_readable($remote_file)) {
            $sftp->chmod('0777', $remote_file);
        }

        $sftp->get($remote_file, $local_file);
    }

    // 新增檔案紀錄檔(同一個遠端檔案不重複拉取)
    // 後續可單獨增加檔案讀取邏輯,使檔案內容處理失敗時可重複處理,並且不需要重複存取 sftp 伺服器去讀取遠端檔案
    SftpFile::updateOrCreate([
        'remote_dir'  => $remote_file,
    ], [
        'action'     => $file_type, // 檔案型別
        'filename'   => $file_name, // 檔名
        'filepath'   => $local_file, // 本地伺服器路徑
    ]);
    
    // 紀錄檔建立成功之後再將檔案移到Archive目錄下,避免重複讀取
    if (!$sftp->is_dir($remote_dir.'Archive/')) {
        // 沒有則建立Archive目錄
        $sftp->mkdir($remote_dir.'Archive/');
    }

    // 已讀取的檔案移到子目錄Archive
    $sftp->rename($remote_file, "Archive/{$remote_file}");
}

6.上傳檔案到 sftp 伺服器的指定位置

// 讀取待處理的檔案列表
$file_list = SftpFile::where([
    'action' => $file_type,
    'state'  => 1
])->get();
if (count($file_list) <= 0) {
    return;
}

// 連線sftp伺服器並登入
$mk_sftp = SftpHelper::getInstance('sftp');

foreach ($file_list as $file) {

    // 校驗推播的檔案是否存在
    if (!file_exists($file->filepath)) {
        throw new ParamsException('推播的檔案不存在');
    }

    $file_path = $file->filepath;
    $remote_file = $file->remote_dir;

    // 推播檔案到sftp伺服器
    // SFTP::SOURCE_LOCAL_FILE 表示以檔案的形式,不設定時表示是按字串形式上傳
    $put_res = $mk_sftp->put($remote_file, $file_path, SFTP::SOURCE_LOCAL_FILE);

    if ($put_res) {
        $file->state = 1;
        $file->save();
    }
}

7.讀取檔案內容

// 當前php.ini設定的是128M
ini_set('memory_limit', '300M');

$local_file = $file_info['filepath'];
$remote_file = $file_info['remote_dir'];

// 讀取檔案資料
$fp = fopen($local_file, 'r');

$file_data = [];
while (!feof($fp)) {

    $row_str = fgets($fp); // 逐行讀取。如果fgets不寫length引數,預設是讀取1k。
    $item = explode(',', trim($row_str));
    
    // 跳過表頭
   
    // 將行資料轉成指定的鍵值對
}

return $file_data;

參考教學