上传附件管理php,实现文件上传管理功能

实现文件上传管理功能

由 学院君 创建于5年前, 最后更新于 5年前

版本号 #1

68437 views

35 likes

0 collects

本节我们将在后台为博客应用实现文件上传管理(包括文件上传、预览及删除、目录创建及删除)功能,并且使用本地文件系统保存上传的文件。

1、配置本地文件系统

让我们从配置开始吧,我们在 public 目录下创建一个 uploads 目录用来存放上传的文件,这样所有上传文件都可以通过浏览器直接访问。

首先我们在博客项目目录下使用如下命令在 public 目录下创建 uploads 子目录:

3bc463415d4eacafebe9c16811d54d08.png

很简单。接下来我们来编辑 config/blog.php:

return [

'title' => 'My Blog',

'posts_per_page' => 5,

'uploads' => [

'storage' => 'local',

'webpath' => '/uploads',

],

];

我们在 uploads 配置项中使用 storage 定义使用的文件系统,使用 webpath 定义 web 访问根目录。

最后,编辑 config/filesystems.php 如下:

//将如下区块代码

'disks' => [

'local' => [

'driver' => 'local',

'root' => storage_path('app'),

],

// 修改成这样

'disks' => [

'local' => [

'driver' => 'local',

'root' => public_path('uploads'),

],

我们将本地存储的根目录修改为前面创建的 public/uploads 目录。

2、创建帮助函数文件

在 Laravel 5.1 项目中有时我们会需要一些不依赖于类的辅助函数,通常我们会将这些辅助函数定义在一个单独文件如  helpers.php 中。我们在 app 目录下创建这个名为 helpers.php 的文件,并编辑其内容如下:

/**

* 返回可读性更好的文件尺寸

*/

function human_filesize($bytes, $decimals = 2)

{

$size = ['B', 'kB', 'MB', 'GB', 'TB', 'PB'];

$factor = floor((strlen($bytes) - 1) / 3);

return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) .@$size[$factor];

}

/**

* 判断文件的MIME类型是否为图片

*/

function is_image($mimeType)

{

return starts_with($mimeType, 'image/');

}

其中 human_filesize() 函数返回一个易读的文件尺寸,is_image() 函数在文件类型为图片的时候返回 true。

要让应用能够正确找到 helpers.php 文件,还要修改项目根目录下的 composer.json:

{

...

"autoload": {

"classmap": [

"database"

],

"psr-4": {

"App\\": "app/"

},

"files": [

"app/helpers.php"

]

},

...

}

在 autoload 配置项的 files 数组中指定要被加载的文件/文件夹。修改完成后记得运行 composer dumpauto 确保修改生效:

1abdac38052b544baa3f2bd8242c2f79.png

现在 helpers.php 中的所有函数都会载入到自动加载器中,你可以在博客应用的代码中任意使用其中的函数。

3、创建文件上传管理服务

现在基本配置已经完成了,让我们创建一个服务类来管理上传文件。

检测文件 MIME 类型

我们想要基于不同类型的上传文件进行不同的操作,这可以通过检测上传文件 MIME 类型轻松实现。

PHP 有一个内置函数 mime_content_type() 用于检测文件的MIME类型,但是该函数已经废弃了,我们使用另一个解决方案。

在 Packagist 中搜索 “mime” 会查询到一个名为 dflydev 的包,我们在博客项目中使用 Composer 安装该依赖包:

composer require "dflydev/apache-mime-types"

我们将使用该依赖包提供的方法来检测文件的 MIME 类型。

创建UploadsManager类

在 app/Services 目录下创建 UploadsManager.php,编辑其内容如下:

namespace App\Services;

use Carbon\Carbon;

use Dflydev\ApacheMimeTypes\PhpRepository;

use Illuminate\Support\Facades\Storage;

class UploadsManager

{

protected $disk;

protected $mimeDetect;

public function __construct(PhpRepository $mimeDetect)

{

$this->disk = Storage::disk(config('blog.uploads.storage'));

$this->mimeDetect = $mimeDetect;

}

/**

* Return files and directories within a folder

*

* @param string $folder

* @return array of [

* 'folder' => 'path to current folder',

* 'folderName' => 'name of just current folder',

* 'breadCrumbs' => breadcrumb array of [ $path => $foldername ]

* 'folders' => array of [ $path => $foldername] of each subfolder

* 'files' => array of file details on each file in folder

* ]

*/

public function folderInfo($folder)

{

$folder = $this->cleanFolder($folder);

$breadcrumbs = $this->breadcrumbs($folder);

$slice = array_slice($breadcrumbs, -1);

$folderName = current($slice);

$breadcrumbs = array_slice($breadcrumbs, 0, -1);

$subfolders = [];

foreach (array_unique($this->disk->directories($folder)) as $subfolder) {

$subfolders["/$subfolder"] = basename($subfolder);

}

$files = [];

foreach ($this->disk->files($folder) as $path) {

$files[] = $this->fileDetails($path);

}

return compact(

'folder',

'folderName',

'breadcrumbs',

'subfolders',

'files'

);

}

/**

* Sanitize the folder name

*/

protected function cleanFolder($folder)

{

return '/' . trim(str_replace('..', '', $folder), '/');

}

/**

* 返回当前目录路径

*/

protected function breadcrumbs($folder)

{

$folder = trim($folder, '/');

$crumbs = ['/' => 'root'];

if (empty($folder)) {

return $crumbs;

}

$folders = explode('/', $folder);

$build = '';

foreach ($folders as $folder) {

$build .= '/'.$folder;

$crumbs[$build] = $folder;

}

return $crumbs;

}

/**

* 返回文件详细信息数组

*/

protected function fileDetails($path)

{

$path = '/' . ltrim($path, '/');

return [

'name' => basename($path),

'fullPath' => $path,

'webPath' => $this->fileWebpath($path),

'mimeType' => $this->fileMimeType($path),

'size' => $this->fileSize($path),

'modified' => $this->fileModified($path),

];

}

/**

* 返回文件完整的web路径

*/

public function fileWebpath($path)

{

$path = rtrim(config('blog.uploads.webpath'), '/') . '/' .ltrim($path, '/');

return url($path);

}

/**

* 返回文件MIME类型

*/

public function fileMimeType($path)

{

return $this->mimeDetect->findType(

pathinfo($path, PATHINFO_EXTENSION)

);

}

/**

* 返回文件大小

*/

public function fileSize($path)

{

return $this->disk->size($path);

}

/**

* 返回最后修改时间

*/

public function fileModified($path)

{

return Carbon::createFromTimestamp(

$this->disk->lastModified($path)

);

}

}

4、实现文件上传管理列表

现在 UploadsManager 服务类已经创建,接下来我们来实现控制器的 index 方法。

创建 index 方法

编辑 app/Http/Controllers/Admin 目录下的UploadController.php 文件内容如下:

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;

use App\Services\UploadsManager;

use Illuminate\Http\Request;

class UploadController extends Controller

{

protected $manager;

public function __construct(UploadsManager $manager)

{

$this->manager = $manager;

}

/**

* Show page of files / subfolders

*/

public function index(Request $request)

{

$folder = $request->get('folder');

$data = $this->manager->folderInfo($folder);

return view('admin.upload.index', $data);

}

}

构造方法中注入了 UploadsManager 依赖,在 index()方法中只需传入 folderInfo() 返回的数据到要渲染的视图并返回即可。

你可能已经注意到 $folder 从请求中获取,是的,我们只需要通过请求参数即可实现文件夹修改。

创建 index 视图

在 resources/views/admin 目录下新建 upload 目录,并在该目录下创建 index.blade.php 文件,编辑该文件内容如下:

@extends('admin.layout')

@section('content')

{{-- 顶部工具栏 --}}

Uploads

@foreach ($breadcrumbs as $path => $disp)

{{ $disp }}

@endforeach

{{ $folderName }}

New Folder

Upload

@include('admin.partials.errors')

@include('admin.partials.success')

NameTypeDateSizeActions

{{-- 子目录 --}}

@foreach ($subfolders as $path => $name)

{{ $name }}

Folder--

Delete

@endforeach

{{-- 所有文件 --}}

@foreach ($files as $file)

@if (is_image($file['mimeType']))

@else

@endif

{{ $file['name'] }}

{{ $file['mimeType'] or 'Unknown' }}{{ $file['modified']->format('j-M-y g:ia') }}{{ human_filesize($file['size']) }}

Delete

@if (is_image($file['mimeType']))

Preview

@endif

@endforeach

@include('admin.upload._modals')

@stop

@section('scripts')

// 确认文件删除

function delete_file(name) {

$("#delete-file-name1").html(name);

$("#delete-file-name2").val(name);

$("#modal-file-delete").modal("show");

}

// 确认目录删除

function delete_folder(name) {

$("#delete-folder-name1").html(name);

$("#delete-folder-name2").val(name);

$("#modal-folder-delete").modal("show");

}

// 预览图片

function preview_image(path) {

$("#preview-image").attr("src", path);

$("#modal-image-view").modal("show");

}

// 初始化数据

$(function() {

$("#uploads-table").DataTable();

});

@stop

尽管这个模板文件很长,但是理解起来并没有什么困难,所有文件上传和下载管理都将在这里进行。

有没有注意到我们在最后包含了 admin.upload._modals?是的,我们将模态对话框放到了一个单独的视图模板中。现在,我们在  resources/views/admin/upload 目录下创建一个空的 _modals.blade.php 文件。

上传管理界面

打开浏览器,进入博客应用后台管理页面,点击顶部导航条的“上传”(Uploads)链接,将会跳转到如下页面:

e381f9c06c04e89ae5ab73e9c9376ae0.png

既漂亮又清爽,有木有?接下来让我们来实现所有的模态对话框及其背后的业务逻辑。

5、完成文件上传管理功能

对于完整的文件上传管理器而言剩下的工作已经不多了,现在是时候完成所有功能了。

添加路由

我们需要为上传管理器定义所有需要的路由,编辑 app/Http/routes.php 添加如下路由:

// 在这一行下面

get('admin/upload', 'UploadController@index');

// 添加如下路由

post('admin/upload/file', 'UploadController@uploadFile');

delete('admin/upload/file', 'UploadController@deleteFile');

post('admin/upload/folder', 'UploadController@createFolder');

delete('admin/upload/folder', 'UploadController@deleteFolder');

定义所有模态对话框

编辑我们之前创建的 _modals.blade.php 文件内容如下:

{{-- 创建目录 --}}

{{-- 删除文件 --}}

{{-- 删除目录 --}}

{{-- 上传文件 --}}

{{-- 浏览图片 --}}

在该文件中总共有5个不同的模态弹出框,分别对应上面定义的5个路由。

添加表单请求验证类

使用 Artisan 命令创建 UploadFileRequest,并编辑其内容如下:

namespace App\Http\Requests;

use App\Http\Requests\Request;

class UploadFileRequest extends Request

{

/**

* Determine if the user is authorized to make this request.

*

* @return bool

*/

public function authorize()

{

return true;

}

/**

* Get the validation rules that apply to the request.

*

* @return array

*/

public function rules()

{

return [

'file' => 'required',

'folder' => 'required',

];

}

}

使用 Artisan 命令创建 UploadNewFolderRequest,并编辑其内容如下:

namespace App\Http\Requests;

use App\Http\Requests\Request;

class UploadNewFolderRequest extends Request

{

/**

* Determine if the user is authorized to make this request.

*

* @return bool

*/

public function authorize()

{

return true;

}

/**

* Get the validation rules that apply to the request.

*

* @return array

*/

public function rules()

{

return [

'folder' => 'required',

'new_folder' => 'required',

];

}

}

同样,这些请求类用于验证表单输入。

完成 UploadController 所有方法

编辑 UploadController.php 文件内容如下:

// 添加如下三个use语句到UploadController控制器顶部

use App\Http\Requests\UploadFileRequest;

use App\Http\Requests\UploadNewFolderRequest;

use Illuminate\Support\Facades\File;

// 添加如下四个方法到UploadController控制器类

/**

* 创建新目录

*/

public function createFolder(UploadNewFolderRequest $request)

{

$new_folder = $request->get('new_folder');

$folder = $request->get('folder').'/'.$new_folder;

$result = $this->manager->createDirectory($folder);

if ($result === true) {

return redirect()

->back()

->withSuccess("Folder '$new_folder' created.");

}

$error = $result ? : "An error occurred creating directory.";

return redirect()

->back()

->withErrors([$error]);

}

/**

* 删除文件

*/

public function deleteFile(Request $request)

{

$del_file = $request->get('del_file');

$path = $request->get('folder').'/'.$del_file;

$result = $this->manager->deleteFile($path);

if ($result === true) {

return redirect()

->back()

->withSuccess("File '$del_file' deleted.");

}

$error = $result ? : "An error occurred deleting file.";

return redirect()

->back()

->withErrors([$error]);

}

/**

* 删除目录

*/

public function deleteFolder(Request $request)

{

$del_folder = $request->get('del_folder');

$folder = $request->get('folder').'/'.$del_folder;

$result = $this->manager->deleteDirectory($folder);

if ($result === true) {

return redirect()

->back()

->withSuccess("Folder '$del_folder' deleted.");

}

$error = $result ? : "An error occurred deleting directory.";

return redirect()

->back()

->withErrors([$error]);

}

/**

* 上传文件

*/

public function uploadFile(UploadFileRequest $request)

{

$file = $_FILES['file'];

$fileName = $request->get('file_name');

$fileName = $fileName ?: $file['name'];

$path = str_finish($request->get('folder'), '/') . $fileName;

$content = File::get($file['tmp_name']);

$result = $this->manager->saveFile($path, $content);

if ($result === true) {

return redirect()

->back()

->withSuccess("File '$fileName' uploaded.");

}

$error = $result ? : "An error occurred uploading file.";

return redirect()

->back()

->withErrors([$error]);

}

完成 UploadsManager 服务类

最后编辑 app/Services/UploadsManager.php 内容如下:

// 在该类中新增以下4个方法

/**

* 创建新目录

*/

public function createDirectory($folder)

{

$folder = $this->cleanFolder($folder);

if ($this->disk->exists($folder)) {

return "Folder '$folder' already exists.";

}

return $this->disk->makeDirectory($folder);

}

/**

* 删除目录

*/

public function deleteDirectory($folder)

{

$folder = $this->cleanFolder($folder);

$filesFolders = array_merge(

$this->disk->directories($folder),

$this->disk->files($folder)

);

if (! empty($filesFolders)) {

return "Directory must be empty to delete it.";

}

return $this->disk->deleteDirectory($folder);

}

/**

* 删除文件

*/

public function deleteFile($path)

{

$path = $this->cleanFolder($path);

if (! $this->disk->exists($path)) {

return "File does not exist.";

}

return $this->disk->delete($path);

}

/**

* 保存文件

*/

public function saveFile($path, $content)

{

$path = $this->cleanFolder($path);

if ($this->disk->exists($path)) {

return "File already exists.";

}

return $this->disk->put($path, $content);

}

至此,已经完成了文件上传管理的所有工作,可以去浏览器完成上传文件、创建目录、删除文件等操作了。

6、测试文件上传和删除功能

在浏览器中访问 http://blog.app/admin/upload,点击“New Folder”创建新目录,在弹出的模态对话框中填写表单:

9b9a75ccda903d998a0a4c95af514e86.png

点击“Create Folder”提交表单,创建目录成功:

6cf4bec11de2ecc60c1a55ef3ad3ee4d.png

点击进入新创建的子目录 laravelacademy,在该目录下点击“Upload”按钮上传文件:

d66f0c43d056b74b668eee9f3e3bbbcb.png

点击“Upload File”上传文件,上传成功后跳转到文件列表:

74c77739c230e4528b70dac3890a02c2.png

但是去 public/uploads/laravelacademy 目录下查看,上传的文件 Laravel学院 并没有扩展名,而且上面列表里 Type 类型值为 Unkown,预览按钮没显示出来也说明了有问题,正确的文件名应该包含扩展名,看来是上传图片时填写的文件名称有问题,应该这样填写为 LaravelAcademy.jpg,这样重新上传后文件列表显示如下:

dc730412e77f798b0b2dc72d92d48f96.png

这样数据都对了,预览按钮也显示出来了,点击“Preview”按钮,页面显示如下:

24c432afdede8df87b8adfbd248c3210.png

最后我们将无效的Laravel学院文件删除,点击其对应的“Delete”按钮,页面弹出确认删除对话框:

528631b6eabc784c18308b106a3a5fe7.png

点击“Delete File”,确认删除。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
下面是 PHP 实现上传附件并保持上传文件名,可上传多次同名文件的代码: ```php <?php if ($_FILES["file"]["error"] > 0) { echo "Error: " . $_FILES["file"]["error"] . "<br>"; } else { $file_name = $_FILES["file"]["name"]; $file_path = "upload/" . $file_name; $i = 1; while (file_exists($file_path)) { $file_name = "(" . $i . ")" . $_FILES["file"]["name"]; $file_path = "upload/" . $file_name; $i++; } move_uploaded_file($_FILES["file"]["tmp_name"], $file_path); echo "File uploaded successfully. File name: " . $file_name; } ?> ``` 在上面的代码中,与上一个问题的代码类似,首先判断上传是否出错,如果出错则输出错误信息。如果上传成功,则获取上传文件的原始文件名,并将文件移动到服务器上的指定目录中,使用 `move_uploaded_file()` 函数实现。但是,为了避免上传同名文件时出现覆盖的情况,需要对上传的文件进行重命名。 在重命名时,先在上传目录中搜索同名文件,如果存在,则在原文件名中添加 `(1)`、`(2)` 等后缀,直到找到一个不存在的文件名为止。最后再将文件移动到上传目录中,并输出上传成功的信息和新的文件名。 需要注意的是,这种重命名方式只适用于简单的上传场景,如果需要处理更复杂的文件重命名逻辑,建议使用专门的库或框架。此外,还需要对上传的文件进行一定的验证和过滤,以确保上传的文件是安全的。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值