This commit is contained in:
zzs 2025-02-20 17:16:53 +08:00
parent 6109b6aea4
commit 098baa995a
6 changed files with 350 additions and 21 deletions

12
pom.xml
View File

@ -32,12 +32,6 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
</properties>
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
@ -59,12 +53,6 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- <dependency>-->
<!-- <groupId>com.github.wb04307201</groupId>-->
<!-- <artifactId>file-preview-spring-boot-starter</artifactId>-->
<!-- <version>1.2.10</version>-->
<!-- </dependency>-->
</dependencies>
<build>

View File

@ -1,8 +1,9 @@
package vin.vio.collaboraonline;
import cn.wubo.file.preview.EnableFilePreview;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.filter.CommonsRequestLoggingFilter;
@SpringBootApplication
public class CollaboraOnlineApplication {
@ -10,5 +11,4 @@ public class CollaboraOnlineApplication {
public static void main(String[] args) {
SpringApplication.run(CollaboraOnlineApplication.class, args);
}
}

View File

@ -0,0 +1,115 @@
package vin.vio.collaboraonline.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import vin.vio.collaboraonline.model.WopiFile;
import vin.vio.collaboraonline.service.FileService;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
/**
* @Description: TODO
* @Date: 2025/2/19 16:49
* @Created: by ZZSLL
*/
@Slf4j
@RestController
@RequestMapping("/wopi")
public class WopiController {
@Autowired
private FileService fileService;
// 检查文件信息
@GetMapping("/files/{fileId}")
public ResponseEntity<?> checkFileInfo(@PathVariable String fileId) {
WopiFile file = fileService.getFileInfo(fileId);
if (file == null) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(createCheckFileInfoResponse(file));
}
// 获取文件内容
@GetMapping("/files/{fileId}/contents")
public ResponseEntity<Resource> getFile(@PathVariable String fileId) {
org.springframework.core.io.Resource fileResource = fileService.loadFileAsResource(fileId);
if (fileResource == null) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok()
.header("Content-Type", "application/octet-stream")
.body(fileResource);
}
// 保存文件
@RequestMapping(value = "/files/{fileId}/contents", method = {RequestMethod.POST, RequestMethod.PUT})
public ResponseEntity<Void> putFile(
@PathVariable String fileId, InputStream contentStream) {
boolean success = fileService.saveFile(fileId, contentStream);
return success ?
ResponseEntity.ok().build() :
ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
private Map<String, Object> createCheckFileInfoResponse(WopiFile file) {
Map<String, Object> response = new LinkedHashMap<>();
// 强制开启所有编辑权限
response.put("BaseFileName", file.getFileName());
response.put("Size", file.getSize());
response.put("OwnerId", "test_admin"); // 固定测试账户
response.put("UserId", "test_user"); // 固定测试用户
// 关键权限开关组
response.put("UserCanWrite", true); // 强制可写
response.put("UserCanNotWriteRelative", false);
response.put("SupportsLocks", false); // 禁用文件锁检测
response.put("SupportsUpdate", true);
response.put("EnableOwnerTermination", true); // 允许强制终止锁
// 关闭所有安全限制
response.put("IsAnonymousUser", true); // 允许匿名访问
response.put("ProtectInClient", false); // 禁用客户端保护
response.put("DisablePrint", false);
response.put("DisableExport", false);
response.put("DisableCopy", false);
// 添加必要协议字段
response.put("HostEditUrl", "http://192.168.3.104:8080/wopi/files/"+file.getFileName());
response.put("PostMessageOrigin", "http://192.168.3.104:8080"); // 本地测试用
// 测试环境特殊配置
response.put("BreadcrumbBrandName", "DEV ENV");
response.put("BreadcrumbBrandUrl", "http://192.168.3.104:8080");
response.put("BreadcrumbDocName", "[测试模式] "+file.getFileName());
// 绕过版本检查
// response.put("LastModifiedTime", "2020-01-01T00:00:00Z");
// response.put("Version", "test_version");
return response;
}
}

View File

@ -0,0 +1,100 @@
package vin.vio.collaboraonline.model;
import lombok.Data;
import java.nio.file.Path;
/**
* @Description: TODO
* @Date: 2025/2/19 16:56
* @Created: by ZZSLL
*/
public class WopiFile {
private String fileId;
private String fileName;
private long size;
private String ownerId;
private Path filePath;
private boolean userCanWrite;
private boolean supportsLocks;
private String contentType;
public WopiFile() {
}
public WopiFile(String fileId, String fileName, long size, String ownerId, Path filePath, boolean userCanWrite, boolean supportsLocks, String contentType) {
this.fileId = fileId;
this.fileName = fileName;
this.size = size;
this.ownerId = ownerId;
this.filePath = filePath;
this.userCanWrite = userCanWrite;
this.supportsLocks = supportsLocks;
this.contentType = contentType;
}
public String getFileId() {
return fileId;
}
public void setFileId(String fileId) {
this.fileId = fileId;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public long getSize() {
return size;
}
public void setSize(long size) {
this.size = size;
}
public String getOwnerId() {
return ownerId;
}
public void setOwnerId(String ownerId) {
this.ownerId = ownerId;
}
public Path getFilePath() {
return filePath;
}
public void setFilePath(Path filePath) {
this.filePath = filePath;
}
public boolean isUserCanWrite() {
return userCanWrite;
}
public void setUserCanWrite(boolean userCanWrite) {
this.userCanWrite = userCanWrite;
}
public boolean isSupportsLocks() {
return supportsLocks;
}
public void setSupportsLocks(boolean supportsLocks) {
this.supportsLocks = supportsLocks;
}
public String getContentType() {
return contentType;
}
public void setContentType(String contentType) {
this.contentType = contentType;
}
}

View File

@ -0,0 +1,132 @@
package vin.vio.collaboraonline.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.UrlResource;
import org.springframework.stereotype.Service;
import vin.vio.collaboraonline.model.WopiFile;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.nio.file.*;
/**
* @Classname FileService
* @Description TODO
* @Date 2025/2/19 16:54
* @Created by violet
*/
@Service
public class FileService {
private static final String BASE_DIR = "E:\\Code\\CollaboraOnline\\files";
public WopiFile getFileInfo(String fileId) {
Path filePath = Paths.get(BASE_DIR, fileId);
File targetFile = filePath.toFile();
if (!targetFile.exists() || !targetFile.canRead() || !targetFile.canWrite()) {
throw new RuntimeException("File not accessible: " + filePath);
}
WopiFile file = new WopiFile();
try {
file.setFileId(fileId);
file.setFileName(targetFile.getName());
file.setSize(targetFile.length());
file.setFilePath(filePath);
file.setOwnerId("admin");
file.setUserCanWrite(true);
file.setSupportsLocks(true);
String contentType = Files.probeContentType(filePath);
file.setContentType(contentType != null ? contentType : "application/octet-stream");
} catch (IOException e) {
throw new RuntimeException("Error reading file metadata: " + filePath, e);
}
return file;
}
public Resource loadFileAsResource(String fileId) {
try {
Path resolvedPath = validateFilePath(fileId);
FileSystemResource resource = new FileSystemResource(resolvedPath);
if (resource.exists() && resource.isReadable() && Files.isRegularFile(resolvedPath)) {
return resource;
}
return null;
} catch (InvalidPathException | SecurityException e) {
throw new RuntimeException("非法文件访问: " + fileId, e);
}
}
public boolean saveFile(String fileId, InputStream content) {
Path targetPath = validateFilePath(fileId);
try {
Path tempFile = Files.createTempFile(targetPath.getParent(), "wopi_", ".tmp");
try (OutputStream os = Files.newOutputStream(tempFile, StandardOpenOption.WRITE)) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = content.read(buffer)) != -1) {
os.write(buffer, 0, bytesRead);
}
}
Files.move(tempFile, targetPath, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
setFilePermissions(targetPath);
return true;
} catch (IOException e) {
throw new RuntimeException("文件保存失败: " + targetPath, e);
}
}
private Path validateFilePath(String fileId) {
if (fileId.contains("..") || fileId.contains(":") || fileId.contains("\\\\")) {
throw new SecurityException("非法文件名: " + fileId);
}
Path basePath = Paths.get(BASE_DIR).normalize().toAbsolutePath();
Path resolvedPath = basePath.resolve(fileId).normalize();
if (!resolvedPath.startsWith(basePath)) {
throw new SecurityException("路径越界访问: " + fileId);
}
return resolvedPath;
}
private void setFilePermissions(Path path) throws IOException {
if (!System.getProperty("os.name").toLowerCase().contains("win")) return;
String aclCommand = String.format(
"icacls \"%s\" /grant:r \"%s\":(F) /inheritance:r",
path.toString(),
System.getProperty("user.name")
);
try {
Runtime.getRuntime().exec(aclCommand).waitFor();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}

View File

@ -1,9 +1,3 @@
spring:
application:
name: CollaboraOnline
file:
preview:
collabora:
domain: http://192.168.3.211:9980
storageDomain: http://192.168.3.104:8080
name: CollaboraOnline