Browse Source

change onlyoffice

master
godo 6 months ago
parent
commit
15fdc41182
  1. 121
      frontend/src/components/window/OnlyOffice.vue
  2. 18
      frontend/src/components/window/WindowInner.vue
  3. 2
      godo/cmd/main.go
  4. 134
      godo/files/onlyoffice.go
  5. 62
      godo/files/pwd.go

121
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>("system");
const win: any = inject<BrowserWindow>("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, // truemodeeditedit
"copy": true, //true
"download": true, //线falsetrue
"modifyContentControl": true, //modeedittrue
"modifyFilter": true, //truefalsemodeedittrue
}
},
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 = () => {

18
frontend/src/components/window/WindowInner.vue

@ -2,7 +2,7 @@
<template v-if="win.url">
<IframeFile v-if="config.editorType == 'local'" :src="win.url" :ext="win.ext" :eventType="win.eventType" />
<template v-if="officeFile">
<OnlyOffice v-if="config.editorType == 'onlyoffice'" :src="win.url" :eventType="win.eventType" :ext="ext" />
<OnlyOffice v-if="config.editorType == 'onlyoffice'" :onlyType="onlyType" :eventType="win.eventType" :ext="ext" />
</template>
<IframeFile v-else :src="win.url" :ext="win.ext" :eventType="win.eventType" />
</template>
@ -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
}
}

2
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)

134
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
}
}

62
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)
}

Loading…
Cancel
Save