Compare commits
257 Commits
v4.3.0
...
fix/e2e-ph
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d0b14515f | ||
|
|
e845846d30 | ||
|
|
a7ad4b4fe4 | ||
|
|
68d4d23a4b | ||
|
|
bb457924cd | ||
|
|
a0d78c57e3 | ||
|
|
7f16243270 | ||
|
|
36a75e86ac | ||
|
|
7c41200028 | ||
|
|
3da0c523e8 | ||
|
|
8c3bc81e08 | ||
|
|
92ca92bee6 | ||
|
|
64c82a2406 | ||
|
|
e44ef813a1 | ||
|
|
9f3b45a4c7 | ||
|
|
b1af0c7d72 | ||
|
|
421640221b | ||
|
|
f6c6e22b0d | ||
|
|
51653483b9 | ||
|
|
a9787b0add | ||
|
|
6cdbf92fb0 | ||
|
|
fdb40680d3 | ||
|
|
6746325bf2 | ||
|
|
05a8bff1e0 | ||
|
|
874ff5b3f6 | ||
|
|
2230cfa52b | ||
|
|
83c581364d | ||
|
|
1d39360c60 | ||
|
|
8c763599fe | ||
|
|
9632f6070c | ||
|
|
cc63659650 | ||
|
|
177f389814 | ||
|
|
406e9ea6ee | ||
|
|
bb461cd74a | ||
|
|
782509376c | ||
|
|
63dc58d088 | ||
|
|
77f5adb19f | ||
|
|
7abfb67451 | ||
|
|
c8dc638c29 | ||
|
|
48ac926289 | ||
|
|
0a4ae41b0c | ||
|
|
bb0139bee6 | ||
|
|
7bf07cb64c | ||
|
|
ab370e66a5 | ||
|
|
421a2760d5 | ||
|
|
42f0e079f0 | ||
|
|
9150346926 | ||
|
|
b65a04857c | ||
|
|
59315c3200 | ||
|
|
08e5a15424 | ||
|
|
b8c283f602 | ||
|
|
11f7ee34de | ||
|
|
5be0d60caf | ||
|
|
6504ae2f45 | ||
|
|
c3155204eb | ||
|
|
ebd35803c6 | ||
|
|
55bda80d33 | ||
|
|
3886e62e8e | ||
|
|
07edf77ba8 | ||
|
|
ddefeb630a | ||
|
|
f050701d64 | ||
|
|
5a50327f8f | ||
|
|
b4cd038c86 | ||
|
|
9b22f9a412 | ||
|
|
99aeeb5faa | ||
|
|
29b09965a3 | ||
|
|
312c31a426 | ||
|
|
c54610caf6 | ||
|
|
72014e1534 | ||
|
|
d407b88b67 | ||
|
|
d1d8ffef7a | ||
|
|
787e9fe615 | ||
|
|
e79c53156b | ||
|
|
7dcd225cea | ||
|
|
3482ee51da | ||
|
|
211df6965f | ||
|
|
448a700687 | ||
|
|
7d17dae1fd | ||
|
|
20a5c1b494 | ||
|
|
4852b1479b | ||
|
|
bef81a940e | ||
|
|
8441d18a7f | ||
|
|
09f4400552 | ||
|
|
4f422f5ef0 | ||
|
|
fdd3bbd5c1 | ||
|
|
bb3c8a1188 | ||
|
|
bad3db400b | ||
|
|
8ac8cd8487 | ||
|
|
a54cd75469 | ||
|
|
26d8c7ab62 | ||
|
|
6b802b6545 | ||
|
|
8d56923efc | ||
|
|
79b9d703dc | ||
|
|
a5d29a4e3c | ||
|
|
7a9b3f0b8f | ||
|
|
9de1ae8c11 | ||
|
|
218f9f0015 | ||
|
|
0c6ea7248a | ||
|
|
13dfca016e | ||
|
|
904b9497a0 | ||
|
|
69444a4d72 | ||
|
|
7d67d0d857 | ||
|
|
a95dda6c20 | ||
|
|
148046f105 | ||
|
|
6d01caee50 | ||
|
|
9f9790d4fd | ||
|
|
fdb5325e02 | ||
|
|
be4080c80d | ||
|
|
a057808624 | ||
|
|
355a69b4fc | ||
|
|
7597864337 | ||
|
|
724db1936b | ||
|
|
75061d843a | ||
|
|
502b21147d | ||
|
|
deb91728d4 | ||
|
|
790c29c205 | ||
|
|
b1bfd81a97 | ||
|
|
a056c9c6c9 | ||
|
|
c559efcceb | ||
|
|
8d5c987fe7 | ||
|
|
fa034d54b6 | ||
|
|
e39e8bd907 | ||
|
|
693b13e818 | ||
|
|
6cd22e0975 | ||
|
|
22c881bbaa | ||
|
|
3cfc51a4d7 | ||
|
|
3e08deb50e | ||
|
|
42cf6b2955 | ||
|
|
02c64977cb | ||
|
|
32b0a46574 | ||
|
|
94a76a72a4 | ||
|
|
bcb278dd0b | ||
|
|
8eab415430 | ||
|
|
aa3108fe98 | ||
|
|
56965b4ff4 | ||
|
|
6eaf04aa71 | ||
|
|
5fe0dd3d29 | ||
|
|
091c955363 | ||
|
|
f09d0d8279 | ||
|
|
713edcfb8d | ||
|
|
7e0f7f6608 | ||
|
|
e4c29bf57f | ||
|
|
b2d0d50507 | ||
|
|
8c480208df | ||
|
|
b010e13de2 | ||
|
|
c2bfba865a | ||
|
|
b02debfc59 | ||
|
|
978aa0dd68 | ||
|
|
14ecf66c70 | ||
|
|
0563fde951 | ||
|
|
8f37e85aa6 | ||
|
|
0c4e5bc420 | ||
|
|
8b6e7dcbdc | ||
|
|
48fe534e77 | ||
|
|
6da291e6e3 | ||
|
|
d49b444462 | ||
|
|
692bb8f964 | ||
|
|
c2abe2f34c | ||
|
|
133973c987 | ||
|
|
8a35eb5d3e | ||
|
|
b1fbeb52a5 | ||
|
|
674cbb9bf5 | ||
|
|
6f53b41baf | ||
|
|
381e1d6b43 | ||
|
|
779c7c77b7 | ||
|
|
096426b8e2 | ||
|
|
d8146f0495 | ||
|
|
be4f3d06e8 | ||
|
|
da784aea84 | ||
|
|
bb3b5b3dff | ||
|
|
ad3e38285f | ||
|
|
60dfefd37c | ||
|
|
8268fc796e | ||
|
|
98f83d9be1 | ||
|
|
c39a205cea | ||
|
|
1671fc3572 | ||
|
|
e550df3e8b | ||
|
|
930cd2b09b | ||
|
|
36672da026 | ||
|
|
5f1e5c8f4b | ||
|
|
b71442543d | ||
|
|
146920496e | ||
|
|
cfa5771b8e | ||
|
|
25e77448d1 | ||
|
|
4a0409953a | ||
|
|
fa2a5d3e35 | ||
|
|
7c1c8a43e8 | ||
|
|
0a8be8ac95 | ||
|
|
0732344e79 | ||
|
|
c8a7371e07 | ||
|
|
aa49cc6ac0 | ||
|
|
cc01769f8f | ||
|
|
8a613513aa | ||
|
|
c249f68972 | ||
|
|
5ecfb39a9e | ||
|
|
f8039102bf | ||
|
|
88fc5910e9 | ||
|
|
41e8042bc7 | ||
|
|
a45a592a39 | ||
|
|
53cc923f1e | ||
|
|
e653eeb5c6 | ||
|
|
6e7559577c | ||
|
|
7455f8279e | ||
|
|
149e7de7fc | ||
|
|
824dbb4e45 | ||
|
|
8fef5c595d | ||
|
|
017a7dccb8 | ||
|
|
079fe5e7b0 | ||
|
|
1e03307657 | ||
|
|
ccaba78e8d | ||
|
|
75fe682433 | ||
|
|
fb6320a244 | ||
|
|
3da330341f | ||
|
|
31c7b2dfb8 | ||
|
|
d2a8ca2cdd | ||
|
|
16fca7ec61 | ||
|
|
39d8d38ee9 | ||
|
|
99982f07f7 | ||
|
|
1246a853b5 | ||
|
|
6382453327 | ||
|
|
4ebee74f70 | ||
|
|
54750f4e06 | ||
|
|
1225362c90 | ||
|
|
fe5ad49008 | ||
|
|
586561ec5a | ||
|
|
cad2d58f44 | ||
|
|
4f86a56f59 | ||
|
|
c86ca0d8cf | ||
|
|
ec633cc0e1 | ||
|
|
fa2a2b4342 | ||
|
|
2b456c9934 | ||
|
|
7965d52f29 | ||
|
|
ad322c2e10 | ||
|
|
da5dac73ab | ||
|
|
b5e711bf87 | ||
|
|
535d2baf0b | ||
|
|
294dcb1994 | ||
|
|
c54a5e9f1a | ||
|
|
3571f2b502 | ||
|
|
6f8416365f | ||
|
|
778a381b5b | ||
|
|
770769ead5 | ||
|
|
8d49abb797 | ||
|
|
d0568c53dc | ||
|
|
276ef9d704 | ||
|
|
dd75f718aa | ||
|
|
0ece6ccb51 | ||
|
|
bbd7530f77 | ||
|
|
2cfd692171 | ||
|
|
77f794831f | ||
|
|
4ee8926d5f | ||
|
|
a850e405ed | ||
|
|
cac68f88df | ||
|
|
3734a66549 | ||
|
|
2331545369 | ||
|
|
0ff1f2c22b | ||
|
|
61f1d6ac8b |
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: "Security Report / 安全漏洞报告"
|
||||
url: "https://github.com/kekingcn/kkFileView/security/advisories/new"
|
||||
about: "For sensitive security issues, please use private security report. / 涉及敏感安全问题请使用私密安全报告。"
|
||||
76
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
name: "Feature Request / 功能建议"
|
||||
description: "Propose a new feature with clear use case and acceptance criteria. / 提交功能建议,请明确场景与验收标准。"
|
||||
title: "[FEATURE] "
|
||||
labels: ["type/feature", "priority/p2", "status/needs-info"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for your idea! / 感谢你的建议!
|
||||
|
||||
Please provide concrete business scenarios and the expected behavior.
|
||||
请尽量提供明确业务场景和期望行为,便于评估优先级与实现方案。
|
||||
|
||||
For urgent production issues, you can use our Knowledge Planet channel for faster processing:
|
||||
https://wx.zsxq.com/group/48844125114258
|
||||
如为线上紧急问题,可通过知识星球渠道加速处理:
|
||||
https://wx.zsxq.com/group/48844125114258
|
||||
|
||||
- type: textarea
|
||||
id: background
|
||||
attributes:
|
||||
label: "Background / 背景"
|
||||
description: "What problem are you trying to solve? / 你要解决什么问题?"
|
||||
placeholder: "Describe current pain points... / 描述当前痛点..."
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: proposal
|
||||
attributes:
|
||||
label: "Proposal / 建议方案"
|
||||
description: "What do you expect kkFileView to support? / 期望 kkFileView 支持什么?"
|
||||
placeholder: "Describe expected feature behavior... / 描述期望功能行为..."
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: use_case
|
||||
attributes:
|
||||
label: "Use Case / 使用场景"
|
||||
description: "Provide 1-3 concrete scenarios. / 提供 1-3 个具体场景"
|
||||
placeholder: |
|
||||
Scenario 1:
|
||||
Scenario 2:
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: alternatives
|
||||
attributes:
|
||||
label: "Alternatives / 备选方案"
|
||||
description: "What alternatives have you considered? / 是否考虑过替代方案?"
|
||||
placeholder: "Existing workaround or alternative... / 当前替代做法..."
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: acceptance
|
||||
attributes:
|
||||
label: "Acceptance Criteria / 验收标准"
|
||||
description: "How do we know this feature is done? / 如何判断该功能完成?"
|
||||
placeholder: |
|
||||
- [ ] Criterion 1
|
||||
- [ ] Criterion 2
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: checkboxes
|
||||
id: checklist
|
||||
attributes:
|
||||
label: "Checklist / 提交前检查"
|
||||
options:
|
||||
- label: "I have searched existing issues and did not find a duplicate feature request. / 我已搜索现有 issue,未发现重复功能建议"
|
||||
required: true
|
||||
- label: "I provided concrete use cases and expected behavior. / 我已提供具体使用场景和期望行为"
|
||||
required: true
|
||||
121
.github/ISSUE_TEMPLATE/issue-report.yml
vendored
Normal file
@@ -0,0 +1,121 @@
|
||||
name: "Issue Report / 问题反馈"
|
||||
description: "Please provide complete required information to help us reproduce and follow up. / 请完整填写必填信息,便于复现与跟进。"
|
||||
title: "[ISSUE] "
|
||||
labels: ["status/needs-info"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for your report! / 感谢反馈!
|
||||
|
||||
**Please fill in all required fields.**
|
||||
**请完整填写所有必填项。**
|
||||
|
||||
Incomplete issues may be closed and asked to resubmit.
|
||||
信息不完整的问题可能会被关闭并要求重新提交。
|
||||
|
||||
For urgent production issues, you can use our Knowledge Planet channel for faster processing:
|
||||
https://wx.zsxq.com/group/48844125114258
|
||||
如为线上紧急问题,可通过知识星球渠道加速处理:
|
||||
https://wx.zsxq.com/group/48844125114258
|
||||
|
||||
- type: dropdown
|
||||
id: issue_type
|
||||
attributes:
|
||||
label: "Issue Type / 问题类型"
|
||||
description: "Select the closest type. / 请选择最接近的问题类型"
|
||||
options:
|
||||
- "Bug / 缺陷"
|
||||
- "Performance / 性能问题"
|
||||
- "Security / 安全问题"
|
||||
- "Question / 使用咨询"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: kkfileview_version
|
||||
attributes:
|
||||
label: "kkFileView Version / kkFileView 版本"
|
||||
placeholder: "e.g. 4.4.0"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: deploy_mode
|
||||
attributes:
|
||||
label: "Deployment Mode / 部署方式"
|
||||
description: "jar / docker / k8s / source, etc. / jar / docker / k8s / 源码部署等"
|
||||
placeholder: "e.g. docker"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: environment
|
||||
attributes:
|
||||
label: "Environment / 环境信息"
|
||||
description: "OS, JDK, LibreOffice/OpenOffice, browser, reverse proxy, etc. / 操作系统、JDK、Office组件、浏览器、反向代理等"
|
||||
placeholder: |
|
||||
- OS:
|
||||
- JDK:
|
||||
- LibreOffice/OpenOffice:
|
||||
- Browser:
|
||||
- Proxy (Nginx/Ingress):
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: reproduce_steps
|
||||
attributes:
|
||||
label: "Steps to Reproduce / 复现步骤"
|
||||
description: "Provide clear, minimal, reproducible steps. / 提供清晰、最小可复现步骤"
|
||||
placeholder: |
|
||||
1) ...
|
||||
2) ...
|
||||
3) ...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: expected_result
|
||||
attributes:
|
||||
label: "Expected Result / 期望结果"
|
||||
placeholder: "What should happen? / 期望实际应该出现什么结果?"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: actual_result
|
||||
attributes:
|
||||
label: "Actual Result / 实际结果"
|
||||
placeholder: "What happened instead? / 实际发生了什么?"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: "Logs & Screenshots / 日志与截图"
|
||||
description: "Paste key logs/error stack and attach screenshots (mask sensitive data). / 粘贴关键日志或异常堆栈,并上传截图(请脱敏)"
|
||||
render: shell
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: sample_file
|
||||
attributes:
|
||||
label: "Sample File / 样例文件(可选)"
|
||||
description: "If possible, provide a minimal sample file or reproducible URL (desensitized). / 如可提供,请附最小样例文件或可复现 URL(脱敏)"
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: checkboxes
|
||||
id: checklist
|
||||
attributes:
|
||||
label: "Checklist / 提交前检查"
|
||||
options:
|
||||
- label: "I have searched existing issues and did not find a duplicate. / 我已搜索现有 issue,未发现重复问题"
|
||||
required: true
|
||||
- label: "I can reproduce this issue on the stated version/environment. / 我可在上述版本与环境复现该问题"
|
||||
required: true
|
||||
- label: "I have masked sensitive information in logs/screenshots. / 我已对日志与截图中的敏感信息做脱敏处理"
|
||||
required: true
|
||||
78
.github/workflows/auto-close-old-issues.yml
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
name: Auto Close Old Issues (1y)
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Daily at 02:20 UTC
|
||||
- cron: '20 2 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
close_old_issues:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Close issues older than 1 year
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const owner = context.repo.owner;
|
||||
const repo = context.repo.repo;
|
||||
const now = new Date();
|
||||
const cutoff = new Date(now.getTime() - 365 * 24 * 60 * 60 * 1000);
|
||||
|
||||
const closeComment = `该 Issue 已超过 1 年未活跃,为便于维护当前问题队列,先做关闭处理。\n\n如该问题在最新版本仍存在,欢迎直接 **Reopen** 本 Issue(或新建 Issue 并关联本单),并补充:\n1. 版本与部署方式\n2. 最小复现步骤\n3. 关键日志/截图(请脱敏)\n\n我们会优先跟进。`;
|
||||
|
||||
let page = 1;
|
||||
let processed = 0;
|
||||
while (true) {
|
||||
const { data: issues } = await github.rest.issues.listForRepo({
|
||||
owner,
|
||||
repo,
|
||||
state: 'open',
|
||||
per_page: 100,
|
||||
page,
|
||||
sort: 'created',
|
||||
direction: 'asc'
|
||||
});
|
||||
|
||||
if (!issues.length) break;
|
||||
|
||||
for (const issue of issues) {
|
||||
// skip pull requests
|
||||
if (issue.pull_request) continue;
|
||||
|
||||
const createdAt = new Date(issue.created_at);
|
||||
if (createdAt > cutoff) {
|
||||
// list is sorted asc by created time; remaining items are newer
|
||||
core.info('Reached issues newer than cutoff, stop scanning.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await github.rest.issues.createComment({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: issue.number,
|
||||
body: closeComment,
|
||||
});
|
||||
|
||||
await github.rest.issues.update({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: issue.number,
|
||||
state: 'closed',
|
||||
});
|
||||
|
||||
processed += 1;
|
||||
core.info(`Closed #${issue.number}`);
|
||||
} catch (e) {
|
||||
core.warning(`Failed to close #${issue.number}: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
page += 1;
|
||||
}
|
||||
|
||||
core.info(`Done. Closed ${processed} old issues.`);
|
||||
34
.github/workflows/copilot-issue-auto-comment.yml
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
name: Copilot Issue Auto Comment
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
copilot_auto_comment:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Ask Copilot to triage issue automatically
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const issue = context.payload.issue;
|
||||
if (!issue) return;
|
||||
|
||||
const owner = context.repo.owner;
|
||||
const repo = context.repo.repo;
|
||||
|
||||
const body = `@copilot 请自动分诊并直接给出可执行建议(无需人工先介入):\n\n- 先判断类型:Bug / Performance / Security / Question / Feature\n- 检查 Issue 信息是否完整(版本、部署方式、复现步骤、日志)\n- 若信息不完整:请直接按模板列出缺失项并引导补充\n- 若信息较完整:请给出下一步排查建议与最小复现建议\n- 若判断为已知问题或已修复:请给出对应版本/修复方向\n\nIssue #${issue.number}\n标题:${issue.title}\n链接:${issue.html_url}\n\n---\n\n补充说明 / Support Notice:\n- GitHub Issues 会持续跟进处理 / We will continue to follow up through GitHub Issues.\n- 如为线上紧急问题,可通过知识星球渠道加速处理 / For urgent production issues, you can use our Knowledge Planet channel for faster processing:\n https://wx.zsxq.com/group/48844125114258`;
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: issue.number,
|
||||
body,
|
||||
});
|
||||
|
||||
core.info(`Copilot prompt comment posted to #${issue.number}`);
|
||||
44
.github/workflows/maven.yml
vendored
@@ -11,14 +11,42 @@ on:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-18.04
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up JDK 8
|
||||
uses: actions/setup-java@v2
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '8'
|
||||
distribution: 'adopt'
|
||||
cache: maven
|
||||
java-version: '21'
|
||||
distribution: 'temurin' # 使用 Eclipse Temurin (AdoptOpenJDK 的继任者)
|
||||
cache: 'maven'
|
||||
|
||||
- name: Cache Maven packages
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.m2/repository
|
||||
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-maven-
|
||||
|
||||
- name: Build with Maven
|
||||
run: mvn -B package --file pom.xml
|
||||
run: mvn -B package -Dmaven.test.skip=true --file pom.xml
|
||||
|
||||
- name: Upload Linux distribution package
|
||||
if: success()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: kkfileview-linux
|
||||
path: server/target/*.tar.gz
|
||||
retention-days: 7
|
||||
|
||||
- name: Upload Windows distribution package
|
||||
if: success()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: kkfileview-windows
|
||||
path: server/target/*.zip
|
||||
retention-days: 7
|
||||
|
||||
118
.github/workflows/nightly-e2e.yml
vendored
Normal file
@@ -0,0 +1,118 @@
|
||||
name: Nightly E2E Full
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '30 18 * * *' # 02:30 Asia/Shanghai
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
e2e-nightly:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 50
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup JDK 21
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: '21'
|
||||
cache: maven
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: tests/e2e/package-lock.json
|
||||
|
||||
- name: Setup Python 3.11
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Install LibreOffice + zip
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libreoffice zip
|
||||
|
||||
- name: Setup Python deps for office fixtures
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r tests/e2e/requirements.txt
|
||||
|
||||
- name: Build kkFileView
|
||||
run: mvn -q -pl server -DskipTests package
|
||||
|
||||
- name: Install E2E deps
|
||||
working-directory: tests/e2e
|
||||
run: |
|
||||
npm ci
|
||||
npx playwright install --with-deps chromium
|
||||
|
||||
- name: Start fixture server
|
||||
run: |
|
||||
cd tests/e2e/fixtures
|
||||
python3 -m http.server 18080 > /tmp/fixture-server.log 2>&1 &
|
||||
|
||||
- name: Start kkFileView
|
||||
run: |
|
||||
JAR_PATH=$(ls server/target/kkFileView-*.jar | head -n 1)
|
||||
nohup env KK_TRUST_HOST='*' KK_NOT_TRUST_HOST='10.*,172.16.*,192.168.*' java -jar "$JAR_PATH" > /tmp/kkfileview.log 2>&1 &
|
||||
|
||||
- name: Wait for services
|
||||
run: |
|
||||
fixture_ready=false
|
||||
for i in {1..60}; do
|
||||
if curl -fsS http://127.0.0.1:18080/sample.txt >/dev/null; then
|
||||
fixture_ready=true
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
if [ "$fixture_ready" != "true" ]; then
|
||||
echo "Error: fixture server did not become ready within 60 seconds." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
kkfileview_ready=false
|
||||
for i in {1..120}; do
|
||||
if curl -fsS http://127.0.0.1:8012/ >/dev/null; then
|
||||
kkfileview_ready=true
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
if [ "$kkfileview_ready" != "true" ]; then
|
||||
echo "Error: kkFileView service did not become ready within 120 seconds." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Run nightly E2E suites
|
||||
working-directory: tests/e2e
|
||||
env:
|
||||
KK_BASE_URL: http://127.0.0.1:8012
|
||||
FIXTURE_BASE_URL: http://127.0.0.1:18080
|
||||
E2E_MAX_PREVIEW_MS: 20000
|
||||
run: npm run test:ci
|
||||
|
||||
- name: Upload Playwright report
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: nightly-playwright-report
|
||||
path: tests/e2e/playwright-report
|
||||
|
||||
- name: Upload service logs
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: nightly-e2e-service-logs
|
||||
path: |
|
||||
/tmp/kkfileview.log
|
||||
/tmp/fixture-server.log
|
||||
100
.github/workflows/pr-e2e-mvp.yml
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
name: PR E2E MVP
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [master]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
e2e-mvp:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 40
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup JDK 21
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: '21'
|
||||
cache: maven
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: tests/e2e/package-lock.json
|
||||
|
||||
- name: Setup Python 3.11
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Install LibreOffice + zip
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libreoffice zip
|
||||
|
||||
- name: Setup Python deps for office fixtures
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r tests/e2e/requirements.txt
|
||||
|
||||
- name: Build kkFileView
|
||||
run: mvn -q -pl server -DskipTests package
|
||||
|
||||
- name: Install E2E deps
|
||||
working-directory: tests/e2e
|
||||
run: |
|
||||
npm install
|
||||
npx playwright install --with-deps chromium
|
||||
|
||||
- name: Start fixture server
|
||||
run: |
|
||||
cd tests/e2e/fixtures
|
||||
python3 -m http.server 18080 > /tmp/fixture-server.log 2>&1 &
|
||||
|
||||
- name: Start kkFileView
|
||||
run: |
|
||||
JAR_PATH=$(ls server/target/kkFileView-*.jar | head -n 1)
|
||||
nohup env KK_TRUST_HOST='*' KK_NOT_TRUST_HOST='10.*,172.16.*,192.168.*' java -jar "$JAR_PATH" > /tmp/kkfileview.log 2>&1 &
|
||||
|
||||
- name: Wait for services
|
||||
run: |
|
||||
for i in {1..60}; do
|
||||
curl -fsS http://127.0.0.1:18080/sample.txt >/dev/null && break
|
||||
sleep 1
|
||||
done
|
||||
for i in {1..120}; do
|
||||
curl -fsS http://127.0.0.1:8012/ >/dev/null && break
|
||||
sleep 1
|
||||
done
|
||||
|
||||
- name: Run E2E
|
||||
working-directory: tests/e2e
|
||||
env:
|
||||
KK_BASE_URL: http://127.0.0.1:8012
|
||||
FIXTURE_BASE_URL: http://127.0.0.1:18080
|
||||
run: npm test
|
||||
|
||||
- name: Upload Playwright report
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: playwright-report
|
||||
path: tests/e2e/playwright-report
|
||||
|
||||
- name: Upload service logs
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: e2e-service-logs
|
||||
path: |
|
||||
/tmp/kkfileview.log
|
||||
/tmp/fixture-server.log
|
||||
@@ -1,5 +1,4 @@
|
||||
FROM keking/kkfileview-jdk:latest
|
||||
MAINTAINER chenjh "842761733@qq.com"
|
||||
FROM keking/kkfileview-base:4.4.0
|
||||
ADD server/target/kkFileView-*.tar.gz /opt/
|
||||
ENV KKFILEVIEW_BIN_FOLDER /opt/kkFileView-4.3.0/bin
|
||||
ENTRYPOINT ["java","-Dfile.encoding=UTF-8","-Dspring.config.location=/opt/kkFileView-4.3.0/config/application.properties","-jar","/opt/kkFileView-4.3.0/bin/kkFileView-4.3.0.jar"]
|
||||
ENV KKFILEVIEW_BIN_FOLDER=/opt/kkFileView-4.4.0/bin
|
||||
ENTRYPOINT ["java","-Dfile.encoding=UTF-8","-Dspring.config.location=/opt/kkFileView-4.4.0/config/application.properties","-jar","/opt/kkFileView-4.4.0/bin/kkFileView-4.4.0.jar"]
|
||||
|
||||
215
README.cn.md
@@ -13,7 +13,7 @@
|
||||
11. 支持 epub 图书文档
|
||||
12. 支持 obj, 3ds, stl, ply, gltf, glb, off, 3dm, fbx, dae, wrl, 3mf, ifc, brep, step, iges, fcstd, bim 等 3D 模型文件
|
||||
13. 支持 dwg, dxf, dwf, iges , igs, dwt, dng, ifc, dwfx, stl, cf2, plt 等 CAD 模型文件
|
||||
14. 支持 txt, xml(渲染), md(渲染), java, php, py, js, css 等所有纯文本
|
||||
14. 支持 txt, xml(渲染), xbrl(渲染), md(渲染), java, php, py, js, css 等所有纯文本
|
||||
15. 支持 zip, rar, jar, tar, gzip, 7z 等压缩包
|
||||
16. 支持 jpg, jpeg, png, gif, bmp, ico, jfif, webp 等图片预览(翻转,缩放,镜像)
|
||||
17. 支持 tif, tiff 图信息模型文件
|
||||
@@ -42,9 +42,7 @@
|
||||
地址:[https://file.kkview.cn](https://file.kkview.cn)
|
||||
|
||||
### 项目文档(Project documentation)
|
||||
1. 详细wiki文档:https://gitee.com/kekingcn/file-online-preview/wikis/pages
|
||||
1. 中文文档:https://gitee.com/kekingcn/file-online-preview/blob/master/README.md
|
||||
1. English document:https://gitee.com/kekingcn/file-online-preview/blob/master/README.en.md
|
||||
1. 详细使用文档:https://kkview.cn/zh-cn/docs/home.html
|
||||
|
||||
### 联系我们,加入组织
|
||||
> 我们会用心回答解决大家在项目使用中的问题,也请大家在提问前至少 Google 或 baidu 过,珍爱生命远离无效的交流沟通
|
||||
@@ -53,61 +51,82 @@
|
||||
|
||||
### 文档预览效果
|
||||
#### 1. 文本预览
|
||||
支持所有类型的文本文档预览, 由于文本文档类型过多,无法全部枚举,默认开启的类型如下 txt,html,htm,asp,jsp,xml,json,properties,md,gitignore,log,java,py,c,cpp,sql,sh,bat,m,bas,prg,cmd
|
||||
支持所有类型的文本文档预览, 由于文本文档类型过多,无法全部枚举,默认开启的类型如下 txt,html,htm,asp,jsp,xml,xbrl,json,properties,md,gitignore,log,java,py,c,cpp,sql,sh,bat,m,bas,prg,cmd
|
||||
文本预览效果如下
|
||||

|
||||

|
||||
|
||||
#### 2. 图片预览
|
||||
支持jpg,jpeg,png,gif等图片预览(翻转,缩放,镜像),预览效果如下
|
||||

|
||||

|
||||
|
||||
#### 3. word文档预览
|
||||
支持doc,docx文档预览,word预览有两种模式:一种是每页word转为图片预览,另一种是整个word文档转成pdf,再预览pdf。两种模式的适用场景如下
|
||||
* 图片预览:word文件大,前台加载整个pdf过慢
|
||||
* pdf预览:内网访问,加载pdf快
|
||||
图片预览模式预览效果如下
|
||||

|
||||

|
||||
pdf预览模式预览效果如下
|
||||

|
||||

|
||||
|
||||
#### 4. ppt文档预览
|
||||
支持ppt,pptx文档预览,和word文档一样,有两种预览模式
|
||||
图片预览模式预览效果如下
|
||||

|
||||

|
||||
pdf预览模式预览效果如下
|
||||

|
||||

|
||||
|
||||
#### 5. pdf文档预览
|
||||
支持pdf文档预览,和word文档一样,有两种预览模式
|
||||
图片预览模式预览效果如下
|
||||

|
||||

|
||||
pdf预览模式预览效果如下
|
||||

|
||||

|
||||
|
||||
#### 6. excel文档预览
|
||||
支持xls,xlsx文档预览,预览效果如下
|
||||

|
||||

|
||||
|
||||
#### 7. 压缩文件预览
|
||||
支持zip,rar,jar,tar,gzip等压缩包,预览效果如下
|
||||

|
||||

|
||||
可点击压缩包中的文件名,直接预览文件,预览效果如下
|
||||

|
||||

|
||||
|
||||
#### 8. 多媒体文件预览
|
||||
理论上支持所有的视频、音频文件,由于无法枚举所有文件格式,默认开启的类型如下
|
||||
mp3,wav,mp4,flv
|
||||
视频预览效果如下
|
||||

|
||||

|
||||
音频预览效果如下
|
||||

|
||||

|
||||
|
||||
#### 9. CAD文档预览
|
||||
支持CAD dwg文档预览,和word文档一样,有两种预览模式
|
||||
图片预览模式预览效果如下
|
||||

|
||||

|
||||
pdf预览模式预览效果如下
|
||||

|
||||

|
||||
|
||||
#### 10. Excel文件纯前端渲染效果
|
||||
|
||||

|
||||
|
||||
#### 11. 流程图bpmn文件预览效果
|
||||
|
||||

|
||||
|
||||
#### 12. 3D模型文件预览效果:
|
||||
|
||||

|
||||
|
||||
#### 13. dcm医疗数位影像文件预览效果:
|
||||
|
||||

|
||||
|
||||
#### 14. drawio流程图预览效果:
|
||||
|
||||

|
||||
|
||||
考虑说明篇幅原因,就不贴其他格式文件的预览效果了,感兴趣的可以参考下面的实例搭建下
|
||||
|
||||
### 快速开始
|
||||
@@ -116,6 +135,7 @@ pdf预览模式预览效果如下
|
||||
- freemarker
|
||||
- redisson
|
||||
- jodconverter
|
||||
|
||||
> 依赖外部环境
|
||||
- redis (可选,默认不用)
|
||||
- OpenOffice 或者 LibreOffice( Windows 下已内置,Linux 脚本启动模式会自动安装,Mac OS 下需要手动安装)
|
||||
@@ -129,7 +149,131 @@ pdf预览模式预览效果如下
|
||||
|
||||
### 历史更新记录
|
||||
|
||||
> 2022年12月14日,v4.1.0 版本发布 :
|
||||
#### > 2025年01月16日,v4.4.0 版本发布 :
|
||||
|
||||
### 新增功能
|
||||
1. xlsx 新增支持打印功能
|
||||
2. 配置文件新增启用 GZIP 压缩
|
||||
3. CAD 格式新增支持转换成 SVG 和 TIF 格式,新增超时结束、线程管理
|
||||
4. 新增删除文件使用验证码校验
|
||||
5. 新增 xbrl 格式预览支持
|
||||
6. PDF 预览新增控制签名、绘图、插图控制、搜索定位页码、定义显示内容等功能
|
||||
7. 新增 CSV 格式前端解析支持
|
||||
8. 新增 ARM64 下的 Docker 镜像支持
|
||||
9. 新增 Office 预览支持转换超时属性设置功能
|
||||
10. 新增预览文件 host 黑名单机制
|
||||
|
||||
### 优化
|
||||
1. 优化 OFD 移动端预览 页面不自适应
|
||||
2. 更新 xlsx 前端解析组件,加速解析速度
|
||||
3. 升级 CAD 组件
|
||||
4. office 功能调整,支持批注、转换页码限制、生成水印等功能
|
||||
5. 升级 markdown 组件
|
||||
6. 升级 dcm 解析组件
|
||||
7. 升级 PDF.JS 解析组件
|
||||
8. 更换视频播放插件为 ckplayer
|
||||
9. tif 解析更加智能化,支持被修改的图片格式
|
||||
10. 针对大小文本文件检测字符编码的正确率,处理并发隐患
|
||||
11. 重构下载文件的代码,新增通用的文件服务器认证访问的设计
|
||||
12. 更新 bootstrap 组件,并精简掉不需要的文件
|
||||
13. 更新 epub 版本,优化 epub 显示效果
|
||||
14. 解决定时清除缓存时,对于多媒体类型文件,只删除了磁盘缓存文件
|
||||
15. 自动检测已安装 Office 组件,增加 LibreOffice 7.5 & 7.6 版本默认路径
|
||||
16. 修改 drawio 默认为预览模式
|
||||
17. 新增 PDF 线程管理、超时管理、内存缓存管理,更新 PDF 解析组件版本
|
||||
18. 优化 Dockerfile,支持真正的跨平台构建镜像
|
||||
|
||||
### 修复
|
||||
1. 修复 forceUpdatedCache 属性设置,但本地缓存文件不更新的问题
|
||||
2. 修复 PDF 解密加密文件转换成功后后台报错的问题
|
||||
3. 修复 BPMN 不支持跨域的问题
|
||||
4. 修复压缩包二级反代特殊符号错误问题
|
||||
5. 修复视频跨域配置导致视频无法预览的问题
|
||||
6. 修复 TXT 文本类分页二次加载问题
|
||||
7. 修复 Drawio 缺少 Base64 组件的问题
|
||||
8. 修复 Markdown 被转义问题
|
||||
9. 修复 EPUB 跨域报错问题
|
||||
10. 修复 URL 特殊符号问题
|
||||
11. 修复压缩包穿越漏洞
|
||||
12. 修复压缩获取路径错误、图片合集路径错误、水印问题等 BUG
|
||||
13. 修复前端解析 XLSX 包含 EMF 格式文件错误问题
|
||||
|
||||
#### > 2023年07月05日,v4.3.0 版本发布 :
|
||||
|
||||
#### 新增功能:
|
||||
1. 新增dcm等医疗数位影像预
|
||||
2. 新增drawio绘图预览
|
||||
3. 新增开启缓存的情况下重新生成的命令 &forceUpdatedCache=true
|
||||
4. 新增dwg CAD文件预览
|
||||
5. 新增PDF文件支持密码功能
|
||||
6. 新增PDF文件生成图片的dpi自定义配置
|
||||
7. 新增删除转换后OFFICE、CAD、TIFF、压缩包源文件配置 默认开启 节约磁盘空间
|
||||
8. 新增前端解析xlsx方法
|
||||
9. 新增pages,eps, iges , igs, dwt, dng, ifc, dwfx, stl, cf2, plt等格式支持
|
||||
|
||||
#### 优化:
|
||||
1. 调整生成的PDF文件 文件名称添加文件后缀 防止生成同名文件
|
||||
2. 调整SQL文件预览方式
|
||||
3. 优化OFD预览兼容性
|
||||
4. 美化TXT文本 分页框的显示
|
||||
5. 升级Linux、Docker版内置office为LibreOffice-7.5.3版本
|
||||
6. 升级Windows版内置office为LibreOffice-7.5.3 Portable版本
|
||||
7. 其他功能优化
|
||||
|
||||
#### 修复:
|
||||
1. 修复反代情况下压缩包获取路径错误
|
||||
2. 修复预览图片的url中如果包含&会导致.click报错
|
||||
3. 修复OFD预览部分已知问题
|
||||
4. 修复预览压缩包时,如果点击的是文件目录(树节点),页面会报错
|
||||
5. 其他已知问题修复
|
||||
|
||||
#### > 2023年04月18日,v4.2.1 版本发布 :
|
||||
|
||||
#### 更新日志:
|
||||
|
||||
1. 修复 dwg 文件预览报空指针的 bug
|
||||
|
||||
#### > 2023年04月13日,v4.2.0 版本发布 :
|
||||
|
||||
#### 新增功能:
|
||||
1. 新增 SVG 格式文件预览支持
|
||||
2. 新增加密的 Office 文件预览支持
|
||||
3. 新增加密的 zip、rar 等压缩包文件预览支持
|
||||
4. 新增 xmind 软件模型文件预览支持
|
||||
5. 新增 bpmn 工作流模型文件预览支持
|
||||
6. 新增 eml 邮件文件预览支持
|
||||
7. 新增 epub 电子书文件预览支持
|
||||
8. 新增 dotm,ett,xlt,xltm,wpt,dot,xlam,xla,dotx 等格式的办公文档预览支持
|
||||
9. 新增 obj, 3ds, stl, ply, gltf, glb, off, 3dm, fbx, dae, wrl, 3mf, ifc, brep, step, iges, fcstd, bim 等 3D 模型文件预览支持
|
||||
10. 新增可配置限制高风险文件上传的功能,比如 exe 文件
|
||||
11. 新增可配置站点的备案信息
|
||||
12. 新增演示站点删除文件需要密码的功能
|
||||
|
||||
#### 优化:
|
||||
1. 文本文档预览加入缓存
|
||||
2. 美化 404、500 报错页
|
||||
3. 优化发票等 ofd 文件预览的印证渲染兼容性
|
||||
4. 移除 office-plugin 模块, 使用新版 jodconverter组件
|
||||
5. 优化 Excel 文件的预览效果
|
||||
6. 优化 CAD 文件的预览效果
|
||||
7. 更新 xstream 、junrar、pdfbox 等依赖的版本
|
||||
8. 更新 TIF 文件转换 PDF 的插件,添加转换缓存
|
||||
9. 优化演示页 UI 部署
|
||||
10. 压缩包文件预览支持目录
|
||||
|
||||
#### 修复:
|
||||
1. 修复部分接口 XSS 问题
|
||||
2. 修复控制台打印的演示地址不跟着 content-path 配置走的问题
|
||||
3. 修复 ofd 文件预览跨域问题
|
||||
4. 修复内部自签证书 https 协议 url 文件无法下载的问题
|
||||
5. 修复特殊符号的文件无法删除的问题
|
||||
6. 修复 PDF 转图片,内存无法回收导致的 OOM
|
||||
7. 修复 xlsx7.4 以上版本文件预览乱码的问题
|
||||
8. 修复 TrustHostFilter 未拦截跨域接口的问题,这是一个安全问题,有使用到 TrustHost 功能的务必升级
|
||||
9. 修复压缩包文件预览在 Linux 系统下文件名乱码的问题
|
||||
10. 修复 ofd 文件预览页码只能显示10页的问题
|
||||
|
||||
#### > 2022年12月14日,v4.1.0 版本发布 :
|
||||
|
||||
1. 全新首页视觉 @wsd7747
|
||||
2. tif图片预览兼容多页tif的pdf转换、jpg转换,以及jpg在线多页预览功能 @zhangzhen1979
|
||||
@@ -142,7 +286,7 @@ pdf预览模式预览效果如下
|
||||
|
||||
感谢 @yl-yue @wsd7747 @zhangzhen1979 @tomhusky @shenghuadun @kischn.sun 的代码贡献
|
||||
|
||||
> 2021年7月6日,v4.0.0 版本发布 :
|
||||
#### > 2021年7月6日,v4.0.0 版本发布 :
|
||||
|
||||
1. 底层集成OpenOffice替换为LibreOffice,Office文件兼容性增强,预览效果提升
|
||||
2. 修复压缩文件目录穿越漏洞
|
||||
@@ -153,7 +297,7 @@ pdf预览模式预览效果如下
|
||||
7. 优化Windows环境下,查找Office组件逻辑(内置的LibreOffice优先)
|
||||
8. 优化启动Office进程改同步执行
|
||||
|
||||
> 2021年6月17日,v3.6.0 版本发布 :
|
||||
#### > 2021年6月17日,v3.6.0 版本发布 :
|
||||
|
||||
ofd 类型文件支持版本,本次版本重要功能均由社区开发贡献,感谢 @gaoxingzaq、@zhangxiaoxiao9527 的代码贡献
|
||||
1. 新增 ofd 类型文件预览支持,ofd 是国产的类似 pdf 格式的文件
|
||||
@@ -161,7 +305,7 @@ ofd 类型文件支持版本,本次版本重要功能均由社区开发贡献
|
||||
3. 美化了 ppt、pptx 类型文件预览效果,比之前版本好看太多
|
||||
4. 更新了 pdfbox、xstream、common-io 等依赖的版本
|
||||
|
||||
> 2021年1月28日 :
|
||||
#### > 2021年1月28日 :
|
||||
|
||||
2020农历年最后一个版本发布,主要包含了部分 UI 改进,和解决了 QQ 群友、 Issue 里反馈的 Bug 修复,最最重要的是发个新版,过个好年
|
||||
|
||||
@@ -184,7 +328,7 @@ ofd 类型文件支持版本,本次版本重要功能均由社区开发贡献
|
||||
17. escaping of dangerous characters to prevent reflected xss
|
||||
18. 修复重复编码导致文档转图片预览失败的问题&编码规范
|
||||
|
||||
> 2020年12月27日 :
|
||||
#### > 2020年12月27日 :
|
||||
|
||||
2020年年终大版本更新,架构全面设计,代码全面重构,代码质量全面提升,二次开发更便捷,欢迎拉源码品鉴,提issue、pr共同建设
|
||||
|
||||
@@ -204,7 +348,7 @@ ofd 类型文件支持版本,本次版本重要功能均由社区开发贡献
|
||||
14. 修复压缩包里文件再次预览失败的bug
|
||||
15. 修复图片预览的bug
|
||||
|
||||
> 2020年05月20日 :
|
||||
#### > 2020年05月20日 :
|
||||
1. 新增支持全局水印,并支持通过参数动态改变水印内容
|
||||
2. 新增支持CAD文件预览
|
||||
3. 新增base.url配置,支持使用nginx反向代理和使用context-path
|
||||
@@ -222,35 +366,35 @@ ofd 类型文件支持版本,本次版本重要功能均由社区开发贡献
|
||||
15. 官网建设:[https://kkview.cn](https://kkview.cn/)
|
||||
16. 官方Docker镜像仓库建设:[https://hub.docker.com/r/keking/kkfileview](https://hub.docker.com/r/keking/kkfileview)
|
||||
|
||||
> 2019年06月18日 :
|
||||
#### > 2019年06月18日 :
|
||||
1. 支持自动清理缓存及预览文件
|
||||
2. 支持http/https下载流url文件预览
|
||||
3. 支持FTP url文件预览
|
||||
4. 加入Docker构建
|
||||
|
||||
> 2019年04月08日 :
|
||||
#### > 2019年04月08日 :
|
||||
1. 缓存及队列实现抽象,提供JDK和REDIS两种实现(REDIS成为可选依赖)
|
||||
2. 打包方式提供zip和tar.gz包,并提供一键启动脚本
|
||||
|
||||
> 2018年01月19日 :
|
||||
#### > 2018年01月19日 :
|
||||
|
||||
1. 大文件入队提前处理
|
||||
1. 新增addTask文件转换入队接口
|
||||
1. 采用redis队列,支持kkFIleView接口和异构系统入队两种方式
|
||||
|
||||
> 2018年01月17日 :
|
||||
#### > 2018年01月17日 :
|
||||
|
||||
1. 优化项目结构,抽象文件预览接口,更方便的加入更多的文件类型预览支持,方便二次开发
|
||||
1. 新增英文文档说明(@幻幻Fate,@汝辉)贡献
|
||||
1. 新增图片预览文件支持类型
|
||||
1. 修复压缩包内轮播图片总是从第一张开始的问题
|
||||
|
||||
> 2018年01月12日 :
|
||||
#### > 2018年01月12日 :
|
||||
|
||||
1. 新增多图片同时预览
|
||||
1. 支持压缩包内图片轮番预览
|
||||
|
||||
> 2018年01月02日 :
|
||||
#### > 2018年01月02日 :
|
||||
|
||||
1. 修复txt等文本编码问题导致预览乱码
|
||||
1. 修复项目模块依赖引入不到的问题
|
||||
@@ -265,14 +409,15 @@ xmind 引用于 [ xmind-embed-viewer](https://github.com/xmindltd/xmind-embed-v
|
||||
epub 引用于 [ epub.js](https://github.com/futurepress/epub.js) 开源协议 BSD许可证
|
||||
压缩包 引用于 [sevenzipjbinding](https://github.com/borisbrodski/sevenzipjbinding )开源协议LGPL
|
||||
3D 引用于 [Online3DViewer](https://github.com/kovacsv/Online3DViewer )开源协议MIT
|
||||
drawio 引用于 [drawio](https://github.com/jgraph/drawio )开源协议 Apache-2.0
|
||||
bpmn流程图 引用于 [bpmn-js](https://github.com/bpmn-io/bpmn-js ) 自定义协议 保留水印 具体自行查看
|
||||
dcm医疗数位影像 引用于 [dcmjs](https://github.com/dcmjs-org/dcmjs )开源协议MIT
|
||||
|
||||
### 使用登记
|
||||
如果这个项目解决了你的实际问题,可在 https://gitee.com/kekingcn/file-online-preview/issues/IGSBV
|
||||
登记下,如果节省了你的三方预览服务费用,也愿意支持下的话,可点击下方【捐助】请作者喝杯咖啡,也是非常感谢
|
||||
|
||||
### Stars 趋势图
|
||||
#### Gitee
|
||||
[](https://whnb.wang/kekingcn/file-online-preview?e=86400)
|
||||
### Stars
|
||||
|
||||
#### GitHub
|
||||
|
||||
|
||||
174
SECURITY_CONFIG.md
Normal file
@@ -0,0 +1,174 @@
|
||||
# kkFileView 安全配置指南
|
||||
|
||||
## ⚠️ 重要安全更新
|
||||
|
||||
从 4.4.0 之后版本开始,kkFileView 增强了安全性,默认拒绝所有未配置的外部文件预览请求,以防止 SSRF(服务器端请求伪造)攻击。
|
||||
|
||||
## 🔒 安全配置说明
|
||||
|
||||
### 1. 信任主机白名单配置(推荐)
|
||||
|
||||
在 `application.properties` 中配置允许预览的域名:
|
||||
|
||||
```properties
|
||||
# 方式1:通过配置文件
|
||||
trust.host = kkview.cn,yourdomain.com,cdn.example.com
|
||||
|
||||
# 方式2:通过环境变量
|
||||
KK_TRUST_HOST=kkview.cn,yourdomain.com,cdn.example.com
|
||||
```
|
||||
|
||||
**示例场景**:
|
||||
- 只允许预览来自 `oss.aliyuncs.com` 和 `cdn.example.com` 的文件
|
||||
```properties
|
||||
trust.host = oss.aliyuncs.com,cdn.example.com
|
||||
```
|
||||
|
||||
### 2. 允许所有主机(不推荐,仅测试环境)
|
||||
|
||||
```properties
|
||||
trust.host = *
|
||||
```
|
||||
|
||||
⚠️ **警告**:此配置会允许访问任意外部地址,存在安全风险,仅应在测试环境使用!
|
||||
|
||||
### 3. 黑名单配置(高级)
|
||||
|
||||
禁止特定域名或内网地址:
|
||||
|
||||
```properties
|
||||
# 禁止访问内网地址(强烈推荐)
|
||||
not.trust.host = localhost,127.0.0.1,192.168.*,10.*,172.16.*,169.254.*
|
||||
|
||||
# 禁止特定恶意域名
|
||||
not.trust.host = malicious-site.com,spam-domain.net
|
||||
```
|
||||
|
||||
**优先级**:黑名单 > 白名单
|
||||
|
||||
### 4. Docker 环境配置
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
-e KK_TRUST_HOST=yourdomain.com,cdn.example.com \
|
||||
-e KK_NOT_TRUST_HOST=localhost,127.0.0.1 \
|
||||
-p 8012:8012 \
|
||||
keking/kkfileview:4.4.0
|
||||
```
|
||||
|
||||
## 🛡️ 安全最佳实践
|
||||
|
||||
### ✅ 推荐配置
|
||||
|
||||
```properties
|
||||
# 1. 明确配置信任主机白名单
|
||||
trust.host = your-cdn.com,your-storage.com
|
||||
|
||||
# 2. 配置黑名单防止内网访问
|
||||
not.trust.host = localhost,127.0.0.1,192.168.*,10.*,172.16.*
|
||||
|
||||
# 3. 禁用文件上传(生产环境)
|
||||
file.upload.disable = true
|
||||
|
||||
# 4. 配置基础URL(使用反向代理时)
|
||||
base.url = https://preview.yourdomain.com
|
||||
```
|
||||
|
||||
### ❌ 不推荐配置
|
||||
|
||||
```properties
|
||||
# 危险:允许所有主机访问
|
||||
trust.host = *
|
||||
|
||||
# 危险:启用文件上传(生产环境)
|
||||
file.upload.disable = false
|
||||
```
|
||||
|
||||
## 🔍 配置验证
|
||||
|
||||
### 测试白名单是否生效
|
||||
|
||||
1. 配置白名单:
|
||||
```properties
|
||||
trust.host = kkview.cn
|
||||
```
|
||||
|
||||
2. 尝试预览白名单内的文件:
|
||||
```
|
||||
http://localhost:8012/onlinePreview?url=https://kkview.cn/test.pdf
|
||||
✅ 应该可以正常预览
|
||||
```
|
||||
|
||||
3. 尝试预览白名单外的文件:
|
||||
```
|
||||
http://localhost:8012/onlinePreview?url=https://other-domain.com/test.pdf
|
||||
❌ 应该被拒绝,显示"不信任的文件源"
|
||||
```
|
||||
|
||||
### 测试黑名单是否生效
|
||||
|
||||
1. 配置黑名单:
|
||||
```properties
|
||||
not.trust.host = localhost,127.0.0.1
|
||||
```
|
||||
|
||||
2. 尝试访问本地文件:
|
||||
```
|
||||
http://localhost:8012/getCorsFile?urlPath=http://127.0.0.1:8080/admin
|
||||
❌ 应该被拒绝
|
||||
```
|
||||
|
||||
## 📋 常见问题
|
||||
|
||||
### Q1: 升级后无法预览文件了?
|
||||
|
||||
**原因**:新版本默认拒绝未配置的主机。
|
||||
|
||||
**解决**:在配置文件中添加信任主机列表:
|
||||
```properties
|
||||
trust.host = your-file-server.com
|
||||
```
|
||||
|
||||
### Q2: 如何临时恢复旧版本行为?
|
||||
|
||||
**不推荐**,但如果确实需要:
|
||||
```properties
|
||||
trust.host = *
|
||||
```
|
||||
|
||||
### Q3: 配置了白名单但还是无法访问?
|
||||
|
||||
检查以下几点:
|
||||
1. 域名是否完全匹配(区分大小写)
|
||||
2. 是否配置了黑名单,黑名单优先级更高
|
||||
3. 查看日志中的 WARNING 信息
|
||||
4. 确认环境变量是否正确设置
|
||||
|
||||
### Q4: 如何允许子域名?
|
||||
|
||||
已支持通配符域名匹配,可使用 `*.example.com`:
|
||||
```properties
|
||||
trust.host = *.example.com
|
||||
```
|
||||
|
||||
说明:
|
||||
- `*.example.com` 会匹配 `cdn.example.com`、`api.internal.example.com`,但不匹配根域 `example.com`
|
||||
- 对于 IP 风格通配(如 `192.168.*`、`10.*`),仅匹配字面量 IPv4 地址,不匹配域名
|
||||
|
||||
## 🚨 安全事件响应
|
||||
|
||||
如果发现可疑的预览请求:
|
||||
|
||||
1. 检查日志文件,搜索 "拒绝访问主机" 关键字
|
||||
2. 确认 `trust.host` 配置是否合理
|
||||
3. 检查是否有异常的网络请求
|
||||
4. 如发现攻击行为,及时更新黑名单配置
|
||||
|
||||
## 📞 获取帮助
|
||||
|
||||
- GitHub Issues: https://github.com/kekingcn/kkFileView/issues
|
||||
- Gitee Issues: https://gitee.com/kekingcn/file-online-preview/issues
|
||||
|
||||
---
|
||||
|
||||
**安全提示**:定期检查和更新信任主机列表,遵循最小权限原则。
|
||||
BIN
doc/img/preview/preview-3ds.png
Normal file
|
After Width: | Height: | Size: 150 KiB |
BIN
doc/img/preview/preview-audio.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
doc/img/preview/preview-bpmn.png
Normal file
|
After Width: | Height: | Size: 135 KiB |
BIN
doc/img/preview/preview-cad-image.png
Normal file
|
After Width: | Height: | Size: 138 KiB |
BIN
doc/img/preview/preview-cad-pdf.png
Normal file
|
After Width: | Height: | Size: 138 KiB |
BIN
doc/img/preview/preview-dcm.png
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
doc/img/preview/preview-doc-image.png
Normal file
|
After Width: | Height: | Size: 159 KiB |
BIN
doc/img/preview/preview-doc-pdf.png
Normal file
|
After Width: | Height: | Size: 219 KiB |
BIN
doc/img/preview/preview-drawio.png
Normal file
|
After Width: | Height: | Size: 320 KiB |
BIN
doc/img/preview/preview-image.png
Normal file
|
After Width: | Height: | Size: 68 KiB |
BIN
doc/img/preview/preview-pdf-image.png
Normal file
|
After Width: | Height: | Size: 187 KiB |
BIN
doc/img/preview/preview-pdf-pdf.png
Normal file
|
After Width: | Height: | Size: 254 KiB |
BIN
doc/img/preview/preview-ppt-image.png
Normal file
|
After Width: | Height: | Size: 392 KiB |
BIN
doc/img/preview/preview-ppt-pdf.png
Normal file
|
After Width: | Height: | Size: 211 KiB |
BIN
doc/img/preview/preview-text.png
Normal file
|
After Width: | Height: | Size: 96 KiB |
BIN
doc/img/preview/preview-video.png
Normal file
|
After Width: | Height: | Size: 469 KiB |
BIN
doc/img/preview/preview-xls.png
Normal file
|
After Width: | Height: | Size: 96 KiB |
BIN
doc/img/preview/preview-xlsx-web.png
Normal file
|
After Width: | Height: | Size: 166 KiB |
BIN
doc/img/preview/preview-xmind.png
Normal file
|
After Width: | Height: | Size: 252 KiB |
BIN
doc/img/preview/preview-zip-inner.png
Normal file
|
After Width: | Height: | Size: 236 KiB |
BIN
doc/img/preview/preview-zip.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
28
docker/kkfileview-base/Dockerfile
Normal file
@@ -0,0 +1,28 @@
|
||||
FROM ubuntu:24.04
|
||||
|
||||
RUN sed -i 's@//.*archive.ubuntu.com@//mirrors.aliyun.com@g' /etc/apt/sources.list.d/ubuntu.sources &&\
|
||||
sed -i 's@//security.ubuntu.com@//mirrors.aliyun.com@g' /etc/apt/sources.list.d/ubuntu.sources &&\
|
||||
sed -i 's@//ports.ubuntu.com@//mirrors.aliyun.com@g' /etc/apt/sources.list.d/ubuntu.sources &&\
|
||||
apt-get update &&\
|
||||
export DEBIAN_FRONTEND=noninteractive &&\
|
||||
apt-get install -y --no-install-recommends openjdk-21-jre tzdata locales xfonts-utils fontconfig libreoffice-nogui &&\
|
||||
echo 'Asia/Shanghai' > /etc/timezone &&\
|
||||
ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime &&\
|
||||
localedef -i zh_CN -c -f UTF-8 -A /usr/share/locale/locale.alias zh_CN.UTF-8 &&\
|
||||
locale-gen zh_CN.UTF-8 &&\
|
||||
apt-get install -y --no-install-recommends ttf-mscorefonts-installer &&\
|
||||
apt-get install -y --no-install-recommends ttf-wqy-microhei ttf-wqy-zenhei xfonts-wqy &&\
|
||||
apt-get autoremove -y &&\
|
||||
apt-get clean &&\
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# 内置一些常用的中文字体,避免普遍性乱码
|
||||
ADD fonts/* /usr/share/fonts/chinese/
|
||||
|
||||
RUN cd /usr/share/fonts/chinese &&\
|
||||
# 安装字体
|
||||
mkfontscale &&\
|
||||
mkfontdir &&\
|
||||
fc-cache -fv
|
||||
|
||||
ENV LANG=zh_CN.UTF-8 LC_ALL=zh_CN.UTF-8
|
||||
50
docker/kkfileview-base/README.cn.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# 构建说明
|
||||
|
||||
由于 kkfileview 的基础运行环境很少变动且制作耗时较久,而 kkfileview 本身代码开发会频繁改动,因此把制作其 Docker 镜像的步骤拆分为两次:
|
||||
|
||||
首先制作 kkfileview 的基础镜像(kkfileview-base)。
|
||||
|
||||
然后使用 kkfileview-base 作为基础镜像进行构建,加快 kkfileview docker 镜像构建与发布。
|
||||
|
||||
执行如下命令即可构建基础镜像:
|
||||
> 这里镜像 tag 以 4.4.0 为例,本项目所维护的 Dockerfile 文件考虑了跨平台兼容性。 如果你需要用到 arm64 架构镜像, 则在arm64 架构机器上同样执行下面的构建命令即可
|
||||
|
||||
```shell
|
||||
docker build --tag keking/kkfileview-base:4.4.0 .
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 跨平台构建
|
||||
|
||||
`docker buildx` 支持在一台机器上构建出多种平台架构的镜像。推荐使用该能力进行跨平台的镜像构建。
|
||||
例如,执行 `docker buildx build` 命令时加上 `--platform=linux/arm64` 参数即可构建出 arm64 架构镜像。这极大方便了那些没有arm64 架构机器却想构建 arm64 架构镜像的用户。
|
||||
|
||||
> 当前本项目仅支持构建 linux/amd64 和 linux/arm64 两种平台架构的镜像
|
||||
> buildx 的 builder driver 可以使用默认的 `docker` 类型, 若使用 `docker-container` 类型可以支持并行构建多种架构, 本文不再赘述, 有兴趣可以自行了解。参考 [Docker Buildx | Docker Documentation](https://docs.docker.com/buildx/working-with-buildx/#build-multi-platform-images)
|
||||
|
||||
**前提要求**
|
||||
|
||||
以当前机器为 amd64 (x86_64)架构为例。需要开启 docker 的 buildx 特性,以及开启 Linux 的 QEMU 用户模式:
|
||||
|
||||
> 使用 WSL2 的 Windows 用户如果安装了最新的 DockerDesktop, 则这些前提要求已满足, 无需额外下述设置。
|
||||
|
||||
1. 安装 docker buildx 客户端插件:
|
||||
> docker 版本要求 >=19.03
|
||||
|
||||
若已安装, 则跳过。详情参考 https://github.com/docker/buildx
|
||||
|
||||
2. 开启 QEMU 的用户模式功能, 并安装其它平台的模拟器:
|
||||
> Linux 内核要求 >=4.8
|
||||
|
||||
使用 `tonistiigi/binfmt` 镜像可快速开启并安装模拟器,执行下面命令:
|
||||
|
||||
```shell
|
||||
docker run --privileged --rm tonistiigi/binfmt --install all
|
||||
```
|
||||
|
||||
现在就可以愉快地开始构建了,构建命令示例:
|
||||
|
||||
```shell
|
||||
docker buildx build --platform=linux/amd64,linux/arm64 -t keking/kkfileview-base:4.4.0 --push .
|
||||
```
|
||||
53
docker/kkfileview-base/README.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# Build Instructions
|
||||
|
||||
Since the base runtime environment for kkfileview rarely changes and takes a long time to build, while the kkfileview code itself is frequently updated, the process of building its Docker image is split into two steps:
|
||||
|
||||
First, create the base image for kkfileview (kkfileview-base).
|
||||
|
||||
Then, use kkfileview-base as the base image to build and speed up the kkfileview Docker image build and release process.
|
||||
|
||||
To build the base image, run the following command:
|
||||
|
||||
> In this example, the image tag is 4.4.0. The Dockerfile maintained in this project considers cross-platform compatibility. If you need an arm64 architecture image, run the same build command on an arm64 architecture machine.
|
||||
|
||||
```shell
|
||||
docker build --tag keking/kkfileview-base:4.4.0 .
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Cross-Platform Build
|
||||
|
||||
`docker buildx` supports building images for multiple platform architectures on a single machine. It is recommended to use this capability for cross-platform image builds.
|
||||
For example, adding the `--platform=linux/arm64` parameter when executing the `docker buildx build` command will allow you to build an arm64 architecture image. This is particularly convenient for users who want to build arm64 images but don't have an arm64 machine.
|
||||
|
||||
> Currently, this project only supports building images for the linux/amd64 and linux/arm64 architectures.
|
||||
> The buildx builder driver can use the default `docker` type, but if you use the `docker-container` type, you can build multiple architectures in parallel. This README will not cover that in detail, you can learn more on your own. Refer to [Docker Buildx | Docker Documentation](https://docs.docker.com/buildx/working-with-buildx/#build-multi-platform-images)
|
||||
|
||||
**Prerequisites**
|
||||
|
||||
Assuming the current machine is amd64 (x86_64) architecture, you'll need to enable the docker buildx feature and enable Linux QEMU user mode:
|
||||
|
||||
> Windows users with WSL2 who have installed a recent version of Docker Desktop will already meet these prerequisites, so no additional setup is required.
|
||||
|
||||
1. Install the docker buildx client plugin:
|
||||
|
||||
> Docker version >=19.03 is required.
|
||||
|
||||
If it's already installed, you can skip this step. For more details, refer to https://github.com/docker/buildx.
|
||||
|
||||
2. Enable QEMU user mode and install emulators for other platforms:
|
||||
|
||||
> Linux kernel version >=4.8 is required.
|
||||
|
||||
You can quickly enable and install emulators using the tonistiigi/binfmt image by running the following command:
|
||||
|
||||
```shell
|
||||
docker run --privileged --rm tonistiigi/binfmt --install all
|
||||
```
|
||||
|
||||
Now you can enjoy the building. Here’s an example build command:
|
||||
|
||||
```shell
|
||||
docker buildx build --platform=linux/amd64,linux/arm64 -t keking/kkfileview-base:4.4.0 --push .
|
||||
```
|
||||
@@ -1,38 +0,0 @@
|
||||
FROM ubuntu:20.04
|
||||
MAINTAINER chenjh "842761733@qq.com"
|
||||
# 内置一些常用的中文字体,避免普遍性乱码
|
||||
COPY fonts/* /usr/share/fonts/chinese/
|
||||
RUN apt-get clean && apt-get update &&\
|
||||
sed -i 's/http:\/\/archive.ubuntu.com/https:\/\/mirrors.aliyun.com/g' /etc/apt/sources.list &&\
|
||||
sed -i 's/# deb/deb/g' /etc/apt/sources.list &&\
|
||||
apt-get install -y --reinstall ca-certificates &&\
|
||||
apt-get clean && apt-get update &&\
|
||||
apt-get install -y locales language-pack-zh-hans &&\
|
||||
localedef -i zh_CN -c -f UTF-8 -A /usr/share/locale/locale.alias zh_CN.UTF-8 && locale-gen zh_CN.UTF-8 &&\
|
||||
export DEBIAN_FRONTEND=noninteractive &&\
|
||||
apt-get install -y tzdata && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime &&\
|
||||
apt-get install -y fontconfig ttf-mscorefonts-installer ttf-wqy-microhei ttf-wqy-zenhei xfonts-wqy &&\
|
||||
apt-get install -y wget &&\
|
||||
cd /tmp &&\
|
||||
wget https://kkview.cn/resource/server-jre-8u251-linux-x64.tar.gz &&\
|
||||
tar -zxf /tmp/server-jre-8u251-linux-x64.tar.gz && mv /tmp/jdk1.8.0_251 /usr/local/ &&\
|
||||
|
||||
# 安装 libreoffice
|
||||
apt-get install -y libxrender1 libxinerama1 libxt6 libxext-dev libfreetype6-dev libcairo2 libcups2 libx11-xcb1 libnss3 &&\
|
||||
wget https://downloadarchive.documentfoundation.org/libreoffice/old/7.5.3.2/deb/x86_64/LibreOffice_7.5.3.2_Linux_x86-64_deb.tar.gz -cO libreoffice_deb.tar.gz &&\
|
||||
tar -zxf /tmp/libreoffice_deb.tar.gz && cd /tmp/LibreOffice_7.5.3.2_Linux_x86-64_deb/DEBS &&\
|
||||
dpkg -i *.deb &&\
|
||||
|
||||
# 清理临时文件
|
||||
rm -rf /tmp/* && rm -rf /var/lib/apt/lists/* &&\
|
||||
cd /usr/share/fonts/chinese &&\
|
||||
mkfontscale &&\
|
||||
mkfontdir &&\
|
||||
fc-cache -fv
|
||||
|
||||
ENV JAVA_HOME /usr/local/jdk1.8.0_251
|
||||
ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
|
||||
ENV PATH $PATH:$JAVA_HOME/bin
|
||||
ENV LANG zh_CN.UTF-8
|
||||
ENV LC_ALL zh_CN.UTF-8
|
||||
CMD ["/bin/bash"]
|
||||
@@ -1,2 +0,0 @@
|
||||
# 执行如下命令构建基础镜像,加快kkfileview docker镜像构建与发布
|
||||
docker build --tag keking/kkfileview-jdk:4.3.0 .
|
||||
23
pom.xml
@@ -6,23 +6,23 @@
|
||||
|
||||
<groupId>cn.keking</groupId>
|
||||
<artifactId>kkFileView-parent</artifactId>
|
||||
<version>4.3.0</version>
|
||||
<version>4.4.0</version>
|
||||
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
<java.version>21</java.version>
|
||||
<jodconverter.version>4.4.6</jodconverter.version>
|
||||
<spring.boot.version>2.4.2</spring.boot.version>
|
||||
<spring.boot.version>3.5.6</spring.boot.version>
|
||||
<poi.version>5.2.2</poi.version>
|
||||
<xdocreport.version>1.0.6</xdocreport.version>
|
||||
<xstream.version>1.4.20</xstream.version>
|
||||
<junrar.version>7.5.4</junrar.version>
|
||||
<redisson.version>3.2.0</redisson.version>
|
||||
<junrar.version>7.5.5</junrar.version>
|
||||
<redisson.version>3.22.0</redisson.version>
|
||||
<sevenzipjbinding.version>16.02-2.01</sevenzipjbinding.version>
|
||||
<jchardet.version>1.0</jchardet.version>
|
||||
<antlr.version>2.7.7</antlr.version>
|
||||
<concurrentlinkedhashmap.version>1.4.2</concurrentlinkedhashmap.version>
|
||||
<rocksdb.version>5.17.2</rocksdb.version>
|
||||
<pdfbox.version>2.0.27</pdfbox.version>
|
||||
<pdfbox.version>3.0.2</pdfbox.version>
|
||||
<jai-imageio.version>1.4.0</jai-imageio.version>
|
||||
<jbig2-imageio.version>3.0.4</jbig2-imageio.version>
|
||||
<galimatias.version>0.2.1</galimatias.version>
|
||||
@@ -32,15 +32,18 @@
|
||||
<ffmpeg.version>4.2.1-1.5.2</ffmpeg.version>
|
||||
<itextpdf.version>5.5.13.3</itextpdf.version>
|
||||
<httpclient.version>3.1</httpclient.version>
|
||||
<aspose-cad.version>23.1</aspose-cad.version>
|
||||
<aspose-cad.version>23.9</aspose-cad.version>
|
||||
<bcprov-jdk15on.version>1.70</bcprov-jdk15on.version>
|
||||
<juniversalchardet.version>1.0.3</juniversalchardet.version>
|
||||
<httpcomponents.version>4.5.14</httpcomponents.version>
|
||||
|
||||
<commons-cli.version>1.2</commons-cli.version>
|
||||
<commons-net.version>3.6</commons-net.version>
|
||||
<commons-lang3.version>3.7</commons-lang3.version>
|
||||
<commons-cli.version>1.5.0</commons-cli.version>
|
||||
<commons-net.version>3.9.0</commons-net.version>
|
||||
<commons-lang3.version>3.13.0</commons-lang3.version>
|
||||
|
||||
|
||||
<maven.compiler.source>${java.version}</maven.compiler.source>
|
||||
<maven.compiler.release>${java.version}</maven.compiler.release>
|
||||
<maven.compiler.target>${java.version}</maven.compiler.target>
|
||||
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<artifactId>kkFileView-parent</artifactId>
|
||||
<groupId>cn.keking</groupId>
|
||||
<version>4.3.0</version>
|
||||
<version>4.4.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>kkFileView</artifactId>
|
||||
@@ -44,21 +44,16 @@
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-tomcat</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-jetty</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-freemarker</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
<!-- web end -->
|
||||
|
||||
<!-- poi start -->
|
||||
@@ -99,6 +94,10 @@
|
||||
<version>${xdocreport.version}</version>
|
||||
</dependency>
|
||||
<!-- poi start -->
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents.client5</groupId>
|
||||
<artifactId>httpclient5</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 对 rar5 的支持 和其他众多压缩支持 可参考 package net.sf.sevenzipjbinding.ArchiveFormat; -->
|
||||
<dependency>
|
||||
@@ -122,6 +121,14 @@
|
||||
<artifactId>redisson</artifactId>
|
||||
<version>${redisson.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 编码检测-JUniversalCharDet-->
|
||||
<dependency>
|
||||
<groupId>com.googlecode.juniversalchardet</groupId>
|
||||
<artifactId>juniversalchardet</artifactId>
|
||||
<version>${juniversalchardet.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 解压(rar)-->
|
||||
<dependency>
|
||||
<groupId>com.github.junrar</groupId>
|
||||
@@ -169,6 +176,12 @@
|
||||
<groupId>org.apache.pdfbox</groupId>
|
||||
<artifactId>pdfbox</artifactId>
|
||||
<version>${pdfbox.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>commons-logging</artifactId>
|
||||
<groupId>commons-logging</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.pdfbox</groupId>
|
||||
@@ -314,6 +327,14 @@
|
||||
</resource>
|
||||
</resources>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.11.0</version>
|
||||
<configuration>
|
||||
<parameters>true</parameters>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/2.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd">
|
||||
<id>make-assembly</id>
|
||||
<id>assembly-linux</id>
|
||||
<formats>
|
||||
<format>tar.gz</format>
|
||||
</formats>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/2.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd">
|
||||
<id>make-assembly</id>
|
||||
<id>assembly-windows</id>
|
||||
<formats>
|
||||
<format>zip</format>
|
||||
</formats>
|
||||
|
||||
@@ -8,7 +8,7 @@ install_redhat() {
|
||||
yum install -y libSM.x86_64 libXrender.x86_64 libXext.x86_64
|
||||
yum groupinstall -y "X Window System"
|
||||
yum localinstall -y *.rpm
|
||||
echo 'install finshed...'
|
||||
echo 'install finished...'
|
||||
else
|
||||
echo 'download package error...'
|
||||
fi
|
||||
@@ -20,7 +20,7 @@ install_ubuntu() {
|
||||
if [ $? -eq 0 ];then
|
||||
apt-get install -y libxinerama1 libcairo2 libcups2 libx11-xcb1
|
||||
dpkg -i *.deb
|
||||
echo 'install finshed...'
|
||||
echo 'install finished...'
|
||||
else
|
||||
echo 'download package error...'
|
||||
fi
|
||||
|
||||
@@ -7,4 +7,4 @@ echo Please check log file in ../log/kkFileView.log for more information
|
||||
echo You can get help in our official home site: https://kkview.cn
|
||||
echo If you need further help, please join our kk opensource community: https://t.zsxq.com/09ZHSXbsQ
|
||||
echo If this project is helpful to you, please star it on https://gitee.com/kekingcn/file-online-preview/stargazers
|
||||
java -Dspring.config.location=..\config\application.properties -jar kkFileView-4.3.0.jar -> ..\log\kkFileView.log
|
||||
java -Dspring.config.location=..\config\application.properties -jar kkFileView-4.4.0.jar -> ..\log\kkFileView.log
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
# Description: v1.1:修改进程启动机制为pid形式。
|
||||
#############################
|
||||
#
|
||||
DIR_HOME=("/opt/openoffice.org3" "/opt/libreoffice" "/opt/libreoffice6.1" "/opt/libreoffice7.0" "/opt/libreoffice7.1" "/opt/libreoffice7.2" "/opt/libreoffice7.3" "/opt/libreoffice7.4" "/opt/openoffice4" "/usr/lib/openoffice" "/usr/lib/libreoffice")
|
||||
DIR_HOME=("/opt/openoffice.org3" "/opt/libreoffice" "/opt/libreoffice6.1" "/opt/libreoffice7.0" "/opt/libreoffice7.1" "/opt/libreoffice7.2" "/opt/libreoffice7.3" "/opt/libreoffice7.4" "/opt/libreoffice7.5" "/opt/libreoffice7.6" "/opt/libreoffice24.2" "/opt/libreoffice24.8" "/opt/libreoffice25.2" "/opt/openoffice4" "/usr/lib/openoffice" "/usr/lib/libreoffice")
|
||||
FLAG=
|
||||
OFFICE_HOME=
|
||||
KKFILEVIEW_BIN_FOLDER=$(cd "$(dirname "$0")" || exit 1 ;pwd)
|
||||
@@ -29,7 +29,7 @@ if [ -s "${PID_FILE}" ]; then
|
||||
else
|
||||
cd "$KKFILEVIEW_BIN_FOLDER" || exit 1
|
||||
echo "Using KKFILEVIEW_BIN_FOLDER $KKFILEVIEW_BIN_FOLDER"
|
||||
grep 'office\.home' ../config/application.properties | grep '!^#'
|
||||
grep 'office\.home' ../config/application.properties | grep -v '^#' | grep -v 'default'
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Using customized office.home"
|
||||
else
|
||||
@@ -51,12 +51,12 @@ else
|
||||
|
||||
## 启动kkFileView
|
||||
echo "Starting kkFileView..."
|
||||
nohup java -Dfile.encoding=UTF-8 -Dspring.config.location=../config/application.properties -jar kkFileView-4.3.0.jar > ../log/kkFileView.log 2>&1 &
|
||||
nohup java -Dfile.encoding=UTF-8 -Dspring.config.location=../config/application.properties -jar kkFileView-4.4.0.jar > ../log/kkFileView.log 2>&1 &
|
||||
echo "Please execute ./showlog.sh to check log for more information"
|
||||
echo "You can get help in our official home site: https://kkview.cn"
|
||||
echo "If you need further help, please join our kk opensource community: https://t.zsxq.com/09ZHSXbsQ"
|
||||
echo "If this project is helpful to you, please star it on https://gitee.com/kekingcn/file-online-preview/stargazers"
|
||||
PROCESS=$(ps -ef | grep kkFileView | awk 'NR==1{print $2}')
|
||||
PROCESS=$(ps -ef | grep -v grep | grep java | grep kkFileView | awk 'NR==1{print $2}')
|
||||
# 启动成功后将进程号写入pid文件
|
||||
echo "$PROCESS" > "$PID_FILE"
|
||||
fi
|
||||
|
||||
@@ -2,7 +2,13 @@
|
||||
server.port = ${KK_SERVER_PORT:8012}
|
||||
server.servlet.context-path= ${KK_CONTEXT_PATH:/}
|
||||
server.servlet.encoding.charset = utf-8
|
||||
#文件上传限制前端
|
||||
#启用GZIP压缩功能
|
||||
server.compression.enabled = true
|
||||
#允许压缩的响应缓冲区最小字节数,默认2048
|
||||
server.compression.min-response-size = 2048
|
||||
#压缩格式
|
||||
server.compression.mime-types=application/javascript,text/css,application/json,application/xml,text/html,text/xml,text/plain,font/woff,application/font-woff,font/eot,image/svg+xml,image/x-icon
|
||||
# 文件上传限制前端
|
||||
spring.servlet.multipart.max-file-size=500MB
|
||||
#文件上传限制
|
||||
spring.servlet.multipart.max-request-size=500MB
|
||||
@@ -16,29 +22,57 @@ spring.freemarker.expose-request-attributes = true
|
||||
spring.freemarker.expose-session-attributes = true
|
||||
spring.freemarker.request-context-attribute = request
|
||||
spring.freemarker.suffix = .ftl
|
||||
# Spring Boot Actuator 健康检查配置
|
||||
# 开启健康检查端点
|
||||
management.endpoints.web.exposure.include=health,info,metrics
|
||||
# 显示详细的健康检查信息(生产环境建议设置为when-authorized)
|
||||
management.endpoint.health.show-details=always
|
||||
# 启用健康检查组件
|
||||
management.health.defaults.enabled=true
|
||||
|
||||
# office-plugin
|
||||
## office转换服务的进程数,默认开启两个进程
|
||||
|
||||
# office设置
|
||||
#openoffice或LibreOffice home路径
|
||||
#office.home = C:\\Program Files (x86)\\OpenOffice 4
|
||||
office.home = ${KK_OFFICE_HOME:default}
|
||||
## office转换服务的端口,默认开启两个进程
|
||||
office.plugin.server.ports = 2001,2002
|
||||
## office 转换服务 task 超时时间,默认五分钟
|
||||
office.plugin.task.timeout = 5m
|
||||
#此属性设置office进程在重新启动之前可以执行的最大任务数。0表示无限数量的任务(永远不会重新启动)
|
||||
office.plugin.task.maxtasksperprocess = 200
|
||||
#此属性设置处理任务所允许的最长时间。如果任务的处理时间长于此超时,则此任务将中止,并处理下一个任务。
|
||||
office.plugin.task.taskexecutiontimeout = 5m
|
||||
#生成限制 默认不限制 使用方法 (1-5)
|
||||
office.pagerange = ${KK_OFFICE_PAGERANGE:false}
|
||||
#生成水印 默认不启用 使用方法 (kkFileView)
|
||||
office.watermark = ${KK_OFFICE_WATERMARK:false}
|
||||
#OFFICE JPEG图片压缩
|
||||
office.quality = ${KK_OFFICE_QUALITY:80}
|
||||
#图像分辨率限制
|
||||
office.maximageresolution = ${KK_OFFICE_MAXIMAGERESOLUTION:150}
|
||||
#导出书签
|
||||
office.exportbookmarks = ${KK_OFFICE_EXPORTBOOKMARKS:true}
|
||||
#批注作为PDF的注释
|
||||
office.exportnotes = ${KK_OFFICE_EXPORTNOTES:true}
|
||||
#加密文档 生成的PDF文档 添加密码(密码为加密文档的密码)
|
||||
office.documentopenpasswords = ${KK_OFFICE_DOCUMENTOPENPASSWORD:true}
|
||||
#xlsx格式前端解析
|
||||
office.type.web = ${KK_OFFICE_TYPE_WEB:web}
|
||||
|
||||
|
||||
# 其他核心设置
|
||||
#预览生成资源路径(默认为打包根路径下的file目录下)
|
||||
#file.dir = D:\\kkFileview\\
|
||||
file.dir = ${KK_FILE_DIR:default}
|
||||
|
||||
#允许预览的本地文件夹 默认不允许任何本地文件被预览
|
||||
#WINDOWS参考 local.preview.dir = \D:\\kkFileview\\1\\1.txt (注意前面必须添加反斜杠)
|
||||
#LINUX参考 local.preview.dir = /opt/1.txt (注意前面必须是正斜杠)
|
||||
#使用方法 windows file://d:/1/1.txt linux file:/opt/1/1.txt
|
||||
#file 协议参考:https://datatracker.ietf.org/doc/html/rfc8089
|
||||
local.preview.dir = ${KK_LOCAL_PREVIEW_DIR:default}
|
||||
|
||||
|
||||
#openoffice home路径
|
||||
#office.home = C:\\Program Files (x86)\\OpenOffice 4
|
||||
office.home = ${KK_OFFICE_HOME:default}
|
||||
|
||||
#是否启用缓存
|
||||
cache.enabled = ${KK_CACHE_ENABLED:true}
|
||||
#缓存实现类型,不配默认为内嵌RocksDB(type = default)实现,可配置为redis(type = redis)实现(需要配置spring.redisson.address等参数)和 JDK 内置对象实现(type = jdk),
|
||||
cache.type = ${KK_CACHE_TYPE:jdk}
|
||||
#redis连接,只有当cache.type = redis时才有用
|
||||
@@ -48,33 +82,64 @@ spring.redisson.password = ${KK_SPRING_REDISSON_PASSWORD:}
|
||||
cache.clean.enabled = ${KK_CACHE_CLEAN_ENABLED:true}
|
||||
#缓存自动清理时间,cache.clean.enabled = true时才有用,cron表达式,基于Quartz cron
|
||||
cache.clean.cron = ${KK_CACHE_CLEAN_CRON:0 0 3 * * ?}
|
||||
|
||||
#######################################可在运行时动态配置#######################################
|
||||
#提供预览服务的地址,默认从请求url读,如果使用nginx等反向代理,需要手动设置
|
||||
#base.url = https://file.keking.cn
|
||||
base.url = ${KK_BASE_URL:default}
|
||||
|
||||
#信任站点,多个用','隔开,设置了之后,会限制只能预览来自信任站点列表的文件,默认不限制
|
||||
#trust.host = kkview.cn
|
||||
# ========== 安全配置(重要)==========
|
||||
# 信任站点白名单配置,多个用','隔开
|
||||
# ⚠️ 安全提示:为防止SSRF攻击,强烈建议配置信任主机白名单
|
||||
# ⚠️ 如果不配置,系统将默认拒绝所有外部文件预览请求
|
||||
#
|
||||
# 配置示例:
|
||||
# trust.host = kkview.cn,yourdomain.com,cdn.example.com
|
||||
#
|
||||
# 如果需要允许所有域名(不推荐,仅用于测试环境),请设置为:
|
||||
# trust.host = *
|
||||
#
|
||||
# 当前配置:
|
||||
trust.host = ${KK_TRUST_HOST:default}
|
||||
|
||||
#是否启用缓存
|
||||
cache.enabled = ${KK_CACHE_ENABLED:true}
|
||||
|
||||
# 不信任站点黑名单配置,多个用','隔开
|
||||
# 黑名单优先级高于白名单,设置后将禁止预览来自这些站点的文件
|
||||
# 建议配置:禁止访问内网地址和本地地址
|
||||
# not.trust.host = localhost,127.0.0.1,0.0.0.0,192.168.*,10.*,172.16.*
|
||||
not.trust.host= ${KK_NOT_TRUST_HOST:default}
|
||||
#文本类型,默认如下,可自定义添加
|
||||
simText = ${KK_SIMTEXT:txt,html,htm,asp,jsp,xml,json,properties,md,gitignore,log,java,py,c,cpp,sql,sh,bat,m,bas,prg,cmd}
|
||||
|
||||
|
||||
#FTP模块设置
|
||||
#预览源为FTP时 FTP用户名,可在ftp url后面加参数ftp.username=ftpuser指定,不指定默认用配置的
|
||||
ftp.username = ${KK_FTP_USERNAME:ftpuser}
|
||||
#预览源为FTP时 FTP密码,可在ftp url后面加参数ftp.password=123456指定,不指定默认用配置的
|
||||
ftp.password = ${KK_FTP_PASSWORD:123456}
|
||||
#预览源为FTP时, FTP连接默认ControlEncoding(根据FTP服务器操作系统选择,Linux一般为UTF-8,Windows一般为GBK),可在ftp url后面加参数ftp.control.encoding=UTF-8指定,不指定默认用配置的
|
||||
ftp.control.encoding = ${KK_FTP_CONTROL_ENCODING:UTF-8}
|
||||
|
||||
#视频设置
|
||||
#多媒体类型,默认如下,可自定义添加
|
||||
media = ${KK_MEDIA:mp3,wav,mp4,flv}
|
||||
media = ${KK_MEDIA:mp3,wav,mp4,flv,mpd,m3u8,ts,mpeg,m4a}
|
||||
#是否开启多媒体类型转视频格式转换,目前可转换视频格式有:avi,mov,wmv,3gp,rm
|
||||
#请谨慎开启此功能,建议异步调用添加到处理队列,并且增加任务队列处理线程,防止视频转换占用完线程资源,转换比较耗费时间,并且控制了只能串行处理转换任务
|
||||
media.convert.disable = ${KK_MEDIA_CONVERT_DISABLE:false}
|
||||
#支持转换的视频类型
|
||||
convertMedias = ${KK_CONVERTMEDIAS:avi,mov,wmv,mkv,3gp,rm}
|
||||
#office类型文档(word ppt)样式,默认为图片(image),可配置为pdf(预览时也有按钮切换)
|
||||
office.preview.type = ${KK_OFFICE_PREVIEW_TYPE:image}
|
||||
#是否关闭office预览切换开关,默认为false,可配置为true关闭
|
||||
office.preview.switch.disabled = ${KK_OFFICE_PREVIEW_SWITCH_DISABLED:false}
|
||||
|
||||
|
||||
|
||||
#PDF预览模块设置
|
||||
#配置PDF文件生成图片的像素大小,dpi 越高,图片质量越清晰,同时也会消耗更多的计算资源。
|
||||
pdf2jpg.dpi = ${KK_PDF2JPG_DPI:144}
|
||||
#PDF转换超时设置 (低于50页) 温馨提示这里数字仅供参考
|
||||
pdf.timeout =${KK_pdf_TIMEOUT:90}
|
||||
#PDF转换超时设置 (高于50小于200页)
|
||||
pdf.timeout80 =${KK_PDF_TIMEOUT80:180}
|
||||
#PDF转换超时设置 (大于200页)
|
||||
pdf.timeout200 =${KK_PDF_TIMEOUT200:300}
|
||||
#PDF转换线程设置
|
||||
pdf.thread =${KK_PDF_THREAD:5}
|
||||
#是否禁止演示模式
|
||||
pdf.presentationMode.disable = ${KK_PDF_PRESENTATION_MODE_DISABLE:true}
|
||||
#是否禁止打开文件
|
||||
@@ -85,15 +150,13 @@ pdf.print.disable = ${KK_PDF_PRINT_DISABLE:true}
|
||||
pdf.download.disable = ${KK_PDF_DOWNLOAD_DISABLE:true}
|
||||
#是否禁止bookmark
|
||||
pdf.bookmark.disable = ${KK_PDF_BOOKMARK_DISABLE:true}
|
||||
#是否禁用首页文件上传
|
||||
file.upload.disable = ${KK_FILE_UPLOAD_ENABLED:false}
|
||||
#是否禁止签名
|
||||
pdf.disable.editing = ${KK_PDF_DISABLE_EDITING:false}
|
||||
#office类型文档(word ppt)样式,默认为图片(image),可配置为pdf(预览时也有按钮切换)
|
||||
office.preview.type = ${KK_OFFICE_PREVIEW_TYPE:image}
|
||||
#是否关闭office预览切换开关,默认为false,可配置为true关闭
|
||||
office.preview.switch.disabled = ${KK_OFFICE_PREVIEW_SWITCH_DISABLED:false}
|
||||
|
||||
#预览源为FTP时 FTP用户名,可在ftp url后面加参数ftp.username=ftpuser指定,不指定默认用配置的
|
||||
ftp.username = ${KK_FTP_USERNAME:ftpuser}
|
||||
#预览源为FTP时 FTP密码,可在ftp url后面加参数ftp.password=123456指定,不指定默认用配置的
|
||||
ftp.password = ${KK_FTP_PASSWORD:123456}
|
||||
#预览源为FTP时, FTP连接默认ControlEncoding(根据FTP服务器操作系统选择,Linux一般为UTF-8,Windows一般为GBK),可在ftp url后面加参数ftp.control.encoding=UTF-8指定,不指定默认用配置的
|
||||
ftp.control.encoding = ${KK_FTP_CONTROL_ENCODING:UTF-8}
|
||||
|
||||
#水印内容
|
||||
#例:watermark.txt = ${WATERMARK_TXT:凯京科技内部文件,严禁外泄}
|
||||
@@ -118,17 +181,38 @@ watermark.height = ${WATERMARK_HEIGHT:80}
|
||||
#水印倾斜度数,要求设置在大于等于0,小于90
|
||||
watermark.angle = ${WATERMARK_ANGLE:10}
|
||||
|
||||
#Tif类型图片浏览模式:tif(利用前端js插件浏览);jpg(转换为jpg后前端显示);pdf(转换为pdf后显示,便于打印)
|
||||
tif.preview.type = ${KK_TIF_PREVIEW_TYPE:tif}
|
||||
#首页功能设置
|
||||
#是否禁用首页文件上传
|
||||
file.upload.disable = ${KK_FILE_UPLOAD_DISABLE:true}
|
||||
# 备案信息,默认为空
|
||||
beian = ${KK_BEIAN:default}
|
||||
#禁止上传类型
|
||||
prohibit = ${KK_PROHIBIT:exe,dll,dat}
|
||||
#启用验证码删除文件 默认关闭
|
||||
delete.captcha= ${KK_DELETE_CAPTCHA:false}
|
||||
#删除密码
|
||||
delete.password = ${KK_DELETE_PASSWORD:123456}
|
||||
#删除 转换后OFFICE、CAD、TIFF、压缩包源文件 默认开启 节约磁盘空间
|
||||
delete.source.file = ${KK_DELETE_SOURCE_FILE:true}
|
||||
#配置PDF文件生成图片的像素大小,dpi 越高,图片质量越清晰,同时也会消耗更多的计算资源。
|
||||
pdf2jpg.dpi = ${KK_PDF2JPG_DPI:144}
|
||||
#xlsx格式前端解析
|
||||
office.type.web = ${KK_OFFICE_TYPE_WEB:web}
|
||||
#首页初始化加载第一页
|
||||
home.pagenumber = ${DEFAULT_HOME_PAGENUMBER:1}
|
||||
#首页是否分页
|
||||
home.pagination = ${DEFAULT_HOME_PAGINATION:true}
|
||||
#首页初始化单页记录数
|
||||
home.pagesize = ${DEFAULT_HOME_PAGSIZE:15}
|
||||
#首页显示查询框
|
||||
home.search = ${DEFAULT_HOME_SEARCH:true}
|
||||
|
||||
#Tif类型设置
|
||||
#Tif类型图片浏览模式:tif(利用前端js插件浏览);jpg(转换为jpg后前端显示);pdf(转换为pdf后显示,便于打印)
|
||||
tif.preview.type = ${KK_TIF_PREVIEW_TYPE:tif}
|
||||
|
||||
#Cad类型设置
|
||||
#Cad类型图片浏览模式:tif(利用前端js插件浏览);svg(转换为svg显示);pdf(转换为pdf后显示,便于打印)
|
||||
cad.preview.type = ${KK_CAD_PREVIEW_TYPE:svg}
|
||||
#Cad转换超时设置
|
||||
cad.timeout =${KK_CAD_TIMEOUT:90}
|
||||
#Cad转换线程设置
|
||||
cad.thread =${KK_CAD_THREAD:5}
|
||||
|
||||
|
||||
|
||||
@@ -13,8 +13,9 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
||||
* @author: chenjh
|
||||
* @since: 2019/4/10 17:22
|
||||
*/
|
||||
@Component
|
||||
@Component(value = ConfigConstants.BEAN_NAME)
|
||||
public class ConfigConstants {
|
||||
public static final String BEAN_NAME = "configConstants";
|
||||
|
||||
static {
|
||||
//pdfbox兼容低版本jdk
|
||||
@@ -35,7 +36,9 @@ public class ConfigConstants {
|
||||
private static String fileDir = ConfigUtils.getHomePath() + File.separator + "file" + File.separator;
|
||||
private static String localPreviewDir;
|
||||
private static CopyOnWriteArraySet<String> trustHostSet;
|
||||
private static CopyOnWriteArraySet<String> notTrustHostSet;
|
||||
private static String pdfPresentationModeDisable;
|
||||
private static String pdfDisableEditing;
|
||||
private static String pdfOpenFileDisable;
|
||||
private static String pdfPrintDisable;
|
||||
private static String pdfDownloadDisable;
|
||||
@@ -48,27 +51,45 @@ public class ConfigConstants {
|
||||
private static String password;
|
||||
private static int pdf2JpgDpi;
|
||||
private static String officeTypeWeb;
|
||||
private static String cadPreviewType;
|
||||
private static Boolean deleteSourceFile;
|
||||
private static Boolean deleteCaptcha;
|
||||
private static String officePageRange;
|
||||
private static String officeWatermark;
|
||||
private static String officeQuality;
|
||||
private static String officeMaxImageResolution;
|
||||
private static Boolean officeExportBookmarks;
|
||||
private static Boolean officeExportNotes;
|
||||
private static Boolean officeDocumentOpenPasswords;
|
||||
private static String cadTimeout;
|
||||
private static int cadThread;
|
||||
private static String homePageNumber;
|
||||
private static String homePagination;
|
||||
private static String homePageSize;
|
||||
private static String homeSearch;
|
||||
private static int pdfTimeout;
|
||||
private static int pdfTimeout80;
|
||||
private static int pdfTimeout200;
|
||||
private static int pdfThread;
|
||||
|
||||
public static final String DEFAULT_CACHE_ENABLED = "true";
|
||||
public static final String DEFAULT_TXT_TYPE = "txt,html,htm,asp,jsp,xml,json,properties,md,gitignore,log,java,py,c,cpp,sql,sh,bat,m,bas,prg,cmd";
|
||||
public static final String DEFAULT_TXT_TYPE = "txt,html,htm,asp,jsp,xml,json,properties,md,gitignore,log,java,py,c,cpp,sql,sh,bat,m,bas,prg,cmd,xbrl";
|
||||
public static final String DEFAULT_MEDIA_TYPE = "mp3,wav,mp4,flv";
|
||||
public static final String DEFAULT_OFFICE_PREVIEW_TYPE = "image";
|
||||
public static final String DEFAULT_OFFICE_PREVIEW_SWITCH_DISABLED = "false";
|
||||
public static final String DEFAULT_FTP_USERNAME = null;
|
||||
public static final String DEFAULT_FTP_PASSWORD = null;
|
||||
public static final String DEFAULT_FTP_CONTROL_ENCODING = "UTF-8";
|
||||
public static final String DEFAULT_BASE_URL = "default";
|
||||
public static final String DEFAULT_FILE_DIR_VALUE = "default";
|
||||
public static final String DEFAULT_LOCAL_PREVIEW_DIR_VALUE = "default";
|
||||
public static final String DEFAULT_TRUST_HOST = "default";
|
||||
public static final String DEFAULT_VALUE = "default";
|
||||
public static final String DEFAULT_PDF_PRESENTATION_MODE_DISABLE = "true";
|
||||
public static final String DEFAULT_PDF_OPEN_FILE_DISABLE = "true";
|
||||
public static final String DEFAULT_PDF_PRINT_DISABLE = "true";
|
||||
public static final String DEFAULT_PDF_DOWNLOAD_DISABLE = "true";
|
||||
public static final String DEFAULT_PDF_BOOKMARK_DISABLE = "true";
|
||||
public static final String DEFAULT_PDF_DISABLE_EDITING = "true";
|
||||
public static final String DEFAULT_FILE_UPLOAD_DISABLE = "false";
|
||||
public static final String DEFAULT_TIF_PREVIEW_TYPE = "tif";
|
||||
public static final String DEFAULT_CAD_PREVIEW_TYPE = "pdf";
|
||||
public static final String DEFAULT_BEIAN = "无";
|
||||
public static final String DEFAULT_SIZE = "500MB";
|
||||
public static final String DEFAULT_PROHIBIT = "exe,dll";
|
||||
@@ -76,6 +97,24 @@ public class ConfigConstants {
|
||||
public static final String DEFAULT_PDF2_JPG_DPI = "105";
|
||||
public static final String DEFAULT_OFFICE_TYPE_WEB = "web";
|
||||
public static final String DEFAULT_DELETE_SOURCE_FILE = "true";
|
||||
public static final String DEFAULT_DELETE_CAPTCHA = "false";
|
||||
public static final String DEFAULT_CAD_TIMEOUT = "90";
|
||||
public static final String DEFAULT_CAD_THREAD = "5";
|
||||
public static final String DEFAULT_OFFICE_PAQERANQE = "false";
|
||||
public static final String DEFAULT_OFFICE_WATERMARK = "false";
|
||||
public static final String DEFAULT_OFFICE_QUALITY = "80";
|
||||
public static final String DEFAULT_OFFICE_MAXIMAQERESOLUTION = "150";
|
||||
public static final String DEFAULT_OFFICE_EXPORTBOOKMARKS = "true";
|
||||
public static final String DEFAULT_OFFICE_EXPORTNOTES = "true";
|
||||
public static final String DEFAULT_OFFICE_EOCUMENTOPENPASSWORDS = "true";
|
||||
public static final String DEFAULT_HOME_PAGENUMBER = "1";
|
||||
public static final String DEFAULT_HOME_PAGINATION = "true";
|
||||
public static final String DEFAULT_HOME_PAGSIZE = "15";
|
||||
public static final String DEFAULT_HOME_SEARCH = "true";
|
||||
public static final String DEFAULT_PDF_TIMEOUT = "90";
|
||||
public static final String DEFAULT_PDF_TIMEOUT80 = "180";
|
||||
public static final String DEFAULT_PDF_TIMEOUT200 = "300";
|
||||
public static final String DEFAULT_PDF_THREAD = "5";
|
||||
|
||||
public static Boolean isCacheEnabled() {
|
||||
return cacheEnabled;
|
||||
@@ -94,7 +133,7 @@ public class ConfigConstants {
|
||||
return simTexts;
|
||||
}
|
||||
|
||||
@Value("${simText:txt,html,htm,asp,jsp,xml,json,properties,md,gitignore,log,java,py,c,cpp,sql,sh,bat,m,bas,prg,cmd}")
|
||||
@Value("${simText:txt,html,htm,asp,jsp,xml,json,properties,md,gitignore,log,java,py,c,cpp,sql,sh,bat,m,bas,prg,cmd,xbrl}")
|
||||
public void setSimText(String simText) {
|
||||
String[] simTextArr = simText.split(",");
|
||||
setSimTextValue(simTextArr);
|
||||
@@ -141,6 +180,7 @@ public class ConfigConstants {
|
||||
public void setMediaConvertDisable(String mediaConvertDisable) {
|
||||
setMediaConvertDisableValue(mediaConvertDisable);
|
||||
}
|
||||
|
||||
public static void setMediaConvertDisableValue(String mediaConvertDisable) {
|
||||
ConfigConstants.mediaConvertDisable = mediaConvertDisable;
|
||||
}
|
||||
@@ -220,7 +260,7 @@ public class ConfigConstants {
|
||||
}
|
||||
|
||||
public static void setFileDirValue(String fileDir) {
|
||||
if (!DEFAULT_FILE_DIR_VALUE.equalsIgnoreCase(fileDir)) {
|
||||
if (!DEFAULT_VALUE.equalsIgnoreCase(fileDir)) {
|
||||
if (!fileDir.endsWith(File.separator)) {
|
||||
fileDir = fileDir + File.separator;
|
||||
}
|
||||
@@ -238,7 +278,7 @@ public class ConfigConstants {
|
||||
}
|
||||
|
||||
public static void setLocalPreviewDirValue(String localPreviewDir) {
|
||||
if (!DEFAULT_LOCAL_PREVIEW_DIR_VALUE.equals(localPreviewDir)) {
|
||||
if (!DEFAULT_VALUE.equals(localPreviewDir)) {
|
||||
if (!localPreviewDir.endsWith(File.separator)) {
|
||||
localPreviewDir = localPreviewDir + File.separator;
|
||||
}
|
||||
@@ -248,19 +288,30 @@ public class ConfigConstants {
|
||||
|
||||
@Value("${trust.host:default}")
|
||||
public void setTrustHost(String trustHost) {
|
||||
setTrustHostValue(trustHost);
|
||||
setTrustHostSet(getHostValue(trustHost));
|
||||
}
|
||||
|
||||
public static void setTrustHostValue(String trustHost) {
|
||||
CopyOnWriteArraySet<String> trustHostSet;
|
||||
if (DEFAULT_TRUST_HOST.equalsIgnoreCase(trustHost)) {
|
||||
trustHostSet = new CopyOnWriteArraySet<>();
|
||||
public static void setTrustHostValue(String trustHost){
|
||||
setTrustHostSet(getHostValue(trustHost));
|
||||
}
|
||||
|
||||
@Value("${not.trust.host:default}")
|
||||
public void setNotTrustHost(String notTrustHost) {
|
||||
setNotTrustHostSet(getHostValue(notTrustHost));
|
||||
}
|
||||
|
||||
public static void setNotTrustHostValue(String notTrustHost){
|
||||
setNotTrustHostSet(getHostValue(notTrustHost));
|
||||
}
|
||||
|
||||
private static CopyOnWriteArraySet<String> getHostValue(String trustHost) {
|
||||
if (DEFAULT_VALUE.equalsIgnoreCase(trustHost)) {
|
||||
return new CopyOnWriteArraySet<>();
|
||||
} else {
|
||||
String[] trustHostArray = trustHost.toLowerCase().split(",");
|
||||
trustHostSet = new CopyOnWriteArraySet<>(Arrays.asList(trustHostArray));
|
||||
setTrustHostSet(trustHostSet);
|
||||
// 去除空格并转小写
|
||||
String[] trustHostArray = trustHost.toLowerCase().replaceAll("\\s+", "").split(",");
|
||||
return new CopyOnWriteArraySet<>(Arrays.asList(trustHostArray));
|
||||
}
|
||||
setTrustHostSet(trustHostSet);
|
||||
}
|
||||
|
||||
public static Set<String> getTrustHostSet() {
|
||||
@@ -271,6 +322,15 @@ public class ConfigConstants {
|
||||
ConfigConstants.trustHostSet = trustHostSet;
|
||||
}
|
||||
|
||||
public static Set<String> getNotTrustHostSet() {
|
||||
return notTrustHostSet;
|
||||
}
|
||||
|
||||
public static void setNotTrustHostSet(CopyOnWriteArraySet<String> notTrustHostSet) {
|
||||
ConfigConstants.notTrustHostSet = notTrustHostSet;
|
||||
}
|
||||
|
||||
|
||||
public static String getPdfPresentationModeDisable() {
|
||||
return pdfPresentationModeDisable;
|
||||
}
|
||||
@@ -292,6 +352,7 @@ public class ConfigConstants {
|
||||
public void setPdfOpenFileDisable(String pdfOpenFileDisable) {
|
||||
setPdfOpenFileDisableValue(pdfOpenFileDisable);
|
||||
}
|
||||
|
||||
public static void setPdfOpenFileDisableValue(String pdfOpenFileDisable) {
|
||||
ConfigConstants.pdfOpenFileDisable = pdfOpenFileDisable;
|
||||
}
|
||||
@@ -299,10 +360,12 @@ public class ConfigConstants {
|
||||
public static String getPdfPrintDisable() {
|
||||
return pdfPrintDisable;
|
||||
}
|
||||
|
||||
@Value("${pdf.print.disable:true}")
|
||||
public void setPdfPrintDisable(String pdfPrintDisable) {
|
||||
setPdfPrintDisableValue(pdfPrintDisable);
|
||||
}
|
||||
|
||||
public static void setPdfPrintDisableValue(String pdfPrintDisable) {
|
||||
ConfigConstants.pdfPrintDisable = pdfPrintDisable;
|
||||
}
|
||||
@@ -315,6 +378,7 @@ public class ConfigConstants {
|
||||
public void setPdfDownloadDisable(String pdfDownloadDisable) {
|
||||
setPdfDownloadDisableValue(pdfDownloadDisable);
|
||||
}
|
||||
|
||||
public static void setPdfDownloadDisableValue(String pdfDownloadDisable) {
|
||||
ConfigConstants.pdfDownloadDisable = pdfDownloadDisable;
|
||||
}
|
||||
@@ -322,21 +386,39 @@ public class ConfigConstants {
|
||||
public static String getPdfBookmarkDisable() {
|
||||
return pdfBookmarkDisable;
|
||||
}
|
||||
|
||||
@Value("${pdf.bookmark.disable:true}")
|
||||
public void setPdfBookmarkDisable(String pdfBookmarkDisable) {
|
||||
setPdfBookmarkDisableValue(pdfBookmarkDisable);
|
||||
}
|
||||
|
||||
public static void setPdfBookmarkDisableValue(String pdfBookmarkDisable) {
|
||||
ConfigConstants.pdfBookmarkDisable = pdfBookmarkDisable;
|
||||
}
|
||||
|
||||
|
||||
public static String getPdfDisableEditing() {
|
||||
return pdfDisableEditing;
|
||||
}
|
||||
|
||||
@Value("${pdf.disable.editing:true}")
|
||||
public void setpdfDisableEditing(String pdfDisableEditing) {
|
||||
setPdfDisableEditingValue(pdfDisableEditing);
|
||||
}
|
||||
|
||||
public static void setPdfDisableEditingValue(String pdfDisableEditing) {
|
||||
ConfigConstants.pdfDisableEditing = pdfDisableEditing;
|
||||
}
|
||||
|
||||
public static String getOfficePreviewSwitchDisabled() {
|
||||
return officePreviewSwitchDisabled;
|
||||
}
|
||||
|
||||
@Value("${office.preview.switch.disabled:true}")
|
||||
public void setOfficePreviewSwitchDisabled(String officePreviewSwitchDisabled) {
|
||||
ConfigConstants.officePreviewSwitchDisabled = officePreviewSwitchDisabled;
|
||||
}
|
||||
|
||||
public static void setOfficePreviewSwitchDisabledValue(String officePreviewSwitchDisabled) {
|
||||
ConfigConstants.officePreviewSwitchDisabled = officePreviewSwitchDisabled;
|
||||
}
|
||||
@@ -345,7 +427,7 @@ public class ConfigConstants {
|
||||
return fileUploadDisable;
|
||||
}
|
||||
|
||||
@Value("${file.upload.disable:false}")
|
||||
@Value("${file.upload.disable:true}")
|
||||
public void setFileUploadDisable(Boolean fileUploadDisable) {
|
||||
setFileUploadDisableValue(fileUploadDisable);
|
||||
}
|
||||
@@ -368,19 +450,10 @@ public class ConfigConstants {
|
||||
ConfigConstants.tifPreviewType = tifPreviewType;
|
||||
}
|
||||
|
||||
public static String getBeian() {
|
||||
return beian;
|
||||
}
|
||||
@Value("${beian:default}")
|
||||
public void setBeian(String beian) {
|
||||
setBeianValue(beian);
|
||||
}
|
||||
public static void setBeianValue(String beian) {
|
||||
ConfigConstants.beian = beian;
|
||||
}
|
||||
public static String[] getProhibit() {
|
||||
return prohibit;
|
||||
}
|
||||
|
||||
@Value("${prohibit:exe,dll}")
|
||||
public void setProhibit(String prohibit) {
|
||||
String[] prohibitArr = prohibit.split(",");
|
||||
@@ -390,13 +463,16 @@ public class ConfigConstants {
|
||||
public static void setProhibitValue(String[] prohibit) {
|
||||
ConfigConstants.prohibit = prohibit;
|
||||
}
|
||||
|
||||
public static String maxSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
@Value("${spring.servlet.multipart.max-file-size:500MB}")
|
||||
public void setSize(String size) {
|
||||
setSizeValue(size);
|
||||
}
|
||||
|
||||
public static void setSizeValue(String size) {
|
||||
ConfigConstants.size = size;
|
||||
}
|
||||
@@ -404,10 +480,12 @@ public class ConfigConstants {
|
||||
public static String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
@Value("${delete.password:123456}")
|
||||
public void setPassword(String password) {
|
||||
setPasswordValue(password);
|
||||
}
|
||||
|
||||
public static void setPasswordValue(String password) {
|
||||
ConfigConstants.password = password;
|
||||
}
|
||||
@@ -416,10 +494,12 @@ public class ConfigConstants {
|
||||
public static int getPdf2JpgDpi() {
|
||||
return pdf2JpgDpi;
|
||||
}
|
||||
|
||||
@Value("${pdf2jpg.dpi:105}")
|
||||
public void pdf2JpgDpi(int pdf2JpgDpi) {
|
||||
setPdf2JpgDpiValue(pdf2JpgDpi);
|
||||
}
|
||||
|
||||
public static void setPdf2JpgDpiValue(int pdf2JpgDpi) {
|
||||
ConfigConstants.pdf2JpgDpi = pdf2JpgDpi;
|
||||
}
|
||||
@@ -427,10 +507,12 @@ public class ConfigConstants {
|
||||
public static String getOfficeTypeWeb() {
|
||||
return officeTypeWeb;
|
||||
}
|
||||
|
||||
@Value("${office.type.web:web}")
|
||||
public void setOfficeTypeWeb(String officeTypeWeb) {
|
||||
setOfficeTypeWebValue(officeTypeWeb);
|
||||
}
|
||||
|
||||
public static void setOfficeTypeWebValue(String officeTypeWeb) {
|
||||
ConfigConstants.officeTypeWeb = officeTypeWeb;
|
||||
}
|
||||
@@ -449,4 +531,286 @@ public class ConfigConstants {
|
||||
ConfigConstants.deleteSourceFile = deleteSourceFile;
|
||||
}
|
||||
|
||||
public static Boolean getDeleteCaptcha() {
|
||||
return deleteCaptcha;
|
||||
}
|
||||
|
||||
@Value("${delete.captcha:false}")
|
||||
public void setDeleteCaptcha(Boolean deleteCaptcha) {
|
||||
setDeleteCaptchaValue(deleteCaptcha);
|
||||
}
|
||||
|
||||
public static void setDeleteCaptchaValue(Boolean deleteCaptcha) {
|
||||
ConfigConstants.deleteCaptcha = deleteCaptcha;
|
||||
}
|
||||
|
||||
/**
|
||||
* 以下为cad转换模块设置
|
||||
*/
|
||||
|
||||
public static String getCadPreviewType() {
|
||||
return cadPreviewType;
|
||||
}
|
||||
|
||||
@Value("${cad.preview.type:svg}")
|
||||
public void setCadPreviewType(String cadPreviewType) {
|
||||
setCadPreviewTypeValue(cadPreviewType);
|
||||
}
|
||||
|
||||
public static void setCadPreviewTypeValue(String cadPreviewType) {
|
||||
ConfigConstants.cadPreviewType = cadPreviewType;
|
||||
}
|
||||
|
||||
|
||||
public static String getCadTimeout() {
|
||||
return cadTimeout;
|
||||
}
|
||||
|
||||
@Value("${cad.timeout:90}")
|
||||
public void setCadTimeout(String cadTimeout) {
|
||||
setCadTimeoutValue(cadTimeout);
|
||||
}
|
||||
|
||||
public static void setCadTimeoutValue(String cadTimeout) {
|
||||
ConfigConstants.cadTimeout = cadTimeout;
|
||||
}
|
||||
|
||||
|
||||
public static int getCadThread() {
|
||||
return cadThread;
|
||||
}
|
||||
|
||||
@Value("${cad.thread:5}")
|
||||
public void setCadThread(int cadThread) {
|
||||
setCadThreadValue(cadThread);
|
||||
}
|
||||
|
||||
public static void setCadThreadValue(int cadThread) {
|
||||
ConfigConstants.cadThread = cadThread;
|
||||
}
|
||||
|
||||
/**
|
||||
* 以下为pdf转换模块设置
|
||||
*/
|
||||
public static int getPdfTimeout() {
|
||||
return pdfTimeout;
|
||||
}
|
||||
|
||||
@Value("${pdf.timeout:90}")
|
||||
public void setPdfTimeout(int pdfTimeout) {
|
||||
setPdfTimeoutValue(pdfTimeout);
|
||||
}
|
||||
|
||||
public static void setPdfTimeoutValue(int pdfTimeout) {
|
||||
ConfigConstants.pdfTimeout = pdfTimeout;
|
||||
}
|
||||
|
||||
|
||||
public static int getPdfTimeout80() {
|
||||
return pdfTimeout80;
|
||||
}
|
||||
|
||||
@Value("${pdf.timeout80:180}")
|
||||
public void setPdfTimeout80(int pdfTimeout80) {
|
||||
setPdfTimeout80Value(pdfTimeout80);
|
||||
}
|
||||
|
||||
public static void setPdfTimeout80Value(int pdfTimeout80) {
|
||||
ConfigConstants.pdfTimeout80 = pdfTimeout80;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static int getPdfTimeout200() {
|
||||
return pdfTimeout200;
|
||||
}
|
||||
|
||||
@Value("${pdf.timeout200:300}")
|
||||
public void setPdfTimeout200(int pdfTimeout200) {
|
||||
setPdfTimeout200Value(pdfTimeout200);
|
||||
}
|
||||
|
||||
public static void setPdfTimeout200Value(int pdfTimeout200) {
|
||||
ConfigConstants.pdfTimeout200 = pdfTimeout200;
|
||||
}
|
||||
|
||||
|
||||
public static int getPdfThread() {
|
||||
return pdfThread;
|
||||
}
|
||||
|
||||
@Value("${pdf.thread:5}")
|
||||
public void setPdfThread(int pdfThread) {
|
||||
setPdfThreadValue(pdfThread);
|
||||
}
|
||||
|
||||
public static void setPdfThreadValue(int pdfThread) {
|
||||
ConfigConstants.pdfThread = pdfThread;
|
||||
}
|
||||
|
||||
/**
|
||||
* 以下为OFFICE转换模块设置
|
||||
*/
|
||||
|
||||
public static String getOfficePageRange() {
|
||||
return officePageRange;
|
||||
}
|
||||
|
||||
@Value("${office.pagerange:false}")
|
||||
public void setOfficePageRange(String officePageRange) {
|
||||
setOfficePageRangeValue(officePageRange);
|
||||
}
|
||||
|
||||
public static void setOfficePageRangeValue(String officePageRange) {
|
||||
ConfigConstants.officePageRange = officePageRange;
|
||||
}
|
||||
|
||||
public static String getOfficeWatermark() {
|
||||
return officeWatermark;
|
||||
}
|
||||
|
||||
@Value("${office.watermark:false}")
|
||||
public void setOfficeWatermark(String officeWatermark) {
|
||||
setOfficeWatermarkValue(officeWatermark);
|
||||
}
|
||||
|
||||
public static void setOfficeWatermarkValue(String officeWatermark) {
|
||||
ConfigConstants.officeWatermark = officeWatermark;
|
||||
}
|
||||
|
||||
public static String getOfficeQuality() {
|
||||
return officeQuality;
|
||||
}
|
||||
|
||||
@Value("${office.quality:80}")
|
||||
public void setOfficeQuality(String officeQuality) {
|
||||
setOfficeQualityValue(officeQuality);
|
||||
}
|
||||
|
||||
public static void setOfficeQualityValue(String officeQuality) {
|
||||
ConfigConstants.officeQuality = officeQuality;
|
||||
}
|
||||
|
||||
public static String getOfficeMaxImageResolution() {
|
||||
return officeMaxImageResolution;
|
||||
}
|
||||
|
||||
@Value("${office.maximageresolution:150}")
|
||||
public void setOfficeMaxImageResolution(String officeMaxImageResolution) {
|
||||
setOfficeMaxImageResolutionValue(officeMaxImageResolution);
|
||||
}
|
||||
|
||||
public static void setOfficeMaxImageResolutionValue(String officeMaxImageResolution) {
|
||||
ConfigConstants.officeMaxImageResolution = officeMaxImageResolution;
|
||||
}
|
||||
|
||||
public static Boolean getOfficeExportBookmarks() {
|
||||
return officeExportBookmarks;
|
||||
}
|
||||
|
||||
@Value("${office.exportbookmarks:true}")
|
||||
public void setOfficeExportBookmarks(Boolean officeExportBookmarks) {
|
||||
setOfficeExportBookmarksValue(officeExportBookmarks);
|
||||
}
|
||||
|
||||
public static void setOfficeExportBookmarksValue(Boolean officeExportBookmarks) {
|
||||
ConfigConstants.officeExportBookmarks = officeExportBookmarks;
|
||||
}
|
||||
|
||||
public static Boolean getOfficeExportNotes() {
|
||||
return officeExportNotes;
|
||||
}
|
||||
|
||||
@Value("${office.exportnotes:true}")
|
||||
public void setExportNotes(Boolean officeExportNotes) {
|
||||
setOfficeExportNotesValue(officeExportNotes);
|
||||
}
|
||||
|
||||
public static void setOfficeExportNotesValue(Boolean officeExportNotes) {
|
||||
ConfigConstants.officeExportNotes = officeExportNotes;
|
||||
}
|
||||
|
||||
public static Boolean getOfficeDocumentOpenPasswords() {
|
||||
return officeDocumentOpenPasswords;
|
||||
}
|
||||
|
||||
@Value("${office.documentopenpasswords:true}")
|
||||
public void setDocumentOpenPasswords(Boolean officeDocumentOpenPasswords) {
|
||||
setOfficeDocumentOpenPasswordsValue(officeDocumentOpenPasswords);
|
||||
}
|
||||
|
||||
public static void setOfficeDocumentOpenPasswordsValue(Boolean officeDocumentOpenPasswords) {
|
||||
ConfigConstants.officeDocumentOpenPasswords = officeDocumentOpenPasswords;
|
||||
}
|
||||
|
||||
/**
|
||||
* 以下为首页显示
|
||||
*/
|
||||
|
||||
public static String getBeian() {
|
||||
return beian;
|
||||
}
|
||||
|
||||
@Value("${beian:default}")
|
||||
public void setBeian(String beian) {
|
||||
setBeianValue(beian);
|
||||
}
|
||||
|
||||
public static void setBeianValue(String beian) {
|
||||
ConfigConstants.beian = beian;
|
||||
}
|
||||
|
||||
|
||||
public static String getHomePageNumber() {
|
||||
return homePageNumber;
|
||||
}
|
||||
|
||||
@Value("${home.pagenumber:1}")
|
||||
public void setHomePageNumber(String homePageNumber) {
|
||||
setHomePageNumberValue(homePageNumber);
|
||||
}
|
||||
|
||||
public static void setHomePageNumberValue(String homePageNumber) {
|
||||
ConfigConstants.homePageNumber = homePageNumber;
|
||||
}
|
||||
|
||||
public static String getHomePagination() {
|
||||
return homePagination;
|
||||
}
|
||||
|
||||
@Value("${home.pagination:true}")
|
||||
public void setHomePagination(String homePagination) {
|
||||
setHomePaginationValue(homePagination);
|
||||
}
|
||||
|
||||
public static void setHomePaginationValue(String homePagination) {
|
||||
ConfigConstants.homePagination = homePagination;
|
||||
}
|
||||
|
||||
public static String getHomePageSize() {
|
||||
return homePageSize;
|
||||
}
|
||||
|
||||
@Value("${home.pagesize:15}")
|
||||
public void setHomePageSize(String homePageSize) {
|
||||
setHomePageSizeValue(homePageSize);
|
||||
}
|
||||
|
||||
public static void setHomePageSizeValue(String homePageSize) {
|
||||
ConfigConstants.homePageSize = homePageSize;
|
||||
}
|
||||
|
||||
public static String getHomeSearch() {
|
||||
return homeSearch;
|
||||
}
|
||||
|
||||
@Value("${home.search:1}")
|
||||
public void setHomeSearch(String homeSearch) {
|
||||
setHomeSearchValue(homeSearch);
|
||||
}
|
||||
|
||||
public static void setHomeSearchValue(String homeSearch) {
|
||||
ConfigConstants.homeSearch = homeSearch;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
@@ -46,11 +46,13 @@ public class ConfigRefreshComponent {
|
||||
String configFilePath = ConfigUtils.getCustomizedConfigPath();
|
||||
String baseUrl;
|
||||
String trustHost;
|
||||
String notTrustHost;
|
||||
String pdfPresentationModeDisable;
|
||||
String pdfOpenFileDisable;
|
||||
String pdfPrintDisable;
|
||||
String pdfDownloadDisable;
|
||||
String pdfBookmarkDisable;
|
||||
String pdfDisableEditing;
|
||||
boolean fileUploadDisable;
|
||||
String tifPreviewType;
|
||||
String prohibit;
|
||||
@@ -60,7 +62,26 @@ public class ConfigRefreshComponent {
|
||||
String password;
|
||||
int pdf2JpgDpi;
|
||||
String officeTypeWeb;
|
||||
String cadPreviewType;
|
||||
boolean deleteSourceFile;
|
||||
boolean deleteCaptcha;
|
||||
String officPageRange;
|
||||
String officWatermark;
|
||||
String officQuality;
|
||||
String officMaxImageResolution;
|
||||
boolean officExportBookmarks;
|
||||
boolean officeExportNotes;
|
||||
boolean officeDocumentOpenPasswords;
|
||||
String cadTimeout;
|
||||
int cadThread;
|
||||
String homePageNumber;
|
||||
String homePagination;
|
||||
String homePageSize;
|
||||
String homeSearch;
|
||||
int pdfTimeout;
|
||||
int pdfTimeout80;
|
||||
int pdfTimeout200;
|
||||
int pdfThread;
|
||||
while (true) {
|
||||
FileReader fileReader = new FileReader(configFilePath);
|
||||
BufferedReader bufferedReader = new BufferedReader(fileReader);
|
||||
@@ -76,15 +97,18 @@ public class ConfigRefreshComponent {
|
||||
ftpControlEncoding = properties.getProperty("ftp.control.encoding", ConfigConstants.DEFAULT_FTP_CONTROL_ENCODING);
|
||||
textArray = text.split(",");
|
||||
mediaArray = media.split(",");
|
||||
baseUrl = properties.getProperty("base.url", ConfigConstants.DEFAULT_BASE_URL);
|
||||
trustHost = properties.getProperty("trust.host", ConfigConstants.DEFAULT_TRUST_HOST);
|
||||
baseUrl = properties.getProperty("base.url", ConfigConstants.DEFAULT_VALUE);
|
||||
trustHost = properties.getProperty("trust.host", ConfigConstants.DEFAULT_VALUE);
|
||||
notTrustHost = properties.getProperty("not.trust.host", ConfigConstants.DEFAULT_VALUE);
|
||||
pdfPresentationModeDisable = properties.getProperty("pdf.presentationMode.disable", ConfigConstants.DEFAULT_PDF_PRESENTATION_MODE_DISABLE);
|
||||
pdfOpenFileDisable = properties.getProperty("pdf.openFile.disable", ConfigConstants.DEFAULT_PDF_OPEN_FILE_DISABLE);
|
||||
pdfPrintDisable = properties.getProperty("pdf.print.disable", ConfigConstants.DEFAULT_PDF_PRINT_DISABLE);
|
||||
pdfDownloadDisable = properties.getProperty("pdf.download.disable", ConfigConstants.DEFAULT_PDF_DOWNLOAD_DISABLE);
|
||||
pdfBookmarkDisable = properties.getProperty("pdf.bookmark.disable", ConfigConstants.DEFAULT_PDF_BOOKMARK_DISABLE);
|
||||
pdfDisableEditing = properties.getProperty("pdf.disable.editing", ConfigConstants.DEFAULT_PDF_DISABLE_EDITING);
|
||||
fileUploadDisable = Boolean.parseBoolean(properties.getProperty("file.upload.disable", ConfigConstants.DEFAULT_FILE_UPLOAD_DISABLE));
|
||||
tifPreviewType = properties.getProperty("tif.preview.type", ConfigConstants.DEFAULT_TIF_PREVIEW_TYPE);
|
||||
cadPreviewType = properties.getProperty("cad.preview.type", ConfigConstants.DEFAULT_CAD_PREVIEW_TYPE);
|
||||
size = properties.getProperty("spring.servlet.multipart.max-file-size", ConfigConstants.DEFAULT_SIZE);
|
||||
beian = properties.getProperty("beian", ConfigConstants.DEFAULT_BEIAN);
|
||||
prohibit = properties.getProperty("prohibit", ConfigConstants.DEFAULT_PROHIBIT);
|
||||
@@ -92,6 +116,24 @@ public class ConfigRefreshComponent {
|
||||
pdf2JpgDpi = Integer.parseInt(properties.getProperty("pdf2jpg.dpi", ConfigConstants.DEFAULT_PDF2_JPG_DPI));
|
||||
officeTypeWeb = properties.getProperty("office.type.web", ConfigConstants.DEFAULT_OFFICE_TYPE_WEB);
|
||||
deleteSourceFile = Boolean.parseBoolean(properties.getProperty("delete.source.file", ConfigConstants.DEFAULT_DELETE_SOURCE_FILE));
|
||||
deleteCaptcha = Boolean.parseBoolean(properties.getProperty("delete.captcha", ConfigConstants.DEFAULT_DELETE_CAPTCHA));
|
||||
officPageRange = properties.getProperty("office.pagerange", ConfigConstants.DEFAULT_OFFICE_PAQERANQE);
|
||||
officWatermark = properties.getProperty("office.watermark", ConfigConstants.DEFAULT_OFFICE_WATERMARK);
|
||||
officQuality = properties.getProperty("office.quality", ConfigConstants.DEFAULT_OFFICE_QUALITY);
|
||||
officMaxImageResolution = properties.getProperty("office.maximageresolution", ConfigConstants.DEFAULT_OFFICE_MAXIMAQERESOLUTION);
|
||||
officExportBookmarks = Boolean.parseBoolean(properties.getProperty("office.exportbookmarks", ConfigConstants.DEFAULT_OFFICE_EXPORTBOOKMARKS));
|
||||
officeExportNotes = Boolean.parseBoolean(properties.getProperty("office.exportnotes", ConfigConstants.DEFAULT_OFFICE_EXPORTNOTES));
|
||||
officeDocumentOpenPasswords = Boolean.parseBoolean(properties.getProperty("office.documentopenpasswords", ConfigConstants.DEFAULT_OFFICE_EOCUMENTOPENPASSWORDS));
|
||||
cadTimeout = properties.getProperty("cad.timeout", ConfigConstants.DEFAULT_CAD_TIMEOUT);
|
||||
homePageNumber = properties.getProperty("home.pagenumber", ConfigConstants.DEFAULT_HOME_PAGENUMBER);
|
||||
homePagination = properties.getProperty("home.pagination", ConfigConstants.DEFAULT_HOME_PAGINATION);
|
||||
homePageSize = properties.getProperty("home.pagesize", ConfigConstants.DEFAULT_HOME_PAGSIZE);
|
||||
homeSearch = properties.getProperty("home.search", ConfigConstants.DEFAULT_HOME_SEARCH);
|
||||
cadThread = Integer.parseInt(properties.getProperty("cad.thread", ConfigConstants.DEFAULT_CAD_THREAD));
|
||||
pdfTimeout = Integer.parseInt(properties.getProperty("pdf.timeout", ConfigConstants.DEFAULT_PDF_TIMEOUT));
|
||||
pdfTimeout80 = Integer.parseInt(properties.getProperty("pdf.timeout80", ConfigConstants.DEFAULT_PDF_TIMEOUT80));
|
||||
pdfTimeout200 = Integer.parseInt(properties.getProperty("pdf.timeout200", ConfigConstants.DEFAULT_PDF_TIMEOUT200));
|
||||
pdfThread = Integer.parseInt(properties.getProperty("pdf.thread", ConfigConstants.DEFAULT_PDF_THREAD));
|
||||
prohibitArray = prohibit.split(",");
|
||||
|
||||
ConfigConstants.setCacheEnabledValueValue(cacheEnabled);
|
||||
@@ -103,21 +145,42 @@ public class ConfigRefreshComponent {
|
||||
ConfigConstants.setFtpControlEncodingValue(ftpControlEncoding);
|
||||
ConfigConstants.setBaseUrlValue(baseUrl);
|
||||
ConfigConstants.setTrustHostValue(trustHost);
|
||||
ConfigConstants.setNotTrustHostValue(notTrustHost);
|
||||
ConfigConstants.setOfficePreviewSwitchDisabledValue(officePreviewSwitchDisabled);
|
||||
ConfigConstants.setPdfPresentationModeDisableValue(pdfPresentationModeDisable);
|
||||
ConfigConstants.setPdfOpenFileDisableValue(pdfOpenFileDisable);
|
||||
ConfigConstants.setPdfPrintDisableValue(pdfPrintDisable);
|
||||
ConfigConstants.setPdfDownloadDisableValue(pdfDownloadDisable);
|
||||
ConfigConstants.setPdfBookmarkDisableValue(pdfBookmarkDisable);
|
||||
ConfigConstants.setPdfDisableEditingValue(pdfDisableEditing);
|
||||
ConfigConstants.setFileUploadDisableValue(fileUploadDisable);
|
||||
ConfigConstants.setTifPreviewTypeValue(tifPreviewType);
|
||||
ConfigConstants.setCadPreviewTypeValue(cadPreviewType);
|
||||
ConfigConstants.setBeianValue(beian);
|
||||
ConfigConstants.setSizeValue(size);
|
||||
ConfigConstants.setProhibitValue(prohibitArray);
|
||||
ConfigConstants.setPasswordValue(password);
|
||||
ConfigConstants.setPdf2JpgDpiValue(pdf2JpgDpi);
|
||||
ConfigConstants.setOfficeTypeWebValue(officeTypeWeb);
|
||||
ConfigConstants.setOfficePageRangeValue(officPageRange);
|
||||
ConfigConstants.setOfficeWatermarkValue(officWatermark);
|
||||
ConfigConstants.setOfficeQualityValue(officQuality);
|
||||
ConfigConstants.setOfficeMaxImageResolutionValue(officMaxImageResolution);
|
||||
ConfigConstants.setOfficeExportBookmarksValue(officExportBookmarks);
|
||||
ConfigConstants.setOfficeExportNotesValue(officeExportNotes);
|
||||
ConfigConstants.setOfficeDocumentOpenPasswordsValue(officeDocumentOpenPasswords);
|
||||
ConfigConstants.setDeleteSourceFileValue(deleteSourceFile);
|
||||
ConfigConstants.setDeleteCaptchaValue(deleteCaptcha);
|
||||
ConfigConstants.setCadTimeoutValue(cadTimeout);
|
||||
ConfigConstants.setCadThreadValue(cadThread);
|
||||
ConfigConstants.setHomePageNumberValue(homePageNumber);
|
||||
ConfigConstants.setHomePaginationValue(homePagination);
|
||||
ConfigConstants.setHomePageSizeValue(homePageSize);
|
||||
ConfigConstants.setHomeSearchValue(homeSearch);
|
||||
ConfigConstants.setPdfTimeoutValue(pdfTimeout);
|
||||
ConfigConstants.setPdfTimeout80Value(pdfTimeout80);
|
||||
ConfigConstants.setPdfTimeout200Value(pdfTimeout200);
|
||||
ConfigConstants.setPdfThreadValue(pdfThread);
|
||||
setWatermarkConfig(properties);
|
||||
bufferedReader.close();
|
||||
fileReader.close();
|
||||
|
||||
@@ -50,26 +50,21 @@ public class RedissonConfig {
|
||||
.setConnectionMinimumIdleSize(connectionMinimumIdleSize)
|
||||
.setConnectionPoolSize(connectionPoolSize)
|
||||
.setDatabase(database)
|
||||
.setDnsMonitoring(dnsMonitoring)
|
||||
.setDnsMonitoringInterval(dnsMonitoringInterval)
|
||||
.setSubscriptionConnectionMinimumIdleSize(subscriptionConnectionMinimumIdleSize)
|
||||
.setSubscriptionConnectionPoolSize(subscriptionConnectionPoolSize)
|
||||
.setSubscriptionsPerConnection(subscriptionsPerConnection)
|
||||
.setClientName(clientName)
|
||||
.setFailedAttempts(failedAttempts)
|
||||
.setRetryAttempts(retryAttempts)
|
||||
.setRetryInterval(retryInterval)
|
||||
.setReconnectionTimeout(reconnectionTimeout)
|
||||
.setTimeout(timeout)
|
||||
.setConnectTimeout(connectTimeout)
|
||||
.setIdleConnectionTimeout(idleConnectionTimeout)
|
||||
.setPingTimeout(pingTimeout)
|
||||
.setPassword(StringUtils.trimToNull(password));
|
||||
Codec codec=(Codec) ClassUtils.forName(getCodec(), ClassUtils.getDefaultClassLoader()).newInstance();
|
||||
config.setCodec(codec);
|
||||
config.setThreads(thread);
|
||||
config.setEventLoopGroup(new NioEventLoopGroup());
|
||||
config.setUseLinuxNativeEpoll(false);
|
||||
return config;
|
||||
}
|
||||
|
||||
|
||||
@@ -30,11 +30,13 @@ public class WebConfig implements WebMvcConfigurer {
|
||||
registry.addResourceHandler("/**").addResourceLocations("classpath:/META-INF/resources/","classpath:/resources/","classpath:/static/","classpath:/public/","file:" + filePath);
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
public FilterRegistrationBean<ChinesePathFilter> getChinesePathFilter() {
|
||||
ChinesePathFilter filter = new ChinesePathFilter();
|
||||
FilterRegistrationBean<ChinesePathFilter> registrationBean = new FilterRegistrationBean<>();
|
||||
registrationBean.setFilter(filter);
|
||||
registrationBean.setOrder(10);
|
||||
return registrationBean;
|
||||
}
|
||||
|
||||
@@ -67,14 +69,20 @@ public class WebConfig implements WebMvcConfigurer {
|
||||
@Bean
|
||||
public FilterRegistrationBean<BaseUrlFilter> getBaseUrlFilter() {
|
||||
Set<String> filterUri = new HashSet<>();
|
||||
filterUri.add("/index");
|
||||
filterUri.add("/");
|
||||
filterUri.add("/onlinePreview");
|
||||
filterUri.add("/picturesPreview");
|
||||
BaseUrlFilter filter = new BaseUrlFilter();
|
||||
FilterRegistrationBean<BaseUrlFilter> registrationBean = new FilterRegistrationBean<>();
|
||||
registrationBean.setFilter(filter);
|
||||
registrationBean.setUrlPatterns(filterUri);
|
||||
registrationBean.setOrder(20);
|
||||
return registrationBean;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public FilterRegistrationBean<UrlCheckFilter> getUrlCheckFilter() {
|
||||
UrlCheckFilter filter = new UrlCheckFilter();
|
||||
FilterRegistrationBean<UrlCheckFilter> registrationBean = new FilterRegistrationBean<>();
|
||||
registrationBean.setFilter(filter);
|
||||
registrationBean.setOrder(30);
|
||||
return registrationBean;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,13 +12,26 @@ public class FileAttribute {
|
||||
private String suffix;
|
||||
private String name;
|
||||
private String url;
|
||||
private String fileKey;
|
||||
private boolean isCompressFile = false;
|
||||
private String compressFileKey;
|
||||
private String filePassword;
|
||||
private String userToken;
|
||||
private boolean usePasswordCache;
|
||||
private String officePreviewType = ConfigConstants.getOfficePreviewType();
|
||||
private String tifPreviewType;
|
||||
private Boolean skipDownLoad = false;
|
||||
private Boolean forceUpdatedCache = false;
|
||||
private String cacheName;
|
||||
private String outFilePath;
|
||||
private String originFilePath;
|
||||
private String cacheListName;
|
||||
private boolean isHtmlView = false;
|
||||
|
||||
/**
|
||||
* 代理请求到文件服务器的认证请求头,格式如下:
|
||||
* {“username”:"test","password":"test"}
|
||||
* 请求文件服务器时,会将 json 直接塞到请求头里
|
||||
*/
|
||||
private String kkProxyAuthorization;
|
||||
|
||||
public FileAttribute() {
|
||||
}
|
||||
@@ -38,12 +51,12 @@ public class FileAttribute {
|
||||
this.officePreviewType = officePreviewType;
|
||||
}
|
||||
|
||||
public String getFileKey() {
|
||||
return fileKey;
|
||||
public boolean isCompressFile() {
|
||||
return isCompressFile;
|
||||
}
|
||||
|
||||
public void setFileKey(String fileKey) {
|
||||
this.fileKey = fileKey;
|
||||
public void setCompressFile(boolean compressFile) {
|
||||
isCompressFile = compressFile;
|
||||
}
|
||||
|
||||
public String getFilePassword() {
|
||||
@@ -54,12 +67,12 @@ public class FileAttribute {
|
||||
this.filePassword = filePassword;
|
||||
}
|
||||
|
||||
public String getUserToken() {
|
||||
return userToken;
|
||||
public boolean getUsePasswordCache() {
|
||||
return usePasswordCache;
|
||||
}
|
||||
|
||||
public void setUserToken(String userToken) {
|
||||
this.userToken = userToken;
|
||||
public void setUsePasswordCache(boolean usePasswordCache) {
|
||||
this.usePasswordCache = usePasswordCache;
|
||||
}
|
||||
|
||||
public String getOfficePreviewType() {
|
||||
@@ -86,10 +99,48 @@ public class FileAttribute {
|
||||
this.suffix = suffix;
|
||||
}
|
||||
|
||||
public String getCompressFileKey() {
|
||||
return compressFileKey;
|
||||
}
|
||||
|
||||
public void setCompressFileKey(String compressFileKey) {
|
||||
this.compressFileKey = compressFileKey;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
public String getCacheName() {
|
||||
return cacheName;
|
||||
}
|
||||
public String getCacheListName() {
|
||||
return cacheListName;
|
||||
}
|
||||
public String getOutFilePath() {
|
||||
return outFilePath;
|
||||
}
|
||||
public String getOriginFilePath() {
|
||||
return originFilePath;
|
||||
}
|
||||
public boolean isHtmlView() {
|
||||
return isHtmlView;
|
||||
}
|
||||
|
||||
public void setCacheName(String cacheName) {
|
||||
this.cacheName = cacheName;
|
||||
}
|
||||
public void setCacheListName(String cacheListName) {
|
||||
this.cacheListName = cacheListName;
|
||||
}
|
||||
public void setOutFilePath(String outFilePath) {
|
||||
this.outFilePath = outFilePath;
|
||||
}
|
||||
public void setOriginFilePath(String originFilePath) {
|
||||
this.originFilePath = originFilePath;
|
||||
}
|
||||
public void setHtmlView(boolean isHtmlView) {
|
||||
this.isHtmlView = isHtmlView;
|
||||
}
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
@@ -124,4 +175,11 @@ public class FileAttribute {
|
||||
this.forceUpdatedCache = forceUpdatedCache;
|
||||
}
|
||||
|
||||
public String getKkProxyAuthorization() {
|
||||
return kkProxyAuthorization;
|
||||
}
|
||||
|
||||
public void setKkProxyAuthorization(String kkProxyAuthorization) {
|
||||
this.kkProxyAuthorization = kkProxyAuthorization;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,9 +19,10 @@ public enum FileType {
|
||||
CODE("codeFilePreviewImpl"),
|
||||
OTHER("otherFilePreviewImpl"),
|
||||
MEDIA("mediaFilePreviewImpl"),
|
||||
MEDIACONVERT("mediaFilePreviewImpl"),
|
||||
MARKDOWN("markdownFilePreviewImpl"),
|
||||
XML("xmlFilePreviewImpl"),
|
||||
FLV("flvFilePreviewImpl"),
|
||||
JSON("jsonFilePreviewImpl"),
|
||||
CAD("cadFilePreviewImpl"),
|
||||
TIFF("tiffFilePreviewImpl"),
|
||||
OFD("ofdFilePreviewImpl"),
|
||||
@@ -43,14 +44,16 @@ public enum FileType {
|
||||
private static final String[] EPUB_TYPES = {"epub"};
|
||||
private static final String[] DCM_TYPES = {"dcm"};
|
||||
private static final String[] DRAWIO_TYPES = {"drawio"};
|
||||
private static final String[] XML_TYPES = {"xml","xbrl"};
|
||||
private static final String[] JSON_TYPES = {"json"};
|
||||
private static final String[] TIFF_TYPES = {"tif", "tiff"};
|
||||
private static final String[] OFD_TYPES = {"ofd"};
|
||||
private static final String[] SVG_TYPES = {"svg"};
|
||||
private static final String[] CAD_TYPES = {"dwg", "dxf", "dwf", "iges", "igs", "dwt", "dng", "ifc", "dwfx", "stl", "cf2", "plt"};
|
||||
private static final String[] SSIM_TEXT_TYPES = ConfigConstants.getSimText();
|
||||
private static final String[] CODES = {"java", "c", "php", "go", "python", "py", "js", "html", "ftl", "css", "lua", "sh", "rb", "yaml", "yml", "json", "h", "cpp", "cs", "aspx", "jsp", "sql"};
|
||||
private static final String[] CODES = {"java", "c", "php", "go", "python", "py", "js", "html", "ftl", "css", "lua", "sh", "rb", "yaml", "yml", "h", "cpp", "cs", "aspx", "jsp", "sql"};
|
||||
private static final String[] MEDIA_TYPES = ConfigConstants.getMedia();
|
||||
public static final String[] MEDIA_TYPES_CONVERT = ConfigConstants.getConvertMedias();
|
||||
public static final String[] MEDIA_CONVERT_TYPES = ConfigConstants.getConvertMedias();
|
||||
private static final Map<String, FileType> FILE_TYPE_MAPPER = new HashMap<>();
|
||||
|
||||
static {
|
||||
@@ -69,8 +72,8 @@ public enum FileType {
|
||||
for (String media : MEDIA_TYPES) {
|
||||
FILE_TYPE_MAPPER.put(media, FileType.MEDIA);
|
||||
}
|
||||
for (String media : MEDIA_TYPES_CONVERT) {
|
||||
FILE_TYPE_MAPPER.put(media, FileType.MEDIA);
|
||||
for (String MEDIACONVERT : MEDIA_CONVERT_TYPES) {
|
||||
FILE_TYPE_MAPPER.put( MEDIACONVERT, FileType. MEDIACONVERT);
|
||||
}
|
||||
for (String tif : TIFF_TYPES) {
|
||||
FILE_TYPE_MAPPER.put(tif, FileType.TIFF);
|
||||
@@ -105,10 +108,14 @@ public enum FileType {
|
||||
for (String drawio : DRAWIO_TYPES) {
|
||||
FILE_TYPE_MAPPER.put(drawio, FileType.DRAWIO);
|
||||
}
|
||||
for (String xml : XML_TYPES) {
|
||||
FILE_TYPE_MAPPER.put(xml, FileType.XML);
|
||||
}
|
||||
for (String json : JSON_TYPES) {
|
||||
FILE_TYPE_MAPPER.put(json, FileType.JSON);
|
||||
}
|
||||
FILE_TYPE_MAPPER.put("md", FileType.MARKDOWN);
|
||||
FILE_TYPE_MAPPER.put("xml", FileType.XML);
|
||||
FILE_TYPE_MAPPER.put("pdf", FileType.PDF);
|
||||
FILE_TYPE_MAPPER.put("flv", FileType.FLV);
|
||||
FILE_TYPE_MAPPER.put("bpmn", FileType.BPMN);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package cn.keking.service;
|
||||
|
||||
import cn.keking.config.ConfigConstants;
|
||||
import cn.keking.model.FileAttribute;
|
||||
import cn.keking.model.FileType;
|
||||
import cn.keking.utils.RarUtils;
|
||||
import cn.keking.web.filter.BaseUrlFilter;
|
||||
@@ -10,11 +12,19 @@ import net.sf.sevenzipjbinding.SevenZipException;
|
||||
import net.sf.sevenzipjbinding.impl.RandomAccessFileInStream;
|
||||
import net.sf.sevenzipjbinding.simple.ISimpleInArchive;
|
||||
import net.sf.sevenzipjbinding.simple.ISimpleInArchiveItem;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.*;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -25,75 +35,79 @@ import java.util.List;
|
||||
@Component
|
||||
public class CompressFileReader {
|
||||
private final FileHandlerService fileHandlerService;
|
||||
private static final String fileDir = ConfigConstants.getFileDir();
|
||||
|
||||
public CompressFileReader(FileHandlerService fileHandlerService) {
|
||||
this.fileHandlerService = fileHandlerService;
|
||||
}
|
||||
public String unRar(String paths, String passWord, String fileName) throws Exception {
|
||||
|
||||
public String unRar(String filePath, String filePassword, String fileName, FileAttribute fileAttribute) throws Exception {
|
||||
List<String> imgUrls = new ArrayList<>();
|
||||
String baseUrl = BaseUrlFilter.getBaseUrl();
|
||||
String archiveFileName = fileHandlerService.getFileNameFromPath(paths);
|
||||
RandomAccessFile randomAccessFile = null;
|
||||
IInArchive inArchive = null;
|
||||
try {
|
||||
randomAccessFile = new RandomAccessFile(paths, "r");
|
||||
inArchive = SevenZip.openInArchive(null, new RandomAccessFileInStream(randomAccessFile));
|
||||
String folderName = paths.substring(paths.lastIndexOf(File.separator) + 1);
|
||||
String extractPath = paths.substring(0, paths.lastIndexOf(folderName));
|
||||
ISimpleInArchive simpleInArchive = inArchive.getSimpleInterface();
|
||||
final String[] str = {null};
|
||||
String packagePath = "_";
|
||||
String folderName = filePath.replace(fileDir, ""); //修复压缩包 多重目录获取路径错误
|
||||
if (fileAttribute.isCompressFile()) {
|
||||
folderName = "_decompression" + folderName;
|
||||
}
|
||||
Path folderPath = Paths.get(fileDir, folderName + packagePath);
|
||||
Files.createDirectories(folderPath);
|
||||
|
||||
try (RandomAccessFile randomAccessFile = new RandomAccessFile(filePath, "r");
|
||||
IInArchive inArchive = SevenZip.openInArchive(null, new RandomAccessFileInStream(randomAccessFile))) {
|
||||
|
||||
ISimpleInArchive simpleInArchive = inArchive.getSimpleInterface();
|
||||
for (final ISimpleInArchiveItem item : simpleInArchive.getArchiveItems()) {
|
||||
if (!item.isFolder()) {
|
||||
ExtractOperationResult result;
|
||||
result = item.extractSlow(data -> {
|
||||
try {
|
||||
str[0] = RarUtils.getUtf8String(item.getPath());
|
||||
if (RarUtils.isMessyCode(str[0])){
|
||||
str[0] = new String(item.getPath().getBytes(StandardCharsets.ISO_8859_1), "gbk");
|
||||
}
|
||||
str[0] = str[0].replace("\\", File.separator); //Linux 下路径错误
|
||||
String str1 = str[0].substring(0, str[0].lastIndexOf(File.separator)+ 1);
|
||||
File file = new File(extractPath, folderName + "_" + File.separator + str1);
|
||||
if (!file.exists()) {
|
||||
file.mkdirs();
|
||||
}
|
||||
OutputStream out = new FileOutputStream( extractPath+ folderName + "_" + File.separator + str[0], true);
|
||||
IOUtils.write(data, out);
|
||||
out.close();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
final Path filePathInsideArchive = getFilePathInsideArchive(item, folderPath);
|
||||
ExtractOperationResult result = item.extractSlow(data -> {
|
||||
try (OutputStream out = new BufferedOutputStream(new FileOutputStream(filePathInsideArchive.toFile(), true))) {
|
||||
out.write(data);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return data.length;
|
||||
}, passWord);
|
||||
if (result == ExtractOperationResult.OK) {
|
||||
FileType type = FileType.typeFromUrl(str[0]);
|
||||
if (type.equals(FileType.PICTURE)) {
|
||||
imgUrls.add(baseUrl +folderName + "_/" + str[0].replace("\\", "/"));
|
||||
}, filePassword);
|
||||
if (result != ExtractOperationResult.OK) {
|
||||
ExtractOperationResult result1 = ExtractOperationResult.valueOf("WRONG_PASSWORD");
|
||||
if (result1.equals(result)) {
|
||||
throw new Exception("Password");
|
||||
}else {
|
||||
throw new Exception("Failed to extract RAR file.");
|
||||
}
|
||||
fileHandlerService.putImgCache(fileName, imgUrls);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
FileType type = FileType.typeFromUrl(filePathInsideArchive.toString());
|
||||
if (type.equals(FileType.PICTURE)) { //图片缓存到集合,为了特殊符号需要进行编码
|
||||
imgUrls.add(baseUrl + URLEncoder.encode(fileName + packagePath+"/"+ folderPath.relativize(filePathInsideArchive).toString().replace("\\", "/"), "UTF-8"));
|
||||
}
|
||||
}
|
||||
}
|
||||
return archiveFileName + "_";
|
||||
fileHandlerService.putImgCache(fileName + packagePath, imgUrls);
|
||||
} catch (Exception e) {
|
||||
throw new Exception(e);
|
||||
} finally {
|
||||
if (inArchive != null) {
|
||||
try {
|
||||
inArchive.close();
|
||||
} catch (SevenZipException e) {
|
||||
System.err.println("Error closing archive: " + e);
|
||||
}
|
||||
}
|
||||
if (randomAccessFile != null) {
|
||||
try {
|
||||
randomAccessFile.close();
|
||||
} catch (IOException e) {
|
||||
System.err.println("Error closing file: " + e);
|
||||
}
|
||||
}
|
||||
throw new Exception("Error processing RAR file: " + e.getMessage(), e);
|
||||
}
|
||||
return folderName + packagePath;
|
||||
}
|
||||
|
||||
}
|
||||
private Path getFilePathInsideArchive(ISimpleInArchiveItem item, Path folderPath) throws SevenZipException, UnsupportedEncodingException {
|
||||
String insideFileName = RarUtils.getUtf8String(item.getPath());
|
||||
if (RarUtils.isMessyCode(insideFileName)) {
|
||||
insideFileName = new String(item.getPath().getBytes(StandardCharsets.ISO_8859_1), "gbk");
|
||||
}
|
||||
|
||||
// 正规化路径并验证是否安全
|
||||
Path normalizedPath = folderPath.resolve(insideFileName).normalize();
|
||||
if (!normalizedPath.startsWith(folderPath)) {
|
||||
throw new SecurityException("Unsafe path detected: " + insideFileName);
|
||||
}
|
||||
|
||||
try {
|
||||
Files.createDirectories(normalizedPath.getParent());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to create directory: " + normalizedPath.getParent(), e);
|
||||
}
|
||||
return normalizedPath;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.ui.ExtendedModelMap;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
@@ -73,7 +73,7 @@ public class FileConvertQueueTask {
|
||||
TimeUnit.SECONDS.sleep(10);
|
||||
} catch (Exception ex) {
|
||||
Thread.currentThread().interrupt();
|
||||
ex.printStackTrace();
|
||||
logger.error("Failed to sleep after exception", ex);
|
||||
}
|
||||
logger.info("处理预览转换任务异常,url:{}", url, e);
|
||||
}
|
||||
|
||||
@@ -7,16 +7,15 @@ import cn.keking.service.cache.CacheService;
|
||||
import cn.keking.service.cache.NotResourceCache;
|
||||
import cn.keking.utils.EncodingDetects;
|
||||
import cn.keking.utils.KkFileUtils;
|
||||
import cn.keking.utils.UrlEncoderUtils;
|
||||
import cn.keking.utils.WebUtils;
|
||||
import cn.keking.web.filter.BaseUrlFilter;
|
||||
import com.aspose.cad.CodePages;
|
||||
import com.aspose.cad.Color;
|
||||
import com.aspose.cad.Image;
|
||||
import com.aspose.cad.LoadOptions;
|
||||
import com.aspose.cad.imageoptions.CadRasterizationOptions;
|
||||
import com.aspose.cad.imageoptions.PdfOptions;
|
||||
import com.itextpdf.text.pdf.PdfReader;
|
||||
import com.aspose.cad.*;
|
||||
import com.aspose.cad.fileformats.cad.CadDrawTypeMode;
|
||||
import com.aspose.cad.fileformats.tiff.enums.TiffExpectedFormat;
|
||||
import com.aspose.cad.imageoptions.*;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.apache.pdfbox.Loader;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.rendering.ImageType;
|
||||
import org.apache.pdfbox.rendering.PDFRenderer;
|
||||
@@ -24,20 +23,25 @@ import org.apache.pdfbox.tools.imageio.ImageIOUtil;
|
||||
import org.apache.poi.EncryptedDocumentException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.DependsOn;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.*;
|
||||
import java.net.URLDecoder;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
/**
|
||||
@@ -45,14 +49,14 @@ import java.util.stream.IntStream;
|
||||
* @date 2017/11/13
|
||||
*/
|
||||
@Component
|
||||
public class FileHandlerService {
|
||||
@DependsOn(ConfigConstants.BEAN_NAME)
|
||||
public class FileHandlerService implements InitializingBean {
|
||||
|
||||
private static final String PDF2JPG_IMAGE_FORMAT = ".jpg";
|
||||
private static final String PDF_PASSWORD_MSG = "password";
|
||||
private final Logger logger = LoggerFactory.getLogger(FileHandlerService.class);
|
||||
private final String fileDir = ConfigConstants.getFileDir();
|
||||
private final CacheService cacheService;
|
||||
|
||||
@Value("${server.tomcat.uri-encoding:UTF-8}")
|
||||
private String uriEncoding;
|
||||
|
||||
@@ -126,11 +130,11 @@ public class FileHandlerService {
|
||||
/**
|
||||
* 获取redis中压缩包内图片文件
|
||||
*
|
||||
* @param fileKey fileKey
|
||||
* @param compressFileKey compressFileKey
|
||||
* @return 图片文件访问url列表
|
||||
*/
|
||||
public List<String> getImgCache(String fileKey) {
|
||||
return cacheService.getImgCache(fileKey);
|
||||
public List<String> getImgCache(String compressFileKey) {
|
||||
return cacheService.getImgCache(compressFileKey);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -143,6 +147,16 @@ public class FileHandlerService {
|
||||
cacheService.putImgCache(fileKey, imgs);
|
||||
}
|
||||
|
||||
/**
|
||||
* cad定义线程池
|
||||
*/
|
||||
private ExecutorService pool = null;
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
pool = Executors.newFixedThreadPool(ConfigConstants.getCadThread());
|
||||
}
|
||||
|
||||
/**
|
||||
* 对转换后的文件进行操作(改变编码方式)
|
||||
*
|
||||
@@ -151,8 +165,7 @@ public class FileHandlerService {
|
||||
public void doActionConvertedFile(String outFilePath) {
|
||||
String charset = EncodingDetects.getJavaEncode(outFilePath);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
try (InputStream inputStream = new FileInputStream(outFilePath);
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, charset))) {
|
||||
try (InputStream inputStream = new FileInputStream(outFilePath); BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, charset))) {
|
||||
String line;
|
||||
while (null != (line = reader.readLine())) {
|
||||
if (line.contains("charset=gb2312")) {
|
||||
@@ -162,31 +175,31 @@ public class FileHandlerService {
|
||||
}
|
||||
// 添加sheet控制头
|
||||
sb.append("<script src=\"js/jquery-3.6.1.min.js\" type=\"text/javascript\"></script>");
|
||||
sb.append("<script src=\"js/excel.header.js\" type=\"text/javascript\"></script>");
|
||||
sb.append("<link rel=\"stylesheet\" href=\"bootstrap/css/xlsx.css\">");
|
||||
sb.append("<script src=\"excel/excel.header.js\" type=\"text/javascript\"></script>");
|
||||
sb.append("<link rel=\"stylesheet\" href=\"excel/excel.css\">");
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
logger.error("Failed to read file: {}", outFilePath, e);
|
||||
}
|
||||
// 重新写入文件
|
||||
try (FileOutputStream fos = new FileOutputStream(outFilePath);
|
||||
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(fos, StandardCharsets.UTF_8))) {
|
||||
try (FileOutputStream fos = new FileOutputStream(outFilePath); BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(fos, StandardCharsets.UTF_8))) {
|
||||
writer.write(sb.toString());
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
logger.error("Failed to write file: {}", outFilePath, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取本地 pdf 转 image 后的 web 访问地址
|
||||
* @param pdfName pdf文件名
|
||||
* @param index 图片索引
|
||||
*
|
||||
* @param pdfFilePath pdf文件名
|
||||
* @param index 图片索引
|
||||
* @return 图片访问地址
|
||||
*/
|
||||
private String getPdf2jpgUrl(String pdfName, int index) {
|
||||
private String getPdf2jpgUrl(String pdfFilePath, int index) {
|
||||
String baseUrl = BaseUrlFilter.getBaseUrl();
|
||||
String pdfFolder = pdfName.substring(0, pdfName.length() - 4);
|
||||
pdfFilePath = pdfFilePath.replace(fileDir, "");
|
||||
String pdfFolder = pdfFilePath.substring(0, pdfFilePath.length() - 4);
|
||||
String urlPrefix;
|
||||
|
||||
try {
|
||||
urlPrefix = baseUrl + URLEncoder.encode(pdfFolder, uriEncoding).replaceAll("\\+", "%20");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
@@ -198,18 +211,18 @@ public class FileHandlerService {
|
||||
|
||||
/**
|
||||
* 获取缓存中的 pdf 转换成 jpg 图片集
|
||||
*
|
||||
* @param pdfFilePath pdf文件路径
|
||||
* @param pdfName pdf文件名称
|
||||
* @return 图片访问集合
|
||||
*/
|
||||
private List<String> loadPdf2jpgCache(String pdfFilePath, String pdfName) {
|
||||
private List<String> loadPdf2jpgCache(String pdfFilePath) {
|
||||
List<String> imageUrls = new ArrayList<>();
|
||||
Integer imageCount = this.getPdf2jpgCache(pdfFilePath);
|
||||
if (Objects.isNull(imageCount)) {
|
||||
return imageUrls;
|
||||
}
|
||||
IntStream.range(0, imageCount).forEach(i -> {
|
||||
String imageUrl = this.getPdf2jpgUrl(pdfName, i);
|
||||
String imageUrl = this.getPdf2jpgUrl(pdfFilePath, i);
|
||||
imageUrls.add(imageUrl);
|
||||
});
|
||||
return imageUrls;
|
||||
@@ -217,75 +230,96 @@ public class FileHandlerService {
|
||||
|
||||
/**
|
||||
* pdf文件转换成jpg图片集
|
||||
*
|
||||
* @param pdfFilePath pdf文件路径
|
||||
* @param pdfName pdf文件名称
|
||||
* @return 图片访问集合
|
||||
* fileNameFilePath pdf文件路径
|
||||
* pdfFilePath pdf输出文件路径
|
||||
* pdfName pdf文件名称
|
||||
* loadPdf2jpgCache 图片访问集合
|
||||
*/
|
||||
public List<String> pdf2jpg(String pdfFilePath, String pdfName, FileAttribute fileAttribute) throws Exception {
|
||||
public List<String> pdf2jpg(String fileNameFilePath, String pdfFilePath, String pdfName, FileAttribute fileAttribute) throws Exception {
|
||||
boolean forceUpdatedCache = fileAttribute.forceUpdatedCache();
|
||||
boolean usePasswordCache = fileAttribute.getUsePasswordCache();
|
||||
String filePassword = fileAttribute.getFilePassword();
|
||||
String pdfPassword = null;
|
||||
PDDocument doc = null;
|
||||
PdfReader pdfReader = null;
|
||||
PDDocument doc;
|
||||
final String[] pdfPassword = {null};
|
||||
final int[] pageCount = new int[1];
|
||||
if (!forceUpdatedCache) {
|
||||
List<String> cacheResult = this.loadPdf2jpgCache(pdfFilePath, pdfName);
|
||||
List<String> cacheResult = this.loadPdf2jpgCache(pdfFilePath);
|
||||
if (!CollectionUtils.isEmpty(cacheResult)) {
|
||||
return cacheResult;
|
||||
}
|
||||
}
|
||||
List<String> imageUrls = new ArrayList<>();
|
||||
File pdfFile = new File(fileNameFilePath);
|
||||
if (!pdfFile.exists()) {
|
||||
return null;
|
||||
}
|
||||
int index = pdfFilePath.lastIndexOf(".");
|
||||
String folder = pdfFilePath.substring(0, index);
|
||||
File path = new File(folder);
|
||||
if (!path.exists() && !path.mkdirs()) {
|
||||
logger.error("创建转换文件【{}】目录失败,请检查目录权限!", folder);
|
||||
}
|
||||
try {
|
||||
File pdfFile = new File(pdfFilePath);
|
||||
if (!pdfFile.exists()) {
|
||||
return null;
|
||||
}
|
||||
doc = PDDocument.load(pdfFile,filePassword);
|
||||
doc = Loader.loadPDF(pdfFile, filePassword);
|
||||
doc.setResourceCache(new NotResourceCache());
|
||||
int pageCount = doc.getNumberOfPages();
|
||||
PDFRenderer pdfRenderer = new PDFRenderer(doc);
|
||||
int index = pdfFilePath.lastIndexOf(".");
|
||||
String folder = pdfFilePath.substring(0, index);
|
||||
File path = new File(folder);
|
||||
if (!path.exists() && !path.mkdirs()) {
|
||||
logger.error("创建转换文件【{}】目录失败,请检查目录权限!", folder);
|
||||
}
|
||||
String imageFilePath;
|
||||
for (int pageIndex = 0; pageIndex < pageCount; pageIndex++) {
|
||||
imageFilePath = folder + File.separator + pageIndex + PDF2JPG_IMAGE_FORMAT;
|
||||
BufferedImage image = pdfRenderer.renderImageWithDPI(pageIndex, ConfigConstants.getPdf2JpgDpi(), ImageType.RGB);
|
||||
ImageIOUtil.writeImage(image, imageFilePath, ConfigConstants.getPdf2JpgDpi());
|
||||
String imageUrl = this.getPdf2jpgUrl(pdfName, pageIndex);
|
||||
imageUrls.add(imageUrl);
|
||||
}
|
||||
try {
|
||||
pdfReader = new PdfReader(pdfFilePath); //读取PDF文件
|
||||
} catch (Exception e) { //获取异常方法 判断是否有加密字符串
|
||||
Throwable[] throwableArray = ExceptionUtils.getThrowables(e);
|
||||
for (Throwable throwable : throwableArray) {
|
||||
if (throwable instanceof IOException || throwable instanceof EncryptedDocumentException) {
|
||||
if (e.getMessage().toLowerCase().contains(PDF_PASSWORD_MSG)) {
|
||||
pdfPassword = PDF_PASSWORD_MSG;
|
||||
}
|
||||
pageCount[0] = doc.getNumberOfPages();
|
||||
} catch (IOException e) {
|
||||
Throwable[] throwableArray = ExceptionUtils.getThrowables(e);
|
||||
for (Throwable throwable : throwableArray) {
|
||||
if (throwable instanceof IOException || throwable instanceof EncryptedDocumentException) {
|
||||
if (e.getMessage().toLowerCase().contains(PDF_PASSWORD_MSG)) {
|
||||
pdfPassword[0] = PDF_PASSWORD_MSG; //查询到该文件是密码文件 输出带密码的值
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!PDF_PASSWORD_MSG.equals(pdfPassword[0])) { //该文件异常 错误原因非密码原因输出错误
|
||||
logger.error("Convert pdf exception, pdfFilePath:{}", pdfFilePath, e);
|
||||
} finally {
|
||||
if (pdfReader != null) { //关闭
|
||||
pdfReader.close();
|
||||
}
|
||||
}
|
||||
//判断是否加密文件 加密文件不缓存
|
||||
if (!PDF_PASSWORD_MSG.equals(pdfPassword)) {
|
||||
this.addPdf2jpgCache(pdfFilePath, pageCount);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.error("Convert pdf to jpg exception, pdfFilePath:{}", pdfFilePath, e);
|
||||
throw new Exception(e);
|
||||
} finally {
|
||||
if (doc != null) { //关闭
|
||||
}
|
||||
Callable <List<String>> call = () -> {
|
||||
try {
|
||||
String imageFilePath;
|
||||
BufferedImage image = null;
|
||||
PDFRenderer pdfRenderer = new PDFRenderer(doc);
|
||||
pdfRenderer.setSubsamplingAllowed(true);
|
||||
for (int pageIndex = 0; pageIndex < pageCount[0]; pageIndex++) {
|
||||
imageFilePath = folder + File.separator + pageIndex + PDF2JPG_IMAGE_FORMAT;
|
||||
image = pdfRenderer.renderImageWithDPI(pageIndex, ConfigConstants.getPdf2JpgDpi(), ImageType.RGB);
|
||||
ImageIOUtil.writeImage(image, imageFilePath, ConfigConstants.getPdf2JpgDpi());
|
||||
String imageUrl = this.getPdf2jpgUrl(pdfFilePath, pageIndex);
|
||||
imageUrls.add(imageUrl);
|
||||
}
|
||||
image.flush();
|
||||
} catch (IOException e) {
|
||||
throw new Exception(e);
|
||||
} finally {
|
||||
doc.close();
|
||||
}
|
||||
return imageUrls;
|
||||
};
|
||||
Future<List<String>> result = pool.submit(call);
|
||||
int pdftimeout;
|
||||
if(pageCount[0] <=50){
|
||||
pdftimeout = ConfigConstants.getPdfTimeout();
|
||||
}else if(pageCount[0] <=200){
|
||||
pdftimeout = ConfigConstants.getPdfTimeout80();
|
||||
}else {
|
||||
pdftimeout = ConfigConstants.getPdfTimeout200();
|
||||
}
|
||||
try {
|
||||
result.get(pdftimeout, TimeUnit.SECONDS);
|
||||
// 如果在超时时间内,没有数据返回:则抛出TimeoutException异常
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
throw new Exception(e);
|
||||
} catch (TimeoutException e) {
|
||||
throw new Exception("overtime");
|
||||
} finally {
|
||||
//关闭
|
||||
doc.close();
|
||||
}
|
||||
if (usePasswordCache || ObjectUtils.isEmpty(filePassword)) { //加密文件 判断是否启用缓存命令
|
||||
this.addPdf2jpgCache(pdfFilePath, pageCount[0]);
|
||||
}
|
||||
return imageUrls;
|
||||
}
|
||||
@@ -297,48 +331,119 @@ public class FileHandlerService {
|
||||
* @param outputFilePath pdf输出文件路径
|
||||
* @return 转换是否成功
|
||||
*/
|
||||
public String cadToPdf(String inputFilePath, String outputFilePath) throws Exception {
|
||||
public String cadToPdf(String inputFilePath, String outputFilePath, String cadPreviewType, FileAttribute fileAttribute) throws Exception {
|
||||
final InterruptionTokenSource source = new InterruptionTokenSource();//CAD延时
|
||||
final SvgOptions SvgOptions = new SvgOptions();
|
||||
final PdfOptions pdfOptions = new PdfOptions();
|
||||
final TiffOptions TiffOptions = new TiffOptions(TiffExpectedFormat.TiffJpegRgb);
|
||||
if (fileAttribute.isCompressFile()) { //判断 是压缩包的创建新的目录
|
||||
int index = outputFilePath.lastIndexOf("/"); //截取最后一个斜杠的前面的内容
|
||||
String folder = outputFilePath.substring(0, index);
|
||||
File path = new File(folder);
|
||||
//目录不存在 创建新的目录
|
||||
if (!path.exists()) {
|
||||
path.mkdirs();
|
||||
}
|
||||
}
|
||||
File outputFile = new File(outputFilePath);
|
||||
LoadOptions opts = new LoadOptions();
|
||||
opts.setSpecifiedEncoding(CodePages.SimpChinese);
|
||||
com.aspose.cad.Image cadImage = Image.load(inputFilePath, opts);
|
||||
CadRasterizationOptions cadRasterizationOptions = new CadRasterizationOptions();
|
||||
cadRasterizationOptions.setBackgroundColor(Color.getWhite());
|
||||
cadRasterizationOptions.setPageWidth(1400);
|
||||
cadRasterizationOptions.setPageHeight(650);
|
||||
cadRasterizationOptions.setAutomaticLayoutsScaling(true);
|
||||
cadRasterizationOptions.setNoScaling(false);
|
||||
cadRasterizationOptions.setDrawType(1);
|
||||
PdfOptions pdfOptions = new PdfOptions();
|
||||
pdfOptions.setVectorRasterizationOptions(cadRasterizationOptions);
|
||||
OutputStream stream = null;
|
||||
try {
|
||||
stream = new FileOutputStream(outputFile);
|
||||
cadImage.save(stream, pdfOptions);
|
||||
} catch (IOException e) {
|
||||
logger.error("PDFFileNotFoundException,inputFilePath:{}", inputFilePath, e);
|
||||
return "null";
|
||||
LoadOptions opts = new LoadOptions();
|
||||
opts.setSpecifiedEncoding(CodePages.SimpChinese);
|
||||
final Image cadImage = Image.load(inputFilePath, opts);
|
||||
try {
|
||||
RasterizationQuality rasterizationQuality = new RasterizationQuality();
|
||||
rasterizationQuality.setArc(RasterizationQualityValue.High);
|
||||
rasterizationQuality.setHatch(RasterizationQualityValue.High);
|
||||
rasterizationQuality.setText(RasterizationQualityValue.High);
|
||||
rasterizationQuality.setOle(RasterizationQualityValue.High);
|
||||
rasterizationQuality.setObjectsPrecision(RasterizationQualityValue.High);
|
||||
rasterizationQuality.setTextThicknessNormalization(true);
|
||||
CadRasterizationOptions cadRasterizationOptions = new CadRasterizationOptions();
|
||||
cadRasterizationOptions.setBackgroundColor(Color.getWhite());
|
||||
cadRasterizationOptions.setPageWidth(cadImage.getWidth());
|
||||
cadRasterizationOptions.setPageHeight(cadImage.getHeight());
|
||||
cadRasterizationOptions.setUnitType(cadImage.getUnitType());
|
||||
cadRasterizationOptions.setAutomaticLayoutsScaling(false);
|
||||
cadRasterizationOptions.setNoScaling(false);
|
||||
cadRasterizationOptions.setQuality(rasterizationQuality);
|
||||
cadRasterizationOptions.setDrawType(CadDrawTypeMode.UseObjectColor);
|
||||
cadRasterizationOptions.setExportAllLayoutContent(true);
|
||||
cadRasterizationOptions.setVisibilityMode(VisibilityMode.AsScreen);
|
||||
switch (cadPreviewType) { //新增格式方法
|
||||
case "svg":
|
||||
SvgOptions.setVectorRasterizationOptions(cadRasterizationOptions);
|
||||
SvgOptions.setInterruptionToken(source.getToken());
|
||||
break;
|
||||
case "pdf":
|
||||
pdfOptions.setVectorRasterizationOptions(cadRasterizationOptions);
|
||||
pdfOptions.setInterruptionToken(source.getToken());
|
||||
break;
|
||||
case "tif":
|
||||
TiffOptions.setVectorRasterizationOptions(cadRasterizationOptions);
|
||||
TiffOptions.setInterruptionToken(source.getToken());
|
||||
break;
|
||||
}
|
||||
Callable<String> call = () -> {
|
||||
try (OutputStream stream = new FileOutputStream(outputFile)) {
|
||||
switch (cadPreviewType) {
|
||||
case "svg":
|
||||
cadImage.save(stream, SvgOptions);
|
||||
break;
|
||||
case "pdf":
|
||||
cadImage.save(stream, pdfOptions);
|
||||
break;
|
||||
case "tif":
|
||||
cadImage.save(stream, TiffOptions);
|
||||
break;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.error("CADFileNotFoundException,inputFilePath:{}", inputFilePath, e);
|
||||
return null;
|
||||
} finally {
|
||||
cadImage.dispose();
|
||||
source.interrupt(); //结束任务
|
||||
source.dispose();
|
||||
}
|
||||
return "true";
|
||||
};
|
||||
Future<String> result = pool.submit(call);
|
||||
try {
|
||||
result.get(Long.parseLong(ConfigConstants.getCadTimeout()), TimeUnit.SECONDS);
|
||||
// 如果在超时时间内,没有数据返回:则抛出TimeoutException异常
|
||||
} catch (InterruptedException e) {
|
||||
logger.error("CAD转换文件异常:", e);
|
||||
return null;
|
||||
} catch (ExecutionException e) {
|
||||
logger.error("CAD转换在尝试取得任务结果时出错:", e);
|
||||
return null;
|
||||
} catch (TimeoutException e) {
|
||||
logger.error("CAD转换时间超时:", e);
|
||||
return null;
|
||||
} finally {
|
||||
source.interrupt(); //结束任务
|
||||
source.dispose();
|
||||
cadImage.dispose();
|
||||
// pool.shutdownNow();
|
||||
}
|
||||
} finally {
|
||||
source.dispose();
|
||||
cadImage.dispose();
|
||||
}
|
||||
} finally {
|
||||
if (stream != null) { //关闭
|
||||
stream.close();
|
||||
}
|
||||
if (cadImage != null) { //关闭
|
||||
cadImage.close();
|
||||
}
|
||||
source.dispose();
|
||||
}
|
||||
return "true";
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param str 原字符串(待截取原串)
|
||||
* @param str 原字符串(待截取原串)
|
||||
* @param posStr 指定字符串
|
||||
* @return 截取截取指定字符串之后的数据
|
||||
*/
|
||||
public static String getSubString(String str, String posStr){
|
||||
public static String getSubString(String str, String posStr) {
|
||||
return str.substring(str.indexOf(posStr) + posStr.length());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取文件属性
|
||||
*
|
||||
@@ -349,45 +454,73 @@ public class FileHandlerService {
|
||||
FileAttribute attribute = new FileAttribute();
|
||||
String suffix;
|
||||
FileType type;
|
||||
String fileName;
|
||||
String originFileName; //原始文件名
|
||||
String outFilePath; //生成文件的路径
|
||||
String originFilePath; //原始文件路径
|
||||
String fullFileName = WebUtils.getUrlParameterReg(url, "fullfilename");
|
||||
String compressFileKey = WebUtils.getUrlParameterReg(url, "kkCompressfileKey"); //压缩包获取文件名
|
||||
String compressFilePath = WebUtils.getUrlParameterReg(url, "kkCompressfilepath"); //压缩包获取文件路径
|
||||
if (StringUtils.hasText(fullFileName)) {
|
||||
fileName = fullFileName;
|
||||
originFileName = fullFileName;
|
||||
type = FileType.typeFromFileName(fullFileName);
|
||||
suffix = KkFileUtils.suffixFromFileName(fullFileName);
|
||||
// 移除fullfilename参数
|
||||
if (url.indexOf("fullfilename=" + fullFileName + "&") > 0) {
|
||||
url = url.replace("fullfilename=" + fullFileName + "&", "");
|
||||
} else {
|
||||
url = url.replace("fullfilename=" + fullFileName, "");
|
||||
}
|
||||
url = WebUtils.clearFullfilenameParam(url);
|
||||
} else {
|
||||
fileName = WebUtils.getFileNameFromURL(url);
|
||||
originFileName = WebUtils.getFileNameFromURL(url);
|
||||
type = FileType.typeFromUrl(url);
|
||||
suffix = WebUtils.suffixFromUrl(url);
|
||||
}
|
||||
if (url.contains("?fileKey=")) {
|
||||
String[] strs = url.split("="); //处理解压后有反代情况下 文件的路径
|
||||
String urlStrr = getSubString(url, strs[1]);
|
||||
urlStrr = urlStrr.substring(0,urlStrr.lastIndexOf("?"));
|
||||
fileName = strs[1] + urlStrr.trim();
|
||||
attribute.setSkipDownLoad(true);
|
||||
boolean isCompressFile = !ObjectUtils.isEmpty(compressFileKey);
|
||||
if (isCompressFile) { //判断是否使用特定压缩包符号
|
||||
try {
|
||||
originFileName = URLDecoder.decode(originFileName, uriEncoding); //转义的文件名 解下出原始文件名
|
||||
attribute.setSkipDownLoad(true);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
logger.error("Failed to decode file name: {}", originFileName, e);
|
||||
}
|
||||
}
|
||||
url = WebUtils.encodeUrlFileName(url);
|
||||
fileName = KkFileUtils.htmlEscape(fileName); //文件名处理
|
||||
if (UrlEncoderUtils.hasUrlEncoded(originFileName)) { //判断文件名是否转义
|
||||
try {
|
||||
originFileName = URLDecoder.decode(originFileName, uriEncoding); //转义的文件名 解下出原始文件名
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
logger.error("Failed to decode file name: {}", originFileName, e);
|
||||
}
|
||||
}else {
|
||||
url = WebUtils.encodeUrlFileName(url); //对未转义的url进行转义
|
||||
}
|
||||
originFileName = KkFileUtils.htmlEscape(originFileName); //文件名处理
|
||||
boolean isHtmlView = suffix.equalsIgnoreCase("xls") || suffix.equalsIgnoreCase("xlsx") || suffix.equalsIgnoreCase("csv") || suffix.equalsIgnoreCase("xlsm") || suffix.equalsIgnoreCase("xlt") || suffix.equalsIgnoreCase("xltm") || suffix.equalsIgnoreCase("et") || suffix.equalsIgnoreCase("ett") || suffix.equalsIgnoreCase("xlam");
|
||||
String cacheFilePrefixName = null;
|
||||
try {
|
||||
cacheFilePrefixName = originFileName.substring(0, originFileName.lastIndexOf(".")) + suffix + "."; //这里统一文件名处理 下面更具类型 各自添加后缀
|
||||
} catch (Exception e) {
|
||||
logger.error("获取文件名后缀错误:", e);
|
||||
// e.printStackTrace();
|
||||
}
|
||||
String cacheFileName = this.getCacheFileName(type, originFileName, cacheFilePrefixName, isHtmlView, isCompressFile);
|
||||
outFilePath = fileDir + cacheFileName;
|
||||
originFilePath = fileDir + originFileName;
|
||||
String cacheListName = cacheFilePrefixName + "ListName"; //文件列表缓存文件名
|
||||
attribute.setType(type);
|
||||
attribute.setName(fileName);
|
||||
attribute.setName(originFileName);
|
||||
attribute.setCacheName(cacheFileName);
|
||||
attribute.setCacheListName(cacheListName);
|
||||
attribute.setHtmlView(isHtmlView);
|
||||
attribute.setOutFilePath(outFilePath);
|
||||
attribute.setOriginFilePath(originFilePath);
|
||||
attribute.setSuffix(suffix);
|
||||
attribute.setUrl(url);
|
||||
if (req != null) {
|
||||
String officePreviewType = req.getParameter("officePreviewType");
|
||||
String forceUpdatedCache = req.getParameter("forceUpdatedCache");
|
||||
String fileKey = WebUtils.getUrlParameterReg(url, "fileKey");
|
||||
String usePasswordCache = req.getParameter("usePasswordCache");
|
||||
if (StringUtils.hasText(officePreviewType)) {
|
||||
attribute.setOfficePreviewType(officePreviewType);
|
||||
}
|
||||
if (StringUtils.hasText(fileKey)) {
|
||||
attribute.setFileKey(fileKey);
|
||||
if (StringUtils.hasText(compressFileKey)) {
|
||||
attribute.setCompressFile(isCompressFile);
|
||||
attribute.setCompressFileKey(compressFileKey);
|
||||
}
|
||||
if ("true".equalsIgnoreCase(forceUpdatedCache)) {
|
||||
attribute.setForceUpdatedCache(true);
|
||||
@@ -402,16 +535,46 @@ public class FileHandlerService {
|
||||
if (StringUtils.hasText(filePassword)) {
|
||||
attribute.setFilePassword(filePassword);
|
||||
}
|
||||
|
||||
String userToken = req.getParameter("userToken");
|
||||
if (StringUtils.hasText(userToken)) {
|
||||
attribute.setUserToken(userToken);
|
||||
if ("true".equalsIgnoreCase(usePasswordCache)) {
|
||||
attribute.setUsePasswordCache(true);
|
||||
}
|
||||
String kkProxyAuthorization = req.getHeader("kk-proxy-authorization");
|
||||
attribute.setKkProxyAuthorization(kkProxyAuthorization);
|
||||
|
||||
}
|
||||
|
||||
return attribute;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缓存的文件名
|
||||
*
|
||||
* @return 文件名
|
||||
*/
|
||||
private String getCacheFileName(FileType type, String originFileName, String cacheFilePrefixName, boolean isHtmlView, boolean isCompressFile) {
|
||||
String cacheFileName;
|
||||
if (type.equals(FileType.OFFICE)) {
|
||||
cacheFileName = cacheFilePrefixName + (isHtmlView ? "html" : "pdf"); //生成文件添加类型后缀 防止同名文件
|
||||
} else if (type.equals(FileType.PDF)) {
|
||||
cacheFileName = originFileName;
|
||||
} else if (type.equals(FileType.MEDIACONVERT)) {
|
||||
cacheFileName = cacheFilePrefixName + "mp4";
|
||||
} else if (type.equals(FileType.CAD)) {
|
||||
String cadPreviewType = ConfigConstants.getCadPreviewType();
|
||||
cacheFileName = cacheFilePrefixName + cadPreviewType; //生成文件添加类型后缀 防止同名文件
|
||||
} else if (type.equals(FileType.COMPRESS)) {
|
||||
cacheFileName = originFileName;
|
||||
} else if (type.equals(FileType.TIFF)) {
|
||||
cacheFileName = cacheFilePrefixName + ConfigConstants.getTifPreviewType();
|
||||
} else {
|
||||
cacheFileName = originFileName;
|
||||
}
|
||||
if (isCompressFile) { //判断是否使用特定压缩包符号
|
||||
cacheFileName = "_decompression" + cacheFileName;
|
||||
}
|
||||
return cacheFileName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 已转换过的视频文件集合(缓存)
|
||||
*/
|
||||
|
||||
@@ -9,7 +9,6 @@ import org.springframework.ui.Model;
|
||||
*/
|
||||
public interface FilePreview {
|
||||
|
||||
String FLV_FILE_PREVIEW_PAGE = "flv";
|
||||
String PDF_FILE_PREVIEW_PAGE = "pdf";
|
||||
String PPT_FILE_PREVIEW_PAGE = "ppt";
|
||||
String COMPRESS_FILE_PREVIEW_PAGE = "compress";
|
||||
@@ -27,12 +26,14 @@ public interface FilePreview {
|
||||
String CODE_FILE_PREVIEW_PAGE = "code";
|
||||
String EXEL_FILE_PREVIEW_PAGE = "html";
|
||||
String XML_FILE_PREVIEW_PAGE = "xml";
|
||||
String JSON_FILE_PREVIEW_PAGE = "json";
|
||||
String MARKDOWN_FILE_PREVIEW_PAGE = "markdown";
|
||||
String BPMN_FILE_PREVIEW_PAGE = "bpmn";
|
||||
String DCM_FILE_PREVIEW_PAGE = "dcm";
|
||||
String DRAWUI_FILE_PREVIEW_PAGE = "drawio";
|
||||
String NOT_SUPPORTED_FILE_PAGE = "fileNotSupported";
|
||||
String XLSX_FILE_PREVIEW_PAGE = "officeweb";
|
||||
String CSV_FILE_PREVIEW_PAGE = "csv";
|
||||
|
||||
String filePreviewHandle(String url, Model model, FileAttribute fileAttribute);
|
||||
}
|
||||
|
||||
@@ -15,8 +15,8 @@ import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.PreDestroy;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.annotation.PreDestroy;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -43,6 +43,12 @@ public class OfficePluginManager {
|
||||
@Value("${office.plugin.task.timeout:5m}")
|
||||
private String timeOut;
|
||||
|
||||
@Value("${office.plugin.task.taskexecutiontimeout:5m}")
|
||||
private String taskExecutionTimeout;
|
||||
|
||||
@Value("${office.plugin.task.maxtasksperprocess:5}")
|
||||
private int maxTasksPerProcess;
|
||||
|
||||
/**
|
||||
* 启动Office组件进程
|
||||
*/
|
||||
@@ -60,10 +66,13 @@ public class OfficePluginManager {
|
||||
String[] portsString = serverPorts.split(",");
|
||||
int[] ports = Arrays.stream(portsString).mapToInt(Integer::parseInt).toArray();
|
||||
long timeout = DurationStyle.detectAndParse(timeOut).toMillis();
|
||||
long taskexecutiontimeout = DurationStyle.detectAndParse(taskExecutionTimeout).toMillis();
|
||||
officeManager = LocalOfficeManager.builder()
|
||||
.officeHome(officeHome)
|
||||
.portNumbers(ports)
|
||||
.processTimeout(timeout)
|
||||
.maxTasksPerProcess(maxTasksPerProcess)
|
||||
.taskExecutionTimeout(taskexecutiontimeout)
|
||||
.build();
|
||||
officeManager.start();
|
||||
InstalledOfficeManagerHolder.setInstance(officeManager);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package cn.keking.service;
|
||||
|
||||
import cn.keking.config.ConfigConstants;
|
||||
import cn.keking.model.FileAttribute;
|
||||
import com.sun.star.document.UpdateDocMode;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
@@ -33,15 +34,36 @@ public class OfficeToPdfService {
|
||||
logger.error("创建目录【{}】失败,请检查目录权限!",outputFilePath_end);
|
||||
}
|
||||
LocalConverter.Builder builder;
|
||||
Map<String, Object> filterData = new HashMap<>();
|
||||
filterData.put("EncryptFile", true);
|
||||
if(!ConfigConstants.getOfficePageRange().equals("false")){
|
||||
filterData.put("PageRange", ConfigConstants.getOfficePageRange()); //限制页面
|
||||
}
|
||||
if(!ConfigConstants.getOfficeWatermark().equals("false")){
|
||||
filterData.put("Watermark", ConfigConstants.getOfficeWatermark()); //水印
|
||||
}
|
||||
filterData.put("Quality", ConfigConstants.getOfficeQuality()); //图片压缩
|
||||
filterData.put("MaxImageResolution", ConfigConstants.getOfficeMaxImageResolution()); //DPI
|
||||
if(ConfigConstants.getOfficeExportBookmarks()){
|
||||
filterData.put("ExportBookmarks", true); //导出书签
|
||||
}
|
||||
if(ConfigConstants.getOfficeExportNotes()){
|
||||
filterData.put("ExportNotes", true); //批注作为PDF的注释
|
||||
}
|
||||
if(ConfigConstants.getOfficeDocumentOpenPasswords()){
|
||||
filterData.put("DocumentOpenPassword", fileAttribute.getFilePassword()); //给PDF添加密码
|
||||
}
|
||||
Map<String, Object> customProperties = new HashMap<>();
|
||||
customProperties.put("FilterData", filterData);
|
||||
if (StringUtils.isNotBlank(fileAttribute.getFilePassword())) {
|
||||
Map<String, Object> loadProperties = new HashMap<>();
|
||||
loadProperties.put("Hidden", true);
|
||||
loadProperties.put("ReadOnly", true);
|
||||
loadProperties.put("UpdateDocMode", UpdateDocMode.NO_UPDATE);
|
||||
loadProperties.put("Password", fileAttribute.getFilePassword());
|
||||
builder = LocalConverter.builder().loadProperties(loadProperties);
|
||||
builder = LocalConverter.builder().loadProperties(loadProperties).storeProperties(customProperties);
|
||||
} else {
|
||||
builder = LocalConverter.builder();
|
||||
builder = LocalConverter.builder().storeProperties(customProperties);
|
||||
}
|
||||
builder.build().convert(inputFile).to(outputFile).execute();
|
||||
}
|
||||
|
||||
@@ -2,9 +2,12 @@ package cn.keking.service.cache;
|
||||
|
||||
import org.apache.pdfbox.cos.COSObject;
|
||||
import org.apache.pdfbox.pdmodel.DefaultResourceCache;
|
||||
import org.apache.pdfbox.pdmodel.documentinterchange.markedcontent.PDPropertyList;
|
||||
import org.apache.pdfbox.pdmodel.graphics.PDXObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
|
||||
import org.apache.pdfbox.pdmodel.graphics.pattern.PDAbstractPattern;
|
||||
import org.apache.pdfbox.pdmodel.graphics.shading.PDShading;
|
||||
import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState;
|
||||
|
||||
/**
|
||||
* @author: Sawyer.Yong
|
||||
@@ -14,7 +17,21 @@ import java.io.IOException;
|
||||
public class NotResourceCache extends DefaultResourceCache {
|
||||
|
||||
@Override
|
||||
public void put(COSObject indirect, PDXObject xobject) throws IOException {
|
||||
// do nothing
|
||||
public void put(COSObject indirect, PDColorSpace colorSpace) {
|
||||
}
|
||||
@Override
|
||||
public void put(COSObject indirect, PDExtendedGraphicsState extGState) {
|
||||
}
|
||||
@Override
|
||||
public void put(COSObject indirect, PDShading shading) {
|
||||
}
|
||||
@Override
|
||||
public void put(COSObject indirect, PDAbstractPattern pattern) {
|
||||
}
|
||||
@Override
|
||||
public void put(COSObject indirect, PDPropertyList propertyList) {
|
||||
}
|
||||
@Override
|
||||
public void put(COSObject indirect, PDXObject xobject) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -101,6 +101,7 @@ public class CacheServiceJDKImpl implements CacheService {
|
||||
initPDFCachePool(CacheService.DEFAULT_PDF_CAPACITY);
|
||||
initIMGCachePool(CacheService.DEFAULT_IMG_CAPACITY);
|
||||
initPdfImagesCachePool(CacheService.DEFAULT_PDFIMG_CAPACITY);
|
||||
initMediaConvertCachePool(CacheService.DEFAULT_MEDIACONVERT_CAPACITY);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -107,6 +107,7 @@ public class CacheServiceRedisImpl implements CacheService {
|
||||
cleanPdfCache();
|
||||
cleanImgCache();
|
||||
cleanPdfImgCache();
|
||||
cleanMediaConvertCache();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -135,4 +136,9 @@ public class CacheServiceRedisImpl implements CacheService {
|
||||
RMapCache<String, Integer> pdfImg = redissonClient.getMapCache(FILE_PREVIEW_PDF_IMGS_KEY);
|
||||
pdfImg.clear();
|
||||
}
|
||||
|
||||
private void cleanMediaConvertCache() {
|
||||
RMapCache<String, Integer> mediaConvertCache = redissonClient.getMapCache(FILE_PREVIEW_MEDIA_CONVERT_KEY);
|
||||
mediaConvertCache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ public class CacheServiceRocksDBImpl implements CacheService {
|
||||
@SuppressWarnings("unchecked")
|
||||
public Map<String, String> getPDFCache() {
|
||||
Map<String, String> result = new HashMap<>();
|
||||
try{
|
||||
try {
|
||||
result = (Map<String, String>) toObject(db.get(FILE_PREVIEW_PDF_KEY.getBytes()));
|
||||
} catch (RocksDBException | IOException | ClassNotFoundException e) {
|
||||
LOGGER.error("Get from RocksDB Exception" + e);
|
||||
@@ -116,7 +116,7 @@ public class CacheServiceRocksDBImpl implements CacheService {
|
||||
@SuppressWarnings("unchecked")
|
||||
public String getPDFCache(String key) {
|
||||
String result = "";
|
||||
try{
|
||||
try {
|
||||
Map<String, String> map = (Map<String, String>) toObject(db.get(FILE_PREVIEW_PDF_KEY.getBytes()));
|
||||
result = map.get(key);
|
||||
} catch (RocksDBException | IOException | ClassNotFoundException e) {
|
||||
@@ -129,7 +129,7 @@ public class CacheServiceRocksDBImpl implements CacheService {
|
||||
@SuppressWarnings("unchecked")
|
||||
public Map<String, List<String>> getImgCache() {
|
||||
Map<String, List<String>> result = new HashMap<>();
|
||||
try{
|
||||
try {
|
||||
result = (Map<String, List<String>>) toObject(db.get(FILE_PREVIEW_IMGS_KEY.getBytes()));
|
||||
} catch (RocksDBException | IOException | ClassNotFoundException e) {
|
||||
LOGGER.error("Get from RocksDB Exception" + e);
|
||||
@@ -142,7 +142,7 @@ public class CacheServiceRocksDBImpl implements CacheService {
|
||||
public List<String> getImgCache(String key) {
|
||||
List<String> result = new ArrayList<>();
|
||||
Map<String, List<String>> map;
|
||||
try{
|
||||
try {
|
||||
map = (Map<String, List<String>>) toObject(db.get(FILE_PREVIEW_IMGS_KEY.getBytes()));
|
||||
result = map.get(key);
|
||||
} catch (RocksDBException | IOException | ClassNotFoundException e) {
|
||||
@@ -156,7 +156,7 @@ public class CacheServiceRocksDBImpl implements CacheService {
|
||||
public Integer getPdfImageCache(String key) {
|
||||
Integer result = 0;
|
||||
Map<String, Integer> map;
|
||||
try{
|
||||
try {
|
||||
map = (Map<String, Integer>) toObject(db.get(FILE_PREVIEW_PDF_IMGS_KEY.getBytes()));
|
||||
result = map.get(key);
|
||||
} catch (RocksDBException | IOException | ClassNotFoundException e) {
|
||||
@@ -180,7 +180,7 @@ public class CacheServiceRocksDBImpl implements CacheService {
|
||||
@SuppressWarnings("unchecked")
|
||||
public Map<String, String> getMediaConvertCache() {
|
||||
Map<String, String> result = new HashMap<>();
|
||||
try{
|
||||
try {
|
||||
result = (Map<String, String>) toObject(db.get(FILE_PREVIEW_MEDIA_CONVERT_KEY.getBytes()));
|
||||
} catch (RocksDBException | IOException | ClassNotFoundException e) {
|
||||
LOGGER.error("Get from RocksDB Exception" + e);
|
||||
@@ -203,7 +203,7 @@ public class CacheServiceRocksDBImpl implements CacheService {
|
||||
@SuppressWarnings("unchecked")
|
||||
public String getMediaConvertCache(String key) {
|
||||
String result = "";
|
||||
try{
|
||||
try {
|
||||
Map<String, String> map = (Map<String, String>) toObject(db.get(FILE_PREVIEW_MEDIA_CONVERT_KEY.getBytes()));
|
||||
result = map.get(key);
|
||||
} catch (RocksDBException | IOException | ClassNotFoundException e) {
|
||||
@@ -218,6 +218,7 @@ public class CacheServiceRocksDBImpl implements CacheService {
|
||||
cleanPdfCache();
|
||||
cleanImgCache();
|
||||
cleanPdfImgCache();
|
||||
cleanMediaConvertCache();
|
||||
} catch (IOException | RocksDBException e) {
|
||||
LOGGER.error("Clean Cache Exception" + e);
|
||||
}
|
||||
@@ -236,7 +237,7 @@ public class CacheServiceRocksDBImpl implements CacheService {
|
||||
@SuppressWarnings("unchecked")
|
||||
private Map<String, Integer> getPdfImageCaches() {
|
||||
Map<String, Integer> map = new HashMap<>();
|
||||
try{
|
||||
try {
|
||||
map = (Map<String, Integer>) toObject(db.get(FILE_PREVIEW_PDF_IMGS_KEY.getBytes()));
|
||||
} catch (RocksDBException | IOException | ClassNotFoundException e) {
|
||||
LOGGER.error("Get from RocksDB Exception" + e);
|
||||
@@ -245,22 +246,22 @@ public class CacheServiceRocksDBImpl implements CacheService {
|
||||
}
|
||||
|
||||
|
||||
private byte[] toByteArray (Object obj) throws IOException {
|
||||
private byte[] toByteArray(Object obj) throws IOException {
|
||||
byte[] bytes;
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
ObjectOutputStream oos = new ObjectOutputStream(bos);
|
||||
oos.writeObject(obj);
|
||||
oos.flush();
|
||||
bytes = bos.toByteArray ();
|
||||
bytes = bos.toByteArray();
|
||||
oos.close();
|
||||
bos.close();
|
||||
return bytes;
|
||||
}
|
||||
|
||||
private Object toObject (byte[] bytes) throws IOException, ClassNotFoundException {
|
||||
private Object toObject(byte[] bytes) throws IOException, ClassNotFoundException {
|
||||
Object obj;
|
||||
ByteArrayInputStream bis = new ByteArrayInputStream (bytes);
|
||||
ObjectInputStream ois = new ObjectInputStream (bis);
|
||||
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
|
||||
ObjectInputStream ois = new ObjectInputStream(bis);
|
||||
obj = ois.readObject();
|
||||
ois.close();
|
||||
bis.close();
|
||||
@@ -281,4 +282,9 @@ public class CacheServiceRocksDBImpl implements CacheService {
|
||||
Map<String, Integer> initPDFIMGCache = new HashMap<>();
|
||||
db.put(FILE_PREVIEW_PDF_IMGS_KEY.getBytes(), toByteArray(initPDFIMGCache));
|
||||
}
|
||||
|
||||
private void cleanMediaConvertCache() throws IOException, RocksDBException {
|
||||
Map<String, String> initMediaConvertCache = new HashMap<>();
|
||||
db.put(FILE_PREVIEW_MEDIA_CONVERT_KEY.getBytes(), toByteArray(initMediaConvertCache));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,17 +3,18 @@ package cn.keking.service.impl;
|
||||
import cn.keking.config.ConfigConstants;
|
||||
import cn.keking.model.FileAttribute;
|
||||
import cn.keking.model.ReturnResponse;
|
||||
import cn.keking.service.FileHandlerService;
|
||||
import cn.keking.service.FilePreview;
|
||||
import cn.keking.utils.DownloadUtils;
|
||||
import cn.keking.service.FileHandlerService;
|
||||
import cn.keking.utils.KkFileUtils;
|
||||
import cn.keking.utils.WebUtils;
|
||||
import cn.keking.web.filter.BaseUrlFilter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static cn.keking.service.impl.OfficeFilePreviewImpl.getPreviewType;
|
||||
|
||||
/**
|
||||
@@ -23,9 +24,9 @@ import static cn.keking.service.impl.OfficeFilePreviewImpl.getPreviewType;
|
||||
@Service
|
||||
public class CadFilePreviewImpl implements FilePreview {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(CadFilePreviewImpl.class);
|
||||
private static final String OFFICE_PREVIEW_TYPE_IMAGE = "image";
|
||||
private static final String OFFICE_PREVIEW_TYPE_ALL_IMAGES = "allImages";
|
||||
private static final String FILE_DIR = ConfigConstants.getFileDir();
|
||||
|
||||
private final FileHandlerService fileHandlerService;
|
||||
private final OtherFilePreviewImpl otherFilePreview;
|
||||
@@ -40,45 +41,50 @@ public class CadFilePreviewImpl implements FilePreview {
|
||||
// 预览Type,参数传了就取参数的,没传取系统默认
|
||||
String officePreviewType = fileAttribute.getOfficePreviewType() == null ? ConfigConstants.getOfficePreviewType() : fileAttribute.getOfficePreviewType();
|
||||
String baseUrl = BaseUrlFilter.getBaseUrl();
|
||||
boolean forceUpdatedCache=fileAttribute.forceUpdatedCache();
|
||||
boolean forceUpdatedCache = fileAttribute.forceUpdatedCache();
|
||||
String fileName = fileAttribute.getName();
|
||||
String suffix = fileAttribute.getSuffix();
|
||||
String pdfName = fileName.substring(0, fileName.lastIndexOf(".")) + suffix +"." + "pdf" ; //生成文件添加类型后缀 防止同名文件
|
||||
String outFilePath = FILE_DIR + pdfName;
|
||||
String cadPreviewType = ConfigConstants.getCadPreviewType();
|
||||
String cacheName = fileAttribute.getCacheName();
|
||||
String outFilePath = fileAttribute.getOutFilePath();
|
||||
// 判断之前是否已转换过,如果转换过,直接返回,否则执行转换
|
||||
if (forceUpdatedCache || !fileHandlerService.listConvertedFiles().containsKey(pdfName) || !ConfigConstants.isCacheEnabled()) {
|
||||
String filePath;
|
||||
ReturnResponse<String> response = DownloadUtils.downLoad(fileAttribute, null);
|
||||
if (forceUpdatedCache || !fileHandlerService.listConvertedFiles().containsKey(cacheName) || !ConfigConstants.isCacheEnabled()) {
|
||||
ReturnResponse<String> response = DownloadUtils.downLoad(fileAttribute, fileName);
|
||||
if (response.isFailure()) {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, response.getMsg());
|
||||
}
|
||||
filePath = response.getContent();
|
||||
String filePath = response.getContent();
|
||||
String imageUrls = null;
|
||||
if (StringUtils.hasText(outFilePath)) {
|
||||
try {
|
||||
imageUrls = fileHandlerService.cadToPdf(filePath, outFilePath);
|
||||
imageUrls = fileHandlerService.cadToPdf(filePath, outFilePath, cadPreviewType, fileAttribute);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
logger.error("Failed to convert CAD file: {}", filePath, e);
|
||||
}
|
||||
if (imageUrls == null ) {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, "office转图片异常,请联系管理员");
|
||||
if (imageUrls == null) {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, "CAD转换异常,请联系管理员");
|
||||
}
|
||||
//是否保留CAD源文件
|
||||
if( ConfigConstants.getDeleteSourceFile()) {
|
||||
if (!fileAttribute.isCompressFile() && ConfigConstants.getDeleteSourceFile()) {
|
||||
KkFileUtils.deleteFileByPath(filePath);
|
||||
}
|
||||
if (ConfigConstants.isCacheEnabled()) {
|
||||
// 加入缓存
|
||||
fileHandlerService.addConvertedFile(pdfName, fileHandlerService.getRelativePath(outFilePath));
|
||||
fileHandlerService.addConvertedFile(cacheName, fileHandlerService.getRelativePath(outFilePath));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (baseUrl != null && (OFFICE_PREVIEW_TYPE_IMAGE.equals(officePreviewType) || OFFICE_PREVIEW_TYPE_ALL_IMAGES.equals(officePreviewType))) {
|
||||
return getPreviewType(model, fileAttribute, officePreviewType, baseUrl, pdfName, outFilePath, fileHandlerService, OFFICE_PREVIEW_TYPE_IMAGE,otherFilePreview);
|
||||
cacheName= WebUtils.encodeFileName(cacheName);
|
||||
if ("tif".equalsIgnoreCase(cadPreviewType)) {
|
||||
model.addAttribute("currentUrl", cacheName);
|
||||
return TIFF_FILE_PREVIEW_PAGE;
|
||||
} else if ("svg".equalsIgnoreCase(cadPreviewType)) {
|
||||
model.addAttribute("currentUrl", cacheName);
|
||||
return SVG_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
model.addAttribute("pdfUrl", pdfName);
|
||||
if (baseUrl != null && (OFFICE_PREVIEW_TYPE_IMAGE.equals(officePreviewType) || OFFICE_PREVIEW_TYPE_ALL_IMAGES.equals(officePreviewType))) {
|
||||
return getPreviewType(model, fileAttribute, officePreviewType, cacheName, outFilePath, fileHandlerService, OFFICE_PREVIEW_TYPE_IMAGE, otherFilePreview);
|
||||
}
|
||||
model.addAttribute("pdfUrl", cacheName);
|
||||
return PDF_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import cn.keking.service.CompressFileReader;
|
||||
import cn.keking.utils.KkFileUtils;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.apache.poi.EncryptedDocumentException;
|
||||
import org.slf4j.Logger;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
@@ -28,6 +29,9 @@ public class CompressFilePreviewImpl implements FilePreview {
|
||||
private final CompressFileReader compressFileReader;
|
||||
private final OtherFilePreviewImpl otherFilePreview;
|
||||
private static final String Rar_PASSWORD_MSG = "password";
|
||||
private static final Logger logger = org.slf4j.LoggerFactory.getLogger(CompressFileReader.class);
|
||||
|
||||
|
||||
public CompressFilePreviewImpl(FileHandlerService fileHandlerService, CompressFileReader compressFileReader, OtherFilePreviewImpl otherFilePreview) {
|
||||
this.fileHandlerService = fileHandlerService;
|
||||
this.compressFileReader = compressFileReader;
|
||||
@@ -36,47 +40,44 @@ public class CompressFilePreviewImpl implements FilePreview {
|
||||
|
||||
@Override
|
||||
public String filePreviewHandle(String url, Model model, FileAttribute fileAttribute) {
|
||||
String fileName=fileAttribute.getName();
|
||||
String fileName = fileAttribute.getName();
|
||||
String filePassword = fileAttribute.getFilePassword();
|
||||
boolean forceUpdatedCache=fileAttribute.forceUpdatedCache();
|
||||
boolean forceUpdatedCache = fileAttribute.forceUpdatedCache();
|
||||
String fileTree = null;
|
||||
// 判断文件名是否存在(redis缓存读取)
|
||||
if (forceUpdatedCache || !StringUtils.hasText(fileHandlerService.getConvertedFile(fileName)) || !ConfigConstants.isCacheEnabled()) {
|
||||
if (forceUpdatedCache || !StringUtils.hasText(fileHandlerService.getConvertedFile(fileName)) || !ConfigConstants.isCacheEnabled()) {
|
||||
ReturnResponse<String> response = DownloadUtils.downLoad(fileAttribute, fileName);
|
||||
if (response.isFailure()) {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, response.getMsg());
|
||||
}
|
||||
String filePath = response.getContent();
|
||||
try {
|
||||
fileTree = compressFileReader.unRar(filePath, filePassword,fileName);
|
||||
fileTree = compressFileReader.unRar(filePath, filePassword, fileName, fileAttribute);
|
||||
} catch (Exception e) {
|
||||
Throwable[] throwableArray = ExceptionUtils.getThrowables(e);
|
||||
for (Throwable throwable : throwableArray) {
|
||||
if (throwable instanceof IOException || throwable instanceof EncryptedDocumentException) {
|
||||
if (e.getMessage().toLowerCase().contains(Rar_PASSWORD_MSG)) {
|
||||
model.addAttribute("needFilePassword", true);
|
||||
return EXEL_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
}
|
||||
if (e.getMessage().toLowerCase().contains(Rar_PASSWORD_MSG)) {
|
||||
model.addAttribute("needFilePassword", true);
|
||||
return EXEL_FILE_PREVIEW_PAGE;
|
||||
}else {
|
||||
logger.error("Error processing RAR file: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
if (!ObjectUtils.isEmpty(fileTree)) {
|
||||
//是否保留压缩包源文件
|
||||
if (ConfigConstants.getDeleteSourceFile()) {
|
||||
if (!fileAttribute.isCompressFile() && ConfigConstants.getDeleteSourceFile()) {
|
||||
KkFileUtils.deleteFileByPath(filePath);
|
||||
}
|
||||
if (ConfigConstants.isCacheEnabled()) {
|
||||
// 加入缓存
|
||||
fileHandlerService.addConvertedFile(fileName, fileTree);
|
||||
}
|
||||
}else {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, "压缩文件密码错误! 压缩文件损坏! 压缩文件类型不受支持!");
|
||||
} else {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, "该压缩包文件无法处理!");
|
||||
}
|
||||
} else {
|
||||
fileTree = fileHandlerService.getConvertedFile(fileName);
|
||||
}
|
||||
model.addAttribute("fileName", fileName);
|
||||
model.addAttribute("fileTree", fileTree);
|
||||
return COMPRESS_FILE_PREVIEW_PAGE;
|
||||
model.addAttribute("fileName", fileName);
|
||||
model.addAttribute("fileTree", fileTree);
|
||||
return COMPRESS_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
package cn.keking.service.impl;
|
||||
|
||||
import cn.keking.model.FileAttribute;
|
||||
import cn.keking.service.FilePreview;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.ui.Model;
|
||||
|
||||
/**
|
||||
* @author : kl
|
||||
* create : 2020-12-27 2:50 下午
|
||||
* flv文件预览处理实现
|
||||
**/
|
||||
@Service
|
||||
public class FlvFilePreviewImpl implements FilePreview {
|
||||
|
||||
private final MediaFilePreviewImpl mediaFilePreview;
|
||||
|
||||
public FlvFilePreviewImpl(MediaFilePreviewImpl mediaFilePreview) {
|
||||
this.mediaFilePreview = mediaFilePreview;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String filePreviewHandle(String url, Model model, FileAttribute fileAttribute) {
|
||||
mediaFilePreview.filePreviewHandle(url,model,fileAttribute);
|
||||
return FLV_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package cn.keking.service.impl;
|
||||
|
||||
import cn.keking.config.ConfigConstants;
|
||||
import cn.keking.model.FileAttribute;
|
||||
import cn.keking.model.ReturnResponse;
|
||||
import cn.keking.service.FileHandlerService;
|
||||
import cn.keking.service.FilePreview;
|
||||
import cn.keking.utils.DownloadUtils;
|
||||
import cn.keking.utils.KkFileUtils;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.util.HtmlUtils;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
|
||||
/**
|
||||
* @author kl (http://kailing.pub)
|
||||
* @since 2025/01/11
|
||||
* JSON 文件预览处理实现
|
||||
*/
|
||||
@Service
|
||||
public class JsonFilePreviewImpl implements FilePreview {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(JsonFilePreviewImpl.class);
|
||||
|
||||
private final FileHandlerService fileHandlerService;
|
||||
private final OtherFilePreviewImpl otherFilePreview;
|
||||
|
||||
public JsonFilePreviewImpl(FileHandlerService fileHandlerService, OtherFilePreviewImpl otherFilePreview) {
|
||||
this.fileHandlerService = fileHandlerService;
|
||||
this.otherFilePreview = otherFilePreview;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String filePreviewHandle(String url, Model model, FileAttribute fileAttribute) {
|
||||
String fileName = fileAttribute.getName();
|
||||
boolean forceUpdatedCache = fileAttribute.forceUpdatedCache();
|
||||
String filePath = fileAttribute.getOriginFilePath();
|
||||
|
||||
if (forceUpdatedCache || !fileHandlerService.listConvertedFiles().containsKey(fileName) || !ConfigConstants.isCacheEnabled()) {
|
||||
ReturnResponse<String> response = DownloadUtils.downLoad(fileAttribute, fileName);
|
||||
if (response.isFailure()) {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, response.getMsg());
|
||||
}
|
||||
filePath = response.getContent();
|
||||
if (ConfigConstants.isCacheEnabled()) {
|
||||
fileHandlerService.addConvertedFile(fileName, filePath);
|
||||
}
|
||||
try {
|
||||
String fileData = readJsonFile(filePath, fileName);
|
||||
String escapedData = HtmlUtils.htmlEscape(fileData);
|
||||
String base64Data = Base64.encodeBase64String(escapedData.getBytes(StandardCharsets.UTF_8));
|
||||
model.addAttribute("textData", base64Data);
|
||||
} catch (IOException e) {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, e.getLocalizedMessage());
|
||||
}
|
||||
return JSON_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
|
||||
String fileData = null;
|
||||
try {
|
||||
fileData = HtmlUtils.htmlEscape(readJsonFile(filePath, fileName));
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("读取JSON文件失败: {}", filePath, e);
|
||||
}
|
||||
String base64Data = Base64.encodeBase64String(fileData.getBytes(StandardCharsets.UTF_8));
|
||||
model.addAttribute("textData", base64Data);
|
||||
return JSON_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取 JSON 文件,强制使用 UTF-8 编码
|
||||
* JSON 标准规定必须使用 UTF-8 编码
|
||||
*/
|
||||
private String readJsonFile(String filePath, String fileName) throws IOException {
|
||||
File file = new File(filePath);
|
||||
if (KkFileUtils.isIllegalFileName(fileName)) {
|
||||
return null;
|
||||
}
|
||||
if (!file.exists() || file.length() == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// JSON 标准规定使用 UTF-8 编码,不依赖自动检测
|
||||
byte[] bytes = Files.readAllBytes(Paths.get(filePath));
|
||||
return new String(bytes, StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
@@ -4,17 +4,19 @@ import cn.keking.config.ConfigConstants;
|
||||
import cn.keking.model.FileAttribute;
|
||||
import cn.keking.model.FileType;
|
||||
import cn.keking.model.ReturnResponse;
|
||||
import cn.keking.service.FilePreview;
|
||||
import cn.keking.utils.ConfigUtils;
|
||||
import cn.keking.utils.DownloadUtils;
|
||||
import cn.keking.service.FileHandlerService;
|
||||
import cn.keking.web.filter.BaseUrlFilter;
|
||||
import cn.keking.service.FilePreview;
|
||||
import cn.keking.utils.DownloadUtils;
|
||||
import org.bytedeco.ffmpeg.global.avcodec;
|
||||
import org.bytedeco.javacv.FFmpegFrameGrabber;
|
||||
import org.bytedeco.javacv.FFmpegFrameRecorder;
|
||||
import org.bytedeco.javacv.Frame;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
@@ -26,10 +28,10 @@ import java.io.File;
|
||||
@Service
|
||||
public class MediaFilePreviewImpl implements FilePreview {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(MediaFilePreviewImpl.class);
|
||||
private final FileHandlerService fileHandlerService;
|
||||
private final OtherFilePreviewImpl otherFilePreview;
|
||||
|
||||
private static Object LOCK=new Object();
|
||||
private static final String mp4 = "mp4";
|
||||
|
||||
public MediaFilePreviewImpl(FileHandlerService fileHandlerService, OtherFilePreviewImpl otherFilePreview) {
|
||||
this.fileHandlerService = fileHandlerService;
|
||||
@@ -38,129 +40,128 @@ public class MediaFilePreviewImpl implements FilePreview {
|
||||
|
||||
@Override
|
||||
public String filePreviewHandle(String url, Model model, FileAttribute fileAttribute) {
|
||||
// 不是http开头,浏览器不能直接访问,需下载到本地
|
||||
if (url != null && !url.toLowerCase().startsWith("http")) {
|
||||
ReturnResponse<String> response = DownloadUtils.downLoad(fileAttribute, fileAttribute.getName());
|
||||
if (response.isFailure()) {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, response.getMsg());
|
||||
} else {
|
||||
url=BaseUrlFilter.getBaseUrl() + fileHandlerService.getRelativePath(response.getContent());
|
||||
fileAttribute.setUrl(url);
|
||||
String fileName = fileAttribute.getName();
|
||||
String suffix = fileAttribute.getSuffix();
|
||||
String cacheName = fileAttribute.getCacheName();
|
||||
String outFilePath = fileAttribute.getOutFilePath();
|
||||
boolean forceUpdatedCache = fileAttribute.forceUpdatedCache();
|
||||
FileType type = fileAttribute.getType();
|
||||
String[] mediaTypesConvert = FileType.MEDIA_CONVERT_TYPES; //获取支持的转换格式
|
||||
boolean mediaTypes = false;
|
||||
for (String temp : mediaTypesConvert) {
|
||||
if (suffix.equals(temp)) {
|
||||
mediaTypes = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(checkNeedConvert(fileAttribute.getSuffix())){
|
||||
url=convertUrl(fileAttribute);
|
||||
}else{
|
||||
//正常media类型
|
||||
String[] medias = ConfigConstants.getMedia();
|
||||
for(String media:medias){
|
||||
if(media.equals(fileAttribute.getSuffix())){
|
||||
model.addAttribute("mediaUrl", url);
|
||||
return MEDIA_FILE_PREVIEW_PAGE;
|
||||
if (!url.toLowerCase().startsWith("http") || checkNeedConvert(mediaTypes)) { //不是http协议的 // 开启转换方式并是支持转换格式的
|
||||
if (forceUpdatedCache || !fileHandlerService.listConvertedFiles().containsKey(cacheName) || !ConfigConstants.isCacheEnabled()) { //查询是否开启缓存
|
||||
ReturnResponse<String> response = DownloadUtils.downLoad(fileAttribute, fileName);
|
||||
if (response.isFailure()) {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, response.getMsg());
|
||||
}
|
||||
}
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, "暂不支持");
|
||||
}
|
||||
model.addAttribute("mediaUrl", url);
|
||||
return MEDIA_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查视频文件处理逻辑
|
||||
* 返回处理过后的url
|
||||
* @return url
|
||||
*/
|
||||
private String convertUrl(FileAttribute fileAttribute) {
|
||||
String url = fileAttribute.getUrl();
|
||||
if(fileHandlerService.listConvertedMedias().containsKey(url)){
|
||||
url= fileHandlerService.getConvertedMedias(url);
|
||||
}else{
|
||||
if(!fileHandlerService.listConvertedMedias().containsKey(url)){
|
||||
synchronized(LOCK){
|
||||
if(!fileHandlerService.listConvertedMedias().containsKey(url)){
|
||||
String convertedUrl=convertToMp4(fileAttribute);
|
||||
//加入缓存
|
||||
fileHandlerService.addConvertedMedias(url,convertedUrl);
|
||||
url=convertedUrl;
|
||||
String filePath = response.getContent();
|
||||
String convertedUrl = null;
|
||||
try {
|
||||
if (mediaTypes) {
|
||||
convertedUrl = convertToMp4(filePath, outFilePath, fileAttribute);
|
||||
} else {
|
||||
convertedUrl = outFilePath; //其他协议的 不需要转换方式的文件 直接输出
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to convert media file: {}", filePath, e);
|
||||
}
|
||||
if (convertedUrl == null) {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, "视频转换异常,请联系管理员");
|
||||
}
|
||||
if (ConfigConstants.isCacheEnabled()) {
|
||||
// 加入缓存
|
||||
fileHandlerService.addConvertedFile(cacheName, fileHandlerService.getRelativePath(outFilePath));
|
||||
}
|
||||
model.addAttribute("mediaUrl", fileHandlerService.getRelativePath(outFilePath));
|
||||
} else {
|
||||
model.addAttribute("mediaUrl", fileHandlerService.listConvertedFiles().get(cacheName));
|
||||
}
|
||||
return MEDIA_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
return url;
|
||||
if (type.equals(FileType.MEDIA)) { // 支持输出 只限默认格式
|
||||
model.addAttribute("mediaUrl", url);
|
||||
return MEDIA_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, "系统还不支持该格式文件的在线预览");
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查视频文件转换是否已开启,以及当前文件是否需要转换
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private boolean checkNeedConvert(String suffix) {
|
||||
private boolean checkNeedConvert(boolean mediaTypes) {
|
||||
//1.检查开关是否开启
|
||||
if("false".equals(ConfigConstants.getMediaConvertDisable())){
|
||||
return false;
|
||||
}
|
||||
//2.检查当前文件是否需要转换
|
||||
String[] mediaTypesConvert = FileType.MEDIA_TYPES_CONVERT;
|
||||
String type = suffix;
|
||||
for(String temp : mediaTypesConvert){
|
||||
if(type.equals(temp)){
|
||||
return true;
|
||||
}
|
||||
if ("true".equals(ConfigConstants.getMediaConvertDisable())) {
|
||||
return mediaTypes;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将浏览器不兼容视频格式转换成MP4
|
||||
* @param fileAttribute
|
||||
* @return
|
||||
*/
|
||||
private static String convertToMp4(FileAttribute fileAttribute) {
|
||||
|
||||
//说明:这里做临时处理,取上传文件的目录
|
||||
String homePath = ConfigUtils.getHomePath();
|
||||
String filePath = homePath+File.separator+"file"+File.separator+"demo"+File.separator+fileAttribute.getName();
|
||||
String convertFileName=fileAttribute.getUrl().replace(fileAttribute.getSuffix(),"mp4");
|
||||
|
||||
File file=new File(filePath);
|
||||
FFmpegFrameGrabber frameGrabber = new FFmpegFrameGrabber(file);
|
||||
String fileName = null;
|
||||
Frame captured_frame = null;
|
||||
private static String convertToMp4(String filePath, String outFilePath, FileAttribute fileAttribute) throws Exception {
|
||||
FFmpegFrameGrabber frameGrabber = FFmpegFrameGrabber.createDefault(filePath);
|
||||
Frame captured_frame;
|
||||
FFmpegFrameRecorder recorder = null;
|
||||
try {
|
||||
fileName = file.getAbsolutePath().replace(fileAttribute.getSuffix(),"mp4");
|
||||
File desFile=new File(fileName);
|
||||
//判断一下防止穿透缓存
|
||||
if(desFile.exists()){
|
||||
return fileName;
|
||||
File desFile = new File(outFilePath);
|
||||
//判断一下防止重复转换
|
||||
if (desFile.exists()) {
|
||||
return outFilePath;
|
||||
}
|
||||
|
||||
frameGrabber.start();
|
||||
recorder = new FFmpegFrameRecorder(fileName, frameGrabber.getImageWidth(), frameGrabber.getImageHeight(), frameGrabber.getAudioChannels());
|
||||
recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264); //avcodec.AV_CODEC_ID_H264 //AV_CODEC_ID_MPEG4
|
||||
recorder.setFormat("mp4");
|
||||
recorder.setFrameRate(frameGrabber.getFrameRate());
|
||||
//recorder.setSampleFormat(frameGrabber.getSampleFormat()); //
|
||||
recorder.setSampleRate(frameGrabber.getSampleRate());
|
||||
|
||||
recorder.setAudioChannels(frameGrabber.getAudioChannels());
|
||||
recorder.setFrameRate(frameGrabber.getFrameRate());
|
||||
recorder.start();
|
||||
while ((captured_frame = frameGrabber.grabFrame()) != null) {
|
||||
try {
|
||||
recorder.setTimestamp(frameGrabber.getTimestamp());
|
||||
recorder.record(captured_frame);
|
||||
} catch (Exception e) {
|
||||
if (fileAttribute.isCompressFile()) { //判断 是压缩包的创建新的目录
|
||||
int index = outFilePath.lastIndexOf("/"); //截取最后一个斜杠的前面的内容
|
||||
String folder = outFilePath.substring(0, index);
|
||||
File path = new File(folder);
|
||||
//目录不存在 创建新的目录
|
||||
if (!path.exists()) {
|
||||
path.mkdirs();
|
||||
}
|
||||
}
|
||||
recorder.stop();
|
||||
recorder.release();
|
||||
frameGrabber.stop();
|
||||
frameGrabber.start();
|
||||
recorder = new FFmpegFrameRecorder(outFilePath, frameGrabber.getImageWidth(), frameGrabber.getImageHeight(), frameGrabber.getAudioChannels());
|
||||
// recorder.setImageHeight(640);
|
||||
// recorder.setImageWidth(480);
|
||||
recorder.setFormat(mp4);
|
||||
recorder.setFrameRate(frameGrabber.getFrameRate());
|
||||
recorder.setSampleRate(frameGrabber.getSampleRate());
|
||||
//视频编码属性配置 H.264 H.265 MPEG
|
||||
recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
|
||||
//设置视频比特率,单位:b
|
||||
recorder.setVideoBitrate(frameGrabber.getVideoBitrate());
|
||||
recorder.setAspectRatio(frameGrabber.getAspectRatio());
|
||||
// 设置音频通用编码格式
|
||||
recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);
|
||||
//设置音频比特率,单位:b (比特率越高,清晰度/音质越好,当然文件也就越大 128000 = 182kb)
|
||||
recorder.setAudioBitrate(frameGrabber.getAudioBitrate());
|
||||
recorder.setAudioOptions(frameGrabber.getAudioOptions());
|
||||
recorder.setAudioChannels(frameGrabber.getAudioChannels());
|
||||
recorder.start();
|
||||
while (true) {
|
||||
captured_frame = frameGrabber.grabFrame();
|
||||
if (captured_frame == null) {
|
||||
System.out.println("转码完成:" + filePath);
|
||||
break;
|
||||
}
|
||||
recorder.record(captured_frame);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
logger.error("Failed to convert video file to mp4: {}", filePath, e);
|
||||
return null;
|
||||
} finally {
|
||||
if (recorder != null) { //关闭
|
||||
recorder.stop();
|
||||
recorder.close();
|
||||
}
|
||||
frameGrabber.stop();
|
||||
frameGrabber.close();
|
||||
}
|
||||
//是否删除源文件
|
||||
//file.delete();
|
||||
return convertFileName;
|
||||
return outFilePath;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import cn.keking.service.OfficeToPdfService;
|
||||
import cn.keking.utils.DownloadUtils;
|
||||
import cn.keking.utils.KkFileUtils;
|
||||
import cn.keking.utils.OfficeUtils;
|
||||
import cn.keking.utils.WebUtils;
|
||||
import cn.keking.web.filter.BaseUrlFilter;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.apache.poi.EncryptedDocumentException;
|
||||
@@ -18,7 +19,6 @@ import org.springframework.ui.Model;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -30,7 +30,6 @@ public class OfficeFilePreviewImpl implements FilePreview {
|
||||
|
||||
public static final String OFFICE_PREVIEW_TYPE_IMAGE = "image";
|
||||
public static final String OFFICE_PREVIEW_TYPE_ALL_IMAGES = "allImages";
|
||||
private static final String FILE_DIR = ConfigConstants.getFileDir();
|
||||
private static final String OFFICE_PASSWORD_MSG = "password";
|
||||
|
||||
private final FileHandlerService fileHandlerService;
|
||||
@@ -47,58 +46,35 @@ public class OfficeFilePreviewImpl implements FilePreview {
|
||||
public String filePreviewHandle(String url, Model model, FileAttribute fileAttribute) {
|
||||
// 预览Type,参数传了就取参数的,没传取系统默认
|
||||
String officePreviewType = fileAttribute.getOfficePreviewType();
|
||||
boolean userToken = fileAttribute.getUsePasswordCache();
|
||||
String baseUrl = BaseUrlFilter.getBaseUrl();
|
||||
String suffix = fileAttribute.getSuffix();
|
||||
String fileName = fileAttribute.getName();
|
||||
String filePassword = fileAttribute.getFilePassword();
|
||||
boolean forceUpdatedCache=fileAttribute.forceUpdatedCache();
|
||||
String userToken = fileAttribute.getUserToken();
|
||||
boolean isHtml = suffix.equalsIgnoreCase("xls") || suffix.equalsIgnoreCase("xlsx") || suffix.equalsIgnoreCase("csv") || suffix.equalsIgnoreCase("xlsm") || suffix.equalsIgnoreCase("xlt") || suffix.equalsIgnoreCase("xltm") || suffix.equalsIgnoreCase("et") || suffix.equalsIgnoreCase("ett") || suffix.equalsIgnoreCase("xlam");
|
||||
String pdfName = fileName.substring(0, fileName.lastIndexOf(".") ) + suffix +"." +(isHtml ? "html" : "pdf"); //生成文件添加类型后缀 防止同名文件
|
||||
String cacheFileName = userToken == null ? pdfName : userToken + "_" + pdfName;
|
||||
String outFilePath = FILE_DIR + cacheFileName;
|
||||
String suffix = fileAttribute.getSuffix(); //获取文件后缀
|
||||
String fileName = fileAttribute.getName(); //获取文件原始名称
|
||||
String filePassword = fileAttribute.getFilePassword(); //获取密码
|
||||
boolean forceUpdatedCache=fileAttribute.forceUpdatedCache(); //是否启用强制更新命令
|
||||
boolean isHtmlView = fileAttribute.isHtmlView(); //xlsx 转换成html
|
||||
String cacheName = fileAttribute.getCacheName(); //转换后的文件名
|
||||
String outFilePath = fileAttribute.getOutFilePath(); //转换后生成文件的路径
|
||||
if (!officePreviewType.equalsIgnoreCase("html")) {
|
||||
if (ConfigConstants.getOfficeTypeWeb() .equalsIgnoreCase("web")) {
|
||||
if (suffix.equalsIgnoreCase("xlsx")) {
|
||||
model.addAttribute("pdfUrl", url);
|
||||
model.addAttribute("pdfUrl", KkFileUtils.htmlEscape(url)); //特殊符号处理
|
||||
return XLSX_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
if (suffix.equalsIgnoreCase("csv")) {
|
||||
model.addAttribute("csvUrl", KkFileUtils.htmlEscape(url));
|
||||
return CSV_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (forceUpdatedCache|| !fileHandlerService.listConvertedFiles().containsKey(pdfName) || !ConfigConstants.isCacheEnabled()) {
|
||||
if (forceUpdatedCache|| !fileHandlerService.listConvertedFiles().containsKey(cacheName) || !ConfigConstants.isCacheEnabled()) {
|
||||
// 下载远程文件到本地,如果文件在本地已存在不会重复下载
|
||||
ReturnResponse<String> response = DownloadUtils.downLoad(fileAttribute, fileName);
|
||||
if (response.isFailure()) {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, response.getMsg());
|
||||
}
|
||||
String filePath = response.getContent();
|
||||
/*
|
||||
* 1. 缓存判断-如果文件已经进行转换过,就直接返回,否则执行转换
|
||||
* 2. 缓存判断-加密文件基于userToken进行缓存,如果没有就不缓存
|
||||
*/
|
||||
boolean isCached = false;
|
||||
boolean isUseCached = false;
|
||||
boolean isPwdProtectedOffice = false;
|
||||
if (ConfigConstants.isCacheEnabled()) {
|
||||
// 全局开启缓存
|
||||
isUseCached = true;
|
||||
if (!forceUpdatedCache && fileHandlerService.listConvertedFiles().containsKey(cacheFileName)) {
|
||||
// 存在缓存
|
||||
isCached = true;
|
||||
}
|
||||
if (OfficeUtils.isPwdProtected(filePath)) {
|
||||
isPwdProtectedOffice = true;
|
||||
if (!StringUtils.hasLength(userToken)) {
|
||||
// 不缓存没有userToken的加密文件
|
||||
isUseCached = false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
isPwdProtectedOffice = OfficeUtils.isPwdProtected(filePath);
|
||||
}
|
||||
|
||||
if (!isCached) {
|
||||
// 没有缓存执行转换逻辑
|
||||
String filePath = response.getContent();
|
||||
boolean isPwdProtectedOffice = OfficeUtils.isPwdProtected(filePath); // 判断是否加密文件
|
||||
if (isPwdProtectedOffice && !StringUtils.hasLength(filePassword)) {
|
||||
// 加密文件需要密码
|
||||
model.addAttribute("needFilePassword", true);
|
||||
@@ -114,40 +90,37 @@ public class OfficeFilePreviewImpl implements FilePreview {
|
||||
model.addAttribute("filePasswordError", true);
|
||||
return EXEL_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, "抱歉,该文件版本不兼容,文件版本错误。");
|
||||
}
|
||||
|
||||
if (isHtml) {
|
||||
if (isHtmlView) {
|
||||
// 对转换后的文件进行操作(改变编码方式)
|
||||
fileHandlerService.doActionConvertedFile(outFilePath);
|
||||
}
|
||||
//是否保留OFFICE源文件
|
||||
if (ConfigConstants.getDeleteSourceFile()) {
|
||||
if (!fileAttribute.isCompressFile() && ConfigConstants.getDeleteSourceFile()) {
|
||||
KkFileUtils.deleteFileByPath(filePath);
|
||||
}
|
||||
if (isUseCached) {
|
||||
if (userToken || !isPwdProtectedOffice) {
|
||||
// 加入缓存
|
||||
fileHandlerService.addConvertedFile(cacheFileName, fileHandlerService.getRelativePath(outFilePath));
|
||||
fileHandlerService.addConvertedFile(cacheName, fileHandlerService.getRelativePath(outFilePath));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if (!isHtmlView && baseUrl != null && (OFFICE_PREVIEW_TYPE_IMAGE.equals(officePreviewType) || OFFICE_PREVIEW_TYPE_ALL_IMAGES.equals(officePreviewType))) {
|
||||
return getPreviewType(model, fileAttribute, officePreviewType, cacheName, outFilePath, fileHandlerService, OFFICE_PREVIEW_TYPE_IMAGE, otherFilePreview);
|
||||
}
|
||||
if (!isHtml && baseUrl != null && (OFFICE_PREVIEW_TYPE_IMAGE.equals(officePreviewType) || OFFICE_PREVIEW_TYPE_ALL_IMAGES.equals(officePreviewType))) {
|
||||
return getPreviewType(model, fileAttribute, officePreviewType, baseUrl, cacheFileName, outFilePath, fileHandlerService, OFFICE_PREVIEW_TYPE_IMAGE, otherFilePreview);
|
||||
}
|
||||
cacheFileName = URLEncoder.encode(cacheFileName).replaceAll("\\+", "%20");
|
||||
model.addAttribute("pdfUrl", cacheFileName);
|
||||
return isHtml ? EXEL_FILE_PREVIEW_PAGE : PDF_FILE_PREVIEW_PAGE;
|
||||
model.addAttribute("pdfUrl", WebUtils.encodeFileName(cacheName)); //输出转义文件名 方便url识别
|
||||
return isHtmlView ? EXEL_FILE_PREVIEW_PAGE : PDF_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
|
||||
static String getPreviewType(Model model, FileAttribute fileAttribute, String officePreviewType, String baseUrl, String pdfName, String outFilePath, FileHandlerService fileHandlerService, String officePreviewTypeImage, OtherFilePreviewImpl otherFilePreview) {
|
||||
static String getPreviewType(Model model, FileAttribute fileAttribute, String officePreviewType, String pdfName, String outFilePath, FileHandlerService fileHandlerService, String officePreviewTypeImage, OtherFilePreviewImpl otherFilePreview) {
|
||||
String suffix = fileAttribute.getSuffix();
|
||||
boolean isPPT = suffix.equalsIgnoreCase("ppt") || suffix.equalsIgnoreCase("pptx");
|
||||
List<String> imageUrls = null;
|
||||
try {
|
||||
imageUrls = fileHandlerService.pdf2jpg(outFilePath, pdfName, fileAttribute);
|
||||
imageUrls = fileHandlerService.pdf2jpg(outFilePath,outFilePath, pdfName, fileAttribute);
|
||||
} catch (Exception e) {
|
||||
Throwable[] throwableArray = ExceptionUtils.getThrowables(e);
|
||||
for (Throwable throwable : throwableArray) {
|
||||
|
||||
@@ -3,17 +3,16 @@ package cn.keking.service.impl;
|
||||
import cn.keking.config.ConfigConstants;
|
||||
import cn.keking.model.FileAttribute;
|
||||
import cn.keking.model.ReturnResponse;
|
||||
import cn.keking.service.FileHandlerService;
|
||||
import cn.keking.service.FilePreview;
|
||||
import cn.keking.utils.DownloadUtils;
|
||||
import cn.keking.service.FileHandlerService;
|
||||
import cn.keking.web.filter.BaseUrlFilter;
|
||||
import cn.keking.utils.WebUtils;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.apache.poi.EncryptedDocumentException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.ui.Model;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -25,38 +24,34 @@ public class PdfFilePreviewImpl implements FilePreview {
|
||||
|
||||
private final FileHandlerService fileHandlerService;
|
||||
private final OtherFilePreviewImpl otherFilePreview;
|
||||
private static final String FILE_DIR = ConfigConstants.getFileDir();
|
||||
private static final String PDF_PASSWORD_MSG = "password";
|
||||
|
||||
public PdfFilePreviewImpl(FileHandlerService fileHandlerService, OtherFilePreviewImpl otherFilePreview) {
|
||||
this.fileHandlerService = fileHandlerService;
|
||||
this.otherFilePreview = otherFilePreview;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String filePreviewHandle(String url, Model model, FileAttribute fileAttribute) {
|
||||
String fileName = fileAttribute.getName();
|
||||
String officePreviewType = fileAttribute.getOfficePreviewType();
|
||||
String baseUrl = BaseUrlFilter.getBaseUrl();
|
||||
boolean forceUpdatedCache=fileAttribute.forceUpdatedCache();
|
||||
String pdfName = fileName.substring(0, fileName.lastIndexOf(".") + 1) + "pdf";
|
||||
String outFilePath = FILE_DIR + pdfName;
|
||||
String pdfName = fileAttribute.getName(); //获取原始文件名
|
||||
String officePreviewType = fileAttribute.getOfficePreviewType(); //转换类型
|
||||
boolean forceUpdatedCache=fileAttribute.forceUpdatedCache(); //是否启用强制更新命令
|
||||
String outFilePath = fileAttribute.getOutFilePath(); //生成的文件路径
|
||||
String originFilePath = fileAttribute.getOriginFilePath(); //原始文件路径
|
||||
if (OfficeFilePreviewImpl.OFFICE_PREVIEW_TYPE_IMAGE.equals(officePreviewType) || OfficeFilePreviewImpl.OFFICE_PREVIEW_TYPE_ALL_IMAGES.equals(officePreviewType)) {
|
||||
//当文件不存在时,就去下载
|
||||
if (forceUpdatedCache || !fileHandlerService.listConvertedFiles().containsKey(pdfName) || !ConfigConstants.isCacheEnabled()) {
|
||||
ReturnResponse<String> response = DownloadUtils.downLoad(fileAttribute, fileName);
|
||||
ReturnResponse<String> response = DownloadUtils.downLoad(fileAttribute, pdfName);
|
||||
if (response.isFailure()) {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, response.getMsg());
|
||||
}
|
||||
outFilePath = response.getContent();
|
||||
originFilePath = response.getContent();
|
||||
if (ConfigConstants.isCacheEnabled()) {
|
||||
// 加入缓存
|
||||
fileHandlerService.addConvertedFile(pdfName, fileHandlerService.getRelativePath(outFilePath));
|
||||
fileHandlerService.addConvertedFile(pdfName, fileHandlerService.getRelativePath(originFilePath));
|
||||
}
|
||||
}
|
||||
List<String> imageUrls;
|
||||
try {
|
||||
imageUrls = fileHandlerService.pdf2jpg(outFilePath, pdfName, fileAttribute);
|
||||
imageUrls = fileHandlerService.pdf2jpg(originFilePath,outFilePath, pdfName, fileAttribute);
|
||||
} catch (Exception e) {
|
||||
Throwable[] throwableArray = ExceptionUtils.getThrowables(e);
|
||||
for (Throwable throwable : throwableArray) {
|
||||
@@ -93,8 +88,7 @@ public class PdfFilePreviewImpl implements FilePreview {
|
||||
fileHandlerService.addConvertedFile(pdfName, fileHandlerService.getRelativePath(outFilePath));
|
||||
}
|
||||
} else {
|
||||
pdfName = URLEncoder.encode(pdfName).replaceAll("\\+", "%20");
|
||||
model.addAttribute("pdfUrl", pdfName);
|
||||
model.addAttribute("pdfUrl", WebUtils.encodeFileName(pdfName));
|
||||
}
|
||||
} else {
|
||||
model.addAttribute("pdfUrl", url);
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
package cn.keking.service.impl;
|
||||
|
||||
import cn.keking.model.FileAttribute;
|
||||
import cn.keking.model.ReturnResponse;
|
||||
import cn.keking.service.FileHandlerService;
|
||||
import cn.keking.service.FilePreview;
|
||||
import cn.keking.utils.DownloadUtils;
|
||||
import cn.keking.utils.KkFileUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.ui.Model;
|
||||
@@ -21,12 +18,10 @@ import java.util.List;
|
||||
public class PictureFilePreviewImpl extends CommonPreviewImpl {
|
||||
|
||||
private final FileHandlerService fileHandlerService;
|
||||
private final OtherFilePreviewImpl otherFilePreview;
|
||||
|
||||
public PictureFilePreviewImpl(FileHandlerService fileHandlerService, OtherFilePreviewImpl otherFilePreview) {
|
||||
super(fileHandlerService, otherFilePreview);
|
||||
this.fileHandlerService = fileHandlerService;
|
||||
this.otherFilePreview = otherFilePreview;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -34,8 +29,8 @@ public class PictureFilePreviewImpl extends CommonPreviewImpl {
|
||||
url= KkFileUtils.htmlEscape(url);
|
||||
List<String> imgUrls = new ArrayList<>();
|
||||
imgUrls.add(url);
|
||||
String fileKey = fileAttribute.getFileKey();
|
||||
List<String> zipImgUrls = fileHandlerService.getImgCache(fileKey);
|
||||
String compressFileKey = fileAttribute.getCompressFileKey();
|
||||
List<String> zipImgUrls = fileHandlerService.getImgCache(compressFileKey);
|
||||
if (!CollectionUtils.isEmpty(zipImgUrls)) {
|
||||
imgUrls.addAll(zipImgUrls);
|
||||
}
|
||||
|
||||
@@ -9,11 +9,17 @@ import cn.keking.utils.DownloadUtils;
|
||||
import cn.keking.utils.EncodingDetects;
|
||||
import cn.keking.utils.KkFileUtils;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.util.HtmlUtils;
|
||||
|
||||
import java.io.*;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
@@ -23,19 +29,21 @@ import java.nio.charset.StandardCharsets;
|
||||
@Service
|
||||
public class SimTextFilePreviewImpl implements FilePreview {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(SimTextFilePreviewImpl.class);
|
||||
|
||||
private final FileHandlerService fileHandlerService;
|
||||
private final OtherFilePreviewImpl otherFilePreview;
|
||||
|
||||
public SimTextFilePreviewImpl(FileHandlerService fileHandlerService,OtherFilePreviewImpl otherFilePreview) {
|
||||
public SimTextFilePreviewImpl(FileHandlerService fileHandlerService, OtherFilePreviewImpl otherFilePreview) {
|
||||
this.fileHandlerService = fileHandlerService;
|
||||
this.otherFilePreview = otherFilePreview;
|
||||
}
|
||||
private static final String FILE_DIR = ConfigConstants.getFileDir();
|
||||
|
||||
@Override
|
||||
public String filePreviewHandle(String url, Model model, FileAttribute fileAttribute) {
|
||||
String fileName = fileAttribute.getName();
|
||||
boolean forceUpdatedCache=fileAttribute.forceUpdatedCache();
|
||||
String filePath = FILE_DIR + fileName;
|
||||
String filePath = fileAttribute.getOriginFilePath();
|
||||
if (forceUpdatedCache || !fileHandlerService.listConvertedFiles().containsKey(fileName) || !ConfigConstants.isCacheEnabled()) {
|
||||
ReturnResponse<String> response = DownloadUtils.downLoad(fileAttribute, fileName);
|
||||
if (response.isFailure()) {
|
||||
@@ -47,7 +55,7 @@ public class SimTextFilePreviewImpl implements FilePreview {
|
||||
}
|
||||
try {
|
||||
String fileData = HtmlUtils.htmlEscape(textData(filePath,fileName));
|
||||
model.addAttribute("textData", Base64.encodeBase64String(fileData.getBytes()));
|
||||
model.addAttribute("textData", Base64.encodeBase64String(fileData.getBytes(StandardCharsets.UTF_8)));
|
||||
} catch (IOException e) {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, e.getLocalizedMessage());
|
||||
}
|
||||
@@ -57,9 +65,9 @@ public class SimTextFilePreviewImpl implements FilePreview {
|
||||
try {
|
||||
fileData = HtmlUtils.htmlEscape(textData(filePath,fileName));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
LOGGER.error("读取文本文件失败: {}", filePath, e);
|
||||
}
|
||||
model.addAttribute("textData", Base64.encodeBase64String(fileData.getBytes()));
|
||||
model.addAttribute("textData", Base64.encodeBase64String(fileData.getBytes(StandardCharsets.UTF_8)));
|
||||
return TXT_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,13 +8,10 @@ import cn.keking.service.FilePreview;
|
||||
import cn.keking.utils.ConvertPicUtil;
|
||||
import cn.keking.utils.DownloadUtils;
|
||||
import cn.keking.utils.KkFileUtils;
|
||||
import cn.keking.web.filter.BaseUrlFilter;
|
||||
import cn.keking.utils.WebUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -32,75 +29,99 @@ public class TiffFilePreviewImpl implements FilePreview {
|
||||
this.fileHandlerService = fileHandlerService;
|
||||
this.otherFilePreview = otherFilePreview;
|
||||
}
|
||||
private final String fileDir = ConfigConstants.getFileDir();
|
||||
@Override
|
||||
public String filePreviewHandle(String url, Model model, FileAttribute fileAttribute) {
|
||||
String fileName = fileAttribute.getName();
|
||||
String baseUrl = BaseUrlFilter.getBaseUrl();
|
||||
String tifPreviewType = ConfigConstants.getTifPreviewType();
|
||||
String tifOnLinePreviewType = fileAttribute.getTifPreviewType();
|
||||
String suffix = fileAttribute.getSuffix();
|
||||
String cacheName = fileAttribute.getCacheName();
|
||||
String outFilePath = fileAttribute.getOutFilePath();
|
||||
boolean forceUpdatedCache=fileAttribute.forceUpdatedCache();
|
||||
if (StringUtils.hasText(tifOnLinePreviewType)) {
|
||||
tifPreviewType = tifOnLinePreviewType;
|
||||
}
|
||||
if ("tif".equalsIgnoreCase(tifPreviewType)) {
|
||||
model.addAttribute("currentUrl", url);
|
||||
return TIFF_FILE_PREVIEW_PAGE;
|
||||
} else if ("jpg".equalsIgnoreCase(tifPreviewType) || "pdf".equalsIgnoreCase(tifPreviewType)) {
|
||||
String pdfName = fileName.substring(0, fileName.lastIndexOf(".")) + suffix +"." + "pdf" ; //生成文件添加类型后缀 防止同名文件
|
||||
String jpgName = fileName.substring(0, fileName.lastIndexOf(".")) + suffix; //生成文件添加类型后缀 防止同名文件
|
||||
String strLocalTif = fileDir + fileName;
|
||||
String outFilePath = fileDir + pdfName;
|
||||
if ("pdf".equalsIgnoreCase(tifPreviewType)) {
|
||||
//当文件不存在时,就去下载
|
||||
if (forceUpdatedCache || !fileHandlerService.listConvertedFiles().containsKey(pdfName) || !ConfigConstants.isCacheEnabled()) {
|
||||
ReturnResponse<String> response = DownloadUtils.downLoad(fileAttribute, fileName);
|
||||
if (response.isFailure()) {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, response.getMsg());
|
||||
}
|
||||
String filePath = response.getContent();
|
||||
if (ConvertPicUtil.convertJpg2Pdf(filePath, outFilePath)) {
|
||||
//是否保留TIFF源文件
|
||||
if (ConfigConstants.getDeleteSourceFile()) {
|
||||
KkFileUtils.deleteFileByPath(filePath);
|
||||
if ("jpg".equalsIgnoreCase(tifPreviewType) || "pdf".equalsIgnoreCase(tifPreviewType)) {
|
||||
if (forceUpdatedCache || !fileHandlerService.listConvertedFiles().containsKey(cacheName) || !ConfigConstants.isCacheEnabled()) {
|
||||
ReturnResponse<String> response = DownloadUtils.downLoad(fileAttribute, fileName);
|
||||
if (response.isFailure()) {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, response.getMsg());
|
||||
}
|
||||
String filePath = response.getContent();
|
||||
if ("pdf".equalsIgnoreCase(tifPreviewType)) {
|
||||
try {
|
||||
ConvertPicUtil.convertJpg2Pdf(filePath, outFilePath);
|
||||
} catch (Exception e) {
|
||||
if (e.getMessage().contains("Bad endianness tag (not 0x4949 or 0x4d4d)") ) {
|
||||
model.addAttribute("imgUrls", url);
|
||||
model.addAttribute("currentUrl", url);
|
||||
return PICTURE_FILE_PREVIEW_PAGE;
|
||||
}else {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, "TIF转pdf异常,请联系系统管理员!" );
|
||||
}
|
||||
if (ConfigConstants.isCacheEnabled()) {
|
||||
// 加入缓存
|
||||
fileHandlerService.addConvertedFile(pdfName, fileHandlerService.getRelativePath(outFilePath));
|
||||
}
|
||||
model.addAttribute("pdfUrl", pdfName);
|
||||
return PDF_FILE_PREVIEW_PAGE;
|
||||
} else {
|
||||
return NOT_SUPPORTED_FILE_PAGE;
|
||||
}
|
||||
} else {
|
||||
model.addAttribute("pdfUrl", pdfName);
|
||||
//是否保留TIFF源文件
|
||||
if (!fileAttribute.isCompressFile() && ConfigConstants.getDeleteSourceFile()) {
|
||||
// KkFileUtils.deleteFileByPath(filePath);
|
||||
}
|
||||
if (ConfigConstants.isCacheEnabled()) {
|
||||
// 加入缓存
|
||||
fileHandlerService.addConvertedFile(cacheName, fileHandlerService.getRelativePath(outFilePath));
|
||||
}
|
||||
model.addAttribute("pdfUrl", WebUtils.encodeFileName(cacheName));
|
||||
return PDF_FILE_PREVIEW_PAGE;
|
||||
}else {
|
||||
// 将tif转换为jpg,返回转换后的文件路径、文件名的list
|
||||
List<String> listPic2Jpg;
|
||||
try {
|
||||
listPic2Jpg = ConvertPicUtil.convertTif2Jpg(filePath, outFilePath,forceUpdatedCache);
|
||||
} catch (Exception e) {
|
||||
if (e.getMessage().contains("Bad endianness tag (not 0x4949 or 0x4d4d)") ) {
|
||||
model.addAttribute("imgUrls", url);
|
||||
model.addAttribute("currentUrl", url);
|
||||
return PICTURE_FILE_PREVIEW_PAGE;
|
||||
}else {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, "TIF转JPG异常,请联系系统管理员!" );
|
||||
}
|
||||
}
|
||||
//是否保留源文件,转换失败保留源文件,转换成功删除源文件
|
||||
if(!fileAttribute.isCompressFile() && ConfigConstants.getDeleteSourceFile()) {
|
||||
KkFileUtils.deleteFileByPath(filePath);
|
||||
}
|
||||
if (ConfigConstants.isCacheEnabled()) {
|
||||
// 加入缓存
|
||||
fileHandlerService.putImgCache(cacheName, listPic2Jpg);
|
||||
fileHandlerService.addConvertedFile(cacheName, fileHandlerService.getRelativePath(outFilePath));
|
||||
}
|
||||
model.addAttribute("imgUrls", listPic2Jpg);
|
||||
model.addAttribute("currentUrl", listPic2Jpg.get(0));
|
||||
return PICTURE_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
}
|
||||
if ("pdf".equalsIgnoreCase(tifPreviewType)) {
|
||||
model.addAttribute("pdfUrl", WebUtils.encodeFileName(cacheName));
|
||||
return PDF_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
else if ("jpg".equalsIgnoreCase(tifPreviewType)) {
|
||||
model.addAttribute("imgUrls", fileHandlerService.getImgCache(cacheName));
|
||||
model.addAttribute("currentUrl", fileHandlerService.getImgCache(cacheName).get(0));
|
||||
return PICTURE_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
}
|
||||
// 不是http开头,浏览器不能直接访问,需下载到本地
|
||||
if (url != null && !url.toLowerCase().startsWith("http")) {
|
||||
if (forceUpdatedCache || !fileHandlerService.listConvertedFiles().containsKey(fileName) || !ConfigConstants.isCacheEnabled()) {
|
||||
ReturnResponse<String> response = DownloadUtils.downLoad(fileAttribute, fileName);
|
||||
if (response.isFailure()) {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, response.getMsg());
|
||||
}
|
||||
model.addAttribute("currentUrl", fileHandlerService.getRelativePath(response.getContent()));
|
||||
if (ConfigConstants.isCacheEnabled()) {
|
||||
// 加入缓存
|
||||
fileHandlerService.addConvertedFile(fileName, fileHandlerService.getRelativePath(outFilePath));
|
||||
}
|
||||
} else {
|
||||
File fileTiff = new File(strLocalTif);
|
||||
// 如果本地不存在这个tif文件,则下载
|
||||
if (!fileTiff.exists()) {
|
||||
ReturnResponse<String> response = DownloadUtils.downLoad(fileAttribute, fileName);
|
||||
if (response.isFailure()) {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, response.getMsg());
|
||||
}
|
||||
strLocalTif = response.getContent();
|
||||
}
|
||||
// 将tif转换为jpg,返回转换后的文件路径、文件名的list
|
||||
List<String> listPic2Jpg = ConvertPicUtil.convertTif2Jpg(strLocalTif, jpgName);
|
||||
// 将返回页面的图片url的list对象
|
||||
List<String> listImageUrls = new ArrayList<>();
|
||||
// 循环,拼装url的list对象
|
||||
for (String strJpg : listPic2Jpg) {
|
||||
listImageUrls.add(baseUrl + strJpg);
|
||||
}
|
||||
model.addAttribute("imgUrls", listImageUrls);
|
||||
model.addAttribute("currentUrl", listImageUrls.get(0));
|
||||
model.addAttribute("currentUrl", WebUtils.encodeFileName(fileName));
|
||||
}
|
||||
return PICTURE_FILE_PREVIEW_PAGE;
|
||||
return TIFF_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
return NOT_SUPPORTED_FILE_PAGE;
|
||||
model.addAttribute("currentUrl", url);
|
||||
return TIFF_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
74
server/src/main/java/cn/keking/utils/CaptchaUtil.java
Normal file
@@ -0,0 +1,74 @@
|
||||
package cn.keking.utils;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.Random;
|
||||
|
||||
public class CaptchaUtil {
|
||||
|
||||
public static final String CAPTCHA_CODE = "captchaCode";
|
||||
public static final String CAPTCHA_GENERATE_TIME = "captchaTime";
|
||||
|
||||
private static final int WIDTH = 100;// 定义图片的width
|
||||
private static final int HEIGHT = 30;// 定义图片的height
|
||||
private static final int CODE_LENGTH = 4;// 定义图片上显示验证码的个数
|
||||
private static final int FONT_HEIGHT = 28;
|
||||
private static final char[] CODE_SEQUENCE = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'M', 'N', 'P', 'Q', 'R', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
|
||||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k', 'm', 'n', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', '2', '3', '4', '5', '6', '7', '8', '9'};
|
||||
|
||||
/**
|
||||
* 指定验证码、生成验证码图片。
|
||||
* @param captchaCode 验证码
|
||||
* @return 验证码图片
|
||||
*/
|
||||
public static BufferedImage generateCaptchaPic(final String captchaCode) {
|
||||
Assert.notNull(captchaCode, "captchaCode must not be null");
|
||||
// 定义图像buffer
|
||||
BufferedImage buffImg = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
|
||||
Graphics gd = buffImg.getGraphics();
|
||||
Random random = new Random();
|
||||
// 将图像填充为白色
|
||||
gd.setColor(Color.WHITE);
|
||||
gd.fillRect(0, 0, WIDTH, HEIGHT);
|
||||
Font font = new Font("Times New Roman", Font.BOLD, FONT_HEIGHT);
|
||||
gd.setFont(font);
|
||||
// 画边框。
|
||||
gd.setColor(Color.BLACK);
|
||||
gd.drawRect(0, 0, WIDTH - 1, HEIGHT - 1);
|
||||
|
||||
// 随机产生40条干扰线,使图象中的认证码不易被其它程序探测到。
|
||||
gd.setColor(Color.BLACK);
|
||||
for (int i = 0; i < 30; i++) {
|
||||
int x = random.nextInt(WIDTH);
|
||||
int y = random.nextInt(HEIGHT);
|
||||
int xl = random.nextInt(12);
|
||||
int yl = random.nextInt(12);
|
||||
gd.drawLine(x, y, x + xl, y + yl);
|
||||
}
|
||||
// randomCode用于保存随机产生的验证码,以便用户登录后进行验证。
|
||||
int red, green, blue;
|
||||
// 产生随机的颜色分量来构造颜色值,这样输出的每位数字的颜色值都将不同。
|
||||
red = random.nextInt(255);
|
||||
green = random.nextInt(255);
|
||||
blue = random.nextInt(255);
|
||||
// 用随机产生的颜色将验证码绘制到图像中。
|
||||
gd.setColor(new Color(red, green, blue));
|
||||
gd.drawString(captchaCode, 18, 27);
|
||||
return buffImg;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成随机字符串。
|
||||
* @return 字符串
|
||||
*/
|
||||
public static String generateCaptchaCode() {
|
||||
Random random = new Random();
|
||||
StringBuilder randomCode = new StringBuilder();
|
||||
for (int i = 0; i < CODE_LENGTH; i++) {
|
||||
randomCode.append(CODE_SEQUENCE[random.nextInt(52)]);
|
||||
}
|
||||
return randomCode.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,26 @@
|
||||
package cn.keking.utils;
|
||||
|
||||
|
||||
import cn.keking.config.ConfigConstants;
|
||||
|
||||
import com.sun.media.jai.codec.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import cn.keking.web.filter.BaseUrlFilter;
|
||||
import com.itextpdf.text.Document;
|
||||
import com.itextpdf.text.Image;
|
||||
import com.itextpdf.text.io.FileChannelRandomAccessSource;
|
||||
import com.itextpdf.text.pdf.PdfWriter;
|
||||
import com.itextpdf.text.pdf.RandomAccessFileOrArray;
|
||||
import com.itextpdf.text.pdf.codec.TiffImage;
|
||||
import com.sun.media.jai.codec.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.media.jai.JAI;
|
||||
import javax.media.jai.RenderedOp;
|
||||
import java.awt.image.RenderedImage;
|
||||
import java.awt.image.renderable.ParameterBlock;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -28,7 +28,6 @@ public class ConvertPicUtil {
|
||||
|
||||
private static final int FIT_WIDTH = 500;
|
||||
private static final int FIT_HEIGHT = 900;
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(ConvertPicUtil.class);
|
||||
private final static String fileDir = ConfigConstants.getFileDir();
|
||||
/**
|
||||
@@ -38,17 +37,14 @@ public class ConvertPicUtil {
|
||||
* @param strOutputFile 输出文件的路径和文件名
|
||||
* @return boolean 是否转换成功
|
||||
*/
|
||||
public static List<String> convertTif2Jpg(String strInputFile, String strOutputFile) {
|
||||
public static List<String> convertTif2Jpg(String strInputFile, String strOutputFile, boolean forceUpdatedCache) throws Exception {
|
||||
List<String> listImageFiles = new ArrayList<>();
|
||||
|
||||
if (strInputFile == null || "".equals(strInputFile.trim())) {
|
||||
return null;
|
||||
}
|
||||
String baseUrl = BaseUrlFilter.getBaseUrl();
|
||||
if (!new File(strInputFile).exists()) {
|
||||
logger.info("找不到文件【" + strInputFile + "】");
|
||||
return null;
|
||||
}
|
||||
strInputFile = strInputFile.replaceAll("\\\\", "/");
|
||||
strOutputFile = strOutputFile.replaceAll(".jpg", "");
|
||||
FileSeekableStream fileSeekStream = null;
|
||||
try {
|
||||
JPEGEncodeParam jpegEncodeParam = new JPEGEncodeParam();
|
||||
@@ -58,20 +54,18 @@ public class ConvertPicUtil {
|
||||
fileSeekStream = new FileSeekableStream(strInputFile);
|
||||
ImageDecoder imageDecoder = ImageCodec.createImageDecoder("TIFF", fileSeekStream, null);
|
||||
int intTifCount = imageDecoder.getNumPages();
|
||||
logger.info("该tif文件共有【" + intTifCount + "】页");
|
||||
String strJpgPath = fileDir+strOutputFile;
|
||||
// logger.info("该tif文件共有【" + intTifCount + "】页");
|
||||
// 处理目标文件夹,如果不存在则自动创建
|
||||
File fileJpgPath = new File(strJpgPath);
|
||||
File fileJpgPath = new File(strOutputFile);
|
||||
if (!fileJpgPath.exists() && !fileJpgPath.mkdirs()) {
|
||||
logger.error("{} 创建失败", strJpgPath);
|
||||
logger.error("{} 创建失败", strOutputFile);
|
||||
}
|
||||
// 循环,处理每页tif文件,转换为jpg
|
||||
for (int i = 0; i < intTifCount; i++) {
|
||||
String strJpg= strJpgPath + "/" + i + ".jpg";
|
||||
String strJpgUrl = strOutputFile + "/" + i + ".jpg";
|
||||
String strJpg= strOutputFile + "/" + i + ".jpg";
|
||||
File fileJpg = new File(strJpg);
|
||||
// 如果文件不存在,则生成
|
||||
if (!fileJpg.exists()) {
|
||||
if (forceUpdatedCache|| !fileJpg.exists()) {
|
||||
RenderedImage renderedImage = imageDecoder.decodeAsRenderedImage(i);
|
||||
ParameterBlock pb = new ParameterBlock();
|
||||
pb.addSource(renderedImage);
|
||||
@@ -80,24 +74,22 @@ public class ConvertPicUtil {
|
||||
pb.add(jpegEncodeParam);
|
||||
RenderedOp renderedOp = JAI.create("filestore", pb);
|
||||
renderedOp.dispose();
|
||||
logger.info("每页分别保存至: " + fileJpg.getCanonicalPath());
|
||||
// logger.info("每页分别保存至: " + fileJpg.getCanonicalPath());
|
||||
}
|
||||
listImageFiles.add(strJpgUrl);
|
||||
strJpg = baseUrl+strJpg.replace(fileDir, "");
|
||||
listImageFiles.add(strJpg);
|
||||
}
|
||||
|
||||
return listImageFiles;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
if (!e.getMessage().contains("Bad endianness tag (not 0x4949 or 0x4d4d)") ) {
|
||||
logger.error("TIF转JPG异常,文件路径:" + strInputFile, e);
|
||||
}
|
||||
throw new Exception(e);
|
||||
} finally {
|
||||
if (fileSeekStream != null) {
|
||||
try {
|
||||
fileSeekStream.close();
|
||||
} catch (IOException e) {
|
||||
logger.error(e.getMessage(), e);
|
||||
}
|
||||
fileSeekStream.close();
|
||||
}
|
||||
}
|
||||
return listImageFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -106,44 +98,41 @@ public class ConvertPicUtil {
|
||||
* @param strJpgFile 输入的jpg的路径和文件名
|
||||
* @param strPdfFile 输出的pdf的路径和文件名
|
||||
*/
|
||||
public static boolean convertJpg2Pdf(String strJpgFile, String strPdfFile) {
|
||||
Document document = null;
|
||||
public static String convertJpg2Pdf(String strJpgFile, String strPdfFile) throws Exception {
|
||||
Document document = new Document();
|
||||
RandomAccessFileOrArray rafa = null;
|
||||
FileOutputStream outputStream = null;
|
||||
try {
|
||||
document = new Document();
|
||||
PdfWriter.getInstance(document, Files.newOutputStream(Paths.get(strPdfFile)));
|
||||
document.open();
|
||||
rafa = new RandomAccessFileOrArray(new FileChannelRandomAccessSource(new RandomAccessFile(strJpgFile, "r").getChannel()));
|
||||
RandomAccessFile aFile = new RandomAccessFile(strJpgFile, "r");
|
||||
FileChannel inChannel = aFile.getChannel();
|
||||
FileChannelRandomAccessSource fcra = new FileChannelRandomAccessSource(inChannel);
|
||||
rafa = new RandomAccessFileOrArray(fcra);
|
||||
int pages = TiffImage.getNumberOfPages(rafa);
|
||||
outputStream = new FileOutputStream(strPdfFile);
|
||||
PdfWriter.getInstance(document, outputStream);
|
||||
document.open();
|
||||
Image image;
|
||||
for (int i = 1; i <= pages; i++) {
|
||||
try {
|
||||
image = TiffImage.getTiffImage(rafa, i);
|
||||
image.scaleToFit(FIT_WIDTH, FIT_HEIGHT);
|
||||
document.add(image);
|
||||
} catch (Exception e) {
|
||||
document.close();
|
||||
rafa.close();
|
||||
e.printStackTrace();
|
||||
}
|
||||
image = TiffImage.getTiffImage(rafa, i);
|
||||
image.scaleToFit(FIT_WIDTH, FIT_HEIGHT);
|
||||
document.add(image);
|
||||
}
|
||||
document.close();
|
||||
rafa.close();
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
logger.error("图片转PDF异常,图片文件路径:" + strJpgFile, e);
|
||||
} catch (IOException e) {
|
||||
if (!e.getMessage().contains("Bad endianness tag (not 0x4949 or 0x4d4d)") ) {
|
||||
logger.error("TIF转JPG异常,文件路径:" + strPdfFile, e);
|
||||
}
|
||||
throw new Exception(e);
|
||||
} finally {
|
||||
try {
|
||||
if (document != null && document.isOpen()) {
|
||||
document.close();
|
||||
}
|
||||
if (rafa != null) {
|
||||
rafa.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
if (document != null) {
|
||||
document.close();
|
||||
}
|
||||
if (rafa != null) {
|
||||
rafa.close();
|
||||
}
|
||||
if (outputStream != null) {
|
||||
outputStream.close();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return strPdfFile;
|
||||
}
|
||||
}
|
||||
26
server/src/main/java/cn/keking/utils/DateUtils.java
Normal file
@@ -0,0 +1,26 @@
|
||||
package cn.keking.utils;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
/**
|
||||
* @author kl (http://kailing.pub)
|
||||
* @since 2023/8/11
|
||||
*/
|
||||
public class DateUtils {
|
||||
/**
|
||||
* 获取当前时间的秒级时间戳
|
||||
* @return
|
||||
*/
|
||||
public static long getCurrentSecond() {
|
||||
return Instant.now().getEpochSecond();
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算当前时间与指定时间的秒级时间戳差值
|
||||
* @param datetime 指定时间
|
||||
* @return 差值
|
||||
*/
|
||||
public static long calculateCurrentTimeDifference(long datetime) {
|
||||
return getCurrentSecond() - datetime;
|
||||
}
|
||||
}
|
||||
@@ -3,16 +3,27 @@ package cn.keking.utils;
|
||||
import cn.keking.config.ConfigConstants;
|
||||
import cn.keking.model.FileAttribute;
|
||||
import cn.keking.model.ReturnResponse;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.mola.galimatias.GalimatiasParseException;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.hc.client5.http.classic.HttpClient;
|
||||
import org.apache.hc.client5.http.impl.DefaultRedirectStrategy;
|
||||
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.client.RequestCallback;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import static cn.keking.utils.KkFileUtils.isFtpUrl;
|
||||
@@ -28,6 +39,10 @@ public class DownloadUtils {
|
||||
private static final String URL_PARAM_FTP_USERNAME = "ftp.username";
|
||||
private static final String URL_PARAM_FTP_PASSWORD = "ftp.password";
|
||||
private static final String URL_PARAM_FTP_CONTROL_ENCODING = "ftp.control.encoding";
|
||||
private static final RestTemplate restTemplate = new RestTemplate();
|
||||
private static final HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
|
||||
private static final ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
|
||||
/**
|
||||
* @param fileAttribute fileAttribute
|
||||
@@ -39,32 +54,34 @@ public class DownloadUtils {
|
||||
String urlStr = null;
|
||||
try {
|
||||
SslUtils.ignoreSsl();
|
||||
urlStr = fileAttribute.getUrl().replaceAll("\\+", "%20");
|
||||
urlStr = fileAttribute.getUrl().replaceAll("\\+", "%20").replaceAll(" ", "%20");
|
||||
} catch (Exception e) {
|
||||
logger.error("忽略SSL证书异常:", e);
|
||||
}
|
||||
ReturnResponse<String> response = new ReturnResponse<>(0, "下载成功!!!", "");
|
||||
String realPath = getRelFilePath(fileName, fileAttribute);
|
||||
|
||||
// 判断是否非法地址
|
||||
if (KkFileUtils.isIllegalFileName(realPath)) {
|
||||
response.setCode(1);
|
||||
response.setContent(null);
|
||||
response.setMsg("下载失败:文件名不合法!" + urlStr);
|
||||
return response;
|
||||
}
|
||||
if (!KkFileUtils.isAllowedUpload(realPath)) {
|
||||
response.setCode(1);
|
||||
response.setContent(null);
|
||||
response.setMsg("下载失败:不支持的类型!" + urlStr);
|
||||
return response;
|
||||
}
|
||||
assert urlStr != null;
|
||||
if (urlStr.contains("?fileKey=")) {
|
||||
if (fileAttribute.isCompressFile()) { //压缩包文件 直接赋予路径 不予下载
|
||||
response.setContent(fileDir + fileName);
|
||||
response.setMsg(fileName);
|
||||
return response;
|
||||
}
|
||||
if(!StringUtils.hasText(realPath)){
|
||||
response.setCode(1);
|
||||
response.setContent(null);
|
||||
response.setMsg("下载失败:文件名不合法!" + urlStr);
|
||||
return response;
|
||||
}
|
||||
if(realPath.equals("cunzhai")){
|
||||
response.setContent(fileDir + fileName);
|
||||
// 如果文件是否已经存在、且不强制更新,则直接返回文件路径
|
||||
if (KkFileUtils.isExist(realPath) && !fileAttribute.forceUpdatedCache()) {
|
||||
response.setContent(realPath);
|
||||
response.setMsg(fileName);
|
||||
return response;
|
||||
}
|
||||
@@ -73,7 +90,31 @@ public class DownloadUtils {
|
||||
if (!fileAttribute.getSkipDownLoad()) {
|
||||
if (isHttpUrl(url)) {
|
||||
File realFile = new File(realPath);
|
||||
FileUtils.copyURLToFile(url, realFile);
|
||||
factory.setConnectionRequestTimeout(2000); //设置超时时间
|
||||
factory.setConnectTimeout(10000);
|
||||
factory.setReadTimeout(72000);
|
||||
HttpClient httpClient = HttpClientBuilder.create().setRedirectStrategy(new DefaultRedirectStrategy()).build();
|
||||
factory.setHttpClient(httpClient); //加入重定向方法
|
||||
restTemplate.setRequestFactory(factory);
|
||||
RequestCallback requestCallback = request -> {
|
||||
request.getHeaders().setAccept(Arrays.asList(MediaType.APPLICATION_OCTET_STREAM, MediaType.ALL));
|
||||
String proxyAuthorization = fileAttribute.getKkProxyAuthorization();
|
||||
if(StringUtils.hasText(proxyAuthorization)){
|
||||
Map<String,String> proxyAuthorizationMap = mapper.readValue(proxyAuthorization, Map.class);
|
||||
proxyAuthorizationMap.forEach((key, value) -> request.getHeaders().set(key, value));
|
||||
}
|
||||
};
|
||||
try {
|
||||
restTemplate.execute(url.toURI(), HttpMethod.GET, requestCallback, fileResponse -> {
|
||||
FileUtils.copyToFile(fileResponse.getBody(), realFile);
|
||||
return null;
|
||||
});
|
||||
} catch (Exception e) {
|
||||
response.setCode(1);
|
||||
response.setContent(null);
|
||||
response.setMsg("下载失败:" + e);
|
||||
return response;
|
||||
}
|
||||
} else if (isFtpUrl(url)) {
|
||||
String ftpUsername = WebUtils.getUrlParameterReg(fileAttribute.getUrl(), URL_PARAM_FTP_USERNAME);
|
||||
String ftpPassword = WebUtils.getUrlParameterReg(fileAttribute.getUrl(), URL_PARAM_FTP_PASSWORD);
|
||||
@@ -115,21 +156,12 @@ public class DownloadUtils {
|
||||
} else { // 文件后缀不一致时,以type为准(针对simText【将类txt文件转为txt】)
|
||||
fileName = fileName.replace(fileName.substring(fileName.lastIndexOf(".") + 1), type);
|
||||
}
|
||||
// 判断是否非法地址
|
||||
if (KkFileUtils.isIllegalFileName(fileName)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String realPath = fileDir + fileName;
|
||||
File dirFile = new File(fileDir);
|
||||
if (!dirFile.exists() && !dirFile.mkdirs()) {
|
||||
logger.error("创建目录【{}】失败,可能是权限不够,请检查", fileDir);
|
||||
}
|
||||
// 文件已在本地存在,跳过文件下载
|
||||
File realFile = new File(realPath);
|
||||
if (realFile.exists()) {
|
||||
fileAttribute.setSkipDownLoad(true);
|
||||
return "cunzhai";
|
||||
}
|
||||
return realPath;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,29 +34,33 @@ public class KkFileUtils {
|
||||
|
||||
/**
|
||||
* 检查文件名是否合规
|
||||
*
|
||||
* @param fileName 文件名
|
||||
* @return 合规结果,true:不合规,false:合规
|
||||
* @return 合规结果, true:不合规,false:合规
|
||||
*/
|
||||
public static boolean isIllegalFileName(String fileName){
|
||||
for (String str: illegalFileStrList){
|
||||
if(fileName.contains(str)){
|
||||
public static boolean isIllegalFileName(String fileName) {
|
||||
for (String str : illegalFileStrList) {
|
||||
if (fileName.contains(str)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否是数字
|
||||
*
|
||||
* @param str 文件名
|
||||
* @return 合规结果,true:不合规,false:合规
|
||||
* @return 合规结果, true:不合规,false:合规
|
||||
*/
|
||||
public static boolean isInteger(String str) {
|
||||
if(StringUtils.hasText(str)){
|
||||
if (StringUtils.hasText(str)) {
|
||||
boolean strResult = str.matches("-?[0-9]+.?[0-9]*");
|
||||
return strResult ;
|
||||
return strResult;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断url是否是http资源
|
||||
*
|
||||
@@ -102,7 +106,7 @@ public class KkFileUtils {
|
||||
|
||||
|
||||
public static String htmlEscape(String input) {
|
||||
if(StringUtils.hasText(input)){
|
||||
if (StringUtils.hasText(input)) {
|
||||
//input = input.replaceAll("\\{", "%7B").replaceAll("}", "%7D").replaceAll("\\\\", "%5C");
|
||||
String htmlStr = HtmlUtils.htmlEscape(input, "UTF-8");
|
||||
//& -> &
|
||||
@@ -186,11 +190,23 @@ public class KkFileUtils {
|
||||
*/
|
||||
public static boolean isAllowedUpload(String file) {
|
||||
String fileType = suffixFromFileName(file);
|
||||
for (String type : ConfigConstants.getProhibit()) {
|
||||
if (type.equals(fileType))
|
||||
for (String type : ConfigConstants.getProhibit()) {
|
||||
if (type.equals(fileType)){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return !ObjectUtils.isEmpty(fileType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断文件是否存在
|
||||
*
|
||||
* @param filePath 文件路径
|
||||
* @return 是否存在 true:存在,false:不存在
|
||||
*/
|
||||
public static boolean isExist(String filePath) {
|
||||
File file = new File(filePath);
|
||||
return file.exists();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -86,6 +86,9 @@ public class LocalOfficeUtils {
|
||||
"/opt/libreoffice7.4",
|
||||
"/opt/libreoffice7.5",
|
||||
"/opt/libreoffice7.6",
|
||||
"/opt/libreoffice24.2",
|
||||
"/opt/libreoffice24.8",
|
||||
"/opt/libreoffice25.2",
|
||||
"/usr/lib64/libreoffice",
|
||||
"/usr/lib/libreoffice",
|
||||
"/usr/local/lib64/libreoffice",
|
||||
|
||||
@@ -4,7 +4,8 @@ import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.apache.poi.EncryptedDocumentException;
|
||||
import org.apache.poi.extractor.ExtractorFactory;
|
||||
import org.apache.poi.hssf.record.crypto.Biff8EncryptionKey;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@@ -19,6 +20,7 @@ import java.nio.file.Paths;
|
||||
*/
|
||||
public class OfficeUtils {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(OfficeUtils.class);
|
||||
private static final String POI_INVALID_PASSWORD_MSG = "password";
|
||||
|
||||
/**
|
||||
@@ -50,7 +52,7 @@ public class OfficeUtils {
|
||||
try {
|
||||
propStream.close();//关闭文件输入流
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
logger.error("Failed to close input stream for file: {}", path, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,7 +65,7 @@ public class OfficeUtils {
|
||||
* @param password 文件密码
|
||||
* @return 是否可打开(兼容)
|
||||
*/
|
||||
public static synchronized boolean isCompatible(String path, @Nullable String password) {
|
||||
public static synchronized boolean isCompatible(String path, String password) {
|
||||
InputStream propStream = null;
|
||||
try {
|
||||
propStream = Files.newInputStream(Paths.get(path));
|
||||
@@ -77,7 +79,7 @@ public class OfficeUtils {
|
||||
try {
|
||||
propStream.close();//关闭文件输入流
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
logger.error("Failed to close input stream for file: {}", path, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package cn.keking.utils;
|
||||
import cn.keking.config.ConfigConstants;
|
||||
import cn.keking.service.ZtreeNodeVo;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.charset.Charset;
|
||||
@@ -15,6 +18,7 @@ import java.util.regex.Pattern;
|
||||
* create : 2023-04-08
|
||||
**/
|
||||
public class RarUtils {
|
||||
private static final Logger logger = LoggerFactory.getLogger(RarUtils.class);
|
||||
private static final String fileDir = ConfigConstants.getFileDir();
|
||||
|
||||
public static byte[] getUTF8BytesFromGBKString(String gbkStr) {
|
||||
@@ -55,7 +59,7 @@ public class RarUtils {
|
||||
str = new String(getUTF8BytesFromGBKString(str), StandardCharsets.UTF_8);
|
||||
}
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
e.printStackTrace();
|
||||
logger.error("Failed to convert string encoding: {}", str, e);
|
||||
}
|
||||
}
|
||||
return str;
|
||||
@@ -75,15 +79,18 @@ public class RarUtils {
|
||||
public static boolean judge(char c){
|
||||
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z');
|
||||
}
|
||||
public static String specialSymbols(String str) {
|
||||
//去除压缩包文件字符串中特殊符号
|
||||
Pattern p = Pattern.compile("\\s|\t|\r|\n|\\+|#|&|=|<7C>|\\p{P}");
|
||||
// Pattern p = Pattern.compile("\\s|\\+|#|&|=|\\p{P}");
|
||||
Matcher m = p.matcher(str);
|
||||
return m.replaceAll("");
|
||||
}
|
||||
public static boolean isMessyCode(String strName) {
|
||||
//去除字符串中的空格 制表符 换行 回车
|
||||
Pattern p = Pattern.compile("\\s*|\t*|\r*|\n*");
|
||||
Matcher m = p.matcher(strName);
|
||||
String after = m.replaceAll("").replaceAll("\\+", "").replaceAll("#", "").replaceAll("&", "");
|
||||
//去除字符串中的标点符号
|
||||
String temp = after.replaceAll("\\p{P}", "");
|
||||
strName = specialSymbols(strName);
|
||||
//处理之后转换成字符数组
|
||||
char[] ch = temp.trim().toCharArray();
|
||||
char[] ch = strName.trim().toCharArray();
|
||||
for (char c : ch) {
|
||||
//判断是否是数字或者英文字符
|
||||
if (!judge(c)) {
|
||||
|
||||
4609
server/src/main/java/cn/keking/utils/SimpleEncodingDetects.java
Normal file
82
server/src/main/java/cn/keking/utils/UrlEncoderUtils.java
Normal file
@@ -0,0 +1,82 @@
|
||||
package cn.keking.utils;
|
||||
import java.util.BitSet;
|
||||
|
||||
|
||||
public class UrlEncoderUtils {
|
||||
|
||||
private static BitSet dontNeedEncoding;
|
||||
|
||||
static {
|
||||
dontNeedEncoding = new BitSet(256);
|
||||
int i;
|
||||
for (i = 'a'; i <= 'z'; i++) {
|
||||
dontNeedEncoding.set(i);
|
||||
}
|
||||
for (i = 'A'; i <= 'Z'; i++) {
|
||||
dontNeedEncoding.set(i);
|
||||
}
|
||||
for (i = '0'; i <= '9'; i++) {
|
||||
dontNeedEncoding.set(i);
|
||||
}
|
||||
dontNeedEncoding.set('+');
|
||||
/**
|
||||
* 这里会有误差,比如输入一个字符串 123+456,它到底是原文就是123+456还是123 456做了urlEncode后的内容呢?<br>
|
||||
* 其实问题是一样的,比如遇到123%2B456,它到底是原文即使如此,还是123+456 urlEncode后的呢? <br>
|
||||
* 在这里,我认为只要符合urlEncode规范的,就当作已经urlEncode过了<br>
|
||||
* 毕竟这个方法的初衷就是判断string是否urlEncode过<br>
|
||||
*/
|
||||
|
||||
dontNeedEncoding.set('-');
|
||||
dontNeedEncoding.set('_');
|
||||
dontNeedEncoding.set('.');
|
||||
dontNeedEncoding.set('*');
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断str是否urlEncoder.encode过<br>
|
||||
* 经常遇到这样的情况,拿到一个URL,但是搞不清楚到底要不要encode.<Br>
|
||||
* 不做encode吧,担心出错,做encode吧,又怕重复了<Br>
|
||||
*
|
||||
* @param str
|
||||
* @return
|
||||
*/
|
||||
public static boolean hasUrlEncoded(String str) {
|
||||
|
||||
/**
|
||||
* 支持JAVA的URLEncoder.encode出来的string做判断。 即: 将' '转成'+' <br>
|
||||
* 0-9a-zA-Z保留 <br>
|
||||
* '-','_','.','*'保留 <br>
|
||||
* 其他字符转成%XX的格式,X是16进制的大写字符,范围是[0-9A-F]
|
||||
*/
|
||||
boolean needEncode = false;
|
||||
for (int i = 0; i < str.length(); i++) {
|
||||
char c = str.charAt(i);
|
||||
if (dontNeedEncoding.get((int) c)) {
|
||||
continue;
|
||||
}
|
||||
if (c == '%' && (i + 2) < str.length()) {
|
||||
// 判断是否符合urlEncode规范
|
||||
char c1 = str.charAt(++i);
|
||||
char c2 = str.charAt(++i);
|
||||
if (isDigit16Char(c1) && isDigit16Char(c2)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// 其他字符,肯定需要urlEncode
|
||||
needEncode = true;
|
||||
break;
|
||||
}
|
||||
|
||||
return !needEncode;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断c是否是16进制的字符
|
||||
*
|
||||
* @param c
|
||||
* @return
|
||||
*/
|
||||
private static boolean isDigit16Char(char c) {
|
||||
return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F');
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,16 @@
|
||||
package cn.keking.utils;
|
||||
|
||||
import io.mola.galimatias.GalimatiasParseException;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.util.Base64Utils;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.util.HtmlUtils;
|
||||
|
||||
import javax.servlet.ServletRequest;
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
@@ -27,7 +29,7 @@ import java.util.regex.Pattern;
|
||||
public class WebUtils {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(WebUtils.class);
|
||||
|
||||
private static final String BASE64_MSG = "base64";
|
||||
/**
|
||||
* 获取标准的URL
|
||||
*
|
||||
@@ -38,6 +40,59 @@ public class WebUtils {
|
||||
return io.mola.galimatias.URL.parse(urlStr).toJavaURL();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 对文件名进行编码
|
||||
*
|
||||
*/
|
||||
public static String encodeFileName(String name) {
|
||||
try {
|
||||
name = URLEncoder.encode(name, "UTF-8").replaceAll("\\+", "%20");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
return null;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 去除fullfilename参数
|
||||
*
|
||||
* @param urlStr
|
||||
* @return
|
||||
*/
|
||||
public static String clearFullfilenameParam(String urlStr) {
|
||||
// 去除特定参数字段
|
||||
Pattern pattern = Pattern.compile("(&fullfilename=[^&]*)");
|
||||
Matcher matcher = pattern.matcher(urlStr);
|
||||
return matcher.replaceAll("");
|
||||
}
|
||||
|
||||
/**
|
||||
* 对URL进行编码
|
||||
*/
|
||||
public static String urlEncoderencode(String urlStr) {
|
||||
|
||||
String fullFileName = getUrlParameterReg(urlStr, "fullfilename"); //获取流文件名
|
||||
if (org.springframework.util.StringUtils.hasText(fullFileName)) {
|
||||
// 移除fullfilename参数
|
||||
urlStr = clearFullfilenameParam(urlStr);
|
||||
} else {
|
||||
fullFileName = getFileNameFromURL(urlStr); //获取文件名
|
||||
}
|
||||
if (KkFileUtils.isIllegalFileName(fullFileName)) { //判断文件名是否带有穿越漏洞
|
||||
return null;
|
||||
}
|
||||
if (!UrlEncoderUtils.hasUrlEncoded(fullFileName)) { //判断文件名是否转义
|
||||
try {
|
||||
urlStr = URLEncoder.encode(urlStr, "UTF-8").replaceAll("\\+", "%20").replaceAll("%3A", ":").replaceAll("%2F", "/").replaceAll("%3F", "?").replaceAll("%26", "&").replaceAll("%3D", "=");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
LOGGER.error("Failed to encode URL: {}", urlStr, e);
|
||||
}
|
||||
}
|
||||
return urlStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取url中的参数
|
||||
*
|
||||
@@ -100,7 +155,7 @@ public class WebUtils {
|
||||
URL urlObj = new URL(url);
|
||||
url = urlObj.getPath().substring(1);
|
||||
} catch (MalformedURLException e) {
|
||||
e.printStackTrace();
|
||||
LOGGER.error("Failed to parse file URL: {}", url, e);
|
||||
}
|
||||
}
|
||||
// 因为url的参数中可能会存在/的情况,所以直接url.lastIndexOf("/")会有问题
|
||||
@@ -205,13 +260,7 @@ public class WebUtils {
|
||||
Matcher matcher = pattern.matcher(url);
|
||||
return matcher.find();
|
||||
}
|
||||
public static boolean kuayu(String host, String wjl) { //查询域名是否相同
|
||||
if (wjl.contains(host)) {
|
||||
return true;
|
||||
}else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 Base64 字符串解码,再解码URL参数, 默认使用 UTF-8
|
||||
* @param source 原始 Base64 字符串
|
||||
@@ -241,9 +290,13 @@ public class WebUtils {
|
||||
* https://github.com/kekingcn/kkFileView/pull/340
|
||||
*/
|
||||
try {
|
||||
return new String(Base64Utils.decodeFromString(source.replaceAll(" ", "+").replaceAll("\n", "")), charsets);
|
||||
return new String(Base64.decodeBase64(source.replaceAll(" ", "+").replaceAll("\n", "")), charsets);
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("url解码异常,可能是接入方法错误或者未使用BASE64", e);
|
||||
if (e.getMessage().toLowerCase().contains(BASE64_MSG)) {
|
||||
LOGGER.error("url解码异常,接入方法错误未使用BASE64");
|
||||
}else {
|
||||
LOGGER.error("url解码异常,其他错误", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -261,4 +314,61 @@ public class WebUtils {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 session 中的 String 属性
|
||||
* @param request 请求
|
||||
* @return 属性值
|
||||
*/
|
||||
public static String getSessionAttr(HttpServletRequest request, String key) {
|
||||
HttpSession session = request.getSession();
|
||||
if (session == null) {
|
||||
return null;
|
||||
}
|
||||
Object value = session.getAttribute(key);
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 session 中的 long 属性
|
||||
* @param request 请求
|
||||
* @param key 属性名
|
||||
* @return 属性值
|
||||
*/
|
||||
public static long getLongSessionAttr(HttpServletRequest request, String key) {
|
||||
String value = getSessionAttr(request, key);
|
||||
if (value == null) {
|
||||
return 0;
|
||||
}
|
||||
return Long.parseLong(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* session 中设置属性
|
||||
* @param request 请求
|
||||
* @param key 属性名
|
||||
*/
|
||||
public static void setSessionAttr(HttpServletRequest request, String key, Object value) {
|
||||
HttpSession session = request.getSession();
|
||||
if (session == null) {
|
||||
return;
|
||||
}
|
||||
session.setAttribute(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除 session 中的属性
|
||||
* @param request 请求
|
||||
* @param key 属性名
|
||||
*/
|
||||
public static void removeSessionAttr(HttpServletRequest request, String key) {
|
||||
HttpSession session = request.getSession();
|
||||
if (session == null) {
|
||||
return;
|
||||
}
|
||||
session.removeAttribute(key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package cn.keking.web.controller;
|
||||
|
||||
import cn.keking.config.ConfigConstants;
|
||||
import cn.keking.model.ReturnResponse;
|
||||
import cn.keking.utils.CaptchaUtil;
|
||||
import cn.keking.utils.DateUtils;
|
||||
import cn.keking.utils.KkFileUtils;
|
||||
import cn.keking.utils.RarUtils;
|
||||
import cn.keking.utils.WebUtils;
|
||||
@@ -11,21 +13,34 @@ import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StreamUtils;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import jakarta.servlet.ServletOutputStream;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.keking.utils.CaptchaUtil.CAPTCHA_CODE;
|
||||
import static cn.keking.utils.CaptchaUtil.CAPTCHA_GENERATE_TIME;
|
||||
|
||||
/**
|
||||
* @author yudian-it
|
||||
* 2017/12/1
|
||||
* 2017/12/1
|
||||
*/
|
||||
@RestController
|
||||
public class FileController {
|
||||
@@ -34,6 +49,7 @@ public class FileController {
|
||||
|
||||
private final String fileDir = ConfigConstants.getFileDir();
|
||||
private final String demoDir = "demo";
|
||||
|
||||
private final String demoPath = demoDir + File.separator;
|
||||
public static final String BASE64_DECODE_ERROR_MSG = "Base64解码失败,请检查你的 %s 是否采用 Base64 + urlEncode 双重编码了!";
|
||||
|
||||
@@ -59,16 +75,12 @@ public class FileController {
|
||||
}
|
||||
|
||||
@GetMapping("/deleteFile")
|
||||
public ReturnResponse<Object> deleteFile(String fileName,String password) {
|
||||
ReturnResponse<Object> checkResult = this.deleteFileCheck(fileName);
|
||||
public ReturnResponse<Object> deleteFile(HttpServletRequest request, String fileName, String password) {
|
||||
ReturnResponse<Object> checkResult = this.deleteFileCheck(request, fileName, password);
|
||||
if (checkResult.isFailure()) {
|
||||
return checkResult;
|
||||
}
|
||||
fileName = checkResult.getContent().toString();
|
||||
if(!ConfigConstants.getPassword().equalsIgnoreCase(password)) {
|
||||
logger.error("删除文件【{}】失败,密码错误!",fileName);
|
||||
return ReturnResponse.failure("删除文件失败,密码错误!");
|
||||
}
|
||||
fileName = checkResult.getContent().toString();
|
||||
File file = new File(fileDir + demoPath + fileName);
|
||||
logger.info("删除文件:{}", file.getAbsolutePath());
|
||||
if (file.exists() && !file.delete()) {
|
||||
@@ -76,9 +88,42 @@ public class FileController {
|
||||
logger.error(msg);
|
||||
return ReturnResponse.failure(msg);
|
||||
}
|
||||
WebUtils.removeSessionAttr(request, CAPTCHA_CODE); //删除缓存验证码
|
||||
return ReturnResponse.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证码方法
|
||||
*/
|
||||
@RequestMapping("/deleteFile/captcha")
|
||||
public void captcha(HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||
if (!ConfigConstants.getDeleteCaptcha()) {
|
||||
return;
|
||||
}
|
||||
|
||||
response.setContentType("image/jpeg");
|
||||
response.setHeader("Pragma", "no-cache");
|
||||
response.setHeader("Cache-Control", "no-cache");
|
||||
response.setDateHeader("Expires", -1);
|
||||
String captchaCode = WebUtils.getSessionAttr(request, CAPTCHA_CODE);
|
||||
long captchaGenerateTime = WebUtils.getLongSessionAttr(request, CAPTCHA_GENERATE_TIME);
|
||||
long timeDifference = DateUtils.calculateCurrentTimeDifference(captchaGenerateTime);
|
||||
|
||||
// 验证码为空,且生成验证码超过50秒,重新生成验证码
|
||||
if (timeDifference > 50 && ObjectUtils.isEmpty(captchaCode)) {
|
||||
captchaCode = CaptchaUtil.generateCaptchaCode();
|
||||
// 更新验证码
|
||||
WebUtils.setSessionAttr(request, CAPTCHA_CODE, captchaCode);
|
||||
WebUtils.setSessionAttr(request, CAPTCHA_GENERATE_TIME, DateUtils.getCurrentSecond());
|
||||
} else {
|
||||
captchaCode = ObjectUtils.isEmpty(captchaCode) ? "wait" : captchaCode;
|
||||
}
|
||||
|
||||
ServletOutputStream outputStream = response.getOutputStream();
|
||||
ImageIO.write(CaptchaUtil.generateCaptchaPic(captchaCode), "jpeg", outputStream);
|
||||
outputStream.close();
|
||||
}
|
||||
|
||||
@GetMapping("/listFiles")
|
||||
public List<Map<String, String>> getFiles() {
|
||||
List<Map<String, String>> list = new ArrayList<>();
|
||||
@@ -106,7 +151,7 @@ public class FileController {
|
||||
return ReturnResponse.failure("文件传接口已禁用");
|
||||
}
|
||||
String fileName = WebUtils.getFileNameFromMultipartFile(file);
|
||||
if(fileName.lastIndexOf(".")==-1){
|
||||
if (fileName.lastIndexOf(".") == -1) {
|
||||
return ReturnResponse.failure("不允许上传的类型");
|
||||
}
|
||||
if (!KkFileUtils.isAllowedUpload(fileName)) {
|
||||
@@ -129,7 +174,7 @@ public class FileController {
|
||||
* @param fileName 文件名
|
||||
* @return 校验结果
|
||||
*/
|
||||
private ReturnResponse<Object> deleteFileCheck(String fileName) {
|
||||
private ReturnResponse<Object> deleteFileCheck(HttpServletRequest request, String fileName, String password) {
|
||||
if (ObjectUtils.isEmpty(fileName)) {
|
||||
return ReturnResponse.failure("文件名为空,删除失败!");
|
||||
}
|
||||
@@ -146,6 +191,16 @@ public class FileController {
|
||||
if (KkFileUtils.isIllegalFileName(fileName)) {
|
||||
return ReturnResponse.failure("非法文件名,删除失败!");
|
||||
}
|
||||
if (ObjectUtils.isEmpty(password)) {
|
||||
return ReturnResponse.failure("密码 or 验证码为空,删除失败!");
|
||||
}
|
||||
|
||||
String expectedPassword = ConfigConstants.getDeleteCaptcha() ? WebUtils.getSessionAttr(request, CAPTCHA_CODE) : ConfigConstants.getPassword();
|
||||
|
||||
if (!password.equalsIgnoreCase(expectedPassword)) {
|
||||
logger.error("删除文件【{}】失败,密码错误!", fileName);
|
||||
return ReturnResponse.failure("删除文件失败,密码错误!");
|
||||
}
|
||||
return ReturnResponse.success(fileName);
|
||||
}
|
||||
|
||||
@@ -158,6 +213,7 @@ public class FileController {
|
||||
String errorMsg = String.format(BASE64_DECODE_ERROR_MSG, "url");
|
||||
return ReturnResponse.failure(errorMsg);
|
||||
}
|
||||
fileUrl = fileUrl.replaceAll("http://", "");
|
||||
if (KkFileUtils.isIllegalFileName(fileUrl)) {
|
||||
return ReturnResponse.failure("不允许访问的路径:");
|
||||
}
|
||||
|
||||
@@ -8,25 +8,34 @@ import cn.keking.service.cache.CacheService;
|
||||
import cn.keking.service.impl.OtherFilePreviewImpl;
|
||||
import cn.keking.utils.KkFileUtils;
|
||||
import cn.keking.utils.WebUtils;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import fr.opensagres.xdocreport.core.io.IOUtils;
|
||||
import io.mola.galimatias.GalimatiasParseException;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.hc.client5.http.classic.HttpClient;
|
||||
import org.apache.hc.client5.http.impl.DefaultRedirectStrategy;
|
||||
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.client.RequestCallback;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static cn.keking.service.FilePreview.PICTURE_FILE_PREVIEW_PAGE;
|
||||
|
||||
@@ -43,6 +52,9 @@ public class OnlinePreviewController {
|
||||
private final CacheService cacheService;
|
||||
private final FileHandlerService fileHandlerService;
|
||||
private final OtherFilePreviewImpl otherFilePreview;
|
||||
private static final RestTemplate restTemplate = new RestTemplate();
|
||||
private static final HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
|
||||
private static final ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
public OnlinePreviewController(FilePreviewFactory filePreviewFactory, FileHandlerService fileHandlerService, CacheService cacheService, OtherFilePreviewImpl otherFilePreview) {
|
||||
this.previewFactory = filePreviewFactory;
|
||||
@@ -53,6 +65,7 @@ public class OnlinePreviewController {
|
||||
|
||||
@GetMapping( "/onlinePreview")
|
||||
public String onlinePreview(String url, Model model, HttpServletRequest req) {
|
||||
|
||||
String fileUrl;
|
||||
try {
|
||||
fileUrl = WebUtils.decodeUrl(url);
|
||||
@@ -60,11 +73,15 @@ public class OnlinePreviewController {
|
||||
String errorMsg = String.format(BASE64_DECODE_ERROR_MSG, "url");
|
||||
return otherFilePreview.notSupportedFile(model, errorMsg);
|
||||
}
|
||||
FileAttribute fileAttribute = fileHandlerService.getFileAttribute(fileUrl, req);
|
||||
FileAttribute fileAttribute = fileHandlerService.getFileAttribute(fileUrl, req); //这里不在进行URL 处理了
|
||||
model.addAttribute("file", fileAttribute);
|
||||
FilePreview filePreview = previewFactory.get(fileAttribute);
|
||||
logger.info("预览文件url:{},previewType:{}", fileUrl, fileAttribute.getType());
|
||||
return filePreview.filePreviewHandle(fileUrl, model, fileAttribute);
|
||||
fileUrl =WebUtils.urlEncoderencode(fileUrl);
|
||||
if (ObjectUtils.isEmpty(fileUrl)) {
|
||||
return otherFilePreview.notSupportedFile(model, "非法路径,不允许访问");
|
||||
}
|
||||
return filePreview.filePreviewHandle(fileUrl, model, fileAttribute); //统一在这里处理 url
|
||||
}
|
||||
|
||||
@GetMapping( "/picturesPreview")
|
||||
@@ -102,58 +119,53 @@ public class OnlinePreviewController {
|
||||
* @param response response
|
||||
*/
|
||||
@GetMapping("/getCorsFile")
|
||||
public void getCorsFile(String urlPath, HttpServletResponse response) throws IOException {
|
||||
public void getCorsFile(String urlPath, HttpServletResponse response,FileAttribute fileAttribute) throws IOException {
|
||||
URL url;
|
||||
try {
|
||||
urlPath = WebUtils.decodeUrl(urlPath);
|
||||
url = WebUtils.normalizedURL(urlPath);
|
||||
} catch (Exception ex) {
|
||||
logger.error(String.format(BASE64_DECODE_ERROR_MSG, urlPath),ex);
|
||||
return;
|
||||
}
|
||||
HttpURLConnection urlcon = null;
|
||||
InputStream inputStream = null;
|
||||
assert urlPath != null;
|
||||
if (!urlPath.toLowerCase().startsWith("http") && !urlPath.toLowerCase().startsWith("https") && !urlPath.toLowerCase().startsWith("ftp")) {
|
||||
logger.info("读取跨域文件异常,可能存在非法访问,urlPath:{}", urlPath);
|
||||
return;
|
||||
}
|
||||
logger.info("下载跨域pdf文件url:{}", urlPath);
|
||||
if (!urlPath.toLowerCase().startsWith("ftp:")){
|
||||
InputStream inputStream = null;
|
||||
logger.info("读取跨域pdf文件url:{}", urlPath);
|
||||
if (!urlPath.toLowerCase().startsWith("ftp:")) {
|
||||
factory.setConnectionRequestTimeout(2000);
|
||||
factory.setConnectTimeout(10000);
|
||||
factory.setReadTimeout(72000);
|
||||
HttpClient httpClient = HttpClientBuilder.create().setRedirectStrategy(new DefaultRedirectStrategy()).build();
|
||||
factory.setHttpClient(httpClient);
|
||||
restTemplate.setRequestFactory(factory);
|
||||
RequestCallback requestCallback = request -> {
|
||||
request.getHeaders().setAccept(Arrays.asList(MediaType.APPLICATION_OCTET_STREAM, MediaType.ALL));
|
||||
String proxyAuthorization = fileAttribute.getKkProxyAuthorization();
|
||||
if(StringUtils.hasText(proxyAuthorization)){
|
||||
Map<String,String> proxyAuthorizationMap = mapper.readValue(proxyAuthorization, Map.class);
|
||||
proxyAuthorizationMap.forEach((key, value) -> request.getHeaders().set(key, value));
|
||||
}
|
||||
};
|
||||
try {
|
||||
URL url = WebUtils.normalizedURL(urlPath);
|
||||
urlcon=(HttpURLConnection)url.openConnection();
|
||||
urlcon.setConnectTimeout(30000);
|
||||
urlcon.setReadTimeout(30000);
|
||||
urlcon.setInstanceFollowRedirects(false);
|
||||
int responseCode = urlcon.getResponseCode();
|
||||
if (responseCode == HttpURLConnection.HTTP_MOVED_PERM || responseCode == HttpURLConnection.HTTP_MOVED_TEMP) { //301 302
|
||||
url =new URL(urlcon.getHeaderField("Location"));
|
||||
urlcon=(HttpURLConnection)url.openConnection();
|
||||
}
|
||||
if (responseCode == HttpURLConnection.HTTP_NOT_FOUND ||responseCode == HttpURLConnection.HTTP_FORBIDDEN || responseCode == HttpURLConnection.HTTP_INTERNAL_ERROR ) { //403 404 500
|
||||
logger.error("读取跨域文件异常,url:{},错误:{}", urlPath,responseCode);
|
||||
} else {
|
||||
if(urlPath.contains( ".svg")) {
|
||||
response.setContentType("image/svg+xml");
|
||||
}
|
||||
inputStream=(url).openStream();
|
||||
IOUtils.copy(inputStream, response.getOutputStream());
|
||||
}
|
||||
} catch (IOException | GalimatiasParseException e) {
|
||||
logger.error("读取跨域文件异常,url:{}", urlPath);
|
||||
} finally {
|
||||
assert urlcon != null;
|
||||
urlcon.disconnect();
|
||||
IOUtils.closeQuietly(inputStream);
|
||||
restTemplate.execute(url.toURI(), HttpMethod.GET, requestCallback, fileResponse -> {
|
||||
IOUtils.copy(fileResponse.getBody(), response.getOutputStream());
|
||||
return null;
|
||||
});
|
||||
} catch (Exception e) {
|
||||
System.out.println(e);
|
||||
}
|
||||
} else {
|
||||
}else{
|
||||
try {
|
||||
URL url = WebUtils.normalizedURL(urlPath);
|
||||
if(urlPath.contains(".svg")) {
|
||||
response.setContentType("image/svg+xml");
|
||||
}
|
||||
inputStream = (url).openStream();
|
||||
IOUtils.copy(inputStream, response.getOutputStream());
|
||||
} catch (IOException | GalimatiasParseException e) {
|
||||
} catch (IOException e) {
|
||||
logger.error("读取跨域文件异常,url:{}", urlPath);
|
||||
} finally {
|
||||
IOUtils.closeQuietly(inputStream);
|
||||
|
||||
@@ -4,8 +4,8 @@ import cn.keking.config.ConfigConstants;
|
||||
import cn.keking.config.WatermarkConfigConstants;
|
||||
import cn.keking.utils.KkFileUtils;
|
||||
|
||||
import javax.servlet.*;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.*;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
@@ -37,11 +37,16 @@ public class AttributeSetFilter implements Filter {
|
||||
request.setAttribute("pdfPrintDisable", ConfigConstants.getPdfPrintDisable());
|
||||
request.setAttribute("pdfDownloadDisable", ConfigConstants.getPdfDownloadDisable());
|
||||
request.setAttribute("pdfBookmarkDisable", ConfigConstants.getPdfBookmarkDisable());
|
||||
request.setAttribute("fileKey", httpRequest.getParameter("fileKey"));
|
||||
request.setAttribute("pdfDisableEditing", ConfigConstants.getPdfDisableEditing());
|
||||
request.setAttribute("switchDisabled", ConfigConstants.getOfficePreviewSwitchDisabled());
|
||||
request.setAttribute("fileUploadDisable", ConfigConstants.getFileUploadDisable());
|
||||
request.setAttribute("beian", ConfigConstants.getBeian());
|
||||
request.setAttribute("size", ConfigConstants.maxSize());
|
||||
request.setAttribute("deleteCaptcha", ConfigConstants.getDeleteCaptcha());
|
||||
request.setAttribute("homePageNumber", ConfigConstants.getHomePageNumber());
|
||||
request.setAttribute("homePagination", ConfigConstants.getHomePagination());
|
||||
request.setAttribute("homePageSize", ConfigConstants.getHomePageSize());
|
||||
request.setAttribute("homeSearch", ConfigConstants.getHomeSearch());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,8 +4,8 @@ import cn.keking.config.ConfigConstants;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
|
||||
import javax.servlet.*;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.*;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
@@ -43,7 +43,7 @@ public class BaseUrlFilter implements Filter {
|
||||
final String urlInHeader = servletRequest.getHeader("X-Base-Url");
|
||||
if (StringUtils.isNotEmpty(urlInHeader)) {
|
||||
baseUrl = urlInHeader;
|
||||
} else if (configBaseUrl != null && !ConfigConstants.DEFAULT_BASE_URL.equalsIgnoreCase(configBaseUrl)) {
|
||||
} else if (configBaseUrl != null && !ConfigConstants.DEFAULT_VALUE.equalsIgnoreCase(configBaseUrl)) {
|
||||
//2、如果配置文件中配置了 baseUrl 且不为 default 则以配置文件为准
|
||||
baseUrl = configBaseUrl;
|
||||
} else {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package cn.keking.web.filter;
|
||||
|
||||
|
||||
import javax.servlet.*;
|
||||
import jakarta.servlet.*;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,10 +3,10 @@ package cn.keking.web.filter;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.servlet.*;
|
||||
import jakarta.servlet.*;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.URLDecoder;
|
||||
@@ -35,7 +35,7 @@ public class TrustDirFilter implements Filter {
|
||||
byte[] bytes = FileCopyUtils.copyToByteArray(classPathResource.getInputStream());
|
||||
this.notTrustDirView = new String(bytes, StandardCharsets.UTF_8);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
logger.error("加载notTrustDir.html失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,14 +2,25 @@ package cn.keking.web.filter;
|
||||
|
||||
import cn.keking.config.ConfigConstants;
|
||||
import cn.keking.utils.WebUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.FilterConfig;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
import jakarta.servlet.Filter;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.FilterConfig;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.ServletResponse;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
@@ -19,7 +30,9 @@ import org.springframework.util.FileCopyUtils;
|
||||
*/
|
||||
public class TrustHostFilter implements Filter {
|
||||
|
||||
private String notTrustHost;
|
||||
private static final Logger logger = LoggerFactory.getLogger(TrustHostFilter.class);
|
||||
private final Map<String, Pattern> wildcardPatternCache = new ConcurrentHashMap<>();
|
||||
private String notTrustHostHtmlView;
|
||||
|
||||
@Override
|
||||
public void init(FilterConfig filterConfig) {
|
||||
@@ -27,9 +40,9 @@ public class TrustHostFilter implements Filter {
|
||||
try {
|
||||
classPathResource.getInputStream();
|
||||
byte[] bytes = FileCopyUtils.copyToByteArray(classPathResource.getInputStream());
|
||||
this.notTrustHost = new String(bytes, StandardCharsets.UTF_8);
|
||||
this.notTrustHostHtmlView = new String(bytes, StandardCharsets.UTF_8);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
logger.error("Failed to load notTrustHost.html file", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,16 +50,181 @@ public class TrustHostFilter implements Filter {
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
|
||||
String url = WebUtils.getSourceUrl(request);
|
||||
String host = WebUtils.getHost(url);
|
||||
if (host != null &&!ConfigConstants.getTrustHostSet().isEmpty() && !ConfigConstants.getTrustHostSet().contains(host)) {
|
||||
String html = this.notTrustHost.replace("${current_host}", host);
|
||||
if (isNotTrustHost(host)) {
|
||||
String currentHost = host == null ? "UNKNOWN" : host;
|
||||
if (response instanceof HttpServletResponse httpServletResponse) {
|
||||
httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
|
||||
}
|
||||
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||
response.setContentType("text/html;charset=UTF-8");
|
||||
String html = this.notTrustHostHtmlView == null
|
||||
? "<html><head><meta charset=\"utf-8\"></head><body>当前预览文件来自不受信任的站点:" + currentHost + "</body></html>"
|
||||
: this.notTrustHostHtmlView.replace("${current_host}", currentHost);
|
||||
response.getWriter().write(html);
|
||||
response.getWriter().close();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isNotTrustHost(String host) {
|
||||
if (host == null || host.trim().isEmpty()) {
|
||||
logger.warn("主机名为空或无效,拒绝访问");
|
||||
return true;
|
||||
}
|
||||
|
||||
// 如果配置了黑名单,优先检查黑名单
|
||||
if (CollectionUtils.isNotEmpty(ConfigConstants.getNotTrustHostSet())
|
||||
&& matchAnyPattern(host, ConfigConstants.getNotTrustHostSet())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 如果配置了白名单,检查是否在白名单中
|
||||
if (CollectionUtils.isNotEmpty(ConfigConstants.getTrustHostSet())) {
|
||||
// 支持通配符 * 表示允许所有主机
|
||||
if (ConfigConstants.getTrustHostSet().contains("*")) {
|
||||
logger.debug("允许所有主机访问(通配符模式): {}", host);
|
||||
return false;
|
||||
}
|
||||
return !matchAnyPattern(host, ConfigConstants.getTrustHostSet());
|
||||
}
|
||||
|
||||
// 安全加固:默认拒绝所有未配置的主机(防止SSRF攻击)
|
||||
// 如果需要允许所有主机,请在配置文件中明确设置 trust.host = *
|
||||
logger.warn("未配置信任主机列表,拒绝访问主机: {},请在配置文件中设置 trust.host 或 KK_TRUST_HOST 环境变量", host);
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean matchAnyPattern(String host, Set<String> hostPatterns) {
|
||||
String normalizedHost = host.toLowerCase(Locale.ROOT);
|
||||
for (String hostPattern : hostPatterns) {
|
||||
if (matchHostPattern(normalizedHost, hostPattern)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 支持三种匹配方式:
|
||||
* 1. 精确匹配:example.com
|
||||
* 2. 通配符匹配:*.example.com、192.168.*
|
||||
* 3. IPv4 CIDR:192.168.0.0/16
|
||||
*/
|
||||
private boolean matchHostPattern(String host, String hostPattern) {
|
||||
if (hostPattern == null || hostPattern.trim().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
String pattern = hostPattern.trim().toLowerCase(Locale.ROOT);
|
||||
|
||||
if ("*".equals(pattern)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (pattern.contains("/")) {
|
||||
return matchIpv4Cidr(host, pattern);
|
||||
}
|
||||
|
||||
if (pattern.contains("*")) {
|
||||
if (isIpv4WildcardPattern(pattern)) {
|
||||
return matchIpv4Wildcard(host, pattern);
|
||||
}
|
||||
Pattern compiledPattern = wildcardPatternCache.computeIfAbsent(pattern, key -> Pattern.compile(wildcardToRegex(key)));
|
||||
return compiledPattern.matcher(host).matches();
|
||||
}
|
||||
|
||||
return host.equals(pattern);
|
||||
}
|
||||
|
||||
private boolean isIpv4WildcardPattern(String pattern) {
|
||||
return pattern.matches("^[0-9.*]+$") && pattern.contains(".");
|
||||
}
|
||||
|
||||
private boolean matchIpv4Wildcard(String host, String pattern) {
|
||||
if (parseLiteralIpv4(host) == null) {
|
||||
return false;
|
||||
}
|
||||
String[] hostParts = host.split("\\.");
|
||||
String[] patternParts = pattern.split("\\.");
|
||||
if (hostParts.length != 4 || patternParts.length < 1 || patternParts.length > 4) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < patternParts.length; i++) {
|
||||
String p = patternParts[i];
|
||||
if ("*".equals(p)) {
|
||||
continue;
|
||||
}
|
||||
if (!p.equals(hostParts[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private String wildcardToRegex(String wildcard) {
|
||||
StringBuilder regexBuilder = new StringBuilder("^");
|
||||
String[] parts = wildcard.split("\\*", -1);
|
||||
for (int i = 0; i < parts.length; i++) {
|
||||
regexBuilder.append(Pattern.quote(parts[i]));
|
||||
if (i < parts.length - 1) {
|
||||
regexBuilder.append(".*");
|
||||
}
|
||||
}
|
||||
regexBuilder.append("$");
|
||||
return regexBuilder.toString();
|
||||
}
|
||||
|
||||
private boolean matchIpv4Cidr(String host, String cidr) {
|
||||
try {
|
||||
String[] parts = cidr.split("/");
|
||||
if (parts.length != 2) {
|
||||
return false;
|
||||
}
|
||||
Long hostInt = parseLiteralIpv4(host);
|
||||
Long networkInt = parseLiteralIpv4(parts[0]);
|
||||
int prefixLength = Integer.parseInt(parts[1]);
|
||||
|
||||
if (hostInt == null || networkInt == null || prefixLength < 0 || prefixLength > 32) {
|
||||
return false;
|
||||
}
|
||||
|
||||
long mask = prefixLength == 0 ? 0L : (0xFFFFFFFFL << (32 - prefixLength)) & 0xFFFFFFFFL;
|
||||
return (hostInt & mask) == (networkInt & mask);
|
||||
} catch (NumberFormatException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 仅解析字面量 IPv4 地址(不做 DNS 解析),防止 DNS rebinding/TOCTOU 风险。
|
||||
*/
|
||||
private Long parseLiteralIpv4(String input) {
|
||||
if (input == null || input.trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
String[] parts = input.trim().split("\\.");
|
||||
if (parts.length != 4) {
|
||||
return null;
|
||||
}
|
||||
long result = 0L;
|
||||
for (String part : parts) {
|
||||
if (part.isEmpty() || part.length() > 3) {
|
||||
return null;
|
||||
}
|
||||
int value;
|
||||
try {
|
||||
value = Integer.parseInt(part);
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
if (value < 0 || value > 255) {
|
||||
return null;
|
||||
}
|
||||
result = (result << 8) | value;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package cn.keking.web.filter;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import jakarta.servlet.*;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* @date 2023/11/30
|
||||
*/
|
||||
public class UrlCheckFilter implements Filter {
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
|
||||
|
||||
final HttpServletRequest httpServletRequest = (HttpServletRequest) request;
|
||||
String servletPath = httpServletRequest.getServletPath();
|
||||
|
||||
boolean redirect = false;
|
||||
|
||||
// servletPath 中不能包含 //
|
||||
if (servletPath.contains("//")) {
|
||||
servletPath = servletPath.replaceAll("//+", "/");
|
||||
redirect = true;
|
||||
}
|
||||
|
||||
// 不能以 / 结尾,同时考虑 **首页** 的特殊性
|
||||
if (servletPath.endsWith("/") && servletPath.length() > 1) {
|
||||
servletPath = servletPath.substring(0, servletPath.length() - 1);
|
||||
redirect = true;
|
||||
}
|
||||
if (redirect) {
|
||||
String redirectUrl;
|
||||
if (StringUtils.isBlank(BaseUrlFilter.getBaseUrl())) {
|
||||
// 正常 BaseUrlFilter 有限此 Filter 执行,不会执行到此
|
||||
redirectUrl = httpServletRequest.getContextPath() + servletPath;
|
||||
} else {
|
||||
if (BaseUrlFilter.getBaseUrl().endsWith("/") && servletPath.startsWith("/")) {
|
||||
// BaseUrlFilter.getBaseUrl() 以 / 结尾,servletPath 以 / 开头,需再去除一次 //
|
||||
redirectUrl = BaseUrlFilter.getBaseUrl() + servletPath.substring(1);
|
||||
} else {
|
||||
redirectUrl = BaseUrlFilter.getBaseUrl() + servletPath;
|
||||
}
|
||||
}
|
||||
((HttpServletResponse) response).sendRedirect(redirectUrl + "?" + httpServletRequest.getQueryString());
|
||||
} else {
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,9 @@
|
||||
| < | < | | | | | | | __/ \ / | | | __/ \ V V /
|
||||
|_|\_\ |_|\_\ |_| |_| |_| \___| \/ |_| \___| \_/\_/
|
||||
|
||||
=> Java Version :: ${java.version}
|
||||
=> Spring Boot :: ${spring-boot.version}
|
||||
=> kkFileView :: 4.3.0
|
||||
=> kkFileView :: 4.4.0
|
||||
=> Home site :: https://kkview.cn
|
||||
=> Github :: https://github.com/kekingcn/kkFileView
|
||||
=> Gitee :: https://gitee.com/kekingcn/file-online-preview
|
||||
|
||||