mirror of
https://gitee.com/kekingcn/file-online-preview.git
synced 2026-09-13 00:14:56 +00:00
Compare commits
8 Commits
pr731
...
codex/ci-a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
37bda20d08 | ||
|
|
1819861647 | ||
|
|
352b86b40d | ||
|
|
853ad0154f | ||
|
|
c88bf04a0d | ||
|
|
6a84e61ecb | ||
|
|
dd6e369e6a | ||
|
|
bd20546b6d |
107
.github/scripts/deploy_windows_winrm.py
vendored
Normal file
107
.github/scripts/deploy_windows_winrm.py
vendored
Normal file
@@ -0,0 +1,107 @@
|
||||
#!/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")
|
||||
deploy_root = optional_env("KK_DEPLOY_ROOT", r"C:\kkFileView-5.0")
|
||||
health_url = optional_env("KK_DEPLOY_HEALTH_URL", "http://127.0.0.1:8012/")
|
||||
artifact_url = require_env("KK_DEPLOY_ARTIFACT_URL")
|
||||
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 {{
|
||||
$env:KK_DEPLOY_ARTIFACT_URL = '{ps_quote(artifact_url)}'
|
||||
$env:KK_DEPLOY_ROOT = '{ps_quote(deploy_root)}'
|
||||
$env:KK_DEPLOY_HEALTH_URL = '{ps_quote(health_url)}'
|
||||
$env:KK_DEPLOY_DRY_RUN = '{ps_quote(dry_run)}'
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File '{ps_quote(remote_ps1_path)}' `
|
||||
$code = $LASTEXITCODE
|
||||
}} finally {{
|
||||
Remove-Item Env:KK_DEPLOY_ARTIFACT_URL -ErrorAction SilentlyContinue
|
||||
Remove-Item Env:KK_DEPLOY_ROOT -ErrorAction SilentlyContinue
|
||||
Remove-Item Env:KK_DEPLOY_HEALTH_URL -ErrorAction SilentlyContinue
|
||||
Remove-Item Env:KK_DEPLOY_DRY_RUN -ErrorAction SilentlyContinue
|
||||
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())
|
||||
203
.github/scripts/remote_windows_deploy.ps1
vendored
Normal file
203
.github/scripts/remote_windows_deploy.ps1
vendored
Normal file
@@ -0,0 +1,203 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
$ArtifactDownloadUrl = Get-RequiredEnv 'KK_DEPLOY_ARTIFACT_URL'
|
||||
$DeployRoot = Get-OptionalEnv 'KK_DEPLOY_ROOT' 'C:\kkFileView-5.0'
|
||||
$HealthUrl = Get-OptionalEnv 'KK_DEPLOY_HEALTH_URL' 'http://127.0.0.1:8012/'
|
||||
$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'
|
||||
$ArtifactZip = Join-Path $DeployTmp 'artifact.zip'
|
||||
$ExtractDir = Join-Path $DeployTmp 'artifact'
|
||||
|
||||
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"
|
||||
}
|
||||
|
||||
$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"
|
||||
|
||||
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
|
||||
|
||||
if (Test-Path $ArtifactZip) {
|
||||
Remove-Item $ArtifactZip -Force
|
||||
}
|
||||
|
||||
if (Test-Path $ExtractDir) {
|
||||
Remove-Item $ExtractDir -Recurse -Force
|
||||
}
|
||||
|
||||
Write-Step 'Downloading workflow artifact via signed URL'
|
||||
$PreviousProgressPreference = $ProgressPreference
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
try {
|
||||
Invoke-WebRequest -Uri $ArtifactDownloadUrl -OutFile $ArtifactZip -UseBasicParsing -TimeoutSec 120
|
||||
} finally {
|
||||
$ProgressPreference = $PreviousProgressPreference
|
||||
}
|
||||
|
||||
if (-not (Test-Path $ArtifactZip)) {
|
||||
throw "Artifact zip was not created: $ArtifactZip"
|
||||
}
|
||||
|
||||
$ArtifactZipInfo = Get-Item $ArtifactZip
|
||||
if ($ArtifactZipInfo.Length -le 0) {
|
||||
throw "Downloaded artifact zip is empty: $ArtifactZip"
|
||||
}
|
||||
|
||||
Expand-Archive -LiteralPath $ArtifactZip -DestinationPath $ExtractDir -Force
|
||||
|
||||
$DownloadedJars = Get-ChildItem $ExtractDir -Filter 'kkFileView-*.jar' -Recurse
|
||||
if (-not $DownloadedJars) {
|
||||
throw 'No kkFileView jar found inside downloaded workflow artifact'
|
||||
}
|
||||
|
||||
if ($DownloadedJars.Count -ne 1) {
|
||||
throw "Expected exactly one kkFileView jar inside downloaded workflow artifact, found $($DownloadedJars.Count)"
|
||||
}
|
||||
|
||||
$DownloadedJar = $DownloadedJars[0]
|
||||
|
||||
$Timestamp = Get-Date -Format 'yyyyMMddHHmmss'
|
||||
$BackupJar = Join-Path $ReleaseDir ("{0}.{1}.bak" -f $JarName, $Timestamp)
|
||||
|
||||
function Stop-KkFileView {
|
||||
$JarPattern = [regex]::Escape($JarName)
|
||||
$Processes = Get-CimInstance Win32_Process | Where-Object {
|
||||
$_.Name -match '^java(\.exe)?$' -and $_.CommandLine -and $_.CommandLine -match $JarPattern
|
||||
}
|
||||
|
||||
foreach ($Process in $Processes) {
|
||||
Write-Step "Stopping java process $($Process.ProcessId)"
|
||||
Stop-Process -Id $Process.ProcessId -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
function Wait-KkFileViewStopped {
|
||||
param([int]$TimeoutSeconds = 30)
|
||||
|
||||
$JarPattern = [regex]::Escape($JarName)
|
||||
for ($i = 0; $i -lt $TimeoutSeconds; $i++) {
|
||||
$Processes = Get-CimInstance Win32_Process | Where-Object {
|
||||
$_.Name -match '^java(\.exe)?$' -and $_.CommandLine -and $_.CommandLine -match $JarPattern
|
||||
}
|
||||
|
||||
if (-not $Processes) {
|
||||
return $true
|
||||
}
|
||||
|
||||
Start-Sleep -Seconds 1
|
||||
}
|
||||
|
||||
return $false
|
||||
}
|
||||
|
||||
function Start-KkFileView {
|
||||
Write-Step "Starting kkFileView"
|
||||
Start-Process -FilePath 'cmd.exe' -ArgumentList '/c', "`"$StartupScript`"" -WorkingDirectory $BinDir -WindowStyle Hidden
|
||||
}
|
||||
|
||||
function Wait-Health {
|
||||
param([string]$Url)
|
||||
|
||||
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) {
|
||||
return $true
|
||||
}
|
||||
} catch {
|
||||
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"
|
||||
95
.github/workflows/master-auto-deploy.yml
vendored
Normal file
95
.github/workflows/master-auto-deploy.yml
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
name: Master Auto Deploy
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: master-auto-deploy-production
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '21'
|
||||
distribution: temurin
|
||||
cache: maven
|
||||
|
||||
- name: Build with Maven
|
||||
run: mvn -B package -Dmaven.test.skip=true --file pom.xml
|
||||
|
||||
- name: Upload server jar artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: kkfileview-server-jar
|
||||
path: server/target/kkFileView-*.jar
|
||||
retention-days: 7
|
||||
|
||||
deploy-windows:
|
||||
needs: build
|
||||
runs-on: ubuntu-22.04
|
||||
env:
|
||||
GITHUB_REPOSITORY_NAME: ${{ github.repository }}
|
||||
GITHUB_RUN_ID_VALUE: ${{ github.run_id }}
|
||||
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_ARTIFACT_NAME: kkfileview-server-jar
|
||||
|
||||
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: Resolve artifact download URL
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
artifact_json=$(curl -fsSL \
|
||||
-H "Authorization: Bearer $GH_TOKEN" \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
"https://api.github.com/repos/$GITHUB_REPOSITORY_NAME/actions/runs/$GITHUB_RUN_ID_VALUE/artifacts")
|
||||
artifact_id=$(ARTIFACT_JSON="$artifact_json" ARTIFACT_NAME="$KK_DEPLOY_ARTIFACT_NAME" python -c "import json, os; payload=json.loads(os.environ['ARTIFACT_JSON']); name=os.environ['ARTIFACT_NAME']; matches=[artifact for artifact in payload.get('artifacts', []) if artifact.get('name') == name]; matches or (_ for _ in ()).throw(SystemExit(f\"Artifact '{name}' not found for run\")); len(matches) == 1 or (_ for _ in ()).throw(SystemExit(f\"Expected one artifact named '{name}', found {len(matches)}\")); print(matches[0]['id'])")
|
||||
headers_file=$(mktemp)
|
||||
curl -fsS -D "$headers_file" -o /dev/null \
|
||||
-H "Authorization: Bearer $GH_TOKEN" \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
"https://api.github.com/repos/$GITHUB_REPOSITORY_NAME/actions/artifacts/$artifact_id/zip"
|
||||
artifact_url=$(awk 'BEGIN{IGNORECASE=1} /^location:/ { sub(/\r$/, "", $0); print substr($0, index($0, ":") + 2); exit }' "$headers_file")
|
||||
test -n "$artifact_url" || (echo "Failed to resolve artifact download redirect URL" && exit 1)
|
||||
rm -f "$headers_file"
|
||||
echo "KK_DEPLOY_ARTIFACT_URL=$artifact_url" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Deploy to Windows server
|
||||
run: python .github/scripts/deploy_windows_winrm.py
|
||||
41
doc/ci-auto-deploy.md
Normal file
41
doc/ci-auto-deploy.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# 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/`
|
||||
|
||||
服务器当前没有安装 `git` 和 `mvn`,因此自动部署链路采用:
|
||||
|
||||
1. GitHub Actions 在 `master` 合并后构建 `kkFileView-*.jar`
|
||||
2. 由 GitHub Actions runner 解析当前 workflow artifact 的临时下载地址
|
||||
3. 通过 WinRM 连接 Windows 服务器
|
||||
4. 由服务器通过临时下载地址拉取 jar artifact
|
||||
5. 备份线上 jar,替换为新版本
|
||||
6. 使用现有 `startup.bat` 重启,并做健康检查
|
||||
7. 如果健康检查失败,则自动回滚旧 jar 并重新拉起
|
||||
|
||||
这样做的目的是不把 GitHub token 下发到生产服务器,服务器只接触一次性 artifact 下载链接。
|
||||
|
||||
## 需要配置的 GitHub Secrets
|
||||
|
||||
- `KK_DEPLOY_HOST`
|
||||
- `KK_DEPLOY_USERNAME`
|
||||
- `KK_DEPLOY_PASSWORD`
|
||||
|
||||
下面这些可以不配,未配置时会使用默认值:
|
||||
|
||||
- `KK_DEPLOY_PORT=5985`
|
||||
- `KK_DEPLOY_ROOT=C:\kkFileView-5.0`
|
||||
- `KK_DEPLOY_HEALTH_URL=http://127.0.0.1:8012/`
|
||||
|
||||
## Workflow
|
||||
|
||||
新增 workflow:`.github/workflows/master-auto-deploy.yml`
|
||||
|
||||
- 触发条件:`push` 到 `master`,或手动 `workflow_dispatch`
|
||||
- 构建产物:`kkfileview-server-jar`
|
||||
- 部署方式:WinRM + runner 侧解析 artifact 临时下载地址 + Windows 服务器拉取 artifact
|
||||
@@ -52,6 +52,12 @@
|
||||
<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>
|
||||
|
||||
16
server/src/main/bin/dev.sh
Executable file
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"
|
||||
@@ -46,12 +46,6 @@ public class WebConfig implements WebMvcConfigurer {
|
||||
filterUri.add("/onlinePreview");
|
||||
filterUri.add("/picturesPreview");
|
||||
filterUri.add("/getCorsFile");
|
||||
filterUri.add("/addTask");
|
||||
filterUri.add("/pdfjs/web/viewer.html");
|
||||
filterUri.add("/msg/index.html");
|
||||
filterUri.add("/eml/index.html");
|
||||
filterUri.add("/heic/index.html");
|
||||
filterUri.add("/drawio/index.html");
|
||||
TrustHostFilter filter = new TrustHostFilter();
|
||||
FilterRegistrationBean<TrustHostFilter> registrationBean = new FilterRegistrationBean<>();
|
||||
registrationBean.setFilter(filter);
|
||||
@@ -65,7 +59,6 @@ public class WebConfig implements WebMvcConfigurer {
|
||||
filterUri.add("/onlinePreview");
|
||||
filterUri.add("/picturesPreview");
|
||||
filterUri.add("/getCorsFile");
|
||||
filterUri.add("/addTask");
|
||||
TrustDirFilter filter = new TrustDirFilter();
|
||||
FilterRegistrationBean<TrustDirFilter> registrationBean = new FilterRegistrationBean<>();
|
||||
registrationBean.setFilter(filter);
|
||||
|
||||
@@ -38,7 +38,7 @@ public enum FileType {
|
||||
|
||||
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", "tgz"};
|
||||
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"};
|
||||
|
||||
@@ -3,8 +3,6 @@ package cn.keking.service;
|
||||
import cn.keking.model.FileAttribute;
|
||||
import cn.keking.model.FileType;
|
||||
import cn.keking.service.cache.CacheService;
|
||||
import cn.keking.web.filter.TrustDirFilter;
|
||||
import cn.keking.web.filter.TrustHostFilter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -60,10 +58,6 @@ public class FileConvertQueueTask {
|
||||
try {
|
||||
url = cacheService.takeQueueTask();
|
||||
if (url != null) {
|
||||
if (!TrustHostFilter.isTrustedSourceUrl(url) || !TrustDirFilter.isTrustedFileUrl(url)) {
|
||||
logger.warn("拒绝处理不受信任的预览转换任务,url:{}", url);
|
||||
continue;
|
||||
}
|
||||
FileAttribute fileAttribute = fileHandlerService.getFileAttribute(url, null);
|
||||
FileType fileType = fileAttribute.getType();
|
||||
logger.info("正在处理预览转换任务,url:{},预览类型:{}", url, fileType);
|
||||
|
||||
@@ -29,7 +29,6 @@ import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.DirectoryStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
@@ -148,28 +147,6 @@ public class FileController {
|
||||
}
|
||||
}
|
||||
|
||||
private Path getDemoBasePath() {
|
||||
return Paths.get(fileDir, demoDir).toAbsolutePath().normalize();
|
||||
}
|
||||
|
||||
private Path resolveDemoPath(String path) {
|
||||
Path demoBasePath = getDemoBasePath();
|
||||
try {
|
||||
if (ObjectUtils.isEmpty(path)) {
|
||||
return demoBasePath;
|
||||
}
|
||||
Path normalizedPath = demoBasePath.resolve(path).normalize();
|
||||
if (!normalizedPath.startsWith(demoBasePath)) {
|
||||
logger.warn("检测到非法目录访问,path:{}", path);
|
||||
return null;
|
||||
}
|
||||
return normalizedPath;
|
||||
} catch (InvalidPathException e) {
|
||||
logger.warn("解析目录路径失败,path:{}", path, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/fileUpload")
|
||||
public ReturnResponse<Object> fileUpload(@RequestParam("file") MultipartFile file,
|
||||
@RequestParam(value = "path", defaultValue = "") String path) {
|
||||
@@ -364,20 +341,17 @@ public class FileController {
|
||||
}
|
||||
|
||||
// ==================== 2. 构建路径和验证 ====================
|
||||
Path resolvedPath = resolveDemoPath(path);
|
||||
if (resolvedPath == null) {
|
||||
result.put("total", 0);
|
||||
result.put("data", Collections.emptyList());
|
||||
return result;
|
||||
String basePath = fileDir + demoPath;
|
||||
if (!ObjectUtils.isEmpty(path)) {
|
||||
basePath += path + File.separator;
|
||||
}
|
||||
|
||||
File currentDir = resolvedPath.toFile();
|
||||
File currentDir = new File(basePath);
|
||||
if (!currentDir.exists() || !currentDir.isDirectory()) {
|
||||
result.put("total", 0);
|
||||
result.put("data", Collections.emptyList());
|
||||
return result;
|
||||
}
|
||||
String basePath = resolvedPath.toString();
|
||||
|
||||
// ==================== 3. 收集所有文件路径 ====================
|
||||
List<Path> allPaths = new ArrayList<>();
|
||||
|
||||
@@ -31,6 +31,11 @@ public class IndexController {
|
||||
return "/main/integrated";
|
||||
}
|
||||
|
||||
@GetMapping( "/contact")
|
||||
public String go2Contact(){
|
||||
return "/main/contact";
|
||||
}
|
||||
|
||||
@GetMapping( "/")
|
||||
public String root() {
|
||||
return "/main/index";
|
||||
|
||||
@@ -8,8 +8,6 @@ import cn.keking.service.FilePreviewFactory;
|
||||
import cn.keking.service.cache.CacheService;
|
||||
import cn.keking.service.impl.OtherFilePreviewImpl;
|
||||
import cn.keking.utils.*;
|
||||
import cn.keking.web.filter.TrustDirFilter;
|
||||
import cn.keking.web.filter.TrustHostFilter;
|
||||
import fr.opensagres.xdocreport.core.io.IOUtils;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
|
||||
@@ -233,11 +231,6 @@ public class OnlinePreviewController {
|
||||
logger.info("{},url:{}", errorMsg, fileUrls);
|
||||
return errorMsg;
|
||||
}
|
||||
if (!TrustHostFilter.isTrustedSourceUrl(fileUrls) || !TrustDirFilter.isTrustedFileUrl(fileUrls)) {
|
||||
String errorMsg = "访问不合法:来源地址不受信任!";
|
||||
logger.info("{},url:{}", errorMsg, fileUrls);
|
||||
return errorMsg;
|
||||
}
|
||||
logger.info("添加转码队列url:{}", fileUrls);
|
||||
cacheService.addQueueTask(fileUrls);
|
||||
return "success";
|
||||
|
||||
@@ -28,7 +28,7 @@ import java.util.Locale;
|
||||
public class TrustDirFilter implements Filter {
|
||||
|
||||
private String notTrustDirView;
|
||||
private static final Logger logger = LoggerFactory.getLogger(TrustDirFilter.class);
|
||||
private final Logger logger = LoggerFactory.getLogger(TrustDirFilter.class);
|
||||
|
||||
|
||||
@Override
|
||||
@@ -59,47 +59,6 @@ public class TrustDirFilter implements Filter {
|
||||
|
||||
}
|
||||
|
||||
public static boolean isTrustedFileUrl(String urlPath) {
|
||||
// 判断URL是否合法
|
||||
if (KkFileUtils.isIllegalFileName(urlPath) || !StringUtils.hasText(urlPath) || !WebUtils.isValidUrl(urlPath)) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
URL url = WebUtils.normalizedURL(urlPath);
|
||||
|
||||
if ("file".equals(url.getProtocol().toLowerCase(Locale.ROOT))) {
|
||||
String filePath = URLDecoder.decode(url.getPath(), StandardCharsets.UTF_8.name());
|
||||
// 将文件路径转换为File对象
|
||||
File targetFile = new File(filePath);
|
||||
// 将配置目录也转换为File对象
|
||||
File fileDir = new File(ConfigConstants.getFileDir());
|
||||
File localPreviewDir = new File(ConfigConstants.getLocalPreviewDir());
|
||||
try {
|
||||
// 获取规范路径
|
||||
String canonicalFilePath = targetFile.getCanonicalPath();
|
||||
String canonicalFileDir = fileDir.getCanonicalPath();
|
||||
String canonicalLocalPreviewDir = localPreviewDir.getCanonicalPath();
|
||||
return isSubDirectory(canonicalFileDir, canonicalFilePath) || isSubDirectory(canonicalLocalPreviewDir, canonicalFilePath);
|
||||
} catch (IOException e) {
|
||||
LoggerFactory.getLogger(TrustDirFilter.class).warn("获取规范路径失败,使用原始路径比较", e);
|
||||
String absFilePath = targetFile.getAbsolutePath();
|
||||
String absFileDir = fileDir.getAbsolutePath();
|
||||
String absLocalPreviewDir = localPreviewDir.getAbsolutePath();
|
||||
absFilePath = absFilePath.replace('\\', '/');
|
||||
absFileDir = absFileDir.replace('\\', '/');
|
||||
absLocalPreviewDir = absLocalPreviewDir.replace('\\', '/');
|
||||
if (!absFileDir.endsWith("/")) absFileDir += "/";
|
||||
if (!absLocalPreviewDir.endsWith("/")) absLocalPreviewDir += "/";
|
||||
return absFilePath.startsWith(absFileDir) || absFilePath.startsWith(absLocalPreviewDir);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} catch (IOException | GalimatiasParseException e) {
|
||||
LoggerFactory.getLogger(TrustDirFilter.class).error("解析URL异常,url:{}", urlPath, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean allowPreview(String urlPath) {
|
||||
// 判断URL是否合法
|
||||
if (KkFileUtils.isIllegalFileName(urlPath) || !StringUtils.hasText(urlPath) || !WebUtils.isValidUrl(urlPath)) {
|
||||
@@ -148,7 +107,7 @@ public class TrustDirFilter implements Filter {
|
||||
/**
|
||||
* 检查子路径是否在父路径下(跨平台)
|
||||
*/
|
||||
private static boolean isSubDirectory(String parentDir, String childPath) {
|
||||
private boolean isSubDirectory(String parentDir, String childPath) {
|
||||
try {
|
||||
File parent = new File(parentDir);
|
||||
File child = new File(childPath);
|
||||
|
||||
@@ -48,10 +48,7 @@ public class TrustHostFilter implements Filter {
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
|
||||
String url = request.getParameter("file");
|
||||
if (url == null || url.trim().isEmpty()) {
|
||||
url = WebUtils.getSourceUrl(request);
|
||||
}
|
||||
String url = WebUtils.getSourceUrl(request);
|
||||
String host = WebUtils.getHost(url);
|
||||
if (isNotTrustHost(host) || !WebUtils.isValidUrl(url)) {
|
||||
String currentHost = host == null ? "UNKNOWN" : host;
|
||||
@@ -70,14 +67,6 @@ public class TrustHostFilter implements Filter {
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isTrustedSourceUrl(String url) {
|
||||
if (!WebUtils.isValidUrl(url)) {
|
||||
return false;
|
||||
}
|
||||
String host = WebUtils.getHost(url);
|
||||
return !new TrustHostFilter().isNotTrustHost(host);
|
||||
}
|
||||
|
||||
public boolean isNotTrustHost(String host) {
|
||||
if (host == null || host.trim().isEmpty()) {
|
||||
logger.warn("主机名为空或无效,拒绝访问");
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,302 @@
|
||||
|
||||
<div id="IconMenuPanel">
|
||||
<div id="rdialogx_d" class="float_dialog2_fixed">
|
||||
<div id="icon_menu_panel" style="border:1" class="div_icon_menu_panel" >
|
||||
|
||||
<table id="savelinksHeaderTable" width="270" border="0" cellspacing="0" border-spacing="0" id="savelinks_table">
|
||||
<tr>
|
||||
<td>
|
||||
<div id="cv_link2">
|
||||
<div id="dummy_header" class="tagSaveLinkHeader" style="text-indent: 1em;">Location Setup</div>
|
||||
<div style="float:right;margin-right:-34px;margin-top:-24px;" id="CloseObjectMenuX"></div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
<table id="savelinksTable" width="270" border="0" cellspacing="0" border-spacing="0">
|
||||
<tr>
|
||||
<td>
|
||||
<div id="dummy">
|
||||
<canvas width="5" height="5"></canvas>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="whiteUnderbar">
|
||||
xxxx
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div id="save_drawing">
|
||||
<img id="save_drawing_image" class="icon_img" src="../app/images/tools/SaveChanges_240x22_Inactive.png" border="0">
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<div id="dummy">
|
||||
<canvas width="5" height="5"></canvas>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
<div id="link_tags_table">
|
||||
|
||||
<table id="savelinksTable_select" width="270" border="0" cellspacing="0" border-spacing="0" id="savelinks_table">
|
||||
<tr>
|
||||
<td>
|
||||
<div class="whiteUnderbar">
|
||||
____
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div id="unlink_location" class="select_link_text">Unlink Location</div>
|
||||
</td>
|
||||
<td>
|
||||
<div id="done_editing_location" class="select_link_text"></div>
|
||||
</td>
|
||||
<td>
|
||||
<div id="edit_cancel_location" class="select_link_text">Edit Fields</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<div id="dummy">
|
||||
<canvas width="5" height="5"></canvas>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
|
||||
<div id="location_text_dynamic">
|
||||
|
||||
<table id="locationTagsTable2" width="270" border="0" cellspacing="0" border-spacing="0">
|
||||
<tr>
|
||||
<td>
|
||||
<div id="cv_link10_id_datalayer_name">
|
||||
<div id="loc_text" class="location_text">Connector</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="whiteUnderbar">
|
||||
_
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div id="cv_link11" class="dropdown">
|
||||
<span class="dropdown-toggle" role="button" data-toggle="dropdown" href="#" data-target="#" id="drop_link_spaces">None Selected<b class="caret"></b></span>
|
||||
<ul id="spaces_drawing" class="dropdown-menu" role="menu" aria-labelledby="drop_link_spaces">
|
||||
</ul>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<canvas width="10" height="10"></canvas>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<div id="cv_link12A">
|
||||
<div id="layer_text" class="location_text">Layer</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="whiteUnderbar">
|
||||
_
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div id="cv_link13A">
|
||||
<input id="layer_tag" type="text" class="input_tags" value=""/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<div id="cv_link12">
|
||||
<div id="loc_text" class="location_text">Type</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="whiteUnderbar">
|
||||
_
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div id="cv_link13">
|
||||
<input id="type_tag" type="text" class="input_tags" value=""/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<div id="cv_link14">
|
||||
<div id="loc_text" class="location_text">Occupancy</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="whiteUnderbar">
|
||||
_
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<input id="occupancy_tag" type="text" class="input_tags" value=""/>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td VALIGN=TOP>
|
||||
<div id="loc_text" class="location_text_tags">Tags</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="whiteUnderbar">
|
||||
_
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div id="cv_link16">
|
||||
<textarea id="other_tags" class="styled_text_area"></textarea>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div id="location_text_static">
|
||||
|
||||
<table id="locationTagsTable" width="270" border="0" cellspacing="0" border-spacing="0" >
|
||||
<tr>
|
||||
<td>
|
||||
<div id="cv_link10_s">
|
||||
<div id="loc_text" class="location_text">Name</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="whiteUnderbar">
|
||||
_
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div id="cv_link11_s" class="location_text">
|
||||
<div id="location_s" class="location_text2">None Selected</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<div id="cv_link12A_s">
|
||||
<div id="loc_text" class="location_text">Layer</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="whiteUnderbar">
|
||||
_
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div id="cv_link13A_s">
|
||||
<div id="layer_s" class="location_text2">-</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<div id="cv_link12_s">
|
||||
<div id="loc_text" class="location_text">Type</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="whiteUnderbar">
|
||||
_
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div id="cv_link13_s">
|
||||
<div id="type_s" class="location_text2">-</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<div id="cv_link14_s">
|
||||
<div id="loc_text" class="location_text">Occupancy</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="whiteUnderbar">
|
||||
_
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div id="cv_link15_s">
|
||||
|
||||
<div id="occupancy_s" class="location_text2">-</div>
|
||||
|
||||
</div
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td VALIGN=TOP>
|
||||
<div id="cv_link16_s">
|
||||
<div id="loc_text" class="location_text_tags2">Tags</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="whiteUnderbar">
|
||||
_
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div id="cv_link17_s">
|
||||
<div id="tags_s" class="location_text2">-</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,308 @@
|
||||
|
||||
<div id="IconMenuPanel">
|
||||
<div id="rdialogx_d" class="float_dialog2_fixed">
|
||||
<div id="icon_menu_panel" style="border:1" class="div_icon_menu_panel" >
|
||||
|
||||
<table id="savelinksHeaderTable" width="270" border="0" cellspacing="0" border-spacing="0" id="savelinks_table">
|
||||
<tr>
|
||||
<td>
|
||||
<div id="cv_link2">
|
||||
<div id="dummy_header" class="tagSaveLinkHeader" style="text-indent: 1em;">Location Setup</div>
|
||||
<div style="float:right;margin-right:-34px;margin-top:-24px;" id="CloseObjectMenuX"></div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
<table id="savelinksTable" width="270" border="0" cellspacing="0" border-spacing="0">
|
||||
<tr>
|
||||
<td>
|
||||
<div id="dummy">
|
||||
<canvas width="5" height="5"></canvas>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="whiteUnderbar">
|
||||
xxxx
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div id="save_drawing">
|
||||
<img id="save_drawing_image" class="icon_img" src="../app/images/tools/SaveChanges_240x22_Inactive.png" border="0">
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<div id="dummy">
|
||||
<canvas width="5" height="5"></canvas>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
<div id="link_tags_table">
|
||||
|
||||
<table id="savelinksTable_select" width="270" border="0" cellspacing="0" border-spacing="0" id="savelinks_table">
|
||||
<tr>
|
||||
<td>
|
||||
<div class="whiteUnderbar">
|
||||
____
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div id="unlink_location" class="select_link_text">Unlink Location</div>
|
||||
</td>
|
||||
<td>
|
||||
<div id="done_editing_location" class="select_link_text"></div>
|
||||
</td>
|
||||
<td>
|
||||
<div id="edit_cancel_location" class="select_link_text">Edit Fields</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<div id="dummy">
|
||||
<canvas width="5" height="5"></canvas>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
|
||||
<div id="location_text_dynamic">
|
||||
|
||||
<table id="locationTagsTable2" width="270" border="0" cellspacing="0" border-spacing="0">
|
||||
<tr>
|
||||
<td>
|
||||
<div id="cv_link10_id_datalayer_name">
|
||||
<div id="loc_text" class="location_text">Connector</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="whiteUnderbar">
|
||||
_
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div id="cv_link11" class="dropdown">
|
||||
<span class="dropdown-toggle" role="button" data-toggle="dropdown" href="#" data-target="#" id="drop_link_spaces">None Selected<b class="caret"></b></span>
|
||||
<ul id="spaces_drawing" class="dropdown-menu" role="menu" aria-labelledby="drop_link_spaces">
|
||||
</ul>
|
||||
</div>
|
||||
<div id="cv_link11_B2">
|
||||
<input id="spaces_manual_tag" type="text" class="input_tags" value=""/>
|
||||
</div>
|
||||
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<div id="cv_link11_B3">
|
||||
<canvas width="10" height="10"></canvas>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<div id="cv_link12A">
|
||||
<div id="layer_text" class="location_text">Layer</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="whiteUnderbar">
|
||||
_
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div id="cv_link13A">
|
||||
<input id="layer_tag" type="text" class="input_tags" value=""/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<div id="cv_link12">
|
||||
<div id="loc_text" class="location_text">Type</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="whiteUnderbar">
|
||||
_
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div id="cv_link13">
|
||||
<input id="type_tag" type="text" class="input_tags" value=""/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<div id="cv_link14">
|
||||
<div id="loc_text" class="location_text">Occupancy</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="whiteUnderbar">
|
||||
_
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<input id="occupancy_tag" type="text" class="input_tags" value=""/>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td VALIGN=TOP>
|
||||
<div id="loc_text" class="location_text_tags">Tags</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="whiteUnderbar">
|
||||
_
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div id="cv_link16">
|
||||
<textarea id="other_tags" class="styled_text_area"></textarea>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div id="location_text_static">
|
||||
|
||||
<table id="locationTagsTable" width="270" border="0" cellspacing="0" border-spacing="0" >
|
||||
<tr>
|
||||
<td>
|
||||
<div id="cv_link10_s">
|
||||
<div id="loc_text" class="location_text">Name</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="whiteUnderbar">
|
||||
_
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div id="cv_link11_s" class="location_text">
|
||||
<div id="location_s" class="location_text2">None Selected</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<div id="cv_link12A_s">
|
||||
<div id="loc_text" class="location_text">Layer</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="whiteUnderbar">
|
||||
_
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div id="cv_link13A_s">
|
||||
<div id="layer_s" class="location_text2">-</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<div id="cv_link12_s">
|
||||
<div id="loc_text" class="location_text">Type</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="whiteUnderbar">
|
||||
_
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div id="cv_link13_s">
|
||||
<div id="type_s" class="location_text2">-</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<div id="cv_link14_s">
|
||||
<div id="loc_text" class="location_text">Occupancy</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="whiteUnderbar">
|
||||
_
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div id="cv_link15_s">
|
||||
|
||||
<div id="occupancy_s" class="location_text2">-</div>
|
||||
|
||||
</div
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td VALIGN=TOP>
|
||||
<div id="cv_link16_s">
|
||||
<div id="loc_text" class="location_text_tags2">Tags</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="whiteUnderbar">
|
||||
_
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div id="cv_link17_s">
|
||||
<div id="tags_s" class="location_text2">-</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1356
server/src/main/resources/static/css/main-pages.css
Normal file
1356
server/src/main/resources/static/css/main-pages.css
Normal file
File diff suppressed because it is too large
Load Diff
120722
server/src/main/resources/static/drawio/js/app.min.js
vendored
120722
server/src/main/resources/static/drawio/js/app.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Microsoft Excel</title><path d="M23 1.5q.41 0 .7.3.3.29.3.7v19q0 .41-.3.7-.29.3-.7.3H7q-.41 0-.7-.3-.3-.29-.3-.7V18H1q-.41 0-.7-.3-.3-.29-.3-.7V7q0-.41.3-.7Q.58 6 1 6h5V2.5q0-.41.3-.7.29-.3.7-.3zM6 13.28l1.42 2.66h2.14l-2.38-3.87 2.34-3.8H7.46l-1.3 2.4-.05.08-.04.09-.64-1.28-.66-1.29H2.59l2.27 3.82-2.48 3.85h2.16zM14.25 21v-3H7.5v3zm0-4.5v-3.75H12v3.75zm0-5.25V7.5H12v3.75zm0-5.25V3H7.5v3zm8.25 15v-3h-6.75v3zm0-4.5v-3.75h-6.75v3.75zm0-5.25V7.5h-6.75v3.75zm0-5.25V3h-6.75v3Z"/></svg>
|
||||
|
After Width: | Height: | Size: 563 B |
@@ -0,0 +1 @@
|
||||
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Adobe Acrobat Reader</title><path d="M23.63 15.3c-.71-.745-2.166-1.17-4.224-1.17-1.1 0-2.377.106-3.761.354a19.443 19.443 0 0 1-2.307-2.661c-.532-.71-.994-1.49-1.42-2.236.817-2.484 1.207-4.507 1.207-5.962 0-1.632-.603-3.336-2.342-3.336-.532 0-1.065.32-1.349.781-.78 1.384-.425 4.4.923 7.381a60.277 60.277 0 0 1-1.703 4.507c-.568 1.349-1.207 2.733-1.917 4.01C2.834 18.53.314 20.34.03 21.758c-.106.533.071 1.03.462 1.42.142.107.639.533 1.49.533 2.59 0 5.323-4.188 6.707-6.707 1.065-.355 2.13-.71 3.194-.994a34.963 34.963 0 0 1 3.407-.745c2.732 2.448 5.145 2.839 6.352 2.839 1.49 0 2.023-.604 2.2-1.1.32-.64.106-1.349-.213-1.704zm-1.42 1.03c-.107.532-.64.887-1.384.887-.213 0-.39-.036-.604-.071-1.348-.32-2.626-.994-3.903-2.059a17.717 17.717 0 0 1 2.98-.248c.746 0 1.385.035 1.81.142.497.106 1.278.426 1.1 1.348zm-7.524-1.668a38.01 38.01 0 0 0-2.945.674 39.68 39.68 0 0 0-2.52.745 40.05 40.05 0 0 0 1.207-2.555c.426-.994.78-2.023 1.136-2.981.354.603.745 1.207 1.135 1.739a50.127 50.127 0 0 0 1.987 2.378zM10.038 1.46a.768.768 0 0 1 .674-.425c.745 0 .887.851.887 1.526 0 1.135-.355 2.874-.958 4.861-1.03-2.768-1.1-5.074-.603-5.962zM6.134 17.997c-1.81 2.981-3.549 4.826-4.613 4.826a.872.872 0 0 1-.532-.177c-.213-.213-.32-.461-.249-.745.213-1.065 2.271-2.555 5.394-3.904Z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1 @@
|
||||
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Microsoft PowerPoint</title><path d="M13.5 1.5q1.453 0 2.795.375 1.342.375 2.508 1.06 1.166.686 2.12 1.641.956.955 1.641 2.121.686 1.166 1.061 2.508Q24 10.547 24 12q0 1.453-.375 2.795-.375 1.342-1.06 2.508-.686 1.166-1.641 2.12-.955.956-2.121 1.641-1.166.686-2.508 1.061-1.342.375-2.795.375-1.29 0-2.52-.305-1.23-.304-2.337-.884-1.108-.58-2.063-1.418-.955-.838-1.693-1.893H.997q-.411 0-.704-.293T0 17.004V6.996q0-.41.293-.703T.996 6h3.89q.739-1.055 1.694-1.893.955-.837 2.063-1.418 1.107-.58 2.337-.884Q12.21 1.5 13.5 1.5zm.75 1.535v8.215h8.215q-.14-1.64-.826-3.076-.686-1.436-1.782-2.531-1.095-1.096-2.537-1.782-1.441-.685-3.07-.826zm-5.262 7.57q0-.68-.228-1.166-.229-.486-.627-.79-.399-.305-.938-.446-.539-.14-1.172-.14H2.848v7.863h1.84v-2.742H5.93q.574 0 1.119-.17t.978-.493q.434-.322.698-.802.263-.48.263-1.114zM13.5 21q1.172 0 2.262-.287t2.056-.82q.967-.534 1.776-1.278.808-.744 1.418-1.664.61-.92.984-1.986.375-1.067.469-2.227h-9.703V3.035q-1.735.14-3.27.908T6.797 6h4.207q.41 0 .703.293t.293.703v10.008q0 .41-.293.703t-.703.293H6.797q.644.715 1.412 1.271.768.557 1.623.944.855.387 1.781.586Q12.54 21 13.5 21zM5.812 9.598q.575 0 .915.228.34.229.34.838 0 .27-.124.44-.123.17-.31.275-.188.105-.422.146-.234.041-.445.041H4.687V9.598Z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1 @@
|
||||
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Microsoft Word</title><path d="M23.004 1.5q.41 0 .703.293t.293.703v19.008q0 .41-.293.703t-.703.293H6.996q-.41 0-.703-.293T6 21.504V18H.996q-.41 0-.703-.293T0 17.004V6.996q0-.41.293-.703T.996 6H6V2.496q0-.41.293-.703t.703-.293zM6.035 11.203l1.442 4.735h1.64l1.57-7.876H9.036l-.937 4.653-1.325-4.5H5.38l-1.406 4.523-.938-4.675H1.312l1.57 7.874h1.641zM22.5 21v-3h-15v3zm0-4.5v-3.75H12v3.75zm0-5.25V7.5H12v3.75zm0-5.25V3h-15v3Z"/></svg>
|
||||
|
After Width: | Height: | Size: 510 B |
File diff suppressed because one or more lines are too long
@@ -23083,7 +23083,7 @@ initCom(PDFViewerApplication);
|
||||
}
|
||||
{
|
||||
const HOSTED_VIEWER_ORIGINS = new Set(["null", "http://mozilla.github.io", "https://mozilla.github.io"]);
|
||||
var validateFileURL = function (file) {
|
||||
var validateFileURL = function (file) {
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
@@ -23091,7 +23091,6 @@ var validateFileURL = function (file) {
|
||||
if (HOSTED_VIEWER_ORIGINS.has(viewerOrigin)) {
|
||||
return;
|
||||
}
|
||||
/* 注释掉跨域检查
|
||||
const fileOrigin = URL.parse(file, window.location)?.origin;
|
||||
if (fileOrigin === viewerOrigin) {
|
||||
return;
|
||||
@@ -23101,8 +23100,7 @@ var validateFileURL = function (file) {
|
||||
message: ex.message
|
||||
});
|
||||
throw ex;
|
||||
*/
|
||||
};
|
||||
};
|
||||
var onFileInputChange = function (evt) {
|
||||
if (this.pdfViewer?.isInPresentationMode) {
|
||||
return;
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
|
||||
<!-- modeler distro -->
|
||||
<script src="bpmn/bpmn-modeler.development.js"></script>
|
||||
<script src="js/jquery-3.7.1.min.js"></script>
|
||||
<script src="js/jquery-3.6.1.min.js"></script>
|
||||
|
||||
<!-- app -->
|
||||
<script>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<link href="cadviewer/app/css/jquery-ui-1.13.2.min.css" media="screen" rel="stylesheet" type="text/css" />
|
||||
|
||||
<!-- 核心脚本 - 最小化依赖 -->
|
||||
<script src="js/jquery-3.7.1.min.js" type="text/javascript"></script>
|
||||
<script src="js/jquery-3.6.1.min.js" type="text/javascript"></script>
|
||||
<script src="cadviewer/app/js/jquery-ui-1.13.2.min.js" type="text/javascript"></script>
|
||||
<script src="cadviewer/app/js/eve.js" type="text/javascript"></script>
|
||||
<script src="cadviewer/app/js/xml2json.min.js" type="text/javascript"></script>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<meta name="viewport" content="width=device-width, user-scalable=yes, initial-scale=1.0">
|
||||
<title>${file.name}代码预览</title>
|
||||
<#include "*/commonHeader.ftl">
|
||||
<script src="js/jquery-3.7.1.min.js" type="text/javascript"></script>
|
||||
<script src="js/jquery-3.6.1.min.js" type="text/javascript"></script>
|
||||
<link rel="stylesheet" href="bootstrap/css/bootstrap.min.css"/>
|
||||
<script src="bootstrap/js/bootstrap.min.js" type="text/javascript"></script>
|
||||
<link rel="stylesheet" href="highlight/default.min.css">
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>${file.name}压缩包预览</title>
|
||||
<script src="js/jquery-3.7.1.min.js"></script>
|
||||
<script src="js/jquery-3.6.1.min.js"></script>
|
||||
<#include "*/commonHeader.ftl">
|
||||
<script src="js/base64.min.js" type="text/javascript"></script>
|
||||
<link href="css/zTreeStyle.css" rel="stylesheet" type="text/css">
|
||||
|
||||
@@ -1,79 +1,43 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, user-scalable=yes, initial-scale=1.0">
|
||||
<title>draw.io 文件预览</title>
|
||||
<title>drawio文件预览</title>
|
||||
<#include "*/commonHeader.ftl">
|
||||
<script src="js/base64.min.js" type="text/javascript"></script>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
iframe {
|
||||
display: block;
|
||||
width: 100%;
|
||||
border: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<iframe id="drawioFrame" title="draw.io 预览"></iframe>
|
||||
|
||||
<iframe src="" width="100%" frameborder="0"></iframe>
|
||||
<#if currentUrl?contains("http://") || currentUrl?contains("https://")>
|
||||
<#assign finalUrl = "${currentUrl}">
|
||||
<#assign finalUrl="${currentUrl}">
|
||||
<#else>
|
||||
<#assign finalUrl = "${baseUrl}${currentUrl}">
|
||||
<#assign finalUrl="${baseUrl}${currentUrl}">
|
||||
</#if>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
// 获取最终文件地址
|
||||
var fileUrl = '${finalUrl}';
|
||||
<script>
|
||||
var url = '${finalUrl}';
|
||||
var kkagent = '${kkagent}';
|
||||
var baseUrl = '${baseUrl}';
|
||||
if (!baseUrl.endsWith('/')) baseUrl += '/';
|
||||
|
||||
// 跨域或代理处理
|
||||
if (kkagent === 'true' || !fileUrl.startsWith(baseUrl)) {
|
||||
fileUrl = baseUrl + 'getCorsFile?urlPath=' + encodeURIComponent(Base64.encode(fileUrl)) + "&key=${kkkey}";
|
||||
var baseUrl = '${baseUrl}'.endsWith('/') ? '${baseUrl}' : '${baseUrl}' + '/';
|
||||
if (kkagent === 'true' || !url.startsWith(baseUrl)) {
|
||||
url = baseUrl + 'getCorsFile?urlPath=' + encodeURIComponent(Base64.encode(url))+ "&key=${kkkey}";
|
||||
}
|
||||
document.getElementsByTagName('iframe')[0].src = "${baseUrl}drawio/index.html?lightbox=1&gapi=0&db=0&od=0&tr=0&gh=0&gl=0&edit=_blank&lang=zh#U"+ encodeURIComponent(url)+"";
|
||||
document.getElementsByTagName('iframe')[0].height = document.documentElement.clientHeight - 10;
|
||||
/**
|
||||
* 页面变化调整高度
|
||||
*/
|
||||
window.onresize = function () {
|
||||
var fm = document.getElementsByTagName("iframe")[0];
|
||||
fm.height = window.document.documentElement.clientHeight - 10;
|
||||
}
|
||||
|
||||
var encodedUrl = encodeURIComponent(fileUrl);
|
||||
var drawioBase = baseUrl + "drawio/index.html";
|
||||
|
||||
// 构建查询参数(保留原有所有参数,增加 file=)
|
||||
var params = new URLSearchParams({
|
||||
lightbox: '1',
|
||||
gapi: '0',
|
||||
db: '0',
|
||||
od: '0',
|
||||
tr: '0',
|
||||
gh: '0',
|
||||
gl: '0',
|
||||
edit: '_blank',
|
||||
lang: 'zh',
|
||||
file: fileUrl // 新增 ?file= 参数
|
||||
});
|
||||
|
||||
// 最终 URL:查询参数 + 原有的 #Uhash
|
||||
var iframeSrc = drawioBase + '?' + params.toString() + '#Uhttp://127.0.0.1/1.drawio';
|
||||
|
||||
var iframe = document.getElementById('drawioFrame');
|
||||
iframe.src = iframeSrc;
|
||||
iframe.height = document.documentElement.clientHeight - 10;
|
||||
|
||||
// 窗口大小变化时调整 iframe 高度
|
||||
window.addEventListener('resize', function() {
|
||||
iframe.height = document.documentElement.clientHeight - 10;
|
||||
});
|
||||
|
||||
// 可选:初始化水印(假设 initWaterMark 已定义)
|
||||
if (typeof initWaterMark === 'function') {
|
||||
window.addEventListener('load', initWaterMark);
|
||||
/*初始化水印*/
|
||||
window.onload = function () {
|
||||
initWaterMark();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,159 +1,44 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>暂不支持预览</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
<meta charset="utf-8"/>
|
||||
<style type="text/css">
|
||||
body {
|
||||
font-family: 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
margin: 0 auto;
|
||||
width: 900px;
|
||||
background-color: #CCB;
|
||||
}
|
||||
|
||||
.error-card {
|
||||
max-width: 600px;
|
||||
width: 100%;
|
||||
background: #ffffff;
|
||||
border-radius: 32px;
|
||||
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
|
||||
text-align: center;
|
||||
padding: 40px 32px 48px;
|
||||
transition: transform 0.2s ease;
|
||||
.container {
|
||||
width: 700px;
|
||||
height: 700px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.error-card:hover {
|
||||
transform: translateY(-4px);
|
||||
img {
|
||||
width: auto;
|
||||
height: auto;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
padding-bottom: 36px;
|
||||
}
|
||||
|
||||
.icon-container {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.icon-container img {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: #1e293b;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.file-type-badge {
|
||||
background: #f1f5f9;
|
||||
color: #0f172a;
|
||||
font-weight: 600;
|
||||
display: inline-block;
|
||||
padding: 6px 16px;
|
||||
border-radius: 40px;
|
||||
font-size: 14px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.reason-box {
|
||||
background: #fef2f2;
|
||||
border-left: 4px solid #dc2626;
|
||||
padding: 16px 20px;
|
||||
border-radius: 16px;
|
||||
margin: 20px 0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.reason-label {
|
||||
font-weight: 600;
|
||||
color: #991b1b;
|
||||
margin-bottom: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.reason-message {
|
||||
color: #1e293b;
|
||||
font-size: 15px;
|
||||
line-height: 1.5;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.community-link {
|
||||
background: #f8fafc;
|
||||
border-radius: 24px;
|
||||
padding: 16px 20px;
|
||||
margin-top: 28px;
|
||||
border: 1px solid #e2e8f0;
|
||||
}
|
||||
|
||||
.community-link p {
|
||||
font-size: 15px;
|
||||
color: #334155;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.community-link a {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
background: #3b82f6;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
padding: 10px 24px;
|
||||
border-radius: 40px;
|
||||
font-weight: 500;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.community-link a:hover {
|
||||
background: #2563eb;
|
||||
}
|
||||
|
||||
.footer-note {
|
||||
margin-top: 24px;
|
||||
font-size: 13px;
|
||||
color: #94a3b8;
|
||||
span {
|
||||
display: block;
|
||||
font-size: 20px;
|
||||
color: blue;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="error-card">
|
||||
<div class="icon-container">
|
||||
<!-- Base64 内嵌 SVG:文档 + 问号,表示不支持 -->
|
||||
<img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0iI2Y1OTAwYiIgd2lkdGg9IjEyMCIgaGVpZ2h0PSIxMjAiPgogIDxwYXRoIGQ9Ik0yMCA2aC04bC0yLTJINGMyLTEuMSAwLTEgMCAwdjE0YzAgMS4xLjkgMiAyIDJoMTZjMS4xIDAgMi0uOSAyLTJWN2MwLTEuMS0uOS0yLTItMnptLTIgMTJINlY4aDQuMjFsMiAySDE4djh6bS01LTRoLTR2LTJoNHYyem0wLTNoLTRWOWg0djJ6Ii8+Cjwvc3ZnPg==" alt="不支持预览">
|
||||
</div>
|
||||
<h1>暂不支持在线预览</h1>
|
||||
<div class="file-type-badge">
|
||||
📄 文件类型:${fileType}
|
||||
</div>
|
||||
<div class="reason-box">
|
||||
<div class="reason-label">
|
||||
⚠️ 具体原因
|
||||
</div>
|
||||
<div class="reason-message">
|
||||
${msg}
|
||||
</div>
|
||||
</div>
|
||||
<div class="community-link">
|
||||
<p>有任何疑问,欢迎加入 kk 开源社区知识星球咨询</p>
|
||||
<a href="https://t.zsxq.com/09ZHSXbsQ" target="_blank" rel="noopener noreferrer">
|
||||
🔗 加入知识星球
|
||||
</a>
|
||||
</div>
|
||||
<div class="footer-note">
|
||||
系统暂不支持此格式在线查看,建议下载后使用本地软件打开
|
||||
</div>
|
||||
<div class="container">
|
||||
<img src="images/sorry.jpg"/>
|
||||
<span>
|
||||
该(${fileType})文件,系统暂不支持在线预览,具体原因如下:
|
||||
<p style="color: red;">${msg}</p>
|
||||
</span>
|
||||
<p>有任何疑问,请加入kk开源社区知识星球咨询:<a href="https://t.zsxq.com/09ZHSXbsQ">https://t.zsxq.com/09ZHSXbsQ</a><br></p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -5,7 +5,7 @@
|
||||
<meta name="viewport" content="width=device-width, user-scalable=yes, initial-scale=1.0">
|
||||
<title>JSON文件预览</title>
|
||||
<#include "*/commonHeader.ftl">
|
||||
<script src="js/jquery-3.7.1.min.js" type="text/javascript"></script>
|
||||
<script src="js/jquery-3.6.1.min.js" type="text/javascript"></script>
|
||||
<link rel="stylesheet" href="bootstrap/css/bootstrap.min.css"/>
|
||||
<script src="bootstrap/js/bootstrap.min.js" type="text/javascript"></script>
|
||||
<script src="js/base64.min.js" type="text/javascript"></script>
|
||||
|
||||
104
server/src/main/resources/web/main/contact.ftl
Normal file
104
server/src/main/resources/web/main/contact.ftl
Normal file
@@ -0,0 +1,104 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>kkFileView 技术支持</title>
|
||||
<link rel="icon" href="./favicon.ico" type="image/x-icon">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600;700&family=JetBrains+Mono:wght@400;600&family=Space+Grotesk:wght@500;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="bootstrap/css/bootstrap.min.css"/>
|
||||
<link rel="stylesheet" href="css/theme.css"/>
|
||||
<link rel="stylesheet" href="css/main-pages.css"/>
|
||||
<script type="text/javascript" src="js/jquery-3.6.1.min.js"></script>
|
||||
<script type="text/javascript" src="bootstrap/js/bootstrap.min.js"></script>
|
||||
</head>
|
||||
|
||||
<body class="app-shell">
|
||||
<nav class="site-nav navbar navbar-inverse navbar-fixed-top">
|
||||
<div class="container">
|
||||
<div class="navbar-header">
|
||||
<a class="navbar-brand" href="https://kkview.cn" target="_blank">kkFileView</a>
|
||||
</div>
|
||||
<ul class="nav navbar-nav">
|
||||
<li><a href="./index">首页</a></li>
|
||||
<li><a href="./integrated">接入说明</a></li>
|
||||
<li><a href="./record">版本发布记录</a></li>
|
||||
<li><a href="./sponsor">赞助开源</a></li>
|
||||
<li class="active"><a href="./contact">技术支持</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="page-shell">
|
||||
<div class="container" role="main">
|
||||
<section class="hero-section release-hero">
|
||||
<div class="hero-copy">
|
||||
<div class="contact-hero-layout">
|
||||
<div class="contact-hero-copy">
|
||||
<span class="eyebrow">Contact Us</span>
|
||||
<h1 class="hero-title">技术支持</h1>
|
||||
<p class="hero-subtitle">
|
||||
如果你在部署、安装、接入或日常使用 kkFileView 时需要更直接的支持,
|
||||
可以加入我们的付费知识星球,获取安装使用技术支持。
|
||||
</p>
|
||||
<div class="hero-actions">
|
||||
<a class="hero-link primary" href="https://wx.zsxq.com/group/48844125114258" target="_blank">加入知识星球</a>
|
||||
<a class="hero-link secondary" href="./integrated">查看接入说明</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="contact-hero-side">
|
||||
<div class="support-summary">
|
||||
<div class="support-summary-item">
|
||||
<span class="tag brand">安装支持</span>
|
||||
<p>提供最新的安装发行包,公示安全补丁等。</p>
|
||||
</div>
|
||||
<div class="support-summary-item">
|
||||
<span class="tag">使用咨询</span>
|
||||
<p>围绕接入、配置和日常使用问题提供支持。</p>
|
||||
</div>
|
||||
<div class="support-summary-item">
|
||||
<span class="tag highlight">付费知识星球</span>
|
||||
<p>通过知识星球联系,我们提供更直接的技术支持。</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="sponsor-grid">
|
||||
<section class="doc-card">
|
||||
<div class="doc-card-header">
|
||||
<div>
|
||||
<span class="eyebrow">Support Scope</span>
|
||||
<h3>支持内容</h3>
|
||||
</div>
|
||||
</div>
|
||||
<p>如果你希望更快完成部署和落地,可以通过知识星球联系我们,获得更直接的安装使用支持。</p>
|
||||
<ul>
|
||||
<li>安装部署相关问题。</li>
|
||||
<li>配置、接入和常见使用问题。</li>
|
||||
<li>围绕实际使用场景的排查与建议。</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="doc-card">
|
||||
<div class="doc-card-header">
|
||||
<div>
|
||||
<span class="eyebrow">Knowledge Planet</span>
|
||||
<h3>加入方式</h3>
|
||||
</div>
|
||||
</div>
|
||||
<p>知识星球地址:</p>
|
||||
<p><a href="https://wx.zsxq.com/group/48844125114258" target="_blank">https://wx.zsxq.com/group/48844125114258</a></p>
|
||||
<div class="note-row">
|
||||
<span class="tag brand"><a href="https://wx.zsxq.com/group/48844125114258" target="_blank">立即加入</a></span>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,118 +1,254 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html lang="en" xmlns="http://www.w3.org/1999/html">
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>接入说明</title>
|
||||
<title>kkFileView 接入说明</title>
|
||||
<link rel="icon" href="./favicon.ico" type="image/x-icon">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600;700&family=JetBrains+Mono:wght@400;600&family=Space+Grotesk:wght@500;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="bootstrap/css/bootstrap.min.css"/>
|
||||
<link rel="stylesheet" href="css/theme.css"/>
|
||||
<script type="text/javascript" src="js/jquery-3.7.1.min.js"></script>
|
||||
<link rel="stylesheet" href="css/main-pages.css"/>
|
||||
<script type="text/javascript" src="js/jquery-3.6.1.min.js"></script>
|
||||
<script type="text/javascript" src="bootstrap/js/bootstrap.min.js"></script>
|
||||
<script type="text/javascript" src="highlight/highlight.min.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<nav class="navbar navbar-inverse navbar-fixed-top">
|
||||
<body class="app-shell">
|
||||
<nav class="site-nav navbar navbar-inverse navbar-fixed-top">
|
||||
<div class="container">
|
||||
<div class="navbar-header">
|
||||
<a class="navbar-brand" href="https://kkview.cn" target='_blank'>kkFileView</a>
|
||||
<a class="navbar-brand" href="https://kkview.cn" target="_blank">kkFileView</a>
|
||||
</div>
|
||||
<ul class="nav navbar-nav">
|
||||
<li><a href="./index">首页</a></li>
|
||||
<li class="active"><a href="./integrated">接入说明</a></li>
|
||||
<li><a href="./record">版本发布记录</a></li>
|
||||
<li><a href="./sponsor">赞助开源</a></li>
|
||||
<li><a href="./contact">技术支持</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container theme-showcase" role="main">
|
||||
<#-- 接入说明 -->
|
||||
<div class="page-header">
|
||||
<h1>接入说明</h1>
|
||||
本文档针对前端项目接入 kkFileView 的说明,并假设 kkFileView 的服务地址为:http://127.0.0.1:8012。
|
||||
<div class="page-shell">
|
||||
<div class="container" role="main">
|
||||
<section class="hero-section release-hero">
|
||||
<div class="hero-copy">
|
||||
<span class="eyebrow">Integration Guide</span>
|
||||
<h1 class="hero-title">5 分钟把 kkFileView 接进你的业务项目。</h1>
|
||||
<p class="hero-subtitle hero-subtitle-inline">
|
||||
这里按常见接入场景提供示例,方便你直接按需选用。默认假设服务地址为 <span class="text-highlight">${baseUrl}</span>。
|
||||
</p>
|
||||
<div class="note-row">
|
||||
<span class="tag brand">HTTP / HTTPS</span>
|
||||
<span class="tag">FTP</span>
|
||||
<span class="tag highlight">AES</span>
|
||||
<span class="tag warn">附加参数</span>
|
||||
</div>
|
||||
<div class="well">
|
||||
<div class="summary-grid">
|
||||
<div class="summary-panel">
|
||||
<strong>URL</strong>
|
||||
<span>所有预览能力统一汇总到 `onlinePreview` 入口。</span>
|
||||
</div>
|
||||
<div class="summary-panel">
|
||||
<strong>Base64</strong>
|
||||
<span>普通接入默认对原始文件地址做 Base64 编码后再传入。</span>
|
||||
</div>
|
||||
<div class="summary-panel">
|
||||
<strong>参数扩展</strong>
|
||||
<span>支持页码、高亮、水印、密码、跨域、AES 和秘钥等控制项。</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div style="font-size: 16px;">
|
||||
【http/https 资源文件预览】如果你的项目需要接入文件预览项目,达到对docx、excel、ppt、jpg等文件的预览效果,那么通过在你的项目中加入下面的代码就可以成功实现:
|
||||
<p style="background-color: #2f332a;color: #cccccc;font-size: 14px;padding:10px;margin-top:10px;">
|
||||
var url = 'http://127.0.0.1:8080/file/test.txt'; //要预览文件的访问地址 <br>
|
||||
window.open('${baseUrl}onlinePreview?url='+encodeURIComponent(base64Encode(url)));
|
||||
</p>
|
||||
</div>
|
||||
<br>
|
||||
<div style="font-size: 16px;">
|
||||
【http/https 流资源文件预览】很多系统内不是直接暴露文件下载地址,而是请求通过id、code等参数到通过统一的接口,后端通过id或code等参数定位文件,再通过OutputStream输出下载,此时下载url是不带文件后缀名的,预览时需要拿到文件名,传一个参数fullfilename=xxx.xxx来指定文件名,示例如下
|
||||
<p style="background-color: #2f332a;color: #cccccc;font-size: 14px;padding:10px;margin-top:10px;">
|
||||
var originUrl = 'http://127.0.0.1:8080/filedownload?fileId=1'; //要预览文件的访问地址<br>
|
||||
var previewUrl = originUrl + '&fullfilename=test.txt'<br>
|
||||
window.open('${baseUrl}onlinePreview?url='+encodeURIComponent(Base64.encode(previewUrl)));
|
||||
</p>
|
||||
</div>
|
||||
<br>
|
||||
<div style="font-size: 16px;">
|
||||
【ftp 资源文件预览】如果要预览的FTP url是可以匿名访问的(不需要用户名密码),则可以直接通过下载url预览,示例如下
|
||||
<p style="background-color: #2f332a;color: #cccccc;font-size: 14px;padding:10px;margin-top:10px;">
|
||||
var url = 'ftp://127.0.0.1/file/test.txt'; //要预览文件的访问地址<br>
|
||||
window.open('${baseUrl}onlinePreview?url='+encodeURIComponent(Base64.encode(url)));
|
||||
</p>
|
||||
</div>
|
||||
<br>
|
||||
<div style="font-size: 16px;">
|
||||
【ftp 加密资源文件预览】如果 FTP 需要认证访问服,可以通过在 url 中加入用户名密码等参数预览,示例如下
|
||||
<p style="background-color: #2f332a;color: #cccccc;font-size: 14px;padding:10px;margin-top:10px;">
|
||||
var originUrl = 'ftp://127.0.0.1/file/test.txt'; //要预览文件的访问地址<br>
|
||||
var previewUrl = originUrl + '?ftp.control.port=xx&ftp.username=xx&ftp.password=xx&ftp.control.encoding=(gbk,utf8等)'; //(为了安全强烈建议在配置中设置相关信息)<br>
|
||||
window.open('${baseUrl}onlinePreview?url='+encodeURIComponent(Base64.encode(previewUrl)));
|
||||
</p>
|
||||
</div>
|
||||
<div style="font-size: 16px;">
|
||||
【Basic 鉴权资源文件预览】如果需要认证访问服,可以通过在url中加入用户名密码等参数预览,示例如下
|
||||
<p style="background-color: #2f332a;color: #cccccc;font-size: 14px;padding:10px;margin-top:10px;">
|
||||
var originUrl = 'http://127.0.0.1/file/test.txt'; //要预览文件的访问地址<br>
|
||||
var previewUrl = originUrl + '?basic.name=admin&basic.pass=123456'; //(为了安全强烈建议在配置中设置相关信息)<br>
|
||||
window.open('${baseUrl}onlinePreview?url='+encodeURIComponent(Base64.encode(previewUrl)));
|
||||
</p>
|
||||
</div>
|
||||
<div style="font-size: 16px;">
|
||||
AES加密接入方法,示例如下
|
||||
<p style="background-color: #2f332a;color: #cccccc;font-size: 14px;padding:10px;margin-top:10px;">
|
||||
主要事项:首先引入下面js 在把url转换成AES,注意前后端key必须相同(注意:JS下载到你接入服务器的网址)<br>
|
||||
<script src="${baseUrl}js/crypto-js.js"></script><br>
|
||||
<script src="${baseUrl}js/aes.js"></script><br>
|
||||
function aesEncrypt(encryptString, key) { <br>
|
||||
var key = CryptoJS.enc.Utf8.parse(key); <br>
|
||||
var srcs = CryptoJS.enc.Utf8.parse(encryptString); <br>
|
||||
var encrypted = CryptoJS.AES.encrypt(srcs, key, { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 }); <br>
|
||||
return encrypted.toString(); <br>
|
||||
}<br>
|
||||
var key = "1234567890123456"; // AES秘钥16位数字<br>
|
||||
var url = "http://127.0.0.1/file/test.txt";<br>
|
||||
window.open('${baseUrl}onlinePreview?url='+encodeURIComponent(aesEncrypt(url, key))+'&encryption=aes');
|
||||
</p>
|
||||
</div>
|
||||
<div class="docs-layout">
|
||||
<aside class="page-toc">
|
||||
<h3>快速导航</h3>
|
||||
<ul>
|
||||
<li><a href="#quick-start">快速开始</a></li>
|
||||
<li><a href="#http-preview">HTTP 文件预览</a></li>
|
||||
<li><a href="#stream-preview">流式接口预览</a></li>
|
||||
<li><a href="#ftp-preview">FTP 预览</a></li>
|
||||
<li><a href="#basic-auth">Basic 鉴权</a></li>
|
||||
<li><a href="#aes-preview">AES 加密</a></li>
|
||||
<li><a href="#extra-params">附加参数</a></li>
|
||||
</ul>
|
||||
</aside>
|
||||
|
||||
<div style="font-size: 16px;">
|
||||
其他参数,示例如下
|
||||
<p style="background-color: #2f332a;color: #cccccc;font-size: 14px;padding:10px;margin-top:10px;">
|
||||
密码参数:&filePassword=加密文件的密码<br>
|
||||
页码参数:&page=选择第几页预览<br>
|
||||
高亮参数:&highlightall=关键字 突出显示 <br>
|
||||
水印参数:&watermarkTxt=你的水印<br>
|
||||
重生参数:&forceUpdatedCache=true <br>
|
||||
跨域参数:&kkagent=true <br>
|
||||
加密缓存:&usePasswordCache=true <br>
|
||||
秘钥参数:&key= 访问秘钥 <br>
|
||||
主要事项:以上参数是把url转换成base64后面在添加<br>
|
||||
var url = 'http://127.0.0.1:8080/file/test.txt'<br>
|
||||
window.open('${baseUrl}onlinePreview?url='+encodeURIComponent(base64Encode(url))+'&filePassword=123&page=1&highlightall=kkfileview&watermarkTxt=kkfileview&kkagent=false&key=123');
|
||||
</p>
|
||||
<div class="docs-content">
|
||||
<section class="doc-card" id="quick-start">
|
||||
<div class="doc-card-header">
|
||||
<div>
|
||||
<span class="eyebrow">Quick Start</span>
|
||||
<h3>接入思路</h3>
|
||||
</div>
|
||||
<div class="tags">
|
||||
<span class="tag brand">推荐入口</span>
|
||||
</div>
|
||||
</div>
|
||||
<p>前端只需要拿到可访问的文件 URL,然后把它编码后拼到 `${baseUrl}onlinePreview` 上即可。对于大多数业务系统,这是最快的落地路径。</p>
|
||||
<ul>
|
||||
<li>普通 HTTP/HTTPS 文件地址:直接 Base64 编码后传入。</li>
|
||||
<li>下载流接口没有后缀名:补充 `fullfilename=xxx.xxx`。</li>
|
||||
<li>鉴权或加密场景:附加 Basic、FTP、AES 等参数。</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="doc-card" id="http-preview">
|
||||
<div class="doc-card-header">
|
||||
<div>
|
||||
<span class="eyebrow">HTTP / HTTPS</span>
|
||||
<h3>普通文件地址预览</h3>
|
||||
</div>
|
||||
<button class="copy-btn" type="button" onclick="copyCode(this)">复制代码</button>
|
||||
</div>
|
||||
<p>适用于系统已经直接暴露出可下载文件地址的情况。前端只需要编码后打开新窗口。</p>
|
||||
<div class="code-block">
|
||||
<code class="language-javascript">var url = 'http://127.0.0.1:8080/file/test.txt';
|
||||
window.open('${baseUrl}onlinePreview?url=' + encodeURIComponent(base64Encode(url)));</code>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="doc-card" id="stream-preview">
|
||||
<div class="doc-card-header">
|
||||
<div>
|
||||
<span class="eyebrow">Streaming</span>
|
||||
<h3>流式接口预览</h3>
|
||||
</div>
|
||||
<button class="copy-btn" type="button" onclick="copyCode(this)">复制代码</button>
|
||||
</div>
|
||||
<p>很多业务系统通过 `fileId`、`code` 等参数走统一下载接口,此时原始 URL 没有后缀名,需要额外指定完整文件名。</p>
|
||||
<div class="code-block">
|
||||
<code class="language-javascript">var originUrl = 'http://127.0.0.1:8080/filedownload?fileId=1';
|
||||
var previewUrl = originUrl + '&fullfilename=test.txt';
|
||||
window.open('${baseUrl}onlinePreview?url=' + encodeURIComponent(Base64.encode(previewUrl)));</code>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="doc-card" id="ftp-preview">
|
||||
<div class="doc-card-header">
|
||||
<div>
|
||||
<span class="eyebrow">FTP</span>
|
||||
<h3>FTP 资源预览</h3>
|
||||
</div>
|
||||
<button class="copy-btn" type="button" onclick="copyCode(this)">复制代码</button>
|
||||
</div>
|
||||
<p>FTP 如果允许匿名访问,可以直接预览;如果需要认证,则把连接参数拼到 URL 后面传入。</p>
|
||||
<div class="code-block">
|
||||
<code class="language-javascript">// 匿名 FTP
|
||||
var url = 'ftp://127.0.0.1/file/test.txt';
|
||||
window.open('${baseUrl}onlinePreview?url=' + encodeURIComponent(Base64.encode(url)));
|
||||
|
||||
// 认证 FTP
|
||||
var originUrl = 'ftp://127.0.0.1/file/test.txt';
|
||||
var previewUrl = originUrl + '?ftp.control.port=21&ftp.username=admin&ftp.password=123456&ftp.control.encoding=utf8';
|
||||
window.open('${baseUrl}onlinePreview?url=' + encodeURIComponent(Base64.encode(previewUrl)));</code>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="doc-card" id="basic-auth">
|
||||
<div class="doc-card-header">
|
||||
<div>
|
||||
<span class="eyebrow">Basic Auth</span>
|
||||
<h3>带 Basic 鉴权的 HTTP 资源</h3>
|
||||
</div>
|
||||
<button class="copy-btn" type="button" onclick="copyCode(this)">复制代码</button>
|
||||
</div>
|
||||
<p>如果文件源本身需要用户名和密码,可以直接把 Basic 鉴权参数拼到地址中,再交给 kkFileView。</p>
|
||||
<div class="code-block">
|
||||
<code class="language-javascript">var originUrl = 'http://127.0.0.1/file/test.txt';
|
||||
var previewUrl = originUrl + '?basic.name=admin&basic.pass=123456';
|
||||
window.open('${baseUrl}onlinePreview?url=' + encodeURIComponent(Base64.encode(previewUrl)));</code>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="doc-card" id="aes-preview">
|
||||
<div class="doc-card-header">
|
||||
<div>
|
||||
<span class="eyebrow">AES</span>
|
||||
<h3>前后端同秘钥加密接入</h3>
|
||||
</div>
|
||||
<button class="copy-btn" type="button" onclick="copyCode(this)">复制代码</button>
|
||||
</div>
|
||||
<p>如果不希望明文传递原始文件地址,可以在前端先做 AES 加密,再通过 `encryption=aes` 告知服务端按 AES 方式解密。</p>
|
||||
<div class="code-block">
|
||||
<code class="language-javascript"><script src="${baseUrl}js/crypto-js.js"></script>
|
||||
<script src="${baseUrl}js/aes.js"></script>
|
||||
|
||||
function aesEncrypt(encryptString, key) {
|
||||
var keyBytes = CryptoJS.enc.Utf8.parse(key);
|
||||
var srcs = CryptoJS.enc.Utf8.parse(encryptString);
|
||||
var encrypted = CryptoJS.AES.encrypt(srcs, keyBytes, {
|
||||
mode: CryptoJS.mode.ECB,
|
||||
padding: CryptoJS.pad.Pkcs7
|
||||
});
|
||||
return encrypted.toString();
|
||||
}
|
||||
|
||||
var key = '1234567890123456';
|
||||
var url = 'http://127.0.0.1/file/test.txt';
|
||||
window.open('${baseUrl}onlinePreview?url=' + encodeURIComponent(aesEncrypt(url, key)) + '&encryption=aes');</code>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="doc-card" id="extra-params">
|
||||
<div class="doc-card-header">
|
||||
<div>
|
||||
<span class="eyebrow">Parameters</span>
|
||||
<h3>常用附加参数</h3>
|
||||
</div>
|
||||
<button class="copy-btn" type="button" onclick="copyCode(this)">复制代码</button>
|
||||
</div>
|
||||
<p>这些参数都应该在原始 URL 编码完成之后,再附加到预览地址后面。</p>
|
||||
<ul>
|
||||
<li>`filePassword`:加密文件的密码。</li>
|
||||
<li>`page`:指定预览页码。</li>
|
||||
<li>`highlightall`:关键字高亮。</li>
|
||||
<li>`watermarkTxt`:动态水印文本。</li>
|
||||
<li>`forceUpdatedCache=true`:强制刷新缓存。</li>
|
||||
<li>`kkagent=true`:需要 kkFileView 代理跨域时启用。</li>
|
||||
<li>`usePasswordCache=true`:开启密码缓存。</li>
|
||||
<li>`key`:实例启用秘钥后传入访问秘钥。</li>
|
||||
</ul>
|
||||
<div class="code-block">
|
||||
<code class="language-javascript">var url = 'http://127.0.0.1:8080/file/test.txt';
|
||||
window.open(
|
||||
'${baseUrl}onlinePreview?url=' +
|
||||
encodeURIComponent(base64Encode(url)) +
|
||||
'&filePassword=123' +
|
||||
'&page=1' +
|
||||
'&highlightall=kkfileview' +
|
||||
'&watermarkTxt=kkfileview' +
|
||||
'&kkagent=false' +
|
||||
'&key=123'
|
||||
);</code>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
if (window.hljs) {
|
||||
document.querySelectorAll('.code-block code').forEach(function (block) {
|
||||
hljs.highlightBlock(block);
|
||||
});
|
||||
}
|
||||
|
||||
function copyCode(button) {
|
||||
var code = button.parentNode.parentNode.querySelector('code').innerText;
|
||||
var originalText = button.textContent;
|
||||
navigator.clipboard.writeText(code).then(function () {
|
||||
button.textContent = '已复制';
|
||||
setTimeout(function () {
|
||||
button.textContent = originalText;
|
||||
}, 1500);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,72 +1,127 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html lang="en" xmlns="http://www.w3.org/1999/html">
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>赞助开源</title>
|
||||
<title>kkFileView 赞助开源</title>
|
||||
<link rel="icon" href="./favicon.ico" type="image/x-icon">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600;700&family=JetBrains+Mono:wght@400;600&family=Space+Grotesk:wght@500;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="bootstrap/css/bootstrap.min.css"/>
|
||||
<link rel="stylesheet" href="css/theme.css"/>
|
||||
<script type="text/javascript" src="js/jquery-3.7.1.min.js"></script>
|
||||
<link rel="stylesheet" href="css/main-pages.css"/>
|
||||
<script type="text/javascript" src="js/jquery-3.6.1.min.js"></script>
|
||||
<script type="text/javascript" src="bootstrap/js/bootstrap.min.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<!-- Fixed navbar -->
|
||||
<nav class="navbar navbar-inverse navbar-fixed-top">
|
||||
<body class="app-shell">
|
||||
<nav class="site-nav navbar navbar-inverse navbar-fixed-top">
|
||||
<div class="container">
|
||||
<div class="navbar-header">
|
||||
<a class="navbar-brand" href="https://kkview.cn" target='_blank'>kkFileView</a>
|
||||
<a class="navbar-brand" href="https://kkview.cn" target="_blank">kkFileView</a>
|
||||
</div>
|
||||
<ul class="nav navbar-nav">
|
||||
<li><a href="./index">首页</a></li>
|
||||
<li><a href="./integrated">接入说明</a></li>
|
||||
<li><a href="./record">版本发布记录</a></li>
|
||||
<li class="active"><a href="./sponsor">赞助开源</a></li>
|
||||
<li><a href="./contact">技术支持</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container theme-showcase" role="main">
|
||||
<#-- 接入说明 -->
|
||||
<div class="page-header">
|
||||
<h1>赞助开源</h1>
|
||||
<ul style="font-size: 16px;">
|
||||
<li>kkFileView 开源至今已 6 个年头,积累 <a target="_blank" href="https://gitee.com/kekingcn/file-online-preview">Gitee(16.9K)</a>、<a target="_blank" href="https://github.com/kekingcn/kkFileView">GitHub(8k)</a> 多的 star</li>
|
||||
<li>kkFileView 被广泛应用于金融、教育、银行、政务、计算机等行业, 不完全统计有 200+ 企业在使用</li>
|
||||
<li>kkFileView 每年的文档站点、演示站点的服务器、CDN 资源, 至少在 1000元以上</li>
|
||||
<li>kkFileView 是一款完全开源的在线预览项目,如果你觉得 kkFileView 对你有帮助,可以通过下面的方式来赞助我们,谢谢!</li>
|
||||
<div class="page-shell">
|
||||
<div class="container" role="main">
|
||||
<section class="hero-section release-hero">
|
||||
<div class="hero-copy">
|
||||
<span class="eyebrow">Sponsor Open Source</span>
|
||||
<h1 class="hero-title">赞助开源</h1>
|
||||
<p class="hero-subtitle">
|
||||
kkFileView 已持续维护多年,被广泛用于金融、教育、银行、政务和企业内部系统。
|
||||
赞助会直接用于文档站、演示站、服务器和 CDN 等基础开销。
|
||||
</p>
|
||||
<div class="summary-grid">
|
||||
<div class="summary-panel">
|
||||
<strong>多年维护</strong>
|
||||
<span>项目长期迭代,持续补格式、补安全、补性能。</span>
|
||||
</div>
|
||||
<div class="summary-panel">
|
||||
<strong>广泛使用</strong>
|
||||
<span>不完全统计已有 200+ 企业或团队在使用。</span>
|
||||
</div>
|
||||
<div class="summary-panel">
|
||||
<strong>基础成本</strong>
|
||||
<span>文档站、演示站和 CDN 等年成本至少在千元级。</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="note-row">
|
||||
<span class="tag brand"><a target="_blank" href="https://gitee.com/kekingcn/file-online-preview">Gitee</a></span>
|
||||
<span class="tag brand"><a target="_blank" href="https://github.com/kekingcn/kkFileView">GitHub</a></span>
|
||||
<span class="tag highlight">完全开源</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="sponsor-grid">
|
||||
<section class="doc-card">
|
||||
<div class="doc-card-header">
|
||||
<div>
|
||||
<span class="eyebrow">Ways To Sponsor</span>
|
||||
<h3>赞助方式</h3>
|
||||
</div>
|
||||
</div>
|
||||
<p>如果你觉得 kkFileView 对你有帮助,可以通过下面的方式赞助项目,支持它继续长期维护。</p>
|
||||
<ul>
|
||||
<li>kkFileView 开源至今已多年,社区持续反馈并推动演进。</li>
|
||||
<li>项目 star 与使用规模已经证明它不是“玩具 demo”,而是被真实系统接入的基础能力。</li>
|
||||
<li>赞助记录为手动录入,存在周级延迟;如有遗漏,可联系作者补录。</li>
|
||||
</ul>
|
||||
|
||||
<div class="donation-wall">
|
||||
<div class="qr-card">
|
||||
<h4>支付宝</h4>
|
||||
<img alt="支付宝赞助码" src="../images/alipay.jpeg"/>
|
||||
</div>
|
||||
<div class="qr-card">
|
||||
<h4>微信支付</h4>
|
||||
<img alt="微信赞助码" src="../images/wxpay.jpeg"/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="doc-card">
|
||||
<div class="doc-card-header">
|
||||
<div>
|
||||
<div style="font-size: 16px; text-align: center;">
|
||||
<img width="400px" height="550px" alt="alipay" src="../images/alipay.jpeg"/> <img width="400px" height="550px" alt="wxpay" src="../images/wxpay.jpeg"/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span class="eyebrow">Sponsor Log</span>
|
||||
<h3>赞助记录</h3>
|
||||
2023-03-14 开启赞助通道,赞助记录为手动录入的,存在周级别延迟,如有遗漏,请联系作者补充
|
||||
<br/>
|
||||
<table class="table table-striped table-bordered">
|
||||
</div>
|
||||
<div class="tags">
|
||||
<span class="tag">2023-03-14 开启赞助通道</span>
|
||||
</div>
|
||||
</div>
|
||||
<p>赞助记录为手动维护,如有遗漏,请联系作者补充。</p>
|
||||
<table class="table sponsor-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>赞助人</th>
|
||||
<th>赞助金额</th>
|
||||
<th>赞助时间</th>
|
||||
<th>备注</th>
|
||||
</tr>
|
||||
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>陈**</td>
|
||||
<td>99</td>
|
||||
<td class="sponsor-amount">99</td>
|
||||
<td>2023-03-14</td>
|
||||
<td></td>
|
||||
<td>首批赞助记录</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<meta name="viewport" content="width=device-width, user-scalable=yes, initial-scale=1.0">
|
||||
<title>${file.name}文本预览</title>
|
||||
<#include "*/commonHeader.ftl">
|
||||
<script src="js/jquery-3.7.1.min.js" type="text/javascript"></script>
|
||||
<script src="js/jquery-3.6.1.min.js" type="text/javascript"></script>
|
||||
<link rel="stylesheet" href="bootstrap/css/bootstrap.min.css"/>
|
||||
<link rel="stylesheet" href="css/index.css"/>
|
||||
<script src="bootstrap/js/bootstrap.min.js" type="text/javascript"></script>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<#setting classic_compatible=true>
|
||||
<link rel="icon" href="./favicon.ico" type="image/x-icon">
|
||||
<link rel="stylesheet" href="bootstrap/css/bootstrap.min.css"/>
|
||||
<script src="js/jquery-3.7.1.min.js" type="text/javascript"></script>
|
||||
<script src="js/jquery-3.6.1.min.js" type="text/javascript"></script>
|
||||
<script src="bootstrap/js/bootstrap.min.js" type="text/javascript"></script>
|
||||
<script src="js/bootbox.min.js" type="text/javascript"></script>
|
||||
<script>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user