From 15fdc4118252da51f9d867525871d10fbe171c93 Mon Sep 17 00:00:00 2001 From: godo Date: Thu, 12 Dec 2024 15:58:27 +0800 Subject: [PATCH] change onlyoffice --- frontend/src/components/window/OnlyOffice.vue | 121 ++++++++-------- .../src/components/window/WindowInner.vue | 18 ++- godo/cmd/main.go | 2 +- godo/files/onlyoffice.go | 134 ++++++++++++++++++ godo/files/pwd.go | 62 ++------ 5 files changed, 219 insertions(+), 118 deletions(-) create mode 100644 godo/files/onlyoffice.go diff --git a/frontend/src/components/window/OnlyOffice.vue b/frontend/src/components/window/OnlyOffice.vue index ab13431..c7bdb56 100644 --- a/frontend/src/components/window/OnlyOffice.vue +++ b/frontend/src/components/window/OnlyOffice.vue @@ -8,14 +8,13 @@ import { DocumentEditor } from "@onlyoffice/document-editor-vue"; import { getSystemConfig, getSplit } from "@/system/config"; import { BrowserWindow, Dialog, Notify, System } from "@/system"; import { ref, onMounted, inject } from "vue"; -import { url } from "inspector"; const config = getSystemConfig(); const sys: any = inject("system"); const win: any = inject("browserWindow"); const props = defineProps({ - src: { + onlyType: { type: String, default: "", }, @@ -29,79 +28,79 @@ const props = defineProps({ }, }); -const editorConfig:any = ref({}); +const editorConfig: any = ref({}); const SP = getSplit(); -const getBlobUrl = (content: any) => { - let blobUrl: string; - if (!content || content === "") { - const emptyBlob = new Blob([], { type: 'application/octet-stream' }); - blobUrl = URL.createObjectURL(emptyBlob); - } else { - try { - // 将 base64 转换为 Blob URL - const byteCharacters = atob(content); - const byteNumbers = new Array(byteCharacters.length); - for (let i = 0; i < byteCharacters.length; i++) { - byteNumbers[i] = byteCharacters.charCodeAt(i); - } - const byteArray = new Uint8Array(byteNumbers); - const blob = new Blob([byteArray], { type: 'application/octet-stream' }); - blobUrl = URL.createObjectURL(blob); - } catch (error) { - console.error("Failed to decode base64 content:", error); - const emptyBlob = new Blob([], { type: 'application/octet-stream' }); - blobUrl = URL.createObjectURL(emptyBlob); - } - } - return blobUrl; -} - -onMounted(() => { +onMounted(async () => { const path = win?.config?.path; - const title = path.split(SP).pop(); - let content = win?.config?.content; - // console.log("Path:", path); - // console.log("Title:", title); - // console.log("Content:", content); - // console.log("ext:",props.ext) - const blobUrl = getBlobUrl(content); - //console.log("Blob URL:", blobUrl); - //console.log("onlyoffice:", config.apiUrl + "/file/onlyoffice"); + let title = "未命名文档" + if (path != "") { + title = path.split(SP).pop(); + } + const fileKey = "docx" + Math.random() editorConfig.value = { document: { fileType: props.ext, // 根据文件扩展名动态设置 fileType - key: "docx" + Math.random(), - + key:fileKey, title: title, - url: blobUrl - //url: config.storenet.url + "/file/readfile?stream=1&path="+path, + //url: blobUrl + url: config.storenet.url + "/file/readfile?stream="+fileKey+"&path=" + path, + "info": { + "owner": "王重阳", + }, + token: config.onlyoffice.sceret, + //文档权限参数 + "permissions": { + "edit": true, //(文件是否可以编辑,false时文件不可编辑) + "fillForms": true, //定义是否能在文档中填充表单 + "print": true, //定义文档是否能打印 + "review": false, //第一是否显示审阅文档菜单 + "comment": true, //定义是否可以注释文档。如果注释权限设置为“ true”,则文档侧栏将包含“注释”菜单选项;只有将mode参数设置为edit时才生效,默认值与edit参数的值一致。 + "copy": true, //是否允许您将内容复制到剪贴板。默认值为true。 + "download": true, //定义是否可以下载文档或仅在线查看或编辑文档。如果下载权限设置为“false”下载为菜单选项将没有。默认值为true。 + "modifyContentControl": true, //定义是否可以更改内容控件设置。仅当mode参数设置为edit时,内容控件修改才可用于文档编辑器。默认值为true。 + "modifyFilter": true, //定义过滤器是否可以全局应用(true)影响所有其他用户,或局部应用(false),即仅适用于当前用户。如果将mode参数设置为edit,则过滤器修改仅对电子表格编辑器可用。默认值为true。 + } }, - documentType: "word", + documentType: props.onlyType, editorConfig: { lang: "zh", - //createUrl:config.apiUrl + "file/writefile?path="+path, - callbackUrl: config.storenet.url + "/file/onlyoffice", - }, - events: { - onError: (e:any) =>{ - console.log("onError:", e); - }, - onDocumentStateChange: (e:any) =>{ - console.log("onDocumentStateChange:", e); - }, - onDocumentReady: (e:any) =>{ - console.log("onDocumentReady:", e); - }, - onRequestViewRights: (e:any) =>{ - console.log("onRequestViewRights:", e); + callbackUrl: config.storenet.url + "/file/onlyofficecallback", + "canCoAuthoring": true, + "canHistoryClose": true, + "canHistoryRestore": true, + "canMakeActionLink": true, + "canRename": true, + "canRequestClose": true, + "canRequestCompareFile": true, + "canRequestCreateNew": true, + "canRequestEditRights": true, + "canRequestInsertImage": true, + "canRequestMailMergeRecipients": true, + "canRequestOpen": true, + "canRequestReferenceData": true, + "canRequestReferenceSource": true, + "canRequestSaveAs": true, + "canRequestSelectDocument": true, + "canRequestSelectSpreadsheet": true, + "canRequestSendNotify": true, + "canRequestSharingSettings": true, + "canRequestUsers": true, + "canSaveDocumentToBinary": true, + "canSendEmailAddresses": true, + "canStartFilling": true, + "canUseHistory": true, + "user": { //用户信息 + "id": "godoos", //用户ID + "name": "写作员" //用户全名称 }, - onSave: (e:any)=>{ - console.log("onSave:", e); - } + }, + "events": { + } } + console.log(editorConfig.value) }) const onDocumentReady = () => { diff --git a/frontend/src/components/window/WindowInner.vue b/frontend/src/components/window/WindowInner.vue index d8167e2..7916a3c 100644 --- a/frontend/src/components/window/WindowInner.vue +++ b/frontend/src/components/window/WindowInner.vue @@ -2,7 +2,7 @@ @@ -25,7 +25,9 @@ const props = defineProps<{ }>(); let ext = "txt" +let onlyType = ref("word") const win = ref(props.win) +//console.log(win) const officeFile = ref(false) if (win.value.config.path) { ext = win.value.config.path.split('.').pop() @@ -39,9 +41,19 @@ function isOffice(ext: string) { const word = ['doc', 'docm', 'docx', 'docxf', 'dot', 'dotm', 'dotx', 'epub', 'fodt', 'fb2', 'mht', 'odt', 'oform', 'ott', 'oxps', 'pdf', 'rtf', 'txt', 'djvu', 'xml', 'xps']; const cell = ['csv', 'fods', 'ods', 'ots', 'xls', 'xlsb', 'xlsm', 'xlsx', 'xlt', 'xltm', 'xltx']; const slide = ['fodp', 'odp', 'otp', 'pot', 'potm', 'potx', 'pps', 'ppsm', 'ppsx', 'ppt', 'pptm', 'pptx'] - if (word.includes(ext) || cell.includes(ext) || slide.includes(ext)) { + if (word.includes(ext)) { + onlyType.value = 'word' return true - } else { + } + else if(cell.includes(ext)){ + onlyType.value = 'cell' + return true + } + else if(slide.includes(ext)){ + onlyType.value = 'slide' + return true + } + else { return false } } diff --git a/godo/cmd/main.go b/godo/cmd/main.go index adce2dc..fdd0bcf 100644 --- a/godo/cmd/main.go +++ b/godo/cmd/main.go @@ -104,7 +104,7 @@ func OsStart() { fileRouter.HandleFunc("/unzip", files.HandleUnZip).Methods(http.MethodGet) fileRouter.HandleFunc("/watch", files.WatchHandler).Methods(http.MethodGet) fileRouter.HandleFunc("/setfilepwd", files.HandleSetFilePwd).Methods(http.MethodGet) - fileRouter.HandleFunc("/onlyoffice", files.OnlyOfficeCallbackHandler).Methods(http.MethodPost) + fileRouter.HandleFunc("/onlyofficecallback", files.OnlyOfficeCallbackHandler).Methods(http.MethodPost) localchatRouter := router.PathPrefix("/localchat").Subrouter() localchatRouter.HandleFunc("/message", localchat.HandleMessage).Methods(http.MethodPost) diff --git a/godo/files/onlyoffice.go b/godo/files/onlyoffice.go new file mode 100644 index 0000000..a216e26 --- /dev/null +++ b/godo/files/onlyoffice.go @@ -0,0 +1,134 @@ +package files + +import ( + "encoding/json" + "fmt" + "godo/libs" + "io" + "log" + "net/http" + "os" + "path/filepath" +) + +// 定义结构体来匹配 JSON 数据结构 +type CallbackData struct { + Key string `json:"key"` + Status int `json:"status"` + URL string `json:"url"` + ChangesURL string `json:"changesurl"` + History struct { + ServerVersion string `json:"serverVersion"` + Changes []struct { + DocumentSHA256 string `json:"documentSha256"` + Created string `json:"created"` + User struct { + ID string `json:"id"` + Name string `json:"name"` + } `json:"user"` + } `json:"changes"` + } `json:"history"` + Users []string `json:"users"` + Actions []struct { + Type int `json:"type"` + UserID string `json:"userid"` + } `json:"actions"` + LastSave string `json:"lastsave"` + NotModified bool `json:"notmodified"` + FileType string `json:"filetype"` +} + +// 定义 OnlyOffice 预期的响应结构 +type OnlyOfficeResponse struct { + Error int `json:"error"` +} + +// OnlyOffice 回调处理函数 +func OnlyOfficeCallbackHandler(w http.ResponseWriter, r *http.Request) { + // 读取请求体 + body, err := io.ReadAll(r.Body) + if err != nil { + http.Error(w, "Failed to read request body", http.StatusBadRequest) + return + } + defer r.Body.Close() + + // 打印原始请求体 + fmt.Printf("Received raw data: %s\n", body) + + // 解析 JSON 数据 + var callbackData CallbackData + err = json.Unmarshal(body, &callbackData) + if err != nil { + http.Error(w, "Failed to decode request body", http.StatusBadRequest) + return + } + + // 打印解析后的回调数据 + fmt.Printf("Received callback: %+v\n", callbackData) + + // 使用 key 查找对应的 path + mapOnlyOfficeMutex.Lock() + path, exists := OnlyOfficekeyPathMap[callbackData.Key] + if exists { + delete(OnlyOfficekeyPathMap, callbackData.Key) // 删除已使用的 key + if callbackData.Status == 2 { + err := downloadFile(path, callbackData.URL) + log.Printf("Download file error: %v", err) + if err != nil { + http.Error(w, fmt.Sprintf("Failed to download file: %v", err), http.StatusInternalServerError) + return + } + } + } + mapOnlyOfficeMutex.Unlock() + // 构造响应 + response := OnlyOfficeResponse{ + Error: 0, + } + + // 设置响应头为 JSON 格式 + w.Header().Set("Content-Type", "application/json") + + // 返回 JSON 响应 + json.NewEncoder(w).Encode(response) +} + +// 下载文件 +func downloadFile(filePath string, url string) error { + resp, err := http.Get(url) + if err != nil { + return err + } + defer resp.Body.Close() + basePath, err := libs.GetOsDir() + if err != nil { + return err + } + fullFilePath := filepath.Join(basePath, filePath) + // 使用 os.OpenFile 创建或截断文件 + out, err := os.OpenFile(fullFilePath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666) + if err != nil { + return err + } + defer out.Close() + // 获取文件密钥 + fileSecret := libs.GetConfigString("filePwd") + if fileSecret != "" { + // 读取文件内容到内存中 + fileBytes, err := io.ReadAll(resp.Body) + if err != nil { + return err + } + encryData, err := libs.EncodeFile(fileSecret, string(fileBytes)) + if err != nil { + return err + } + _, err = out.Write([]byte(encryData)) + return err + } else { + _, err = io.Copy(out, resp.Body) + return err + } + +} diff --git a/godo/files/pwd.go b/godo/files/pwd.go index a48f744..38a4a93 100644 --- a/godo/files/pwd.go +++ b/godo/files/pwd.go @@ -2,7 +2,6 @@ package files import ( "encoding/base64" - "encoding/json" "fmt" "godo/libs" "io" @@ -11,10 +10,16 @@ import ( "os" "path/filepath" "strings" + "sync" ) +// 全局变量来存储 key 和 path 的映射关系 +var OnlyOfficekeyPathMap = make(map[string]string) +var mapOnlyOfficeMutex sync.Mutex + func HandleReadFile(w http.ResponseWriter, r *http.Request) { path := r.URL.Query().Get("path") + //log.Printf("read file: %s", path) if path == "" { libs.ErrorMsg(w, "文件路径不能为空") return @@ -23,6 +28,9 @@ func HandleReadFile(w http.ResponseWriter, r *http.Request) { isStream := false if stream != "" { isStream = true + mapOnlyOfficeMutex.Lock() + OnlyOfficekeyPathMap[stream] = path + mapOnlyOfficeMutex.Unlock() } basePath, err := libs.GetOsDir() @@ -259,55 +267,3 @@ func HandleSetFilePwd(w http.ResponseWriter, r *http.Request) { } libs.SuccessMsg(w, nil, "设置密码成功") } - -// 定义结构体来匹配 JSON 数据结构 -type CallbackData struct { - Key string `json:"key"` - Status int `json:"status"` - Users []string `json:"users"` - Actions []struct { - Type int `json:"type"` - UserID string `json:"userid"` - } `json:"actions"` -} - -// 定义 OnlyOffice 预期的响应结构 -type OnlyOfficeResponse struct { - Error int `json:"error"` -} - -func OnlyOfficeCallbackHandler(w http.ResponseWriter, r *http.Request) { - // 读取请求体 - body, err := io.ReadAll(r.Body) - if err != nil { - http.Error(w, "Failed to read request body", http.StatusBadRequest) - return - } - defer r.Body.Close() - - // 打印原始请求体 - fmt.Printf("Received raw data: %s\n", body) - - // 解析 JSON 数据 - // var callbackData CallbackData - // err = json.Unmarshal(body, &callbackData) - // if err != nil { - // http.Error(w, "Failed to decode request body", http.StatusBadRequest) - // return - // } - - // // 打印解析后的回调数据 - // fmt.Printf("Received callback: %+v\n", callbackData) - - // 构造响应 - response := OnlyOfficeResponse{ - Error: 0, - } - - // 设置响应头为 JSON 格式 - w.Header().Set("Content-Type", "application/json") - - // 返回 JSON 响应 - json.NewEncoder(w).Encode(response) - -}