Merge pull request #169 from jamebal/dev

perf: 适配 onlyoffice JWT
This commit is contained in:
jmal 2024-06-08 23:53:16 +08:00 committed by GitHub
commit 927dfe8a86
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 242 additions and 49 deletions

View File

@ -338,5 +338,22 @@ export default {
method: 'get',
params
})
}
},
// 获取office jwt
getOfficeJwt: function(data) {
return request({
url: '/office/signature',
method: 'post',
data
})
},
// 获取office jwt
getPublicOfficeJwt: function(params, data) {
return request({
url: '/public/office/signature',
method: 'post',
params,
data
})
},
}

View File

@ -83,6 +83,21 @@ export default {
url: '/user/setting/resetMenuAndRole',
method: 'put',
})
},
// 获取office配置
getOfficeConfig: function() {
return request({
url: '/office/config',
method: 'get'
})
},
// 更新office配置
updateOfficeConfig: function(data) {
return request({
url: '/office/config',
method: 'put',
data
})
}
}

View File

@ -2206,7 +2206,7 @@ export default {
},
extendedInfo(file) {
if (!file.exif && !file.video) {
return
return ''
}
return "\r\n" + formatExif(file.exif) + formatVideo(file.video)
},

View File

@ -22,6 +22,7 @@
<script>
import api from "@/api/file-api"
import settingApi from "@/api/setting-api"
import fileConfig from "@/utils/file-config";
import SparkMD5 from 'spark-md5'
import HistoryPopover from "@/components/HistoryPopover/index.vue";
@ -49,6 +50,10 @@ export default {
type: String,
default: ''
},
sharer: {
type: String,
default: undefined
},
file: {
type: Object,
default: function () {
@ -75,7 +80,12 @@ export default {
historyPageIndex: 1,
historyPageSize: 50,
viewHistory: false,
historyOperationLoading: false
historyOperationLoading: false,
officeServerConfig: {
documentServer: undefined,
callbackServer: undefined,
tokenEnabled: false
}
}
},
beforeDestroy() {
@ -93,24 +103,9 @@ export default {
if (!id) {
return
}
let officeApiUrl = fileConfig.officeApiUrl()
$J.loadScript($J.apiUrl(officeApiUrl), (e) => {
if (e !== null) {
this.$emit('onClose')
this.$store.dispatch('updateMessage', { event: 'loadFileFailed'})
return
}
if (this.shareId) {
api.getPublicFileInfoById({fileId: this.file.id, shareId: this.shareId}).then(res => {
this.file = res.data
this.loadFile()
})
} else {
api.getFileInfoById({id: this.file.id}).then(res => {
this.file = res.data
this.loadFile()
})
}
settingApi.getOfficeConfig().then(res => {
this.officeServerConfig = res.data
this.loadOfficeApi()
})
},
immediate: true,
@ -134,6 +129,27 @@ export default {
destroyed() {
},
methods: {
loadOfficeApi() {
let officeApiUrl = fileConfig.officeApiUrl(this.officeServerConfig.documentServer)
$J.loadScript($J.apiUrl(officeApiUrl), (e) => {
if (e !== null) {
this.$emit('onClose')
this.$store.dispatch('updateMessage', { event: 'loadFileFailed'})
return
}
if (this.shareId) {
api.getPublicFileInfoById({fileId: this.file.id, shareId: this.shareId}).then(res => {
this.file = res.data
this.loadFile()
})
} else {
api.getFileInfoById({id: this.file.id}).then(res => {
this.file = res.data
this.loadFile()
})
}
})
},
viewHistoryFile({historyInfo}) {
const historyUrl = window.location.origin + fileConfig.previewHistoryUrl(historyInfo.id, this.$store.state.user.name, this.$store.state.user.token)
this.onRequestHistoryData(null, historyInfo, historyUrl)
@ -188,11 +204,15 @@ export default {
this.docEditor = new DocsAPI.DocEditor(this.id, this.docEditorConfig)
},
onRequestHistoryData(event, historyInfo, url) {
this.docEditor.setHistoryData({
let historyConfig = {
"fileType": this.fileType,
"key": `${new Date(historyInfo.metadata.time).getTime()}-${SparkMD5.hash(this.file.id)}`,
"url": url,
"version": historyInfo.metadata.time,
}
api.getOfficeJwt(historyConfig).then(res => {
historyConfig.token = res.data
this.docEditor.setHistoryData(historyConfig)
})
},
onRequestHistory() {
@ -221,14 +241,25 @@ export default {
this.$refs.historyPopover.loadHistoryList(this.file.id)
})
this.destroyEditor()
this.fileUrl = window.location.origin + fileConfig.previewUrl(this.$store.state.user.name, this.file, this.$store.getters.token)
if (this.readOnly && window.shareId) {
this.fileUrl = window.location.origin + fileConfig.publicPreviewUrl(this.file, window.shareId, this.$store.getters.shareToken)
const file_username = this.sharer ? this.sharer : this.$store.state.user.name
let callbackServer = this.officeServerConfig.callbackServer
if (callbackServer) {
if (callbackServer.endsWith('/')) {
// 去掉最后的/
callbackServer = callbackServer.substring(0, this.officeServerConfig.callbackServer.length - 1)
}
} else {
callbackServer = window.location.origin
}
this.fileKey = `${new Date(this.file.updateDate).getTime()}-${SparkMD5.hash(this.file.id)}`
let callbackUrl = fileConfig.officeCallBackUrl(this.$store.getters.token, this.$store.getters.name, this.file.id)
this.fileUrl = fileConfig.previewUrl(file_username, this.file, this.$store.getters.token, undefined, callbackServer)
this.fileKey = `${new Date(this.file.updateDate).getTime()}-${SparkMD5.hash(this.file.id + callbackServer)}`
if (this.readOnly && window.shareId) {
this.fileUrl = fileConfig.publicPreviewUrl(this.file, window.shareId, this.$store.getters.shareToken, callbackServer)
this.fileKey = `${new Date(this.file.updateDate).getTime()}-${SparkMD5.hash(window.shareId + callbackServer)}`
}
let callbackUrl = fileConfig.officeCallBackUrl(this.officeServerConfig.callbackServer, this.$store.getters.token, this.$store.getters.name, this.file.id)
this.docEditorConfig = {
document: {
fileType: this.fileType,
@ -261,6 +292,28 @@ export default {
callbackUrl: callbackUrl,
}
}
if (this.readOnly && window.shareId) {
const shareToken = this.$store.getters.shareToken ? this.$store.getters.shareToken : 'token'
this.docEditorConfig.editorConfig.callbackUrl = null
api.getPublicOfficeJwt({shareId: window.shareId, shareToken: shareToken}, this.docEditorConfig).then(res => {
this.loadOffice(res.data)
})
} else {
if (this.officeServerConfig.tokenEnabled) {
api.getOfficeJwt(this.docEditorConfig).then(res => {
this.loadOffice(res.data)
})
} else {
this.loadOffice()
}
}
},
loadOffice(token) {
if (token) {
this.docEditorConfig.token = token
}
if (!this.$pc) {
this.docEditorConfig.type = 'mobile'
}
@ -277,6 +330,7 @@ export default {
this.docEditorConfig.editorConfig.user.name = "Visitor_" + visitor
}
}
this.docEditorConfig.events = {
"onAppReady": this.onAppReady,
"onDocumentReady": this.onDocumentReady,
@ -317,6 +371,7 @@ export default {
if (this.hasHistoryVersion && !doc.getElementById('box-doc-name-history-btn')) {
const toolbar = doc.getElementById('box-doc-name')
toolbar.style.justifyContent = 'right'
toolbar.style.padding = '0'
let versionButton = document.createElement("button")
versionButton.classList.add('btn', 'btn-header')
versionButton.setAttribute('id', 'box-doc-name-history-btn')

View File

@ -19,7 +19,7 @@
<pdf-preview v-if="fileType === 'pdf'" :file="file" :shareId="shareId" :file-url="fileUrl" @onReady="onReady"></pdf-preview>
<drawio v-else-if="fileType === 'drawio'" v-show="fileReday" :file="file" :shareId="shareId" :read-only="readOnly" @onEdit="onEdit" @onReady="onReady" @onClose="close"></drawio>
<my-mind-editor v-else-if="fileType === 'mind'" :file="file" :shareId="shareId" :read-only="readOnly" @onEdit="onEdit" @onReady="onReady" @onClose="close"></my-mind-editor>
<only-office-editor ref="officeEditor" v-else :file="file" :file-url="fileUrl" :shareId="shareId" :read-only="readOnly" @onEdit="onEdit" @manualSave="manualSave" @onClose="close" @onReady="onReady"></only-office-editor>
<only-office-editor ref="officeEditor" v-else :file="file" :file-url="fileUrl" :shareId="shareId" :sharer="sharer" :read-only="readOnly" @onEdit="onEdit" @manualSave="manualSave" @onClose="close" @onReady="onReady"></only-office-editor>
</div>
</div>
</div>
@ -30,7 +30,6 @@
import OnlyOfficeEditor from "@/components/office/OnlyOfficeEditor";
import MessageDialog from "@/components/message/MessageDialog";
import Bus from "@/assets/js/bus";
import fileConfig from "@/utils/file-config";
import PdfPreview from "@/components/office/PdfPreview";
import Drawio from "@/components/office/Drawio";
@ -56,6 +55,10 @@ export default {
type: String,
default: undefined
},
sharer: {
type: String,
default: undefined
},
status: {
type: Boolean,
default: false
@ -207,7 +210,7 @@ export default {
this.$message.info('请手动保存')
},
/**
* 检查要预览调文件是否为只读
* 检查要预览的文件是否为只读
* @param file userId
*/
checkReadOnly(file){

View File

@ -12,17 +12,23 @@ export default {
return window.location.origin + `/webDAV/${username}`
},
// office api url
officeApiUrl: function() {
const url = window._env_.API_OFFICE_URL || process.env.API_OFFICE_URL || `${window.location.origin}/office`
return `${url}/web-apps/apps/api/documents/api.js`
officeApiUrl: function(documentServer) {
let serverUrl = documentServer || window._env_.API_URL || process.env.API_URL || `${window.location.origin}/office`
if (!serverUrl.endsWith('/')) {
serverUrl = serverUrl + "/"
}
return serverUrl + "web-apps/apps/api/documents/api.js"
},
// office回调url
officeCallBackUrl: function(token, username, fileId) {
const url = window._env_.API_URL || process.env.API_URL || 'http://jmalcloud:8088'
return `${url}/office/track?jmal-token=${token}&name=${username}&fileId=${fileId}`
officeCallBackUrl: function(callbackServer, token, username, fileId) {
let callbackServerUrl = callbackServer || window._env_.API_URL || process.env.API_URL || 'http://jmalcloud:8088'
if (!callbackServerUrl.endsWith('/')) {
callbackServerUrl = callbackServerUrl + "/"
}
return `${callbackServerUrl}office/track?jmal-token=${token}&name=${username}&fileId=${fileId}`
},
// 预览文件的url
previewUrl: function(username, file, token, shareToken) {
previewUrl: function(username, file, token, shareToken, serverUrl) {
let owner = null
if (username !== store.getters.name || localStorage.getItem('mountFileOwner') !== null) {
owner = localStorage.getItem('mountFileOwner')
@ -32,7 +38,8 @@ export default {
if (owner == null) {
owner = store.getters.name
}
let fileUrl = `${this.baseUrl}/file/${owner}${encodeURI(file.path)}${encodeURI(file.name)}`
let baseUrl = serverUrl || this.baseUrl
let fileUrl = `${baseUrl}/file/${owner}${encodeURI(file.path)}${encodeURI(file.name)}`
fileUrl = fileUrl.replace(/%5C/g, '/')
if (token) {
return `${fileUrl}?jmal-token=${token}&name=${username}`
@ -80,11 +87,12 @@ export default {
window.open(url, '_blank')
},
// 共享文件预览Url
publicPreviewUrl: function(file, shareId, shareToken) {
publicPreviewUrl: function(file, shareId, shareToken, serverUrl) {
if (!shareToken) {
shareToken = "none"
}
return `${this.baseUrl}/public/s/preview/${file.name}?fileId=${file.id}&shareId=${shareId}&shareToken=${shareToken}`
let baseUrl = serverUrl || this.baseUrl
return `${baseUrl}/public/s/preview/${file.name}?fileId=${file.id}&shareId=${shareId}&shareToken=${shareToken}`
},
// 共享文件打包下载
publicPackageDownload: function(shareId, fileIds, shareToken) {

View File

@ -255,7 +255,7 @@
<image-viewer :fileList="fileList" :shareId="shareId" :file="imagePreviewRow"
:status.sync="imagePreviewVisible"></image-viewer>
<video-preview :file="videoPreviewRow" :shareId="shareId" :status.sync="videoPreviewVisible"></video-preview>
<office-preview :file="officePreviewRow" :shareId="shareId" :status.sync="officePreviewVisible"></office-preview>
<office-preview :file="officePreviewRow" :shareId="shareId" :sharer="sharerUsername" :status.sync="officePreviewVisible"></office-preview>
<el-divider v-if="!linkFailed && !showShareCode" class="grid-divider" content-position="center"><i
class="el-icon-folder-opened"></i>&nbsp;{{ summaries }}
</el-divider>
@ -418,6 +418,7 @@ export default {
showUpdateDateItem: this.$pc,// 列表模式下是否显示修改时间
showSizeItem: this.$pc,// 列表模式下是否显示文件大小
sharer: undefined,//分享者信息,
sharerUsername: undefined,
loginTitle: '',
sharerAvatarUrl: '',
netdiskName: 'JmalCloud',
@ -698,6 +699,7 @@ export default {
if (this.sharer.netdiskName) {
this.netdiskName = this.sharer.netdiskName
}
this.sharerUsername = this.sharer.username
if (this.sharer.netdiskLogo) {
this.netdiskLogo = this.sharer.netdiskLogo
}

View File

@ -65,13 +65,18 @@
</div>
</el-tab-pane>
<el-tab-pane label="OnlyOffice" name="2" class="setting-tab-panel">
<div v-if="activeName === '2'">
<office-config></office-config>
</div>
</el-tab-pane>
<el-tab-pane label="视频转码" name="3" class="setting-tab-panel">
<div v-if="activeName === '3'">
<transcode-config></transcode-config>
</div>
</el-tab-pane>
<el-tab-pane label="LDAP认证" name="2" class="setting-tab-panel">
<div v-if="activeName === '2'">
<el-tab-pane label="LDAP认证" name="4" class="setting-tab-panel">
<div v-if="activeName === '4'">
<ldap-config></ldap-config>
</div>
</el-tab-pane>
@ -91,11 +96,12 @@ import UploadImageInput from "@/components/input/UploadImageInput.vue";
import {mapGetters, mapState} from "vuex";
import store from "@/store";
import TaskProgress from "@/components/TaskProgress/index.vue";
import LdapConfig from "@/views/setting/ldapConfig.vue";
import TranscodeConfig from "@/views/setting/transcodeConfig.vue";
import LdapConfig from "@/views/setting/cloudSetting/ldapConfig.vue";
import TranscodeConfig from "@/views/setting/cloudSetting/transcodeConfig.vue";
import OfficeConfig from "@/views/setting/cloudSetting/officeConfig.vue";
export default {
components: {TranscodeConfig, LdapConfig, TaskProgress, UploadImageInput, Logo},
components: {OfficeConfig, TranscodeConfig, LdapConfig, TaskProgress, UploadImageInput, Logo},
data() {
return {
activeName: '1',

View File

@ -0,0 +1,83 @@
<template>
<div class="container">
<el-form :rules="rules" ref="officeConfigForm" :model="configFormData" label-width="150px" size="small"
style="width: 520px" autocomplete="off">
<el-form-item label="OnlyOffice地址" prop="documentServer">
<el-input placeholder="OnlyOffice Server 地址, 一般不用填" v-model.number="configFormData.documentServer"></el-input>
</el-form-item>
<el-form-item label="密钥" prop="secret">
<el-input type="password" placeholder="OnlyOffice JWT (留空为不使用密钥)" v-model="configFormData.secret"></el-input>
</el-form-item>
<el-form-item label="回调服务地址" prop="callbackServer">
<el-input placeholder="JmalCloud Server 地址, 一般不用填" v-model.number="configFormData.callbackServer"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" v-loading="saveConfigLoading" @click="saveConfig">保存配置</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
import settingApi from "@/api/setting-api";
export default {
name: 'OfficeConfig',
props: {
data: {
type: Array,
default: []
},
},
computed: {
},
data() {
return {
saveConfigLoading: false,
configFormData: {
documentServer: undefined,
secret: undefined,
tokenEnabled: false,
callbackServer: undefined
},
rules: {
// documentServer: [
// {required: true, message: '请输入文档服务器地址', trigger: 'blur'}
// ]
}
}
},
mounted() {
this.getOfficeConfig()
},
methods: {
getOfficeConfig() {
settingApi.getOfficeConfig().then(res => {
this.configFormData = res.data
})
},
saveConfig() {
this.saveConfigLoading = true
if (this.configFormData.documentServer === '') {
this.configFormData.documentServer = undefined
}
if (this.configFormData.callbackServer === '') {
this.configFormData.callbackServer = undefined
}
settingApi.updateOfficeConfig(this.configFormData).then(() => {
this.saveConfigLoading = false
this.$message.success('保存成功')
}).catch(() => {
this.saveConfigLoading = false
})
}
}
}
</script>
<style lang="scss" scoped>
.container {
padding: 0 !important;
}
</style>

View File

@ -1,6 +1,6 @@
<template>
<div class="container">
<el-form :rules="rules" ref="transcodeConfigform" :model="configFormData" label-width="120px" size="small"
<el-form :rules="rules" ref="transcodeConfigForm" :model="configFormData" label-width="120px" size="small"
style="width: 520px" autocomplete="off">
<el-form-item label="功能状态" prop="enable">
<el-select v-model="configFormData.enable">
@ -74,8 +74,12 @@ export default {
})
},
saveConfig() {
this.saveConfigLoading = true
settingApi.setTranscodeConfig(this.configFormData).then(() => {
this.saveConfigLoading = false
this.$message.success('保存成功')
}).catch(() => {
this.saveConfigLoading = false
})
}
}