【PHP庫】phpseclib – sftp遠程文件操作

需求場景說明

對接的三方商家需要進行文件傳輸,並且對方提供的方式是 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;

參考教程