mirror of
https://gitee.com/kekingcn/file-online-preview.git
synced 2026-09-13 00:14:56 +00:00
Compare commits
4 Commits
fix/addtas
...
codex/add-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7323447468 | ||
|
|
67c6ba3b13 | ||
|
|
3e16ed9d3b | ||
|
|
32a887aa2c |
11
.github/workflows/maven.yml
vendored
11
.github/workflows/maven.yml
vendored
@@ -11,7 +11,7 @@ on:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
@@ -33,10 +33,10 @@ jobs:
|
||||
${{ runner.os }}-maven-
|
||||
|
||||
- name: Build with Maven
|
||||
run: mvn -B package -Dmaven.test.skip=true --file pom.xml
|
||||
run: mvn -B package "-Dmaven.test.skip=true" --file pom.xml
|
||||
|
||||
- name: Upload Linux distribution package
|
||||
if: success()
|
||||
if: success() && runner.os == 'Linux'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: kkfileview-linux
|
||||
@@ -44,9 +44,12 @@ jobs:
|
||||
retention-days: 7
|
||||
|
||||
- name: Upload Windows distribution package
|
||||
if: success()
|
||||
if: success() && runner.os == 'Windows'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: kkfileview-windows
|
||||
path: server/target/*.zip
|
||||
retention-days: 7
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ ubuntu-latest, windows-latest, macos-latest ]
|
||||
|
||||
2
.github/workflows/nightly-e2e.yml
vendored
2
.github/workflows/nightly-e2e.yml
vendored
@@ -115,4 +115,4 @@ jobs:
|
||||
name: nightly-e2e-service-logs
|
||||
path: |
|
||||
/tmp/kkfileview.log
|
||||
/tmp/fixture-server.log
|
||||
/tmp/fixture-server.log
|
||||
66
SECURITY.md
Normal file
66
SECURITY.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Security fixes are handled for the latest released version of kkFileView and the
|
||||
current `master` branch. Older versions may be evaluated case by case, but users
|
||||
are encouraged to upgrade to the latest release before reporting or verifying a
|
||||
security issue.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Please report security vulnerabilities privately through GitHub Private
|
||||
Vulnerability Reporting:
|
||||
|
||||
https://github.com/kekingcn/kkFileView/security/advisories/new
|
||||
|
||||
Do not publish vulnerability details, proof-of-concept code, exploit steps,
|
||||
sensitive logs, or private deployment information in public GitHub issues,
|
||||
discussions, pull requests, or comments.
|
||||
|
||||
When reporting a vulnerability, please include as much of the following
|
||||
information as you can safely share:
|
||||
|
||||
- Affected kkFileView version or commit
|
||||
- Deployment mode, operating system, JDK version, and related middleware
|
||||
- Clear reproduction steps
|
||||
- Impact assessment and affected feature or endpoint
|
||||
- Sanitized logs, screenshots, or sample files if they are required to reproduce
|
||||
the issue
|
||||
- Whether the issue is already being disclosed elsewhere
|
||||
|
||||
The maintainers will review private reports, ask for additional information when
|
||||
needed, coordinate a fix, and publish disclosure information when appropriate.
|
||||
|
||||
If the private reporting link is unavailable, please open a public issue only to
|
||||
request a private contact channel, without including technical vulnerability
|
||||
details.
|
||||
|
||||
---
|
||||
|
||||
# 安全策略
|
||||
|
||||
## 支持版本
|
||||
|
||||
kkFileView 安全修复主要覆盖最新发布版本和当前 `master` 分支。旧版本问题会视影响范围和维护成本单独评估,但建议用户优先升级到最新版本后再验证或报告安全问题。
|
||||
|
||||
## 报告安全漏洞
|
||||
|
||||
请通过 GitHub Private Vulnerability Reporting 私密提交安全漏洞:
|
||||
|
||||
https://github.com/kekingcn/kkFileView/security/advisories/new
|
||||
|
||||
请不要在公开 GitHub issue、discussion、pull request 或评论中发布漏洞细节、PoC、利用步骤、敏感日志或私有部署信息。
|
||||
|
||||
提交漏洞时,请在可安全分享的前提下尽量提供以下信息:
|
||||
|
||||
- 受影响的 kkFileView 版本或提交
|
||||
- 部署方式、操作系统、JDK 版本和相关中间件信息
|
||||
- 清晰的复现步骤
|
||||
- 影响范围,以及受影响的功能或接口
|
||||
- 复现所需的脱敏日志、截图或样例文件
|
||||
- 该问题是否已在其他渠道披露
|
||||
|
||||
维护者会在私密渠道中评估报告,在需要时继续确认细节,协调修复,并在适当时发布披露信息。
|
||||
|
||||
如果私密报告链接不可用,请只在公开 issue 中请求私密联系方式,不要包含任何技术漏洞细节。
|
||||
@@ -46,6 +46,7 @@ public class WebConfig implements WebMvcConfigurer {
|
||||
filterUri.add("/onlinePreview");
|
||||
filterUri.add("/picturesPreview");
|
||||
filterUri.add("/getCorsFile");
|
||||
filterUri.add("/addTask");
|
||||
TrustHostFilter filter = new TrustHostFilter();
|
||||
FilterRegistrationBean<TrustHostFilter> registrationBean = new FilterRegistrationBean<>();
|
||||
registrationBean.setFilter(filter);
|
||||
@@ -59,6 +60,7 @@ public class WebConfig implements WebMvcConfigurer {
|
||||
filterUri.add("/onlinePreview");
|
||||
filterUri.add("/picturesPreview");
|
||||
filterUri.add("/getCorsFile");
|
||||
filterUri.add("/addTask");
|
||||
TrustDirFilter filter = new TrustDirFilter();
|
||||
FilterRegistrationBean<TrustDirFilter> registrationBean = new FilterRegistrationBean<>();
|
||||
registrationBean.setFilter(filter);
|
||||
|
||||
54
server/src/main/resources/static/xlsx/luckyexcel-worker.js
Normal file
54
server/src/main/resources/static/xlsx/luckyexcel-worker.js
Normal file
@@ -0,0 +1,54 @@
|
||||
// LuckyExcel's bundled getBinaryContent reads window.XMLHttpRequest.
|
||||
// Web Worker exposes XMLHttpRequest on self, so provide a minimal window alias
|
||||
// before loading the UMD bundle.
|
||||
self.window = self;
|
||||
|
||||
importScripts('./luckyexcel.umd.js');
|
||||
|
||||
self.console.log = function () {};
|
||||
|
||||
self.onmessage = function (event) {
|
||||
var data = event.data || {};
|
||||
var url = data.url;
|
||||
var name = data.name;
|
||||
|
||||
if (!url) {
|
||||
self.postMessage({
|
||||
type: 'error',
|
||||
message: '文件URL为空'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
LuckyExcel.transformExcelToLuckyByUrl(
|
||||
url,
|
||||
name,
|
||||
function (exportJson, luckysheetfile) {
|
||||
if (!exportJson || !exportJson.sheets || exportJson.sheets.length === 0) {
|
||||
self.postMessage({
|
||||
type: 'error',
|
||||
message: '读取excel文件内容失败!'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
self.postMessage({
|
||||
type: 'success',
|
||||
exportJson: exportJson
|
||||
});
|
||||
},
|
||||
function (error) {
|
||||
self.postMessage({
|
||||
type: 'error',
|
||||
message: error && error.message ? error.message : String(error)
|
||||
});
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
self.postMessage({
|
||||
type: 'error',
|
||||
message: error && error.message ? error.message : String(error)
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -3938,7 +3938,7 @@ var LuckySheet = /** @class */function (_super) {
|
||||
_this.sheetList = allFileOption.sheetList;
|
||||
_this.imageList = allFileOption.imageList;
|
||||
_this.hide = allFileOption.hide;
|
||||
console.log(allFileOption, 'allFileOption');
|
||||
// console.log(allFileOption, 'allFileOption');
|
||||
_this.dataVerificationSelectCount = allFileOption.dataVerificationSelectCount;
|
||||
//Output
|
||||
_this.name = sheetName;
|
||||
|
||||
@@ -205,37 +205,40 @@
|
||||
// 异步加载Excel文件
|
||||
async function loadTextAsync() {
|
||||
if (isLoading) return;
|
||||
|
||||
|
||||
isLoading = true;
|
||||
updateProgress(10);
|
||||
|
||||
|
||||
try {
|
||||
initWaterMark();
|
||||
|
||||
|
||||
const value = url;
|
||||
const name = '${file.name}';
|
||||
|
||||
|
||||
if (!value) {
|
||||
showError('文件URL为空');
|
||||
return;
|
||||
}
|
||||
|
||||
updateProgress(30);
|
||||
|
||||
|
||||
// 使用异步方式加载
|
||||
await new Promise(resolve => setTimeout(resolve, 100)); // 给UI更新一点时间
|
||||
|
||||
// 或者使用现有的同步方法,但放在setTimeout中避免阻塞
|
||||
await transformWithTimeout(value, name);
|
||||
|
||||
|
||||
const exportJson = await transformWithWorker(value, name);
|
||||
|
||||
updateProgress(80);
|
||||
|
||||
await createLuckysheet(exportJson);
|
||||
|
||||
updateProgress(100);
|
||||
|
||||
|
||||
// 延迟隐藏加载界面,让用户看到加载完成
|
||||
setTimeout(() => {
|
||||
hideLoading();
|
||||
isLoading = false;
|
||||
}, 500);
|
||||
|
||||
|
||||
} catch (error) {
|
||||
console.error('加载Excel失败:', error);
|
||||
showError('加载失败: ' + error.message);
|
||||
@@ -243,35 +246,93 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 使用setTimeout将同步任务拆分
|
||||
function transformWithTimeout(value, name) {
|
||||
function transformWithWorker(value, name) {
|
||||
return new Promise((resolve, reject) => {
|
||||
updateProgress(50);
|
||||
|
||||
// 将转换过程放在setTimeout中,避免阻塞主线程
|
||||
setTimeout(() => {
|
||||
|
||||
if (!window.Worker) {
|
||||
transformOnMainThread(value, name, resolve, reject);
|
||||
return;
|
||||
}
|
||||
|
||||
let worker;
|
||||
try {
|
||||
worker = new Worker('xlsx/luckyexcel-worker.js');
|
||||
} catch (error) {
|
||||
transformOnMainThread(value, name, resolve, reject);
|
||||
return;
|
||||
}
|
||||
|
||||
let settled = false;
|
||||
const fallbackToMainThread = function(error) {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
settled = true;
|
||||
worker.terminate();
|
||||
if (error) {
|
||||
console.warn('Excel Worker转换失败,回退主线程转换:', error);
|
||||
}
|
||||
transformOnMainThread(value, name, resolve, reject);
|
||||
};
|
||||
|
||||
worker.onmessage = function(event) {
|
||||
const data = event.data || {};
|
||||
|
||||
if (data.type === 'success') {
|
||||
settled = true;
|
||||
worker.terminate();
|
||||
resolve(data.exportJson);
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.type === 'error') {
|
||||
fallbackToMainThread(data.message || 'Excel转换失败');
|
||||
}
|
||||
};
|
||||
|
||||
worker.onerror = function(error) {
|
||||
fallbackToMainThread(error && error.message ? error.message : error);
|
||||
};
|
||||
|
||||
worker.postMessage({
|
||||
url: value,
|
||||
name: name
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function transformOnMainThread(value, name, resolve, reject) {
|
||||
try {
|
||||
LuckyExcel.transformExcelToLuckyByUrl(value, name, function(exportJson, luckysheetfile) {
|
||||
if (!exportJson || !exportJson.sheets || exportJson.sheets.length === 0) {
|
||||
reject(new Error("读取excel文件内容失败!"));
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(exportJson);
|
||||
}, function(error) {
|
||||
reject(error);
|
||||
});
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
}
|
||||
|
||||
function createLuckysheet(exportJson) {
|
||||
return new Promise((resolve, reject) => {
|
||||
requestAnimationFrame(() => {
|
||||
try {
|
||||
LuckyExcel.transformExcelToLuckyByUrl(value, name, function(exportJson, luckysheetfile){
|
||||
if(exportJson.sheets==null || exportJson.sheets.length==0){
|
||||
reject(new Error("读取excel文件内容失败!"));
|
||||
return;
|
||||
}
|
||||
|
||||
updateProgress(80);
|
||||
|
||||
// 使用requestAnimationFrame来更新UI,避免阻塞
|
||||
requestAnimationFrame(() => {
|
||||
try {
|
||||
window.luckysheet.destroy();
|
||||
window.luckysheet.create({
|
||||
container: 'luckysheet',
|
||||
lang: "zh",
|
||||
showtoolbarConfig:{
|
||||
image: true,
|
||||
print: true,
|
||||
exportXlsx: true,
|
||||
},
|
||||
allowCopy: true, // 是否允许拷贝
|
||||
window.luckysheet.destroy();
|
||||
window.luckysheet.create({
|
||||
container: 'luckysheet',
|
||||
lang: "zh",
|
||||
showtoolbarConfig:{
|
||||
image: true,
|
||||
print: true,
|
||||
exportXlsx: true,
|
||||
},
|
||||
allowCopy: true, // 是否允许拷贝
|
||||
showtoolbar: ${xlsxshowtoolbar?string('true','false')}, // 是否显示工具栏
|
||||
showinfobar: true, // 是否显示顶部信息栏
|
||||
// myFolderUrl: "/",//作用:左上角<返回按钮的链接
|
||||
@@ -287,29 +348,23 @@
|
||||
sheetFormulaBar: false, // 是否显示公式栏
|
||||
enableAddBackTop: true,//返回头部按钮
|
||||
forceCalculation: false, //下面是导出插件 默认关闭
|
||||
data: exportJson.sheets,
|
||||
title: exportJson.info.name,
|
||||
userInfo: exportJson.info.name.creator,
|
||||
// 添加加载完成的回调
|
||||
hook: {
|
||||
workbookCreateAfter: function() {
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
updateProgress(90);
|
||||
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
data: exportJson.sheets,
|
||||
title: exportJson.info.name,
|
||||
userInfo: exportJson.info.name.creator,
|
||||
// 添加加载完成的回调
|
||||
hook: {
|
||||
workbookCreateAfter: function() {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
|
||||
updateProgress(90);
|
||||
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -330,4 +385,4 @@
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
27
server/src/test/java/cn/keking/config/WebConfigTests.java
Normal file
27
server/src/test/java/cn/keking/config/WebConfigTests.java
Normal file
@@ -0,0 +1,27 @@
|
||||
package cn.keking.config;
|
||||
|
||||
import cn.keking.web.filter.TrustDirFilter;
|
||||
import cn.keking.web.filter.TrustHostFilter;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class WebConfigTests {
|
||||
|
||||
private final WebConfig webConfig = new WebConfig();
|
||||
|
||||
@Test
|
||||
void shouldApplyTrustHostFilterToAddTaskEndpoint() {
|
||||
FilterRegistrationBean<TrustHostFilter> registration = webConfig.getTrustHostFilter();
|
||||
|
||||
assertTrue(registration.getUrlPatterns().contains("/addTask"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldApplyTrustDirFilterToAddTaskEndpoint() {
|
||||
FilterRegistrationBean<TrustDirFilter> registration = webConfig.getTrustDirFilter();
|
||||
|
||||
assertTrue(registration.getUrlPatterns().contains("/addTask"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user