【PHP庫】phpseclib – sftp遠程文件操作
- 2022 年 8 月 7 日
- 筆記
- 【Composer】, 【PHP】, php
需求場景說明
對接的三方商家需要進行文件傳輸,並且對方提供的方式是 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;