PHP 文件系统函数

在本文中,我们介绍了PHP中的文件系统函数。我们使用文件和目录,确定文件权限和可用磁盘空间,以及读取和写入文件。对于我们的示例,我们使用PHPCLI。PHP教程是ZetCode上PHP语言的简明教程。

$ php -v
php -v
PHP 8.1.2 (cli) (built: Aug  8 2022 07:28:23) (NTS)
...

我们使用PHP版本8.1.2。

PHP有一组丰富的函数来处理文件和目录。PHP包含低级和高级文件系统函数。fopen函数是低级函数的一个例子;它是类似C函数的薄包装。file函数是高级PHP文件系统函数的示例。

PHP文件大小和类型

filesize函数返回给定文件的大小。大小以字节为单位指定。

<?php

$filename = "fruits.txt";

$fsize = filesize($filename);

echo "The file size is: $fsize bytes\n";

在示例中,我们确定了fruits.txt文件的大小。该文件位于当前工作目录中,即与PHP脚本位于同一目录中。

$ php get_filesize.php
The file size is: 40 bytes

fruits.txt文件的大小为40字节。

filetype函数获取一个文件的类型,可能的返回值有:fifochardirblocklinkfilesocketunknown。p>

<?php

echo filetype("myfile") . PHP_EOL;
echo filetype(".") . PHP_EOL;
echo filetype("/dev/tty1") . PHP_EOL;
echo filetype("/var/run/cups/cups.sock") . PHP_EOL;

脚本决定了四个文件的类型。

$ php file_types.php
file
dir
char
socket

这四个文件分别是常规文件、目录、字符设备和套接字。

PHP检查文件是否存在

我们可能会想使用一个不存在的文件。file_exists函数可用于防止这种情况。

<?php

if ($argc != 2) {

    exit("Usage: file_existence.php filename\n");
}

$filename = $argv[1];

$r = file_exists($filename);

if (!$r) {

    exit("Cannot determine the size of the file; the file does not exist\n");
}

$fsize = filesize($filename);

echo "The file size is: $fsize bytes\n";

此脚本在计算文件大小之前检查给定文件是否存在。

if ($argc != 2) {

    exit("Usage: file_existence.php filename\n");
}

$argc是一个特殊变量,它包含传递给脚本的参数数量。我们需要两个参数:一个脚本名称和另一个作为参数传递的文件名。

$filename = $argv[1];

$argv是传递给脚本的参数数组。我们得到第二个元素。

$r = file_exists($filename);

if (!$r) {

    exit("Cannot determine the size of the file; the file does not exist\n");
}

我们使用file_exists函数检查文件是否存在。如果不存在,我们将终止脚本并显示一条消息。

$ php file_existence.php fruits.txt
The file size is: 40 bytes

这是file_existence.php脚本的示例输出。

在下面的示例中,我们创建一个新文件,将其删除,并检查其是否存在。touch函数设置文件的访问和修改时间。如果文件不存在,则创建它。unlink函数删除一个文件。

<?php

$filename = "newfile.txt";

if (file_exists($filename)) {

    echo "The file $filename exists\n";

} else {

    echo "The file $filename does not exist\n";

    $r = touch($filename);

    if (!$r) {

        exit("Failed to touch $filename file\n");
    } else {

        echo "The file $filename has been created\n";
    }
}


$r = unlink($filename);

if ($r) {

    echo "The file $filename was deleted\n";
} else {

    exit("Failed to delete $filename file\n");
}

if (file_exists($filename)) {

    echo "The file $filename exists\n";
} else {

    echo "The file $filename does not exist\n";
}

在代码示例中,我们使用了所有三个函数:file_existstouchunlink

$r = touch($filename);

touch函数用于创建一个名为newfile.txt的新文件。

if (!$r) {

    exit("Failed to touch $filename file\n");
} else {

    echo "The file $filename has been created\n";
}

如果touch功能失败,将打印一条错误消息。许多PHP函数在失败时返回一个false值。

$r = unlink($filename);

unlink函数删除文件。

$ php file_existence2.php
The file newfile.txt does not exist
The file newfile.txt has been created
The file newfile.txt was deleted
The file newfile.txt does not exist

这是file_existence2.php的输出。

PHP复制和重命名文件

copy函数复制一个文件,rename函数重命名一个文件。如果目标文件已经存在,它将被覆盖。

<?php

$r = copy("myfile.txt", "myfile2.txt");

if ($r) {

    echo "Successfully copied file\n";
} else {

    exit("Failed to copy file\n");
}

脚本复制一个文件。

<?php

$r = rename("myfile2.txt", "myfile_back.txt");

if ($r) {

    echo "Successfully renamed file\n";
} else {

    exit("Failed to rename file\n");
}

在此脚本中,我们使用rename函数将myfile2.txt文件重命名为myfile_back.txt

E_WARNING

某些文件系统函数在失败时发出E_WARNING。这是一个运行时警告(非致命错误)。脚本的执行不会停止。

PHP在这方面并不一致;并非所有文件系统函数都会发出此警告——大多数函数仅在失败时返回错误值。

<?php

set_error_handler("mywarning_handler", E_WARNING);

$r = unlink('image1.png');

if ($r) {

    echo "File successfully deleted\n";
}

function mywarning_handler($errno, $errstr) {

    echo "Failed to delete file\n";
    echo "$errstr: $errno\n";
}

在脚本中,我们删除了一个文件并提供了一个自定义错误处理程序。

set_error_handler("mywarning_handler", E_WARNING);

使用set_error_handler函数设置自定义错误处理程序。

function mywarning_handler($errno, $errstr) {

    echo "Failed to delete file\n";
    echo "$errstr: $errno\n";
}

处理程序接收错误编号和错误字符串作为参数。

$ php custom_error_handler.php
Failed to delete file
unlink(image1.png): No such file or directory: 2

custom_error_handler.php在没有要删除的image1.png时给出此输出。

PHP目录

dirname函数返回父目录的路径。从PHP7开始,我们可以提供一个可选的levels参数,它告诉要上升的父目录的数量。

<?php

$home_dir = getenv("HOME");

echo dirname($home_dir). PHP_EOL;
echo dirname("/etc/") . PHP_EOL;
echo dirname(".") . PHP_EOL;
echo dirname("/usr/local/lib", 2) . PHP_EOL;

在脚本中,我们打印了四个目录的父目录。

$home_dir = getenv("HOME");

我们使用getenv函数获取当前用户的主目录。

echo dirname($home_dir). PHP_EOL;

此行打印用户主目录的父目录。

echo dirname(".") . PHP_EOL;

在这里,我们打印当前工作目录的父目录。

echo dirname("/usr/local/lib", 2) . PHP_EOL;

在这一行中,我们打印了/usr/local/lib目录的第二个父目录。

$ php parent_directories.php
/home
/
.
/usr

这是parent_directories.php的输出。

getcwd函数返回当前工作目录,chdir函数将当前工作目录更改为新目录。

<?php

$cd = getcwd();

echo "Current directory:" . $cd . PHP_EOL;

chdir("..");

$cd2 = getcwd();

echo "Current directory:" . $cd2 . PHP_EOL;

该脚本与getcmdchdir函数一起使用。

$ php current_directory.php
Current directory:/home/janbodnar/prog/phpfiles
Current directory:/home/janbodnar/prog

这是脚本的示例输出。

PHP列表目录

在下面的五个例子中,我们列出了目录的内容。有几种方法可以完成这项任务。

<?php

$folder = '/home/janbodnar/prog';
$fh = opendir($folder);

if ($fh === false) {

    exit("Cannot open directory\n");
}

while (false !== ($entry = readdir($fh))) {
    echo "$entry\n";
}

closedir($fh);

opendir函数打开一个目录句柄。readdir函数从目录句柄中读取一个条目。目录句柄在脚本末尾用closedir函数关闭。

is_dir函数判断文件名是否为目录,is_file函数判断文件名是否为文件。

<?php

$folder = '/home/janbodnar/prog/';

$fh = opendir($folder);

if ($fh === false) {

    exit("Cannot open directory\n");
}

$dirs = [];
$files = [];

while (false !== ($entry = readdir($fh))) {

    if (is_dir($folder . '/' . $entry)) {

        array_push($dirs, $entry);
    }

    if (is_file($folder . '/' . $entry)) {

        array_push($files, $entry);
    }
}

echo "Directories:\n";

foreach ($dirs as $dr) {
    echo "$dr\n";
}

echo "Files:\n";

foreach ($files as $myfile) {
    echo "$myfile\n";
}

closedir($fh);

在第二个示例中,我们将条目分为子目录和文件。脚本首先打印子目录,然后打印检查目录的文件。

if (is_dir($folder . '/' . $entry)) {

    array_push($dirs, $entry);
}

必须将目录的完整路径提供给is_dir函数。

glob函数查找与模式匹配的路径名。

<?php

foreach (glob('/home/janbodnar/*', GLOB_ONLYDIR) as $dir) {

    echo "$dir\n";
}

使用GLOB_ONLYDIR标志,glob函数仅返回与模式匹配的目录条目。

scandir是一个高级函数,用于列出指定路径中的文件和目录。该函数返回目录中的文件和目录数组。

<?php

$files = scandir('.', SCANDIR_SORT_DESCENDING);

print_r($files);

该脚本打印当前工作目录的文件和子目录数组。SCANDIR_SORT_DESCENDING标志按字母降序对条目进行排序。

在前面的例子中,我们只列出了一个目录的内容;我们没有包括子目录的元素。使用RecursiveDirectoryIteratorRecursiveIteratorIterator类我们可以使用递归轻松迭代文件系统目录。换句话说,我们遍历所有子目录,直到列出目录树中的所有项目。

<?php

$folder = '/home/janbodnar/prog/';

$rdi = new RecursiveDirectoryIterator($folder);
$rii = new RecursiveIteratorIterator($rdi);

foreach ($rii as $filename) {

    echo "$filename\n";
}

该脚本打印给定目录的所有深度级别的所有项目。

PHP路径

路径是计算机文件的完整指定名称,包括文件在文件系统目录结构中的位置。realpath函数返回规范的绝对路径名,basename函数返回路径的尾随名称部分。

<?php

echo realpath("myfile.txt") . PHP_EOL;

echo basename("/home/janbodnar/prog/phpfiles/myfile.txt") . PHP_EOL;
echo basename("/home/janbodnar/prog/phpfiles/myfile.txt", ".txt") . PHP_EOL;

此脚本使用realpathbasename函数。

echo basename("/home/janbodnar/prog/phpfiles/myfile.txt", ".txt") . PHP_EOL;

如果我们指定第二个参数,后缀名,它也会从路径名中移除。

$ php paths.php
/home/janbodnar/prog/phpfiles/myfile.txt
myfile.txt
myfile

这是paths.php示例的输出。

pathinfo函数返回有关文件路径的信息。

<?php

$path_parts = pathinfo('myfile.txt');

echo $path_parts['dirname'] . PHP_EOL;
echo $path_parts['basename'] . PHP_EOL;
echo $path_parts['extension'] . PHP_EOL;
echo $path_parts['filename'] . PHP_EOL;

该函数返回一个包含以下元素的关联数组:目录名、基名、扩展名(如果有)和文件名。

$ php path_info.php
.
myfile.txt
txt
myfile

这是输出。

PHP创建文件

fopen函数打开文件或URL。函数的第一个参数是文件名,第二个参数是我们打开窗口的模式。例如,'r'模式打开只读,'w'只写。如果我们以'w'模式打开一个文件并且它不存在,则会创建它。模式列表可以在fopen()的PHP手册中找到。

fopen返回文件句柄。这是一个用于操作文件的对象;例如,我们将它传递给fwrite函数以写入文件。

<?php

$filename = "names.txt";

if (file_exists($filename)) {

    exit("The file already exists\n");
}

$fh = fopen($filename, 'w');

if ($fh === false) {

    exit("Cannot create file\n");
}

echo "Successfully created file\n";

fclose($fh);

该示例创建了一个名为names.txt的新文件。

if (file_exists($filename)) {

    exit("The file already exists\n");
}

首先,我们检查文件是否存在。

$fh = fopen('names.txt', 'w');

创建了names.txt文件并返回了该文件的句柄。

fclose($fh);

我们使用fclose函数关闭文件句柄。

PHP读取文件

在接下来的例子中,我们将读取文件内容。

fread从handle引用的文件指针中读取最多length个字节。一旦读取了length字节或到达EOF(文件末尾),读取就会停止。

<?php

$fh = fopen('balzac.txt', 'r');

if ($fh === false) {

    exit("Cannot open file for reading\n");
}

while (!feof($fh)) {

    $chunk = fread($fh, 1024);

    if ($chunk === false) {

        exit("Cannot read from file\n");
    }

    echo $chunk;
}

fclose($fh);

该示例使用fread函数读取整个文件并将其输出到控制台。

while (!feof($fh)) {

    $chunk = fread($fh, 1024);

    if ($chunk === false) {

        exit("Cannot read from file\n");
    }

    echo $chunk;
}

feof测试文件指针上的文件结尾。fread每1KB块读取文件,直到到达EOF。

$ php read_file.php balzac.txt
Honoré de Balzac, (born Honoré Balzac, 20 May 1799 – 18 August 1850)
was a French novelist and playwright. His magnum opus was a sequence
of short stories and novels collectively entitled La Comédie Humaine,
which presents a panorama of French life in the years after the 1815
Fall of Napoleon Bonaparte.

这是read_file.php示例的输出。

在第二个例子中,我们使用了fgets函数,它从文件句柄中读取一行。

<?php

$fh = fopen('balzac.txt', 'r');

if ($fh === false) {

    exit("Cannot open file for reading\n");
}

while (!feof($fh)) {

    $line = fgets($fh);

    if ($line === false) {

        exit("Cannot read from file\n");
    }

    echo $line;
}

fclose($fh);

该示例逐行读取balzac.txt文件的内容。

file是一个高级函数,它将整个文件读取到一个数组中。

<?php

$lines = file('balzac.txt');

if ($lines === false) {

    exit("Cannot read file\n");
}

foreach ($lines as $line) {

    echo $line;
}

在这个例子中,我们使用file函数一次读取整个文件。我们使用foreach循环遍历返回的数组。

file_get_contents是另一个高级函数,它将整个文件读入一个字符串。

<?php

$content = file_get_contents('balzac.txt');

if ($content === false) {

    exit("Cannot read file\n");
}

echo "$content";

该示例使用file_get_contents函数一次读取整个文件。它以字符串形式返回数据。

PHP读取格式化数据

fscanf函数根据格式解析来自文件的输入。每次调用fscanf都会从文件中读取一行。

$ cat items.txt
coins 5
pens 6
chairs 12
books 20

我们要解析items.txt文件。

<?php

$fh = fopen("items.txt", "r");

if ($fh === false) {

    exit("Cannot read file\n");
}

while ($data = fscanf($fh, "%s %d")) {

    list($item, $quantity) = $data;
    echo "$item: $quantity\n";
}

fclose($fh);

fscanf函数采用格式说明符来读取字符串和数字。

PHP阅读网页

PHP文件系统函数也可用于读取网页。

<?php

$ph = fopen("http://webcode.me", "r");

if ($ph === false) {

    exit("Failed to open stream to URL\n");
}

while (!feof($ph)) {

    $buf = fread($ph, 1024);

    if ($buf === false) {

        exit("Cannot read page\n");
    }

    echo $buf;
}

fclose($ph);

我们从一个小型网站wecode.me读取了一个页面。

$ph = fopen("http://webcode.me", "r");

使用fopen函数,我们打开网页的句柄。

while (!feof($ph)) {

    $buf = fread($ph, 1024);

    if ($buf === false) {

        exit("Cannot read page\n");
    }

    echo $buf;
}

我们阅读网页直到结束;feof用于测试网页的结尾。使用fread函数以1KB的块读取页面。

$ php read_page.php
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>My html page</title>
</head>
<body>

    <p>
        Today is a beautiful day. We go swimming and fishing.
    </p>

    <p>
         Hello there. How are you?
    </p>

</body>
</html>

这是read_page.php脚本的输出。

以下示例使用高级函数读取同一网页。

<?php

$page = file_get_contents('http://webcode.me');

if ($page === false) {

    exit("Cannot read page\n");
}

echo $page;

file_get_contents一次读取整个网页。

fgetss函数从文件句柄中读取一行并去除HTML标记。

<?php

$ph = fopen("http://webcode.me", "r");

if ($ph === false) {

    exit("Failed to open stream to URL\n");
}

while (!feof($ph)) {

    $buf = fgetss($ph, 1024);

    if ($buf === false) {

        exit("Cannot read from page\n");
    }

    echo trim($buf);
}

fclose($ph);

我们读取webcode.me网页的内容并剥离HTML标签。

echo trim($buf);

trim函数删除前导和尾随空格。

$ php read_page3.php
My html pageToday is a beautiful day. We go swimming and fishing.Hello there. How are you?

这是示例的输出;输出包含页面正文中的标题和文本。

PHP写入文件

fwrite函数将字符串写入由文件句柄引用的文件。

<?php

$fh = fopen('names.txt', 'w');

if ($fh === false) {

    exit("Cannot open file\n");
}

$r = fwrite($fh, 'Jane' . PHP_EOL);
check_retval($r);

$r = fwrite($fh, 'Lucy' . PHP_EOL);
check_retval($r);

$r = fwrite($fh, 'Mark' . PHP_EOL);
check_retval($r);

$r = fwrite($fh, 'Lubos' . PHP_EOL);
check_retval($r);

fclose($fh);

function check_retval($val) {

    if ($val === false) {

        exit("Cannot write to file\n");
    }
}

我们以写入模式打开一个names.txt,并向其中写入四行。

$fh = fopen('names.txt', 'w');

fopen函数以写入模式打开文件。如果该文件不存在,则会自动创建。

$r = fwrite($fh, 'Jane' . PHP_EOL);

使用fwrite函数,我们向文件写入一行。该函数将文件句柄作为其第一个参数。

$ php write_file.php
$ cat names.txt
Jane
Lucy
Mark
Lubos

我们写入names.txt文件并检查其内容。

我们可以使用高级file_put_contents方法一次性将字符串写入文件。

<?php

$filename = "names.txt";

$buf = file_get_contents($filename);

if ($buf === false) {

    exit("Cannot get file contents\n");
}

$buf .= "John\nPaul\nRobert\n";

$r = file_put_contents($filename, $buf);

if ($r === false) {

    exit("Cannot write to file\n");
}

在示例中,我们使用file_get_contents函数读取names.txt文件的内容,并使用file_put_contents函数附加新字符串。

PHP可读、可写、可执行文件

is_readableis_writableis_executable函数检查文件是否可读、可写和可执行。

<?php

$filename = "myfile.txt";

echo get_current_user() . PHP_EOL;

if (is_readable($filename)) {

    echo "The file can be read\n";
} else {

    echo "Cannot read file\n";
}

if (is_writable($filename)) {

    echo "The file can be written to\n";
} else {

    echo "Cannot write to file\n";
}

if (is_executable($filename)) {

    echo "The file can be executed\n";
} else {

    echo "Cannot execute file\n";
}

我们在myfile.txt文件上运行这三个函数。脚本检查当前用户的这些属性。

$ php rwe.php
janbodnar
The file can be read
The file can be written to
Cannot execute file

janbodnar用户可以读取和写入文件,但不能执行该文件。

PHP文件时间

Linux上有三种文件时间:上次访问时间、上次更改时间和上次修改时间。以下PHP函数确定这些时间:fileatimefilectimefilemtime

<?php

$filename = "myfile.txt";

$atime =  fileatime($filename);
$ctime =  filectime($filename);
$mtime =  filemtime($filename);

echo date("F d, Y H:i:s\n", $atime);
echo date("F d, Y H:i:s\n", $ctime);
echo date("F d, Y H:i:s\n", $mtime);

脚本打印myfile.txt文件的文件时间。

$ php file_times.php
April 20, 2016 17:52:54
April 20, 2016 17:53:33
April 20, 2016 17:52:29

这是file_times.php脚本的示例输出。

PHP文件权限

文件系统为不同的用户和用户组强制执行文件权限。fileperms函数获取文件权限;它以数字模式返回文件的权限。

<?php

$perms = fileperms("myfile.txt");

echo decoct($perms & 0777) . PHP_EOL;

$info .= (($perms & 0x0100) ? 'r' : '-');
$info .= (($perms & 0x0080) ? 'w' : '-');
$info .= (($perms & 0x0040) ?
            (($perms & 0x0800) ? 's' : 'x' ) :
            (($perms & 0x0800) ? 'S' : '-'));

$info .= (($perms & 0x0020) ? 'r' : '-');
$info .= (($perms & 0x0010) ? 'w' : '-');
$info .= (($perms & 0x0008) ?
            (($perms & 0x0400) ? 's' : 'x' ) :
            (($perms & 0x0400) ? 'S' : '-'));

$info .= (($perms & 0x0004) ? 'r' : '-');
$info .= (($perms & 0x0002) ? 'w' : '-');
$info .= (($perms & 0x0001) ?
            (($perms & 0x0200) ? 't' : 'x' ) :
            (($perms & 0x0200) ? 'T' : '-'));

echo "$info\n";

脚本决定myfile.txt的文件权限。权限以Unix风格打印到控制台。

echo decoct($perms & 0777) . PHP_EOL;

传统上,Unix上的权限以八进制表示形式编写。decot函数将十进制表示形式转换为八进制形式。

$info .= (($perms & 0x0100) ? 'r' : '-');

在这一行中,我们检查文件权限是否允许文件所有者读取它。

$ php file_permissions.php
660
rw-rw----

这是file_permissions.php脚本的示例输出。

可以使用chmod函数更改文件权限。

<?php

$perms1 = fileperms("myfile.txt");

echo decoct($perms1 & 0777) . PHP_EOL;

$r = chmod("myfile", 0660);

if ($r) {

    echo "File mode successfully changed\n";
} else {

    exit("Failed to change file mode\n");
}

$perms2 = fileperms("myfile");

echo decoct($perms2 & 0777) . PHP_EOL;


该脚本更改了myfile.txt文件的权限。

$r = chmod("myfile", 0660);

chmod函数在其第二个参数中接受权限作为八进制值。八进制值前面有0。

$ php file_permissions2.php
664
File mode successfully changed
660

文件的权限由664改为660。

PHPCSV文件格式

fgetcsv函数从CSV(逗号分隔值)文件中读取一行;它返回一个包含读取字段的索引数组。fputcsv函数将一行格式化为CSV并将其写入文件。

<?php

$nums = [1, 2, 5, 3, 2, 6, 4, 2, 4,
    8, 7, 3, 8, 5, 4, 3];

$fh = fopen('numbers.csv', 'w');

if ($fh === false) {

    exit("Failed to open file\n");
}

$r = fputcsv($fh, $nums);

if ($r === false) {

    exit("Failed to write values\n");
}

echo "The values have been successfully written\n";

fclose($fh);

此脚本将数组中的数字写入CSV文件。

$ php csv_output.php
The values have been successfully written
$ cat numbers.csv
1,2,5,3,2,6,4,2,4,8,7,3,8,5,4,3

我们运行脚本并检查文件内容。

在下面的示例中,我们从CSV文件中读取数据。

<?php

$fh = fopen('numbers.csv', 'r');

if ($fh === false) {

    exit("Failed to open file\n");
}

while (($data = fgetcsv($fh)) !== false) {

    $num = count($data);

    for ($i=0; $i < $num; $i++) {

        echo "$data[$i] ";
    }
}

echo "\n";

fclose($fh);

该脚本使用fgetcsvnumbers.csv文件中读取值并将它们打印到控制台。

$ php csv_input.php
1 2 5 3 2 6 4 2 4 8 7 3 8 5 4 3

这是csv_input.php脚本的输出。

PHP磁盘空间

disk_total_space函数以字节为单位返回文件系统或磁盘分区的总大小,disk_total_space函数以字节为单位返回文件系统或磁盘分区上的可用空间。

<?php

const BYTES_IN_GIGABYTE = 1073741824;

$total_space_bytes = disk_total_space("/");

if ($total_space_bytes === false) {

    exit("The disk_total_space() failed\n");
}

$free_space_bytes = disk_free_space("/");

if ($free_space_bytes === false) {

    exit("The disk_free_space() failed\n");
}

$total_space_gb = floor($total_space_bytes / BYTES_IN_GIGABYTE);
$free_space_gb = floor($free_space_bytes / BYTES_IN_GIGABYTE);

echo "Total space: $total_space_gb GB\n";
echo "Free space: $free_space_gb GB\n";

该脚本计算根分区上的总空间和可用空间。空间转换为千兆字节。

$ php disk_space.php
Total space: 289 GB
Free space: 50 GB

这是脚本的示例输出。

在本教程中,我们介绍了PHP文件系统函数。

列出所有PHP教程。

赞(0) 打赏

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏