Compare commits
83 Commits
v4.4.0
...
paseo/ai-d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c6691d785 | ||
|
|
36ae290cb6 | ||
|
|
597715ce33 | ||
|
|
a8a08c1dcc | ||
|
|
7757729efd | ||
|
|
b246bfdac7 | ||
|
|
d35393ba22 | ||
|
|
c893dd7095 | ||
|
|
9bdb18d833 | ||
|
|
58fc1af74f | ||
|
|
1b3cf33bf0 | ||
|
|
c9005d0c04 | ||
|
|
37bda20d08 | ||
|
|
1819861647 | ||
|
|
352b86b40d | ||
|
|
853ad0154f | ||
|
|
c88bf04a0d | ||
|
|
6a84e61ecb | ||
|
|
dd6e369e6a | ||
|
|
bd20546b6d | ||
|
|
c41c14bf3c | ||
|
|
3fbf0156e2 | ||
|
|
530304b832 | ||
|
|
5499150395 | ||
|
|
2dcc62f3da | ||
|
|
405a12ef07 | ||
|
|
bb0734b3d3 | ||
|
|
a9e394950f | ||
|
|
be6023701c | ||
|
|
02bcd35779 | ||
|
|
b6dd8129ea | ||
|
|
db5cd68a1e | ||
|
|
d1c310ab63 | ||
|
|
b10e14899d | ||
|
|
eee3a2ed38 | ||
|
|
68d4d23a4b | ||
|
|
bb457924cd | ||
|
|
a0d78c57e3 | ||
|
|
7f16243270 | ||
|
|
36a75e86ac | ||
|
|
7c41200028 | ||
|
|
3da0c523e8 | ||
|
|
8c3bc81e08 | ||
|
|
9d72706c2d | ||
|
|
0aa8de27d4 | ||
|
|
b08f4657e5 | ||
|
|
1e04822532 | ||
|
|
4e5e9b7cba | ||
|
|
a2fe4658c4 | ||
|
|
e43f10138f | ||
|
|
7dc0469b30 | ||
|
|
904af89190 | ||
|
|
2425bea9b6 | ||
|
|
b5579ae890 | ||
|
|
595f3f55f2 | ||
|
|
c96b7ebd1e | ||
|
|
9a3ec88390 | ||
|
|
ea35da6694 | ||
|
|
00a8e094db | ||
|
|
58d0b24b16 | ||
|
|
b20637652a | ||
|
|
e1e146cb5f | ||
|
|
e52f374252 | ||
|
|
daab5963bc | ||
|
|
38a9c142e2 | ||
|
|
4afe1caa33 | ||
|
|
2dd008532a | ||
|
|
92ca92bee6 | ||
|
|
64c82a2406 | ||
|
|
e44ef813a1 | ||
|
|
9f3b45a4c7 | ||
|
|
b1af0c7d72 | ||
|
|
421640221b | ||
|
|
f6c6e22b0d | ||
|
|
51653483b9 | ||
|
|
a9787b0add | ||
|
|
6cdbf92fb0 | ||
|
|
fdb40680d3 | ||
|
|
6746325bf2 | ||
|
|
05a8bff1e0 | ||
|
|
874ff5b3f6 | ||
|
|
2230cfa52b | ||
|
|
83c581364d |
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
|
||||
117
.github/scripts/deploy_windows_winrm.py
vendored
Normal file
@@ -0,0 +1,117 @@
|
||||
#!/usr/bin/env python3
|
||||
import base64
|
||||
import os
|
||||
import pathlib
|
||||
import sys
|
||||
import uuid
|
||||
|
||||
import winrm
|
||||
|
||||
|
||||
def require_env(name: str) -> str:
|
||||
value = os.getenv(name, "").strip()
|
||||
if not value:
|
||||
raise SystemExit(f"Missing required environment variable: {name}")
|
||||
return value
|
||||
|
||||
|
||||
def optional_env(name: str, default: str) -> str:
|
||||
value = os.getenv(name, "").strip()
|
||||
return value if value else default
|
||||
|
||||
|
||||
def ps_quote(value: str) -> str:
|
||||
return value.replace("'", "''")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
host = require_env("KK_DEPLOY_HOST")
|
||||
port = optional_env("KK_DEPLOY_PORT", "5985")
|
||||
username = require_env("KK_DEPLOY_USERNAME")
|
||||
password = require_env("KK_DEPLOY_PASSWORD")
|
||||
env_pairs = {
|
||||
"KK_DEPLOY_ROOT": optional_env("KK_DEPLOY_ROOT", r"C:\kkFileView-5.0"),
|
||||
"KK_DEPLOY_HEALTH_URL": optional_env("KK_DEPLOY_HEALTH_URL", "http://127.0.0.1:8012/"),
|
||||
"KK_DEPLOY_REPO_URL": optional_env("KK_DEPLOY_REPO_URL", "https://github.com/kekingcn/kkFileView.git"),
|
||||
"KK_DEPLOY_BRANCH": optional_env("KK_DEPLOY_BRANCH", "master"),
|
||||
"KK_DEPLOY_SOURCE_ROOT": optional_env("KK_DEPLOY_SOURCE_ROOT", r"C:\kkFileView-source"),
|
||||
"KK_DEPLOY_JAVA_HOME": optional_env("KK_DEPLOY_JAVA_HOME", r"C:\Program Files\jdk-21.0.2"),
|
||||
"KK_DEPLOY_GIT_EXE": optional_env("KK_DEPLOY_GIT_EXE", r"C:\kkFileView-tools\git\cmd\git.exe"),
|
||||
"KK_DEPLOY_MVN_CMD": optional_env("KK_DEPLOY_MVN_CMD", r"C:\kkFileView-tools\maven\bin\mvn.cmd"),
|
||||
"KK_DEPLOY_MAVEN_SETTINGS": optional_env("KK_DEPLOY_MAVEN_SETTINGS", ""),
|
||||
"KK_DEPLOY_DRY_RUN": optional_env("KK_DEPLOY_DRY_RUN", "false").lower(),
|
||||
}
|
||||
|
||||
script_path = pathlib.Path(__file__).with_name("remote_windows_deploy.ps1")
|
||||
script_body = script_path.read_text(encoding="utf-8")
|
||||
payload = script_body.encode("utf-8-sig")
|
||||
payload_b64 = base64.b64encode(payload).decode("ascii")
|
||||
|
||||
endpoint = f"http://{host}:{port}/wsman"
|
||||
session = winrm.Session(endpoint, auth=(username, password), transport="ntlm")
|
||||
|
||||
suffix = uuid.uuid4().hex
|
||||
remote_b64_path = fr"C:\Windows\Temp\kkfileview_deploy_{suffix}.b64"
|
||||
remote_ps1_path = fr"C:\Windows\Temp\kkfileview_deploy_{suffix}.ps1"
|
||||
|
||||
prep = session.run_ps(
|
||||
f"""
|
||||
$ErrorActionPreference = 'Stop'
|
||||
if (Test-Path '{ps_quote(remote_b64_path)}') {{ Remove-Item '{ps_quote(remote_b64_path)}' -Force }}
|
||||
if (Test-Path '{ps_quote(remote_ps1_path)}') {{ Remove-Item '{ps_quote(remote_ps1_path)}' -Force }}
|
||||
New-Item -ItemType File -Path '{ps_quote(remote_b64_path)}' -Force | Out-Null
|
||||
"""
|
||||
)
|
||||
if prep.status_code != 0:
|
||||
sys.stderr.write(prep.std_err.decode("utf-8", errors="ignore"))
|
||||
return prep.status_code
|
||||
|
||||
chunk_size = 1200
|
||||
for start in range(0, len(payload_b64), chunk_size):
|
||||
chunk = payload_b64[start : start + chunk_size]
|
||||
append = session.run_ps(
|
||||
f"Add-Content -LiteralPath '{ps_quote(remote_b64_path)}' -Value '{chunk}'"
|
||||
)
|
||||
if append.status_code != 0:
|
||||
sys.stderr.write(append.std_err.decode("utf-8", errors="ignore"))
|
||||
return append.status_code
|
||||
|
||||
result = session.run_ps(
|
||||
f"""
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$raw = Get-Content -LiteralPath '{ps_quote(remote_b64_path)}' -Raw
|
||||
[System.IO.File]::WriteAllBytes('{ps_quote(remote_ps1_path)}', [Convert]::FromBase64String($raw))
|
||||
try {{
|
||||
"""
|
||||
+ "\n".join(
|
||||
f" $env:{key} = '{ps_quote(value)}'" for key, value in env_pairs.items()
|
||||
)
|
||||
+ f"""
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File '{ps_quote(remote_ps1_path)}' `
|
||||
$code = $LASTEXITCODE
|
||||
}} finally {{
|
||||
"""
|
||||
+ "\n".join(
|
||||
f" Remove-Item Env:{key} -ErrorAction SilentlyContinue" for key in env_pairs
|
||||
)
|
||||
+ f"""
|
||||
Remove-Item '{ps_quote(remote_b64_path)}' -Force -ErrorAction SilentlyContinue
|
||||
Remove-Item '{ps_quote(remote_ps1_path)}' -Force -ErrorAction SilentlyContinue
|
||||
}}
|
||||
exit $code
|
||||
"""
|
||||
)
|
||||
|
||||
stdout = result.std_out.decode("utf-8", errors="ignore").strip()
|
||||
stderr = result.std_err.decode("utf-8", errors="ignore").strip()
|
||||
|
||||
if stdout:
|
||||
print(stdout)
|
||||
if stderr:
|
||||
print(stderr, file=sys.stderr)
|
||||
|
||||
return result.status_code
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
327
.github/scripts/remote_windows_deploy.ps1
vendored
Normal file
@@ -0,0 +1,327 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
|
||||
function Write-Step {
|
||||
param([string]$Message)
|
||||
Write-Host "==> $Message"
|
||||
}
|
||||
|
||||
function Get-RequiredEnv {
|
||||
param([string]$Name)
|
||||
|
||||
$Value = [Environment]::GetEnvironmentVariable($Name)
|
||||
if ([string]::IsNullOrWhiteSpace($Value)) {
|
||||
throw "Missing required environment variable: $Name"
|
||||
}
|
||||
|
||||
return $Value
|
||||
}
|
||||
|
||||
function Get-OptionalEnv {
|
||||
param(
|
||||
[string]$Name,
|
||||
[string]$DefaultValue
|
||||
)
|
||||
|
||||
$Value = [Environment]::GetEnvironmentVariable($Name)
|
||||
if ([string]::IsNullOrWhiteSpace($Value)) {
|
||||
return $DefaultValue
|
||||
}
|
||||
|
||||
return $Value
|
||||
}
|
||||
|
||||
$DeployRoot = Get-OptionalEnv 'KK_DEPLOY_ROOT' 'C:\kkFileView-5.0'
|
||||
$HealthUrl = Get-OptionalEnv 'KK_DEPLOY_HEALTH_URL' 'http://127.0.0.1:8012/'
|
||||
$RepoUrl = Get-OptionalEnv 'KK_DEPLOY_REPO_URL' 'https://github.com/kekingcn/kkFileView.git'
|
||||
$Branch = Get-OptionalEnv 'KK_DEPLOY_BRANCH' 'master'
|
||||
$SourceRoot = Get-OptionalEnv 'KK_DEPLOY_SOURCE_ROOT' 'C:\kkFileView-source'
|
||||
$JavaHome = Get-OptionalEnv 'KK_DEPLOY_JAVA_HOME' 'C:\Program Files\jdk-21.0.2'
|
||||
$GitExe = Get-OptionalEnv 'KK_DEPLOY_GIT_EXE' 'C:\kkFileView-tools\git\cmd\git.exe'
|
||||
$MvnCmd = Get-OptionalEnv 'KK_DEPLOY_MVN_CMD' 'C:\kkFileView-tools\maven\bin\mvn.cmd'
|
||||
$MavenSettings = Get-OptionalEnv 'KK_DEPLOY_MAVEN_SETTINGS' ''
|
||||
$DryRun = Get-OptionalEnv 'KK_DEPLOY_DRY_RUN' 'false'
|
||||
|
||||
$BinDir = Join-Path $DeployRoot 'bin'
|
||||
$StartupScript = Join-Path $BinDir 'startup.bat'
|
||||
$ReleaseDir = Join-Path $DeployRoot 'releases'
|
||||
$DeployTmp = Join-Path $DeployRoot 'deploy-tmp'
|
||||
$BuildOutputDir = Join-Path (Join-Path $SourceRoot 'server') 'target'
|
||||
|
||||
if (-not (Test-Path $DeployRoot)) {
|
||||
throw "Deploy root not found: $DeployRoot"
|
||||
}
|
||||
|
||||
if (-not (Test-Path $BinDir)) {
|
||||
throw "Bin directory not found: $BinDir"
|
||||
}
|
||||
|
||||
if (-not (Test-Path $StartupScript)) {
|
||||
throw "Startup script not found: $StartupScript"
|
||||
}
|
||||
|
||||
$CurrentJar = Get-ChildItem $BinDir -Filter 'kkFileView-*.jar' | Sort-Object LastWriteTime -Descending | Select-Object -First 1
|
||||
if (-not $CurrentJar) {
|
||||
throw "No kkFileView jar found in $BinDir"
|
||||
}
|
||||
|
||||
$JavaExe = Join-Path $JavaHome 'bin\java.exe'
|
||||
if (-not (Test-Path $JavaExe)) {
|
||||
throw "JDK 21 java executable not found: $JavaExe"
|
||||
}
|
||||
|
||||
if (-not (Test-Path $GitExe)) {
|
||||
throw "Git executable not found: $GitExe"
|
||||
}
|
||||
|
||||
if (-not (Test-Path $MvnCmd)) {
|
||||
throw "Maven executable not found: $MvnCmd"
|
||||
}
|
||||
|
||||
if (-not [string]::IsNullOrWhiteSpace($MavenSettings) -and -not (Test-Path $MavenSettings)) {
|
||||
throw "Maven settings file not found: $MavenSettings"
|
||||
}
|
||||
|
||||
$JarName = $CurrentJar.Name
|
||||
$JarPath = $CurrentJar.FullName
|
||||
|
||||
Write-Step "Deploy root: $DeployRoot"
|
||||
Write-Step "Current jar: $JarPath"
|
||||
Write-Step "Startup script: $StartupScript"
|
||||
Write-Step "Health url: $HealthUrl"
|
||||
Write-Step "Source root: $SourceRoot"
|
||||
Write-Step "Branch: $Branch"
|
||||
Write-Step "Git exe: $GitExe"
|
||||
Write-Step "Maven cmd: $MvnCmd"
|
||||
Write-Step "Java home: $JavaHome"
|
||||
if (-not [string]::IsNullOrWhiteSpace($MavenSettings)) {
|
||||
Write-Step "Maven settings: $MavenSettings"
|
||||
}
|
||||
|
||||
function Invoke-External {
|
||||
param(
|
||||
[string]$FilePath,
|
||||
[string[]]$Arguments,
|
||||
[string]$WorkingDirectory = $null
|
||||
)
|
||||
|
||||
$previous = $null
|
||||
if ($WorkingDirectory) {
|
||||
$previous = Get-Location
|
||||
Set-Location $WorkingDirectory
|
||||
}
|
||||
|
||||
try {
|
||||
& $FilePath @Arguments
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Command failed ($LASTEXITCODE): $FilePath $($Arguments -join ' ')"
|
||||
}
|
||||
} finally {
|
||||
if ($previous) {
|
||||
Set-Location $previous
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Assert-SafeSourceRoot {
|
||||
param([string]$PathToCheck)
|
||||
|
||||
$FullPath = [System.IO.Path]::GetFullPath($PathToCheck)
|
||||
$RootPath = [System.IO.Path]::GetPathRoot($FullPath)
|
||||
if ($FullPath.TrimEnd('\') -eq $RootPath.TrimEnd('\')) {
|
||||
throw "Refusing to use drive root as source root: $FullPath"
|
||||
}
|
||||
|
||||
$DangerousLeafNames = @(
|
||||
'Windows',
|
||||
'Users',
|
||||
'Program Files',
|
||||
'Program Files (x86)',
|
||||
'ProgramData'
|
||||
)
|
||||
$LeafName = Split-Path -Leaf $FullPath.TrimEnd('\')
|
||||
if ($DangerousLeafNames -contains $LeafName) {
|
||||
throw "Refusing to use a high-risk source root path: $FullPath"
|
||||
}
|
||||
}
|
||||
|
||||
$env:JAVA_HOME = $JavaHome
|
||||
$env:Path = (Join-Path $JavaHome 'bin') + ';' + (Split-Path -Parent $GitExe) + ';' + (Split-Path -Parent $MvnCmd) + ';' + $env:Path
|
||||
|
||||
Write-Step 'Validating Git executable'
|
||||
Invoke-External -FilePath $GitExe -Arguments @('--version')
|
||||
|
||||
Write-Step 'Validating Maven executable'
|
||||
$MavenVersionArgs = @('-version')
|
||||
if (-not [string]::IsNullOrWhiteSpace($MavenSettings)) {
|
||||
$MavenVersionArgs = @('-s', $MavenSettings, '-version')
|
||||
}
|
||||
Invoke-External -FilePath $MvnCmd -Arguments $MavenVersionArgs
|
||||
|
||||
if ($DryRun -eq 'true') {
|
||||
Write-Step "Dry run enabled, remote validation finished"
|
||||
return
|
||||
}
|
||||
|
||||
New-Item -ItemType Directory -Force -Path $ReleaseDir | Out-Null
|
||||
New-Item -ItemType Directory -Force -Path $DeployTmp | Out-Null
|
||||
|
||||
function Sync-Repository {
|
||||
Assert-SafeSourceRoot -PathToCheck $SourceRoot
|
||||
|
||||
if (-not (Test-Path (Join-Path $SourceRoot '.git'))) {
|
||||
if (Test-Path $SourceRoot) {
|
||||
Remove-Item $SourceRoot -Recurse -Force
|
||||
}
|
||||
|
||||
$parent = Split-Path -Parent $SourceRoot
|
||||
if ($parent) {
|
||||
New-Item -ItemType Directory -Force -Path $parent | Out-Null
|
||||
}
|
||||
|
||||
Write-Step "Cloning repository from $RepoUrl"
|
||||
Invoke-External -FilePath $GitExe -Arguments @('clone', '--depth', '1', '--branch', $Branch, '--single-branch', $RepoUrl, $SourceRoot)
|
||||
return
|
||||
}
|
||||
|
||||
Write-Step "Fetching latest branch state from origin/$Branch"
|
||||
Invoke-External -FilePath $GitExe -Arguments @('remote', 'set-url', 'origin', $RepoUrl) -WorkingDirectory $SourceRoot
|
||||
Invoke-External -FilePath $GitExe -Arguments @('fetch', '--prune', '--depth', '1', 'origin', $Branch) -WorkingDirectory $SourceRoot
|
||||
Invoke-External -FilePath $GitExe -Arguments @('checkout', '-B', $Branch, "origin/$Branch") -WorkingDirectory $SourceRoot
|
||||
Invoke-External -FilePath $GitExe -Arguments @('reset', '--hard', "origin/$Branch") -WorkingDirectory $SourceRoot
|
||||
Invoke-External -FilePath $GitExe -Arguments @('clean', '-fd') -WorkingDirectory $SourceRoot
|
||||
}
|
||||
|
||||
function Build-KkFileView {
|
||||
Write-Step 'Building kkFileView from source'
|
||||
$BuildArgs = @('-B', 'clean', 'package', '-Dmaven.test.skip=true', '--file', 'pom.xml')
|
||||
if (-not [string]::IsNullOrWhiteSpace($MavenSettings)) {
|
||||
$BuildArgs = @('-s', $MavenSettings) + $BuildArgs
|
||||
}
|
||||
Invoke-External -FilePath $MvnCmd -Arguments $BuildArgs -WorkingDirectory $SourceRoot
|
||||
}
|
||||
|
||||
Sync-Repository
|
||||
Build-KkFileView
|
||||
|
||||
$DownloadedJars = Get-ChildItem $BuildOutputDir -Filter 'kkFileView-*.jar' -File
|
||||
if (-not $DownloadedJars) {
|
||||
throw "No kkFileView jar found in build output: $BuildOutputDir"
|
||||
}
|
||||
|
||||
if ($DownloadedJars.Count -ne 1) {
|
||||
throw "Expected exactly one kkFileView jar in build output, found $($DownloadedJars.Count)"
|
||||
}
|
||||
|
||||
$DownloadedJar = $DownloadedJars[0]
|
||||
|
||||
$Timestamp = Get-Date -Format 'yyyyMMddHHmmss'
|
||||
$BackupJar = Join-Path $ReleaseDir ("{0}.{1}.bak" -f $JarName, $Timestamp)
|
||||
|
||||
function Stop-KkFileView {
|
||||
foreach ($Process in @(Get-KkFileViewJavaProcesses) + @(Get-KkFileViewLauncherProcesses)) {
|
||||
Write-Step "Stopping process $($Process.ProcessId)"
|
||||
Stop-Process -Id $Process.ProcessId -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
function Get-KkFileViewJavaProcesses {
|
||||
$JarPattern = [regex]::Escape($JarName)
|
||||
return Get-CimInstance Win32_Process | Where-Object {
|
||||
$_.Name -match '^java(\.exe)?$' -and $_.CommandLine -and $_.CommandLine -match $JarPattern
|
||||
}
|
||||
}
|
||||
|
||||
function Get-KkFileViewLauncherProcesses {
|
||||
$StartupPattern = [regex]::Escape([System.IO.Path]::GetFileName($StartupScript))
|
||||
return Get-CimInstance Win32_Process | Where-Object {
|
||||
$_.Name -ieq 'cmd.exe' -and $_.CommandLine -and $_.CommandLine -match $StartupPattern
|
||||
}
|
||||
}
|
||||
|
||||
function Wait-KkFileViewStopped {
|
||||
param([int]$TimeoutSeconds = 30)
|
||||
|
||||
for ($i = 0; $i -lt $TimeoutSeconds; $i++) {
|
||||
$JavaProcesses = @(Get-KkFileViewJavaProcesses)
|
||||
$CmdProcesses = @(Get-KkFileViewLauncherProcesses)
|
||||
if ((@($JavaProcesses).Count + @($CmdProcesses).Count) -eq 0) {
|
||||
return $true
|
||||
}
|
||||
|
||||
Start-Sleep -Seconds 1
|
||||
}
|
||||
|
||||
return $false
|
||||
}
|
||||
|
||||
function Start-KkFileView {
|
||||
Write-Step "Starting kkFileView"
|
||||
$CreateResult = Invoke-CimMethod -ClassName Win32_Process -MethodName Create -Arguments @{
|
||||
CommandLine = ('cmd.exe /c ""' + $StartupScript + '""')
|
||||
CurrentDirectory = $BinDir
|
||||
}
|
||||
|
||||
if ($CreateResult.ReturnValue -ne 0) {
|
||||
throw "Failed to start kkFileView launcher, Win32_Process.Create returned $($CreateResult.ReturnValue)"
|
||||
}
|
||||
|
||||
Write-Step "Launcher process created with pid $($CreateResult.ProcessId)"
|
||||
}
|
||||
|
||||
function Wait-Health {
|
||||
param([string]$Url)
|
||||
|
||||
$SuccessfulChecks = 0
|
||||
for ($i = 0; $i -lt 24; $i++) {
|
||||
Start-Sleep -Seconds 5
|
||||
try {
|
||||
$Response = Invoke-WebRequest -Uri $Url -UseBasicParsing -TimeoutSec 5
|
||||
if ($Response.StatusCode -eq 200 -and @(Get-KkFileViewJavaProcesses).Count -gt 0) {
|
||||
$SuccessfulChecks++
|
||||
} else {
|
||||
$SuccessfulChecks = 0
|
||||
}
|
||||
|
||||
if ($SuccessfulChecks -ge 3) {
|
||||
return $true
|
||||
}
|
||||
} catch {
|
||||
$SuccessfulChecks = 0
|
||||
Start-Sleep -Milliseconds 200
|
||||
}
|
||||
}
|
||||
|
||||
return $false
|
||||
}
|
||||
|
||||
Write-Step "Backing up current jar to $BackupJar"
|
||||
Copy-Item $JarPath $BackupJar -Force
|
||||
|
||||
Stop-KkFileView
|
||||
if (-not (Wait-KkFileViewStopped)) {
|
||||
throw "Timed out waiting for the previous kkFileView process to exit"
|
||||
}
|
||||
|
||||
Write-Step "Replacing jar with artifact output"
|
||||
Copy-Item $DownloadedJar.FullName $JarPath -Force
|
||||
|
||||
Start-KkFileView
|
||||
|
||||
if (-not (Wait-Health -Url $HealthUrl)) {
|
||||
Write-Step "Health check failed, rolling back"
|
||||
Stop-KkFileView
|
||||
if (-not (Wait-KkFileViewStopped)) {
|
||||
throw "Timed out waiting for the failed kkFileView process to exit during rollback"
|
||||
}
|
||||
Copy-Item $BackupJar $JarPath -Force
|
||||
Start-KkFileView
|
||||
|
||||
if (-not (Wait-Health -Url $HealthUrl)) {
|
||||
throw "Deployment failed and rollback health check also failed"
|
||||
}
|
||||
|
||||
throw "Deployment failed, rollback completed successfully"
|
||||
}
|
||||
|
||||
Write-Step "Deployment completed successfully"
|
||||
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}`);
|
||||
52
.github/workflows/master-auto-deploy.yml
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
name: Master Auto Deploy
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: master-auto-deploy-production
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
deploy-windows:
|
||||
runs-on: ubuntu-22.04
|
||||
env:
|
||||
KK_DEPLOY_HOST: ${{ secrets.KK_DEPLOY_HOST }}
|
||||
KK_DEPLOY_PORT: ${{ secrets.KK_DEPLOY_PORT }}
|
||||
KK_DEPLOY_USERNAME: ${{ secrets.KK_DEPLOY_USERNAME }}
|
||||
KK_DEPLOY_PASSWORD: ${{ secrets.KK_DEPLOY_PASSWORD }}
|
||||
KK_DEPLOY_ROOT: ${{ secrets.KK_DEPLOY_ROOT }}
|
||||
KK_DEPLOY_HEALTH_URL: ${{ secrets.KK_DEPLOY_HEALTH_URL }}
|
||||
KK_DEPLOY_REPO_URL: ${{ vars.KK_DEPLOY_REPO_URL }}
|
||||
KK_DEPLOY_BRANCH: ${{ vars.KK_DEPLOY_BRANCH }}
|
||||
KK_DEPLOY_SOURCE_ROOT: ${{ vars.KK_DEPLOY_SOURCE_ROOT }}
|
||||
KK_DEPLOY_JAVA_HOME: ${{ vars.KK_DEPLOY_JAVA_HOME }}
|
||||
KK_DEPLOY_GIT_EXE: ${{ vars.KK_DEPLOY_GIT_EXE }}
|
||||
KK_DEPLOY_MVN_CMD: ${{ vars.KK_DEPLOY_MVN_CMD }}
|
||||
KK_DEPLOY_MAVEN_SETTINGS: ${{ vars.KK_DEPLOY_MAVEN_SETTINGS }}
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Install WinRM dependencies
|
||||
run: pip install pywinrm
|
||||
|
||||
- name: Validate deploy secrets
|
||||
run: |
|
||||
test -n "$KK_DEPLOY_HOST" || (echo "Missing secret: KK_DEPLOY_HOST" && exit 1)
|
||||
test -n "$KK_DEPLOY_USERNAME" || (echo "Missing secret: KK_DEPLOY_USERNAME" && exit 1)
|
||||
test -n "$KK_DEPLOY_PASSWORD" || (echo "Missing secret: KK_DEPLOY_PASSWORD" && exit 1)
|
||||
|
||||
- name: Deploy to Windows server
|
||||
run: python .github/scripts/deploy_windows_winrm.py
|
||||
42
.github/workflows/maven.yml
vendored
@@ -12,13 +12,41 @@ on:
|
||||
jobs:
|
||||
build:
|
||||
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 + archive tools
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libreoffice zip p7zip-full
|
||||
|
||||
- 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 + archive tools
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libreoffice zip p7zip-full
|
||||
|
||||
- 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
.gitignore
vendored
@@ -26,6 +26,7 @@ nbdist/
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
.DS_Store
|
||||
.artifacts/
|
||||
|
||||
server/src/main/cache/
|
||||
server/src/main/file/
|
||||
|
||||
230
AGENTS.md
Normal file
@@ -0,0 +1,230 @@
|
||||
# AGENTS.md
|
||||
|
||||
This document is for coding agents and automation tools working in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
- Project: `kkFileView`
|
||||
- Stack: Spring Boot + Freemarker + Redis/Redisson (optional) + JODConverter + front-end preview pages
|
||||
- Main module: `server`
|
||||
- Default local URL: `http://127.0.0.1:8012/`
|
||||
- Production demo: `https://file.kkview.cn/`
|
||||
|
||||
This repository is a document preview service. Most user-facing work falls into one of these areas:
|
||||
|
||||
1. preview routing and file-type dispatch
|
||||
2. conversion pipelines for Office / PDF / CAD / archives / images
|
||||
3. Freemarker preview templates under `server/src/main/resources/web`
|
||||
4. CI, E2E fixtures, and production deployment automation
|
||||
|
||||
## Repository Layout
|
||||
|
||||
- `server/`
|
||||
Main application code, templates, config, packaged artifacts
|
||||
- `server/src/main/java/cn/keking/`
|
||||
Core Java application code
|
||||
- `server/src/main/resources/web/`
|
||||
Freemarker preview templates
|
||||
- `server/src/main/resources/static/`
|
||||
Front-end static assets used by preview pages
|
||||
- `server/src/main/config/`
|
||||
Main runtime config files
|
||||
- `server/src/main/bin/`
|
||||
Local startup/dev scripts
|
||||
- `tests/e2e/`
|
||||
Playwright-based end-to-end tests and fixtures
|
||||
- `.github/workflows/`
|
||||
CI and deployment workflows
|
||||
- `.github/scripts/`
|
||||
Windows production deployment scripts over WinRM
|
||||
|
||||
## Key Entry Points
|
||||
|
||||
- App entry:
|
||||
- `server/src/main/java/cn/keking/ServerMain.java`
|
||||
- Preview controller:
|
||||
- `server/src/main/java/cn/keking/web/controller/OnlinePreviewController.java`
|
||||
- File attribute parsing / request handling:
|
||||
- `server/src/main/java/cn/keking/service/FileHandlerService.java`
|
||||
- Office preview flow:
|
||||
- `server/src/main/java/cn/keking/service/impl/OfficeFilePreviewImpl.java`
|
||||
- PDF preview flow:
|
||||
- `server/src/main/java/cn/keking/service/impl/PdfFilePreviewImpl.java`
|
||||
- Archive extraction:
|
||||
- `server/src/main/java/cn/keking/service/CompressFileReader.java`
|
||||
|
||||
## Important Templates
|
||||
|
||||
- `server/src/main/resources/web/compress.ftl`
|
||||
Archive directory/tree preview page
|
||||
- `server/src/main/resources/web/pdf.ftl`
|
||||
PDF preview container page
|
||||
- `server/src/main/resources/web/picture.ftl`
|
||||
Single image preview page
|
||||
- `server/src/main/resources/web/officePicture.ftl`
|
||||
Office/PDF image-mode preview page
|
||||
- `server/src/main/resources/web/officeweb.ftl`
|
||||
Front-end xlsx/html preview page
|
||||
|
||||
When debugging UX issues, inspect the exact template selected by the preview flow first. Do not assume two similar preview pages share the same CSS or behavior.
|
||||
|
||||
## Local Development
|
||||
|
||||
### Recommended dev mode
|
||||
|
||||
Use:
|
||||
|
||||
```bash
|
||||
./server/src/main/bin/dev.sh
|
||||
```
|
||||
|
||||
This runs Spring Boot with resource hot reload using:
|
||||
|
||||
- `spring-boot:run`
|
||||
- `-Dspring-boot.run.addResources=true`
|
||||
- `server/src/main/config/application.properties`
|
||||
|
||||
For front-end template or CSS/JS edits, prefer `dev.sh` over rebuilding jars repeatedly.
|
||||
|
||||
### Jar build
|
||||
|
||||
```bash
|
||||
mvn -q -pl server -DskipTests package
|
||||
```
|
||||
|
||||
### Main test command used in CI
|
||||
|
||||
```bash
|
||||
mvn -B package -Dmaven.test.skip=true --file pom.xml
|
||||
```
|
||||
|
||||
## Configuration Notes
|
||||
|
||||
Primary runtime config used by the scripts and defaults committed in this repository:
|
||||
|
||||
- `server/src/main/config/application.properties`
|
||||
|
||||
Optional environment-specific config:
|
||||
|
||||
- `server/src/main/config/test.properties`
|
||||
|
||||
Be careful: the repository defaults point at `application.properties`. If a deployment environment explicitly starts the app with `test.properties`, treat that as an environment-specific override rather than the repository default. Always verify the actual startup command before assuming which config file is active.
|
||||
|
||||
Examples of config that commonly affects behavior:
|
||||
|
||||
- `office.preview.type`
|
||||
- `office.preview.switch.disabled`
|
||||
- `trust.host`
|
||||
- `not.trust.host`
|
||||
- `file.upload.disable`
|
||||
|
||||
## Preview Behavior Notes
|
||||
|
||||
- Office files can render in `pdf` mode or `image` mode.
|
||||
- PDF preview uses `pdf.ftl`.
|
||||
- Single images use `picture.ftl`.
|
||||
- Office image-mode previews use `officePicture.ftl`.
|
||||
- Archive previews are not simple file lists; they can load nested previews via the archive UI in `compress.ftl`.
|
||||
|
||||
When changing preview defaults, verify both:
|
||||
|
||||
1. server-side default config
|
||||
2. front-end mode-switch links/buttons
|
||||
|
||||
## Archive Preview Notes
|
||||
|
||||
Archive preview is a sensitive area because it combines:
|
||||
|
||||
- directory tree generation
|
||||
- extraction to disk
|
||||
- nested preview URL construction
|
||||
- inline iframe loading
|
||||
|
||||
If an archive-contained Office file gets stuck on loading:
|
||||
|
||||
1. verify the extracted file on disk is not corrupted
|
||||
2. verify conversion output exists
|
||||
3. verify the preview template points to the correct generated artifact
|
||||
4. verify the running Office manager / LibreOffice process is healthy
|
||||
|
||||
Do not assume “loading forever” is a front-end issue.
|
||||
|
||||
## Testing
|
||||
|
||||
### Targeted Java tests
|
||||
|
||||
Example targeted test:
|
||||
|
||||
```bash
|
||||
mvn -q -pl server -Dtest=PdfViewerCompatibilityTests test
|
||||
```
|
||||
|
||||
### E2E tests
|
||||
|
||||
See:
|
||||
|
||||
- `tests/e2e/README.md`
|
||||
|
||||
PR E2E currently covers:
|
||||
|
||||
- common preview smoke tests
|
||||
- Office smoke tests
|
||||
- archive smoke tests
|
||||
- basic security and performance checks
|
||||
|
||||
## CI / Deployment
|
||||
|
||||
### CI
|
||||
|
||||
- `maven.yml`
|
||||
- builds on `push` to `master`
|
||||
- builds on PRs targeting `master`
|
||||
- `pr-e2e-mvp.yml`
|
||||
- runs E2E on PRs to `master`
|
||||
|
||||
### Production deployment
|
||||
|
||||
- `master-auto-deploy.yml`
|
||||
- triggers on push to `master`
|
||||
- deploys to Windows over WinRM
|
||||
|
||||
Deployment script:
|
||||
|
||||
- `.github/scripts/remote_windows_deploy.ps1`
|
||||
|
||||
Important operational detail:
|
||||
|
||||
- the committed `bin/startup.bat` in this repo points at `..\config\application.properties`
|
||||
- if production uses a different config file, treat that as an out-of-repo server override rather than a repository default
|
||||
|
||||
If a production config change “does not take effect”, inspect the actual startup command or deployed `startup.bat` on the server first and verify which config file path it is using.
|
||||
|
||||
## Working Conventions For Agents
|
||||
|
||||
- Prefer minimal, targeted changes over wide refactors.
|
||||
- Inspect the active preview template before editing CSS.
|
||||
- Verify whether behavior is controlled by config, back-end routing, or front-end template logic before changing code.
|
||||
- For production/debug tasks, distinguish clearly between:
|
||||
- repository source defaults
|
||||
- deployed server config
|
||||
- runtime process arguments
|
||||
- When changing defaults, mention whether the change affects:
|
||||
- local dev only
|
||||
- repository default config
|
||||
- deployed server config
|
||||
- existing query-param overrides
|
||||
|
||||
## Suggested Validation Checklist
|
||||
|
||||
For preview-related changes, validate as many of these as apply:
|
||||
|
||||
1. target URL returns `200`
|
||||
2. selected template is the expected one
|
||||
3. generated intermediate artifacts exist when required
|
||||
4. target UI element or style change is actually present in rendered HTML
|
||||
5. targeted Java test passes
|
||||
6. relevant E2E path is still compatible
|
||||
|
||||
## Non-Goals
|
||||
|
||||
This file is not a replacement for user-facing product documentation. Keep it focused on helping coding agents navigate the codebase and make correct changes faster.
|
||||
143
README.cn.md
@@ -1,6 +1,6 @@
|
||||
# kkFileView
|
||||
文档在线预览项目解决方案,项目使用流行的spring boot搭建,易上手和部署。万能的文件预览开源项目,基本支持主流文档格式预览,如:
|
||||
1. 支持 doc, docx, xls, xlsx, xlsm, ppt, pptx, csv, tsv, dotm, xlt, xltm, dot, dotx,xlam, xla ,pages 等 Office 办公文档
|
||||
1. 支持 doc, docx, xls, xlsx, xlsm, ppt, pptx, csv, tsv, dotm, xlt, xltm, dot, dotx,xlam, xla ,pages ,pptm 等 Office 办公文档
|
||||
2. 支持 wps, dps, et, ett, wpt 等国产 WPS Office 办公文档
|
||||
3. 支持 odt, ods, ots, odp, otp, six, ott, fodt, fods 等OpenOffice、LibreOffice 办公文档
|
||||
4. 支持 vsd, vsdx 等 Visio 流程图文件
|
||||
@@ -9,18 +9,18 @@
|
||||
7. 支持 pdf ,ofd, rtf 等文档
|
||||
8. 支持 xmind 软件模型文件
|
||||
9. 支持 bpmn 工作流文件
|
||||
10. 支持 eml 邮件文件
|
||||
10. 支持 eml, msg 邮件文件
|
||||
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(渲染), xbrl(渲染), md(渲染), java, php, py, js, css 等所有纯文本
|
||||
15. 支持 zip, rar, jar, tar, gzip, 7z 等压缩包
|
||||
16. 支持 jpg, jpeg, png, gif, bmp, ico, jfif, webp 等图片预览(翻转,缩放,镜像)
|
||||
16. 支持 jpg, jpeg, png, gif, bmp, ico, jfif, webp ,heic ,heif等图片预览(翻转,缩放,镜像)
|
||||
17. 支持 tif, tiff 图信息模型文件
|
||||
18. 支持 tga 图像格式文件
|
||||
19. 支持 svg 矢量图像格式文件
|
||||
20. 支持 mp3,wav,mp4,flv 等音视频格式文件
|
||||
21. 支持 avi,mov,rm,webm,ts,rm,mkv,mpeg,ogg,mpg,rmvb,wmv,3gp,ts,swf 等视频格式转码预览
|
||||
21. 支持 avi,mov,rm,webm,ts,rm,mkv,mpeg,ogg,mpg,rmvb,wmv,3gp,ts 等视频格式转码预览
|
||||
22. 支持 dcm 等医疗数位影像预览
|
||||
23. 支持 drawio 绘图预览
|
||||
|
||||
@@ -53,80 +53,81 @@
|
||||
#### 1. 文本预览
|
||||
支持所有类型的文本文档预览, 由于文本文档类型过多,无法全部枚举,默认开启的类型如下 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流程图预览效果:
|
||||
|
||||

|
||||

|
||||
|
||||
考虑说明篇幅原因,就不贴其他格式文件的预览效果了,感兴趣的可以参考下面的实例搭建下
|
||||
|
||||
### 快速开始
|
||||
> 项目使用技术
|
||||
@@ -148,7 +149,97 @@ pdf预览模式预览效果如下
|
||||
|
||||
### 历史更新记录
|
||||
|
||||
#### > 2023年07月05日,v4.3 版本发布 :
|
||||
#### > 2026年01月20日,v5.0 版本发布 :
|
||||
#### 优化内容
|
||||
1. xlsx 前端解析优化 - 提升Excel文件前端渲染性能
|
||||
2. 图片解析优化 - 改进图片处理机制
|
||||
3. tif 解析优化 - 增强TIF格式支持
|
||||
4. svg 解析优化 - 优化SVG矢量图渲染
|
||||
5. json 解析优化 - 改进JSON文件处理
|
||||
6. ftp多客户端接入优化 - 提升FTP服务兼容性
|
||||
7. 首页目录访问优化 - 采用post服务端分页机制
|
||||
8. marked 解析优化 - 改进Markdown渲染
|
||||
|
||||
#### 新增功能
|
||||
1. msg邮件解析 - 新增msg格式邮件文件预览支持
|
||||
2. heic图片解析 - 新增HEIC格式图片预览支持
|
||||
3. 跨域方法 - 新增跨域处理机制
|
||||
4. 高亮方法 - 新增文本高亮功能
|
||||
5. 页码方法 - 新增文档页码控制
|
||||
6. AES加密方法 - 新增AES加密支持
|
||||
7. Basic鉴权方法 - 新增Basic认证机制
|
||||
8. 秘钥方法 - 新增密钥管理功能
|
||||
9. 防重复转换 - 新增重复文件转换防护
|
||||
10. 异步等待 - 新增异步处理机制
|
||||
11. 上传限制 - 新增不支持文件上传限制
|
||||
12. cadviewer转换方法 - 新增CAD查看器转换功能
|
||||
|
||||
#### 修复问题
|
||||
1. 压缩包路径问题 - 修复压缩包内部路径处理
|
||||
2. 安全问题 - 修复安全漏洞
|
||||
3. 图片水印不全问题 - 修复水印显示不完整
|
||||
4. SSL自签证书接入问题 - 修复自签名证书兼容性
|
||||
|
||||
#### 更新内容
|
||||
1. JDK版本要求 - 强制要求JDK 21及以上版本
|
||||
2. pdf前端解析更新 - 升级PDF前端渲染组件
|
||||
3. odf前端解析更新 - 升级ODF文档前端渲染
|
||||
4. 3D模型前端解析更新 - 升级3D模型查看器
|
||||
5. pdf后端异步转换优化 - 实现多线程异步转换
|
||||
6. tif后端异步转换优化 - 实现多线程异步转换
|
||||
7. 视频后端异步转换优化 - 实现多线程异步转换
|
||||
8. CAD后端异步转换优化 - 实现多线程异步转换
|
||||
|
||||
#### > 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等医疗数位影像预
|
||||
|
||||
174
README.md
@@ -4,7 +4,7 @@
|
||||
|
||||
Document online preview project solution, built using the popular Spring Boot framework for easy setup and deployment. This versatile open source project provides basic support for a wide range of document formats, including:
|
||||
|
||||
1. Supports Office documents such as `doc`, `docx`, `xls`, `xlsx`, `xlsm`, `ppt`, `pptx`, `csv`, `tsv`, , `dotm`, `xlt`, `xltm`, `dot`, `xlam`, `dotx`, `xla,` ,`pages` etc.
|
||||
1. Supports Office documents such as `doc`, `docx`, `xls`, `xlsx`, `xlsm`, `ppt`, `pptx`, `csv`, `tsv`, , `dotm`, `xlt`, `xltm`, `dot`, `xlam`, `dotx`, `xla,` ,`pages` ,`pptm` etc.
|
||||
2. Supports domestic WPS Office documents such as `wps`, `dps`, `et` , `ett`, ` wpt`.
|
||||
3. Supports OpenOffice, LibreOffice office documents such as `odt`, `ods`, `ots`, `odp`, `otp`, `six`, `ott`, `fodt` and `fods`.
|
||||
4. Supports Visio flowchart files such as `vsd`, `vsdx`.
|
||||
@@ -13,13 +13,13 @@ Document online preview project solution, built using the popular Spring Boot fr
|
||||
7. Supports document formats like `pdf`, `ofd`, and `rtf`.
|
||||
8. Supports software model files like `xmind`.
|
||||
9. Support for `bpmn` workflow files.
|
||||
10. Support for `eml` mail files
|
||||
10. Support for `eml` , `msg` mail files
|
||||
11. Support for `epub` book documents
|
||||
12. Supports 3D model files like `obj`, `3ds`, `stl`, `ply`, `gltf`, `glb`, `off`, `3dm`, `fbx`, `dae`, `wrl`, `3mf`, `ifc`, `brep`, `step`, `iges`, `fcstd`, `bim`, etc.
|
||||
13. Supports CAD model files such as `dwg`, `dxf`, `dwf` `iges` ,` igs`, `dwt` , `dng` , `ifc` , `dwfx` , `stl` , `cf2` , `plt`, etc.
|
||||
14. Supports all plain text files such as `txt`, `xml` (rendering), `md` (rendering), `java`, `php`, `py`, `js`, `css`, etc.
|
||||
15. Supports compressed packages such as `zip`, `rar`, `jar`, `tar`, `gzip`, `7z`, etc.
|
||||
16. Supports image previewing (flip, zoom, mirror) of `jpg`, `jpeg`, `png`, `gif`, `bmp`, `ico`, `jfif`, `webp`, etc.
|
||||
16. Supports image previewing (flip, zoom, mirror) of `jpg`, `jpeg`, `png`, `gif`, `bmp`, `ico`, `jfif`, `webp`, `heic`, ,`heif` etc.
|
||||
17. Supports image information model files such as `tif` and `tiff`.
|
||||
18. Supports image format files such as `tga`.
|
||||
19. Supports vector image format files such as `svg`.
|
||||
@@ -63,6 +63,174 @@ URL:[https://file.kkview.cn](https://file.kkview.cn)
|
||||
|
||||
2. second step:Run the main method of `/server/src/main/java/cn/keking/ServerMain.java`. After starting,visit `http://localhost:8012/`.
|
||||
|
||||
## Change History
|
||||
|
||||
### Version 5.0 (January 20, 2026)
|
||||
|
||||
#### Optimizations
|
||||
1. Enhanced xlsx front-end parsing - Improved Excel file front-end rendering performance
|
||||
2. Optimized image parsing - Enhanced image processing mechanism
|
||||
3. Improved tif parsing - Enhanced TIF format support
|
||||
4. Enhanced svg parsing - Optimized SVG vector image rendering
|
||||
5. Improved json parsing - Enhanced JSON file processing
|
||||
6. Optimized ftp multi-client access - Improved FTP service compatibility
|
||||
7. Enhanced home page directory access - Implemented post server-side pagination mechanism
|
||||
8. Improved marked parsing - Enhanced Markdown rendering
|
||||
|
||||
#### New Features
|
||||
1. msg email parsing - Added support for msg format email file preview
|
||||
2. heic image parsing - Added support for HEIC format image preview
|
||||
3. Cross-domain methods - Added cross-domain processing mechanism
|
||||
4. Highlighting methods - Added text highlighting functionality
|
||||
5. Pagination methods - Added document page control
|
||||
6. AES encryption methods - Added AES encryption support
|
||||
7. Basic authentication methods - Added Basic authentication mechanism
|
||||
8. Key management methods - Added key management functionality
|
||||
9. Anti-duplicate conversion - Added duplicate file conversion protection
|
||||
10. Async waiting - Added asynchronous processing mechanism
|
||||
11. Upload restrictions - Added restrictions for unsupported file uploads
|
||||
12. cadviewer conversion methods - Added CAD viewer conversion functionality
|
||||
|
||||
#### Fixed Issues
|
||||
1. Compressed file path issues - Fixed internal path handling in compressed files
|
||||
2. Security issues - Fixed security vulnerabilities
|
||||
3. Incomplete image watermark issues - Fixed incomplete watermark display
|
||||
4. SSL self-signed certificate access issues - Fixed compatibility with self-signed certificates
|
||||
|
||||
#### Updates
|
||||
1. JDK version requirement - Mandatory requirement for JDK 21 or higher
|
||||
2. pdf front-end parsing update - Upgraded PDF front-end rendering component
|
||||
3. odf front-end parsing update - Upgraded ODF document front-end rendering
|
||||
4. 3D model front-end parsing update - Upgraded 3D model viewer
|
||||
5. pdf backend async conversion optimization - Implemented multi-threaded asynchronous conversion
|
||||
6. tif backend async conversion optimization - Implemented multi-threaded asynchronous conversion
|
||||
7. Video backend async conversion optimization - Implemented multi-threaded asynchronous conversion
|
||||
8. CAD backend async conversion optimization - Implemented multi-threaded asynchronous conversion
|
||||
|
||||
### Version 4.4.0 (January 16, 2025)
|
||||
|
||||
#### New Features
|
||||
1. xlsx printing support
|
||||
2. Added GZIP compression enablement in configuration
|
||||
3. CAD format now supports conversion to SVG and TIF formats, added timeout termination and thread management
|
||||
4. Added captcha verification for file deletion
|
||||
5. Added xbrl format preview support
|
||||
6. PDF preview added control over signatures, drawings, illustration control, search positioning pagination, and display content definition
|
||||
7. Added CSV format front-end parsing support
|
||||
8. Added Docker image support for ARM64
|
||||
9. Added Office preview conversion timeout property setting
|
||||
10. Added preview file host blacklist mechanism
|
||||
|
||||
#### Optimizations
|
||||
1. Optimized OFD mobile preview page adaptability
|
||||
2. Updated xlsx front-end parsing component to accelerate parsing speed
|
||||
3. Upgraded CAD component
|
||||
4. Office function adjustments, supporting comments, conversion page limit, watermark generation, etc.
|
||||
5. Upgraded markdown component
|
||||
6. Upgraded dcm parsing component
|
||||
7. Upgraded PDF.JS parsing component
|
||||
8. Changed video player plugin to ckplayer
|
||||
9. Smarter tif parsing, supporting modified image formats
|
||||
10. Improved character encoding detection accuracy for large and small text files, handling concurrency vulnerabilities
|
||||
11. Refactored file download code, added general file server authentication access design
|
||||
12. Updated bootstrap component and streamlined unnecessary files
|
||||
13. Updated epub version, optimized epub display effect
|
||||
14. Fixed issue where scheduled cache cleanup only deleted disk cache files for multimedia file types
|
||||
15. Auto-detection of installed Office components, added default paths for LibreOffice 7.5 & 7.6 versions
|
||||
16. Changed drawio default to preview mode
|
||||
17. Added PDF thread management, timeout management, memory cache management, updated PDF parsing component version
|
||||
18. Optimized Dockerfile for true cross-platform image building
|
||||
|
||||
#### Fixes
|
||||
1. Fixed forceUpdatedCache property setting issue where local cache files weren't updated
|
||||
2. Fixed PDF decryption error after successful encrypted file conversion
|
||||
3. Fixed BPMN cross-domain support issue
|
||||
4. Fixed special character error in compressed package secondary reverse proxy
|
||||
5. Fixed video cross-domain configuration causing video preview failure
|
||||
6. Fixed TXT text pagination secondary loading issue
|
||||
7. Fixed Drawio missing Base64 component issue
|
||||
8. Fixed Markdown escaping issue
|
||||
9. Fixed EPUB cross-domain error
|
||||
10. Fixed URL special character issues
|
||||
11. Fixed compressed package traversal vulnerability
|
||||
12. Fixed compressed file path errors, image collection path errors, watermark issues, etc.
|
||||
13. Fixed front-end parsing XLSX containing EMF format file errors
|
||||
|
||||
### Version 4.3.0 (July 5, 2023)
|
||||
|
||||
#### New Features
|
||||
1. Added DCM medical digital imaging preview
|
||||
2. Added drawio drawing preview
|
||||
3. Added command to regenerate with cache enabled: &forceUpdatedCache=true
|
||||
4. Added dwg CAD file preview
|
||||
5. Added PDF file password support
|
||||
6. Added DPI customization for PDF file image generation
|
||||
7. Added configuration to delete converted OFFICE, CAD, TIFF, compressed package source files (enabled by default to save disk space)
|
||||
8. Added front-end xlsx parsing method
|
||||
9. Added support for pages, eps, iges, igs, dwt, dng, ifc, dwfx, stl, cf2, plt and other formats
|
||||
|
||||
#### Optimizations
|
||||
1. Modified generated PDF file names to include file extensions to prevent duplicate names
|
||||
2. Adjusted SQL file preview method
|
||||
3. Optimized OFD preview compatibility
|
||||
4. Beautified TXT text pagination box display
|
||||
5. Upgraded Linux/Docker built-in office to LibreOffice-7.5.3
|
||||
6. Upgraded Windows built-in office to LibreOffice-7.5.3 Portable
|
||||
7. Other functional optimizations
|
||||
|
||||
#### Fixes
|
||||
1. Fixed compressed package path errors in reverse proxy scenarios
|
||||
2. Fixed .click error when image preview URLs contain &
|
||||
3. Fixed known OFD preview issues
|
||||
4. Fixed page error when clicking on file directories (tree nodes) in compressed package preview
|
||||
5. Other known issue fixes
|
||||
|
||||
### Version 4.2.1 (April 18, 2023)
|
||||
|
||||
#### Change Log
|
||||
1. Fixed null pointer bug in dwg file preview
|
||||
|
||||
### Version 4.2.0 (April 13, 2023)
|
||||
|
||||
#### New Features
|
||||
1. Added SVG format file preview support
|
||||
2. Added encrypted Office file preview support
|
||||
3. Added encrypted zip, rar, and other compressed package file preview support
|
||||
4. Added xmind software model file preview support
|
||||
5. Added BPMN workflow model file preview support
|
||||
6. Added eml email file preview support
|
||||
7. Added EPUB e-book file preview support
|
||||
8. Added office document format support: dotm, ett, xlt, xltm, wpt, dot, xlam, xla, dotx, etc.
|
||||
9. Added 3D model file support: obj, 3ds, stl, ply, gltf, glb, off, 3dm, fbx, dae, wrl, 3mf, ifc, brep, step, iges, fcstd, bim, etc.
|
||||
10. Added configurable high-risk file upload restrictions (e.g., exe files)
|
||||
11. Added configurable site filing information
|
||||
12. Added password requirement for demo site file deletion
|
||||
|
||||
#### Optimizations
|
||||
1. Added caching for text document preview
|
||||
2. Beautified 404, 500 error pages
|
||||
3. Optimized invoice and other OFD file preview seal rendering compatibility
|
||||
4. Removed office-plugin module, using new jodconverter component
|
||||
5. Optimized Excel file preview effect
|
||||
6. Optimized CAD file preview effect
|
||||
7. Updated xstream, junrar, pdfbox, and other dependency versions
|
||||
8. Updated TIF to PDF conversion plugin, added conversion cache
|
||||
9. Optimized demo page UI deployment
|
||||
10. Compressed package file preview supports directories
|
||||
|
||||
#### Fixes
|
||||
1. Fixed XSS issues in some interfaces
|
||||
2. Fixed console printed demo address not following content-path configuration
|
||||
3. Fixed OFD file preview cross-domain issues
|
||||
4. Fixed internal self-signed certificate HTTPS URL file download issues
|
||||
5. Fixed special character file deletion issues
|
||||
6. Fixed OOM caused by unreclaimed memory in PDF to image conversion
|
||||
7. Fixed garbled preview for xlsx 7.4+ version files
|
||||
8. Fixed TrustHostFilter not intercepting cross-domain interfaces (security issue - upgrade required if using TrustHost)
|
||||
9. Fixed compressed package file preview filename garbled issue on Linux systems
|
||||
10. Fixed OFD file preview only displaying 10 pages
|
||||
|
||||
|
||||
### Changelog
|
||||
> December 14, 2022, version 4.1.0 released:
|
||||
|
||||
|
||||
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
|
||||
|
||||
---
|
||||
|
||||
**安全提示**:定期检查和更新信任主机列表,遵循最小权限原则。
|
||||
57
doc/ci-auto-deploy.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# kkFileView master 自动部署
|
||||
|
||||
当前线上 Windows 服务器的实际部署信息如下:
|
||||
|
||||
- 部署根目录:`C:\kkFileView-5.0`
|
||||
- 运行 jar:`C:\kkFileView-5.0\bin\kkFileView-5.0.jar`
|
||||
- 启动脚本:`C:\kkFileView-5.0\bin\startup.bat`
|
||||
- 运行配置:`C:\kkFileView-5.0\config\test.properties`
|
||||
- 健康检查地址:`http://127.0.0.1:8012/`
|
||||
|
||||
当前自动部署链路采用服务器拉最新源码并本机编译的方式:
|
||||
|
||||
1. 通过 WinRM 连接 Windows 服务器
|
||||
2. 在服务器上的源码目录执行 `git fetch/reset/clean`,同步到 `origin/$KK_DEPLOY_BRANCH`(默认 `master`)
|
||||
3. 使用服务器上的 JDK 21 和 Maven 执行 `mvn clean package -Dmaven.test.skip=true`
|
||||
4. 备份线上 jar,替换为新构建产物
|
||||
5. 使用现有 `startup.bat` 重启,并做健康检查
|
||||
6. 如果健康检查失败,则自动回滚旧 jar 并重新拉起
|
||||
|
||||
## 需要配置的 GitHub Secrets
|
||||
|
||||
- `KK_DEPLOY_HOST`
|
||||
- `KK_DEPLOY_USERNAME`
|
||||
- `KK_DEPLOY_PASSWORD`
|
||||
|
||||
以下部署参数当前由 workflow 从 GitHub Secrets 读取;如果未单独配置,则使用脚本默认值:
|
||||
|
||||
- `KK_DEPLOY_PORT=5985`
|
||||
- `KK_DEPLOY_ROOT=C:\kkFileView-5.0`
|
||||
- `KK_DEPLOY_HEALTH_URL=http://127.0.0.1:8012/`
|
||||
|
||||
下面这些非敏感参数可以通过 workflow env 或 GitHub Variables 覆盖;未配置时会使用默认值:
|
||||
- `KK_DEPLOY_REPO_URL=https://github.com/kekingcn/kkFileView.git`
|
||||
- `KK_DEPLOY_BRANCH=master`
|
||||
- `KK_DEPLOY_SOURCE_ROOT=C:\kkFileView-source`
|
||||
- `KK_DEPLOY_JAVA_HOME=C:\Program Files\jdk-21.0.2`
|
||||
- `KK_DEPLOY_GIT_EXE=C:\kkFileView-tools\git\cmd\git.exe`
|
||||
- `KK_DEPLOY_MVN_CMD=C:\kkFileView-tools\maven\bin\mvn.cmd`
|
||||
- `KK_DEPLOY_MAVEN_SETTINGS=`
|
||||
|
||||
如果服务器到 GitHub 的拉取速度不稳定,也可以把 `KK_DEPLOY_REPO_URL` 改成你自己的 Git 镜像地址。
|
||||
如果服务器访问 Maven Central 不稳定,也可以通过 `KK_DEPLOY_MAVEN_SETTINGS` 指向自定义 `settings.xml`,切换到就近镜像仓库。
|
||||
|
||||
## 服务器前置环境
|
||||
|
||||
服务器需要具备以下工具:
|
||||
|
||||
- Git for Windows(推荐安装在 `C:\kkFileView-tools\git`)
|
||||
- Apache Maven 3.9.x(推荐安装在 `C:\kkFileView-tools\maven`)
|
||||
- JDK 21(当前线上已存在:`C:\Program Files\jdk-21.0.2`)
|
||||
|
||||
## Workflow
|
||||
|
||||
新增 workflow:`.github/workflows/master-auto-deploy.yml`
|
||||
|
||||
- 触发条件:`push` 到 `master`,或手动 `workflow_dispatch`
|
||||
- 部署方式:WinRM + 服务器源码同步 + 服务器本机 Maven 编译 + jar 替换/回滚
|
||||
44
doc/e2e-improvement-checklist.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# E2E 完善清单(基于 PR342 回归经验)
|
||||
|
||||
## 背景
|
||||
本次手工回归已经验证了以下关键链路:
|
||||
- TXT
|
||||
- XLSX
|
||||
- ZIP
|
||||
- PDF
|
||||
- DOCX
|
||||
- MP4
|
||||
- CAD / DXF
|
||||
|
||||
但当前 GitHub CI 中,自动化 E2E 仅覆盖了其中一部分,且大多只断言 HTTP 200,没有校验最终预览效果。
|
||||
|
||||
## 本次落地目标
|
||||
|
||||
### 1. 补齐缺失的关键链路
|
||||
- [x] PDF 预览 smoke
|
||||
- [x] MP4 预览 smoke
|
||||
- [x] CAD / DXF 预览 smoke
|
||||
|
||||
### 2. 升级断言方式
|
||||
- [x] 不再只看 `status === 200`
|
||||
- [x] 增加标题/页面关键字断言,确认命中了正确预览模板
|
||||
- [x] 对 PDF / DOCX / CAD 增加“等待页 -> 最终页面”的轮询兼容
|
||||
|
||||
### 3. 补齐 CI 所需 fixture
|
||||
- [x] `sample.pdf` 进入 required fixture 清单
|
||||
- [x] `sample.mp4` 进入 required fixture 清单
|
||||
- [x] `text.dxf` 进入 required fixture 清单
|
||||
- [x] 将 MP4 与 DXF fixture 作为仓库内静态样例纳入 CI
|
||||
|
||||
### 4. 后续可继续增强(本次未全部落地)
|
||||
- [ ] 为 PDF / Office / CAD 增加截图型 nightly artifact
|
||||
- [ ] 为 `/picturesPreview` 增加独立 smoke
|
||||
- [ ] 为 OFD 增加稳定 fixture 和 smoke case
|
||||
- [ ] 为媒体预览增加更多格式(如 wav / mp3 / mov)
|
||||
- [ ] 为 CAD 增加第二份标准样例,避免单样例偏差
|
||||
- [ ] 将当前 HTML 测试报告模板收敛成 nightly 自动产物
|
||||
|
||||
## 预期收益
|
||||
- 让 CI 覆盖这次 PR342 真正验证过的关键主链路
|
||||
- 避免未来出现“CI 绿了,但 PDF / MP4 / CAD 实际挂了”的情况
|
||||
- 让 E2E 更接近用户真实感知,而不是仅验证接口可达
|
||||
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 |
@@ -5,7 +5,7 @@ RUN sed -i 's@//.*archive.ubuntu.com@//mirrors.aliyun.com@g' /etc/apt/sources.li
|
||||
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-8-jre tzdata locales xfonts-utils fontconfig libreoffice-nogui &&\
|
||||
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 &&\
|
||||
|
||||
95
pom.xml
@@ -6,53 +6,75 @@
|
||||
|
||||
<groupId>cn.keking</groupId>
|
||||
<artifactId>kkFileView-parent</artifactId>
|
||||
<version>4.4.0</version>
|
||||
<version>5.0</version>
|
||||
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
<jodconverter.version>4.4.6</jodconverter.version>
|
||||
<spring.boot.version>2.4.2</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.5</junrar.version>
|
||||
<redisson.version>3.2.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>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>
|
||||
<bytedeco.version>1.5.2</bytedeco.version>
|
||||
<opencv.version>4.1.2-1.5.2</opencv.version>
|
||||
<openblas.version>0.3.6-1.5.1</openblas.version>
|
||||
<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.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.5.0</commons-cli.version>
|
||||
<commons-net.version>3.9.0</commons-net.version>
|
||||
<commons-lang3.version>3.13.0</commons-lang3.version>
|
||||
|
||||
|
||||
<!-- ========== Java 和编译配置 ========== -->
|
||||
<java.version>21</java.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>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
|
||||
<!-- ========== Spring 框架 ========== -->
|
||||
<spring.boot.version>3.5.6</spring.boot.version>
|
||||
|
||||
<!-- ========== 文档转换和Office处理 ========== -->
|
||||
<jodconverter.version>4.4.11</jodconverter.version>
|
||||
<poi.version>5.2.5</poi.version>
|
||||
<xdocreport.version>1.0.6</xdocreport.version>
|
||||
<aspose-cad.version>25.10</aspose-cad.version>
|
||||
|
||||
<!-- ========== PDF 处理 ========== -->
|
||||
<pdfbox.version>3.0.6</pdfbox.version>
|
||||
|
||||
<!-- ========== 图像处理 ========== -->
|
||||
<jai-imageio.version>1.4.0</jai-imageio.version>
|
||||
<jbig2-imageio.version>3.0.4</jbig2-imageio.version>
|
||||
<commons-imaging.version>1.0.0-alpha6</commons-imaging.version>
|
||||
|
||||
<!-- ========== 视频处理 (JavaCV) ========== -->
|
||||
<bytedeco.version>1.5.12</bytedeco.version>
|
||||
<opencv.version>4.11.0-1.5.12</opencv.version>
|
||||
<openblas.version>0.3.30-1.5.12</openblas.version>
|
||||
<ffmpeg.version>7.1.1-1.5.12</ffmpeg.version>
|
||||
|
||||
<!-- ========== 压缩文件处理 ========== -->
|
||||
<sevenzipjbinding.version>16.02-2.01</sevenzipjbinding.version>
|
||||
<junrar.version>7.5.5</junrar.version>
|
||||
|
||||
<!-- ========== 缓存和存储 ========== -->
|
||||
<redisson.version>4.0.0</redisson.version>
|
||||
<rocksdb.version>5.17.2</rocksdb.version>
|
||||
<concurrentlinkedhashmap.version>1.4.2</concurrentlinkedhashmap.version>
|
||||
|
||||
<!-- ========== 网络通信 ========== -->
|
||||
<httpcomponents.version>4.5.16</httpcomponents.version>
|
||||
<commons-net.version>3.12.0</commons-net.version>
|
||||
|
||||
<!-- ========== Apache Commons 工具库 ========== -->
|
||||
<commons-lang3.version>3.20.0</commons-lang3.version>
|
||||
<commons-cli.version>1.11.0</commons-cli.version>
|
||||
|
||||
<!-- ========== 编码和字符处理 ========== -->
|
||||
<juniversalchardet.version>1.0.3</juniversalchardet.version>
|
||||
<jchardet.version>1.0</jchardet.version>
|
||||
|
||||
<!-- ========== 安全相关 ========== -->
|
||||
<bcprov-jdk15on.version>1.70</bcprov-jdk15on.version>
|
||||
|
||||
<!-- ========== 其他工具库 ========== -->
|
||||
<xstream.version>1.4.21</xstream.version>
|
||||
<antlr.version>2.7.7</antlr.version>
|
||||
<galimatias.version>0.2.1</galimatias.version>
|
||||
</properties>
|
||||
|
||||
<modules>
|
||||
<module>server</module>
|
||||
</modules>
|
||||
|
||||
<!-- ========== 项目信息 ========== -->
|
||||
<name>kkFileView-parent</name>
|
||||
<description>专注文件在线预览服务</description>
|
||||
<url>https://github.com/kekingcn/kkFileView</url>
|
||||
@@ -88,5 +110,4 @@
|
||||
<system>github</system>
|
||||
<url>https://github.com/kekingcn/kkFileView/issues</url>
|
||||
</issueManagement>
|
||||
|
||||
</project>
|
||||
</project>
|
||||
383
server/pom.xml
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<artifactId>kkFileView-parent</artifactId>
|
||||
<groupId>cn.keking</groupId>
|
||||
<version>4.4.0</version>
|
||||
<version>5.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>kkFileView</artifactId>
|
||||
@@ -24,44 +24,49 @@
|
||||
</dependencyManagement>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>aspose-maven-repository</id>
|
||||
<url>https://repository.aspose.com/repo</url>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<!-- Aspose 仓库,且只启用 releases -->
|
||||
<repository>
|
||||
<id>aspose-maven-repository</id>
|
||||
<url>https://repository.aspose.com/repo</url>
|
||||
<releases>
|
||||
<enabled>true</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<!-- ========== Spring Boot 框架依赖 ========== -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</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>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-devtools</artifactId>
|
||||
<scope>runtime</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- ========== 文档格式转换 ========== -->
|
||||
<dependency>
|
||||
<groupId>org.jodconverter</groupId>
|
||||
<artifactId>jodconverter-local</artifactId>
|
||||
<version>${jodconverter.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- web start -->
|
||||
<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>
|
||||
<!-- web end -->
|
||||
|
||||
<!-- poi start -->
|
||||
<!-- ========== Office文档处理 (POI相关) ========== -->
|
||||
<dependency>
|
||||
<groupId>org.apache.poi</groupId>
|
||||
<artifactId>poi</artifactId>
|
||||
@@ -98,14 +103,31 @@
|
||||
<artifactId>fr.opensagres.xdocreport.document</artifactId>
|
||||
<version>${xdocreport.version}</version>
|
||||
</dependency>
|
||||
<!-- poi start -->
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
<version>${httpcomponents.version}</version>
|
||||
<groupId>com.aspose</groupId>
|
||||
<artifactId>aspose-cad</artifactId>
|
||||
<version>${aspose-cad.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 对 rar5 的支持 和其他众多压缩支持 可参考 package net.sf.sevenzipjbinding.ArchiveFormat; -->
|
||||
<!-- ========== PDF处理 ========== -->
|
||||
<dependency>
|
||||
<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>
|
||||
<artifactId>pdfbox-tools</artifactId>
|
||||
<version>${pdfbox.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- ========== 压缩文件处理 ========== -->
|
||||
<dependency>
|
||||
<groupId>net.sf.sevenzipjbinding</groupId>
|
||||
<artifactId>sevenzipjbinding</artifactId>
|
||||
@@ -116,78 +138,13 @@
|
||||
<artifactId>sevenzipjbinding-all-platforms</artifactId>
|
||||
<version>${sevenzipjbinding.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
<version>${commons-lang3.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.redisson</groupId>
|
||||
<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>
|
||||
<artifactId>junrar</artifactId>
|
||||
<version>${junrar.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.sourceforge.jchardet</groupId>
|
||||
<artifactId>jchardet</artifactId>
|
||||
<version>${jchardet.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>antlr</groupId>
|
||||
<artifactId>antlr</artifactId>
|
||||
<version>${antlr.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-cli</groupId>
|
||||
<artifactId>commons-cli</artifactId>
|
||||
<version>${commons-cli.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- FTP -->
|
||||
<dependency>
|
||||
<groupId>commons-net</groupId>
|
||||
<artifactId>commons-net</artifactId>
|
||||
<version>${commons-net.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.thoughtworks.xstream</groupId>
|
||||
<artifactId>xstream</artifactId>
|
||||
<version>${xstream.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.googlecode.concurrentlinkedhashmap</groupId>
|
||||
<artifactId>concurrentlinkedhashmap-lru</artifactId>
|
||||
<version>${concurrentlinkedhashmap.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.rocksdb</groupId>
|
||||
<artifactId>rocksdbjni</artifactId>
|
||||
<version>${rocksdb.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.pdfbox</groupId>
|
||||
<artifactId>pdfbox</artifactId>
|
||||
<version>${pdfbox.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.pdfbox</groupId>
|
||||
<artifactId>pdfbox-tools</artifactId>
|
||||
<version>${pdfbox.version}</version>
|
||||
</dependency>
|
||||
<!-- ========== 图像处理 ========== -->
|
||||
<dependency>
|
||||
<groupId>com.github.jai-imageio</groupId>
|
||||
<artifactId>jai-imageio-jpeg2000</artifactId>
|
||||
@@ -203,79 +160,12 @@
|
||||
<artifactId>jbig2-imageio</artifactId>
|
||||
<version>${jbig2-imageio.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.aspose</groupId>
|
||||
<artifactId>aspose-cad</artifactId>
|
||||
<version>${aspose-cad.version}</version>
|
||||
</dependency>
|
||||
<!-- 密钥算法 -->
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15on</artifactId>
|
||||
<version>${bcprov-jdk15on.version}</version>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-imaging</artifactId>
|
||||
<version>${commons-imaging.version}</version>
|
||||
</dependency>
|
||||
<!-- url 规范化 -->
|
||||
<dependency>
|
||||
<groupId>io.mola.galimatias</groupId>
|
||||
<artifactId>galimatias</artifactId>
|
||||
<version>${galimatias.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 以下是bytedeco 基于opencv ffmpeg封装的javacv,用于视频处理 -->
|
||||
<dependency>
|
||||
<groupId>org.bytedeco</groupId>
|
||||
<artifactId>javacv</artifactId>
|
||||
<version>${bytedeco.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bytedeco</groupId>
|
||||
<artifactId>javacpp</artifactId>
|
||||
<version>${bytedeco.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 此版本中主要兼容linux和windows系统,如需兼容其他系统平台,请引入对应依赖即可 -->
|
||||
<dependency>
|
||||
<groupId>org.bytedeco</groupId>
|
||||
<artifactId>opencv</artifactId>
|
||||
<version>${opencv.version}</version>
|
||||
<classifier>linux-x86_64</classifier>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bytedeco</groupId>
|
||||
<artifactId>opencv</artifactId>
|
||||
<version>${opencv.version}</version>
|
||||
<classifier>windows-x86_64</classifier>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bytedeco</groupId>
|
||||
<artifactId>openblas</artifactId>
|
||||
<version>${openblas.version}</version>
|
||||
<classifier>linux-x86_64</classifier>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bytedeco</groupId>
|
||||
<artifactId>openblas</artifactId>
|
||||
<version>${openblas.version}</version>
|
||||
<classifier>windows-x86_64</classifier>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bytedeco</groupId>
|
||||
<artifactId>ffmpeg</artifactId>
|
||||
<version>${ffmpeg.version}</version>
|
||||
<classifier>linux-x86_64</classifier>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bytedeco</groupId>
|
||||
<artifactId>ffmpeg</artifactId>
|
||||
<version>${ffmpeg.version}</version>
|
||||
<classifier>windows-x86_64</classifier>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.itextpdf</groupId>
|
||||
<artifactId>itextpdf</artifactId>
|
||||
<version>${itextpdf.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- JAI 系统依赖 -->
|
||||
<dependency>
|
||||
<groupId>javax.media</groupId>
|
||||
<artifactId>jai_core</artifactId>
|
||||
@@ -291,25 +181,134 @@
|
||||
<systemPath>${pom.basedir}/lib/jai_codec-1.1.3.jar</systemPath>
|
||||
</dependency>
|
||||
|
||||
<!-- test dependency - start -->
|
||||
<!-- ========== 视频处理 (JavaCV) ========== -->
|
||||
<dependency>
|
||||
<groupId>org.bytedeco</groupId>
|
||||
<artifactId>javacv</artifactId>
|
||||
<version>${bytedeco.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bytedeco</groupId>
|
||||
<artifactId>javacpp</artifactId>
|
||||
<version>${bytedeco.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bytedeco</groupId>
|
||||
<artifactId>opencv</artifactId>
|
||||
<version>${opencv.version}</version>
|
||||
<classifier>linux-x86_64</classifier>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bytedeco</groupId>
|
||||
<artifactId>opencv</artifactId>
|
||||
<version>${opencv.version}</version>
|
||||
<classifier>windows-x86_64</classifier>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bytedeco</groupId>
|
||||
<artifactId>openblas</artifactId>
|
||||
<version>${openblas.version}</version>
|
||||
<classifier>linux-x86_64</classifier>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bytedeco</groupId>
|
||||
<artifactId>openblas</artifactId>
|
||||
<version>${openblas.version}</version>
|
||||
<classifier>windows-x86_64</classifier>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bytedeco</groupId>
|
||||
<artifactId>ffmpeg</artifactId>
|
||||
<version>${ffmpeg.version}</version>
|
||||
<classifier>linux-x86_64</classifier>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bytedeco</groupId>
|
||||
<artifactId>ffmpeg</artifactId>
|
||||
<version>${ffmpeg.version}</version>
|
||||
<classifier>windows-x86_64</classifier>
|
||||
</dependency>
|
||||
|
||||
<!-- ========== 网络通信 ========== -->
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents.client5</groupId>
|
||||
<artifactId>httpclient5</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-net</groupId>
|
||||
<artifactId>commons-net</artifactId>
|
||||
<version>${commons-net.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- ========== 缓存和存储 ========== -->
|
||||
<dependency>
|
||||
<groupId>org.redisson</groupId>
|
||||
<artifactId>redisson</artifactId>
|
||||
<version>${redisson.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.rocksdb</groupId>
|
||||
<artifactId>rocksdbjni</artifactId>
|
||||
<version>${rocksdb.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.googlecode.concurrentlinkedhashmap</groupId>
|
||||
<artifactId>concurrentlinkedhashmap-lru</artifactId>
|
||||
<version>${concurrentlinkedhashmap.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- ========== 编码检测和字符处理 ========== -->
|
||||
<dependency>
|
||||
<groupId>com.googlecode.juniversalchardet</groupId>
|
||||
<artifactId>juniversalchardet</artifactId>
|
||||
<version>${juniversalchardet.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.sourceforge.jchardet</groupId>
|
||||
<artifactId>jchardet</artifactId>
|
||||
<version>${jchardet.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- ========== Apache Commons 工具库 ========== -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
<version>${commons-lang3.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-cli</groupId>
|
||||
<artifactId>commons-cli</artifactId>
|
||||
<version>${commons-cli.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- ========== 其他工具库 ========== -->
|
||||
<dependency>
|
||||
<groupId>antlr</groupId>
|
||||
<artifactId>antlr</artifactId>
|
||||
<version>${antlr.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.thoughtworks.xstream</groupId>
|
||||
<artifactId>xstream</artifactId>
|
||||
<version>${xstream.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15on</artifactId>
|
||||
<version>${bcprov-jdk15on.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.mola.galimatias</groupId>
|
||||
<artifactId>galimatias</artifactId>
|
||||
<version>${galimatias.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- ========== 测试依赖 ========== -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-httpclient</groupId>
|
||||
<artifactId>commons-httpclient</artifactId>
|
||||
<version>${httpclient.version}</version>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>commons-logging</artifactId>
|
||||
<groupId>commons-logging</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<!-- test dependency - end -->
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
@@ -327,6 +326,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>
|
||||
@@ -365,4 +372,4 @@
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
</project>
|
||||
|
||||
16
server/src/main/bin/dev.sh
Executable file
@@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
ROOT_DIR=$(cd "$(dirname "$0")/../../../.." || exit 1; pwd)
|
||||
SERVER_DIR="$ROOT_DIR/server"
|
||||
|
||||
if [ -n "$JAVA_HOME" ]; then
|
||||
export PATH="$JAVA_HOME/bin:$PATH"
|
||||
fi
|
||||
|
||||
cd "$SERVER_DIR" || exit 1
|
||||
|
||||
mvn spring-boot:run \
|
||||
-Dspring-boot.run.addResources=true \
|
||||
-Dspring-boot.run.jvmArguments="-Dfile.encoding=UTF-8 -Dspring.config.location=$SERVER_DIR/src/main/config/application.properties"
|
||||
478
server/src/main/config/test.properties
Normal file
@@ -0,0 +1,478 @@
|
||||
###############################################################################
|
||||
# 一、服务器基础配置(需要重启生效)
|
||||
###############################################################################
|
||||
|
||||
# 服务器端口号,默认8012
|
||||
# 可以通过环境变量 KK_SERVER_PORT 覆盖
|
||||
server.port = ${KK_SERVER_PORT:8012}
|
||||
|
||||
# 应用上下文路径,默认为根路径 /
|
||||
# 可以通过环境变量 KK_CONTEXT_PATH 覆盖
|
||||
server.servlet.context-path = ${KK_CONTEXT_PATH:/}
|
||||
|
||||
# 字符编码设置,统一使用UTF-8
|
||||
server.servlet.encoding.charset = utf-8
|
||||
|
||||
# 启用响应压缩,减少网络传输
|
||||
server.compression.enabled = true
|
||||
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
|
||||
|
||||
# 文件上传大小限制,默认500MB
|
||||
# 注意:需要同时设置spring.servlet.multipart.max-file-size和max-request-size
|
||||
spring.servlet.multipart.max-file-size = 500MB
|
||||
spring.servlet.multipart.max-request-size = 500MB
|
||||
|
||||
# FreeMarker模板引擎配置
|
||||
spring.freemarker.template-loader-path = classpath:/web/
|
||||
spring.freemarker.cache = false
|
||||
spring.freemarker.charset = UTF-8
|
||||
spring.freemarker.check-template-location = true
|
||||
spring.freemarker.content-type = text/html
|
||||
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
|
||||
management.endpoint.health.show-details = always
|
||||
management.health.defaults.enabled = true
|
||||
|
||||
|
||||
###############################################################################
|
||||
# 二、Office文档处理配置(部分支持动态配置)
|
||||
###############################################################################
|
||||
|
||||
# Office组件安装路径,默认为自动查找
|
||||
# Windows示例(注意双反斜杠):C:\\Program Files (x86)\\OpenOffice 4
|
||||
# Linux示例:/opt/libreoffice
|
||||
# MacOS示例:/Applications/LibreOffice.app/Contents
|
||||
office.home = ${KK_OFFICE_HOME:default}
|
||||
|
||||
# Office组件服务端口,支持多个端口实现负载均衡
|
||||
office.plugin.server.ports = 2001,2002
|
||||
|
||||
# Office组件任务超时时间,默认5分钟
|
||||
office.plugin.task.timeout = 5m
|
||||
|
||||
# 每个进程最大任务数,防止内存溢出
|
||||
office.plugin.task.maxtasksperprocess = 200
|
||||
|
||||
# 任务执行超时时间,默认5分钟
|
||||
office.plugin.task.taskexecutiontimeout = 5m
|
||||
|
||||
# Office文档分页范围,支持动态配置
|
||||
# 默认false,开启后可以指定转换的页面范围
|
||||
office.pagerange = ${KK_OFFICE_PAGERANGE:false}
|
||||
|
||||
# Office文档水印功能,支持动态配置
|
||||
# 默认false,开启后会在Office文档上添加水印
|
||||
office.watermark = ${KK_OFFICE_WATERMARK:false}
|
||||
|
||||
# Office图片质量,1-100,默认80
|
||||
# 值越高图片质量越好,但文件越大
|
||||
office.quality = ${KK_OFFICE_QUALITY:80}
|
||||
|
||||
# Office图片最大分辨率,默认150
|
||||
# 控制生成图片的最大分辨率
|
||||
office.maximageresolution = ${KK_OFFICE_MAXIMAGERESOLUTION:150}
|
||||
|
||||
# 导出Office书签,支持动态配置
|
||||
# 默认true,转换PDF时保留书签
|
||||
office.exportbookmarks = ${KK_OFFICE_EXPORTBOOKMARKS:true}
|
||||
|
||||
# 是否将Office文档中的批注作为PDF注释导出,默认为true(导出)
|
||||
# 保留批注便于文档审阅
|
||||
office.exportnotes = ${KK_OFFICE_EXPORTNOTES:true}
|
||||
|
||||
# 加密文档生成的PDF是否添加密码,默认为true(添加)
|
||||
# 密码为原始加密文档的密码,增强文档安全性
|
||||
office.documentopenpasswords = ${KK_OFFICE_DOCUMENTOPENPASSWORD:true}
|
||||
|
||||
# Excel文档(xlsx)的前端解析方式,默认为web(Web端解析)
|
||||
# web: 使用前端SheetJS库解析,减轻服务器压力
|
||||
# image: 服务器转换为图片,兼容性更好
|
||||
office.type.web = ${KK_OFFICE_TYPE_WEB:web}
|
||||
|
||||
# Office文档预览类型
|
||||
# 支持动态配置,可选值:image/pdf
|
||||
office.preview.type = ${KK_OFFICE_PREVIEW_TYPE:image}
|
||||
|
||||
# 是否关闭Office预览模式切换开关,默认为false(允许切换)
|
||||
# 设置为true时,用户无法在图片和PDF模式间切换
|
||||
office.preview.switch.disabled = ${KK_OFFICE_PREVIEW_SWITCH_DISABLED:false}
|
||||
|
||||
|
||||
###############################################################################
|
||||
# 三、CAD文件处理配置(支持动态配置)
|
||||
###############################################################################
|
||||
|
||||
# CAD文件预览类型
|
||||
# svg: 转换为SVG矢量格式(缩放不失真)
|
||||
# pdf: 转换为PDF格式(便于打印和标注)
|
||||
cad.preview.type = ${KK_CAD_PREVIEW_TYPE:svg}
|
||||
|
||||
# Cad转换模块设置(aspose-cad=1 ,cadviewer=3)
|
||||
# aspose-cad 默认集成到系统,但是特别吃服务器性能 (支持转换格式为 svg、pdf)
|
||||
# cadviewer 下载地址 https://cadviewer.com/alldownloads/autoxchange/ 更具自己系统下载转换包(支持转换格式为 svg、svgz、pdf)
|
||||
# 1=aspose-cad 转换格式为 pdf,svg,tif 支持类型最多
|
||||
# 2=cadviewer 转换格式为 pdf,svg 支持的类型 dwg dxf dwf
|
||||
cad.conversionmodule = 2
|
||||
|
||||
# Cad 后端转换包路径 (linux 严格注意大小写)
|
||||
# cadviewer windows 修改名称为 cadviewer.exe linux修改名称为 cadviewer 需要安装字体
|
||||
# cadviewer 字体下载 https://cadviewer.com/downloads/fonts/fonts.tar.gz 放在 cad.file.path 目录里面的fonts.
|
||||
cad.cadconverterpath = D:/github/AutoXChange/
|
||||
|
||||
|
||||
# CAD文件处理线程数
|
||||
cad.thread = ${KK_CAD_THREAD:5}
|
||||
|
||||
# CAD文件处理超时时间(秒)
|
||||
cad.timeout = ${KK_CAD_TIMEOUT:90}
|
||||
|
||||
|
||||
###############################################################################
|
||||
# 四、PDF文件处理配置(支持动态配置)
|
||||
###############################################################################
|
||||
|
||||
# 是否禁止PDF演示模式,默认为true(禁止)
|
||||
pdf.presentationMode.disable = ${KK_PDF_PRESENTATION_MODE_DISABLE:true}
|
||||
|
||||
# 是否禁止PDF文件菜单中的"打开文件"选项,默认为true(禁止)
|
||||
pdf.openFile.disable = ${KK_PDF_OPEN_FILE_DISABLE:true}
|
||||
|
||||
# 是否禁止PDF打印功能,默认为true(禁止)
|
||||
pdf.print.disable = ${KK_PDF_PRINT_DISABLE:true}
|
||||
|
||||
# 是否禁止PDF下载功能,默认为true(禁止)
|
||||
pdf.download.disable = ${KK_PDF_DOWNLOAD_DISABLE:true}
|
||||
|
||||
# 是否禁止PDF书签/大纲功能,默认为true(禁止)
|
||||
pdf.bookmark.disable = ${KK_PDF_BOOKMARK_DISABLE:true}
|
||||
|
||||
# 是否禁止PDF编辑功能(注释、表单等),默认为false(允许编辑)
|
||||
pdf.disable.editing = ${KK_PDF_DISABLE_EDITING:false}
|
||||
|
||||
# PDF处理最大线程数,控制并发处理能力
|
||||
pdf.max.threads = 10
|
||||
|
||||
# PDF处理超时配置
|
||||
pdf.timeout.small = 90
|
||||
pdf.timeout.medium = 180
|
||||
pdf.timeout.large = 300
|
||||
pdf.timeout.xlarge = 600
|
||||
|
||||
# PDF智能DPI优化
|
||||
# 是否启用PDF DPI智能调整,默认为true(启用)
|
||||
# 根据PDF页数自动调整DPI,平衡清晰度和性能
|
||||
pdf.dpi.enabled = true
|
||||
|
||||
# PDF转图片的基准DPI,默认为144
|
||||
# 当DPI优化禁用时使用此值
|
||||
pdf2jpg.dpi = ${KK_PDF2JPG_DPI:144}
|
||||
|
||||
# 智能DPI分级配置
|
||||
# 小文件(0-50页):150 DPI(高质量)
|
||||
pdf.dpi.small = 150
|
||||
# 中等文件(50-100页):120 DPI(平衡质量与性能)
|
||||
pdf.dpi.medium = 120
|
||||
# 大文件(100-200页):96 DPI(优化性能)
|
||||
pdf.dpi.large = 96
|
||||
# 超大文件(200-500页):72 DPI(快速转换)
|
||||
pdf.dpi.xlarge = 72
|
||||
# 巨量文件(>500页):72 DPI(最小资源消耗)
|
||||
pdf.dpi.xxlarge = 72
|
||||
|
||||
|
||||
###############################################################################
|
||||
# 五、TIF文件处理配置(支持动态配置)
|
||||
###############################################################################
|
||||
|
||||
# TIF文件预览类型
|
||||
# tif: 使用前端Tiff.js插件直接浏览(需要浏览器支持)
|
||||
# jpg: 服务器转换为JPG格式后显示
|
||||
# pdf: 服务器转换为PDF格式显示
|
||||
tif.preview.type = ${KK_TIF_PREVIEW_TYPE:tif}
|
||||
|
||||
# TIF文件处理线程数
|
||||
tif.thread = 5
|
||||
|
||||
# TIF文件处理超时时间(秒)
|
||||
tif.timeout = 90
|
||||
|
||||
|
||||
###############################################################################
|
||||
# 六、媒体文件处理配置(支持动态配置)
|
||||
###############################################################################
|
||||
|
||||
# 媒体文件类型(音频、视频)
|
||||
media = ${KK_MEDIA:mp3,wav,mp4,flv,mpd,m3u8,ts,mpeg,m4a}
|
||||
|
||||
# 需要转换的媒体文件类型
|
||||
convertMedias = ${KK_CONVERTMEDIAS:avi,mov,wmv,mkv,3gp,rm,mpeg}
|
||||
|
||||
# 媒体文件超时控制
|
||||
media.timeout.enabled = true
|
||||
media.small.file.timeout = 30
|
||||
media.medium.file.timeout = 60
|
||||
media.large.file.timeout = 180
|
||||
media.xl.file.timeout = 300
|
||||
media.xxl.file.timeout = 600
|
||||
media.xxxl.file.timeout = 1200
|
||||
|
||||
# 媒体文件转换最大大小(MB)
|
||||
media.convert.max.size = 300
|
||||
|
||||
# 是否禁用视频格式转换功能,默认为false(禁用)
|
||||
# ⚠️ 重要:视频转换非常消耗CPU和内存资源
|
||||
media.convert.disable = ${KK_MEDIA_CONVERT_DISABLE:true}
|
||||
|
||||
|
||||
###############################################################################
|
||||
# 七、文件存储与缓存配置(支持动态配置)
|
||||
###############################################################################
|
||||
|
||||
# 预览生成资源的存储路径,默认为应用根路径下的file目录
|
||||
# Windows示例:D:\\kkFileview\\(注意双反斜杠)
|
||||
# Linux示例:/opt/kkfileview/file/
|
||||
# 重要:确保应用有该目录的读写权限
|
||||
file.dir = ${KK_FILE_DIR:default}
|
||||
|
||||
# 允许预览的本地文件夹路径,默认为default(禁止所有本地文件预览)
|
||||
# ⚠️ 安全警告:配置此路径可能允许访问系统文件,请谨慎配置
|
||||
# Windows示例(注意前面加反斜杠):\D:\\kkFileview\\1\\1.txt
|
||||
# Linux示例(注意前面加正斜杠):/opt/1.txt
|
||||
# 使用file协议访问:file://d:/1/1.txt(Windows)或 file:/opt/1.txt(Linux)
|
||||
local.preview.dir = \D:\\
|
||||
|
||||
# 是否启用缓存,支持动态配置
|
||||
# 默认true,开启缓存提高性能
|
||||
cache.enabled = ${KK_CACHE_ENABLED:true}
|
||||
|
||||
# 缓存实现类型,默认为jdk(使用JDK内置对象实现)
|
||||
# 可选值:
|
||||
# jdk: JDK内置ConcurrentHashMap,单机部署推荐
|
||||
# redis: Redis分布式缓存,集群部署推荐
|
||||
# default: 内嵌RocksDB,支持持久化
|
||||
cache.type = ${KK_CACHE_TYPE:jdk}
|
||||
|
||||
# Redis部署模式,默认为single(单机模式)
|
||||
# 可选值:
|
||||
# single: 单机模式(默认)
|
||||
# cluster: 集群模式
|
||||
# sentinel: 哨兵模式(高可用)
|
||||
# master-slave: 主从模式
|
||||
spring.redisson.mode = single
|
||||
# Redis连接地址,支持多种格式:
|
||||
# 单机模式:redis://127.0.0.1:6379
|
||||
# 集群模式:redis://node1:6379,redis://node2:6379,redis://node3:6379
|
||||
# 哨兵模式:redis://sentinel1:26379,redis://sentinel2:26379
|
||||
spring.redisson.address = ${KK_SPRING_REDISSON_ADDRESS:redis://127.0.0.1:6379}
|
||||
# Redis连接密码,无密码时留空
|
||||
spring.redisson.password = ${KK_SPRING_REDISSON_PASSWORD:}
|
||||
# Redis数据库索引,默认为0(0-15)
|
||||
# 不同业务可使用不同数据库隔离
|
||||
spring.redisson.database = ${KK_SPRING_REDISSON_DATABASE:0}
|
||||
|
||||
# 缓存清理配置
|
||||
# 是否启用缓存自动清理,默认为true(启用)
|
||||
# 定期清理过期缓存,避免磁盘空间无限增长
|
||||
cache.clean.enabled = ${KK_CACHE_CLEAN_ENABLED:true}
|
||||
# 缓存自动清理时间,使用Quartz cron表达式,默认为每天凌晨3点执行
|
||||
# 表达式格式:秒 分 时 日 月 周 年(可选)
|
||||
# 0 0 3 * * ? 表示每天3:00:00执行清理
|
||||
cache.clean.cron = ${KK_CACHE_CLEAN_CRON:0 0 3 * * ?}
|
||||
|
||||
|
||||
###############################################################################
|
||||
# 八、安全与访问控制配置(支持动态配置)
|
||||
###############################################################################
|
||||
|
||||
# 提供预览服务的地址,默认从请求url读,如果使用nginx等反向代理,需要手动设置
|
||||
# base.url = https://file.keking.cn
|
||||
base.url = ${KK_BASE_URL:default}
|
||||
|
||||
# 信任站点白名单配置,多个用','隔开
|
||||
# ⚠️ 安全提示:为防止SSRF攻击,强烈建议配置信任主机白名单
|
||||
# ⚠️ 如果不配置,系统将默认拒绝所有外部文件预览请求
|
||||
# 配置示例:
|
||||
# trust.host = kkview.cn,yourdomain.com,cdn.example.com
|
||||
# 如果需要允许所有域名(不推荐,仅用于测试环境),请设置为:
|
||||
# trust.host = *
|
||||
# 当前配置:默认本机测试 (正式启用请修改)
|
||||
trust.host = *
|
||||
|
||||
# 不信任站点黑名单配置,多个用逗号隔开
|
||||
# 黑名单优先级高于白名单,设置后将禁止预览来自这些站点的文件
|
||||
# 建议配置:禁止访问内网地址和本地地址,防止内部信息泄露
|
||||
# 配置示例:
|
||||
# not.trust.host = localhost,127.0.0.1,0.0.0.0,192.168.*,10.*,172.16.*,172.17.*,172.18.*,172.19.*,172.20.*,172.21.*,172.22.*,172.23.*,172.24.*,172.25.*,172.26.*,172.27.*,172.28.*,172.29.*,172.30.*,172.31.*
|
||||
not.trust.host = ${KK_NOT_TRUST_HOST:default}
|
||||
|
||||
# 禁止访问的文件类型,安全限制
|
||||
# 支持动态配置,格式:exe,dll,dat
|
||||
prohibit = ${KK_PROHIBIT:exe,dll,dat}
|
||||
|
||||
# 是否忽略SSL证书验证,默认为true(忽略)
|
||||
# 用于开发环境或自签名证书场景
|
||||
# 生产环境建议设置为false,启用完整的证书验证
|
||||
kk.ignore.ssl = true
|
||||
|
||||
# 是否启用URL重定向功能,默认为true(启用)
|
||||
# 用于处理文件下载、外部资源引用等场景
|
||||
kk.enable.redirect = true
|
||||
|
||||
|
||||
###############################################################################
|
||||
# 九、水印配置(支持动态配置)
|
||||
###############################################################################
|
||||
|
||||
# 水印文本内容
|
||||
# 可以通过环境变量 WATERMARK_TXT 覆盖
|
||||
watermark.txt = ${WATERMARK_TXT:}
|
||||
|
||||
# 水印X轴间距
|
||||
# 可以通过环境变量 WATERMARK_X_SPACE 覆盖
|
||||
watermark.x.space = ${WATERMARK_X_SPACE:10}
|
||||
|
||||
# 水印Y轴间距
|
||||
# 可以通过环境变量 WATERMARK_Y_SPACE 覆盖
|
||||
watermark.y.space = ${WATERMARK_Y_SPACE:10}
|
||||
|
||||
# 水印字体
|
||||
# 可以通过环境变量 WATERMARK_FONT 覆盖
|
||||
watermark.font = ${WATERMARK_FONT:微软雅黑}
|
||||
|
||||
# 水印字体大小
|
||||
# 可以通过环境变量 WATERMARK_FONTSIZE 覆盖
|
||||
watermark.fontsize = ${WATERMARK_FONTSIZE:18px}
|
||||
|
||||
# 水印颜色
|
||||
# 可以通过环境变量 WATERMARK_COLOR 覆盖
|
||||
watermark.color = ${WATERMARK_COLOR:black}
|
||||
|
||||
# 水印透明度,0.0-1.0
|
||||
# 可以通过环境变量 WATERMARK_ALPHA 覆盖
|
||||
watermark.alpha = ${WATERMARK_ALPHA:0.2}
|
||||
|
||||
# 水印宽度
|
||||
# 可以通过环境变量 WATERMARK_WIDTH 覆盖
|
||||
watermark.width = ${WATERMARK_WIDTH:180}
|
||||
|
||||
# 水印高度
|
||||
# 可以通过环境变量 WATERMARK_HEIGHT 覆盖
|
||||
watermark.height = ${WATERMARK_HEIGHT:80}
|
||||
|
||||
# 水印旋转角度
|
||||
# 可以通过环境变量 WATERMARK_ANGLE 覆盖
|
||||
watermark.angle = ${WATERMARK_ANGLE:10}
|
||||
|
||||
|
||||
###############################################################################
|
||||
# 十、FTP文件访问配置(支持动态配置)
|
||||
###############################################################################
|
||||
|
||||
# FTP模块设置
|
||||
# 预览源为FTP时,可在ftp url后面加参数?ftp.username=ftpuser&ftp.password=123456&ftp.control.encoding=GBK,指定,不指定默认用配置的 (为了安全我们强烈建议在配置中设置相关信息)
|
||||
# ftp.control.encodin (根据FTP服务器操作系统选择,Linux一般为UTF-8,Windows一般为GBK)
|
||||
# 使用方法,支持,分割第一个是域名或者IP地址后面是用户名在后面是密码,在后面是编码(用户名密码和编码用:分割, 域名用,分割),切记url不需要任何协议头
|
||||
# ftp.username = 地址:端口:用户名:密码:编码,192.168.0.2:21:name:123456:UTF-8,www.xxx.com:21:admin:pass:UTF-8 多客户端,分割
|
||||
# ftp.username =10.99.1.2:21:666:88888:GBK
|
||||
ftp.username = false
|
||||
|
||||
|
||||
###############################################################################
|
||||
# 十一、首页与文件管理配置(支持动态配置)
|
||||
###############################################################################
|
||||
|
||||
# 是否禁用首页文件上传功能,默认为true(禁用)
|
||||
# 设置为true可关闭上传功能,仅用于预览
|
||||
file.upload.disable = false
|
||||
|
||||
# 网站备案信息,显示在首页底部,默认为空
|
||||
beian = ${KK_BEIAN:default}
|
||||
|
||||
# 首页初始化加载的页码,默认为1(第一页)
|
||||
home.pagenumber = ${DEFAULT_HOME_PAGENUMBER:1}
|
||||
|
||||
# 首页每页显示的文件数量,默认为20
|
||||
home.pagesize = ${DEFAULT_HOME_PAGSIZE:20}
|
||||
|
||||
# 文件删除验证配置
|
||||
# 是否启用验证码验证删除文件,默认为false(不启用)
|
||||
# 启用后删除文件需要输入验证码,防止误删
|
||||
delete.captcha = ${KK_DELETE_CAPTCHA:false}
|
||||
|
||||
# 删除文件密码,默认为123456
|
||||
delete.password = ${KK_DELETE_PASSWORD:123456}
|
||||
|
||||
# 是否删除转换后的源文件,默认为true(删除)
|
||||
# 启用可节约磁盘空间,但会丢失原始文件
|
||||
delete.source.file = ${KK_DELETE_SOURCE_FILE:true}
|
||||
|
||||
|
||||
###############################################################################
|
||||
# 十二、权限与认证配置(支持动态配置)
|
||||
###############################################################################
|
||||
|
||||
# 是否启用图片预览权限,默认为true(启用)
|
||||
# 设置为false可禁用所有图片预览功能
|
||||
kk.Picturespreview = true
|
||||
|
||||
# 是否启用跨域文件获取权限,默认为true(启用)
|
||||
kk.Getcorsfile = true
|
||||
|
||||
# 是否启用添加异步任务权限,默认为true(启用)
|
||||
# 大文件转换通常使用异步任务处理
|
||||
kk.addTask = true
|
||||
|
||||
# API密钥功能,默认为false(禁用)
|
||||
# 启用后需要提供密钥才能调用API
|
||||
kk.key = false
|
||||
|
||||
# AES加密密钥,必须为16位字符
|
||||
# 启用AES加密时,接入方需使用相同的密钥
|
||||
# 用于敏感数据传输加密
|
||||
aes.key = 1234567890123456
|
||||
|
||||
# Basic认证配置,格式:域名:用户名:密码,多个用逗号分隔
|
||||
# 用于保护特定域名的访问
|
||||
# 示例:192.168.0.1:admin:pass123,example.com:user:pass456
|
||||
basic.name = 10.99.1.2:aaa:bbb
|
||||
|
||||
# User-Agent验证字符串,默认不启用
|
||||
# 可用于简单的客户端验证
|
||||
useragent = false
|
||||
|
||||
|
||||
###############################################################################
|
||||
# 十三、高级功能与兼容性配置(支持动态配置)
|
||||
###############################################################################
|
||||
|
||||
# 异步配置刷新定时时间(秒)
|
||||
kk.refreshschedule = 2
|
||||
|
||||
# 首页是否显示AES密钥 默认为false(禁用)
|
||||
kk.isshowaeskey = false
|
||||
|
||||
# 是否允许XLSX编辑
|
||||
kk.xlsxallowedit = true
|
||||
|
||||
# 是否显示XLSX工具栏
|
||||
kk.xlsxshowtoolbar = true
|
||||
|
||||
# 首页是否显示key密钥 默认为false(禁用)
|
||||
kk.isshowkey = true
|
||||
|
||||
# 预览html文件 是否启用JavaScript 默认为true(启用)
|
||||
kk.scriptjs = true
|
||||
|
||||
|
||||
###############################################################################
|
||||
# 十四、文件类型分类配置(支持动态配置)
|
||||
###############################################################################
|
||||
|
||||
# 纯文本文件类型,直接显示
|
||||
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}
|
||||
@@ -5,214 +5,283 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.annotation.PreDestroy;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.*;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
/**
|
||||
* @auther: chenjh
|
||||
* @time: 2019/4/10 16:16
|
||||
* @description 每隔1s读取并更新一次配置文件
|
||||
*/
|
||||
@Component
|
||||
public class ConfigRefreshComponent {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ConfigRefreshComponent.class);
|
||||
private static final long DEBOUNCE_DELAY_SECONDS = 5;
|
||||
|
||||
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
|
||||
private final ExecutorService watchServiceExecutor = Executors.newSingleThreadExecutor();
|
||||
private final Object lock = new Object();
|
||||
|
||||
private ScheduledFuture<?> scheduledReloadTask;
|
||||
private WatchService watchService;
|
||||
private volatile boolean running = true;
|
||||
|
||||
@PostConstruct
|
||||
void refresh() {
|
||||
Thread configRefreshThread = new Thread(new ConfigRefreshThread());
|
||||
configRefreshThread.start();
|
||||
void init() {
|
||||
loadConfig();
|
||||
watchServiceExecutor.submit(this::watchConfigFile);
|
||||
}
|
||||
|
||||
static class ConfigRefreshThread implements Runnable {
|
||||
@Override
|
||||
public void run() {
|
||||
@PreDestroy
|
||||
void destroy() {
|
||||
running = false;
|
||||
watchServiceExecutor.shutdownNow();
|
||||
scheduler.shutdownNow();
|
||||
if (watchService != null) {
|
||||
try {
|
||||
watchService.close();
|
||||
} catch (IOException e) {
|
||||
LOGGER.warn("关闭 WatchService 时发生异常", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void watchConfigFile() {
|
||||
try {
|
||||
String configFilePath = ConfigUtils.getCustomizedConfigPath();
|
||||
Path configPath = Paths.get(configFilePath);
|
||||
Path configDir = configPath.getParent();
|
||||
if (configDir == null) {
|
||||
LOGGER.error("配置文件路径无效: {}", configFilePath);
|
||||
return;
|
||||
}
|
||||
|
||||
watchService = FileSystems.getDefault().newWatchService();
|
||||
configDir.register(watchService,
|
||||
StandardWatchEventKinds.ENTRY_MODIFY,
|
||||
StandardWatchEventKinds.ENTRY_CREATE,
|
||||
StandardWatchEventKinds.ENTRY_DELETE);
|
||||
LOGGER.info("开始监听配置文件: {}", configFilePath);
|
||||
|
||||
while (running && !Thread.currentThread().isInterrupted()) {
|
||||
try {
|
||||
WatchKey key = watchService.take();
|
||||
for (WatchEvent<?> event : key.pollEvents()) {
|
||||
WatchEvent.Kind<?> kind = event.kind();
|
||||
Path changedPath = (Path) event.context();
|
||||
if (changedPath.equals(configPath.getFileName())) {
|
||||
handleConfigChange(kind);
|
||||
}
|
||||
}
|
||||
if (!key.reset()) {
|
||||
LOGGER.warn("WatchKey 无法重置");
|
||||
break;
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("初始化配置文件监听失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleConfigChange(WatchEvent.Kind<?> kind) {
|
||||
if (kind == StandardWatchEventKinds.ENTRY_DELETE) {
|
||||
running = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (kind == StandardWatchEventKinds.ENTRY_MODIFY ||
|
||||
kind == StandardWatchEventKinds.ENTRY_CREATE) {
|
||||
synchronized (lock) {
|
||||
if (scheduledReloadTask != null && !scheduledReloadTask.isDone()) {
|
||||
scheduledReloadTask.cancel(false);
|
||||
}
|
||||
scheduledReloadTask = scheduler.schedule(() -> {
|
||||
try {
|
||||
loadConfig();
|
||||
LOGGER.info("配置文件已重新加载");
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("重新加载配置失败", e);
|
||||
}
|
||||
}, DEBOUNCE_DELAY_SECONDS, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void loadConfig() {
|
||||
synchronized (lock) {
|
||||
try {
|
||||
Properties properties = new Properties();
|
||||
String text;
|
||||
String media;
|
||||
boolean cacheEnabled;
|
||||
String[] textArray;
|
||||
String[] mediaArray;
|
||||
String officePreviewType;
|
||||
String officePreviewSwitchDisabled;
|
||||
String ftpUsername;
|
||||
String ftpPassword;
|
||||
String ftpControlEncoding;
|
||||
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;
|
||||
String[] prohibitArray;
|
||||
String beian;
|
||||
String size;
|
||||
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);
|
||||
Path configPath = Paths.get(configFilePath);
|
||||
if (!Files.exists(configPath)) {
|
||||
LOGGER.warn("配置文件不存在: {}", configFilePath);
|
||||
return;
|
||||
}
|
||||
|
||||
try (BufferedReader bufferedReader = new BufferedReader(new FileReader(configFilePath))) {
|
||||
properties.load(bufferedReader);
|
||||
ConfigUtils.restorePropertiesFromEnvFormat(properties);
|
||||
cacheEnabled = Boolean.parseBoolean(properties.getProperty("cache.enabled", ConfigConstants.DEFAULT_CACHE_ENABLED));
|
||||
text = properties.getProperty("simText", ConfigConstants.DEFAULT_TXT_TYPE);
|
||||
media = properties.getProperty("media", ConfigConstants.DEFAULT_MEDIA_TYPE);
|
||||
officePreviewType = properties.getProperty("office.preview.type", ConfigConstants.DEFAULT_OFFICE_PREVIEW_TYPE);
|
||||
officePreviewSwitchDisabled = properties.getProperty("office.preview.switch.disabled", ConfigConstants.DEFAULT_OFFICE_PREVIEW_SWITCH_DISABLED);
|
||||
ftpUsername = properties.getProperty("ftp.username", ConfigConstants.DEFAULT_FTP_USERNAME);
|
||||
ftpPassword = properties.getProperty("ftp.password", ConfigConstants.DEFAULT_FTP_PASSWORD);
|
||||
ftpControlEncoding = properties.getProperty("ftp.control.encoding", ConfigConstants.DEFAULT_FTP_CONTROL_ENCODING);
|
||||
textArray = text.split(",");
|
||||
mediaArray = media.split(",");
|
||||
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);
|
||||
password = properties.getProperty("delete.password", ConfigConstants.DEFAULT_PASSWORD);
|
||||
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);
|
||||
ConfigConstants.setSimTextValue(textArray);
|
||||
ConfigConstants.setMediaValue(mediaArray);
|
||||
ConfigConstants.setOfficePreviewTypeValue(officePreviewType);
|
||||
ConfigConstants.setFtpUsernameValue(ftpUsername);
|
||||
ConfigConstants.setFtpPasswordValue(ftpPassword);
|
||||
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);
|
||||
updateConfigConstants(properties);
|
||||
setWatermarkConfig(properties);
|
||||
bufferedReader.close();
|
||||
fileReader.close();
|
||||
TimeUnit.SECONDS.sleep(1);
|
||||
LOGGER.info("配置文件重新加载完成");
|
||||
}
|
||||
} catch (IOException | InterruptedException e) {
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("读取配置文件异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void setWatermarkConfig(Properties properties) {
|
||||
String watermarkTxt = properties.getProperty("watermark.txt", WatermarkConfigConstants.DEFAULT_WATERMARK_TXT);
|
||||
String watermarkXSpace = properties.getProperty("watermark.x.space", WatermarkConfigConstants.DEFAULT_WATERMARK_X_SPACE);
|
||||
String watermarkYSpace = properties.getProperty("watermark.y.space", WatermarkConfigConstants.DEFAULT_WATERMARK_Y_SPACE);
|
||||
String watermarkFont = properties.getProperty("watermark.font", WatermarkConfigConstants.DEFAULT_WATERMARK_FONT);
|
||||
String watermarkFontsize = properties.getProperty("watermark.fontsize", WatermarkConfigConstants.DEFAULT_WATERMARK_FONTSIZE);
|
||||
String watermarkColor = properties.getProperty("watermark.color", WatermarkConfigConstants.DEFAULT_WATERMARK_COLOR);
|
||||
String watermarkAlpha = properties.getProperty("watermark.alpha", WatermarkConfigConstants.DEFAULT_WATERMARK_ALPHA);
|
||||
String watermarkWidth = properties.getProperty("watermark.width", WatermarkConfigConstants.DEFAULT_WATERMARK_WIDTH);
|
||||
String watermarkHeight = properties.getProperty("watermark.height", WatermarkConfigConstants.DEFAULT_WATERMARK_HEIGHT);
|
||||
String watermarkAngle = properties.getProperty("watermark.angle", WatermarkConfigConstants.DEFAULT_WATERMARK_ANGLE);
|
||||
WatermarkConfigConstants.setWatermarkTxtValue(watermarkTxt);
|
||||
WatermarkConfigConstants.setWatermarkXSpaceValue(watermarkXSpace);
|
||||
WatermarkConfigConstants.setWatermarkYSpaceValue(watermarkYSpace);
|
||||
WatermarkConfigConstants.setWatermarkFontValue(watermarkFont);
|
||||
WatermarkConfigConstants.setWatermarkFontsizeValue(watermarkFontsize);
|
||||
WatermarkConfigConstants.setWatermarkColorValue(watermarkColor);
|
||||
WatermarkConfigConstants.setWatermarkAlphaValue(watermarkAlpha);
|
||||
WatermarkConfigConstants.setWatermarkWidthValue(watermarkWidth);
|
||||
WatermarkConfigConstants.setWatermarkHeightValue(watermarkHeight);
|
||||
WatermarkConfigConstants.setWatermarkAngleValue(watermarkAngle);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateConfigConstants(Properties properties) {
|
||||
// 1. 缓存配置
|
||||
boolean cacheEnabled = Boolean.parseBoolean(getProperty(properties, "cache.enabled", ConfigConstants.DEFAULT_CACHE_ENABLED));
|
||||
ConfigConstants.setCacheEnabledValueValue(cacheEnabled);
|
||||
|
||||
// 2. 文件类型配置
|
||||
ConfigConstants.setSimTextValue(getProperty(properties, "simText", ConfigConstants.DEFAULT_TXT_TYPE).split(","));
|
||||
ConfigConstants.setMediaValue(getProperty(properties, "media", ConfigConstants.DEFAULT_MEDIA_TYPE).split(","));
|
||||
ConfigConstants.setConvertMediaValue(getProperty(properties, "convertMedias", "avi,mov,wmv,mkv,3gp,rm").split(","));
|
||||
ConfigConstants.setProhibitValue(getProperty(properties, "prohibit", ConfigConstants.DEFAULT_PROHIBIT).split(","));
|
||||
ConfigConstants.setMediaConvertDisableValue(getProperty(properties, "media.convert.disable", "true"));
|
||||
ConfigConstants.setTifPreviewTypeValue(getProperty(properties, "tif.preview.type", ConfigConstants.DEFAULT_TIF_PREVIEW_TYPE));
|
||||
ConfigConstants.setCadPreviewTypeValue(getProperty(properties, "cad.preview.type", ConfigConstants.DEFAULT_CAD_PREVIEW_TYPE));
|
||||
|
||||
// 3. Office配置
|
||||
ConfigConstants.setOfficePreviewTypeValue(getProperty(properties, "office.preview.type", ConfigConstants.DEFAULT_OFFICE_PREVIEW_TYPE));
|
||||
ConfigConstants.setOfficePreviewSwitchDisabledValue(getProperty(properties, "office.preview.switch.disabled", ConfigConstants.DEFAULT_OFFICE_PREVIEW_SWITCH_DISABLED));
|
||||
ConfigConstants.setOfficeTypeWebValue(getProperty(properties, "office.type.web", ConfigConstants.DEFAULT_OFFICE_TYPE_WEB));
|
||||
ConfigConstants.setOfficePageRangeValue(getProperty(properties, "office.pagerange", ConfigConstants.DEFAULT_OFFICE_PAQERANQE));
|
||||
ConfigConstants.setOfficeWatermarkValue(getProperty(properties, "office.watermark", ConfigConstants.DEFAULT_OFFICE_WATERMARK));
|
||||
ConfigConstants.setOfficeQualityValue(getProperty(properties, "office.quality", ConfigConstants.DEFAULT_OFFICE_QUALITY));
|
||||
ConfigConstants.setOfficeMaxImageResolutionValue(getProperty(properties, "office.maximageresolution", ConfigConstants.DEFAULT_OFFICE_MAXIMAQERESOLUTION));
|
||||
ConfigConstants.setOfficeExportBookmarksValue(Boolean.parseBoolean(getProperty(properties, "office.exportbookmarks", ConfigConstants.DEFAULT_OFFICE_EXPORTBOOKMARKS)));
|
||||
ConfigConstants.setOfficeExportNotesValue(Boolean.parseBoolean(getProperty(properties, "office.exportnotes", ConfigConstants.DEFAULT_OFFICE_EXPORTNOTES)));
|
||||
ConfigConstants.setOfficeDocumentOpenPasswordsValue(Boolean.parseBoolean(getProperty(properties, "office.documentopenpasswords", ConfigConstants.DEFAULT_OFFICE_EOCUMENTOPENPASSWORDS)));
|
||||
|
||||
// 4. FTP配置
|
||||
ConfigConstants.setFtpUsernameValue(getProperty(properties, "ftp.username", ConfigConstants.DEFAULT_FTP_USERNAME));
|
||||
|
||||
// 5. 路径配置
|
||||
ConfigConstants.setBaseUrlValue(getProperty(properties, "base.url", ConfigConstants.DEFAULT_VALUE));
|
||||
ConfigConstants.setFileDirValue(getProperty(properties, "file.dir", ConfigConstants.DEFAULT_VALUE));
|
||||
ConfigConstants.setLocalPreviewDirValue(getProperty(properties, "local.preview.dir", ConfigConstants.DEFAULT_VALUE));
|
||||
|
||||
// 6. 安全配置
|
||||
ConfigConstants.setTrustHostValue(getProperty(properties, "trust.host", ConfigConstants.DEFAULT_VALUE));
|
||||
ConfigConstants.setNotTrustHostValue(getProperty(properties, "not.trust.host", ConfigConstants.DEFAULT_VALUE));
|
||||
|
||||
// 7. PDF配置
|
||||
ConfigConstants.setPdfPresentationModeDisableValue(getProperty(properties, "pdf.presentationMode.disable", ConfigConstants.DEFAULT_PDF_PRESENTATION_MODE_DISABLE));
|
||||
ConfigConstants.setPdfOpenFileDisableValue(getProperty(properties, "pdf.openFile.disable", ConfigConstants.DEFAULT_PDF_OPEN_FILE_DISABLE));
|
||||
ConfigConstants.setPdfPrintDisableValue(getProperty(properties, "pdf.print.disable", ConfigConstants.DEFAULT_PDF_PRINT_DISABLE));
|
||||
ConfigConstants.setPdfDownloadDisableValue(getProperty(properties, "pdf.download.disable", ConfigConstants.DEFAULT_PDF_DOWNLOAD_DISABLE));
|
||||
ConfigConstants.setPdfBookmarkDisableValue(getProperty(properties, "pdf.bookmark.disable", ConfigConstants.DEFAULT_PDF_BOOKMARK_DISABLE));
|
||||
ConfigConstants.setPdfDisableEditingValue(getProperty(properties, "pdf.disable.editing", ConfigConstants.DEFAULT_PDF_DISABLE_EDITING));
|
||||
ConfigConstants.setPdf2JpgDpiValue(Integer.parseInt(getProperty(properties, "pdf2jpg.dpi", ConfigConstants.DEFAULT_PDF2_JPG_DPI)));
|
||||
|
||||
// 8. CAD配置
|
||||
ConfigConstants.setCadTimeoutValue(getProperty(properties, "cad.timeout", ConfigConstants.DEFAULT_CAD_TIMEOUT));
|
||||
ConfigConstants.setCadThreadValue(Integer.parseInt(getProperty(properties, "cad.thread", ConfigConstants.DEFAULT_CAD_THREAD)));
|
||||
ConfigConstants.setCadConverterPathValue(getProperty(properties, "cad.cadconverterpath", ConfigConstants.DEFAULT_CAD_CONVERT));
|
||||
ConfigConstants.setconversionModuleValue(Integer.parseInt(getProperty(properties, "cad.conversionmodule", ConfigConstants.DEFAULT_CAD_VERSION)));
|
||||
|
||||
// 9. TIF配置
|
||||
ConfigConstants.setTifTimeoutValue(getProperty(properties, "tif.timeout", ConfigConstants.DEFAULT_TIF_TIMEOUT));
|
||||
ConfigConstants.setTifThreadValue(Integer.parseInt(getProperty(properties, "tif.thread", ConfigConstants.DEFAULT_TIF_THREAD)));
|
||||
|
||||
// 10. 文件操作配置
|
||||
ConfigConstants.setFileUploadDisableValue(Boolean.parseBoolean(getProperty(properties, "file.upload.disable", ConfigConstants.DEFAULT_FILE_UPLOAD_DISABLE)));
|
||||
ConfigConstants.setSizeValue(getProperty(properties, "spring.servlet.multipart.max-file-size", ConfigConstants.DEFAULT_SIZE));
|
||||
ConfigConstants.setPasswordValue(getProperty(properties, "delete.password", ConfigConstants.DEFAULT_PASSWORD));
|
||||
ConfigConstants.setDeleteSourceFileValue(Boolean.parseBoolean(getProperty(properties, "delete.source.file", ConfigConstants.DEFAULT_DELETE_SOURCE_FILE)));
|
||||
ConfigConstants.setDeleteCaptchaValue(Boolean.parseBoolean(getProperty(properties, "delete.captcha", ConfigConstants.DEFAULT_DELETE_CAPTCHA)));
|
||||
|
||||
// 11. 首页配置
|
||||
ConfigConstants.setBeianValue(getProperty(properties, "beian", ConfigConstants.DEFAULT_BEIAN));
|
||||
ConfigConstants.setHomePageNumberValue(getProperty(properties, "home.pagenumber", ConfigConstants.DEFAULT_HOME_PAGENUMBER));
|
||||
ConfigConstants.setHomePaginationValue(getProperty(properties, "home.pagination", ConfigConstants.DEFAULT_HOME_PAGINATION));
|
||||
ConfigConstants.setHomePageSizeValue(getProperty(properties, "home.pagesize", ConfigConstants.DEFAULT_HOME_PAGSIZE));
|
||||
ConfigConstants.setHomeSearchValue(getProperty(properties, "home.search", ConfigConstants.DEFAULT_HOME_SEARCH));
|
||||
|
||||
// 12. 权限配置
|
||||
ConfigConstants.setKeyValue(getProperty(properties, "kk.key", ConfigConstants.DEFAULT_KEY));
|
||||
ConfigConstants.setPicturesPreviewValue(Boolean.parseBoolean(getProperty(properties, "kk.Picturespreview", ConfigConstants.DEFAULT_PICTURES_PREVIEW)));
|
||||
ConfigConstants.setGetCorsFileValue(Boolean.parseBoolean(getProperty(properties, "kk.Getcorsfile", ConfigConstants.DEFAULT_GET_CORS_FILE)));
|
||||
ConfigConstants.setAddTaskValue(Boolean.parseBoolean(getProperty(properties, "kk.addTask", ConfigConstants.DEFAULT_ADD_TASK)));
|
||||
ConfigConstants.setaesKeyValue(getProperty(properties, "aes.key", ConfigConstants.DEFAULT_AES_KEY));
|
||||
|
||||
// 13. UserAgent配置
|
||||
ConfigConstants.setUserAgentValue(getProperty(properties, "useragent", ConfigConstants.DEFAULT_USER_AGENT));
|
||||
|
||||
// 14. Basic认证配置
|
||||
ConfigConstants.setBasicNameValue(getProperty(properties, "basic.name", ConfigConstants.DEFAULT_BASIC_NAME));
|
||||
|
||||
// 15. 视频转换配置
|
||||
ConfigConstants.setMediaConvertMaxSizeValue(Integer.parseInt(getProperty(properties, "media.convert.max.size", ConfigConstants.DEFAULT_MEDIA_CONVERT_MAX_SIZE)));
|
||||
ConfigConstants.setMediaTimeoutEnabledValue(Boolean.parseBoolean(getProperty(properties, "media.timeout.enabled", ConfigConstants.DEFAULT_MEDIA_TIMEOUT_ENABLED)));
|
||||
ConfigConstants.setMediaSmallFileTimeoutValue(Integer.parseInt(getProperty(properties, "media.small.file.timeout", ConfigConstants.DEFAULT_MEDIA_SMALL_FILE_TIMEOUT)));
|
||||
ConfigConstants.setMediaMediumFileTimeoutValue(Integer.parseInt(getProperty(properties, "media.medium.file.timeout", ConfigConstants.DEFAULT_MEDIA_MEDIUM_FILE_TIMEOUT)));
|
||||
ConfigConstants.setMediaLargeFileTimeoutValue(Integer.parseInt(getProperty(properties, "media.large.file.timeout", ConfigConstants.DEFAULT_MEDIA_LARGE_FILE_TIMEOUT)));
|
||||
ConfigConstants.setMediaXLFileTimeoutValue(Integer.parseInt(getProperty(properties, "media.xl.file.timeout", ConfigConstants.DEFAULT_MEDIA_XL_FILE_TIMEOUT)));
|
||||
ConfigConstants.setMediaXXLFileTimeoutValue(Integer.parseInt(getProperty(properties, "media.xxl.file.timeout", ConfigConstants.DEFAULT_MEDIA_XXL_FILE_TIMEOUT)));
|
||||
ConfigConstants.setMediaXXXLFileTimeoutValue(Integer.parseInt(getProperty(properties, "media.xxxl.file.timeout", ConfigConstants.DEFAULT_MEDIA_XXXL_FILE_TIMEOUT)));
|
||||
|
||||
// 16. PDF DPI配置
|
||||
ConfigConstants.setPdfDpiEnabledValue(Boolean.parseBoolean(getProperty(properties, "pdf.dpi.enabled", ConfigConstants.DEFAULT_PDF_DPI_ENABLED)));
|
||||
ConfigConstants.setPdfSmallDpiValue(Integer.parseInt(getProperty(properties, "pdf.dpi.small", ConfigConstants.DEFAULT_PDF_SMALL_DTI)));
|
||||
ConfigConstants.setPdfMediumDpiValue(Integer.parseInt(getProperty(properties, "pdf.dpi.medium", ConfigConstants.DEFAULT_PDF_MEDIUM_DPI)));
|
||||
ConfigConstants.setPdfLargeDpiValue(Integer.parseInt(getProperty(properties, "pdf.dpi.large", ConfigConstants.DEFAULT_PDF_LARGE_DPI)));
|
||||
ConfigConstants.setPdfXLargeDpiValue(Integer.parseInt(getProperty(properties, "pdf.dpi.xlarge", ConfigConstants.DEFAULT_PDF_XLARGE_DPI)));
|
||||
ConfigConstants.setPdfXXLargeDpiValue(Integer.parseInt(getProperty(properties, "pdf.dpi.xxlarge", ConfigConstants.DEFAULT_PDF_XXLARGE_DPI)));
|
||||
|
||||
// 17. PDF超时配置(新)
|
||||
ConfigConstants.setPdfTimeoutSmallValue(Integer.parseInt(getProperty(properties, "pdf.timeout.small", ConfigConstants.DEFAULT_PDF_TIMEOUT_SMALL)));
|
||||
ConfigConstants.setPdfTimeoutMediumValue(Integer.parseInt(getProperty(properties, "pdf.timeout.medium", ConfigConstants.DEFAULT_PDF_TIMEOUT_MEDIUM)));
|
||||
ConfigConstants.setPdfTimeoutLargeValue(Integer.parseInt(getProperty(properties, "pdf.timeout.large", ConfigConstants.DEFAULT_PDF_TIMEOUT_LARGE)));
|
||||
ConfigConstants.setPdfTimeoutXLargeValue(Integer.parseInt(getProperty(properties, "pdf.timeout.xlarge", ConfigConstants.DEFAULT_PDF_TIMEOUT_XLARGE)));
|
||||
|
||||
// 18. PDF线程配置
|
||||
ConfigConstants.setPdfMaxThreadsValue(Integer.parseInt(getProperty(properties, "pdf.max.threads", ConfigConstants.DEFAULT_PDF_MAX_THREADS)));
|
||||
|
||||
// 19. CAD水印配置
|
||||
ConfigConstants.setCadwatermarkValue(Boolean.parseBoolean(getProperty(properties, "cad.watermark", ConfigConstants.DEFAULT_CAD_WATERMARK)));
|
||||
|
||||
// 20. SSL忽略配置
|
||||
ConfigConstants.setIgnoreSSLValue(Boolean.parseBoolean(getProperty(properties, "kk.ignore.ssl", ConfigConstants.DEFAULT_IGNORE_SSL)));
|
||||
|
||||
// 21. 重定向启用配置
|
||||
ConfigConstants.setEnableRedirectValue(Boolean.parseBoolean(getProperty(properties, "kk.enable.redirect", ConfigConstants.DEFAULT_ENABLE_REDIRECT)));
|
||||
|
||||
// 22. 异步定时刷新
|
||||
ConfigConstants.setRefreshScheduleValue(Integer.parseInt(getProperty(properties, "kk.refreshschedule", ConfigConstants.DEFAULT_ENABLE_REFRECSHSCHEDULE)));
|
||||
|
||||
// 23. 其他配置
|
||||
ConfigConstants.setIsShowaesKeyValue(Boolean.parseBoolean(getProperty(properties, "kk.isshowaeskey", ConfigConstants.DEFAULT_SHOW_AES_KEY)));
|
||||
ConfigConstants.setIsJavaScriptValue(Boolean.parseBoolean(getProperty(properties, "kk.isjavascript", ConfigConstants.DEFAULT_IS_JAVASCRIPT)));
|
||||
ConfigConstants.setXlsxAllowEditValue(Boolean.parseBoolean(getProperty(properties, "kk.xlsxallowedit", ConfigConstants.DEFAULT_XLSX_ALLOW_EDIT)));
|
||||
ConfigConstants.setXlsxShowtoolbarValue(Boolean.parseBoolean(getProperty(properties, "kk.xlsxshowtoolbar", ConfigConstants.DEFAULT_XLSX_SHOW_TOOLBAR)));
|
||||
ConfigConstants.setisShowKeyValue(Boolean.parseBoolean(getProperty(properties, "kk.isshowkey", ConfigConstants.DEFAULT_IS_SHOW_KEY)));
|
||||
ConfigConstants.setscriptJsValue(Boolean.parseBoolean(getProperty(properties, "kk.scriptjs", ConfigConstants.DEFAULT_SCRIPT_JS)));
|
||||
}
|
||||
|
||||
private String getProperty(Properties properties, String key, String defaultValue) {
|
||||
return properties.getProperty(key, defaultValue).trim();
|
||||
}
|
||||
|
||||
private void setWatermarkConfig(Properties properties) {
|
||||
WatermarkConfigConstants.setWatermarkTxtValue(getProperty(properties, "watermark.txt", WatermarkConfigConstants.DEFAULT_WATERMARK_TXT));
|
||||
WatermarkConfigConstants.setWatermarkXSpaceValue(getProperty(properties, "watermark.x.space", WatermarkConfigConstants.DEFAULT_WATERMARK_X_SPACE));
|
||||
WatermarkConfigConstants.setWatermarkYSpaceValue(getProperty(properties, "watermark.y.space", WatermarkConfigConstants.DEFAULT_WATERMARK_Y_SPACE));
|
||||
WatermarkConfigConstants.setWatermarkFontValue(getProperty(properties, "watermark.font", WatermarkConfigConstants.DEFAULT_WATERMARK_FONT));
|
||||
WatermarkConfigConstants.setWatermarkFontsizeValue(getProperty(properties, "watermark.fontsize", WatermarkConfigConstants.DEFAULT_WATERMARK_FONTSIZE));
|
||||
WatermarkConfigConstants.setWatermarkColorValue(getProperty(properties, "watermark.color", WatermarkConfigConstants.DEFAULT_WATERMARK_COLOR));
|
||||
WatermarkConfigConstants.setWatermarkAlphaValue(getProperty(properties, "watermark.alpha", WatermarkConfigConstants.DEFAULT_WATERMARK_ALPHA));
|
||||
WatermarkConfigConstants.setWatermarkWidthValue(getProperty(properties, "watermark.width", WatermarkConfigConstants.DEFAULT_WATERMARK_WIDTH));
|
||||
WatermarkConfigConstants.setWatermarkHeightValue(getProperty(properties, "watermark.height", WatermarkConfigConstants.DEFAULT_WATERMARK_HEIGHT));
|
||||
WatermarkConfigConstants.setWatermarkAngleValue(getProperty(properties, "watermark.angle", WatermarkConfigConstants.DEFAULT_WATERMARK_ANGLE));
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,8 @@ package cn.keking.config;
|
||||
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.redisson.Redisson;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.redisson.client.codec.Codec;
|
||||
import org.redisson.config.Config;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
|
||||
@@ -11,146 +13,166 @@ import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Redisson 客户端配置
|
||||
* Created by kl on 2017/09/26.
|
||||
* redisson 客户端配置
|
||||
*/
|
||||
@ConditionalOnExpression("'${cache.type:default}'.equals('redis')")
|
||||
@ConfigurationProperties(prefix = "spring.redisson")
|
||||
@Configuration
|
||||
public class RedissonConfig {
|
||||
|
||||
private String address;
|
||||
private int connectionMinimumIdleSize = 10;
|
||||
private int idleConnectionTimeout=10000;
|
||||
private int pingTimeout=1000;
|
||||
private int connectTimeout=10000;
|
||||
private int timeout=3000;
|
||||
private int retryAttempts=3;
|
||||
private int retryInterval=1500;
|
||||
private int reconnectionTimeout=3000;
|
||||
private int failedAttempts=3;
|
||||
private String password = null;
|
||||
private int subscriptionsPerConnection=5;
|
||||
private String clientName=null;
|
||||
private int subscriptionConnectionMinimumIdleSize = 1;
|
||||
private int subscriptionConnectionPoolSize = 50;
|
||||
private int connectionPoolSize = 64;
|
||||
private int database = 0;
|
||||
private boolean dnsMonitoring = false;
|
||||
private int dnsMonitoringInterval = 5000;
|
||||
// ========================== 连接配置 ==========================
|
||||
private static String address;
|
||||
private static String password;
|
||||
private static String clientName;
|
||||
private static int database = 0;
|
||||
private static String mode = "single";
|
||||
private static String masterName = "kkfile";
|
||||
|
||||
private int thread; //当前处理核数量 * 2
|
||||
// ========================== 超时配置 ==========================
|
||||
private static int idleConnectionTimeout = 10000;
|
||||
private static int connectTimeout = 10000;
|
||||
private static int timeout = 3000;
|
||||
|
||||
private String codec="org.redisson.codec.JsonJacksonCodec";
|
||||
// ========================== 重试配置 ==========================
|
||||
private static int retryAttempts = 3;
|
||||
private static int retryInterval = 1500;
|
||||
|
||||
// ========================== 连接池配置 ==========================
|
||||
private static int connectionMinimumIdleSize = 10;
|
||||
private static int connectionPoolSize = 64;
|
||||
private static int subscriptionsPerConnection = 5;
|
||||
private static int subscriptionConnectionMinimumIdleSize = 1;
|
||||
private static int subscriptionConnectionPoolSize = 50;
|
||||
|
||||
// ========================== 其他配置 ==========================
|
||||
private static int dnsMonitoringInterval = 5000;
|
||||
private static int thread; // 当前处理核数量 * 2
|
||||
private static String codec = "org.redisson.codec.JsonJacksonCodec";
|
||||
|
||||
@Bean
|
||||
Config config() throws Exception {
|
||||
public static RedissonClient config() throws Exception {
|
||||
Config config = new Config();
|
||||
config.useSingleServer().setAddress(address)
|
||||
|
||||
// 密码处理
|
||||
if (StringUtils.isBlank(password)) {
|
||||
password = null;
|
||||
}
|
||||
|
||||
// 根据模式创建对应的 Redisson 配置
|
||||
switch (mode) {
|
||||
case "cluster":
|
||||
configureClusterMode(config);
|
||||
break;
|
||||
case "master-slave":
|
||||
configureMasterSlaveMode(config);
|
||||
break;
|
||||
case "sentinel":
|
||||
configureSentinelMode(config);
|
||||
break;
|
||||
default:
|
||||
configureSingleMode(config);
|
||||
break;
|
||||
}
|
||||
|
||||
return Redisson.create(config);
|
||||
}
|
||||
|
||||
// ========================== 配置方法 ==========================
|
||||
|
||||
/**
|
||||
* 配置集群模式
|
||||
*/
|
||||
private static void configureClusterMode(Config config) {
|
||||
String[] clusterAddresses = address.split(",");
|
||||
config.useClusterServers()
|
||||
.setScanInterval(2000)
|
||||
.addNodeAddress(clusterAddresses)
|
||||
.setPassword(password)
|
||||
.setRetryAttempts(retryAttempts)
|
||||
.setTimeout(timeout)
|
||||
.setMasterConnectionPoolSize(100)
|
||||
.setSlaveConnectionPoolSize(100);
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置主从模式
|
||||
*/
|
||||
private static void configureMasterSlaveMode(Config config) {
|
||||
String[] masterSlaveAddresses = address.split(",");
|
||||
validateMasterSlaveAddresses(masterSlaveAddresses);
|
||||
|
||||
String[] slaveAddresses = new String[masterSlaveAddresses.length - 1];
|
||||
System.arraycopy(masterSlaveAddresses, 1, slaveAddresses, 0, slaveAddresses.length);
|
||||
|
||||
config.useMasterSlaveServers()
|
||||
.setDatabase(database)
|
||||
.setPassword(password)
|
||||
.setMasterAddress(masterSlaveAddresses[0])
|
||||
.addSlaveAddress(slaveAddresses);
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置哨兵模式
|
||||
*/
|
||||
private static void configureSentinelMode(Config config) {
|
||||
String[] sentinelAddresses = address.split(",");
|
||||
config.useSentinelServers()
|
||||
.setDatabase(database)
|
||||
.setPassword(password)
|
||||
.setMasterName(masterName)
|
||||
.addSentinelAddress(sentinelAddresses);
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置单机模式
|
||||
*/
|
||||
private static void configureSingleMode(Config config) throws Exception {
|
||||
config.useSingleServer()
|
||||
.setAddress(address)
|
||||
.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);
|
||||
|
||||
// 设置编码器
|
||||
Class<?> codecClass = ClassUtils.forName(getCodec(), ClassUtils.getDefaultClassLoader());
|
||||
Codec codecInstance = (Codec) codecClass.getDeclaredConstructor().newInstance();
|
||||
config.setCodec(codecInstance);
|
||||
// 设置线程和事件循环组
|
||||
config.setThreads(thread);
|
||||
config.setEventLoopGroup(new NioEventLoopGroup());
|
||||
config.setUseLinuxNativeEpoll(false);
|
||||
return config;
|
||||
}
|
||||
|
||||
public int getThread() {
|
||||
return thread;
|
||||
/**
|
||||
* 验证主从模式地址
|
||||
*/
|
||||
private static void validateMasterSlaveAddresses(String[] addresses) {
|
||||
if (addresses.length == 1) {
|
||||
throw new IllegalArgumentException(
|
||||
"redis.redisson.address MUST have multiple redis addresses for master-slave mode.");
|
||||
}
|
||||
}
|
||||
|
||||
public void setThread(int thread) {
|
||||
this.thread = thread;
|
||||
}
|
||||
// ========================== Getter和Setter方法 ==========================
|
||||
|
||||
// 连接配置
|
||||
public String getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public void setAddress(String address) {
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
public int getIdleConnectionTimeout() {
|
||||
return idleConnectionTimeout;
|
||||
}
|
||||
|
||||
public void setIdleConnectionTimeout(int idleConnectionTimeout) {
|
||||
this.idleConnectionTimeout = idleConnectionTimeout;
|
||||
}
|
||||
|
||||
public int getPingTimeout() {
|
||||
return pingTimeout;
|
||||
}
|
||||
|
||||
public void setPingTimeout(int pingTimeout) {
|
||||
this.pingTimeout = pingTimeout;
|
||||
}
|
||||
|
||||
public int getConnectTimeout() {
|
||||
return connectTimeout;
|
||||
}
|
||||
|
||||
public void setConnectTimeout(int connectTimeout) {
|
||||
this.connectTimeout = connectTimeout;
|
||||
}
|
||||
|
||||
public int getTimeout() {
|
||||
return timeout;
|
||||
}
|
||||
|
||||
public void setTimeout(int timeout) {
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
public int getRetryAttempts() {
|
||||
return retryAttempts;
|
||||
}
|
||||
|
||||
public void setRetryAttempts(int retryAttempts) {
|
||||
this.retryAttempts = retryAttempts;
|
||||
}
|
||||
|
||||
public int getRetryInterval() {
|
||||
return retryInterval;
|
||||
}
|
||||
|
||||
public void setRetryInterval(int retryInterval) {
|
||||
this.retryInterval = retryInterval;
|
||||
}
|
||||
|
||||
public int getReconnectionTimeout() {
|
||||
return reconnectionTimeout;
|
||||
}
|
||||
|
||||
public void setReconnectionTimeout(int reconnectionTimeout) {
|
||||
this.reconnectionTimeout = reconnectionTimeout;
|
||||
}
|
||||
|
||||
public int getFailedAttempts() {
|
||||
return failedAttempts;
|
||||
}
|
||||
|
||||
public void setFailedAttempts(int failedAttempts) {
|
||||
this.failedAttempts = failedAttempts;
|
||||
RedissonConfig.address = address;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
@@ -158,15 +180,7 @@ public class RedissonConfig {
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public int getSubscriptionsPerConnection() {
|
||||
return subscriptionsPerConnection;
|
||||
}
|
||||
|
||||
public void setSubscriptionsPerConnection(int subscriptionsPerConnection) {
|
||||
this.subscriptionsPerConnection = subscriptionsPerConnection;
|
||||
RedissonConfig.password = password;
|
||||
}
|
||||
|
||||
public String getClientName() {
|
||||
@@ -174,39 +188,7 @@ public class RedissonConfig {
|
||||
}
|
||||
|
||||
public void setClientName(String clientName) {
|
||||
this.clientName = clientName;
|
||||
}
|
||||
|
||||
public int getSubscriptionConnectionMinimumIdleSize() {
|
||||
return subscriptionConnectionMinimumIdleSize;
|
||||
}
|
||||
|
||||
public void setSubscriptionConnectionMinimumIdleSize(int subscriptionConnectionMinimumIdleSize) {
|
||||
this.subscriptionConnectionMinimumIdleSize = subscriptionConnectionMinimumIdleSize;
|
||||
}
|
||||
|
||||
public int getSubscriptionConnectionPoolSize() {
|
||||
return subscriptionConnectionPoolSize;
|
||||
}
|
||||
|
||||
public void setSubscriptionConnectionPoolSize(int subscriptionConnectionPoolSize) {
|
||||
this.subscriptionConnectionPoolSize = subscriptionConnectionPoolSize;
|
||||
}
|
||||
|
||||
public int getConnectionMinimumIdleSize() {
|
||||
return connectionMinimumIdleSize;
|
||||
}
|
||||
|
||||
public void setConnectionMinimumIdleSize(int connectionMinimumIdleSize) {
|
||||
this.connectionMinimumIdleSize = connectionMinimumIdleSize;
|
||||
}
|
||||
|
||||
public int getConnectionPoolSize() {
|
||||
return connectionPoolSize;
|
||||
}
|
||||
|
||||
public void setConnectionPoolSize(int connectionPoolSize) {
|
||||
this.connectionPoolSize = connectionPoolSize;
|
||||
RedissonConfig.clientName = clientName;
|
||||
}
|
||||
|
||||
public int getDatabase() {
|
||||
@@ -214,30 +196,130 @@ public class RedissonConfig {
|
||||
}
|
||||
|
||||
public void setDatabase(int database) {
|
||||
this.database = database;
|
||||
RedissonConfig.database = database;
|
||||
}
|
||||
|
||||
public boolean isDnsMonitoring() {
|
||||
return dnsMonitoring;
|
||||
public static String getMode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
public void setDnsMonitoring(boolean dnsMonitoring) {
|
||||
this.dnsMonitoring = dnsMonitoring;
|
||||
public void setMode(String mode) {
|
||||
RedissonConfig.mode = mode;
|
||||
}
|
||||
|
||||
public static String getMasterNamee() {
|
||||
return masterName;
|
||||
}
|
||||
|
||||
public void setMasterNamee(String masterName) {
|
||||
RedissonConfig.masterName = masterName;
|
||||
}
|
||||
|
||||
// 超时配置
|
||||
public int getIdleConnectionTimeout() {
|
||||
return idleConnectionTimeout;
|
||||
}
|
||||
|
||||
public void setIdleConnectionTimeout(int idleConnectionTimeout) {
|
||||
RedissonConfig.idleConnectionTimeout = idleConnectionTimeout;
|
||||
}
|
||||
|
||||
public int getConnectTimeout() {
|
||||
return connectTimeout;
|
||||
}
|
||||
|
||||
public void setConnectTimeout(int connectTimeout) {
|
||||
RedissonConfig.connectTimeout = connectTimeout;
|
||||
}
|
||||
|
||||
public int getTimeout() {
|
||||
return timeout;
|
||||
}
|
||||
|
||||
public void setTimeout(int timeout) {
|
||||
RedissonConfig.timeout = timeout;
|
||||
}
|
||||
|
||||
// 重试配置
|
||||
public int getRetryAttempts() {
|
||||
return retryAttempts;
|
||||
}
|
||||
|
||||
public void setRetryAttempts(int retryAttempts) {
|
||||
RedissonConfig.retryAttempts = retryAttempts;
|
||||
}
|
||||
|
||||
public int getRetryInterval() {
|
||||
return retryInterval;
|
||||
}
|
||||
|
||||
public void setRetryInterval(int retryInterval) {
|
||||
RedissonConfig.retryInterval = retryInterval;
|
||||
}
|
||||
|
||||
// 连接池配置
|
||||
public int getConnectionMinimumIdleSize() {
|
||||
return connectionMinimumIdleSize;
|
||||
}
|
||||
|
||||
public void setConnectionMinimumIdleSize(int connectionMinimumIdleSize) {
|
||||
RedissonConfig.connectionMinimumIdleSize = connectionMinimumIdleSize;
|
||||
}
|
||||
|
||||
public int getConnectionPoolSize() {
|
||||
return connectionPoolSize;
|
||||
}
|
||||
|
||||
public void setConnectionPoolSize(int connectionPoolSize) {
|
||||
RedissonConfig.connectionPoolSize = connectionPoolSize;
|
||||
}
|
||||
|
||||
public int getSubscriptionsPerConnection() {
|
||||
return subscriptionsPerConnection;
|
||||
}
|
||||
|
||||
public void setSubscriptionsPerConnection(int subscriptionsPerConnection) {
|
||||
RedissonConfig.subscriptionsPerConnection = subscriptionsPerConnection;
|
||||
}
|
||||
|
||||
public int getSubscriptionConnectionMinimumIdleSize() {
|
||||
return subscriptionConnectionMinimumIdleSize;
|
||||
}
|
||||
|
||||
public void setSubscriptionConnectionMinimumIdleSize(int subscriptionConnectionMinimumIdleSize) {
|
||||
RedissonConfig.subscriptionConnectionMinimumIdleSize = subscriptionConnectionMinimumIdleSize;
|
||||
}
|
||||
|
||||
public int getSubscriptionConnectionPoolSize() {
|
||||
return subscriptionConnectionPoolSize;
|
||||
}
|
||||
|
||||
public void setSubscriptionConnectionPoolSize(int subscriptionConnectionPoolSize) {
|
||||
RedissonConfig.subscriptionConnectionPoolSize = subscriptionConnectionPoolSize;
|
||||
}
|
||||
|
||||
// 其他配置
|
||||
public int getDnsMonitoringInterval() {
|
||||
return dnsMonitoringInterval;
|
||||
}
|
||||
|
||||
public void setDnsMonitoringInterval(int dnsMonitoringInterval) {
|
||||
this.dnsMonitoringInterval = dnsMonitoringInterval;
|
||||
RedissonConfig.dnsMonitoringInterval = dnsMonitoringInterval;
|
||||
}
|
||||
|
||||
public String getCodec() {
|
||||
public int getThread() {
|
||||
return thread;
|
||||
}
|
||||
|
||||
public void setThread(int thread) {
|
||||
RedissonConfig.thread = thread;
|
||||
}
|
||||
|
||||
public static String getCodec() {
|
||||
return codec;
|
||||
}
|
||||
|
||||
public void setCodec(String codec) {
|
||||
this.codec = codec;
|
||||
RedissonConfig.codec = codec;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ public enum FileType {
|
||||
MEDIACONVERT("mediaFilePreviewImpl"),
|
||||
MARKDOWN("markdownFilePreviewImpl"),
|
||||
XML("xmlFilePreviewImpl"),
|
||||
JSON("jsonFilePreviewImpl"),
|
||||
CAD("cadFilePreviewImpl"),
|
||||
TIFF("tiffFilePreviewImpl"),
|
||||
OFD("ofdFilePreviewImpl"),
|
||||
@@ -32,24 +33,27 @@ public enum FileType {
|
||||
EPUB("epubFilePreviewImpl"),
|
||||
BPMN("bpmnFilePreviewImpl"),
|
||||
DCM("dcmFilePreviewImpl"),
|
||||
MSG("msgFilePreviewImpl"),
|
||||
DRAWIO("drawioFilePreviewImpl");
|
||||
|
||||
private static final String[] OFFICE_TYPES = {"docx", "wps", "doc", "docm", "xls", "xlsx", "csv" ,"xlsm", "ppt", "pptx", "vsd", "rtf", "odt", "wmf", "emf", "dps", "et", "ods", "ots", "tsv", "odp", "otp", "sxi", "ott", "vsdx", "fodt", "fods", "xltx","tga","psd","dotm","ett","xlt","xltm","wpt","dot","xlam","dotx","xla","pages", "eps"};
|
||||
private static final String[] PICTURE_TYPES = {"jpg", "jpeg", "png", "gif", "bmp", "ico", "jfif", "webp"};
|
||||
private static final String[] OFFICE_TYPES = {"docx", "wps", "doc", "docm", "xls", "xlsx", "csv" ,"xlsm", "ppt", "pptx", "vsd", "rtf", "odt", "wmf", "emf", "dps", "et", "ods", "ots", "tsv", "odp", "otp", "sxi", "ott", "vsdx", "fodt", "fods", "xltx","tga","psd","dotm","ett","xlt","xltm","wpt","dot","xlam","dotx","xla","pages", "eps", "pptm"};
|
||||
private static final String[] PICTURE_TYPES = {"jpg", "jpeg", "png", "gif", "bmp", "ico", "jfif", "webp", "heic", "avif", "heif"};
|
||||
private static final String[] ARCHIVE_TYPES = {"rar", "zip", "jar", "7-zip", "tar", "gzip", "7z"};
|
||||
private static final String[] ONLINE3D_TYPES = {"obj", "3ds", "stl", "ply", "off", "3dm", "fbx", "dae", "wrl", "3mf", "ifc","glb","o3dv","gltf","stp","bim","fcstd","step","iges","brep"};
|
||||
private static final String[] EML_TYPES = {"eml"};
|
||||
private static final String[] MSG_TYPES = {"msg"};
|
||||
private static final String[] XMIND_TYPES = {"xmind"};
|
||||
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_CONVERT_TYPES = ConfigConstants.getConvertMedias();
|
||||
private static final Map<String, FileType> FILE_TYPE_MAPPER = new HashMap<>();
|
||||
@@ -94,6 +98,9 @@ public enum FileType {
|
||||
for (String eml : EML_TYPES) {
|
||||
FILE_TYPE_MAPPER.put(eml, FileType.EML);
|
||||
}
|
||||
for (String msg : MSG_TYPES) {
|
||||
FILE_TYPE_MAPPER.put(msg, FileType.MSG);
|
||||
}
|
||||
for (String xmind : XMIND_TYPES) {
|
||||
FILE_TYPE_MAPPER.put(xmind, FileType.XMIND);
|
||||
}
|
||||
@@ -109,6 +116,9 @@ public enum FileType {
|
||||
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("pdf", FileType.PDF);
|
||||
FILE_TYPE_MAPPER.put("bpmn", FileType.BPMN);
|
||||
|
||||