标签归档:文件系统

Node.js 操作文件系统

> 文件读写
完整读取一个文件时,可用readFile方法或readFileSync:

fs.readFile(filename, [options], callback)

var fs = require('fs');
fs.readFile('./t.txt', function(err, data){
	if(err) console.log("读文件发送错误")
	slse console.log(data);
});

注意,如果没有指定options,那么data就是二进制数据(Buffer对象,可调用toString()方法获取字符串),options设置:

flag		r, r+, w, w+, a, a+
encoding	utf8, ascii, base64

在options参数值中,可使用encoding属性指定使用何种编码格式来读取该文件,过程就是读取文件,然后转换成指定编码后存入Buffer对象,否则就是直接读取文件的原始二进制内容。如果在readFile函数中使用options参数并将encoding属性值指定为某种编码格式,则回调函数中的第二个参数值返回将文件内容根据指定编码格式进行编码后的字符串。

在使用同步方式读取文件时,使用readFileSync方法:

var data=fs.readFileSync(filename, [options])

在完整写入一个文件时,我们可以使用fs模块中的writeFile方法或writeFileSync方法:

fs.writeFile(filename,data,[options],callback)
fs.writeFileSync(filename,data, [options]);

var fs = require('fs');
fs.writeFile('./t.txt', "数据", function(err){
	if(err) console.log("写文件失败");
	else console.log("写文件成功");
});

// 向文件追加数据
var fs = require('fs');
fs.writeFile('./t.txt', "追加数据", {flag:'a'}, function(err){
	if(err) console.log("写文件失败");
	else console.log("写文件成功");
});

// base64读入图片,base64解码数据写入图片
// 读入的data是一个Buffer对象,toString后就是base64字符串
// 然后写文件时,指定base64解码字符串(得到二进制数据)
var fs=require('fs');
fs.readFile('./a.gif','base64',function(err,data){
	fs.writeFile('./b.gif',data.toString(), "base64", function(err){});
});

产生data可以是字符串也可以是一个Buffer对象。options设置:

flat		默认为w
mode		文件权限,默认0666
encoding	指定使用何种编码来写入文件,data是Buffer时被忽略

将一个字符串或一个缓存区中的数据追加到一个文件底部时,我们可以使用fs模块中的appendFile方法或appendFileSync方法。

>从指定位置开始读写文件

fs.open(filename, flags,[mode],callback)
fs.openSync(filename, flags,[mode])

//回调函数第二参数为打开的文件描述符
var fs=require('fs');
fs.open('./t.txt', 'r', function(err,fd) {

});

// 取到文件描述符后,使用read读取
fs.read(fd, buffer, offset, length, position, callback)

> 创建与读取目录

// mode默认为0777
fs.mkdir(path, [mode], callback);

// 读取目录,files是一个数组
var fs = require('fs');
fs.readdir('D:/', function(err, files){
    console.log(files);
});

> 查看文件或目录的信息

// 当查看连接文件时,必须使用lstat
fs.stat(path,callback);
fs.lstat(path, callback);

// stats是一个fs.Stats对象,有一系列方法和属性
// 比如是isFile isDirectory
fs.stat('./t.txt', function(err, stats){

});

在使用open方法或openSync方法打开文件并返回文件描述符后,可以使用fs模块中的fstat方法查询被打开的文件信息。

> 检查文件或目录是否存在

fs.exists(path, function(exists){});

> 获取文件或目录的绝对路径

fs.realpath(path, function(err, resolvedPath){});

> 修改文件访问时间及修改时间

fs.utime(path, atime, mtime, function(err){});
fs.utime(path, new Date(), new Date(), function(err){});

在使用open方法或openSync方法打开文件并返回文件描述符后,可以使用fs模块中的futimes方法修改文件的访问时间或修改时间。

> 修改文件或目录的读写权限

fs.chmod('./t.txt', 06000, function(err){});

> 文件或目录的其它操作

fs.rename(oldPath,newPath,callback)
fs.link(srcpath,dstpath,callback)
fs.unlink(path,callback)
fs.symlink(srcpath,dstpath,[type],callback)
fs.truncate(filename,len,callback)
fs.rmdir(path,callback)
fs.watchFile('./message.txt',function(curr, prev) {})

Laravel 文件系统详解

Laravel 文件系统的配置在conf/filesystems.php:

<?php

return [

    /*
    |--------------------------------------------------------------------------
    | Default Filesystem Disk
    |--------------------------------------------------------------------------
    |
    | Here you may specify the default filesystem disk that should be used
    | by the framework. A "local" driver, as well as a variety of cloud
    | based drivers are available for your choosing. Just store away!
    |
    | Supported: "local", "ftp", "s3", "rackspace"
    |
    */

    'default' => 'local',

    /*
    |--------------------------------------------------------------------------
    | Default Cloud Filesystem Disk
    |--------------------------------------------------------------------------
    |
    | Many applications store files both locally and in the cloud. For this
    | reason, you may specify a default "cloud" driver here. This driver
    | will be bound as the Cloud disk implementation in the container.
    |
    */

    'cloud' => 's3',

    /*
    |--------------------------------------------------------------------------
    | Filesystem Disks
    |--------------------------------------------------------------------------
    |
    | Here you may configure as many filesystem "disks" as you wish, and you
    | may even configure multiple disks of the same driver. Defaults have
    | been setup for each driver as an example of the required options.
    |
    */

    'disks' => [

        'local' => [
            'driver' => 'local',
            'root'   => storage_path('app'),
        ],

        'ftp' => [
            'driver'   => 'ftp',
            'host'     => 'ftp.example.com',
            'username' => 'your-username',
            'password' => 'your-password',

            // Optional FTP Settings...
            // 'port'     => 21,
            // 'root'     => '',
            // 'passive'  => true,
            // 'ssl'      => true,
            // 'timeout'  => 30,
        ],

        's3' => [
            'driver' => 's3',
            'key'    => 'xxx',
            'secret' => 'xxxxxx',
            'region' => 'us-west-2',
            'bucket' => 'xxx',
        ],

        'rackspace' => [
            'driver'    => 'rackspace',
            'username'  => 'your-username',
            'key'       => 'your-key',
            'container' => 'your-container',
            'endpoint'  => 'https://identity.api.rackspacecloud.com/v2.0/',
            'region'    => 'IAD',
            'url_type'  => 'publicURL',
        ],

    ],

];

先来了解一下这个文件。disks列出了所有支持的磁盘,磁盘的名称(key)也是磁盘的驱动名称(一一对应关系)。default对应了文件系统默认使用的磁盘驱动,clound对应了云存储对应的磁盘驱动。

然后,简单梳理下流程:

// 1 config/app.php中有Illuminate\Filesystem\FilesystemServiceProvider::class
<?php

namespace Illuminate\Filesystem;

use Illuminate\Support\ServiceProvider;

class FilesystemServiceProvider extends ServiceProvider
{
    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        $this->registerNativeFilesystem();

        $this->registerFlysystem();
    }

    /**
     * Register the native filesystem implementation.
     *
     * @return void
     */
    protected function registerNativeFilesystem()
    {
        $this->app->singleton('files', function () { return new Filesystem; });
    }

    /**
     * Register the driver based filesystem.
     *
     * @return void
     */
    protected function registerFlysystem()
    {
        $this->registerManager();

        $this->app->singleton('filesystem.disk', function () {
            return $this->app['filesystem']->disk($this->getDefaultDriver());
        });

        $this->app->singleton('filesystem.cloud', function () {
            return $this->app['filesystem']->disk($this->getCloudDriver());
        });
    }

    /**
     * Register the filesystem manager.
     *
     * @return void
     */
    protected function registerManager()
    {
        $this->app->singleton('filesystem', function () {
            return new FilesystemManager($this->app);
        });
    }

    /**
     * Get the default file driver.
     *
     * @return string
     */
    protected function getDefaultDriver()
    {
        return $this->app['config']['filesystems.default'];
    }

    /**
     * Get the default cloud based file driver.
     *
     * @return string
     */
    protected function getCloudDriver()
    {
        return $this->app['config']['filesystems.cloud'];
    }
}

虽然这个类非常的简单。需要知道如下的情况即可:
1 app(“files”) 返回一个操作本地文件系统的实例(实际这里是做绑定),这个跟下面说的没有关系
2 app(“filesystem”) 返回一个文件系统抽象,实际是一个磁盘管理器(管理多个磁盘)
3 app(“filesystem.disk”) 返回默认驱动对应磁盘抽象(一个适配器)
4 app(“filesystem.cloud”) 返回默认的云存储磁盘抽象(一个适配器)
5 Storage 这个Facade对应的是filesystem,磁盘管理器

——————————————————————-
1 app(“files”) 对应的类Illuminate\Filesystem\Filesystem(路径是vendor\illuinate\filestem\Filesystem.php),这个类没有继承其它类,它是独立的,它的存在主要作用是提供了和磁盘适配器一致的接口去操作任意路径的目录和文件,其方法大多是原生PHP函数的封装。

注意:有一个Facade对应这个实例,File files Illuminate\Filesystem\Filesystem

2 app(“filesystem”) 对应的类Illuminate\Filesystem\FilesystemManager(路径是vendor\illuinate\filestem\FilesystemManager.php):

// 里面有一个disks数组保存了具体的磁盘实例
public function disk($name = null)
    {
        $name = $name ?: $this->getDefaultDriver();
        return $this->disks[$name] = $this->get($name);
    }

具体怎么产生磁盘实例的,可以跟踪get()方法,大体流程是通过名称去获取配置,根据配置实例化一个Flysystem适配器,把这个适配器传递到Flysystem,这样就产生一个Flysystem实例,这个实例会被一个Illuminate\Filesystem\FilesystemAdapter封装,这里的这个封装感觉应该是没有必要的,因为Flysystem实例本身已经是一个抽象了,也提供了一致的API,很遗憾,Laravel的设计者大体是觉得它的API不够诗意吧。OK,Illuminate\Filesystem\FilesystemAdapter是具体的磁盘的抽象,对磁盘,都提供了一致的API。

还有一点,Illuminate\Filesystem\FilesystemManager是管理磁盘,它有默认的磁盘(配置中的default),而Illuminate\Filesystem\FilesystemAdapter中的统一API可以通过这个对象调用(它去调用默认的磁盘的方法):

// $this->disk()没有指定驱动,就是取默认驱动,然后调用默认驱动的方法
    public function __call($method, $parameters)
    {
        return call_user_func_array([$this->disk(), $method], $parameters);
    }

这样的搞法确实很方便,但是总感觉绕一圈的感觉。而且IDE对这种搞法,也无法识别。要正确使用,还是需要很熟悉它们之间的关系才行。

3 和 4 是具体的Illuminate\Filesystem\FilesystemAdapter实例。

最后,看看磁盘适配器:

    public function __construct(FilesystemInterface $driver)
    {
        $this->driver = $driver;
    }

注意,这里的driver是一个Flysystem抽象(重用了这个组件),里面提供的get() put() copy() move() mimeType() makeDirectory() deleteDirectory等方法,实际是调用Flysystem对应的方法,你甚至还可以直接调用Flysystem的方法,它又是通过__call()魔术方法实现的,有点无语的感觉。

Flysystem本身是一个强大的组件。Laravel没有重新发明轮子。而是改装了一下,使这个轮子更好使用而已。(实际本来的轮子已经足够好用)。

还有一个稍微需要注意的是,本地disk驱动(适配器)提供的操作方法 和 Illuminate\Filesystem\Filesystem的实例app(“files”)是一致的, 对本地来说,app(“files”)针对任意目录,而本地disk驱动(适配器)是基于一个预先配置的路径的,默认是’root’ => storage_path(‘app’), 就是storge里面的app目录(全局提供了一个storage_path()函数,如果没有参数就返回一个storage目录路径,否则就是返回相对storage目录的路径)。

至于这里提到的云存储,API是一致的,不过还需要探讨多一些。比如s3云存储,Flysystem本身也提供了适配器,它依赖AWS的SDK,所以,这个调用栈可能就有点深了。AWS提供的SDK,底层服务使用GuzzleHttp来通信,所以理论上应该提供了针对它的相关的控制选项。 SDK中的Aws\S3\S3Client继承自Aws\AwsClient,查看其构造函数说明:

__construct ( array $args ) 

http: (array, default=array(0)) Set to an array of SDK request options to apply to each request (e.g., proxy, verify, etc.).

针对Http控制的仅仅有这一样的说明。也没有点出默认是使用GuzzleHttp。实际上,所有针对GuzzleHttp可用的配置参数,这里都可以传递进来:

'http' => [
                'connect_timeout' => 20,
                'timeout' => 60,
                'verify' => false
            ],

比如这里的超时控制,GuzzleHttp默认是0,意思就不超时,这个在后台进程中,经常因为这个默认值,导致进程僵死。又比如,如果要控制其走代理:

// 控制S3是否使用代理
$http_proxy = env('S3_PROXY', false);
if(!empty($http_proxy)) {
    $proxy = env($http_proxy, false);
    if(!empty($proxy)) {
        $config['disks']['s3']['http']['proxy'] = [
            'http'  => 'tcp://'.$proxy,
            'https' => 'tcp://'.$proxy
        ];
    }
}

由于中美网络差异,使用香港作为代理跳板,是很常见的。