作者 赵彬吉

update

<?php
namespace App\Helper;
/**
* 计算文件hash
*
* 七牛云的计算规则
*/
class FileEtag
{
const BLOCK_SIZE = 4194304; //4*1024*1024 分块上传块大小,该参数为接口规格,不能修改
private static function packArray($v, $a)
{
return call_user_func_array('pack', array_merge(array($v), (array)$a));
}
private static function blockCount($fsize)
{
return intval(($fsize + (self::BLOCK_SIZE - 1)) / self::BLOCK_SIZE);
}
private static function calcSha1($data)
{
$sha1Str = sha1($data, true);
$err = error_get_last();
if ($err !== null) {
return array(null, $err);
}
$byteArray = unpack('C*', $sha1Str);
return array($byteArray, null);
}
private static function base64_urlSafeEncode($data)
{
$find = array('+', '/');
$replace = array('-', '_');
return str_replace($find, $replace, base64_encode($data));
}
public static function sum($filename)
{
$fhandler = fopen($filename, 'r');
$err = error_get_last();
if ($err !== null) {
return array(null, $err);
}
$fstat = fstat($fhandler);
$fsize = $fstat['size'];
if ((int)$fsize === 0) {
fclose($fhandler);
return array('Fto5o-5ea0sNMlW_75VgGJCv2AcJ', null);
}
$blockCnt = self::blockCount($fsize);
$sha1Buf = array();
if ($blockCnt <= 1) {
array_push($sha1Buf, 0x16);
$fdata = fread($fhandler, self::BLOCK_SIZE);
if ($err !== null) {
fclose($fhandler);
return array(null, $err);
}
list($sha1Code,) = self::calcSha1($fdata);
$sha1Buf = array_merge($sha1Buf, $sha1Code);
} else {
array_push($sha1Buf, 0x96);
$sha1BlockBuf = array();
for ($i = 0; $i < $blockCnt; $i++) {
$fdata = fread($fhandler, self::BLOCK_SIZE);
list($sha1Code, $err) = self::calcSha1($fdata);
if ($err !== null) {
fclose($fhandler);
return array(null, $err);
}
$sha1BlockBuf = array_merge($sha1BlockBuf, $sha1Code);
}
$tmpData = self::packArray('C*', $sha1BlockBuf);
list($sha1Final,) = self::calcSha1($tmpData);
$sha1Buf = array_merge($sha1Buf, $sha1Final);
}
$etag = self::base64_urlSafeEncode(self::packArray('C*', $sha1Buf));
return array($etag, null);
}
}
... ...
<?php
namespace App\Http\Controllers\Bside;
use App\Services\Facades\Upload;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
class FileController extends BaseController
{
/**
* 上传
* @param Request $request
* @return \Illuminate\Http\JsonResponse
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
* @author zbj
* @date 2023/4/20
*/
public function upload(Request $request){
// 上传文件
$files = Upload::puts('files', $this->param['config']);
foreach ($files as &$file){
$file = Storage::disk('upload')->url($file);
}
return $this->success($files);
}
/**
* 下载
* @param Request $request
* @return \Symfony\Component\HttpFoundation\StreamedResponse
* @author zbj
* @date 2023/4/20
*/
public function download(Request $request){
$path = Upload::url2path($this->param['url']);
return Storage::disk('upload')->download($path);
}
}
... ...
... ... @@ -2,7 +2,7 @@
namespace App\Providers;
use App\Services\PaginatorServer;
use App\Services\PaginatorService;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
... ... @@ -16,7 +16,7 @@ class AppServiceProvider extends ServiceProvider
{
//自定义分页
$this->app->bind('Illuminate\Pagination\LengthAwarePaginator',function ($app,$options){
return new PaginatorServer($options['items'], $options['total'], $options['perPage'], $options['currentPage'] , $options['options']);
return new PaginatorService($options['items'], $options['total'], $options['perPage'], $options['currentPage'] , $options['options']);
});
}
... ...
... ... @@ -13,13 +13,14 @@ class BaseService
{
/**
* @notes: 手动抛出异常
* @param string $msg
* @param string $code
* @throws BsideGlobalException
* @author:wlj
* @date: 2022/7/19 14:25
*/
public function fail(string $code = Code::SYSTEM_ERROR)
public function fail(string $msg, string $code = Code::SYSTEM_ERROR)
{
throw new BsideGlobalException($code);
throw new BsideGlobalException($code, $msg);
}
}
... ...
<?php
namespace App\Services\Facades;
use Illuminate\Support\Facades\Facade;
/**
* @see \App\Services\UploadService
* @method static string put(string $input_name="file", string|array $config="default") 单个文件上传
* @method static array puts(string $input_name="file", string|array $config="default") 批量获取上传的文件
* @method static array filePut(string $filename, string $content, string|array $config="default")
* @method static string url2path(string $url, string|array $disk="upload")
*/
class Upload extends Facade
{
protected static function getFacadeAccessor()
{
return '\App\Services\UploadService';
}
}
... ...
... ... @@ -11,7 +11,7 @@ use Illuminate\Pagination\LengthAwarePaginator;
* @author zbj
* @date 2023/4/14
*/
class PaginatorServer extends LengthAwarePaginator
class PaginatorService extends LengthAwarePaginator
{
public function toArray()
{
... ...
<?php
namespace App\Services;
use App\Helper\FileEtag;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Illuminate\Http\UploadedFile;
/**
* Class UploadServer
* @package App\Services
*/
class UploadService extends BaseService
{
/**
* 默认配置
* @see $defaultConfig
* @var array
*/
public $config = [];
/**
* 默认配置
* @var array
*/
public $defaultConfig = [
// 上传文件的大小范围
'size' => [
'max' => 1024 * 1024 * 2, // 2M
],
// 磁盘
'disk' => 'upload',
// 扩展名
'ext' => [
'png', 'jpg', 'jpeg', 'gif'
],
// 目录
'path' => '',
];
/**
* @param string|array $config
* @author:dc
* @time 2022/1/7 14:38
*/
private function config($config = 'default')
{
if (is_array($config)) {
$driver = $config['driver'];
} else {
$driver = $config;
}
// 加载配置
$this->config = $this->defaultConfig;
if ($driver != 'default') {
$conf = config('upload.' . $driver);
if ($conf) {
// 合并默认配置
$this->config = array_merge($this->config, $conf);
}
}
// 追加目录
if ($config['path'] ?? '') {
$this->config['path'] = $this->config['path'] . (strpos($config['path'], '/') !== 0 ? '/' : '') . $config['path'];
}
}
/**
*
* @param string $input_file
* @param string $config
* @return string
* @throws \Exception
* @author:dc
* @time 2021/12/30 16:37
*/
public function put($input_file = "file", $config = 'default'): string
{
$this->config($config);
if ($input_file instanceof UploadedFile) {
$file = &$input_file;
} else {
// 是否是已存在服务器的图片
if (Str::contains($input_file, env('APP_URL'))) {
return $input_file;
} else {
// 获取post上传文件
$file = request()->file($input_file);
// 如果不是上传,是否本地文件
if (!$file && is_file($input_file)) {
// 获取文件的名字
$origin_name = explode(DIRECTORY_SEPARATOR, $input_file);
!is_array($origin_name) && $origin_name = [$origin_name];
// 创建一个上传对象
$file = UploadedFile::createFromBase(new \Symfony\Component\HttpFoundation\File\UploadedFile($input_file, array_pop($origin_name)));
}
}
}
if ($file) {
// 验证扩展名
if (!in_array($file->extension(), $this->config['ext'])) {
$this->fail('文件扩展名不匹配,文件扩展名支持' . implode(',', $this->config['ext']));
}
// 验证文件大小
if ($file->getSize() > $this->config['size']['max']) {
$this->fail('文件大小超出' . ($this->config['size']['max'] / 1024 / 1024) . 'M');
}
//判断文件hash是否已存在
if ($filename = $this->getFileHash($file)) {
return $filename;
}
$pathDate = date('Y-m');
// 保存处理过的图片
if (!empty($img)) {
// 保存的图片名称
$save_name = $this->config['path'] . '/' . $pathDate . '/' . $file->hashName();
// 保存文件
if (!Storage::disk($this->config['disk'])
->put($save_name, $img->getEncoded())) {
$save_name = '';
}
} else {
// 保存文件
$save_name = $file->storeAs($this->config['path'] . '/' . $pathDate, $file->hashName(), $this->config['disk']);
}
if ($save_name) {
//保存文件hash
$this->setFileHash($file, $save_name);
// 返回地址
return $save_name;
}
$this->fail( '上传失败');
}
// 异常
$this->fail('请上传文件');
}
/**
* 批量上传
* @param string $input_file
* @param string $config
* @return array
* @throws \Exception
* @author:dc
* @time 2021/12/30 16:36
*/
public function puts($input_file = "file", $config = 'default'): array
{
$request = request();
$files = $request->post($input_file, []);
$files_ = $request->file($input_file);
if ($files_) {
$files = array_merge($files, $files_);
}
$filename = [];
if ($files instanceof UploadedFile) {
$filename[] = $this->put($files, $config);
} else if ($files) {
foreach ($files as $file) {
$filename[] = $this->put($file, $config);
}
} else {
// 异常
$this->fail('请上传文件');
}
return $filename;
}
/**
* 上传文件,文件内容
* @param string $filename
* @param string $content
* @param string $config
* @return string
* @throws \App\Exceptions\BsideGlobalException
* @author:dc
* @time 2022/1/7 14:45
*/
public function filePut(string $filename, string $content, $config = 'default')
{
if ($content) {
$this->config($config);
$save_name = $this->config['path'] . '/' . $filename;
// 保存文件
$result = Storage::disk($this->config['disk'])->put($save_name, $content);
if ($result) {
// 返回地址
return $save_name;
}
$this->fail('上传失败');
}
// 异常
$this->fail('请上传文件');
}
/**
* 文件hash是否存在 存在返回文件路径
* @param $file
* @return mixed|string|null
*/
protected function getFileHash($file)
{
$hash = FileEtag::sum($file)[0];
if (!$hash) {
return '';
}
return DB::table('gl_file_hash')->where('hash', $hash)->value('filename');
}
/**
* 保存文件hash
* @param $file
* @param $save_path
* @return bool
*/
protected function setFileHash($file, $save_path): bool
{
$hash = FileEtag::sum($file)[0];
if (!$hash) {
return false;
}
$data = [
'filename' => $save_path,
'hash' => $hash,
];
DB::table('gl_file_hash')->insert($data);
return true;
}
/**
* 文件地址转本地路径
* @param $url
* @param string $disk
* @return array|string|string[]
* @author zbj
* @date 2023/4/20
*/
public function url2path($url, $disk = 'upload'){
$upload_url = config('filesystems')['disks'][$disk]['url'];
return str_replace($upload_url . '/', '', $url);
}
}
... ...
... ... @@ -53,6 +53,13 @@ return [
'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
],
'upload' => [
'driver' => 'local',
'root' => env('UPLOAD_ROOT_PATH') ?: public_path('upload'),
'url' => env('UPLOAD_LOCAL_URL') ?: env('APP_URL') . '/upload',
'visibility' => 'public',
]
],
/*
... ...
<?php
/**
* 上传配置
* 如果其他配置没有设置某一项则使用default的
* 默认配置见下
* @see \App\Services\UploadService::$config
*/
return [
// 产品图集
'product' => [
'size' => [
'max' => 1024*1024*2, // 2M
],
'path' => '/product'
],
];
... ...
... ... @@ -133,9 +133,15 @@ Route::middleware(['bloginauth'])->group(function () {
Route::post('/save', [\App\Http\Controllers\Bside\DeptController::class, 'save'])->name('dept_save');
Route::any('/delete', [\App\Http\Controllers\Bside\DeptController::class, 'delete'])->name('dept_delete');
});
//文件操作
Route::prefix('file')->group(function () {
Route::post('/upload', [\App\Http\Controllers\Bside\FileController::class, 'upload'])->name('file_upload');
});
});
//无需登录验证的路由组
Route::group([], function () {
Route::any('/login', [\App\Http\Controllers\Bside\ComController::class, 'login'])->name('login');
Route::get('/file/download', [\App\Http\Controllers\Bside\FileController::class, 'download'])->name('file_download');
});
... ...