Jump to content
  • Hello visitors, welcome to the Hacker World Forum!

    Red Team 1949  (formerly CHT Attack and Defense Team) In this rapidly changing Internet era, we maintain our original intention and create the best community to jointly exchange network technologies. You can obtain hacker attack and defense skills and knowledge in the forum, or you can join our Telegram communication group to discuss and communicate in real time. All kinds of advertisements are prohibited in the forum. Please register as a registered user to check our usage and privacy policy. Thank you for your cooperation.

    TheHackerWorld Official

【PHP库】phpseclib - sftp远程文件操作

 Share


Recommended Posts

需求场景说明

对接的三方商家需要进行文件传输,并且对方提供的方式是 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;

参考教程

  • 官方示例文档
  • github地址
  • api 文档
Link to post
Link to comment
Share on other sites

 Share

discussion group

discussion group

    You don't have permission to chat.
    • Recently Browsing   0 members

      • No registered users viewing this page.
    ×
    ×
    • Create New...