mirror of
https://github.com/kuaifan/dootask.git
synced 2025-12-11 10:33:54 +00:00
1021 lines
36 KiB
PHP
1021 lines
36 KiB
PHP
<?php
|
||
|
||
namespace App\Models;
|
||
|
||
use Request;
|
||
use App\Module\Apps;
|
||
use App\Module\Base;
|
||
use App\Tasks\PushTask;
|
||
use App\Exceptions\ApiException;
|
||
use Illuminate\Support\Facades\DB;
|
||
use Hhxsv5\LaravelS\Swoole\Task\Task;
|
||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||
|
||
/**
|
||
* App\Models\File
|
||
*
|
||
* @property int $id
|
||
* @property int|null $pid 上级ID
|
||
* @property string|null $pids 上级ID递归
|
||
* @property int|null $cid 复制ID
|
||
* @property string|null $name 名称
|
||
* @property string|null $type 类型
|
||
* @property string|null $ext 后缀名
|
||
* @property int|null $size 大小(B)
|
||
* @property int|null $userid 拥有者ID
|
||
* @property int|null $share 是否共享
|
||
* @property int|null $guest_access 是否允许游客访问
|
||
* @property int|null $pshare 所属分享ID
|
||
* @property int|null $created_id 创建者
|
||
* @property \Illuminate\Support\Carbon|null $created_at
|
||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||
* @property \Illuminate\Support\Carbon|null $deleted_at
|
||
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel cancelAppend()
|
||
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel cancelHidden()
|
||
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel change($array)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel getKeyValue()
|
||
* @method static \Illuminate\Database\Eloquent\Builder|File newModelQuery()
|
||
* @method static \Illuminate\Database\Eloquent\Builder|File newQuery()
|
||
* @method static \Illuminate\Database\Eloquent\Builder|File onlyTrashed()
|
||
* @method static \Illuminate\Database\Eloquent\Builder|File query()
|
||
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel remove()
|
||
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel saveOrIgnore()
|
||
* @method static \Illuminate\Database\Eloquent\Builder|File whereCid($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|File whereCreatedAt($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|File whereCreatedId($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|File whereDeletedAt($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|File whereExt($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|File whereGuestAccess($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|File whereId($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|File whereName($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|File wherePid($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|File wherePids($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|File wherePshare($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|File whereShare($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|File whereSize($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|File whereType($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|File whereUpdatedAt($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|File whereUserid($value)
|
||
* @method static \Illuminate\Database\Eloquent\Builder|File withTrashed()
|
||
* @method static \Illuminate\Database\Eloquent\Builder|File withoutTrashed()
|
||
* @mixin \Eloquent
|
||
*/
|
||
class File extends AbstractModel
|
||
{
|
||
use SoftDeletes;
|
||
|
||
/**
|
||
* 文件文件
|
||
*/
|
||
const codeExt = [
|
||
'txt',
|
||
'htaccess', 'htgroups', 'htpasswd', 'conf', 'bat', 'cmd', 'cpp', 'c', 'cc', 'cxx', 'h', 'hh', 'hpp', 'ino', 'cs', 'css',
|
||
'dockerfile', 'go', 'golang', 'html', 'htm', 'xhtml', 'vue', 'we', 'wpy', 'java', 'js', 'jsm', 'jsx', 'json', 'jsp', 'less', 'lua', 'makefile', 'gnumakefile',
|
||
'ocamlmakefile', 'make', 'mysql', 'nginx', 'ini', 'cfg', 'prefs', 'm', 'mm', 'pl', 'pm', 'p6', 'pl6', 'pm6', 'pgsql', 'php',
|
||
'inc', 'phtml', 'shtml', 'php3', 'php4', 'php5', 'phps', 'phpt', 'aw', 'ctp', 'module', 'ps1', 'py', 'r', 'rb', 'ru', 'gemspec', 'rake', 'guardfile', 'rakefile',
|
||
'gemfile', 'rs', 'sass', 'scss', 'sh', 'bash', 'bashrc', 'sql', 'sqlserver', 'swift', 'ts', 'typescript', 'str', 'vbs', 'vb', 'v', 'vh', 'sv', 'svh', 'xml',
|
||
'rdf', 'rss', 'wsdl', 'xslt', 'atom', 'mathml', 'mml', 'xul', 'xbl', 'xaml', 'yaml', 'yml',
|
||
'asp', 'properties', 'gitignore', 'log', 'bas', 'prg', 'python', 'ftl', 'aspx', 'plist'
|
||
];
|
||
|
||
/**
|
||
* office文件
|
||
*/
|
||
const officeExt = [
|
||
// 文本文件
|
||
'doc', 'docx', // Microsoft Word 文档
|
||
'dot', 'dotx', // Word 模板
|
||
'odt', // OpenDocument 文本格式
|
||
'ott', // OpenDocument 文本模板
|
||
'rtf', // 富文本格式
|
||
|
||
// 电子表格
|
||
'xls', 'xlsx', // Microsoft Excel 电子表格
|
||
'xlsm', // Excel 含宏的工作簿
|
||
'xlt', 'xltx', // Excel 模板
|
||
'ods', // OpenDocument 电子表格格式
|
||
'ots', // OpenDocument 电子表格模板
|
||
'csv', // 逗号分隔值
|
||
'tsv', // 制表符分隔值
|
||
|
||
// 演示文稿
|
||
'ppt', 'pptx', // Microsoft PowerPoint 演示文稿
|
||
'pps', 'ppsx', // PowerPoint 幻灯片放映
|
||
'pot', 'potx', // PowerPoint 模板
|
||
'odp', // OpenDocument 演示文稿格式
|
||
'otp', // OpenDocument 演示文稿模板
|
||
];
|
||
|
||
/**
|
||
* 图片文件
|
||
*/
|
||
const imageExt = [
|
||
'jpg', 'jpeg', 'webp', 'png', 'gif', 'bmp'
|
||
];
|
||
|
||
/**
|
||
* 本地媒体文件
|
||
*/
|
||
const localExt = [
|
||
'jpg', 'jpeg', 'webp', 'png', 'gif', 'bmp', 'ico', 'raw',
|
||
'tif', 'tiff',
|
||
'mp3', 'wav', 'mp4', 'flv',
|
||
// 'avi', 'mov', 'wmv', 'mkv', '3gp', 'rm', // 这一排是要转换的,无法使用本地播放
|
||
];
|
||
|
||
/**
|
||
* 压缩包下载大小限制
|
||
*/
|
||
const zipMaxSize = 1024 * 1024 * 1024; // 1G
|
||
|
||
|
||
/**
|
||
* 获取文件列表
|
||
* @param user $user
|
||
* @param int $pid
|
||
* @param string $type
|
||
* @param bool $isGetparent
|
||
* @return array
|
||
*/
|
||
public function getFileList($user, int $pid, $type = "all", $isGetparent = true)
|
||
{
|
||
$permission = 1000;
|
||
$userids = $user->isTemp() ? [$user->userid] : [0, $user->userid];
|
||
$builder = File::wherePid($pid)
|
||
->when($type == 'dir', function ($q) {
|
||
$q->whereType('folder');
|
||
});
|
||
if ($pid > 0) {
|
||
File::permissionFind($pid, $userids, 0, $permission);
|
||
} else {
|
||
$builder->whereUserid($user->userid);
|
||
}
|
||
//
|
||
$array = $builder->take(500)->get()->toArray();
|
||
foreach ($array as &$item) {
|
||
$item['permission'] = $permission;
|
||
}
|
||
//
|
||
if ($pid > 0) {
|
||
// 遍历获取父级
|
||
if ($isGetparent) {
|
||
while ($pid > 0) {
|
||
$file = File::whereId($pid)->first();
|
||
if (empty($file)) {
|
||
break;
|
||
}
|
||
$pid = $file->pid;
|
||
$temp = $file->toArray();
|
||
$temp['permission'] = $file->getPermission($userids);
|
||
$array[] = $temp;
|
||
}
|
||
}
|
||
// 去除没有权限的文件
|
||
$isUnset = false;
|
||
foreach ($array as $index1 => $item1) {
|
||
if ($item1['permission'] === -1) {
|
||
foreach ($array as $index2 => $item2) {
|
||
if ($item2['pid'] === $item1['id']) {
|
||
$array[$index2]['pid'] = 0;
|
||
}
|
||
}
|
||
$isUnset = true;
|
||
unset($array[$index1]);
|
||
}
|
||
}
|
||
if ($isUnset) {
|
||
$array = array_values($array);
|
||
}
|
||
} else {
|
||
// 获取共享相关
|
||
DB::statement("SET SQL_MODE=''");
|
||
$pre = DB::connection()->getTablePrefix();
|
||
$list = File::select(["files.*", DB::raw("MAX({$pre}file_users.permission) as permission")])
|
||
->join('file_users', 'files.id', '=', 'file_users.file_id')
|
||
->where('files.userid', '!=', $user->userid)
|
||
->whereIn('file_users.userid', $userids)
|
||
->groupBy('files.id')
|
||
->take(100)
|
||
->when($type == 'dir', function ($q) {
|
||
$q->where('files.type', 'folder');
|
||
})
|
||
->get();
|
||
if ($list->isNotEmpty()) {
|
||
foreach ($list as $file) {
|
||
$temp = $file->toArray();
|
||
$temp['pid'] = 0;
|
||
$array[] = $temp;
|
||
}
|
||
}
|
||
}
|
||
// 图片直接返回预览地址
|
||
foreach ($array as &$item) {
|
||
$item = File::handleImageUrl($item);
|
||
}
|
||
return $array;
|
||
}
|
||
|
||
/**
|
||
* 保存文件内容(上传文件)
|
||
* @param user $user
|
||
* @param int $pid
|
||
* @param string $webkitRelativePath
|
||
* @param bool $overwrite
|
||
* @return array
|
||
*/
|
||
public function contentUpload($user, int $pid, $webkitRelativePath, $overwrite = false)
|
||
{
|
||
$userid = $user->userid;
|
||
if ($pid > 0) {
|
||
if (File::wherePid($pid)->count() >= 300) {
|
||
return Base::retError('每个文件夹里最多只能创建300个文件或文件夹');
|
||
}
|
||
$row = File::permissionFind($pid, $user, 1);
|
||
$userid = $row->userid;
|
||
} else {
|
||
if (File::whereUserid($user->userid)->wherePid(0)->count() >= 300) {
|
||
return Base::retError('每个文件夹里最多只能创建300个文件或文件夹');
|
||
}
|
||
}
|
||
//
|
||
$dirs = explode("/", $webkitRelativePath);
|
||
$addItem = [];
|
||
while (count($dirs) > 1) {
|
||
$dirName = array_shift($dirs);
|
||
if ($dirName) {
|
||
AbstractModel::transaction(function () use ($dirName, $user, $userid, &$pid, &$addItem) {
|
||
$dirRow = File::wherePid($pid)->whereType('folder')->whereName($dirName)->lockForUpdate()->first();
|
||
if (empty($dirRow)) {
|
||
$dirRow = File::createInstance([
|
||
'pid' => $pid,
|
||
'type' => 'folder',
|
||
'name' => $dirName,
|
||
'userid' => $userid,
|
||
'created_id' => $user->userid,
|
||
]);
|
||
$dirRow->handleDuplicateName();
|
||
if ($dirRow->saveBeforePP()) {
|
||
$addItem[] = File::find($dirRow->id);
|
||
}
|
||
}
|
||
if (empty($dirRow)) {
|
||
throw new ApiException('创建文件夹失败');
|
||
}
|
||
$pid = $dirRow->id;
|
||
});
|
||
foreach ($addItem as $tmpRow) {
|
||
$tmpRow->pushMsg('add', $tmpRow);
|
||
}
|
||
}
|
||
}
|
||
//
|
||
$path = 'uploads/tmp/file/' . date("Ym") . '/';
|
||
$data = Base::upload([
|
||
"file" => Request::file('files'),
|
||
"type" => 'more',
|
||
"autoThumb" => false,
|
||
"path" => $path,
|
||
"quality" => true
|
||
]);
|
||
if (Base::isError($data)) {
|
||
throw new ApiException($data['msg']);
|
||
}
|
||
$data = $data['data'];
|
||
//
|
||
$type = match ($data['ext']) {
|
||
'text', 'md', 'markdown' => 'document',
|
||
'drawio' => 'drawio',
|
||
'mind' => 'mind',
|
||
'doc', 'docx', 'dot', 'dotx', 'odt', 'ott', 'rtf' => "word",
|
||
'xls', 'xlsx', 'xlsm', 'xlt', 'xltx', 'ods', 'ots', 'csv', 'tsv' => "excel",
|
||
'ppt', 'pptx', 'pps', 'ppsx', 'pot', 'potx', 'odp', 'otp' => "ppt",
|
||
'wps' => "wps",
|
||
'jpg', 'jpeg', 'webp', 'png', 'gif', 'bmp', 'ico', 'raw', 'svg' => "picture",
|
||
'rar', 'zip', 'jar', '7-zip', 'tar', 'gzip', '7z', 'gz', 'apk', 'dmg' => "archive",
|
||
'tif', 'tiff' => "tif",
|
||
'dwg', 'dxf' => "cad",
|
||
'ofd' => "ofd",
|
||
'pdf' => "pdf",
|
||
'txt' => "txt",
|
||
'htaccess', 'htgroups', 'htpasswd', 'conf', 'bat', 'cmd', 'cpp', 'c', 'cc', 'cxx', 'h', 'hh', 'hpp', 'ino', 'cs', 'css',
|
||
'dockerfile', 'go', 'golang', 'html', 'htm', 'xhtml', 'vue', 'we', 'wpy', 'java', 'js', 'jsm', 'jsx', 'json', 'jsp', 'less', 'lua', 'makefile', 'gnumakefile',
|
||
'ocamlmakefile', 'make', 'mysql', 'nginx', 'ini', 'cfg', 'prefs', 'm', 'mm', 'pl', 'pm', 'p6', 'pl6', 'pm6', 'pgsql', 'php',
|
||
'inc', 'phtml', 'shtml', 'php3', 'php4', 'php5', 'phps', 'phpt', 'aw', 'ctp', 'module', 'ps1', 'py', 'r', 'rb', 'ru', 'gemspec', 'rake', 'guardfile', 'rakefile',
|
||
'gemfile', 'rs', 'sass', 'scss', 'sh', 'bash', 'bashrc', 'sql', 'sqlserver', 'swift', 'ts', 'typescript', 'str', 'vbs', 'vb', 'v', 'vh', 'sv', 'svh', 'xml',
|
||
'rdf', 'rss', 'wsdl', 'xslt', 'atom', 'mathml', 'mml', 'xul', 'xbl', 'xaml', 'yaml', 'yml',
|
||
'asp', 'properties', 'gitignore', 'log', 'bas', 'prg', 'python', 'ftl', 'aspx', 'plist' => "code",
|
||
'mp3', 'wav', 'mp4', 'flv',
|
||
'avi', 'mov', 'wmv', 'mkv', '3gp', 'rm' => "media",
|
||
'xmind' => "xmind",
|
||
'rp' => "axure",
|
||
default => "",
|
||
};
|
||
if ($data['ext'] == 'markdown') {
|
||
$data['ext'] = 'md';
|
||
}
|
||
$file = null;
|
||
$params = [
|
||
'pid' => $pid,
|
||
'name' => Base::rightDelete($data['name'], '.' . $data['ext']),
|
||
'type' => $type,
|
||
'ext' => $data['ext'],
|
||
'userid' => $userid,
|
||
'created_id' => $user->userid,
|
||
];
|
||
if ($overwrite) {
|
||
$file = self::wherePid($params['pid'])->whereExt($params['ext'])->whereName($params['name'])->first();
|
||
}
|
||
if (!$file) {
|
||
$overwrite = false;
|
||
$file = File::createInstance($params);
|
||
$file->handleDuplicateName();
|
||
}
|
||
// 开始创建
|
||
return AbstractModel::transaction(function () use ($overwrite, $addItem, $webkitRelativePath, $type, $user, $data, $file) {
|
||
$file->size = $data['size'] * 1024;
|
||
$file->saveBeforePP();
|
||
//
|
||
$data = Base::uploadMove($data, "uploads/file/" . $file->type . "/" . date("Ym") . "/" . $file->id . "/");
|
||
$content = [
|
||
'from' => '',
|
||
'type' => $type,
|
||
'ext' => $data['ext'],
|
||
'url' => $data['path'],
|
||
];
|
||
if (isset($data['width'])) {
|
||
$content['width'] = $data['width'];
|
||
$content['height'] = $data['height'];
|
||
}
|
||
$content = FileContent::createInstance([
|
||
'fid' => $file->id,
|
||
'content' => $content,
|
||
'text' => '',
|
||
'size' => $file->size,
|
||
'userid' => $user->userid,
|
||
]);
|
||
$content->save();
|
||
//
|
||
$tmpRow = File::find($file->id);
|
||
$tmpRow->pushMsg('add', $tmpRow);
|
||
//
|
||
$data = File::handleImageUrl($tmpRow->toArray());
|
||
$data['full_name'] = $webkitRelativePath ?: ($data['name'] . '.' . $data['ext']);
|
||
$data['overwrite'] = $overwrite ? 1 : 0;
|
||
//
|
||
$addItem[] = $data;
|
||
|
||
return ['data' => $data, 'addItem' => $addItem];
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 是否有访问权限
|
||
* @param array $userids
|
||
* @return int -1:没有权限,0:访问权限,1:读写权限,1000:所有者或创建者
|
||
*/
|
||
public function getPermission(array $userids)
|
||
{
|
||
$validUserIds = array_filter($userids);
|
||
if (in_array($this->userid, $validUserIds) || in_array($this->created_id, $validUserIds)) {
|
||
// ① 自己的文件夹 或 自己创建的文件夹
|
||
return 1000;
|
||
}
|
||
$row = $this->getShareInfo();
|
||
if ($row) {
|
||
$fileUser = FileUser::whereFileId($row->id)->whereIn('userid', $userids)->orderByDesc('permission')->first();
|
||
if ($fileUser) {
|
||
// ② 在指定共享成员内
|
||
return $fileUser->permission;
|
||
}
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
/**
|
||
* 获取共享数据(含自身)
|
||
* @return $this|null
|
||
*/
|
||
public function getShareInfo()
|
||
{
|
||
if ($this->share) {
|
||
return $this;
|
||
}
|
||
$pid = $this->pid;
|
||
while ($pid > 0) {
|
||
$row = self::whereId($pid)->first();
|
||
if (empty($row)) {
|
||
break;
|
||
}
|
||
if ($row->share) {
|
||
return $row;
|
||
}
|
||
$pid = $row->pid;
|
||
}
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* 是否处于共享文件夹内(不含自身)
|
||
* @return File|false
|
||
*/
|
||
public function isNnShare()
|
||
{
|
||
$pid = $this->pid;
|
||
while ($pid > 0) {
|
||
$row = self::whereId($pid)->first();
|
||
if (empty($row)) {
|
||
break;
|
||
}
|
||
if ($row->share) {
|
||
return $row;
|
||
}
|
||
$pid = $row->pid;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* 目录内是否存在共享文件或文件夹
|
||
* @return bool
|
||
*/
|
||
public function isSubShare()
|
||
{
|
||
return $this->type == 'folder' && File::where("pids", "like", "%,{$this->id},%")->whereShare(1)->exists();
|
||
}
|
||
|
||
/**
|
||
* 设置/关闭 共享(同时遍历取消里面的共享)
|
||
* @param $share
|
||
* @return bool
|
||
*/
|
||
public function updataShare($share = null)
|
||
{
|
||
if ($share === null) {
|
||
$share = FileUser::whereFileId($this->id)->count() == 0 ? 0 : 1;
|
||
}
|
||
if ($this->share != $share) {
|
||
AbstractModel::transaction(function () use ($share) {
|
||
$this->share = $share;
|
||
$this->pshare = $share ? $this->id : 0;
|
||
$this->save();
|
||
File::where("pids", "like", "%,{$this->id},%")->update(['pshare' => $share ? $this->id : 0]);
|
||
if ($share === 0) {
|
||
FileUser::deleteFileAll($this->id, $this->userid);
|
||
}
|
||
$list = self::wherePid($this->id)->get();
|
||
if ($list->isNotEmpty()) {
|
||
foreach ($list as $item) {
|
||
$item->updataShare(0);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* 处理重名
|
||
* @return void
|
||
*/
|
||
public function handleDuplicateName()
|
||
{
|
||
$builder = self::wherePid($this->pid)->whereUserid($this->userid)->whereExt($this->ext);
|
||
$exist = $builder->clone()->whereName($this->name)->exists();
|
||
if (!$exist) {
|
||
return; // 未重名,不需要处理
|
||
}
|
||
// 发现重名,自动重命名
|
||
$nextNum = 2;
|
||
if (preg_match("/(.*?)(\s+\(\d+\))*$/", $this->name)) {
|
||
$preName = preg_replace("/(.*?)(\s+\(\d+\))*$/", "$1", $this->name);
|
||
$nextNum = $builder->clone()->where("name", "LIKE", "{$preName}%")->count() + 1;
|
||
}
|
||
$newName = "{$this->name} ({$nextNum})";
|
||
if ($builder->clone()->whereName($newName)->exists()) {
|
||
$nextNum = rand(100, 9999);
|
||
$newName = "{$this->name} ({$nextNum})";
|
||
}
|
||
$this->name = $newName;
|
||
}
|
||
|
||
/**
|
||
* 保存前更新pids/pshare
|
||
* @return bool
|
||
*/
|
||
public function saveBeforePP()
|
||
{
|
||
$pid = $this->pid;
|
||
$pshare = $this->share ? $this->id : 0;
|
||
$array = [];
|
||
while ($pid > 0) {
|
||
$array[] = $pid;
|
||
$file = self::select(['id', 'pid', 'share'])->find($pid);
|
||
if ($file) {
|
||
$pid = $file->pid;
|
||
if ($file->share) {
|
||
$pshare = $file->id;
|
||
}
|
||
} else {
|
||
$pid = 0;
|
||
}
|
||
}
|
||
$opids = $this->pids;
|
||
if ($array) {
|
||
$array = array_values(array_reverse($array));
|
||
$this->pids = ',' . implode(',', $array) . ',';
|
||
} else {
|
||
$this->pids = '';
|
||
}
|
||
$this->pshare = $pshare;
|
||
if (!$this->save()) {
|
||
return false;
|
||
}
|
||
// 更新子文件(夹)
|
||
if ($opids != $this->pids) {
|
||
self::wherePid($this->id)->chunkById(100, function ($lists) {
|
||
/** @var self $item */
|
||
foreach ($lists as $item) {
|
||
$item->saveBeforePP();
|
||
}
|
||
});
|
||
}
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* 遍历删除文件(夹)
|
||
* @return bool
|
||
*/
|
||
public function deleteFile()
|
||
{
|
||
AbstractModel::transaction(function () {
|
||
$this->delete();
|
||
$this->pushMsg('delete');
|
||
FileUser::deleteFileAll($this->id);
|
||
FileContent::whereFid($this->id)->delete();
|
||
$list = self::wherePid($this->id)->get();
|
||
if ($list->isNotEmpty()) {
|
||
foreach ($list as $file) {
|
||
$file->deleteFile();
|
||
}
|
||
}
|
||
});
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* 强制删除文件
|
||
* @return true
|
||
*/
|
||
public function forceDeleteFile()
|
||
{
|
||
AbstractModel::transaction(function () {
|
||
$this->forceDelete();
|
||
FileContent::withTrashed()
|
||
->whereFid($this->id)
|
||
->orderBy('id')
|
||
->chunk(500, function ($contents) {
|
||
/** @var FileContent $content */
|
||
foreach ($contents as $content) {
|
||
$content->forceDeleteContent();
|
||
}
|
||
});
|
||
});
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* 获取文件分享链接
|
||
* @param $userid
|
||
* @param $refresh
|
||
* @return array
|
||
*/
|
||
public function getShareLink($userid, $refresh = false)
|
||
{
|
||
if ($this->type == 'folder') {
|
||
throw new ApiException('文件夹不支持分享');
|
||
}
|
||
return FileLink::generateLink($this->id, $userid, $refresh);
|
||
}
|
||
|
||
/**
|
||
* 获取文件名称加后缀
|
||
* @return string|null
|
||
*/
|
||
public function getNameAndExt()
|
||
{
|
||
return $this->ext ? "{$this->name}.{$this->ext}" : $this->name;
|
||
}
|
||
|
||
/**
|
||
* 推送消息
|
||
* @param $action
|
||
* @param File|null $data 发送内容,默认为[id]
|
||
* @param array $userid 指定会员,默认为可查看文件的人
|
||
*/
|
||
public function pushMsg($action, $data = null, $userid = null)
|
||
{
|
||
if ($data === null) {
|
||
$data = [
|
||
'id' => $this->id
|
||
];
|
||
}
|
||
//
|
||
$userid = $this->pushUserid($action, $userid);
|
||
if (empty($userid)) {
|
||
return;
|
||
}
|
||
//
|
||
$msg = [
|
||
'type' => 'file',
|
||
'action' => $action,
|
||
'data' => $data,
|
||
];
|
||
if ($action == 'content') {
|
||
$msg['nickname'] = User::nickname();
|
||
$msg['time'] = time();
|
||
}
|
||
$params = [
|
||
'ignoreFd' => Request::header('fd'),
|
||
'userid' => $userid,
|
||
'msg' => $msg
|
||
];
|
||
$task = new PushTask($params, false);
|
||
Task::deliver($task);
|
||
}
|
||
|
||
/**
|
||
* 文件推送消息
|
||
* @param $action
|
||
* @param array|null $data 发送内容
|
||
* @param int $userid 会员ID
|
||
*/
|
||
public static function pushMsgSimple($action, $data, $userid)
|
||
{
|
||
if (empty($data) || empty($userid)) {
|
||
return;
|
||
}
|
||
$msg = [
|
||
'type' => 'file',
|
||
'action' => $action,
|
||
'data' => $data,
|
||
];
|
||
$params = [
|
||
'userid' => $userid,
|
||
'msg' => $msg
|
||
];
|
||
Task::deliver(new PushTask($params));
|
||
}
|
||
|
||
/**
|
||
* 获取推送会员
|
||
* @param $action
|
||
* @param $userid
|
||
* @return array|int[]|mixed|null[]
|
||
*/
|
||
public function pushUserid($action, $userid = null) {
|
||
$wherePath = "/manage/file";
|
||
if ($userid === null) {
|
||
$array = [$this->userid];
|
||
if ($action == 'add' && $this->pid == 0) {
|
||
return $array;
|
||
}
|
||
if ($action == 'content') {
|
||
$wherePath = "/single/file/{$this->id}";
|
||
} elseif ($this->pid > 0) {
|
||
$wherePath = "/manage/file/{$this->pid}";
|
||
} else {
|
||
$tmpArray = FileUser::whereFileId($this->id)->pluck('userid')->toArray();
|
||
if (empty($tmpArray)) {
|
||
return $array;
|
||
}
|
||
if (!in_array(0, $tmpArray)) {
|
||
return $tmpArray;
|
||
}
|
||
}
|
||
$tmpArray = WebSocket::wherePath($wherePath)->pluck('userid')->toArray();
|
||
if (empty($tmpArray)) {
|
||
return $array;
|
||
}
|
||
$array = array_values(array_filter(array_unique(array_merge($array, $tmpArray))));
|
||
} else {
|
||
$array = is_array($userid) ? $userid : [$userid];
|
||
if (in_array(0, $array)) {
|
||
return WebSocket::wherePath($wherePath)->pluck('userid')->toArray();
|
||
}
|
||
}
|
||
return $array;
|
||
}
|
||
|
||
/**
|
||
* code获取文件ID、名称
|
||
* @param $code
|
||
* @return File
|
||
*/
|
||
public static function code2IdName($code) {
|
||
$arr = explode(",", base64_decode($code));
|
||
if (empty($arr)) {
|
||
return null;
|
||
}
|
||
$fileId = intval($arr[0]);
|
||
if (empty($fileId)) {
|
||
return null;
|
||
}
|
||
return File::select(['id', 'name'])->find($fileId);
|
||
}
|
||
|
||
|
||
/**
|
||
* 处理返回图片地址
|
||
* @param array $item
|
||
* @return array
|
||
*/
|
||
public static function handleImageUrl($item)
|
||
{
|
||
if (in_array($item['ext'], self::imageExt) ) {
|
||
$content = Base::json2array(FileContent::whereFid($item['id'])->orderByDesc('id')->value('content'));
|
||
if ($content) {
|
||
$item['image_url'] = Base::fillUrl($content['url']);
|
||
$item['image_width'] = intval($content['width']);
|
||
$item['image_height'] = intval($content['height']);
|
||
}
|
||
}
|
||
return $item;
|
||
}
|
||
|
||
/**
|
||
* 获取文件并检测权限
|
||
* @param int $id
|
||
* @param User|array|int $user 要求权限的用户,如:[0, 1]
|
||
* @param int $limit 要求权限: 0-访问权限、1-读写权限、1000-所有者或创建者
|
||
* @param int $permission
|
||
* @return File
|
||
*/
|
||
public static function permissionFind($id, $user, int $limit = 0, int &$permission = -1)
|
||
{
|
||
$file = File::find(intval($id));
|
||
if (empty($file)) {
|
||
throw new ApiException('文件不存在或已被删除');
|
||
}
|
||
//
|
||
if ($user instanceof User) {
|
||
$userids = $user->isTemp() ? [$user->userid] : [0, $user->userid];
|
||
} else {
|
||
$userids = is_array($user) ? $user : [$user];
|
||
}
|
||
$permission = $file->getPermission($userids);
|
||
if ($permission < $limit) {
|
||
$msg = match ($limit) {
|
||
1000 => '仅限所有者或创建者操作',
|
||
1 => '没有修改写入权限',
|
||
default => '没有查看访问权限',
|
||
};
|
||
throw new ApiException($msg);
|
||
}
|
||
return $file;
|
||
}
|
||
|
||
/**
|
||
* 格式化内容数据
|
||
* @param array $data [path, size, ext, name]
|
||
* @return array
|
||
*/
|
||
public static function formatFileData(array $data)
|
||
{
|
||
$fileName = $data['name'];
|
||
$filePath = $data['path'];
|
||
$fileSize = $data['size'];
|
||
$fileExt = $data['ext'];
|
||
$publicPath = public_path($filePath);
|
||
//
|
||
switch ($fileExt) {
|
||
case 'md':
|
||
case 'text':
|
||
// 文本
|
||
$data['content'] = [
|
||
'type' => $fileExt,
|
||
'content' => file_get_contents($publicPath) ?: 'Content deleted',
|
||
];
|
||
$data['file_mode'] = $fileExt;
|
||
break;
|
||
|
||
case 'drawio':
|
||
// 图表
|
||
$data['content'] = [
|
||
'xml' => file_get_contents($publicPath)
|
||
];
|
||
$data['file_mode'] = $fileExt;
|
||
break;
|
||
|
||
case 'mind':
|
||
// 思维导图
|
||
$data['content'] = Base::json2array(file_get_contents($publicPath));
|
||
$data['file_mode'] = $fileExt;
|
||
break;
|
||
|
||
default:
|
||
if (in_array($fileExt, self::codeExt) && $fileSize < 2 * 1024 * 1024)
|
||
{
|
||
// 文本预览,限制2M内的文件
|
||
$data['content'] = [
|
||
'content' => file_get_contents($publicPath) ?: 'Content deleted',
|
||
];
|
||
$data['file_mode'] = 'code';
|
||
}
|
||
elseif (in_array($fileExt, File::officeExt))
|
||
{
|
||
// office预览
|
||
$data['content'] = json_decode('{}');
|
||
$data['file_mode'] = 'office';
|
||
}
|
||
else
|
||
{
|
||
// 其他预览
|
||
$name = Base::rightDelete($fileName, ".{$fileExt}") . ".{$fileExt}";
|
||
$data['content'] = [
|
||
'preview' => true,
|
||
'name' => $name,
|
||
'key' => urlencode(Base::urlAddparameter($filePath, [
|
||
'name' => $name,
|
||
'ext' => $fileExt
|
||
])),
|
||
];
|
||
$data['file_mode'] = 'preview';
|
||
}
|
||
break;
|
||
}
|
||
return $data;
|
||
}
|
||
|
||
/**
|
||
* 移交文件
|
||
* @param $originalUserid
|
||
* @param $newUserid
|
||
* @return void
|
||
*/
|
||
public static function transfer($originalUserid, $newUserid)
|
||
{
|
||
if (!self::whereUserid($originalUserid)->exists()) {
|
||
return;
|
||
}
|
||
|
||
// 创建一个文件夹存放移交的文件
|
||
$name = User::userid2nickname($originalUserid) ?: ('ID:' . $originalUserid);
|
||
$file = File::createInstance([
|
||
'pid' => 0,
|
||
'name' => "【{$name}】移交的文件",
|
||
'type' => "folder",
|
||
'ext' => "",
|
||
'userid' => $newUserid,
|
||
'created_id' => 0,
|
||
]);
|
||
$file->handleDuplicateName();
|
||
$file->saveBeforePP();
|
||
|
||
// 移交文件
|
||
self::whereUserid($originalUserid)->chunkById(100, function($list) use ($file, $newUserid) {
|
||
/** @var self $item */
|
||
foreach ($list as $item) {
|
||
if ($item->pid === 0) {
|
||
$item->pid = $file->id;
|
||
}
|
||
$item->userid = $newUserid;
|
||
$item->saveBeforePP();
|
||
}
|
||
});
|
||
|
||
// 移交文件权限
|
||
FileUser::whereUserid($originalUserid)->chunkById(100, function ($list) use ($newUserid) {
|
||
/** @var FileUser $item */
|
||
foreach ($list as $item) {
|
||
$row = FileUser::whereFileId($item->file_id)->whereUserid($newUserid)->first();
|
||
if ($row) {
|
||
// 已存在则删除原数据,判断改变已存在的数据
|
||
$row->permission = max($row->permission, $item->permission);
|
||
$row->save();
|
||
$item->delete();
|
||
} else {
|
||
// 不存在则改变原数据
|
||
$item->userid = $newUserid;
|
||
$item->save();
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 获取文件树并计算文件总大小
|
||
*
|
||
* @param int $fileId
|
||
* @param User $user
|
||
* @param int $permission 0-访问权限、1-读写权限、1000-所有者或创建者
|
||
* @param string $path
|
||
* @param int $totalSize
|
||
* @return object
|
||
*/
|
||
public static function getFilesTree(int $fileId, User $user, $permission = 1, $path = '', &$totalSize = 0) {
|
||
$file = File::permissionFind($fileId, $user, $permission);
|
||
$file->path = ltrim($path . '/' . $file->name, '/');
|
||
$file->children = [];
|
||
if ($file->type == 'folder') {
|
||
$files = $file->getFileList($user, $fileId, 'all', false);
|
||
foreach ($files as &$childFile) {
|
||
$childFile['path'] = $file->path . '/' . $childFile['name'];
|
||
if ($childFile['type'] == 'folder') {
|
||
$childFile['children'] = self::getFilesTree($childFile['id'], $user, $permission, $file->path, $totalSize);
|
||
} else {
|
||
$totalSize += $childFile['size'];
|
||
}
|
||
}
|
||
$file->children = $files;
|
||
} else {
|
||
$totalSize += $file->size;
|
||
}
|
||
$file->totalSize = $totalSize;
|
||
return $file;
|
||
}
|
||
|
||
/**
|
||
* 文件夹文件添加到压缩文件
|
||
*
|
||
* @param \ZipArchive $zip
|
||
* @param object $file
|
||
* @return void
|
||
*/
|
||
public static function addFileTreeToZip($zip, $file)
|
||
{
|
||
if ($file->type != 'folder' && $file->name != '') {
|
||
$content = FileContent::whereFid($file->id)->orderByDesc('id')->first();
|
||
$content = Base::json2array($content?->content ?: []);
|
||
$typeExtensions = [
|
||
'word' => 'docx',
|
||
'excel' => 'xlsx',
|
||
'ppt' => 'pptx',
|
||
];
|
||
if (array_key_exists($file->type, $typeExtensions)) {
|
||
$filePath = empty($content) ? public_path('assets/office/empty.' . $typeExtensions[$file->type]) : public_path($content['url']);
|
||
}
|
||
//
|
||
$relativePath = $file->path . '.' . $file->ext;
|
||
if (file_exists($filePath)) {
|
||
$zip->addFile($filePath, $relativePath);
|
||
} else {
|
||
if (empty($content['url'])) {
|
||
$zip->addFromString($relativePath, $content['content']);
|
||
} else {
|
||
$filePath = public_path($content['url']);
|
||
$zip->addFile($filePath, $relativePath);
|
||
}
|
||
}
|
||
} else {
|
||
if (isset($file->children)) {
|
||
foreach ($file->children as $childFile) {
|
||
try {
|
||
self::addFileTreeToZip($zip, (object)$childFile);
|
||
} catch (\Exception $e) {
|
||
}
|
||
}
|
||
}
|
||
// 在压缩包中创建文件夹
|
||
$zip->addEmptyDir($file->path);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 根据文件类型判断是否需要安装应用
|
||
* @param $type
|
||
* @return void
|
||
*/
|
||
public static function isNeedInstallApp($type): void
|
||
{
|
||
// 文件类型与应用的映射配置
|
||
$fileTypeAppMapping = [
|
||
// Office 应用映射
|
||
[
|
||
'types' => ['word', 'excel', 'ppt', 'docx', 'xlsx', 'pptx'],
|
||
'app_id' => 'office',
|
||
'app_name' => 'OnlyOffice'
|
||
],
|
||
// Drawio 应用映射
|
||
[
|
||
'types' => ['drawio'],
|
||
'app_id' => 'drawio',
|
||
'app_name' => 'Drawio'
|
||
],
|
||
// Minder 应用映射
|
||
[
|
||
'types' => ['mind'],
|
||
'app_id' => 'minder',
|
||
'app_name' => 'Minder'
|
||
]
|
||
];
|
||
|
||
// 遍历配置检查是否需要安装应用
|
||
foreach ($fileTypeAppMapping as $config) {
|
||
if (in_array($type, $config['types'])) {
|
||
Apps::isInstalledThrow($config['app_id']);
|
||
}
|
||
}
|
||
}
|
||
}
|