perf: 优化文件权限

This commit is contained in:
kuaifan 2021-12-29 23:54:13 +08:00
parent 6c67ff3fe8
commit 9d89334cc5
10 changed files with 118 additions and 104 deletions

View File

@ -33,13 +33,18 @@ class FileController extends AbstractController
$data = Request::all(); $data = Request::all();
$pid = intval($data['pid']); $pid = intval($data['pid']);
// //
$permission = 1000;
if ($pid > 0) { if ($pid > 0) {
File::allowFind($pid); File::permissionFind($pid, 0, $permission);
$builder = File::wherePid($pid); $builder = File::wherePid($pid);
} else { } else {
$builder = File::whereUserid($user->userid); $builder = File::whereUserid($user->userid);
} }
//
$array = $builder->take(500)->get()->toArray(); $array = $builder->take(500)->get()->toArray();
foreach ($array as &$item) {
$item['permission'] = $permission;
}
// //
if ($pid > 0) { if ($pid > 0) {
// 遍历获取父级 // 遍历获取父级
@ -50,7 +55,7 @@ class FileController extends AbstractController
} }
$pid = $file->pid; $pid = $file->pid;
$temp = $file->toArray(); $temp = $file->toArray();
$temp['allow'] = $file->chackAllow($user->userid); $temp['permission'] = $file->getPermission($user->userid);
$array[] = $temp; $array[] = $temp;
} }
} else { } else {
@ -81,23 +86,27 @@ class FileController extends AbstractController
/** /**
* 获取单条数据 * 获取单条数据
* *
* @apiParam {String} [code] 链接码(用于预览) * @apiParam {Number|String} id
* @apiParam {Number} [id] 文件ID需要权限用于管理 * - Number 文件ID需要登录
* - String 链接码(不需要登录,用于预览)
* *
* @return array * @return array
*/ */
public function one() public function one()
{ {
if (Request::exists("code")) { $id = Request::input('id');
$fileLink = FileLink::whereCode(Request::input('code'))->first(); //
if (Base::isNumber($id)) {
User::auth();
$file = File::permissionFind(intval($id));
} elseif ($id) {
$fileLink = FileLink::whereCode($id)->first();
$file = $fileLink?->file; $file = $fileLink?->file;
if (empty($file)) { if (empty($file)) {
return Base::retError('链接不存在'); return Base::retError('链接不存在');
} }
} else { } else {
User::auth(); return Base::retError('参数错误');
$id = intval(Request::input('id'));
$file = File::allowFind($id);
} }
return Base::retSuccess('success', $file); return Base::retSuccess('success', $file);
} }
@ -146,7 +155,7 @@ class FileController extends AbstractController
// //
if ($id > 0) { if ($id > 0) {
// 修改 // 修改
$file = File::allowFind($id, 1); $file = File::permissionFind($id, 1);
// //
$file->name = $name; $file->name = $name;
$file->save(); $file->save();
@ -180,7 +189,7 @@ class FileController extends AbstractController
if (File::wherePid($pid)->count() >= 300) { if (File::wherePid($pid)->count() >= 300) {
return Base::retError('每个文件夹里最多只能创建300个文件或文件夹'); return Base::retError('每个文件夹里最多只能创建300个文件或文件夹');
} }
$row = File::allowFind($pid, 1, '主文件不存在'); $row = File::permissionFind($pid, 1);
$userid = $row->userid; $userid = $row->userid;
} else { } else {
if (File::whereUserid($user->userid)->wherePid(0)->count() >= 300) { if (File::whereUserid($user->userid)->wherePid(0)->count() >= 300) {
@ -215,7 +224,7 @@ class FileController extends AbstractController
// //
$id = intval(Request::input('id')); $id = intval(Request::input('id'));
// //
$row = File::allowFind($id); $row = File::permissionFind($id);
// //
$userid = $user->userid; $userid = $user->userid;
if ($row->pid > 0) { if ($row->pid > 0) {
@ -257,7 +266,7 @@ class FileController extends AbstractController
$id = intval(Request::input('id')); $id = intval(Request::input('id'));
$pid = intval(Request::input('pid')); $pid = intval(Request::input('pid'));
// //
$file = File::allowFind($id, 1000); $file = File::permissionFind($id, 1000);
// //
if ($pid > 0) { if ($pid > 0) {
if (!File::whereUserid($user->userid)->whereId($pid)->exists()) { if (!File::whereUserid($user->userid)->whereId($pid)->exists()) {
@ -291,7 +300,7 @@ class FileController extends AbstractController
// //
$id = intval(Request::input('id')); $id = intval(Request::input('id'));
// //
$file = File::allowFind($id, 1000); $file = File::permissionFind($id, 1000);
// //
$file->deleteFile(); $file->deleteFile();
return Base::retSuccess('删除成功', $file); return Base::retSuccess('删除成功', $file);
@ -300,20 +309,25 @@ class FileController extends AbstractController
/** /**
* 获取文件内容 * 获取文件内容
* *
* @apiParam {String} [code] 链接码(用于预览) * @apiParam {Number|String} id
* @apiParam {Number} [id] 文件ID需要权限用于管理 * - Number 文件ID需要登录
* - String 链接码(不需要登录,用于预览)
*/ */
public function content() public function content()
{ {
if (Request::exists("code")) { $id = Request::input('id');
$fileLink = FileLink::whereCode(Request::input('code'))->first(); //
if (Base::isNumber($id)) {
User::auth();
$file = File::permissionFind(intval($id));
} elseif ($id) {
$fileLink = FileLink::whereCode($id)->first();
$file = $fileLink?->file; $file = $fileLink?->file;
if (empty($file)) { if (empty($file)) {
return Base::retError('链接不存在'); return Base::retError('链接不存在');
} }
} else { } else {
$id = intval(Request::input('id')); return Base::retError('参数错误');
$file = File::allowFind($id);
} }
// //
$content = FileContent::whereFid($file->id)->orderByDesc('id')->first(); $content = FileContent::whereFid($file->id)->orderByDesc('id')->first();
@ -334,7 +348,7 @@ class FileController extends AbstractController
$id = Base::getPostInt('id'); $id = Base::getPostInt('id');
$content = Base::getPostValue('content'); $content = Base::getPostValue('content');
// //
$file = File::allowFind($id, 1); $file = File::permissionFind($id, 1);
// //
$text = ''; $text = '';
if ($file->type == 'document') { if ($file->type == 'document') {
@ -387,7 +401,7 @@ class FileController extends AbstractController
$key = Request::input('key'); $key = Request::input('key');
$url = Request::input('url'); $url = Request::input('url');
// //
$file = File::allowFind($id, 1); $file = File::permissionFind($id, 1);
// //
if ($status === 2) { if ($status === 2) {
$parse = parse_url($url); $parse = parse_url($url);
@ -434,7 +448,7 @@ class FileController extends AbstractController
if (File::wherePid($pid)->count() >= 300) { if (File::wherePid($pid)->count() >= 300) {
return Base::retError('每个文件夹里最多只能创建300个文件或文件夹'); return Base::retError('每个文件夹里最多只能创建300个文件或文件夹');
} }
$row = File::allowFind($pid, 1, '主文件不存在'); $row = File::permissionFind($pid, 1);
$userid = $row->userid; $userid = $row->userid;
} else { } else {
if (File::whereUserid($user->userid)->wherePid(0)->count() >= 300) { if (File::whereUserid($user->userid)->wherePid(0)->count() >= 300) {
@ -618,7 +632,7 @@ class FileController extends AbstractController
// //
$id = intval(Request::input('id')); $id = intval(Request::input('id'));
// //
$file = File::allowFind($id); $file = File::permissionFind($id);
// //
if ($file->userid == $user->userid) { if ($file->userid == $user->userid) {
return Base::retError('不能退出自己共享的文件'); return Base::retError('不能退出自己共享的文件');
@ -653,7 +667,7 @@ class FileController extends AbstractController
$id = intval(Request::input('id')); $id = intval(Request::input('id'));
$refresh = Request::input('refresh', 'no'); $refresh = Request::input('refresh', 'no');
// //
$file = File::allowFind($id, 1000); $file = File::permissionFind($id, 1000);
if ($file->type == 'folder') { if ($file->type == 'folder') {
return Base::retError('文件夹暂不支持此功能'); return Base::retError('文件夹暂不支持此功能');
} }

View File

@ -53,30 +53,9 @@ class File extends AbstractModel
/** /**
* 是否有访问权限 * 是否有访问权限
* @param $userid * @param $userid
* @param int $permission 要求权限: 0-访问权限、1-读写权限、1000-所有者
*/
public function exceAllow($userid, $permission = 0)
{
if ($this->chackAllow($userid) < $permission) {
if ($permission == 1000) {
$msg = '仅限所有者操作';
} elseif ($permission == 1) {
$msg = '没有读写权限';
} else {
$msg = '没有访问权限';
}
throw new ApiException($msg);
}
}
/**
* 是否有访问权限
* 自己的文件夹
* 在指定共享成员内
* @param $userid
* @return int -1:没有权限0:访问权限1:读写权限1000:所有者 * @return int -1:没有权限0:访问权限1:读写权限1000:所有者
*/ */
public function chackAllow($userid) public function getPermission($userid)
{ {
if ($userid == $this->userid) { if ($userid == $this->userid) {
// ① 自己的文件夹 // ① 自己的文件夹
@ -238,17 +217,26 @@ class File extends AbstractModel
/** /**
* 获取文件并检测权限 * 获取文件并检测权限
* @param $id * @param $id
* @param int $permission 要求权限: 0-访问权限、1-读写权限、1000-所有者 * @param int $limit 要求权限: 0-访问权限、1-读写权限、1000-所有者
* @param null $noExistTis 文件不存在的描述 * @param $permission
* @return File * @return File
*/ */
public static function allowFind($id, $permission = 0, $noExistTis = null) public static function permissionFind($id, $limit = 0, &$permission = -1)
{ {
$file = File::find($id); $file = File::find($id);
if (empty($file)) { if (empty($file)) {
throw new ApiException($noExistTis ?: '文件不存在或已被删除'); throw new ApiException('文件不存在或已被删除');
}
//
$permission = $file->getPermission(User::userid());
if ($permission < $limit) {
$msg = match ($limit) {
1000 => '仅限所有者操作',
1 => '没有读写权限',
default => '没有访问权限',
};
throw new ApiException($msg);
} }
$file->exceAllow(User::userid(), $permission);
return $file; return $file;
} }
} }

View File

@ -46,15 +46,19 @@ export default {
return "office_" + Math.round(Math.random() * 10000); return "office_" + Math.round(Math.random() * 10000);
} }
}, },
code: {
type: String,
default: ''
},
value: { value: {
type: [Object, Array], type: [Object, Array],
default: function () { default: function () {
return {} return {}
} }
}, },
code: { readOnly: {
type: String, type: Boolean,
default: '' default: false
}, },
}, },
@ -80,18 +84,6 @@ export default {
computed: { computed: {
...mapState(['userToken', 'userInfo']), ...mapState(['userToken', 'userInfo']),
isPreview() {
return !!this.code
},
fileUrl() {
if (this.isPreview) {
return 'http://nginx/api/file/content/?code=' + this.code;
} else {
return 'http://nginx/api/file/content/?id=' + this.value.id + '&token=' + this.userToken;
}
},
fileType() { fileType() {
return this.getType(this.value.type); return this.getType(this.value.type);
}, },
@ -102,9 +94,9 @@ export default {
}, },
watch: { watch: {
fileUrl: { 'value.id': {
handler(url) { handler(id) {
if (!url) { if (!id) {
return; return;
} }
this.loadIng++; this.loadIng++;
@ -112,9 +104,9 @@ export default {
this.loadIng--; this.loadIng--;
if (e !== null) { if (e !== null) {
$A.modalAlert("组件加载失败!"); $A.modalAlert("组件加载失败!");
return; } else {
this.loadFile()
} }
this.loadFile()
}) })
}, },
immediate: true, immediate: true,
@ -135,9 +127,6 @@ export default {
}, },
loadFile() { loadFile() {
if (!this.fileUrl) {
return;
}
if (this.docEditor !== null) { if (this.docEditor !== null) {
this.docEditor.destroyEditor(); this.docEditor.destroyEditor();
this.docEditor = null; this.docEditor = null;
@ -154,12 +143,13 @@ export default {
break; break;
} }
// //
let fileKey = this.code || this.value.id;
const config = { const config = {
"document": { "document": {
"fileType": this.fileType, "fileType": this.fileType,
"key": this.fileType + '-' + this.value.id, "key": this.fileType + '-' + fileKey,
"title": this.fileName + '.' + this.fileType, "title": this.fileName + '.' + this.fileType,
"url": this.fileUrl, "url": 'http://nginx/api/file/content/?id=' + fileKey + '&token=' + this.userToken,
}, },
"editorConfig": { "editorConfig": {
"mode": "edit", "mode": "edit",
@ -171,7 +161,7 @@ export default {
"customization": { "customization": {
"uiTheme": "theme-classic-light", "uiTheme": "theme-classic-light",
}, },
"callbackUrl": 'http://nginx/api/file/content/office?id=' + this.value.id + '&token=' + this.userToken, "callbackUrl": 'http://nginx/api/file/content/office?id=' + fileKey + '&token=' + this.userToken,
} }
}; };
if (this.isPreview) { if (this.isPreview) {

View File

@ -111,7 +111,7 @@
type: Boolean, type: Boolean,
default: false default: false
}, },
readonly: { readOnly: {
type: Boolean, type: Boolean,
default: false default: false
}, },
@ -180,7 +180,7 @@
} }
} }
}, },
readonly(value) { readOnly(value) {
if (this.editor !== null) { if (this.editor !== null) {
if (value) { if (value) {
this.editor.setMode('readonly'); this.editor.setMode('readonly');
@ -317,7 +317,7 @@
editor.on('Init', (e) => { editor.on('Init', (e) => {
this.editorT = editor; this.editorT = editor;
this.editorT.setContent(this.content); this.editorT.setContent(this.content);
if (this.readonly) { if (this.readOnly) {
this.editorT.setMode('readonly'); this.editorT.setMode('readonly');
} else { } else {
this.editorT.setMode('design'); this.editorT.setMode('design');
@ -345,7 +345,7 @@
this.spinShow = false; this.spinShow = false;
this.editor = editor; this.editor = editor;
this.editor.setContent(this.content); this.editor.setContent(this.content);
if (this.readonly) { if (this.readOnly) {
this.editor.setMode('readonly'); this.editor.setMode('readonly');
} else { } else {
this.editor.setMode('design'); this.editor.setMode('design');

View File

@ -312,7 +312,8 @@ export default {
this.unsaveTip = false; this.unsaveTip = false;
}, },
formatName({name, ext}) { formatName(file) {
let {name, ext} = file;
if (ext != '') { if (ext != '') {
name += "." + ext; name += "." + ext;
} }

View File

@ -5,6 +5,7 @@
<div v-show="!['word', 'excel', 'ppt'].includes(file.type)" class="edit-header"> <div v-show="!['word', 'excel', 'ppt'].includes(file.type)" class="edit-header">
<div class="header-title"> <div class="header-title">
{{formatName(file)}} {{formatName(file)}}
<Tag color="default">{{$L('只读')}}</Tag>
</div> </div>
<Dropdown v-if="file.type=='mind' || file.type=='flow' || file.type=='sheet'" <Dropdown v-if="file.type=='mind' || file.type=='flow' || file.type=='sheet'"
trigger="click" trigger="click"
@ -26,12 +27,12 @@
<div v-if="contentDetail" class="content-body"> <div v-if="contentDetail" class="content-body">
<template v-if="file.type=='document'"> <template v-if="file.type=='document'">
<MDPreview v-if="contentDetail.type=='md'" :initialValue="contentDetail.content"/> <MDPreview v-if="contentDetail.type=='md'" :initialValue="contentDetail.content"/>
<TEditor v-else v-model="contentDetail.content" height="100%" readonly/> <TEditor v-else v-model="contentDetail.content" height="100%" readOnly/>
</template> </template>
<Flow v-else-if="file.type=='flow'" ref="myFlow" v-model="contentDetail" readOnly/> <Flow v-else-if="file.type=='flow'" ref="myFlow" v-model="contentDetail" readOnly/>
<Minder v-else-if="file.type=='mind'" ref="myMind" v-model="contentDetail" readOnly/> <Minder v-else-if="file.type=='mind'" ref="myMind" v-model="contentDetail" readOnly/>
<LuckySheet v-else-if="file.type=='sheet'" ref="mySheet" v-model="contentDetail" readOnly/> <LuckySheet v-else-if="file.type=='sheet'" ref="mySheet" v-model="contentDetail" readOnly/>
<OnlyOffice v-else-if="['word', 'excel', 'ppt'].includes(file.type)" v-model="contentDetail" :code="code"/> <OnlyOffice v-else-if="['word', 'excel', 'ppt'].includes(file.type)" v-model="contentDetail" :code="code" readOnly/>
</div> </div>
</template> </template>
<div v-if="loadContent > 0 || previewLoad" class="content-load"><Loading/></div> <div v-if="loadContent > 0 || previewLoad" class="content-load"><Loading/></div>
@ -82,9 +83,9 @@ export default {
}, },
watch: { watch: {
code: { 'file.id': {
handler(code) { handler(id) {
if (code) { if (id) {
this.contentDetail = null; this.contentDetail = null;
this.getContent(); this.getContent();
} }
@ -132,7 +133,7 @@ export default {
this.$store.dispatch("call", { this.$store.dispatch("call", {
url: 'file/content', url: 'file/content',
data: { data: {
code: this.code, id: this.code || this.file.id,
}, },
}).then(({data}) => { }).then(({data}) => {
this.loadIng--; this.loadIng--;
@ -161,7 +162,8 @@ export default {
} }
}, },
formatName({name, ext}) { formatName(file) {
let {name, ext} = file;
if (ext != '') { if (ext != '') {
name += "." + ext; name += "." + ext;
} }

View File

@ -28,6 +28,7 @@
<li v-else v-for="item in navigator" @click="pid=item.id"> <li v-else v-for="item in navigator" @click="pid=item.id">
<i v-if="item.share" class="taskfont">&#xe63f;</i> <i v-if="item.share" class="taskfont">&#xe63f;</i>
<span :title="item.name">{{item.name}}</span> <span :title="item.name">{{item.name}}</span>
<span v-if="item.share && item.permission == 0" class="readonly">{{$L('只读')}}</span>
</li> </li>
</ul> </ul>
<Button v-if="shearFile" :disabled="shearFile.pid == pid" size="small" type="primary" @click="shearTo"> <Button v-if="shearFile" :disabled="shearFile.pid == pid" size="small" type="primary" @click="shearTo">
@ -282,7 +283,8 @@
v-model="editShow" v-model="editShow"
class="page-file-drawer" class="page-file-drawer"
:mask-closable="false"> :mask-closable="false">
<FileContent v-model="editShow" :file="editInfo"/> <FileContent v-if="editInfo.permission > 0" v-model="editShow" :file="editInfo"/>
<FilePreview v-else-if="editInfo.permission > -1" :file="editInfo"/>
</DrawerOverlay> </DrawerOverlay>
</div> </div>
@ -298,11 +300,12 @@ import {sortBy} from "lodash";
import UserInput from "../../components/UserInput"; import UserInput from "../../components/UserInput";
import DrawerOverlay from "../../components/DrawerOverlay"; import DrawerOverlay from "../../components/DrawerOverlay";
const FilePreview = () => import('./components/FilePreview');
const FileContent = () => import('./components/FileContent'); const FileContent = () => import('./components/FileContent');
export default { export default {
components: {DrawerOverlay, UserInput, FileContent}, components: {FilePreview, DrawerOverlay, UserInput, FileContent},
data() { data() {
return { return {
loadIng: 0, loadIng: 0,
@ -382,7 +385,7 @@ export default {
linkLoad: 0, linkLoad: 0,
editShow: false, editShow: false,
editInfo: {}, editInfo: {permission: -1},
uploadDir: false, uploadDir: false,
uploadIng: 0, uploadIng: 0,
@ -475,7 +478,7 @@ export default {
let {pid, files} = this; let {pid, files} = this;
let array = []; let array = [];
while (pid > 0) { while (pid > 0) {
let file = files.find(({id, allow}) => id == pid && allow > -1); let file = files.find(({id, permission}) => id == pid && permission > -1);
if (file) { if (file) {
array.unshift(file); array.unshift(file);
pid = file.pid; pid = file.pid;
@ -674,7 +677,8 @@ export default {
] ]
}, },
formatName({name, ext}) { formatName(file) {
let {name, ext} = file;
if (ext != '') { if (ext != '') {
name += "." + ext; name += "." + ext;
} }

View File

@ -3,7 +3,7 @@
<PageTitle :title="fileInfo.name"/> <PageTitle :title="fileInfo.name"/>
<Loading v-if="loadIng > 0"/> <Loading v-if="loadIng > 0"/>
<template v-else> <template v-else>
<FilePreview v-if="fileCode" :code="fileCode" :file="fileInfo"/> <FilePreview v-if="code" :code="code" :file="fileInfo"/>
<FileContent v-else v-model="fileShow" :file="fileInfo"/> <FileContent v-else v-model="fileShow" :file="fileInfo"/>
</template> </template>
</div> </div>
@ -28,9 +28,10 @@ export default {
return { return {
loadIng: 0, loadIng: 0,
code: null,
fileShow: true, fileShow: true,
fileInfo: {}, fileInfo: {},
fileCode: null,
} }
}, },
mounted() { mounted() {
@ -47,13 +48,13 @@ export default {
methods: { methods: {
getInfo() { getInfo() {
let id = this.$route.params.id; let id = this.$route.params.id;
let data = {}; let data = {id};
if (id > 0) { if (/^\d+$/.test(id)) {
data.id = id; this.code = null;
this.fileCode = null; } else if (id) {
} else if (id != '') { this.code = id;
data.code = id; } else {
this.fileCode = id; return;
} }
this.loadIng++; this.loadIng++;
this.$store.dispatch("call", { this.$store.dispatch("call", {

View File

@ -54,6 +54,9 @@
color: #000000; color: #000000;
} }
} }
.ivu-tag {
margin-left: 4px;
}
} }
.header-user { .header-user {
margin-right: 24px; margin-right: 24px;

View File

@ -149,6 +149,17 @@
overflow: hidden; overflow: hidden;
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
&.readonly {
transform: scale(0.8);
transform-origin: right center;
border-radius: 2px;
line-height: 20px;
font-size: 12px;
padding: 0 5px;
color: #515a6e;
background: #f7f7f7;
border: 1px solid #e8eaec;
}
} }
} }
} }