對接的三方商家需要進行檔案傳輸,並且對方提供的方式是 sftp 的伺服器賬號,我們需根據他們提供的目錄進行下載和上傳指定檔案。
composer require phpseclib/phpseclib:~3.0
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;