mirror of
https://gitee.com/kekingcn/file-online-preview.git
synced 2026-04-09 01:37:34 +00:00
新增 页码定位 美化前端 其他功能调整等
This commit is contained in:
Binary file not shown.
@@ -17,6 +17,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
||||
public class ConfigConstants {
|
||||
public static final String BEAN_NAME = "configConstants";
|
||||
|
||||
|
||||
static {
|
||||
// PDFBox兼容低版本JDK
|
||||
System.setProperty("sun.java2d.cmm", "sun.java2d.cmm.kcms.KcmsServiceProvider");
|
||||
@@ -90,7 +91,7 @@ public class ConfigConstants {
|
||||
public static final String DEFAULT_PICTURES_PREVIEW = "true";
|
||||
public static final String DEFAULT_GET_CORS_FILE = "true";
|
||||
public static final String DEFAULT_ADD_TASK = "true";
|
||||
public static final String DEFAULT_AES_KEY = "1234567890123456";
|
||||
public static final String DEFAULT_AES_KEY = "false";
|
||||
|
||||
// 12. UserAgent配置常量
|
||||
public static final String DEFAULT_USER_AGENT = "false";
|
||||
@@ -134,6 +135,17 @@ public class ConfigConstants {
|
||||
// 20. 重定向启用配置常量
|
||||
public static final String DEFAULT_ENABLE_REDIRECT = "true";
|
||||
|
||||
// 22. 异步定时
|
||||
public static final String DEFAULT_ENABLE_REFRECSHSCHEDULE = "5";
|
||||
|
||||
// 23. 其他配置常量
|
||||
public static final String DEFAULT_SHOW_AES_KEY = "1234567890123456";
|
||||
public static final String DEFAULT_IS_JAVASCRIPT = "false";
|
||||
public static final String DEFAULT_XLSX_ALLOW_EDIT = "false";
|
||||
public static final String DEFAULT_XLSX_SHOW_TOOLBAR = "false";
|
||||
public static final String DEFAULT_IS_SHOW_KEY= "false";
|
||||
public static final String DEFAULT_SCRIPT_JS ="false" ;
|
||||
|
||||
// ==================================================
|
||||
// 配置变量定义区(按功能分类,均为静态变量)
|
||||
// ==================================================
|
||||
@@ -254,6 +266,18 @@ public class ConfigConstants {
|
||||
// 21. 重定向启用配置
|
||||
private static Boolean enableRedirect;
|
||||
|
||||
// 22. 异步定时
|
||||
private static int refreshSchedule;
|
||||
|
||||
// 23. 其他配置变量
|
||||
private static boolean isShowaesKey;
|
||||
private static boolean isJavaScript;
|
||||
private static boolean xlsxAllowEdit;
|
||||
private static boolean xlsxShowtoolbar;
|
||||
private static boolean isShowKey;
|
||||
private static boolean scriptJs;
|
||||
|
||||
|
||||
|
||||
// ==================================================
|
||||
// 获取方法(按功能分类)
|
||||
@@ -579,6 +603,36 @@ public class ConfigConstants {
|
||||
return enableRedirect;
|
||||
}
|
||||
|
||||
// 22. 异步定时刷新时间
|
||||
public static int getTime() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 23. 其他配置获取方法
|
||||
public static boolean getisShowaesKey() {
|
||||
return isShowaesKey;
|
||||
}
|
||||
|
||||
public static boolean getisJavaScript() {
|
||||
return isJavaScript;
|
||||
}
|
||||
|
||||
public static boolean getxlsxAllowEdit() {
|
||||
return xlsxAllowEdit;
|
||||
}
|
||||
|
||||
public static boolean getxlsxShowtoolbar() {
|
||||
return xlsxShowtoolbar;
|
||||
}
|
||||
|
||||
public static boolean getisShowKey() {
|
||||
return isShowKey;
|
||||
}
|
||||
|
||||
public static boolean getscriptJs() {
|
||||
return scriptJs;
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// Setter方法(按功能分类)
|
||||
// ==================================================
|
||||
@@ -1036,7 +1090,7 @@ public class ConfigConstants {
|
||||
}
|
||||
|
||||
// 12. 权限配置Setter方法
|
||||
@Value("${kk.Key:}")
|
||||
@Value("${kk.key:false}")
|
||||
public void setKey(String key) {
|
||||
setKeyValue(key);
|
||||
}
|
||||
@@ -1072,7 +1126,7 @@ public class ConfigConstants {
|
||||
ConfigConstants.addTask = addTask;
|
||||
}
|
||||
|
||||
@Value("${ase.key:1234567890123456}")
|
||||
@Value("${aes.key:1234567890123456}")
|
||||
public void setaesKey(String aesKey) {
|
||||
setaesKeyValue(aesKey);
|
||||
}
|
||||
@@ -1305,4 +1359,69 @@ public class ConfigConstants {
|
||||
public static void setEnableRedirectValue(Boolean enableRedirect) {
|
||||
ConfigConstants.enableRedirect = enableRedirect;
|
||||
}
|
||||
|
||||
// 22 异步定时刷新时间
|
||||
@Value("${kk.refreshSchedule:5}")
|
||||
public void setRefreshSchedule(int refreshSchedule) {
|
||||
setRefreshScheduleValue(refreshSchedule);
|
||||
}
|
||||
|
||||
public static void setRefreshScheduleValue(int refreshSchedule) {
|
||||
ConfigConstants.refreshSchedule = refreshSchedule;
|
||||
}
|
||||
|
||||
// 23. 其他配置Setter方法
|
||||
@Value("${kk.isshowaeskey:false}")
|
||||
public void setIsShowaesKey(String isShowaesKey) {
|
||||
setIsShowaesKeyValue(Boolean.parseBoolean(isShowaesKey));
|
||||
}
|
||||
|
||||
public static void setIsShowaesKeyValue(boolean isShowaesKey) {
|
||||
ConfigConstants.isShowaesKey = isShowaesKey;
|
||||
}
|
||||
|
||||
@Value("${kk.isjavascript:false}")
|
||||
public void setIsJavaScript(String isJavaScript) {
|
||||
setIsJavaScriptValue(Boolean.parseBoolean(isJavaScript));
|
||||
}
|
||||
|
||||
public static void setIsJavaScriptValue(boolean isJavaScript) {
|
||||
ConfigConstants.isJavaScript = isJavaScript;
|
||||
}
|
||||
|
||||
@Value("${kk.xlsxallowedit:false}")
|
||||
public void setXlsxAllowEdit(String xlsxAllowEdit) {
|
||||
setXlsxAllowEditValue(Boolean.parseBoolean(xlsxAllowEdit));
|
||||
}
|
||||
|
||||
public static void setXlsxAllowEditValue(boolean xlsxAllowEdit) {
|
||||
ConfigConstants.xlsxAllowEdit = xlsxAllowEdit;
|
||||
}
|
||||
|
||||
@Value("${kk.xlsxshowtoolbar:false}")
|
||||
public void setXlsxShowtoolbar(String xlsxShowtoolbar) {
|
||||
setXlsxShowtoolbarValue(Boolean.parseBoolean(xlsxShowtoolbar));
|
||||
}
|
||||
|
||||
public static void setXlsxShowtoolbarValue(boolean xlsxShowtoolbar) {
|
||||
ConfigConstants.xlsxShowtoolbar = xlsxShowtoolbar;
|
||||
}
|
||||
|
||||
@Value("${kk.isshowkey:false}")
|
||||
public void setisShowKey(String isShowKey) {
|
||||
setisShowKeyValue(Boolean.parseBoolean(isShowKey));
|
||||
}
|
||||
|
||||
public static void setisShowKeyValue(boolean isShowKey) {
|
||||
ConfigConstants.isShowKey = isShowKey;
|
||||
}
|
||||
|
||||
@Value("${kk.scriptjs:false}")
|
||||
public void setscriptJs(String scriptJs) {
|
||||
setscriptJsValue(Boolean.parseBoolean(scriptJs));
|
||||
}
|
||||
|
||||
public static void setscriptJsValue(boolean scriptJs) {
|
||||
ConfigConstants.scriptJs = scriptJs;
|
||||
}
|
||||
}
|
||||
@@ -251,11 +251,11 @@ public class ConfigRefreshComponent {
|
||||
String homeSearch = properties.getProperty("home.search", ConfigConstants.DEFAULT_HOME_SEARCH);
|
||||
|
||||
// 12. 权限配置
|
||||
String key = properties.getProperty("kk.Key", ConfigConstants.DEFAULT_KEY);
|
||||
String key = properties.getProperty("kk.key=", ConfigConstants.DEFAULT_KEY);
|
||||
boolean picturesPreview = Boolean.parseBoolean(properties.getProperty("kk.Picturespreview", ConfigConstants.DEFAULT_PICTURES_PREVIEW));
|
||||
boolean getCorsFile = Boolean.parseBoolean(properties.getProperty("kk.Getcorsfile", ConfigConstants.DEFAULT_GET_CORS_FILE));
|
||||
boolean addTask = Boolean.parseBoolean(properties.getProperty("kk.addTask", ConfigConstants.DEFAULT_ADD_TASK));
|
||||
String aesKey = properties.getProperty("ase.key", ConfigConstants.DEFAULT_AES_KEY);
|
||||
String aesKey = properties.getProperty("aes.key", ConfigConstants.DEFAULT_AES_KEY);
|
||||
|
||||
// 13. UserAgent配置
|
||||
String userAgent = properties.getProperty("useragent", ConfigConstants.DEFAULT_USER_AGENT);
|
||||
@@ -299,6 +299,17 @@ public class ConfigRefreshComponent {
|
||||
// 21. 重定向启用配置
|
||||
boolean enableRedirect = Boolean.parseBoolean(properties.getProperty("kk.enable.redirect", ConfigConstants.DEFAULT_ENABLE_REDIRECT));
|
||||
|
||||
// 22. 重定向启用配置
|
||||
int refreshSchedule = Integer.parseInt(properties.getProperty("kk.refreshSchedule ", ConfigConstants.DEFAULT_ENABLE_REFRECSHSCHEDULE).trim());
|
||||
|
||||
// 23. 其他配置
|
||||
boolean isShowaesKey = Boolean.parseBoolean(properties.getProperty("kk.isshowaeskey", ConfigConstants.DEFAULT_SHOW_AES_KEY));
|
||||
boolean isJavaScript = Boolean.parseBoolean(properties.getProperty("kk.isjavascript", ConfigConstants.DEFAULT_IS_JAVASCRIPT));
|
||||
boolean xlsxAllowEdit = Boolean.parseBoolean(properties.getProperty("kk.xlsxallowedit", ConfigConstants.DEFAULT_XLSX_ALLOW_EDIT));
|
||||
boolean xlsxShowtoolbar = Boolean.parseBoolean(properties.getProperty("kk.xlsxshowtoolbar", ConfigConstants.DEFAULT_XLSX_SHOW_TOOLBAR));
|
||||
boolean isShowKey = Boolean.parseBoolean(properties.getProperty("kk.isshowkey", ConfigConstants.DEFAULT_IS_SHOW_KEY));
|
||||
boolean scriptJs = Boolean.parseBoolean(properties.getProperty("kk.scriptjs", ConfigConstants.DEFAULT_SCRIPT_JS));
|
||||
|
||||
// 设置配置值
|
||||
// 1. 缓存配置
|
||||
ConfigConstants.setCacheEnabledValueValue(cacheEnabled);
|
||||
@@ -385,16 +396,6 @@ public class ConfigRefreshComponent {
|
||||
ConfigConstants.setMediaXLFileTimeoutValue(mediaXLFileTimeout);
|
||||
ConfigConstants.setMediaXXLFileTimeoutValue(mediaXXLFileTimeout);
|
||||
ConfigConstants.setMediaXXXLFileTimeoutValue(mediaXXXLFileTimeout);
|
||||
|
||||
// 19. CAD水印配置
|
||||
ConfigConstants.setCadwatermarkValue(cadwatermark);
|
||||
|
||||
// 20. SSL忽略配置
|
||||
ConfigConstants.setIgnoreSSLValue(ignoreSSL);
|
||||
|
||||
// 21. 重定向启用配置
|
||||
ConfigConstants.setEnableRedirectValue(enableRedirect);
|
||||
|
||||
// 16. PDF DPI配置
|
||||
ConfigConstants.setPdfDpiEnabledValue(pdfDpiEnabled);
|
||||
ConfigConstants.setPdfSmallDpiValue(pdfSmallDpi);
|
||||
@@ -411,6 +412,26 @@ public class ConfigRefreshComponent {
|
||||
|
||||
// 18. PDF线程配置
|
||||
ConfigConstants.setPdfMaxThreadsValue(pdfMaxThreads);
|
||||
|
||||
// 19. CAD水印配置
|
||||
ConfigConstants.setCadwatermarkValue(cadwatermark);
|
||||
|
||||
// 20. SSL忽略配置
|
||||
ConfigConstants.setIgnoreSSLValue(ignoreSSL);
|
||||
|
||||
// 21. 重定向启用配置
|
||||
ConfigConstants.setEnableRedirectValue(enableRedirect);
|
||||
|
||||
// 22. 重定向启用配置
|
||||
ConfigConstants.setRefreshScheduleValue(refreshSchedule);
|
||||
|
||||
// 23. 其他配置
|
||||
ConfigConstants.setIsShowaesKeyValue(isShowaesKey);
|
||||
ConfigConstants.setIsJavaScriptValue(isJavaScript);
|
||||
ConfigConstants.setXlsxAllowEditValue(xlsxAllowEdit);
|
||||
ConfigConstants.setXlsxShowtoolbarValue(xlsxShowtoolbar);
|
||||
ConfigConstants.setisShowKeyValue(isShowKey);
|
||||
ConfigConstants.setscriptJsValue(scriptJs);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -56,44 +56,44 @@ public class CadToPdfService {
|
||||
* @param fileAttribute 文件属性
|
||||
* @return 转换结果的CompletableFuture
|
||||
*/
|
||||
public CompletableFuture<Boolean> cadToPdfAsync(String inputFilePath, String outputFilePath,
|
||||
public CompletableFuture<Boolean> cadToPdfAsync(String inputFilePath, String outputFilePath,String cacheName,
|
||||
String cadPreviewType, FileAttribute fileAttribute) {
|
||||
String fileName = new File(inputFilePath).getName();
|
||||
|
||||
// 立即创建初始状态,防止重复执行
|
||||
FileConvertStatusManager.startConvert(fileName);
|
||||
FileConvertStatusManager.startConvert(cacheName);
|
||||
// 创建可取消的任务
|
||||
CompletableFuture<Boolean> taskFuture = new CompletableFuture<>();
|
||||
taskCompletionStatus.put(fileName, new AtomicBoolean(false));
|
||||
taskCompletionStatus.put(cacheName, new AtomicBoolean(false));
|
||||
|
||||
// 提交任务到线程池
|
||||
Future<?> future = virtualThreadExecutor.submit(() -> {
|
||||
try {
|
||||
// 添加初始状态更新
|
||||
FileConvertStatusManager.updateProgress(fileName, "正在启动转换任务", 5);
|
||||
boolean result = convertCadWithConcurrencyControl(inputFilePath, outputFilePath,
|
||||
FileConvertStatusManager.updateProgress(cacheName, "正在启动转换任务", 5);
|
||||
boolean result = convertCadWithConcurrencyControl(inputFilePath, outputFilePath,cacheName,
|
||||
cadPreviewType, fileAttribute);
|
||||
if (result) {
|
||||
taskFuture.complete(true);
|
||||
taskCompletionStatus.get(fileName).set(true);
|
||||
taskCompletionStatus.get(cacheName).set(true);
|
||||
} else {
|
||||
taskFuture.complete(false);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("CAD转换任务执行失败: {}", fileName, e);
|
||||
FileConvertStatusManager.markError(fileName, "转换过程异常: " + e.getMessage());
|
||||
logger.error("CAD转换任务执行失败: {}", cacheName, e);
|
||||
FileConvertStatusManager.markError(cacheName, "转换过程异常: " + e.getMessage());
|
||||
taskFuture.completeExceptionally(e);
|
||||
} finally {
|
||||
// 移除任务记录
|
||||
runningTasks.remove(fileName);
|
||||
taskCompletionStatus.remove(fileName);
|
||||
runningTasks.remove(cacheName);
|
||||
taskCompletionStatus.remove(cacheName);
|
||||
}
|
||||
});
|
||||
|
||||
// 记录正在运行的任务
|
||||
runningTasks.put(fileName, future);
|
||||
runningTasks.put(cacheName, future);
|
||||
|
||||
// 设置超时取消
|
||||
scheduleTimeoutCheck(fileName, taskFuture, future, outputFilePath);
|
||||
scheduleTimeoutCheck(cacheName, taskFuture, future, outputFilePath);
|
||||
|
||||
return taskFuture;
|
||||
}
|
||||
@@ -160,34 +160,34 @@ public class CadToPdfService {
|
||||
/**
|
||||
* 带并发控制的CAD转换
|
||||
*/
|
||||
private boolean convertCadWithConcurrencyControl(String inputFilePath, String outputFilePath,
|
||||
private boolean convertCadWithConcurrencyControl(String inputFilePath, String outputFilePath,String cacheName,
|
||||
String cadPreviewType, FileAttribute fileAttribute)
|
||||
throws Exception {
|
||||
String fileName = new File(inputFilePath).getName();
|
||||
|
||||
long acquireStartTime = System.currentTimeMillis();
|
||||
|
||||
// 获取并发许可
|
||||
if (!concurrentLimit.tryAcquire(30, TimeUnit.SECONDS)) {
|
||||
long acquireTime = System.currentTimeMillis() - acquireStartTime;
|
||||
logger.warn("获取并发许可超时,文件: {}, 等待时间: {}ms", fileName, acquireTime);
|
||||
FileConvertStatusManager.updateProgress(fileName, "系统繁忙,等待资源中...", 15);
|
||||
logger.warn("获取并发许可超时,文件: {}, 等待时间: {}ms", cacheName, acquireTime);
|
||||
FileConvertStatusManager.updateProgress(cacheName, "系统繁忙,等待资源中...", 15);
|
||||
throw new TimeoutException("系统繁忙,请稍后重试");
|
||||
}
|
||||
|
||||
long acquireTime = System.currentTimeMillis() - acquireStartTime;
|
||||
logger.debug("获取并发许可成功: {}, 等待时间: {}ms", fileName, acquireTime);
|
||||
logger.debug("获取并发许可成功: {}, 等待时间: {}ms", cacheName, acquireTime);
|
||||
|
||||
// 更新状态
|
||||
FileConvertStatusManager.updateProgress(fileName, "已获取转换资源,开始转换", 20);
|
||||
FileConvertStatusManager.updateProgress(cacheName, "已获取转换资源,开始转换", 20);
|
||||
|
||||
long conversionStartTime = System.currentTimeMillis();
|
||||
|
||||
try {
|
||||
boolean result = performCadConversion(inputFilePath, outputFilePath, cadPreviewType, fileAttribute);
|
||||
boolean result = performCadConversion(inputFilePath, outputFilePath,cacheName, cadPreviewType, fileAttribute);
|
||||
|
||||
long conversionTime = System.currentTimeMillis() - conversionStartTime;
|
||||
logger.debug("CAD转换核心完成: {}, 转换耗时: {}ms, 总耗时(含等待): {}ms",
|
||||
fileName, conversionTime, conversionTime + acquireTime);
|
||||
cacheName, conversionTime, conversionTime + acquireTime);
|
||||
|
||||
return result;
|
||||
|
||||
@@ -199,49 +199,48 @@ public class CadToPdfService {
|
||||
/**
|
||||
* 执行实际的CAD转换逻辑
|
||||
*/
|
||||
private boolean performCadConversion(String inputFilePath, String outputFilePath,
|
||||
private boolean performCadConversion(String inputFilePath, String outputFilePath,String cacheName,
|
||||
String cadPreviewType, FileAttribute fileAttribute) {
|
||||
final InterruptionTokenSource source = new InterruptionTokenSource();
|
||||
String fileName = new File(inputFilePath).getName();
|
||||
long totalStartTime = System.currentTimeMillis();
|
||||
try {
|
||||
// 1. 验证输入参数
|
||||
long validationStartTime = System.currentTimeMillis();
|
||||
FileConvertStatusManager.updateProgress(fileName, "正在验证文件参数", 25);
|
||||
FileConvertStatusManager.updateProgress(cacheName, "正在验证文件参数", 25);
|
||||
if (!validateInputParameters(inputFilePath, outputFilePath, cadPreviewType)) {
|
||||
long validationTime = System.currentTimeMillis() - validationStartTime;
|
||||
logger.error("CAD转换参数验证失败: {}, 验证耗时: {}ms", fileName, validationTime);
|
||||
FileConvertStatusManager.markError(fileName, "文件参数验证失败");
|
||||
logger.error("CAD转换参数验证失败: {}, 验证耗时: {}ms", cacheName, validationTime);
|
||||
FileConvertStatusManager.markError(cacheName, "文件参数验证失败");
|
||||
return false;
|
||||
}
|
||||
long validationTime = System.currentTimeMillis() - validationStartTime;
|
||||
|
||||
// 2. 创建输出目录
|
||||
long directoryStartTime = System.currentTimeMillis();
|
||||
FileConvertStatusManager.updateProgress(fileName, "正在准备输出目录", 30);
|
||||
FileConvertStatusManager.updateProgress(cacheName, "正在准备输出目录", 30);
|
||||
createOutputDirectoryIfNeeded(outputFilePath, fileAttribute.isCompressFile());
|
||||
long directoryTime = System.currentTimeMillis() - directoryStartTime;
|
||||
|
||||
// 3. 加载并转换CAD文件
|
||||
long loadStartTime = System.currentTimeMillis();
|
||||
FileConvertStatusManager.updateProgress(fileName, "正在加载CAD文件", 40);
|
||||
FileConvertStatusManager.updateProgress(cacheName, "正在加载CAD文件", 40);
|
||||
LoadOptions loadOptions = createLoadOptions();
|
||||
|
||||
try (Image cadImage = Image.load(inputFilePath, loadOptions)) {
|
||||
long loadTime = System.currentTimeMillis() - loadStartTime;
|
||||
logger.debug("CAD文件加载完成: {}, 加载耗时: {}ms", fileName, loadTime);
|
||||
logger.debug("CAD文件加载完成: {}, 加载耗时: {}ms", cacheName, loadTime);
|
||||
|
||||
FileConvertStatusManager.updateProgress(fileName, "CAD文件加载完成,开始渲染", 50);
|
||||
FileConvertStatusManager.updateProgress(cacheName, "CAD文件加载完成,开始渲染", 50);
|
||||
|
||||
// 4. 创建光栅化选项
|
||||
long rasterizationStartTime = System.currentTimeMillis();
|
||||
FileConvertStatusManager.updateProgress(fileName, "正在设置渲染参数", 60);
|
||||
FileConvertStatusManager.updateProgress(cacheName, "正在设置渲染参数", 60);
|
||||
CadRasterizationOptions rasterizationOptions = createRasterizationOptions(cadImage);
|
||||
long rasterizationTime = System.currentTimeMillis() - rasterizationStartTime;
|
||||
|
||||
// 5. 根据预览类型创建选项
|
||||
long optionsStartTime = System.currentTimeMillis();
|
||||
FileConvertStatusManager.updateProgress(fileName, "正在配置输出格式", 70);
|
||||
FileConvertStatusManager.updateProgress(cacheName, "正在配置输出格式", 70);
|
||||
var options = switch (cadPreviewType.toLowerCase()) {
|
||||
case "svg" -> createSvgOptions(rasterizationOptions, source);
|
||||
case "pdf" -> createPdfOptions(rasterizationOptions, source);
|
||||
@@ -251,15 +250,15 @@ public class CadToPdfService {
|
||||
long optionsTime = System.currentTimeMillis() - optionsStartTime;
|
||||
// 6. 保存转换结果
|
||||
long saveStartTime = System.currentTimeMillis();
|
||||
FileConvertStatusManager.updateProgress(fileName, "正在生成输出文件", 80);
|
||||
FileConvertStatusManager.updateProgress(cacheName, "正在生成输出文件", 80);
|
||||
saveConvertedFile(outputFilePath, cadImage, options);
|
||||
long saveTime = System.currentTimeMillis() - saveStartTime;
|
||||
FileConvertStatusManager.updateProgress(fileName, "文件转换完成", 90);
|
||||
FileConvertStatusManager.updateProgress(cacheName, "文件转换完成", 90);
|
||||
// 计算总时间
|
||||
long totalTime = System.currentTimeMillis() - totalStartTime;
|
||||
// 记录详细的性能信息
|
||||
logger.debug("CAD转换详细耗时 - 文件: {}, 验证={}ms, 目录={}ms, 加载={}ms, 光栅化={}ms, 选项={}ms, 保存={}ms, 总耗时={}ms",
|
||||
fileName, validationTime, directoryTime, loadTime,
|
||||
cacheName, validationTime, directoryTime, loadTime,
|
||||
rasterizationTime, optionsTime, saveTime, totalTime);
|
||||
|
||||
logger.info("CAD转换完成: 总耗时: {}ms", totalTime);
|
||||
@@ -272,9 +271,9 @@ public class CadToPdfService {
|
||||
}
|
||||
|
||||
// 转换成功,标记为完成
|
||||
FileConvertStatusManager.updateProgress(fileName, "转换成功", 100);
|
||||
FileConvertStatusManager.updateProgress(cacheName, "转换成功", 100);
|
||||
// 短暂延迟后清理状态,给前端一个显示100%的机会
|
||||
FileConvertStatusManager.convertSuccess(fileName);
|
||||
FileConvertStatusManager.convertSuccess(cacheName);
|
||||
|
||||
|
||||
return true;
|
||||
@@ -282,12 +281,12 @@ public class CadToPdfService {
|
||||
|
||||
} catch (Exception e) {
|
||||
long totalTime = System.currentTimeMillis() - totalStartTime;
|
||||
logger.error("CAD转换执行失败: {}, 耗时: {}ms", fileName, totalTime, e);
|
||||
logger.error("CAD转换执行失败: {}, 耗时: {}ms", cacheName, totalTime, e);
|
||||
|
||||
// 检查是否已经标记为超时
|
||||
FileConvertStatusManager.ConvertStatus status = FileConvertStatusManager.getConvertStatus(fileName);
|
||||
FileConvertStatusManager.ConvertStatus status = FileConvertStatusManager.getConvertStatus(cacheName);
|
||||
if (status == null || status.getStatus() != FileConvertStatusManager.Status.TIMEOUT) {
|
||||
FileConvertStatusManager.markError(fileName, "转换失败: " + e.getMessage());
|
||||
FileConvertStatusManager.markError(cacheName, "转换失败: " + e.getMessage());
|
||||
}
|
||||
|
||||
// 删除可能已创建的不完整文件
|
||||
@@ -302,7 +301,7 @@ public class CadToPdfService {
|
||||
long cleanupTime = System.currentTimeMillis() - cleanupStartTime;
|
||||
long totalTime = System.currentTimeMillis() - totalStartTime;
|
||||
logger.debug("CAD转换资源清理完成: {}, 清理耗时: {}ms, 总耗时: {}ms",
|
||||
fileName, cleanupTime, totalTime);
|
||||
cacheName, cleanupTime, totalTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,10 +254,16 @@ public class FileHandlerService {
|
||||
try {
|
||||
originFileName = URLDecoder.decode(originFileName, uriEncoding); //转义的文件名 解下出原始文件名
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
logger.error("Failed to decode file name: {}", originFileName, e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}else {
|
||||
url = WebUtils.encodeUrlFileName(url); //对未转义的url进行转义
|
||||
url = Objects.requireNonNull(WebUtils.encodeUrlFileName(url))
|
||||
.replaceAll("\\+", "%20")
|
||||
.replaceAll("%3A", ":")
|
||||
.replaceAll("%2F", "/")
|
||||
.replaceAll("%3F", "?")
|
||||
.replaceAll("%26", "&")
|
||||
.replaceAll("%3D", "=");
|
||||
}
|
||||
originFileName = KkFileUtils.htmlEscape(originFileName); //文件名处理
|
||||
boolean isHtmlView = suffix.equalsIgnoreCase("xls") || suffix.equalsIgnoreCase("xlsx") || suffix.equalsIgnoreCase("csv") || suffix.equalsIgnoreCase("xlsm") || suffix.equalsIgnoreCase("xlt") || suffix.equalsIgnoreCase("xltm") || suffix.equalsIgnoreCase("et") || suffix.equalsIgnoreCase("ett") || suffix.equalsIgnoreCase("xlam");
|
||||
|
||||
@@ -2,6 +2,7 @@ package cn.keking.service;
|
||||
|
||||
import cn.keking.config.ConfigConstants;
|
||||
import cn.keking.model.FileAttribute;
|
||||
import cn.keking.utils.FileConvertStatusManager;
|
||||
import org.bytedeco.ffmpeg.global.avcodec;
|
||||
import org.bytedeco.javacv.FFmpegFrameGrabber;
|
||||
import org.bytedeco.javacv.FFmpegFrameRecorder;
|
||||
@@ -12,7 +13,6 @@ import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
@@ -89,15 +89,17 @@ public class Mediatomp4Service {
|
||||
* 异步转换方法(带任务ID和超时控制)
|
||||
*/
|
||||
public static CompletableFuture<Boolean> convertToMp4Async(
|
||||
String filePath, String outFilePath, FileAttribute fileAttribute) {
|
||||
String filePath, String outFilePath,String cacheName, FileAttribute fileAttribute) {
|
||||
|
||||
String taskId = generateTaskId(filePath);
|
||||
// 立即创建初始状态,防止重复执行
|
||||
FileConvertStatusManager.startConvert(cacheName);
|
||||
CompletableFuture<Boolean> resultFuture = new CompletableFuture<>();
|
||||
|
||||
// 创建转换线程
|
||||
Thread conversionThread = new Thread(() -> {
|
||||
try {
|
||||
boolean result = convertToMp4WithCancellation(filePath, outFilePath,
|
||||
boolean result = convertToMp4WithCancellation(filePath, outFilePath,cacheName,
|
||||
fileAttribute, taskId, resultFuture);
|
||||
resultFuture.complete(result);
|
||||
} catch (Exception e) {
|
||||
@@ -120,10 +122,11 @@ public class Mediatomp4Service {
|
||||
// 设置超时监控
|
||||
File inputFile = new File(filePath);
|
||||
long fileSizeMB = inputFile.length() / (1024 * 1024);
|
||||
scheduleTimeoutMonitor(taskId, calculateTimeout(fileSizeMB));
|
||||
scheduleTimeoutMonitor(taskId, calculateTimeout(fileSizeMB),cacheName);
|
||||
return resultFuture;
|
||||
|
||||
} catch (Exception e) {
|
||||
FileConvertStatusManager.markError(cacheName, "转换过程异常: " + e.getMessage());
|
||||
resultFuture.completeExceptionally(e);
|
||||
cleanupFailedFile(outFilePath);
|
||||
return resultFuture;
|
||||
@@ -134,25 +137,18 @@ public class Mediatomp4Service {
|
||||
* 带取消支持的同步转换方法(核心改进)
|
||||
*/
|
||||
private static boolean convertToMp4WithCancellation(
|
||||
String filePath, String outFilePath, FileAttribute fileAttribute,
|
||||
String filePath, String outFilePath,String cacheName, FileAttribute fileAttribute,
|
||||
String taskId, CompletableFuture<Boolean> resultFuture) throws Exception {
|
||||
|
||||
FFmpegFrameGrabber frameGrabber = null;
|
||||
FFmpegFrameRecorder recorder = null;
|
||||
ConversionContext context = null;
|
||||
|
||||
try {
|
||||
File sourceFile = new File(filePath);
|
||||
if (!sourceFile.exists()) {
|
||||
throw new FileNotFoundException("源文件不存在: " + filePath);
|
||||
}
|
||||
|
||||
File desFile = new File(outFilePath);
|
||||
if (desFile.exists() && !fileAttribute.forceUpdatedCache()) {
|
||||
logger.info("目标文件已存在,跳过转换: {}", outFilePath);
|
||||
return true;
|
||||
}
|
||||
|
||||
FileConvertStatusManager.updateProgress(cacheName, "正在启动转换任务", 10);
|
||||
// 初始化抓取器
|
||||
frameGrabber = new FFmpegFrameGrabber(sourceFile);
|
||||
frameGrabber.setOption("stimeout", "10000000"); // 10秒超时
|
||||
@@ -168,7 +164,7 @@ public class Mediatomp4Service {
|
||||
|
||||
configureRecorder(recorder, frameGrabber);
|
||||
recorder.start();
|
||||
|
||||
FileConvertStatusManager.updateProgress(cacheName, "正在启动转换任务", 40);
|
||||
// 创建任务上下文
|
||||
context = new ConversionContext(frameGrabber, recorder);
|
||||
|
||||
@@ -180,7 +176,7 @@ public class Mediatomp4Service {
|
||||
logger.info("开始转换任务 {}: {} -> {}", taskId, filePath, outFilePath);
|
||||
|
||||
// 核心:使用非阻塞方式读取帧
|
||||
return processFramesWithTimeout(frameGrabber, recorder, context, taskId);
|
||||
return processFramesWithTimeout(frameGrabber, recorder, context, taskId,cacheName);
|
||||
|
||||
} catch (Exception e) {
|
||||
// 检查是否是取消操作
|
||||
@@ -208,7 +204,7 @@ public class Mediatomp4Service {
|
||||
*/
|
||||
private static boolean processFramesWithTimeout(
|
||||
FFmpegFrameGrabber grabber, FFmpegFrameRecorder recorder,
|
||||
ConversionContext context, String taskId) throws Exception {
|
||||
ConversionContext context, String taskId, String cacheName) throws Exception {
|
||||
|
||||
long frameCount = 0;
|
||||
long startTime = System.currentTimeMillis();
|
||||
@@ -227,10 +223,9 @@ public class Mediatomp4Service {
|
||||
logger.warn("任务 {} 帧读取超时,可能文件损坏", taskId);
|
||||
throw new TimeoutException("帧读取超时");
|
||||
}
|
||||
|
||||
// 尝试抓取帧
|
||||
frame = grabber.grabFrame();
|
||||
|
||||
FileConvertStatusManager.updateProgress(cacheName, "正在启动转换任务", 60);
|
||||
if (frame == null) {
|
||||
consecutiveNullFrames++;
|
||||
|
||||
@@ -260,7 +255,7 @@ public class Mediatomp4Service {
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
FileConvertStatusManager.updateProgress(cacheName, "正在启动转换任务", 80);
|
||||
// 成功获取到帧,重置计数器
|
||||
consecutiveNullFrames = 0;
|
||||
lastFrameTime = System.currentTimeMillis();
|
||||
@@ -295,12 +290,12 @@ public class Mediatomp4Service {
|
||||
// 完成录制
|
||||
recorder.stop();
|
||||
recorder.close();
|
||||
|
||||
FileConvertStatusManager.updateProgress(cacheName, "正在启动转换任务", 100);
|
||||
long totalTime = System.currentTimeMillis() - startTime;
|
||||
double fps = totalTime > 0 ? (frameCount * 1000.0) / totalTime : 0;
|
||||
logger.info("任务 {} 转换完成: {} 帧, 耗时: {}ms, 平均速度: {} fps",
|
||||
taskId, frameCount, totalTime, String.format("%.2f", fps));
|
||||
|
||||
FileConvertStatusManager.convertSuccess(cacheName);
|
||||
return true;
|
||||
|
||||
} catch (Exception e) {
|
||||
@@ -404,11 +399,12 @@ public class Mediatomp4Service {
|
||||
/**
|
||||
* 配置超时监控
|
||||
*/
|
||||
private static void scheduleTimeoutMonitor(String taskId, long timeoutSeconds) {
|
||||
private static void scheduleTimeoutMonitor(String taskId, long timeoutSeconds,String cacheName) {
|
||||
ScheduledFuture<?> timeoutFuture = monitorExecutor.schedule(() -> {
|
||||
ConversionTask task = activeTasks.get(taskId);
|
||||
if (task != null && !task.context.completed) {
|
||||
logger.warn("任务 {} 超时 ({}秒),开始强制终止", taskId, timeoutSeconds);
|
||||
FileConvertStatusManager.markTimeout(cacheName);
|
||||
cancelConversion(taskId);
|
||||
task.future.completeExceptionally(
|
||||
new TimeoutException("转换超时: " + timeoutSeconds + "秒")
|
||||
@@ -471,16 +467,6 @@ public class Mediatomp4Service {
|
||||
throw new FileNotFoundException("源文件不存在: " + filePath);
|
||||
}
|
||||
|
||||
File desFile = new File(outFilePath);
|
||||
if (desFile.exists()) {
|
||||
if (fileAttribute.forceUpdatedCache()) {
|
||||
if (!desFile.delete()) {
|
||||
throw new IOException("无法删除已存在的文件: " + outFilePath);
|
||||
}
|
||||
} else {
|
||||
throw new IllegalStateException("目标文件已存在,跳过转换");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -79,29 +79,20 @@ public class OfficeToPdfService {
|
||||
// 格式化显示耗时(支持不同时间单位)
|
||||
String durationFormatted;
|
||||
if (duration.toMinutes() > 0) {
|
||||
durationFormatted = String.format("%d分%d秒",
|
||||
duration.toMinutes(),
|
||||
duration.toSecondsPart());
|
||||
durationFormatted = String.format("%d分%d秒", duration.toMinutes(), duration.toSecondsPart());
|
||||
} else if (duration.toSeconds() > 0) {
|
||||
durationFormatted = String.format("%d.%03d秒",
|
||||
duration.toSeconds(),
|
||||
duration.toMillisPart());
|
||||
durationFormatted = String.format("%d.%03d秒",duration.toSeconds(), duration.toMillisPart());
|
||||
} else {
|
||||
durationFormatted = String.format("%d毫秒", duration.toMillis());
|
||||
}
|
||||
|
||||
logger.info("文件转换成功:{} -> {},耗时:{}",
|
||||
inputFile.getName(),
|
||||
outputFile.getName(),
|
||||
durationFormatted);
|
||||
inputFile.getName(),outputFile.getName(), durationFormatted);
|
||||
|
||||
} catch (OfficeException e) {
|
||||
Instant endTime = Instant.now();
|
||||
Duration duration = Duration.between(startTime, endTime);
|
||||
logger.error("文件转换失败:{},已耗时:{}毫秒,错误信息:{}",
|
||||
inputFile.getName(),
|
||||
duration.toMillis(),
|
||||
e.getMessage());
|
||||
logger.error("文件转换失败:{},已耗时:{}毫秒,错误信息:{}", inputFile.getName(), duration.toMillis(), e.getMessage());
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
||||
package cn.keking.service;
|
||||
|
||||
import cn.keking.config.ConfigConstants;
|
||||
import cn.keking.utils.FileConvertStatusManager;
|
||||
import cn.keking.utils.WebUtils;
|
||||
import cn.keking.web.filter.BaseUrlFilter;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
@@ -60,14 +61,13 @@ public class TifToPdfService {
|
||||
/**
|
||||
* TIF转JPG - 虚拟线程版本
|
||||
*/
|
||||
public List<String> convertTif2Jpg(String strInputFile, String strOutputFile,
|
||||
public List<String> convertTif2Jpg(String strInputFile, String strOutputFile,String fileName,
|
||||
String cacheName,
|
||||
boolean forceUpdatedCache) throws Exception {
|
||||
String fileName = new File(strInputFile).getName();
|
||||
Instant startTime = Instant.now();
|
||||
|
||||
try {
|
||||
List<String> result = performTifToJpgConversionVirtual(
|
||||
strInputFile, strOutputFile, forceUpdatedCache
|
||||
strInputFile, strOutputFile, forceUpdatedCache,fileName,cacheName
|
||||
);
|
||||
|
||||
Duration elapsedTime = Duration.between(startTime, Instant.now());
|
||||
@@ -90,7 +90,8 @@ public class TifToPdfService {
|
||||
* 虚拟线程执行TIF转JPG转换
|
||||
*/
|
||||
private List<String> performTifToJpgConversionVirtual(String strInputFile, String strOutputFile,
|
||||
boolean forceUpdatedCache) throws Exception {
|
||||
boolean forceUpdatedCache,String fileName,
|
||||
String cacheName) throws Exception {
|
||||
Instant totalStart = Instant.now();
|
||||
|
||||
String baseUrl = BaseUrlFilter.getBaseUrl();
|
||||
@@ -105,7 +106,7 @@ public class TifToPdfService {
|
||||
if (!outputDir.exists() && !outputDir.mkdirs()) {
|
||||
throw new IOException("创建目录失败: " + outputDirPath);
|
||||
}
|
||||
|
||||
FileConvertStatusManager.updateProgress(cacheName, "正在转换TIF为PDF", 30);
|
||||
// 加载所有图片
|
||||
List<BufferedImage> images;
|
||||
try {
|
||||
@@ -121,12 +122,12 @@ public class TifToPdfService {
|
||||
logger.warn("TIF文件没有可转换的页面: {}", strInputFile);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
FileConvertStatusManager.updateProgress(cacheName, "正在转换TIF为PDF", 50);
|
||||
List<String> result = convertPagesVirtualThreads(images, outputDirPath, baseUrl, forceUpdatedCache);
|
||||
|
||||
Duration totalTime = Duration.between(totalStart, Instant.now());
|
||||
logger.info("TIF转换PNG完成,总页数: {}, 总耗时: {}ms", pageCount, totalTime.toMillis());
|
||||
|
||||
FileConvertStatusManager.updateProgress(cacheName, "正在转换TIF为PDF", 100);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -211,9 +212,9 @@ public class TifToPdfService {
|
||||
/**
|
||||
* TIF转PDF - 虚拟线程版本
|
||||
*/
|
||||
public void convertTif2Pdf(String strJpgFile, String strPdfFile,
|
||||
public void convertTif2Pdf(String strJpgFile, String strPdfFile,String fileName,
|
||||
String cacheName,
|
||||
boolean forceUpdatedCache) throws Exception {
|
||||
String fileName = new File(strJpgFile).getName();
|
||||
Instant startTime = Instant.now();
|
||||
|
||||
try {
|
||||
@@ -224,8 +225,8 @@ public class TifToPdfService {
|
||||
logger.info("PDF文件已存在,跳过转换: {}", strPdfFile);
|
||||
return;
|
||||
}
|
||||
|
||||
boolean result = performTifToPdfConversionVirtual(strJpgFile, strPdfFile);
|
||||
FileConvertStatusManager.updateProgress(cacheName, "正在转换TIF为PDF", 30);
|
||||
boolean result = performTifToPdfConversionVirtual(strJpgFile, strPdfFile,fileName,cacheName);
|
||||
Duration elapsedTime = Duration.between(startTime, Instant.now());
|
||||
|
||||
logger.info("TIF转PDF{} - 文件: {}, 耗时: {}ms",
|
||||
@@ -244,7 +245,7 @@ public class TifToPdfService {
|
||||
/**
|
||||
* 虚拟线程执行TIF转PDF转换(保持顺序)
|
||||
*/
|
||||
private boolean performTifToPdfConversionVirtual(String strJpgFile, String strPdfFile) throws Exception {
|
||||
private boolean performTifToPdfConversionVirtual(String strJpgFile, String strPdfFile,String fileName,String cacheName) throws Exception {
|
||||
Instant totalStart = Instant.now();
|
||||
|
||||
File tiffFile = new File(strJpgFile);
|
||||
@@ -257,14 +258,14 @@ public class TifToPdfService {
|
||||
logger.warn("TIFF文件没有可转换的页面: {}", strJpgFile);
|
||||
return false;
|
||||
}
|
||||
|
||||
FileConvertStatusManager.updateProgress(cacheName, "正在转换TIF为PDF", 30);
|
||||
int pageCount = images.size();
|
||||
AtomicInteger processedCount = new AtomicInteger(0);
|
||||
AtomicInteger errorCount = new AtomicInteger(0);
|
||||
|
||||
// 创建页面处理结果的列表
|
||||
List<CompletableFuture<ProcessedPageResult>> futures = new ArrayList<>(pageCount);
|
||||
|
||||
FileConvertStatusManager.updateProgress(cacheName, "正在转换TIF为PDF", 50);
|
||||
// 为每个页面创建处理任务
|
||||
for (int pageIndex = 0; pageIndex < pageCount; pageIndex++) {
|
||||
final int currentPageIndex = pageIndex;
|
||||
@@ -286,7 +287,7 @@ public class TifToPdfService {
|
||||
|
||||
futures.add(future);
|
||||
}
|
||||
|
||||
FileConvertStatusManager.updateProgress(cacheName, "正在转换TIF为PDF", 70);
|
||||
// 等待所有任务完成
|
||||
CompletableFuture<Void> allFutures = CompletableFuture.allOf(
|
||||
futures.toArray(new CompletableFuture[0])
|
||||
@@ -332,7 +333,7 @@ public class TifToPdfService {
|
||||
errorCount.incrementAndGet();
|
||||
}
|
||||
}
|
||||
|
||||
FileConvertStatusManager.updateProgress(cacheName, "正在转换TIF为PDF", 100);
|
||||
// 保存PDF
|
||||
document.save(strPdfFile);
|
||||
|
||||
|
||||
@@ -55,7 +55,6 @@ public class CadFilePreviewImpl implements FilePreview {
|
||||
// 预览Type,参数传了就取参数的,没传取系统默认
|
||||
String officePreviewType = fileAttribute.getOfficePreviewType() == null ?
|
||||
ConfigConstants.getOfficePreviewType() : fileAttribute.getOfficePreviewType();
|
||||
String baseUrl = BaseUrlFilter.getBaseUrl();
|
||||
boolean forceUpdatedCache = fileAttribute.forceUpdatedCache();
|
||||
String fileName = fileAttribute.getName();
|
||||
String cadPreviewType = ConfigConstants.getCadPreviewType();
|
||||
@@ -63,20 +62,9 @@ public class CadFilePreviewImpl implements FilePreview {
|
||||
String outFilePath = fileAttribute.getOutFilePath();
|
||||
|
||||
// 查询转换状态
|
||||
FileConvertStatusManager.ConvertStatus status = FileConvertStatusManager.getConvertStatus(fileName);
|
||||
if (status != null) {
|
||||
if (status.getStatus() == FileConvertStatusManager.Status.CONVERTING) {
|
||||
// 正在转换中,返回等待页面
|
||||
model.addAttribute("fileName", fileName);
|
||||
model.addAttribute("message", status.getRealTimeMessage());
|
||||
return WAITING_FILE_PREVIEW_PAGE;
|
||||
} else if (status.getStatus() == FileConvertStatusManager.Status.TIMEOUT) {
|
||||
// 超时状态,不允许重新转换
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, "文件转换已超时,无法继续转换");
|
||||
} else if (status.getStatus() == FileConvertStatusManager.Status.FAILED) {
|
||||
// 失败状态,不允许重新转换
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, "文件转换失败,无法继续转换");
|
||||
}
|
||||
String statusResult = officefilepreviewimpl.checkAndHandleConvertStatus(model, fileName, cacheName, fileAttribute);
|
||||
if (statusResult != null) {
|
||||
return statusResult;
|
||||
}
|
||||
|
||||
// 判断之前是否已转换过,如果转换过,直接返回,否则执行转换
|
||||
@@ -117,6 +105,7 @@ public class CadFilePreviewImpl implements FilePreview {
|
||||
CompletableFuture<Boolean> conversionFuture = cadtopdfservice.cadToPdfAsync(
|
||||
filePath,
|
||||
outFilePath,
|
||||
cacheName,
|
||||
ConfigConstants.getCadPreviewType(),
|
||||
fileAttribute
|
||||
);
|
||||
@@ -166,11 +155,8 @@ public class CadFilePreviewImpl implements FilePreview {
|
||||
|
||||
if (baseUrl != null && (OFFICE_PREVIEW_TYPE_IMAGE.equals(officePreviewType) ||
|
||||
OFFICE_PREVIEW_TYPE_ALL_IMAGES.equals(officePreviewType))) {
|
||||
return officefilepreviewimpl.getPreviewType(model, fileAttribute, officePreviewType,
|
||||
cacheName, outFilePath, fileHandlerService,
|
||||
OFFICE_PREVIEW_TYPE_IMAGE, otherFilePreview);
|
||||
return officefilepreviewimpl.getPreviewType(model, fileAttribute, officePreviewType, cacheName, outFilePath);
|
||||
}
|
||||
|
||||
model.addAttribute("pdfUrl", cacheName);
|
||||
return PDF_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,11 @@ public class CodeFilePreviewImpl implements FilePreview {
|
||||
@Override
|
||||
public String filePreviewHandle(String url, Model model, FileAttribute fileAttribute) {
|
||||
filePreviewHandle.filePreviewHandle(url, model, fileAttribute);
|
||||
String suffix = fileAttribute.getSuffix();
|
||||
if(suffix.equalsIgnoreCase("htm") || suffix.equalsIgnoreCase("html") || suffix.equalsIgnoreCase("shtml") ){
|
||||
model.addAttribute("pdfUrl", url);
|
||||
return TXT_FILE_PREVIEW_PAGE; //直接输出html
|
||||
}
|
||||
return CODE_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,23 +8,23 @@ import cn.keking.service.FileHandlerService;
|
||||
import cn.keking.service.FilePreview;
|
||||
import cn.keking.service.Mediatomp4Service;
|
||||
import cn.keking.utils.DownloadUtils;
|
||||
import cn.keking.utils.FileConvertStatusManager;
|
||||
import cn.keking.utils.KkFileUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.ui.Model;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* @author : kl
|
||||
* @authorboke : kailing.pub
|
||||
* @create : 2018-03-25 上午11:58
|
||||
* @description:
|
||||
* @description: 异步视频文件预览实现
|
||||
**/
|
||||
@Service
|
||||
public class MediaFilePreviewImpl implements FilePreview {
|
||||
@@ -32,10 +32,20 @@ public class MediaFilePreviewImpl implements FilePreview {
|
||||
private static final Logger logger = LoggerFactory.getLogger(MediaFilePreviewImpl.class);
|
||||
private final FileHandlerService fileHandlerService;
|
||||
private final OtherFilePreviewImpl otherFilePreview;
|
||||
private final Mediatomp4Service mediatomp4Service;
|
||||
private final OfficeFilePreviewImpl officefilepreviewimpl;
|
||||
|
||||
public MediaFilePreviewImpl(FileHandlerService fileHandlerService, OtherFilePreviewImpl otherFilePreview) {
|
||||
// 用于处理回调的线程池
|
||||
private static final ExecutorService callbackExecutor = Executors.newFixedThreadPool(3);
|
||||
|
||||
public MediaFilePreviewImpl(FileHandlerService fileHandlerService,
|
||||
OtherFilePreviewImpl otherFilePreview,
|
||||
Mediatomp4Service mediatomp4Service,
|
||||
OfficeFilePreviewImpl officefilepreviewimpl) {
|
||||
this.fileHandlerService = fileHandlerService;
|
||||
this.otherFilePreview = otherFilePreview;
|
||||
this.mediatomp4Service = mediatomp4Service;
|
||||
this.officefilepreviewimpl = officefilepreviewimpl;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -46,69 +56,61 @@ public class MediaFilePreviewImpl implements FilePreview {
|
||||
String outFilePath = fileAttribute.getOutFilePath();
|
||||
boolean forceUpdatedCache = fileAttribute.forceUpdatedCache();
|
||||
FileType type = fileAttribute.getType();
|
||||
String[] mediaTypesConvert = FileType.MEDIA_CONVERT_TYPES; //获取支持的转换格式
|
||||
|
||||
// 检查是否是需要转换的视频格式
|
||||
boolean mediaTypes = false;
|
||||
String[] mediaTypesConvert = FileType.MEDIA_CONVERT_TYPES;
|
||||
for (String temp : mediaTypesConvert) {
|
||||
if (suffix.equalsIgnoreCase(temp)) {
|
||||
mediaTypes = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 查询转换状态
|
||||
String statusResult = officefilepreviewimpl.checkAndHandleConvertStatus(model, fileName, cacheName, fileAttribute);
|
||||
if (statusResult != null) {
|
||||
return statusResult;
|
||||
}
|
||||
// 非HTTP协议或需要转换的文件
|
||||
if (!url.toLowerCase().startsWith("http") || checkNeedConvert(mediaTypes)) {
|
||||
// 检查缓存
|
||||
File outputFile = new File(outFilePath);
|
||||
if (outputFile.exists() && !forceUpdatedCache && ConfigConstants.isCacheEnabled()) {
|
||||
String relativePath = fileHandlerService.getRelativePath(outFilePath);
|
||||
if (fileHandlerService.listConvertedFiles().containsKey(cacheName)) {
|
||||
if (fileHandlerService.listConvertedMedias().containsKey(cacheName)) {
|
||||
model.addAttribute("mediaUrl", relativePath);
|
||||
logger.info("使用已缓存的视频文件: {}", cacheName);
|
||||
return MEDIA_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
}
|
||||
|
||||
// 下载文件
|
||||
ReturnResponse<String> response = DownloadUtils.downLoad(fileAttribute, fileName);
|
||||
if (response.isFailure()) {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, response.getMsg());
|
||||
}
|
||||
|
||||
String filePath = response.getContent();
|
||||
|
||||
try {
|
||||
if (mediaTypes) {
|
||||
// 检查文件大小限制
|
||||
if (isFileSizeExceeded(filePath)) {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute,
|
||||
"视频文件大小超过" + ConfigConstants.getMediaConvertMaxSize() + "MB限制,禁止转换");
|
||||
}
|
||||
|
||||
// 使用改进的转换方法
|
||||
String convertedPath = convertVideoWithImprovedTimeout(filePath, outFilePath, fileAttribute);
|
||||
if (convertedPath != null) {
|
||||
model.addAttribute("mediaUrl", fileHandlerService.getRelativePath(convertedPath));
|
||||
|
||||
// 缓存转换结果
|
||||
if (ConfigConstants.isCacheEnabled()) {
|
||||
fileHandlerService.addConvertedFile(cacheName, fileHandlerService.getRelativePath(convertedPath));
|
||||
}
|
||||
return MEDIA_FILE_PREVIEW_PAGE;
|
||||
} else {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, "视频转换失败,请联系管理员");
|
||||
}
|
||||
} else {
|
||||
// 不需要转换的文件
|
||||
model.addAttribute("mediaUrl", fileHandlerService.getRelativePath(outFilePath));
|
||||
return MEDIA_FILE_PREVIEW_PAGE;
|
||||
if (mediaTypes) {
|
||||
// 检查文件大小限制
|
||||
if (isFileSizeExceeded(filePath)) {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute,
|
||||
"视频文件大小超过" + ConfigConstants.getMediaConvertMaxSize() + "MB限制,禁止转换");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("处理媒体文件失败: {}", filePath, e);
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute,
|
||||
"视频处理异常: " + getErrorMessage(e));
|
||||
try {
|
||||
// 启动异步转换,并添加回调处理
|
||||
startAsyncConversion(filePath, outFilePath, cacheName, fileAttribute);
|
||||
// 返回等待页面
|
||||
model.addAttribute("fileName", fileName);
|
||||
model.addAttribute("message", "视频文件正在转换中,请稍候...");
|
||||
return WAITING_FILE_PREVIEW_PAGE;
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to start video conversion: {}", filePath, e);
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, "视频转换异常,请联系管理员");
|
||||
}
|
||||
} else {
|
||||
// 不需要转换的文件,直接返回
|
||||
model.addAttribute("mediaUrl", fileHandlerService.getRelativePath(outFilePath));
|
||||
return MEDIA_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
}
|
||||
|
||||
// HTTP协议的媒体文件,直接播放
|
||||
if (type.equals(FileType.MEDIA)) {
|
||||
model.addAttribute("mediaUrl", url);
|
||||
@@ -118,6 +120,45 @@ public class MediaFilePreviewImpl implements FilePreview {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, "系统还不支持该格式文件的在线预览");
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动异步转换,并在转换完成后处理后续操作
|
||||
*/
|
||||
private void startAsyncConversion(String filePath, String outFilePath,
|
||||
String cacheName, FileAttribute fileAttribute) {
|
||||
// 启动异步转换
|
||||
CompletableFuture<Boolean> conversionFuture = mediatomp4Service.convertToMp4Async(
|
||||
filePath,
|
||||
outFilePath,
|
||||
cacheName,
|
||||
fileAttribute
|
||||
);
|
||||
|
||||
// 添加转换完成后的回调
|
||||
conversionFuture.whenCompleteAsync((success, throwable) -> {
|
||||
if (success != null && success) {
|
||||
try {
|
||||
// 1. 是否保留源文件(只在转换成功后才删除)
|
||||
if (!fileAttribute.isCompressFile() && ConfigConstants.getDeleteSourceFile()) {
|
||||
KkFileUtils.deleteFileByPath(filePath);
|
||||
}
|
||||
// 2. 加入视频缓存(只在转换成功后才添加)
|
||||
if (ConfigConstants.isCacheEnabled()) {
|
||||
fileHandlerService.addConvertedMedias(cacheName,
|
||||
fileHandlerService.getRelativePath(outFilePath));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("视频转换后续处理失败: {}", filePath, e);
|
||||
}
|
||||
} else {
|
||||
// 转换失败,保留源文件供排查问题
|
||||
logger.error("视频转换失败,保留源文件: {}", filePath);
|
||||
if (throwable != null) {
|
||||
logger.error("视频转换失败原因: ", throwable);
|
||||
}
|
||||
}
|
||||
}, callbackExecutor);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查文件大小是否超过限制
|
||||
*/
|
||||
@@ -139,99 +180,14 @@ public class MediaFilePreviewImpl implements FilePreview {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 改进的转换方法
|
||||
*/
|
||||
private String convertVideoWithImprovedTimeout(String filePath, String outFilePath,
|
||||
FileAttribute fileAttribute) {
|
||||
try {
|
||||
// 检查文件是否存在
|
||||
File outputFile = new File(outFilePath);
|
||||
if (outputFile.exists() && !fileAttribute.forceUpdatedCache()) {
|
||||
logger.info("输出文件已存在且非强制更新模式,跳过转换!");
|
||||
return outFilePath;
|
||||
}
|
||||
|
||||
// 使用改进的异步转换方法
|
||||
CompletableFuture<Boolean> future =
|
||||
Mediatomp4Service.convertToMp4Async(filePath, outFilePath, fileAttribute);
|
||||
|
||||
// 计算超时时间
|
||||
File inputFile = new File(filePath);
|
||||
long fileSizeMB = inputFile.length() / (1024 * 1024);
|
||||
int timeoutSeconds = calculateTimeout(fileSizeMB);
|
||||
|
||||
try {
|
||||
boolean result = future.get(timeoutSeconds, TimeUnit.SECONDS);
|
||||
if (result) {
|
||||
// 验证输出文件
|
||||
File convertedFile = new File(outFilePath);
|
||||
if (!convertedFile.exists() || convertedFile.length() == 0) {
|
||||
throw new IOException("转换完成但输出文件无效");
|
||||
}
|
||||
return outFilePath;
|
||||
} else {
|
||||
throw new Exception("转换返回失败状态");
|
||||
}
|
||||
} catch (TimeoutException e) {
|
||||
// 超时后尝试获取任务ID并取消
|
||||
logger.error("视频转换超时: {}, 文件大小: {}MB, 超时: {}秒",
|
||||
filePath, fileSizeMB, timeoutSeconds);
|
||||
|
||||
throw new RuntimeException("视频转换超时,文件可能过大");
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("视频转换异常: {}", filePath, e);
|
||||
throw new RuntimeException("视频转换失败: " + getErrorMessage(e), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算超时时间 - 从配置文件读取
|
||||
*/
|
||||
public int calculateTimeout(long fileSizeMB) {
|
||||
// 如果超时功能被禁用,返回一个非常大的值
|
||||
if (!ConfigConstants.isMediaTimeoutEnabled()) {
|
||||
return Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
// 根据文件大小从配置文件读取超时时间
|
||||
if (fileSizeMB < 10) return ConfigConstants.getMediaSmallFileTimeout(); // 小文件
|
||||
if (fileSizeMB < 50) return ConfigConstants.getMediaMediumFileTimeout(); // 中等文件
|
||||
if (fileSizeMB < 200) return ConfigConstants.getMediaLargeFileTimeout(); // 较大文件
|
||||
if (fileSizeMB < 500) return ConfigConstants.getMediaXLFileTimeout(); // 大文件
|
||||
if (fileSizeMB < 1024) return ConfigConstants.getMediaXXLFileTimeout(); // 超大文件
|
||||
return ConfigConstants.getMediaXXXLFileTimeout(); // 极大文件
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否需要转换
|
||||
*/
|
||||
private boolean checkNeedConvert(boolean mediaTypes) {
|
||||
// 1.检查开关是否开启
|
||||
// 检查转换开关是否开启
|
||||
if ("true".equals(ConfigConstants.getMediaConvertDisable())) {
|
||||
return mediaTypes;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取友好的错误信息
|
||||
*/
|
||||
private String getErrorMessage(Exception e) {
|
||||
if (e instanceof CancellationException) {
|
||||
return "转换被取消";
|
||||
} else if (e instanceof TimeoutException) {
|
||||
return "转换超时";
|
||||
} else if (e.getMessage() != null) {
|
||||
// 截取主要错误信息
|
||||
String msg = e.getMessage();
|
||||
if (msg.length() > 100) {
|
||||
msg = msg.substring(0, 100) + "...";
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
return "未知错误";
|
||||
}
|
||||
}
|
||||
@@ -8,19 +8,23 @@ import cn.keking.service.FilePreview;
|
||||
import cn.keking.service.OfficeToPdfService;
|
||||
import cn.keking.service.PdfToJpgService;
|
||||
import cn.keking.utils.DownloadUtils;
|
||||
import cn.keking.utils.FileConvertStatusManager;
|
||||
import cn.keking.utils.KkFileUtils;
|
||||
import cn.keking.utils.OfficeUtils;
|
||||
import cn.keking.utils.WebUtils;
|
||||
import cn.keking.web.filter.BaseUrlFilter;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.apache.poi.EncryptedDocumentException;
|
||||
import org.jodconverter.core.office.OfficeException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* Created by kl on 2018/1/17.
|
||||
@@ -29,9 +33,12 @@ import java.util.List;
|
||||
@Service
|
||||
public class OfficeFilePreviewImpl implements FilePreview {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(OfficeFilePreviewImpl.class);
|
||||
public static final String OFFICE_PREVIEW_TYPE_IMAGE = "image";
|
||||
public static final String OFFICE_PREVIEW_TYPE_ALL_IMAGES = "allImages";
|
||||
private static final String OFFICE_PASSWORD_MSG = "password";
|
||||
|
||||
// 用于处理回调的线程池
|
||||
private static final ExecutorService callbackExecutor = Executors.newFixedThreadPool(3);
|
||||
|
||||
private final FileHandlerService fileHandlerService;
|
||||
private final OfficeToPdfService officeToPdfService;
|
||||
@@ -54,12 +61,16 @@ public class OfficeFilePreviewImpl implements FilePreview {
|
||||
String suffix = fileAttribute.getSuffix(); //获取文件后缀
|
||||
String fileName = fileAttribute.getName(); //获取文件原始名称
|
||||
String filePassword = fileAttribute.getFilePassword(); //获取密码
|
||||
boolean forceUpdatedCache=fileAttribute.forceUpdatedCache(); //是否启用强制更新命令
|
||||
boolean forceUpdatedCache = fileAttribute.forceUpdatedCache(); //是否启用强制更新命令
|
||||
boolean isHtmlView = fileAttribute.isHtmlView(); //xlsx 转换成html
|
||||
String cacheName = fileAttribute.getCacheName(); //转换后的文件名
|
||||
String outFilePath = fileAttribute.getOutFilePath(); //转换后生成文件的路径
|
||||
|
||||
// 查询转换状态
|
||||
checkAndHandleConvertStatus(model, fileName, cacheName,fileAttribute);
|
||||
|
||||
if (!officePreviewType.equalsIgnoreCase("html")) {
|
||||
if (ConfigConstants.getOfficeTypeWeb() .equalsIgnoreCase("web")) {
|
||||
if (ConfigConstants.getOfficeTypeWeb().equalsIgnoreCase("web")) {
|
||||
if (suffix.equalsIgnoreCase("xlsx")) {
|
||||
model.addAttribute("pdfUrl", KkFileUtils.htmlEscape(url)); //特殊符号处理
|
||||
return XLSX_FILE_PREVIEW_PAGE;
|
||||
@@ -70,14 +81,190 @@ public class OfficeFilePreviewImpl implements FilePreview {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (forceUpdatedCache|| !fileHandlerService.listConvertedFiles().containsKey(cacheName) || !ConfigConstants.isCacheEnabled()) {
|
||||
// 下载远程文件到本地,如果文件在本地已存在不会重复下载
|
||||
ReturnResponse<String> response = DownloadUtils.downLoad(fileAttribute, fileName);
|
||||
if (response.isFailure()) {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, response.getMsg());
|
||||
|
||||
// 图片预览模式(异步转换)
|
||||
if (!isHtmlView && baseUrl != null && (OFFICE_PREVIEW_TYPE_IMAGE.equals(officePreviewType) || OFFICE_PREVIEW_TYPE_ALL_IMAGES.equals(officePreviewType))) {
|
||||
boolean jiami = false;
|
||||
if (!ObjectUtils.isEmpty(filePassword)) {
|
||||
jiami = pdftojpgservice.hasEncryptedPdfCacheSimple(outFilePath);
|
||||
}
|
||||
if (forceUpdatedCache || !fileHandlerService.listConvertedFiles().containsKey(cacheName) || !ConfigConstants.isCacheEnabled()) {
|
||||
if (jiami) {
|
||||
return getPreviewType(model, fileAttribute, officePreviewType, cacheName, outFilePath);
|
||||
}
|
||||
// 下载文件
|
||||
ReturnResponse<String> response = DownloadUtils.downLoad(fileAttribute, fileName);
|
||||
if (response.isFailure()) {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, response.getMsg());
|
||||
}
|
||||
String filePath = response.getContent();
|
||||
|
||||
// 检查是否加密文件
|
||||
boolean isPwdProtectedOffice = OfficeUtils.isPwdProtected(filePath);
|
||||
if (isPwdProtectedOffice && !StringUtils.hasLength(filePassword)) {
|
||||
// 加密文件需要密码
|
||||
model.addAttribute("needFilePassword", true);
|
||||
model.addAttribute("fileName", fileName);
|
||||
model.addAttribute("cacheName", cacheName);
|
||||
return EXEL_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
|
||||
try {
|
||||
// 启动异步转换
|
||||
startAsyncOfficeConversion(filePath, outFilePath, cacheName, fileAttribute, officePreviewType);
|
||||
// 返回等待页面
|
||||
model.addAttribute("fileName", fileName);
|
||||
model.addAttribute("message", "文件正在转换中,请稍候...");
|
||||
return WAITING_FILE_PREVIEW_PAGE;
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to start Office conversion: {}", filePath, e);
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, "文件转换异常,请联系管理员");
|
||||
}
|
||||
} else {
|
||||
// 如果已有缓存,直接渲染预览
|
||||
return getPreviewType(model, fileAttribute, officePreviewType, cacheName, outFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
// 处理普通Office转PDF预览
|
||||
return handleRegularOfficePreview(model, fileAttribute, fileName, forceUpdatedCache, cacheName, outFilePath,
|
||||
isHtmlView, userToken, filePassword);
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动异步Office转换
|
||||
*/
|
||||
private void startAsyncOfficeConversion(String filePath, String outFilePath, String cacheName,
|
||||
FileAttribute fileAttribute,
|
||||
String officePreviewType) {
|
||||
// 启动异步转换
|
||||
CompletableFuture<List<String>> conversionFuture = CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
// 更新状态
|
||||
FileConvertStatusManager.startConvert(cacheName);
|
||||
FileConvertStatusManager.updateProgress(cacheName, "正在启动Office转换", 20);
|
||||
|
||||
// 转换Office到PDF
|
||||
FileConvertStatusManager.updateProgress(cacheName, "正在转换Office到jpg", 60);
|
||||
officeToPdfService.openOfficeToPDF(filePath, outFilePath, fileAttribute);
|
||||
|
||||
|
||||
if (fileAttribute.isHtmlView()) {
|
||||
// 对转换后的文件进行操作(改变编码方式)
|
||||
FileConvertStatusManager.updateProgress(cacheName, "处理HTML编码", 95);
|
||||
fileHandlerService.doActionConvertedFile(outFilePath);
|
||||
}
|
||||
|
||||
// 是否需要转换为图片
|
||||
List<String> imageUrls = null;
|
||||
if (OFFICE_PREVIEW_TYPE_IMAGE.equals(officePreviewType) ||
|
||||
OFFICE_PREVIEW_TYPE_ALL_IMAGES.equals(officePreviewType)) {
|
||||
FileConvertStatusManager.updateProgress(cacheName, "正在转换PDF为图片", 90);
|
||||
imageUrls = pdftojpgservice.pdf2jpg(outFilePath, outFilePath, cacheName, fileAttribute);
|
||||
}
|
||||
|
||||
// 缓存处理
|
||||
boolean isPwdProtectedOffice = OfficeUtils.isPwdProtected(filePath);
|
||||
boolean userToken = fileAttribute.getUsePasswordCache();
|
||||
String filePassword = fileAttribute.getFilePassword();
|
||||
|
||||
if (ConfigConstants.isCacheEnabled() && (ObjectUtils.isEmpty(filePassword) || userToken || !isPwdProtectedOffice)) {
|
||||
fileHandlerService.addConvertedFile(cacheName, fileHandlerService.getRelativePath(outFilePath));
|
||||
}
|
||||
|
||||
FileConvertStatusManager.updateProgress(cacheName, "转换完成", 100);
|
||||
FileConvertStatusManager.convertSuccess(cacheName);
|
||||
|
||||
return imageUrls;
|
||||
} catch (OfficeException e) {
|
||||
boolean isPwdProtectedOffice = OfficeUtils.isPwdProtected(filePath);
|
||||
String filePassword = fileAttribute.getFilePassword();
|
||||
if (isPwdProtectedOffice && !OfficeUtils.isCompatible(filePath, filePassword)) {
|
||||
FileConvertStatusManager.markError(cacheName, "文件密码错误,请重新输入");
|
||||
} else {
|
||||
logger.error("Office转换执行失败: {}", cacheName, e);
|
||||
FileConvertStatusManager.markError(cacheName, "Office转换失败: " + e.getMessage());
|
||||
}
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
logger.error("Office转换执行失败: {}", cacheName, e);
|
||||
|
||||
// 检查是否已经标记为超时
|
||||
FileConvertStatusManager.ConvertStatus status = FileConvertStatusManager.getConvertStatus(cacheName);
|
||||
if (status == null || status.getStatus() != FileConvertStatusManager.Status.TIMEOUT) {
|
||||
FileConvertStatusManager.markError(cacheName, "转换失败: " + e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
// 添加转换完成后的回调
|
||||
conversionFuture.thenAcceptAsync(imageUrls -> {
|
||||
try {
|
||||
// 这里假设imageUrls不为null且不为空表示转换成功
|
||||
if (imageUrls != null && !imageUrls.isEmpty()) {
|
||||
// 是否保留源文件(只在转换成功后才删除)
|
||||
if (!fileAttribute.isCompressFile() && ConfigConstants.getDeleteSourceFile()) {
|
||||
KkFileUtils.deleteFileByPath(filePath);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Office转换后续处理失败: {}", filePath, e);
|
||||
}
|
||||
}, callbackExecutor);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取预览类型(图片预览)
|
||||
*/
|
||||
String getPreviewType(Model model, FileAttribute fileAttribute, String officePreviewType,
|
||||
String cacheName, String outFilePath) {
|
||||
String suffix = fileAttribute.getSuffix();
|
||||
boolean isPPT = suffix.equalsIgnoreCase("ppt") || suffix.equalsIgnoreCase("pptx");
|
||||
List<String> imageUrls;
|
||||
|
||||
try {
|
||||
if (pdftojpgservice.hasEncryptedPdfCacheSimple(outFilePath)) {
|
||||
imageUrls = pdftojpgservice.getEncryptedPdfCache(outFilePath);
|
||||
} else {
|
||||
imageUrls = fileHandlerService.loadPdf2jpgCache(outFilePath);
|
||||
}
|
||||
|
||||
if (imageUrls == null || imageUrls.isEmpty()) {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, "Office转换缓存异常,请联系管理员");
|
||||
}
|
||||
|
||||
model.addAttribute("imgUrls", imageUrls);
|
||||
model.addAttribute("currentUrl", imageUrls.getFirst());
|
||||
|
||||
if (OFFICE_PREVIEW_TYPE_IMAGE.equals(officePreviewType)) {
|
||||
// PPT 图片模式使用专用预览页面
|
||||
return (isPPT ? PPT_FILE_PREVIEW_PAGE : OFFICE_PICTURE_FILE_PREVIEW_PAGE);
|
||||
} else {
|
||||
return PICTURE_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("渲染Office预览页面失败: {}", cacheName, e);
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, "渲染预览页面异常,请联系管理员");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理普通Office预览(转PDF)
|
||||
*/
|
||||
private String handleRegularOfficePreview(Model model, FileAttribute fileAttribute,
|
||||
String fileName, boolean forceUpdatedCache, String cacheName,
|
||||
String outFilePath, boolean isHtmlView, boolean userToken,
|
||||
String filePassword) {
|
||||
|
||||
if (forceUpdatedCache || !fileHandlerService.listConvertedFiles().containsKey(cacheName) || !ConfigConstants.isCacheEnabled()) {
|
||||
// 下载远程文件到本地,如果文件在本地已存在不会重复下载
|
||||
ReturnResponse<String> response = DownloadUtils.downLoad(fileAttribute, fileName);
|
||||
if (response.isFailure()) {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, response.getMsg());
|
||||
}
|
||||
String filePath = response.getContent();
|
||||
boolean isPwdProtectedOffice = OfficeUtils.isPwdProtected(filePath); // 判断是否加密文件
|
||||
|
||||
boolean isPwdProtectedOffice = OfficeUtils.isPwdProtected(filePath); // 判断是否加密文件
|
||||
if (isPwdProtectedOffice && !StringUtils.hasLength(filePassword)) {
|
||||
// 加密文件需要密码
|
||||
model.addAttribute("needFilePassword", true);
|
||||
@@ -109,43 +296,33 @@ public class OfficeFilePreviewImpl implements FilePreview {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if (!isHtmlView && baseUrl != null && (OFFICE_PREVIEW_TYPE_IMAGE.equals(officePreviewType) || OFFICE_PREVIEW_TYPE_ALL_IMAGES.equals(officePreviewType))) {
|
||||
return getPreviewType(model, fileAttribute, officePreviewType, cacheName, outFilePath, fileHandlerService, OFFICE_PREVIEW_TYPE_IMAGE, otherFilePreview);
|
||||
}
|
||||
model.addAttribute("pdfUrl", WebUtils.encodeFileName(cacheName)); //输出转义文件名 方便url识别
|
||||
return isHtmlView ? EXEL_FILE_PREVIEW_PAGE : PDF_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
|
||||
String getPreviewType(Model model, FileAttribute fileAttribute, String officePreviewType, String pdfName, String outFilePath, FileHandlerService fileHandlerService, String officePreviewTypeImage, OtherFilePreviewImpl otherFilePreview) {
|
||||
String suffix = fileAttribute.getSuffix();
|
||||
boolean isPPT = suffix.equalsIgnoreCase("ppt") || suffix.equalsIgnoreCase("pptx");
|
||||
List<String> imageUrls = null;
|
||||
try {
|
||||
imageUrls = pdftojpgservice.pdf2jpg(outFilePath,outFilePath, pdfName, fileAttribute);
|
||||
} catch (Exception e) {
|
||||
Throwable[] throwableArray = ExceptionUtils.getThrowables(e);
|
||||
for (Throwable throwable : throwableArray) {
|
||||
if (throwable instanceof IOException || throwable instanceof EncryptedDocumentException) {
|
||||
if (e.getMessage().toLowerCase().contains(OFFICE_PASSWORD_MSG)) {
|
||||
model.addAttribute("needFilePassword", true);
|
||||
return EXEL_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 异步方法
|
||||
*/
|
||||
public String checkAndHandleConvertStatus(Model model, String fileName, String cacheName, FileAttribute fileAttribute){
|
||||
FileConvertStatusManager.ConvertStatus status = FileConvertStatusManager.getConvertStatus(cacheName);
|
||||
int refreshSchedule = ConfigConstants.getTime();
|
||||
if (status != null) {
|
||||
if (status.getStatus() == FileConvertStatusManager.Status.CONVERTING) {
|
||||
// 正在转换中,返回等待页面
|
||||
model.addAttribute("fileName", fileName);
|
||||
model.addAttribute("time", refreshSchedule);
|
||||
model.addAttribute("message", status.getRealTimeMessage());
|
||||
return WAITING_FILE_PREVIEW_PAGE;
|
||||
} else if (status.getStatus() == FileConvertStatusManager.Status.TIMEOUT) {
|
||||
// 超时状态,不允许重新转换
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, "文件转换已超时,无法继续转换");
|
||||
} else if (status.getStatus() == FileConvertStatusManager.Status.FAILED) {
|
||||
// 失败状态,不允许重新转换
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, "文件转换失败,无法继续转换");
|
||||
}
|
||||
}
|
||||
if (imageUrls == null || imageUrls.size() < 1) {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, "office转图片异常,请联系管理员");
|
||||
}
|
||||
model.addAttribute("imgUrls", imageUrls);
|
||||
model.addAttribute("currentUrl", imageUrls.get(0));
|
||||
if (officePreviewTypeImage.equals(officePreviewType)) {
|
||||
// PPT 图片模式使用专用预览页面
|
||||
return (isPPT ? PPT_FILE_PREVIEW_PAGE : OFFICE_PICTURE_FILE_PREVIEW_PAGE);
|
||||
} else {
|
||||
return PICTURE_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -7,14 +7,23 @@ import cn.keking.service.FileHandlerService;
|
||||
import cn.keking.service.FilePreview;
|
||||
import cn.keking.service.PdfToJpgService;
|
||||
import cn.keking.utils.DownloadUtils;
|
||||
import cn.keking.utils.FileConvertStatusManager;
|
||||
import cn.keking.utils.KkFileUtils;
|
||||
import cn.keking.utils.WebUtils;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.apache.poi.EncryptedDocumentException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* Created by kl on 2018/1/17.
|
||||
@@ -23,80 +32,263 @@ import java.util.List;
|
||||
@Service
|
||||
public class PdfFilePreviewImpl implements FilePreview {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(PdfFilePreviewImpl.class);
|
||||
private static final String PDF_PASSWORD_MSG = "password";
|
||||
private final FileHandlerService fileHandlerService;
|
||||
private final OtherFilePreviewImpl otherFilePreview;
|
||||
private final PdfToJpgService pdftojpgservice;
|
||||
private static final String PDF_PASSWORD_MSG = "password";
|
||||
public PdfFilePreviewImpl(FileHandlerService fileHandlerService, OtherFilePreviewImpl otherFilePreview, PdfToJpgService pdftojpgservice) {
|
||||
|
||||
// 用于处理回调的线程池
|
||||
private static final ExecutorService callbackExecutor = Executors.newFixedThreadPool(3);
|
||||
|
||||
public PdfFilePreviewImpl(FileHandlerService fileHandlerService,
|
||||
OtherFilePreviewImpl otherFilePreview,
|
||||
PdfToJpgService pdftojpgservice) {
|
||||
this.fileHandlerService = fileHandlerService;
|
||||
this.otherFilePreview = otherFilePreview;
|
||||
this.pdftojpgservice = pdftojpgservice;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String filePreviewHandle(String url, Model model, FileAttribute fileAttribute) {
|
||||
String pdfName = fileAttribute.getName(); //获取原始文件名
|
||||
String officePreviewType = fileAttribute.getOfficePreviewType(); //转换类型
|
||||
boolean forceUpdatedCache=fileAttribute.forceUpdatedCache(); //是否启用强制更新命令
|
||||
boolean forceUpdatedCache = fileAttribute.forceUpdatedCache(); //是否启用强制更新命令
|
||||
String outFilePath = fileAttribute.getOutFilePath(); //生成的文件路径
|
||||
String originFilePath = fileAttribute.getOriginFilePath(); //原始文件路径
|
||||
if (OfficeFilePreviewImpl.OFFICE_PREVIEW_TYPE_IMAGE.equals(officePreviewType) || OfficeFilePreviewImpl.OFFICE_PREVIEW_TYPE_ALL_IMAGES.equals(officePreviewType)) {
|
||||
//当文件不存在时,就去下载
|
||||
if (forceUpdatedCache || !fileHandlerService.listConvertedFiles().containsKey(pdfName) || !ConfigConstants.isCacheEnabled()) {
|
||||
String originFilePath; //原始文件路径
|
||||
String cacheName = fileAttribute.getCacheName();
|
||||
String filePassword = fileAttribute.getFilePassword(); // 获取密码
|
||||
int refreshSchedule = ConfigConstants.getTime();
|
||||
// 查询转换状态
|
||||
FileConvertStatusManager.ConvertStatus status = FileConvertStatusManager.getConvertStatus(cacheName);
|
||||
if (status != null) {
|
||||
if (status.getStatus() == FileConvertStatusManager.Status.CONVERTING) {
|
||||
// 正在转换中,返回等待页面
|
||||
model.addAttribute("fileName", pdfName);
|
||||
model.addAttribute("time", refreshSchedule);
|
||||
model.addAttribute("message", status.getRealTimeMessage());
|
||||
return WAITING_FILE_PREVIEW_PAGE;
|
||||
} else if (status.getStatus() == FileConvertStatusManager.Status.TIMEOUT) {
|
||||
// 超时状态,不允许重新转换
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, "文件转换已超时,无法继续转换");
|
||||
}
|
||||
}
|
||||
boolean jiami=false;
|
||||
if(!ObjectUtils.isEmpty(filePassword)){
|
||||
jiami=pdftojpgservice.hasEncryptedPdfCacheSimple(outFilePath);
|
||||
}
|
||||
if (OfficeFilePreviewImpl.OFFICE_PREVIEW_TYPE_IMAGE.equals(officePreviewType) ||
|
||||
OfficeFilePreviewImpl.OFFICE_PREVIEW_TYPE_ALL_IMAGES.equals(officePreviewType)) {
|
||||
|
||||
// 判断之前是否已转换过,如果转换过,直接返回,否则执行转换
|
||||
if (forceUpdatedCache || !fileHandlerService.listConvertedFiles().containsKey(cacheName) || !ConfigConstants.isCacheEnabled()) {
|
||||
if(jiami){
|
||||
return renderPreview(model, cacheName, outFilePath,
|
||||
officePreviewType, pdfName, fileAttribute);
|
||||
}
|
||||
// 当文件不存在时,就去下载
|
||||
ReturnResponse<String> response = DownloadUtils.downLoad(fileAttribute, pdfName);
|
||||
if (response.isFailure()) {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, response.getMsg());
|
||||
}
|
||||
originFilePath = response.getContent();
|
||||
if (ConfigConstants.isCacheEnabled()) {
|
||||
// 加入缓存
|
||||
fileHandlerService.addConvertedFile(pdfName, fileHandlerService.getRelativePath(originFilePath));
|
||||
// 检查文件是否需要密码,但不启动转换
|
||||
if (filePassword == null || filePassword.trim().isEmpty()) {
|
||||
// 没有提供密码,先检查文件是否需要密码
|
||||
if (checkIfPdfNeedsPassword(originFilePath, cacheName, pdfName)) {
|
||||
model.addAttribute("needFilePassword", true);
|
||||
model.addAttribute("fileName", pdfName);
|
||||
model.addAttribute("cacheName", cacheName);
|
||||
return EXEL_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
}
|
||||
try {
|
||||
// 启动异步转换
|
||||
startAsyncPdfConversion(originFilePath, outFilePath, cacheName, pdfName, fileAttribute);
|
||||
// 返回等待页面
|
||||
model.addAttribute("fileName", pdfName);
|
||||
model.addAttribute("message", "文件正在转换中,请稍候...");
|
||||
return WAITING_FILE_PREVIEW_PAGE;
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to start PDF conversion: {}", originFilePath, e);
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, "PDF转换异常,请联系管理员");
|
||||
}
|
||||
} else {
|
||||
// 如果已有缓存,直接渲染预览
|
||||
return renderPreview(model, cacheName, outFilePath,
|
||||
officePreviewType, pdfName, fileAttribute);
|
||||
}
|
||||
List<String> imageUrls;
|
||||
try {
|
||||
imageUrls = pdftojpgservice.pdf2jpg(originFilePath,outFilePath, pdfName, fileAttribute);
|
||||
} else {
|
||||
// 处理普通PDF预览(非图片转换)
|
||||
return handleRegularPdfPreview(url, model, fileAttribute, pdfName, forceUpdatedCache, outFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查PDF文件是否需要密码(不进行实际转换)
|
||||
*/
|
||||
private boolean checkIfPdfNeedsPassword(String originFilePath, String cacheName, String pdfName) {
|
||||
try {
|
||||
// 尝试用空密码加载PDF,检查是否需要密码
|
||||
File pdfFile = new File(originFilePath);
|
||||
if (!pdfFile.exists()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 使用try-with-resources确保资源释放
|
||||
try (org.apache.pdfbox.pdmodel.PDDocument tempDoc = org.apache.pdfbox.Loader.loadPDF(pdfFile, "")) {
|
||||
// 如果能加载成功,说明不需要密码
|
||||
int pageCount = tempDoc.getNumberOfPages();
|
||||
logger.info("PDF文件不需要密码,总页数: {},文件: {}", pageCount, originFilePath);
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
Throwable[] throwableArray = ExceptionUtils.getThrowables(e);
|
||||
for (Throwable throwable : throwableArray) {
|
||||
if (throwable instanceof IOException || throwable instanceof EncryptedDocumentException) {
|
||||
if (e.getMessage().toLowerCase().contains(PDF_PASSWORD_MSG)) {
|
||||
model.addAttribute("needFilePassword", true);
|
||||
return EXEL_FILE_PREVIEW_PAGE;
|
||||
FileConvertStatusManager.convertSuccess(cacheName);
|
||||
logger.info("PDF文件需要密码: {}", originFilePath);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, "pdf转图片异常,请联系管理员");
|
||||
logger.warn("PDF文件检查异常: {}", e.getMessage());
|
||||
return false;
|
||||
}
|
||||
if (imageUrls == null || imageUrls.size() < 1) {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, "pdf转图片异常,请联系管理员");
|
||||
} catch (Exception e) {
|
||||
logger.error("检查PDF密码状态失败: {}", originFilePath, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动异步PDF转换
|
||||
*/
|
||||
private void startAsyncPdfConversion(String originFilePath, String outFilePath,
|
||||
String cacheName, String pdfName,
|
||||
FileAttribute fileAttribute) {
|
||||
// 启动异步转换
|
||||
CompletableFuture<List<String>> conversionFuture = CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
// 更新状态
|
||||
FileConvertStatusManager.startConvert(cacheName);
|
||||
FileConvertStatusManager.updateProgress(cacheName, "正在启动PDF转换", 10);
|
||||
|
||||
List<String> imageUrls = pdftojpgservice.pdf2jpg(originFilePath, outFilePath,
|
||||
pdfName, fileAttribute);
|
||||
|
||||
if (imageUrls != null && !imageUrls.isEmpty()) {
|
||||
boolean usePasswordCache = fileAttribute.getUsePasswordCache();
|
||||
String filePassword = fileAttribute.getFilePassword();
|
||||
if (ConfigConstants.isCacheEnabled() && (ObjectUtils.isEmpty(filePassword) || usePasswordCache)) {
|
||||
fileHandlerService.addConvertedFile(pdfName, fileHandlerService.getRelativePath(outFilePath));
|
||||
}
|
||||
FileConvertStatusManager.updateProgress(cacheName, "转换完成", 100);
|
||||
// 短暂延迟后清理状态
|
||||
FileConvertStatusManager.convertSuccess(cacheName);
|
||||
return imageUrls;
|
||||
} else {
|
||||
FileConvertStatusManager.markError(cacheName, "PDF转换失败,未生成图片");
|
||||
return null;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Throwable[] throwableArray = ExceptionUtils.getThrowables(e);
|
||||
for (Throwable throwable : throwableArray) {
|
||||
if (throwable instanceof IOException || throwable instanceof EncryptedDocumentException) {
|
||||
if (e.getMessage().toLowerCase().contains(PDF_PASSWORD_MSG)) {
|
||||
// 标记为需要密码的状态
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.error("PDF转换执行失败: {}", cacheName, e);
|
||||
|
||||
// 检查是否已经标记为超时
|
||||
FileConvertStatusManager.ConvertStatus status = FileConvertStatusManager.getConvertStatus(cacheName);
|
||||
if (status == null || status.getStatus() != FileConvertStatusManager.Status.TIMEOUT) {
|
||||
FileConvertStatusManager.markError(cacheName, "转换失败: " + e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
// 添加转换完成后的回调
|
||||
conversionFuture.whenCompleteAsync((imageUrls, throwable) -> {
|
||||
if (imageUrls != null && !imageUrls.isEmpty()) {
|
||||
try {
|
||||
// 是否保留PDF源文件(只在转换成功后才删除)
|
||||
if (!fileAttribute.isCompressFile() && ConfigConstants.getDeleteSourceFile()) {
|
||||
KkFileUtils.deleteFileByPath(originFilePath);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("PDF转换后续处理失败: {}", originFilePath, e);
|
||||
}
|
||||
} else {
|
||||
// 转换失败,保留源文件供排查问题
|
||||
logger.error("PDF转换失败,保留源文件: {}", originFilePath);
|
||||
if (throwable != null) {
|
||||
logger.error("转换失败原因: ", throwable);
|
||||
}
|
||||
}
|
||||
}, callbackExecutor);
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染预览页面
|
||||
*/
|
||||
private String renderPreview(Model model, String cacheName,
|
||||
String outFilePath, String officePreviewType,
|
||||
String pdfName, FileAttribute fileAttribute) {
|
||||
try {
|
||||
List<String> imageUrls;
|
||||
if(pdftojpgservice.hasEncryptedPdfCacheSimple(outFilePath)){
|
||||
imageUrls = pdftojpgservice.getEncryptedPdfCache(outFilePath);
|
||||
}else {
|
||||
imageUrls = fileHandlerService.loadPdf2jpgCache(outFilePath);
|
||||
}
|
||||
if (imageUrls == null || imageUrls.isEmpty()) {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, "PDF转换缓存异常,请联系管理员");
|
||||
}
|
||||
|
||||
model.addAttribute("imgUrls", imageUrls);
|
||||
model.addAttribute("currentUrl", imageUrls.get(0));
|
||||
|
||||
if (OfficeFilePreviewImpl.OFFICE_PREVIEW_TYPE_IMAGE.equals(officePreviewType)) {
|
||||
return OFFICE_PICTURE_FILE_PREVIEW_PAGE;
|
||||
} else {
|
||||
return PICTURE_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
} else {
|
||||
// 不是http开头,浏览器不能直接访问,需下载到本地
|
||||
if (url != null && !url.toLowerCase().startsWith("http")) {
|
||||
if (!fileHandlerService.listConvertedFiles().containsKey(pdfName) || !ConfigConstants.isCacheEnabled()) {
|
||||
ReturnResponse<String> response = DownloadUtils.downLoad(fileAttribute, pdfName);
|
||||
if (response.isFailure()) {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, response.getMsg());
|
||||
}
|
||||
model.addAttribute("pdfUrl", fileHandlerService.getRelativePath(response.getContent()));
|
||||
if (ConfigConstants.isCacheEnabled()) {
|
||||
// 加入缓存
|
||||
fileHandlerService.addConvertedFile(pdfName, fileHandlerService.getRelativePath(outFilePath));
|
||||
}
|
||||
} else {
|
||||
model.addAttribute("pdfUrl", WebUtils.encodeFileName(pdfName));
|
||||
} catch (Exception e) {
|
||||
logger.error("渲染PDF预览页面失败: {}", cacheName, e);
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, "渲染预览页面异常,请联系管理员");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理普通PDF预览(非图片转换)
|
||||
*/
|
||||
private String handleRegularPdfPreview(String url, Model model, FileAttribute fileAttribute,
|
||||
String pdfName, boolean forceUpdatedCache,
|
||||
String outFilePath) {
|
||||
// 不是http开头,浏览器不能直接访问,需下载到本地
|
||||
if (url != null && !url.toLowerCase().startsWith("http")) {
|
||||
if (forceUpdatedCache || !fileHandlerService.listConvertedFiles().containsKey(pdfName) || !ConfigConstants.isCacheEnabled()) {
|
||||
ReturnResponse<String> response = DownloadUtils.downLoad(fileAttribute, pdfName);
|
||||
if (response.isFailure()) {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, response.getMsg());
|
||||
}
|
||||
model.addAttribute("pdfUrl", fileHandlerService.getRelativePath(response.getContent()));
|
||||
if (ConfigConstants.isCacheEnabled()) {
|
||||
// 加入缓存
|
||||
fileHandlerService.addConvertedFile(pdfName, fileHandlerService.getRelativePath(outFilePath));
|
||||
}
|
||||
} else {
|
||||
model.addAttribute("pdfUrl", url);
|
||||
model.addAttribute("pdfUrl", WebUtils.encodeFileName(pdfName));
|
||||
}
|
||||
} else {
|
||||
model.addAttribute("pdfUrl", url);
|
||||
}
|
||||
return PDF_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -77,7 +77,7 @@ public class SimTextFilePreviewImpl implements FilePreview {
|
||||
return null;
|
||||
}
|
||||
if (!file.exists() || file.length() == 0) {
|
||||
return "";
|
||||
return "KK提醒您:文件不存在或者已经被删除了!";
|
||||
} else {
|
||||
String charset = EncodingDetects.getJavaEncode(filePath);
|
||||
if ("ASCII".equals(charset)) {
|
||||
|
||||
@@ -7,12 +7,18 @@ import cn.keking.service.FileHandlerService;
|
||||
import cn.keking.service.FilePreview;
|
||||
import cn.keking.service.TifToPdfService;
|
||||
import cn.keking.utils.DownloadUtils;
|
||||
import cn.keking.utils.FileConvertStatusManager;
|
||||
import cn.keking.utils.KkFileUtils;
|
||||
import cn.keking.utils.WebUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.ui.Model;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* tiff 图片文件处理
|
||||
@@ -22,21 +28,37 @@ import java.util.List;
|
||||
@Service
|
||||
public class TiffFilePreviewImpl implements FilePreview {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(TiffFilePreviewImpl.class);
|
||||
|
||||
// 用于处理回调的线程池
|
||||
private static final ExecutorService callbackExecutor = Executors.newFixedThreadPool(3);
|
||||
|
||||
private final FileHandlerService fileHandlerService;
|
||||
private final OtherFilePreviewImpl otherFilePreview;
|
||||
private final TifToPdfService tiftoservice;
|
||||
public TiffFilePreviewImpl(FileHandlerService fileHandlerService,OtherFilePreviewImpl otherFilePreview,TifToPdfService tiftoservice) {
|
||||
private final OfficeFilePreviewImpl officefilepreviewimpl;
|
||||
|
||||
public TiffFilePreviewImpl(FileHandlerService fileHandlerService, OtherFilePreviewImpl otherFilePreview, TifToPdfService tiftoservice, OfficeFilePreviewImpl officefilepreviewimpl) {
|
||||
this.fileHandlerService = fileHandlerService;
|
||||
this.otherFilePreview = otherFilePreview;
|
||||
this.tiftoservice = tiftoservice;
|
||||
this.officefilepreviewimpl = officefilepreviewimpl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String filePreviewHandle(String url, Model model, FileAttribute fileAttribute) {
|
||||
String fileName = fileAttribute.getName();
|
||||
String tifPreviewType = ConfigConstants.getTifPreviewType();
|
||||
String cacheName = fileAttribute.getCacheName();
|
||||
String cacheName = fileAttribute.getCacheName();
|
||||
String outFilePath = fileAttribute.getOutFilePath();
|
||||
boolean forceUpdatedCache=fileAttribute.forceUpdatedCache();
|
||||
boolean forceUpdatedCache = fileAttribute.forceUpdatedCache();
|
||||
|
||||
// 查询转换状态
|
||||
String statusResult = officefilepreviewimpl.checkAndHandleConvertStatus(model, fileName, cacheName, fileAttribute);
|
||||
if (statusResult != null) {
|
||||
return statusResult;
|
||||
}
|
||||
|
||||
if ("jpg".equalsIgnoreCase(tifPreviewType) || "pdf".equalsIgnoreCase(tifPreviewType)) {
|
||||
if (forceUpdatedCache || !fileHandlerService.listConvertedFiles().containsKey(cacheName) || !ConfigConstants.isCacheEnabled()) {
|
||||
ReturnResponse<String> response = DownloadUtils.downLoad(fileAttribute, fileName);
|
||||
@@ -44,66 +66,111 @@ public class TiffFilePreviewImpl implements FilePreview {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, response.getMsg());
|
||||
}
|
||||
String filePath = response.getContent();
|
||||
|
||||
try {
|
||||
// 启动异步转换
|
||||
startAsyncTiffConversion(filePath, outFilePath, cacheName, fileName, fileAttribute, tifPreviewType, forceUpdatedCache);
|
||||
// 返回等待页面
|
||||
model.addAttribute("fileName", fileName);
|
||||
model.addAttribute("message", "文件正在转换中,请稍候...");
|
||||
return WAITING_FILE_PREVIEW_PAGE;
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to start TIF conversion: {}", filePath, e);
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, "TIF转换异常,请联系系统管理员!");
|
||||
}
|
||||
} else {
|
||||
// 如果已有缓存,直接渲染预览
|
||||
if ("pdf".equalsIgnoreCase(tifPreviewType)) {
|
||||
try {
|
||||
tiftoservice.convertTif2Pdf(filePath, outFilePath,forceUpdatedCache);
|
||||
} catch (Exception e) {
|
||||
if (e.getMessage().contains("Bad endianness tag (not 0x4949 or 0x4d4d)") ) {
|
||||
model.addAttribute("imgUrls", url);
|
||||
model.addAttribute("currentUrl", url);
|
||||
return PICTURE_FILE_PREVIEW_PAGE;
|
||||
}else {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, "TIF转pdf异常,请联系系统管理员!" );
|
||||
}
|
||||
}
|
||||
//是否保留TIFF源文件
|
||||
if (!fileAttribute.isCompressFile() && ConfigConstants.getDeleteSourceFile()) {
|
||||
KkFileUtils.deleteFileByPath(filePath);
|
||||
}
|
||||
if (ConfigConstants.isCacheEnabled()) {
|
||||
// 加入缓存
|
||||
fileHandlerService.addConvertedFile(cacheName, fileHandlerService.getRelativePath(outFilePath));
|
||||
}
|
||||
model.addAttribute("pdfUrl", WebUtils.encodeFileName(cacheName));
|
||||
return PDF_FILE_PREVIEW_PAGE;
|
||||
}else {
|
||||
// 将tif转换为jpg,返回转换后的文件路径、文件名的list
|
||||
List<String> listPic2Jpg;
|
||||
try {
|
||||
listPic2Jpg = tiftoservice.convertTif2Jpg(filePath, outFilePath,forceUpdatedCache);
|
||||
} catch (Exception e) {
|
||||
if (e.getMessage().contains("Bad endianness tag (not 0x4949 or 0x4d4d)") ) {
|
||||
model.addAttribute("imgUrls", url);
|
||||
model.addAttribute("currentUrl", url);
|
||||
return PICTURE_FILE_PREVIEW_PAGE;
|
||||
}else {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, "TIF转JPG异常,请联系系统管理员!" );
|
||||
}
|
||||
} else if ("jpg".equalsIgnoreCase(tifPreviewType)) {
|
||||
List<String> imgCache = fileHandlerService.getImgCache(cacheName);
|
||||
if (imgCache == null || imgCache.isEmpty()) {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, "TIF转换缓存异常,请联系系统管理员!");
|
||||
}
|
||||
//是否保留源文件,转换失败保留源文件,转换成功删除源文件
|
||||
if(!fileAttribute.isCompressFile() && ConfigConstants.getDeleteSourceFile()) {
|
||||
KkFileUtils.deleteFileByPath(filePath);
|
||||
}
|
||||
if (ConfigConstants.isCacheEnabled()) {
|
||||
// 加入缓存
|
||||
fileHandlerService.putImgCache(cacheName, listPic2Jpg);
|
||||
fileHandlerService.addConvertedFile(cacheName, fileHandlerService.getRelativePath(outFilePath));
|
||||
}
|
||||
model.addAttribute("imgUrls", listPic2Jpg);
|
||||
model.addAttribute("currentUrl", listPic2Jpg.get(0));
|
||||
model.addAttribute("imgUrls", imgCache);
|
||||
model.addAttribute("currentUrl", imgCache.getFirst());
|
||||
return PICTURE_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
}
|
||||
if ("pdf".equalsIgnoreCase(tifPreviewType)) {
|
||||
model.addAttribute("pdfUrl", WebUtils.encodeFileName(cacheName));
|
||||
return PDF_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
else if ("jpg".equalsIgnoreCase(tifPreviewType)) {
|
||||
model.addAttribute("imgUrls", fileHandlerService.getImgCache(cacheName));
|
||||
model.addAttribute("currentUrl", fileHandlerService.getImgCache(cacheName).get(0));
|
||||
return PICTURE_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
}
|
||||
|
||||
// 处理普通TIF预览(不进行转换)
|
||||
return handleRegularTiffPreview(url, model, fileAttribute, fileName, forceUpdatedCache, outFilePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动异步TIF转换
|
||||
*/
|
||||
private void startAsyncTiffConversion(String filePath, String outFilePath, String cacheName,
|
||||
String fileName, FileAttribute fileAttribute,
|
||||
String tifPreviewType, boolean forceUpdatedCache) {
|
||||
// 启动异步转换
|
||||
CompletableFuture<Void> conversionFuture = CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
// 更新状态
|
||||
FileConvertStatusManager.startConvert(cacheName);
|
||||
FileConvertStatusManager.updateProgress(cacheName, "正在启动TIF转换", 10);
|
||||
|
||||
if ("pdf".equalsIgnoreCase(tifPreviewType)) {
|
||||
tiftoservice.convertTif2Pdf(filePath, outFilePath,fileName,cacheName, forceUpdatedCache);
|
||||
|
||||
// 转换成功,更新缓存
|
||||
if (ConfigConstants.isCacheEnabled()) {
|
||||
fileHandlerService.addConvertedFile(cacheName, fileHandlerService.getRelativePath(outFilePath));
|
||||
}
|
||||
} else {
|
||||
List<String> listPic2Jpg = tiftoservice.convertTif2Jpg(filePath, outFilePath,fileName,cacheName, forceUpdatedCache);
|
||||
// 转换成功,更新缓存
|
||||
if (ConfigConstants.isCacheEnabled()) {
|
||||
fileHandlerService.putImgCache(cacheName, listPic2Jpg);
|
||||
fileHandlerService.addConvertedFile(cacheName, fileHandlerService.getRelativePath(outFilePath));
|
||||
}
|
||||
}
|
||||
FileConvertStatusManager.convertSuccess(cacheName);
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
// 检查是否为Bad endianness tag异常
|
||||
if (e.getMessage() != null && e.getMessage().contains("Bad endianness tag (not 0x4949 or 0x4d4d)")) {
|
||||
// 特殊处理:对于这种异常,我们不标记为转换失败,而是记录日志
|
||||
logger.warn("TIF文件格式异常(Bad endianness tag),将尝试直接预览: {}", filePath);
|
||||
FileConvertStatusManager.convertSuccess(cacheName);
|
||||
return null;
|
||||
} else {
|
||||
logger.error("TIF转换执行失败: {}", cacheName, e);
|
||||
|
||||
// 检查是否已经标记为超时
|
||||
FileConvertStatusManager.ConvertStatus status = FileConvertStatusManager.getConvertStatus(cacheName);
|
||||
if (status == null || status.getStatus() != FileConvertStatusManager.Status.TIMEOUT) {
|
||||
FileConvertStatusManager.markError(cacheName, "转换失败: " + e.getMessage());
|
||||
}
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 添加转换完成后的回调
|
||||
conversionFuture.thenRunAsync(() -> {
|
||||
try {
|
||||
// 是否保留源文件(只在转换成功后才删除)
|
||||
if (!fileAttribute.isCompressFile() && ConfigConstants.getDeleteSourceFile()) {
|
||||
KkFileUtils.deleteFileByPath(filePath);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("TIF转换后续处理失败: {}", filePath, e);
|
||||
}
|
||||
}, callbackExecutor).exceptionally(throwable -> {
|
||||
// 转换失败,记录日志但不删除源文件
|
||||
logger.error("TIF转换失败,保留源文件供排查: {}", filePath, throwable);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理普通TIF预览(不进行转换)
|
||||
*/
|
||||
private String handleRegularTiffPreview(String url, Model model, FileAttribute fileAttribute,
|
||||
String fileName, boolean forceUpdatedCache, String outFilePath) {
|
||||
// 不是http开头,浏览器不能直接访问,需下载到本地
|
||||
if (url != null && !url.toLowerCase().startsWith("http")) {
|
||||
if (forceUpdatedCache || !fileHandlerService.listConvertedFiles().containsKey(fileName) || !ConfigConstants.isCacheEnabled()) {
|
||||
@@ -117,12 +184,11 @@ public class TiffFilePreviewImpl implements FilePreview {
|
||||
fileHandlerService.addConvertedFile(fileName, fileHandlerService.getRelativePath(outFilePath));
|
||||
}
|
||||
} else {
|
||||
model.addAttribute("currentUrl", WebUtils.encodeFileName(fileName));
|
||||
model.addAttribute("currentUrl", WebUtils.encodeFileName(fileName));
|
||||
}
|
||||
return TIFF_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
model.addAttribute("currentUrl", url);
|
||||
return TIFF_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -55,7 +55,7 @@ public class DownloadUtils {
|
||||
|
||||
String urlStr = null;
|
||||
try {
|
||||
urlStr = fileAttribute.getUrl().replaceAll("\\+", "%20").replaceAll(" ", "%20");
|
||||
urlStr = fileAttribute.getUrl();
|
||||
} catch (Exception e) {
|
||||
logger.error("处理URL异常:", e);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import org.springframework.web.util.HtmlUtils;
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class KkFileUtils {
|
||||
@@ -236,5 +237,15 @@ public class KkFileUtils {
|
||||
File file = new File(filePath);
|
||||
return file.exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否是数字
|
||||
*/
|
||||
public static boolean isNumeric(String str){
|
||||
Pattern pattern = Pattern.compile("[0-9]*");
|
||||
if (ObjectUtils.isEmpty(str)){
|
||||
return false;
|
||||
}
|
||||
Matcher isNum = pattern.matcher(str);
|
||||
return isNum.matches();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import java.util.BitSet;
|
||||
|
||||
public class UrlEncoderUtils {
|
||||
|
||||
private static BitSet dontNeedEncoding;
|
||||
private static final BitSet dontNeedEncoding;
|
||||
|
||||
static {
|
||||
dontNeedEncoding = new BitSet(256);
|
||||
@@ -19,7 +19,7 @@ public class UrlEncoderUtils {
|
||||
dontNeedEncoding.set(i);
|
||||
}
|
||||
dontNeedEncoding.set('+');
|
||||
/**
|
||||
/*
|
||||
* 这里会有误差,比如输入一个字符串 123+456,它到底是原文就是123+456还是123 456做了urlEncode后的内容呢?<br>
|
||||
* 其实问题是一样的,比如遇到123%2B456,它到底是原文即使如此,还是123+456 urlEncode后的呢? <br>
|
||||
* 在这里,我认为只要符合urlEncode规范的,就当作已经urlEncode过了<br>
|
||||
@@ -36,13 +36,10 @@ public class UrlEncoderUtils {
|
||||
* 判断str是否urlEncoder.encode过<br>
|
||||
* 经常遇到这样的情况,拿到一个URL,但是搞不清楚到底要不要encode.<Br>
|
||||
* 不做encode吧,担心出错,做encode吧,又怕重复了<Br>
|
||||
*
|
||||
* @param str
|
||||
* @return
|
||||
*/
|
||||
public static boolean hasUrlEncoded(String str) {
|
||||
|
||||
/**
|
||||
/*
|
||||
* 支持JAVA的URLEncoder.encode出来的string做判断。 即: 将' '转成'+' <br>
|
||||
* 0-9a-zA-Z保留 <br>
|
||||
* '-','_','.','*'保留 <br>
|
||||
@@ -51,7 +48,7 @@ public class UrlEncoderUtils {
|
||||
boolean needEncode = false;
|
||||
for (int i = 0; i < str.length(); i++) {
|
||||
char c = str.charAt(i);
|
||||
if (dontNeedEncoding.get((int) c)) {
|
||||
if (dontNeedEncoding.get(c)) {
|
||||
continue;
|
||||
}
|
||||
if (c == '%' && (i + 2) < str.length()) {
|
||||
@@ -72,9 +69,6 @@ public class UrlEncoderUtils {
|
||||
|
||||
/**
|
||||
* 判断c是否是16进制的字符
|
||||
*
|
||||
* @param c
|
||||
* @return
|
||||
*/
|
||||
private static boolean isDigit16Char(char c) {
|
||||
return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F');
|
||||
|
||||
@@ -229,11 +229,7 @@ public class WebUtils {
|
||||
if (fileNameEndIndex < fileNameStartIndex) {
|
||||
return url;
|
||||
}
|
||||
try {
|
||||
encodedFileName = URLEncoder.encode(noQueryUrl.substring(fileNameStartIndex, fileNameEndIndex), "UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
return null;
|
||||
}
|
||||
encodedFileName = URLEncoder.encode(noQueryUrl.substring(fileNameStartIndex, fileNameEndIndex), StandardCharsets.UTF_8);
|
||||
return url.substring(0, fileNameStartIndex) + encodedFileName + url.substring(fileNameEndIndex);
|
||||
}
|
||||
|
||||
@@ -471,6 +467,8 @@ public class WebUtils {
|
||||
*/
|
||||
public static void applyBasicAuthHeaders(HttpHeaders headers, FileAttribute fileAttribute) {
|
||||
String url = fileAttribute.getUrl();
|
||||
System.out.println(" T555.");
|
||||
System.out.println(url);
|
||||
// 从配置文件读取User-Agent,如果没有配置则使用默认值
|
||||
String customUserAgent=ConfigConstants.getUserAgent();
|
||||
String userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36";
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package cn.keking.web.controller;
|
||||
|
||||
import cn.keking.config.ConfigConstants;
|
||||
import cn.keking.model.FileType;
|
||||
import cn.keking.model.ReturnResponse;
|
||||
import cn.keking.utils.CaptchaUtil;
|
||||
import cn.keking.utils.DateUtils;
|
||||
@@ -27,13 +28,10 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.*;
|
||||
|
||||
import static cn.keking.utils.CaptchaUtil.CAPTCHA_CODE;
|
||||
import static cn.keking.utils.CaptchaUtil.CAPTCHA_GENERATE_TIME;
|
||||
@@ -54,23 +52,72 @@ public class FileController {
|
||||
public static final String BASE64_DECODE_ERROR_MSG = "Base64解码失败,请检查你的 %s 是否采用 Base64 + urlEncode 双重编码了!";
|
||||
|
||||
@PostMapping("/fileUpload")
|
||||
public ReturnResponse<Object> fileUpload(@RequestParam("file") MultipartFile file) {
|
||||
ReturnResponse<Object> checkResult = this.fileUploadCheck(file);
|
||||
public ReturnResponse<Object> fileUpload(@RequestParam("file") MultipartFile file,
|
||||
@RequestParam(value = "path", defaultValue = "") String path) {
|
||||
ReturnResponse<Object> checkResult = this.fileUploadCheck(file, path);
|
||||
if (checkResult.isFailure()) {
|
||||
return checkResult;
|
||||
}
|
||||
File outFile = new File(fileDir + demoPath);
|
||||
if (!outFile.exists() && !outFile.mkdirs()) {
|
||||
logger.error("创建文件夹【{}】失败,请检查目录权限!", fileDir + demoPath);
|
||||
|
||||
String uploadPath = fileDir + demoPath;
|
||||
if (!ObjectUtils.isEmpty(path)) {
|
||||
uploadPath += path + File.separator;
|
||||
}
|
||||
|
||||
File outFile = new File(uploadPath);
|
||||
if (!outFile.exists() && !outFile.mkdirs()) {
|
||||
logger.error("创建文件夹【{}】失败,请检查目录权限!", uploadPath);
|
||||
return ReturnResponse.failure("创建文件夹失败,请检查目录权限!");
|
||||
}
|
||||
|
||||
String fileName = checkResult.getContent().toString();
|
||||
logger.info("上传文件:{}{}{}", fileDir, demoPath, fileName);
|
||||
try (InputStream in = file.getInputStream(); OutputStream out = Files.newOutputStream(Paths.get(fileDir + demoPath + fileName))) {
|
||||
logger.info("上传文件:{}{}", uploadPath, fileName);
|
||||
|
||||
try (InputStream in = file.getInputStream();
|
||||
OutputStream out = Files.newOutputStream(Paths.get(uploadPath + fileName))) {
|
||||
StreamUtils.copy(in, out);
|
||||
return ReturnResponse.success(null);
|
||||
} catch (IOException e) {
|
||||
logger.error("文件上传失败", e);
|
||||
return ReturnResponse.failure();
|
||||
return ReturnResponse.failure("文件上传失败");
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/createFolder")
|
||||
public ReturnResponse<Object> createFolder(@RequestParam(value = "path", defaultValue = "") String path,
|
||||
@RequestParam("folderName") String folderName) {
|
||||
if (ConfigConstants.getFileUploadDisable()) {
|
||||
return ReturnResponse.failure("文件上传接口已禁用");
|
||||
}
|
||||
try {
|
||||
// 验证文件夹名称
|
||||
if (ObjectUtils.isEmpty(folderName)) {
|
||||
return ReturnResponse.failure("文件夹名称不能为空");
|
||||
}
|
||||
|
||||
if (KkFileUtils.isIllegalFileName(folderName)) {
|
||||
return ReturnResponse.failure("非法文件夹名称");
|
||||
}
|
||||
String basePath = fileDir + demoPath;
|
||||
if (!ObjectUtils.isEmpty(path)) {
|
||||
basePath += path + File.separator;
|
||||
}
|
||||
|
||||
File newFolder = new File(basePath + folderName);
|
||||
if (newFolder.exists()) {
|
||||
return ReturnResponse.failure("文件夹已存在");
|
||||
}
|
||||
|
||||
if (newFolder.mkdirs()) {
|
||||
logger.info("创建文件夹:{}", newFolder.getAbsolutePath());
|
||||
return ReturnResponse.success();
|
||||
} else {
|
||||
logger.error("创建文件夹失败:{}", newFolder.getAbsolutePath());
|
||||
return ReturnResponse.failure("创建文件夹失败,请检查目录权限");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("创建文件夹异常", e);
|
||||
return ReturnResponse.failure("创建文件夹失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,15 +128,55 @@ public class FileController {
|
||||
return checkResult;
|
||||
}
|
||||
fileName = checkResult.getContent().toString();
|
||||
File file = new File(fileDir + demoPath + fileName);
|
||||
logger.info("删除文件:{}", file.getAbsolutePath());
|
||||
if (file.exists() && !file.delete()) {
|
||||
String msg = String.format("删除文件【%s】失败,请检查目录权限!", file.getPath());
|
||||
logger.error(msg);
|
||||
return ReturnResponse.failure(msg);
|
||||
|
||||
// 构建完整路径
|
||||
String fullPath = fileDir + demoPath + fileName;
|
||||
File file = new File(fullPath);
|
||||
|
||||
logger.info("删除文件/文件夹:{}", file.getAbsolutePath());
|
||||
if (file.exists()) {
|
||||
if (file.isDirectory()) {
|
||||
// 删除文件夹及其内容
|
||||
if (deleteDirectory(file)) {
|
||||
WebUtils.removeSessionAttr(request, CAPTCHA_CODE);
|
||||
return ReturnResponse.success();
|
||||
} else {
|
||||
String msg = String.format("删除文件夹【%s】失败,请检查目录权限!", file.getPath());
|
||||
logger.error(msg);
|
||||
return ReturnResponse.failure(msg);
|
||||
}
|
||||
} else {
|
||||
// 删除文件
|
||||
if (file.delete()) {
|
||||
WebUtils.removeSessionAttr(request, CAPTCHA_CODE);
|
||||
return ReturnResponse.success();
|
||||
} else {
|
||||
String msg = String.format("删除文件【%s】失败,请检查目录权限!", file.getPath());
|
||||
logger.error(msg);
|
||||
return ReturnResponse.failure(msg);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return ReturnResponse.failure("文件或文件夹不存在");
|
||||
}
|
||||
WebUtils.removeSessionAttr(request, CAPTCHA_CODE); //删除缓存验证码
|
||||
return ReturnResponse.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归删除目录
|
||||
*/
|
||||
private boolean deleteDirectory(File dir) {
|
||||
if (dir.isDirectory()) {
|
||||
File[] children = dir.listFiles();
|
||||
if (children != null) {
|
||||
for (File child : children) {
|
||||
boolean success = deleteDirectory(child);
|
||||
if (!success) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return dir.delete();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -124,32 +211,156 @@ public class FileController {
|
||||
outputStream.close();
|
||||
}
|
||||
|
||||
@GetMapping("/listFiles")
|
||||
public List<Map<String, String>> getFiles() {
|
||||
List<Map<String, String>> list = new ArrayList<>();
|
||||
File file = new File(fileDir + demoPath);
|
||||
if (file.exists()) {
|
||||
File[] files = Objects.requireNonNull(file.listFiles());
|
||||
Arrays.sort(files, (f1, f2) -> Long.compare(f2.lastModified(), f1.lastModified()));
|
||||
Arrays.stream(files).forEach(file1 -> {
|
||||
Map<String, String> fileName = new HashMap<>();
|
||||
fileName.put("fileName", demoDir + "/" + file1.getName());
|
||||
list.add(fileName);
|
||||
});
|
||||
@PostMapping("/listFiles")
|
||||
public Map<String, Object> getFiles(@RequestParam(value = "path", defaultValue = "") String path,
|
||||
@RequestParam(value = "searchText", defaultValue = "") String searchText,
|
||||
@RequestParam(defaultValue = "0") int page,
|
||||
@RequestParam(defaultValue = "20") int size,
|
||||
@RequestParam(required = false) String sort,
|
||||
@RequestParam(required = false) String order) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
List<Map<String, Object>> fileList = new ArrayList<>();
|
||||
|
||||
try {
|
||||
// 构建完整路径
|
||||
String basePath = fileDir + demoPath;
|
||||
if (!ObjectUtils.isEmpty(path)) {
|
||||
basePath += path + File.separator;
|
||||
}
|
||||
|
||||
File currentDir = new File(basePath);
|
||||
if (currentDir.exists() && currentDir.isDirectory()) {
|
||||
File[] files = currentDir.listFiles();
|
||||
if (files != null) {
|
||||
// 转换为List
|
||||
List<File> fileObjects = new ArrayList<>(Arrays.asList(files));
|
||||
|
||||
// 如果搜索文本不为空,进行过滤
|
||||
if (!ObjectUtils.isEmpty(searchText)) {
|
||||
String searchLower = searchText.toLowerCase();
|
||||
fileObjects.removeIf(f -> !f.getName().toLowerCase().contains(searchLower));
|
||||
}
|
||||
|
||||
// 排序
|
||||
Comparator<File> comparator = getFileComparator(sort, order);
|
||||
if (comparator != null) {
|
||||
fileObjects.sort(comparator);
|
||||
}
|
||||
|
||||
int total = fileObjects.size();
|
||||
int start = page * size;
|
||||
int end = Math.min(start + size, total);
|
||||
|
||||
if (start < total) {
|
||||
for (int i = start; i < end; i++) {
|
||||
File f = fileObjects.get(i);
|
||||
Map<String, Object> fileInfo = new HashMap<>();
|
||||
|
||||
fileInfo.put("name", f.getName());
|
||||
fileInfo.put("isDirectory", f.isDirectory());
|
||||
fileInfo.put("lastModified", f.lastModified());
|
||||
fileInfo.put("size", f.length());
|
||||
|
||||
// 构建路径信息
|
||||
String relativePath = demoDir + "/" + (ObjectUtils.isEmpty(path) ? "" : path + "/") + f.getName();
|
||||
fileInfo.put("relativePath", relativePath);
|
||||
|
||||
// 如果是目录,保存完整的相对路径用于导航
|
||||
if (f.isDirectory()) {
|
||||
String fullPath = ObjectUtils.isEmpty(path) ? f.getName() : path + "/" + f.getName();
|
||||
fileInfo.put("fullPath", fullPath);
|
||||
}
|
||||
|
||||
// 获取文件属性
|
||||
try {
|
||||
Path filePath = f.toPath();
|
||||
BasicFileAttributes attrs = Files.readAttributes(filePath, BasicFileAttributes.class);
|
||||
fileInfo.put("creationTime", attrs.creationTime().toMillis());
|
||||
} catch (IOException e) {
|
||||
logger.warn("获取文件属性失败: {}", f.getName(), e);
|
||||
}
|
||||
|
||||
fileList.add(fileInfo);
|
||||
}
|
||||
}
|
||||
|
||||
result.put("total", total);
|
||||
result.put("data", fileList);
|
||||
}
|
||||
} else {
|
||||
result.put("total", 0);
|
||||
result.put("data", Collections.emptyList());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("获取文件列表失败", e);
|
||||
result.put("total", 0);
|
||||
result.put("data", Collections.emptyList());
|
||||
}
|
||||
return list;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件比较器
|
||||
*/
|
||||
private Comparator<File> getFileComparator(String sort, String order) {
|
||||
if (ObjectUtils.isEmpty(sort)) {
|
||||
// 默认按文件夹优先,然后按名称排序
|
||||
return (f1, f2) -> {
|
||||
if (f1.isDirectory() && !f2.isDirectory()) {
|
||||
return -1;
|
||||
} else if (!f1.isDirectory() && f2.isDirectory()) {
|
||||
return 1;
|
||||
} else {
|
||||
return f1.getName().compareToIgnoreCase(f2.getName());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
boolean isDesc = "desc".equalsIgnoreCase(order);
|
||||
|
||||
return switch (sort) {
|
||||
case "name" -> (f1, f2) -> {
|
||||
int compare = f1.getName().compareToIgnoreCase(f2.getName());
|
||||
return isDesc ? -compare : compare;
|
||||
};
|
||||
case "lastModified" -> (f1, f2) -> {
|
||||
long compare = Long.compare(f1.lastModified(), f2.lastModified());
|
||||
return isDesc ? (int) -compare : (int) compare;
|
||||
};
|
||||
case "size" -> (f1, f2) -> {
|
||||
if (f1.isDirectory() && f2.isDirectory()) {
|
||||
return 0;
|
||||
} else if (f1.isDirectory()) {
|
||||
return isDesc ? 1 : -1;
|
||||
} else if (f2.isDirectory()) {
|
||||
return isDesc ? -1 : 1;
|
||||
} else {
|
||||
long compare = Long.compare(f1.length(), f2.length());
|
||||
return isDesc ? (int) -compare : (int) compare;
|
||||
}
|
||||
};
|
||||
case "isDirectory" -> (f1, f2) -> {
|
||||
if (f1.isDirectory() && !f2.isDirectory()) {
|
||||
return isDesc ? 1 : -1;
|
||||
} else if (!f1.isDirectory() && f2.isDirectory()) {
|
||||
return isDesc ? -1 : 1;
|
||||
} else {
|
||||
return f1.getName().compareToIgnoreCase(f2.getName());
|
||||
}
|
||||
};
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传文件前校验
|
||||
*
|
||||
* @param file 文件
|
||||
* @return 校验结果
|
||||
*/
|
||||
private ReturnResponse<Object> fileUploadCheck(MultipartFile file) {
|
||||
private ReturnResponse<Object> fileUploadCheck(MultipartFile file, String path) {
|
||||
if (ConfigConstants.getFileUploadDisable()) {
|
||||
return ReturnResponse.failure("文件传接口已禁用");
|
||||
return ReturnResponse.failure("文件上传接口已禁用");
|
||||
}
|
||||
|
||||
String fileName = WebUtils.getFileNameFromMultipartFile(file);
|
||||
if (fileName.lastIndexOf(".") == -1) {
|
||||
return ReturnResponse.failure("不允许上传的类型");
|
||||
@@ -160,47 +371,56 @@ public class FileController {
|
||||
if (KkFileUtils.isIllegalFileName(fileName)) {
|
||||
return ReturnResponse.failure("不允许上传的文件名: " + fileName);
|
||||
}
|
||||
FileType type = FileType.typeFromFileName(fileName);
|
||||
if (Objects.equals(type, FileType.OTHER)) {
|
||||
return ReturnResponse.failure("该文件格式还不支持预览,请联系管理员,添加该格式: " + fileName);
|
||||
}
|
||||
|
||||
// 判断是否存在同名文件
|
||||
if (existsFile(fileName)) {
|
||||
if (existsFile(fileName, path)) {
|
||||
return ReturnResponse.failure("存在同名文件,请先删除原有文件再次上传");
|
||||
}
|
||||
|
||||
return ReturnResponse.success(fileName);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 删除文件前校验
|
||||
*
|
||||
* @param fileName 文件名
|
||||
* @return 校验结果
|
||||
*/
|
||||
private ReturnResponse<Object> deleteFileCheck(HttpServletRequest request, String fileName, String password) {
|
||||
if (ObjectUtils.isEmpty(fileName)) {
|
||||
return ReturnResponse.failure("文件名为空,删除失败!");
|
||||
}
|
||||
try {
|
||||
fileName = WebUtils.decodeUrl(fileName,"base64");
|
||||
fileName = WebUtils.decodeUrl(fileName, "base64");
|
||||
} catch (Exception ex) {
|
||||
String errorMsg = String.format(BASE64_DECODE_ERROR_MSG, fileName);
|
||||
return ReturnResponse.failure(errorMsg + "删除失败!");
|
||||
}
|
||||
assert fileName != null;
|
||||
|
||||
if (ObjectUtils.isEmpty(fileName)) {
|
||||
return ReturnResponse.failure("文件名为空,删除失败!");
|
||||
}
|
||||
if (fileName.contains("/")) {
|
||||
fileName = fileName.substring(fileName.lastIndexOf("/") + 1);
|
||||
}
|
||||
if (KkFileUtils.isIllegalFileName(fileName)) {
|
||||
return ReturnResponse.failure("非法文件名,删除失败!");
|
||||
}
|
||||
|
||||
if (ObjectUtils.isEmpty(password)) {
|
||||
return ReturnResponse.failure("密码 or 验证码为空,删除失败!");
|
||||
}
|
||||
|
||||
String expectedPassword = ConfigConstants.getDeleteCaptcha() ? WebUtils.getSessionAttr(request, CAPTCHA_CODE) : ConfigConstants.getPassword();
|
||||
String expectedPassword = ConfigConstants.getDeleteCaptcha() ?
|
||||
WebUtils.getSessionAttr(request, CAPTCHA_CODE) :
|
||||
ConfigConstants.getPassword();
|
||||
|
||||
if (!password.equalsIgnoreCase(expectedPassword)) {
|
||||
logger.error("删除文件【{}】失败,密码错误!", fileName);
|
||||
return ReturnResponse.failure("删除文件失败,密码错误!");
|
||||
}
|
||||
|
||||
return ReturnResponse.success(fileName);
|
||||
}
|
||||
|
||||
@@ -220,8 +440,12 @@ public class FileController {
|
||||
return RarUtils.getTree(fileUrl);
|
||||
}
|
||||
|
||||
private boolean existsFile(String fileName) {
|
||||
File file = new File(fileDir + demoPath + fileName);
|
||||
private boolean existsFile(String fileName, String path) {
|
||||
String fullPath = fileDir + demoPath;
|
||||
if (!ObjectUtils.isEmpty(path)) {
|
||||
fullPath += path + File.separator;
|
||||
}
|
||||
File file = new File(fullPath + fileName);
|
||||
return file.exists();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,6 +77,9 @@ public class OnlinePreviewController {
|
||||
public String onlinePreview(@RequestParam String url,
|
||||
@RequestParam(required = false) String key,
|
||||
@RequestParam(required = false) String encryption,
|
||||
@RequestParam(defaultValue = "false") String highlightall,
|
||||
@RequestParam(defaultValue = "0") String page,
|
||||
@RequestParam(defaultValue = "false") String kkagent,
|
||||
Model model,
|
||||
HttpServletRequest req) {
|
||||
// 验证访问权限
|
||||
@@ -90,7 +93,12 @@ public class OnlinePreviewController {
|
||||
String errorMsg = String.format(BASE64_DECODE_ERROR_MSG, "url");
|
||||
return otherFilePreview.notSupportedFile(model, errorMsg);
|
||||
}
|
||||
FileAttribute fileAttribute = fileHandlerService.getFileAttribute(fileUrl, req); //这里不在进行URL 处理了
|
||||
FileAttribute fileAttribute = fileHandlerService.getFileAttribute(fileUrl, req);
|
||||
|
||||
highlightall= KkFileUtils.htmlEscape(highlightall);
|
||||
model.addAttribute("highlightall", highlightall);
|
||||
model.addAttribute("page", page);
|
||||
model.addAttribute("kkagent", kkagent);
|
||||
model.addAttribute("file", fileAttribute);
|
||||
FilePreview filePreview = previewFactory.get(fileAttribute);
|
||||
logger.info("预览文件url:{},previewType:{}", fileUrl, fileAttribute.getType());
|
||||
@@ -151,8 +159,8 @@ public class OnlinePreviewController {
|
||||
public void getCorsFile(@RequestParam String urlPath,
|
||||
@RequestParam(required = false) String key,
|
||||
HttpServletResponse response,
|
||||
@RequestParam(required = false) String encryption,
|
||||
FileAttribute fileAttribute) throws Exception {
|
||||
HttpServletRequest req,
|
||||
@RequestParam(required = false) String encryption) throws Exception {
|
||||
|
||||
// 1. 验证接口是否开启
|
||||
if (!ConfigConstants.getGetCorsFile()) {
|
||||
@@ -177,6 +185,7 @@ public class OnlinePreviewController {
|
||||
logger.info("读取跨域文件异常,可能存在非法访问,urlPath:{}", urlPath);
|
||||
return;
|
||||
}
|
||||
FileAttribute fileAttribute = fileHandlerService.getFileAttribute(urlPath, req);
|
||||
InputStream inputStream = null;
|
||||
logger.info("读取跨域pdf文件url:{}", urlPath);
|
||||
if (!isFtpUrl(url)) {
|
||||
@@ -188,7 +197,6 @@ public class OnlinePreviewController {
|
||||
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
restTemplate.setRequestFactory(factory);
|
||||
|
||||
RequestCallback requestCallback = request -> {
|
||||
request.getHeaders().setAccept(Arrays.asList(MediaType.APPLICATION_OCTET_STREAM, MediaType.ALL));
|
||||
WebUtils.applyBasicAuthHeaders(request.getHeaders(), fileAttribute);
|
||||
|
||||
@@ -47,6 +47,14 @@ public class AttributeSetFilter implements Filter {
|
||||
request.setAttribute("homePagination", ConfigConstants.getHomePagination());
|
||||
request.setAttribute("homePageSize", ConfigConstants.getHomePageSize());
|
||||
request.setAttribute("homeSearch", ConfigConstants.getHomeSearch());
|
||||
request.setAttribute("isshowaeskey", ConfigConstants.getisShowaesKey());
|
||||
request.setAttribute("isjavascript", ConfigConstants.getisJavaScript());
|
||||
request.setAttribute("xlsxallowEdit", ConfigConstants.getxlsxAllowEdit());
|
||||
request.setAttribute("xlsxshowtoolbar", ConfigConstants.getxlsxShowtoolbar());
|
||||
request.setAttribute("aeskey", ConfigConstants.getaesKey());
|
||||
request.setAttribute("isshowkey", ConfigConstants.getisShowKey());
|
||||
request.setAttribute("kkkey", ConfigConstants.getKey());
|
||||
request.setAttribute("scriptjs", ConfigConstants.getscriptJs());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
BIN
server/src/main/resources/static/css/loading.gif
Normal file
BIN
server/src/main/resources/static/css/loading.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 67 KiB |
115
server/src/main/resources/static/css/officePicture.css
Normal file
115
server/src/main/resources/static/css/officePicture.css
Normal file
@@ -0,0 +1,115 @@
|
||||
body {
|
||||
background-color: #404040;
|
||||
}
|
||||
.container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.img-area {
|
||||
text-align: center;
|
||||
}
|
||||
.my-photo {
|
||||
max-width: 98%;
|
||||
margin:0 auto;
|
||||
border-radius:3px;
|
||||
box-shadow:rgba(0,0,0,0.15) 0 0 8px;
|
||||
background:#FBFBFB;
|
||||
border:1px solid #ddd;
|
||||
margin:1px auto;
|
||||
padding:5px;
|
||||
}
|
||||
.image-container {
|
||||
/*display: flex; !* 使用flexbox布局 *!*/
|
||||
/*justify-content: space-around; !* 图片间隔对齐 *!*/
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.image-container img {
|
||||
width: 100%; /* 根据需要调整宽度 */
|
||||
height: auto; /* 保持图片的宽高比 */
|
||||
}
|
||||
|
||||
.image-container img:hover {
|
||||
width: 150%; /* 根据需要调整宽度 */
|
||||
height: auto; /* 保持图片的宽高比 */
|
||||
}
|
||||
|
||||
.button-container {
|
||||
position: absolute;
|
||||
top: 5px; /* 根据需要调整距离顶部的位置 */
|
||||
right: 10px; /* 根据需要调整距离右侧的位置 */
|
||||
}
|
||||
|
||||
.button-container button {
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
padding: 5px 10px 5px 22px;
|
||||
cursor: pointer;
|
||||
border: #b0b0b0 1px;
|
||||
}
|
||||
/*顺时针旋转90度*/
|
||||
.imgT90 {
|
||||
transform: rotate(90deg);
|
||||
-ms-transform: rotate(90deg);
|
||||
-webkit-transform: rotate(90deg);
|
||||
-o-transform: rotate(90deg);
|
||||
-moz-transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.imgT-90 {
|
||||
transform: rotate(-90deg);
|
||||
-ms-transform: rotate(-90deg);
|
||||
-webkit-transform: rotate(-90deg);
|
||||
-o-transform: rotate(-90deg);
|
||||
-moz-transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
/*顺时针旋转180度*/
|
||||
.imgT180 {
|
||||
transform: rotate(180deg);
|
||||
-ms-transform: rotate(180deg);
|
||||
-webkit-transform: rotate(180deg);
|
||||
-o-transform: rotate(180deg);
|
||||
-moz-transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.imgT-180 {
|
||||
transform: rotate(-180deg);
|
||||
-ms-transform: rotate(-180deg);
|
||||
-webkit-transform: rotate(-180deg);
|
||||
-o-transform: rotate(-180deg);
|
||||
-moz-transform: rotate(-180deg);
|
||||
}
|
||||
|
||||
/*顺时针旋转270度*/
|
||||
.imgT270 {
|
||||
transform: rotate(270deg);
|
||||
-ms-transform: rotate(270deg);
|
||||
-webkit-transform: rotate(270deg);
|
||||
-o-transform: rotate(270deg);
|
||||
-moz-transform: rotate(270deg);
|
||||
}
|
||||
|
||||
.imgT-270 {
|
||||
transform: rotate(-270deg);
|
||||
-ms-transform: rotate(-270deg);
|
||||
-webkit-transform: rotate(-270deg);
|
||||
-o-transform: rotate(-270deg);
|
||||
-moz-transform: rotate(-270deg);
|
||||
}
|
||||
|
||||
.imgS150 {
|
||||
transform: scale(1.5);
|
||||
-ms-transform: scale(1.5);
|
||||
-webkit-transform: scale(1.5);
|
||||
-o-transform: scale(1.5);
|
||||
-moz-transform: scale(1.5);
|
||||
}
|
||||
|
||||
.imgS200 {
|
||||
transform: scale(2);
|
||||
-ms-transform: scale(2);
|
||||
-webkit-transform: scale(2);
|
||||
-o-transform: scale(2);
|
||||
-moz-transform: scale(2);
|
||||
}
|
||||
157
server/src/main/resources/static/highlight/highlight.css
Normal file
157
server/src/main/resources/static/highlight/highlight.css
Normal file
@@ -0,0 +1,157 @@
|
||||
/* 分页容器样式 */
|
||||
.black {
|
||||
|
||||
margin: 10px 0;
|
||||
text-align: center;
|
||||
background: #f8f9fa;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
/* 分页按钮基础样式 */
|
||||
.black button {
|
||||
margin: 0 5px;
|
||||
padding: 6px 16px;
|
||||
border: 1px solid #007bff;
|
||||
border-radius: 4px;
|
||||
background: #007bff;
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
/* 分页按钮悬停效果 */
|
||||
.black button:hover {
|
||||
background: #0056b3;
|
||||
border-color: #0056b3;
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* 分页按钮激活状态 */
|
||||
.black button:active {
|
||||
transform: translateY(0);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* 分页信息文字 */
|
||||
.black span {
|
||||
margin: 0 10px;
|
||||
color: #6c757d;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 输入框样式 */
|
||||
.black input[type="number"] {
|
||||
width: 70px;
|
||||
padding: 5px 8px;
|
||||
margin: 0 5px;
|
||||
border: 1px solid #ced4da;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.black input[type="number"]:focus {
|
||||
outline: none;
|
||||
border-color: #80bdff;
|
||||
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
|
||||
}
|
||||
|
||||
/* 内容区域样式 */
|
||||
.divContent {
|
||||
padding: 20px;
|
||||
margin: 15px 0;
|
||||
background: white;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #e9ecef;
|
||||
line-height: 1.6;
|
||||
color: #212529;
|
||||
font-family: "Segoe UI", "Microsoft YaHei", sans-serif;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
/* 代码块样式 */
|
||||
.divContent pre {
|
||||
margin: 15px 0;
|
||||
padding: 15px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 4px;
|
||||
border-left: 4px solid #007bff;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.divContent code {
|
||||
font-family: "Consolas", "Monaco", "Courier New", monospace;
|
||||
font-size: 14px;
|
||||
color: #e83e8c;
|
||||
}
|
||||
|
||||
/* 行号样式 */
|
||||
.divContent .line-number {
|
||||
display: inline-block;
|
||||
width: 30px;
|
||||
margin-right: 10px;
|
||||
color: #6c757d;
|
||||
text-align: right;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* 高亮搜索关键词 */
|
||||
.highlight {
|
||||
background: #fff3cd;
|
||||
padding: 1px 3px;
|
||||
border-radius: 2px;
|
||||
color: #856404;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* 面板标题样式 */
|
||||
.panel-heading {
|
||||
background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%);
|
||||
color: white;
|
||||
padding: 15px 20px;
|
||||
border-radius: 6px 6px 0 0;
|
||||
}
|
||||
|
||||
.panel-title {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.panel-title a {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.panel-title a:hover {
|
||||
text-decoration: underline;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
/* 响应式调整 */
|
||||
@media (max-width: 768px) {
|
||||
.black {
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.black button {
|
||||
padding: 5px 12px;
|
||||
margin: 2px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.black input[type="number"] {
|
||||
width: 60px;
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
.divContent {
|
||||
padding: 15px;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
234
server/src/main/resources/static/js/aes.js
Normal file
234
server/src/main/resources/static/js/aes.js
Normal file
@@ -0,0 +1,234 @@
|
||||
;(function (root, factory, undef) {
|
||||
if (typeof exports === "object") {
|
||||
// CommonJS
|
||||
module.exports = exports = factory(require("./core"), require("./enc-base64"), require("./md5"), require("./evpkdf"), require("./cipher-core"));
|
||||
}
|
||||
else if (typeof define === "function" && define.amd) {
|
||||
// AMD
|
||||
define(["./core", "./enc-base64", "./md5", "./evpkdf", "./cipher-core"], factory);
|
||||
}
|
||||
else {
|
||||
// Global (browser)
|
||||
factory(root.CryptoJS);
|
||||
}
|
||||
}(this, function (CryptoJS) {
|
||||
|
||||
(function () {
|
||||
// Shortcuts
|
||||
var C = CryptoJS;
|
||||
var C_lib = C.lib;
|
||||
var BlockCipher = C_lib.BlockCipher;
|
||||
var C_algo = C.algo;
|
||||
|
||||
// Lookup tables
|
||||
var SBOX = [];
|
||||
var INV_SBOX = [];
|
||||
var SUB_MIX_0 = [];
|
||||
var SUB_MIX_1 = [];
|
||||
var SUB_MIX_2 = [];
|
||||
var SUB_MIX_3 = [];
|
||||
var INV_SUB_MIX_0 = [];
|
||||
var INV_SUB_MIX_1 = [];
|
||||
var INV_SUB_MIX_2 = [];
|
||||
var INV_SUB_MIX_3 = [];
|
||||
|
||||
// Compute lookup tables
|
||||
(function () {
|
||||
// Compute double table
|
||||
var d = [];
|
||||
for (var i = 0; i < 256; i++) {
|
||||
if (i < 128) {
|
||||
d[i] = i << 1;
|
||||
} else {
|
||||
d[i] = (i << 1) ^ 0x11b;
|
||||
}
|
||||
}
|
||||
|
||||
// Walk GF(2^8)
|
||||
var x = 0;
|
||||
var xi = 0;
|
||||
for (var i = 0; i < 256; i++) {
|
||||
// Compute sbox
|
||||
var sx = xi ^ (xi << 1) ^ (xi << 2) ^ (xi << 3) ^ (xi << 4);
|
||||
sx = (sx >>> 8) ^ (sx & 0xff) ^ 0x63;
|
||||
SBOX[x] = sx;
|
||||
INV_SBOX[sx] = x;
|
||||
|
||||
// Compute multiplication
|
||||
var x2 = d[x];
|
||||
var x4 = d[x2];
|
||||
var x8 = d[x4];
|
||||
|
||||
// Compute sub bytes, mix columns tables
|
||||
var t = (d[sx] * 0x101) ^ (sx * 0x1010100);
|
||||
SUB_MIX_0[x] = (t << 24) | (t >>> 8);
|
||||
SUB_MIX_1[x] = (t << 16) | (t >>> 16);
|
||||
SUB_MIX_2[x] = (t << 8) | (t >>> 24);
|
||||
SUB_MIX_3[x] = t;
|
||||
|
||||
// Compute inv sub bytes, inv mix columns tables
|
||||
var t = (x8 * 0x1010101) ^ (x4 * 0x10001) ^ (x2 * 0x101) ^ (x * 0x1010100);
|
||||
INV_SUB_MIX_0[sx] = (t << 24) | (t >>> 8);
|
||||
INV_SUB_MIX_1[sx] = (t << 16) | (t >>> 16);
|
||||
INV_SUB_MIX_2[sx] = (t << 8) | (t >>> 24);
|
||||
INV_SUB_MIX_3[sx] = t;
|
||||
|
||||
// Compute next counter
|
||||
if (!x) {
|
||||
x = xi = 1;
|
||||
} else {
|
||||
x = x2 ^ d[d[d[x8 ^ x2]]];
|
||||
xi ^= d[d[xi]];
|
||||
}
|
||||
}
|
||||
}());
|
||||
|
||||
// Precomputed Rcon lookup
|
||||
var RCON = [0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36];
|
||||
|
||||
/**
|
||||
* AES block cipher algorithm.
|
||||
*/
|
||||
var AES = C_algo.AES = BlockCipher.extend({
|
||||
_doReset: function () {
|
||||
var t;
|
||||
|
||||
// Skip reset of nRounds has been set before and key did not change
|
||||
if (this._nRounds && this._keyPriorReset === this._key) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Shortcuts
|
||||
var key = this._keyPriorReset = this._key;
|
||||
var keyWords = key.words;
|
||||
var keySize = key.sigBytes / 4;
|
||||
|
||||
// Compute number of rounds
|
||||
var nRounds = this._nRounds = keySize + 6;
|
||||
|
||||
// Compute number of key schedule rows
|
||||
var ksRows = (nRounds + 1) * 4;
|
||||
|
||||
// Compute key schedule
|
||||
var keySchedule = this._keySchedule = [];
|
||||
for (var ksRow = 0; ksRow < ksRows; ksRow++) {
|
||||
if (ksRow < keySize) {
|
||||
keySchedule[ksRow] = keyWords[ksRow];
|
||||
} else {
|
||||
t = keySchedule[ksRow - 1];
|
||||
|
||||
if (!(ksRow % keySize)) {
|
||||
// Rot word
|
||||
t = (t << 8) | (t >>> 24);
|
||||
|
||||
// Sub word
|
||||
t = (SBOX[t >>> 24] << 24) | (SBOX[(t >>> 16) & 0xff] << 16) | (SBOX[(t >>> 8) & 0xff] << 8) | SBOX[t & 0xff];
|
||||
|
||||
// Mix Rcon
|
||||
t ^= RCON[(ksRow / keySize) | 0] << 24;
|
||||
} else if (keySize > 6 && ksRow % keySize == 4) {
|
||||
// Sub word
|
||||
t = (SBOX[t >>> 24] << 24) | (SBOX[(t >>> 16) & 0xff] << 16) | (SBOX[(t >>> 8) & 0xff] << 8) | SBOX[t & 0xff];
|
||||
}
|
||||
|
||||
keySchedule[ksRow] = keySchedule[ksRow - keySize] ^ t;
|
||||
}
|
||||
}
|
||||
|
||||
// Compute inv key schedule
|
||||
var invKeySchedule = this._invKeySchedule = [];
|
||||
for (var invKsRow = 0; invKsRow < ksRows; invKsRow++) {
|
||||
var ksRow = ksRows - invKsRow;
|
||||
|
||||
if (invKsRow % 4) {
|
||||
var t = keySchedule[ksRow];
|
||||
} else {
|
||||
var t = keySchedule[ksRow - 4];
|
||||
}
|
||||
|
||||
if (invKsRow < 4 || ksRow <= 4) {
|
||||
invKeySchedule[invKsRow] = t;
|
||||
} else {
|
||||
invKeySchedule[invKsRow] = INV_SUB_MIX_0[SBOX[t >>> 24]] ^ INV_SUB_MIX_1[SBOX[(t >>> 16) & 0xff]] ^
|
||||
INV_SUB_MIX_2[SBOX[(t >>> 8) & 0xff]] ^ INV_SUB_MIX_3[SBOX[t & 0xff]];
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
encryptBlock: function (M, offset) {
|
||||
this._doCryptBlock(M, offset, this._keySchedule, SUB_MIX_0, SUB_MIX_1, SUB_MIX_2, SUB_MIX_3, SBOX);
|
||||
},
|
||||
|
||||
decryptBlock: function (M, offset) {
|
||||
// Swap 2nd and 4th rows
|
||||
var t = M[offset + 1];
|
||||
M[offset + 1] = M[offset + 3];
|
||||
M[offset + 3] = t;
|
||||
|
||||
this._doCryptBlock(M, offset, this._invKeySchedule, INV_SUB_MIX_0, INV_SUB_MIX_1, INV_SUB_MIX_2, INV_SUB_MIX_3, INV_SBOX);
|
||||
|
||||
// Inv swap 2nd and 4th rows
|
||||
var t = M[offset + 1];
|
||||
M[offset + 1] = M[offset + 3];
|
||||
M[offset + 3] = t;
|
||||
},
|
||||
|
||||
_doCryptBlock: function (M, offset, keySchedule, SUB_MIX_0, SUB_MIX_1, SUB_MIX_2, SUB_MIX_3, SBOX) {
|
||||
// Shortcut
|
||||
var nRounds = this._nRounds;
|
||||
|
||||
// Get input, add round key
|
||||
var s0 = M[offset] ^ keySchedule[0];
|
||||
var s1 = M[offset + 1] ^ keySchedule[1];
|
||||
var s2 = M[offset + 2] ^ keySchedule[2];
|
||||
var s3 = M[offset + 3] ^ keySchedule[3];
|
||||
|
||||
// Key schedule row counter
|
||||
var ksRow = 4;
|
||||
|
||||
// Rounds
|
||||
for (var round = 1; round < nRounds; round++) {
|
||||
// Shift rows, sub bytes, mix columns, add round key
|
||||
var t0 = SUB_MIX_0[s0 >>> 24] ^ SUB_MIX_1[(s1 >>> 16) & 0xff] ^ SUB_MIX_2[(s2 >>> 8) & 0xff] ^ SUB_MIX_3[s3 & 0xff] ^ keySchedule[ksRow++];
|
||||
var t1 = SUB_MIX_0[s1 >>> 24] ^ SUB_MIX_1[(s2 >>> 16) & 0xff] ^ SUB_MIX_2[(s3 >>> 8) & 0xff] ^ SUB_MIX_3[s0 & 0xff] ^ keySchedule[ksRow++];
|
||||
var t2 = SUB_MIX_0[s2 >>> 24] ^ SUB_MIX_1[(s3 >>> 16) & 0xff] ^ SUB_MIX_2[(s0 >>> 8) & 0xff] ^ SUB_MIX_3[s1 & 0xff] ^ keySchedule[ksRow++];
|
||||
var t3 = SUB_MIX_0[s3 >>> 24] ^ SUB_MIX_1[(s0 >>> 16) & 0xff] ^ SUB_MIX_2[(s1 >>> 8) & 0xff] ^ SUB_MIX_3[s2 & 0xff] ^ keySchedule[ksRow++];
|
||||
|
||||
// Update state
|
||||
s0 = t0;
|
||||
s1 = t1;
|
||||
s2 = t2;
|
||||
s3 = t3;
|
||||
}
|
||||
|
||||
// Shift rows, sub bytes, add round key
|
||||
var t0 = ((SBOX[s0 >>> 24] << 24) | (SBOX[(s1 >>> 16) & 0xff] << 16) | (SBOX[(s2 >>> 8) & 0xff] << 8) | SBOX[s3 & 0xff]) ^ keySchedule[ksRow++];
|
||||
var t1 = ((SBOX[s1 >>> 24] << 24) | (SBOX[(s2 >>> 16) & 0xff] << 16) | (SBOX[(s3 >>> 8) & 0xff] << 8) | SBOX[s0 & 0xff]) ^ keySchedule[ksRow++];
|
||||
var t2 = ((SBOX[s2 >>> 24] << 24) | (SBOX[(s3 >>> 16) & 0xff] << 16) | (SBOX[(s0 >>> 8) & 0xff] << 8) | SBOX[s1 & 0xff]) ^ keySchedule[ksRow++];
|
||||
var t3 = ((SBOX[s3 >>> 24] << 24) | (SBOX[(s0 >>> 16) & 0xff] << 16) | (SBOX[(s1 >>> 8) & 0xff] << 8) | SBOX[s2 & 0xff]) ^ keySchedule[ksRow++];
|
||||
|
||||
// Set output
|
||||
M[offset] = t0;
|
||||
M[offset + 1] = t1;
|
||||
M[offset + 2] = t2;
|
||||
M[offset + 3] = t3;
|
||||
},
|
||||
|
||||
keySize: 256/32
|
||||
});
|
||||
|
||||
/**
|
||||
* Shortcut functions to the cipher's object interface.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* var ciphertext = CryptoJS.AES.encrypt(message, key, cfg);
|
||||
* var plaintext = CryptoJS.AES.decrypt(ciphertext, key, cfg);
|
||||
*/
|
||||
C.AES = BlockCipher._createHelper(AES);
|
||||
}());
|
||||
|
||||
|
||||
return CryptoJS.AES;
|
||||
|
||||
}));
|
||||
6191
server/src/main/resources/static/js/crypto-js.js
Normal file
6191
server/src/main/resources/static/js/crypto-js.js
Normal file
File diff suppressed because it is too large
Load Diff
299
server/src/main/resources/static/js/fenye.js
Normal file
299
server/src/main/resources/static/js/fenye.js
Normal file
@@ -0,0 +1,299 @@
|
||||
function htmlttt (str){
|
||||
var s = "";
|
||||
if(str.length == 0) return "";
|
||||
s = str.replace(/&/gi,"&");
|
||||
s = s.replace(/ /gi," ");
|
||||
s = s.replace(/'/gi,"\'");
|
||||
s = s.replace(/"/gi,"\"");
|
||||
s = s.replace(/javascript/g,"javascript ");
|
||||
s = s.replace(/iframe/gi, "iframe ");
|
||||
return s;
|
||||
}
|
||||
|
||||
function isEmptyString(str) {
|
||||
return !str || str.length === 0;
|
||||
}
|
||||
|
||||
function removeExtraNewlines(str) {
|
||||
// 替换连续的换行符为单个换行符
|
||||
return str.replace(/(?:\r\n|\r|\n){2,}/g, '\n');
|
||||
}
|
||||
|
||||
function DHTMLpagenation(content,kkkeyword,Length,page,txt)
|
||||
{
|
||||
if(page==0){
|
||||
page=1;
|
||||
}
|
||||
this.content=content; // 内容
|
||||
this.contentLength=s.length; // 内容长度
|
||||
this.pageSizeCount; // 总页数
|
||||
this.perpageLength= Length; //default perpage byte length.
|
||||
this.currentPage= page; // 起始页为第1页
|
||||
this.regularExp=/\d+/; // 建立正则表达式,匹配数字型字符串。
|
||||
this.divDisplayContent;
|
||||
this.contentStyle=null;
|
||||
this.strDisplayContent="";
|
||||
this.divDisplayPagenation;
|
||||
this.strDisplayPagenation="";
|
||||
|
||||
// 把第二个参数赋给perpageLength;
|
||||
arguments.length==2 ? perpageLength = arguments[1] : '';
|
||||
|
||||
try {
|
||||
//创建要显示的DIV
|
||||
divExecuteTime=document.createElement("DIV");
|
||||
document.body.appendChild(divExecuteTime);
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
}
|
||||
|
||||
// 得到divPagenation容器。
|
||||
if(document.getElementById("divPagenation"))
|
||||
{
|
||||
divDisplayPagenation=document.getElementById("divPagenation");
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
//创建分页信息
|
||||
divDisplayPagenation=document.createElement("DIV");
|
||||
divDisplayPagenation.id="divPagenation";
|
||||
document.body.appendChild(divDisplayPagenation);
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 得到divContent容器
|
||||
if(document.getElementById("divContent"))
|
||||
{
|
||||
divDisplayContent=document.getElementById("divContent");
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
//创建每页显示内容的消息的DIV
|
||||
divDisplayContent=document.createElement("DIV");
|
||||
divDisplayContent.id="divContent";
|
||||
document.body.appendChild(divDisplayContent);
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
DHTMLpagenation.initialize();
|
||||
return this;
|
||||
|
||||
};
|
||||
|
||||
//初始化分页;
|
||||
//包括把加入CSS,检查是否需要分页
|
||||
DHTMLpagenation.initialize=function()
|
||||
{
|
||||
divDisplayContent.className= contentStyle != null ? contentStyle : "divContent";
|
||||
|
||||
if(contentLength<=perpageLength)
|
||||
{
|
||||
if(txt =="code"){
|
||||
content = htmlttt(content);
|
||||
strDisplayContent = '<pre><code>'+content+'</code></pre>';
|
||||
divDisplayContent.innerHTML=strDisplayContent;
|
||||
if (!!window.ActiveXObject || "ActiveXObject" in window){
|
||||
}else{
|
||||
hljs.highlightAll(kkkeyword);
|
||||
}
|
||||
}else if(txt =="js"){
|
||||
content = htmlttt(content);
|
||||
var result = js_beautify(content, 1, "\t");
|
||||
strDisplayContent = '<pre><code class="language-js">'+result+'</code></pre>';
|
||||
divDisplayContent.innerHTML=strDisplayContent;
|
||||
if (!!window.ActiveXObject || "ActiveXObject" in window){
|
||||
}else{
|
||||
hljs.highlightAll(kkkeyword);
|
||||
}
|
||||
}else{
|
||||
content = removeExtraNewlines(content);
|
||||
let list = content.split('\n') // 换行符分割
|
||||
for(let i=0;i<list.length;i++) {
|
||||
list[i] = i + "." + list[i] // 加序号
|
||||
}
|
||||
let txt = list.join('\n');
|
||||
if (kkkeyword!==""&&kkkeyword!==null&&kkkeyword!=="null") {
|
||||
txt = txt.split(kkkeyword).join("<span class='highlight'>" + kkkeyword + "</span>");
|
||||
}
|
||||
divDisplayContent.innerHTML=txt;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
pageSizeCount=Math.ceil((contentLength/perpageLength));
|
||||
|
||||
DHTMLpagenation.goto(currentPage);
|
||||
|
||||
DHTMLpagenation.displayContent();
|
||||
};
|
||||
|
||||
//显示分页栏
|
||||
DHTMLpagenation.displayPage=function()
|
||||
{
|
||||
strDisplayPagenation="";
|
||||
if(currentPage && currentPage !=1)
|
||||
{
|
||||
|
||||
strDisplayPagenation+='<button onclick="DHTMLpagenation.previous()">上一页 </button>';
|
||||
}
|
||||
else
|
||||
{
|
||||
strDisplayPagenation+="上一页";
|
||||
}
|
||||
|
||||
if(currentPage && currentPage!=pageSizeCount)
|
||||
{
|
||||
strDisplayPagenation+='<button onclick="DHTMLpagenation.next()">下一页 </button>';
|
||||
|
||||
strDisplayPagenation+="<input type='number' value='"+currentPage+"' id='yemaPerpageLength' style='width:70px' /><button type='button' onclick='DHTMLpagenation.tiaozhuan()'/> 跳转</button> ";
|
||||
}
|
||||
else
|
||||
{
|
||||
strDisplayPagenation+="下一页";
|
||||
}
|
||||
if (isEmptyString(currentPage)) {
|
||||
currentPage =1;
|
||||
}
|
||||
strDisplayPagenation+=+currentPage+"/" + pageSizeCount + "页。<br>每页 " + perpageLength + " 字符,调整字符数:<input type='number' value='"+perpageLength+"' id='ctlPerpageLength' style='width:70px' /><button onclick='DHTMLpagenation.change()' /> 确定</button>";
|
||||
divDisplayPagenation.innerHTML=strDisplayPagenation;
|
||||
};
|
||||
|
||||
//上一页
|
||||
DHTMLpagenation.previous=function()
|
||||
{
|
||||
DHTMLpagenation.goto(currentPage-1);
|
||||
};
|
||||
|
||||
//下一页
|
||||
DHTMLpagenation.next=function()
|
||||
{
|
||||
|
||||
DHTMLpagenation.goto(currentPage+1);
|
||||
};
|
||||
|
||||
//跳转至某一页
|
||||
DHTMLpagenation.goto=function(iCurrentPage)
|
||||
{
|
||||
if (isEmptyString(iCurrentPage)) {
|
||||
iCurrentPage =1;
|
||||
}
|
||||
if(regularExp.test(iCurrentPage))
|
||||
{
|
||||
currentPage=iCurrentPage;
|
||||
//获取当前的内容 里面包含 ❈
|
||||
var currentContent = s.substr((currentPage-1)*perpageLength,perpageLength);
|
||||
//当前页是否有 ❈ 获取最后一个 ❈ 的位置
|
||||
var indexOf = currentContent.indexOf("❈");
|
||||
if(indexOf >= 0)
|
||||
{
|
||||
//获取从开始位置到当前页位置的内容
|
||||
var beginToEndContent = s.substr(0,currentPage*perpageLength);
|
||||
|
||||
//获取开始到当前页位置的内容 中的 * 的最后的下标
|
||||
var reCount = beginToEndContent.split("❈").length - 1;
|
||||
|
||||
var contentArray = currentContent.split("❈");
|
||||
|
||||
currentContent = replaceStr(contentArray,reCount,matchContent);
|
||||
|
||||
}
|
||||
|
||||
strDisplayContent=currentContent;
|
||||
}
|
||||
else
|
||||
{
|
||||
alert("页面参数错误");
|
||||
}
|
||||
DHTMLpagenation.displayPage();
|
||||
DHTMLpagenation.displayContent();
|
||||
};
|
||||
//显示当前页内容
|
||||
DHTMLpagenation.displayContent=function()
|
||||
{
|
||||
if(txt =="code"){
|
||||
strDisplayContent = htmlttt(strDisplayContent);
|
||||
strDisplayContent = "<pre><code>"+strDisplayContent+"</code></pre>";
|
||||
divDisplayContent.innerHTML=strDisplayContent;
|
||||
if (!!window.ActiveXObject || "ActiveXObject" in window){
|
||||
}else{
|
||||
hljs.highlightAll(kkkeyword);
|
||||
}
|
||||
}else if(txt =="js"){
|
||||
strDisplayContent = htmlttt(strDisplayContent);
|
||||
var result = js_beautify(strDisplayContent, 1, "\t");
|
||||
strDisplayContent ='<pre><code class="language-js">'+result+'</code></pre>';
|
||||
divDisplayContent.innerHTML=strDisplayContent;
|
||||
if (!!window.ActiveXObject || "ActiveXObject" in window){
|
||||
}else{
|
||||
hljs.highlightAll(kkkeyword);
|
||||
}
|
||||
}else{
|
||||
if (kkkeyword!==""&&kkkeyword!==null&&kkkeyword!=="null") {
|
||||
|
||||
strDisplayContent = strDisplayContent.split(kkkeyword).join("<span class='highlight'>" + kkkeyword + "</span>");
|
||||
}
|
||||
divDisplayContent.innerHTML=strDisplayContent;
|
||||
}
|
||||
};
|
||||
//改变每页的字节数
|
||||
DHTMLpagenation.change=function()
|
||||
{
|
||||
|
||||
var iPerpageLength = document.getElementById("ctlPerpageLength").value;
|
||||
if(regularExp.test(iPerpageLength))
|
||||
{
|
||||
|
||||
DHTMLpagenation(s,iPerpageLength);
|
||||
}
|
||||
else
|
||||
{
|
||||
alert("请输入数字");
|
||||
}
|
||||
};
|
||||
//改变页码
|
||||
DHTMLpagenation.tiaozhuan=function()
|
||||
{
|
||||
var yema = document.getElementById("yemaPerpageLength").value;
|
||||
if(regularExp.test(yema))
|
||||
{
|
||||
DHTMLpagenation.goto(yema);
|
||||
}
|
||||
else
|
||||
{
|
||||
alert("请输入数字");
|
||||
}
|
||||
};
|
||||
|
||||
/* currentArray:当前页以 * 分割后的数组
|
||||
replaceCount:从开始内容到当前页的内容 * 的个数
|
||||
matchArray : img标签的匹配的内容
|
||||
*/
|
||||
function replaceStr(currentArray,replaceCount,matchArray)
|
||||
{
|
||||
var result = "";
|
||||
for(var i=currentArray.length -1,j = replaceCount-1 ;i>=1; i--)
|
||||
{
|
||||
|
||||
var temp = (matchArray[j] + currentArray[i]);
|
||||
|
||||
result = temp + result;
|
||||
|
||||
j--;
|
||||
}
|
||||
|
||||
result = currentArray[0] + result ;
|
||||
|
||||
return result;
|
||||
}
|
||||
72
server/src/main/resources/static/js/jsformat.js
Normal file
72
server/src/main/resources/static/js/jsformat.js
Normal file
@@ -0,0 +1,72 @@
|
||||
function js_beautify(js_source_text,indent_size,indent_character,indent_level)
|
||||
{var input,output,token_text,last_type,last_text,last_word,current_mode,modes,indent_string;var whitespace,wordchar,punct,parser_pos,line_starters,in_case;var prefix,token_type,do_block_just_closed,var_line,var_line_tainted;function trim_output()
|
||||
{while(output.length&&(output[output.length-1]===' '||output[output.length-1]===indent_string)){output.pop();}}
|
||||
function print_newline(ignore_repeated)
|
||||
{ignore_repeated=typeof ignore_repeated==='undefined'?true:ignore_repeated;trim_output();if(!output.length){return;}
|
||||
if(output[output.length-1]!=="\n"||!ignore_repeated){output.push("\n");}
|
||||
for(var i=0;i<indent_level;i++){output.push(indent_string);}}
|
||||
function print_space()
|
||||
{var last_output=output.length?output[output.length-1]:' ';if(last_output!==' '&&last_output!=='\n'&&last_output!==indent_string){output.push(' ');}}
|
||||
function print_token()
|
||||
{output.push(token_text);}
|
||||
function indent()
|
||||
{indent_level++;}
|
||||
function unindent()
|
||||
{if(indent_level){indent_level--;}}
|
||||
function remove_indent()
|
||||
{if(output.length&&output[output.length-1]===indent_string){output.pop();}}
|
||||
function set_mode(mode)
|
||||
{modes.push(current_mode);current_mode=mode;}
|
||||
function restore_mode()
|
||||
{do_block_just_closed=current_mode==='DO_BLOCK';current_mode=modes.pop();}
|
||||
function in_array(what,arr)
|
||||
{for(var i=0;i<arr.length;i++)
|
||||
{if(arr[i]===what){return true;}}
|
||||
return false;}
|
||||
function get_next_token()
|
||||
{var n_newlines=0;var c='';do{if(parser_pos>=input.length){return['','TK_EOF'];}
|
||||
c=input.charAt(parser_pos);parser_pos+=1;if(c==="\n"){n_newlines+=1;}}
|
||||
while(in_array(c,whitespace));if(n_newlines>1){for(var i=0;i<2;i++){print_newline(i===0);}}
|
||||
var wanted_newline=(n_newlines===1);if(in_array(c,wordchar)){if(parser_pos<input.length){while(in_array(input.charAt(parser_pos),wordchar)){c+=input.charAt(parser_pos);parser_pos+=1;if(parser_pos===input.length){break;}}}
|
||||
if(parser_pos!==input.length&&c.match(/^[0-9]+[Ee]$/)&&input.charAt(parser_pos)==='-'){parser_pos+=1;var t=get_next_token(parser_pos);c+='-'+t[0];return[c,'TK_WORD'];}
|
||||
if(c==='in'){return[c,'TK_OPERATOR'];}
|
||||
return[c,'TK_WORD'];}
|
||||
if(c==='('||c==='['){return[c,'TK_START_EXPR'];}
|
||||
if(c===')'||c===']'){return[c,'TK_END_EXPR'];}
|
||||
if(c==='{'){return[c,'TK_START_BLOCK'];}
|
||||
if(c==='}'){return[c,'TK_END_BLOCK'];}
|
||||
if(c===';'){return[c,'TK_END_COMMAND'];}
|
||||
if(c==='/'){var comment='';if(input.charAt(parser_pos)==='*'){parser_pos+=1;if(parser_pos<input.length){while(!(input.charAt(parser_pos)==='*'&&input.charAt(parser_pos+1)&&input.charAt(parser_pos+1)==='/')&&parser_pos<input.length){comment+=input.charAt(parser_pos);parser_pos+=1;if(parser_pos>=input.length){break;}}}
|
||||
parser_pos+=2;return['/*'+comment+'*/','TK_BLOCK_COMMENT'];}
|
||||
if(input.charAt(parser_pos)==='/'){comment=c;while(input.charAt(parser_pos)!=="\x0d"&&input.charAt(parser_pos)!=="\x0a"){comment+=input.charAt(parser_pos);parser_pos+=1;if(parser_pos>=input.length){break;}}
|
||||
parser_pos+=1;if(wanted_newline){print_newline();}
|
||||
return[comment,'TK_COMMENT'];}}
|
||||
if(c==="'"||c==='"'||(c==='/'&&((last_type==='TK_WORD'&&last_text==='return')||(last_type==='TK_START_EXPR'||last_type==='TK_END_BLOCK'||last_type==='TK_OPERATOR'||last_type==='TK_EOF'||last_type==='TK_END_COMMAND')))){var sep=c;var esc=false;c='';if(parser_pos<input.length){while(esc||input.charAt(parser_pos)!==sep){c+=input.charAt(parser_pos);if(!esc){esc=input.charAt(parser_pos)==='\\';}else{esc=false;}
|
||||
parser_pos+=1;if(parser_pos>=input.length){break;}}}
|
||||
parser_pos+=1;if(last_type==='TK_END_COMMAND'){print_newline();}
|
||||
return[sep+c+sep,'TK_STRING'];}
|
||||
if(in_array(c,punct)){while(parser_pos<input.length&&in_array(c+input.charAt(parser_pos),punct)){c+=input.charAt(parser_pos);parser_pos+=1;if(parser_pos>=input.length){break;}}
|
||||
return[c,'TK_OPERATOR'];}
|
||||
return[c,'TK_UNKNOWN'];}
|
||||
indent_character=indent_character||' ';indent_size=indent_size||4;indent_string='';while(indent_size--){indent_string+=indent_character;}
|
||||
input=js_source_text;last_word='';last_type='TK_START_EXPR';last_text='';output=[];do_block_just_closed=false;var_line=false;var_line_tainted=false;whitespace="\n\r\t ".split('');wordchar='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_$'.split('');punct='+ - * / % & ++ -- = += -= *= /= %= == === != !== > < >= <= >> << >>> >>>= >>= <<= && &= | || ! !! , : ? ^ ^= |='.split(' ');line_starters='continue,try,throw,return,var,if,switch,case,default,for,while,break,function'.split(',');current_mode='BLOCK';modes=[current_mode];indent_level=indent_level||0;parser_pos=0;in_case=false;while(true){var t=get_next_token(parser_pos);token_text=t[0];token_type=t[1];if(token_type==='TK_EOF'){break;}
|
||||
switch(token_type){case'TK_START_EXPR':var_line=false;set_mode('EXPRESSION');if(last_type==='TK_END_EXPR'||last_type==='TK_START_EXPR'){}else if(last_type!=='TK_WORD'&&last_type!=='TK_OPERATOR'){print_space();}else if(in_array(last_word,line_starters)&&last_word!=='function'){print_space();}
|
||||
print_token();break;case'TK_END_EXPR':print_token();restore_mode();break;case'TK_START_BLOCK':if(last_word==='do'){set_mode('DO_BLOCK');}else{set_mode('BLOCK');}
|
||||
if(last_type!=='TK_OPERATOR'&&last_type!=='TK_START_EXPR'){if(last_type==='TK_START_BLOCK'){print_newline();}else{print_space();}}
|
||||
print_token();indent();break;case'TK_END_BLOCK':if(last_type==='TK_START_BLOCK'){trim_output();unindent();}else{unindent();print_newline();}
|
||||
print_token();restore_mode();break;case'TK_WORD':if(do_block_just_closed){print_space();print_token();print_space();break;}
|
||||
if(token_text==='case'||token_text==='default'){if(last_text===':'){remove_indent();}else{unindent();print_newline();indent();}
|
||||
print_token();in_case=true;break;}
|
||||
prefix='NONE';if(last_type==='TK_END_BLOCK'){if(!in_array(token_text.toLowerCase(),['else','catch','finally'])){prefix='NEWLINE';}else{prefix='SPACE';print_space();}}else if(last_type==='TK_END_COMMAND'&&(current_mode==='BLOCK'||current_mode==='DO_BLOCK')){prefix='NEWLINE';}else if(last_type==='TK_END_COMMAND'&¤t_mode==='EXPRESSION'){prefix='SPACE';}else if(last_type==='TK_WORD'){prefix='SPACE';}else if(last_type==='TK_START_BLOCK'){prefix='NEWLINE';}else if(last_type==='TK_END_EXPR'){print_space();prefix='NEWLINE';}
|
||||
if(last_type!=='TK_END_BLOCK'&&in_array(token_text.toLowerCase(),['else','catch','finally'])){print_newline();}else if(in_array(token_text,line_starters)||prefix==='NEWLINE'){if(last_text==='else'){print_space();}else if((last_type==='TK_START_EXPR'||last_text==='=')&&token_text==='function'){}else if(last_type==='TK_WORD'&&(last_text==='return'||last_text==='throw')){print_space();}else if(last_type!=='TK_END_EXPR'){if((last_type!=='TK_START_EXPR'||token_text!=='var')&&last_text!==':'){if(token_text==='if'&&last_type==='TK_WORD'&&last_word==='else'){print_space();}else{print_newline();}}}else{if(in_array(token_text,line_starters)&&last_text!==')'){print_newline();}}}else if(prefix==='SPACE'){print_space();}
|
||||
print_token();last_word=token_text;if(token_text==='var'){var_line=true;var_line_tainted=false;}
|
||||
break;case'TK_END_COMMAND':print_token();var_line=false;break;case'TK_STRING':if(last_type==='TK_START_BLOCK'||last_type==='TK_END_BLOCK'){print_newline();}else if(last_type==='TK_WORD'){print_space();}
|
||||
print_token();break;case'TK_OPERATOR':var start_delim=true;var end_delim=true;if(var_line&&token_text!==','){var_line_tainted=true;if(token_text===':'){var_line=false;}}
|
||||
if(token_text===':'&&in_case){print_token();print_newline();break;}
|
||||
in_case=false;if(token_text===','){if(var_line){if(var_line_tainted){print_token();print_newline();var_line_tainted=false;}else{print_token();print_space();}}else if(last_type==='TK_END_BLOCK'){print_token();print_newline();}else{if(current_mode==='BLOCK'){print_token();print_newline();}else{print_token();print_space();}}
|
||||
break;}else if(token_text==='--'||token_text==='++'){if(last_text===';'){start_delim=true;end_delim=false;}else{start_delim=false;end_delim=false;}}else if(token_text==='!'&&last_type==='TK_START_EXPR'){start_delim=false;end_delim=false;}else if(last_type==='TK_OPERATOR'){start_delim=false;end_delim=false;}else if(last_type==='TK_END_EXPR'){start_delim=true;end_delim=true;}else if(token_text==='.'){start_delim=false;end_delim=false;}else if(token_text===':'){if(last_text.match(/^\d+$/)){start_delim=true;}else{start_delim=false;}}
|
||||
if(start_delim){print_space();}
|
||||
print_token();if(end_delim){print_space();}
|
||||
break;case'TK_BLOCK_COMMENT':print_newline();print_token();print_newline();break;case'TK_COMMENT':print_space();print_token();print_newline();break;case'TK_UNKNOWN':print_token();break;}
|
||||
last_type=token_type;last_text=token_text;}
|
||||
return output.join('');}
|
||||
@@ -2612,7 +2612,8 @@
|
||||
return this.destroy();
|
||||
}
|
||||
var images = [];
|
||||
forEach(isImg ? [element] : element.querySelectorAll('img'), function (image) {
|
||||
//console.log('Hello, World!');
|
||||
forEach(isImg ? [element] : element.querySelectorAll('div'), function (image) {
|
||||
if (isFunction(options.filter)) {
|
||||
if (options.filter.call(_this12, image)) {
|
||||
images.push(image);
|
||||
@@ -2989,7 +2990,8 @@
|
||||
}
|
||||
var isImg = element.localName === 'img';
|
||||
var images = [];
|
||||
forEach(isImg ? [element] : element.querySelectorAll('img'), function (image) {
|
||||
// console.log('Hello, World!');
|
||||
forEach(isImg ? [element] : element.querySelectorAll('div'), function (image) {
|
||||
if (isFunction(options.filter)) {
|
||||
if (options.filter.call(_this, image)) {
|
||||
images.push(image);
|
||||
|
||||
@@ -24,7 +24,23 @@
|
||||
* pdfjsVersion = 5.4.530
|
||||
* pdfjsBuild = 50cc4adac
|
||||
*/
|
||||
/******/ var __webpack_modules__ = ({
|
||||
/******/
|
||||
var queryStringg = document.location.search.substring(1);
|
||||
var paramss = parseQueryStringg(queryStringg);
|
||||
function parseQueryStringg(query) {
|
||||
const paramss = new Map();
|
||||
for (const [key, value] of new URLSearchParams(query)) {
|
||||
paramss.set(key.toLowerCase(), value);
|
||||
}
|
||||
return paramss;
|
||||
}
|
||||
var kkSIZE = paramss.get("pdfautofetch");
|
||||
if (kkSIZE == "true" ) {
|
||||
kkSIZE = 65536*16;
|
||||
} else {
|
||||
kkSIZE = 65536;
|
||||
}
|
||||
var __webpack_modules__ = ({
|
||||
|
||||
/***/ 34:
|
||||
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
|
||||
@@ -20714,7 +20730,7 @@ class TextLayer {
|
||||
|
||||
|
||||
|
||||
|
||||
const DEFAULT_RANGE_CHUNK_SIZE = kkSIZE;
|
||||
const RENDERING_CANCELLED_TIMEOUT = 100;
|
||||
function getDocument(src = {}) {
|
||||
if (typeof src === "string" || src instanceof URL) {
|
||||
@@ -20736,7 +20752,7 @@ function getDocument(src = {}) {
|
||||
const withCredentials = src.withCredentials === true;
|
||||
const password = src.password ?? null;
|
||||
const rangeTransport = src.range instanceof PDFDataRangeTransport ? src.range : null;
|
||||
const rangeChunkSize = Number.isInteger(src.rangeChunkSize) && src.rangeChunkSize > 0 ? src.rangeChunkSize : 2 ** 16;
|
||||
const rangeChunkSize = Number.isInteger(src.rangeChunkSize) && src.rangeChunkSize > 0 ? src.rangeChunkSize : DEFAULT_RANGE_CHUNK_SIZE;
|
||||
let worker = src.worker instanceof PDFWorker ? src.worker : null;
|
||||
const verbosity = src.verbosity;
|
||||
const docBaseUrl = typeof src.docBaseUrl === "string" && !isDataScheme(src.docBaseUrl) ? src.docBaseUrl : null;
|
||||
|
||||
@@ -41,9 +41,10 @@
|
||||
const viewer = new BpmnJS({
|
||||
container: '#diagram'
|
||||
});
|
||||
var url = '${finalUrl}';
|
||||
var url = '${finalUrl}';
|
||||
var kkagent = '${kkagent}';
|
||||
var baseUrl = '${baseUrl}'.endsWith('/') ? '${baseUrl}' : '${baseUrl}' + '/';
|
||||
if (!url.startsWith(baseUrl)) {
|
||||
if (kkagent === 'true' || !url.startsWith(baseUrl)) {
|
||||
url = baseUrl + 'getCorsFile?urlPath=' + encodeURIComponent(Base64.encode(url));
|
||||
}
|
||||
async function showDiagram(diagramXML) {
|
||||
|
||||
@@ -3,26 +3,22 @@
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, user-scalable=yes, initial-scale=1.0">
|
||||
<title>代码预览</title>
|
||||
<title>${file.name}代码预览</title>
|
||||
<#include "*/commonHeader.ftl">
|
||||
<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">
|
||||
<link rel="stylesheet" href="highlight/highlight.css">
|
||||
<script src="highlight/highlight.min.js" type="text/javascript"></script>
|
||||
<script src="js/fenye.js" type="text/javascript"></script>
|
||||
<#if "${file.suffix?lower_case}" == "js" >
|
||||
<script src="js/jsformat.js" type="text/javascript"></script>
|
||||
</#if>
|
||||
<script src="js/base64.min.js" type="text/javascript"></script>
|
||||
<script>hljs.highlightAll()</script>
|
||||
|
||||
<style>
|
||||
div.code {
|
||||
white-space: pre;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<input hidden id="textData" value="${textData}"/>
|
||||
|
||||
<div class="container">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
@@ -32,39 +28,30 @@
|
||||
</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div id="code" class='code'></div>
|
||||
</div>
|
||||
<div id="divPagenation" class="black" >
|
||||
</div>
|
||||
<div id="divContent" class="panel-body">
|
||||
</div>
|
||||
<div id="divPagenationx" class="black" >
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<script>
|
||||
/**
|
||||
<script type="text/javascript">
|
||||
var base64data = $("#textData").val()
|
||||
var s = Base64.decode(base64data);
|
||||
var kkkeyword = '${highlightall}';
|
||||
var Length= 20000;
|
||||
var page= '${page}';
|
||||
<#if "${file.suffix?lower_case}" == "js" > var txt = "js";<#else>var txt = "code";</#if>
|
||||
DHTMLpagenation(s,kkkeyword,Length,page,txt);
|
||||
/**
|
||||
* 初始化
|
||||
*/
|
||||
window.onload = function () {
|
||||
initWaterMark();
|
||||
loadText();
|
||||
}
|
||||
|
||||
/**
|
||||
*加载普通文本
|
||||
*/
|
||||
function loadText() {
|
||||
var base64data = $("#textData").val()
|
||||
var textData = Base64.decode(base64data);
|
||||
|
||||
var textPreData = "<pre><code>" + textData + "</code></pre>";
|
||||
$("#code").append(textPreData);
|
||||
document.querySelectorAll('div.code').forEach(block => {
|
||||
// then highlight each
|
||||
hljs.highlightBlock(block);
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
@@ -54,9 +54,10 @@
|
||||
<script src="dcm/index.umd.js"></script>
|
||||
|
||||
<script>
|
||||
var url = '${finalUrl}';
|
||||
var url = '${finalUrl}';
|
||||
var kkagent = '${kkagent}';
|
||||
var baseUrl = '${baseUrl}'.endsWith('/') ? '${baseUrl}' : '${baseUrl}' + '/';
|
||||
if (!url.startsWith(baseUrl)) {
|
||||
if (kkagent === 'true' || !url.startsWith(baseUrl)) {
|
||||
url = baseUrl + 'getCorsFile?urlPath=' + encodeURIComponent(Base64.encode(url));
|
||||
}
|
||||
"use strict";
|
||||
|
||||
@@ -16,9 +16,10 @@
|
||||
<#assign finalUrl="${baseUrl}${currentUrl}">
|
||||
</#if>
|
||||
<script>
|
||||
var url = '${finalUrl}';
|
||||
var url = '${finalUrl}';
|
||||
var kkagent = '${kkagent}';
|
||||
var baseUrl = '${baseUrl}'.endsWith('/') ? '${baseUrl}' : '${baseUrl}' + '/';
|
||||
if (!url.startsWith(baseUrl)) {
|
||||
if (kkagent === 'true' || !url.startsWith(baseUrl)) {
|
||||
url = baseUrl + 'getCorsFile?urlPath=' + encodeURIComponent(Base64.encode(url));
|
||||
}
|
||||
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)+"";
|
||||
|
||||
@@ -19,10 +19,11 @@
|
||||
<iframe src="" width="100%" frameborder="0"></iframe>
|
||||
</body>
|
||||
<script type="text/javascript">
|
||||
var url = '${finalUrl}';
|
||||
var url = '${finalUrl}';
|
||||
var kkagent = '${kkagent}';
|
||||
var baseUrl = '${baseUrl}'.endsWith('/') ? '${baseUrl}' : '${baseUrl}' + '/';
|
||||
if (!url.startsWith(baseUrl)) {
|
||||
url = baseUrl + 'getCorsFile?urlPath=' + encodeURIComponent(Base64.encode(url));
|
||||
if (kkagent === 'true' || !url.startsWith(baseUrl)) {
|
||||
url = baseUrl + 'getCorsFile?urlPath=' + encodeURIComponent(Base64.encode(url));
|
||||
}
|
||||
document.getElementsByTagName('iframe')[0].src = "${baseUrl}eml/index.html?file="+encodeURIComponent(url);
|
||||
document.getElementsByTagName('iframe')[0].height = document.documentElement.clientHeight - 10;
|
||||
|
||||
@@ -25,10 +25,11 @@
|
||||
<div id="next" class="arrow">›</div>
|
||||
</body>
|
||||
<script type="text/javascript">
|
||||
var url = '${finalUrl}';
|
||||
var url = '${finalUrl}';
|
||||
var kkagent = '${kkagent}';
|
||||
var baseUrl = '${baseUrl}'.endsWith('/') ? '${baseUrl}' : '${baseUrl}' + '/';
|
||||
if (!url.startsWith(baseUrl)) {
|
||||
url = baseUrl + 'getCorsFile?urlPath=' + encodeURIComponent(Base64.encode(url));
|
||||
if (kkagent === 'true' || !url.startsWith(baseUrl)) {
|
||||
url = baseUrl + 'getCorsFile?urlPath=' + encodeURIComponent(Base64.encode(url));
|
||||
}
|
||||
|
||||
function blobToArrayBuffer(blob) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -18,14 +18,16 @@
|
||||
</body>
|
||||
<script type="text/javascript">
|
||||
var url = '${finalUrl}';
|
||||
var kkagent = '${kkagent}';
|
||||
var baseUrl = '${baseUrl}'.endsWith('/') ? '${baseUrl}' : '${baseUrl}' + '/';
|
||||
if (!url.startsWith(baseUrl)) {
|
||||
|
||||
if (kkagent === 'true' || !url.startsWith(baseUrl)) {
|
||||
url = baseUrl + 'getCorsFile?urlPath=' + encodeURIComponent(Base64.encode(url));
|
||||
}
|
||||
if(IsPhone()){
|
||||
document.getElementsByTagName('iframe')[0].src = "${baseUrl}ofd/index.html?file=" + encodeURIComponent(url)+"&scale=width";
|
||||
if(IsPhone()){
|
||||
document.getElementsByTagName('iframe')[0].src = "${baseUrl}ofd/index.html?file=" + encodeURIComponent(url)+"&scale=width"+"&page=${page}";
|
||||
}else{
|
||||
document.getElementsByTagName('iframe')[0].src = "${baseUrl}ofd/index.html?file="+ encodeURIComponent(url)+"";
|
||||
document.getElementsByTagName('iframe')[0].src = "${baseUrl}ofd/index.html?file="+ encodeURIComponent(url)+"&page=${page}";
|
||||
}
|
||||
document.getElementsByTagName('iframe')[0].height = document.documentElement.clientHeight - 10;
|
||||
/**
|
||||
|
||||
@@ -2,64 +2,317 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>PDF图片预览</title>
|
||||
<title>${file.name}图片预览</title>
|
||||
<#include "*/commonHeader.ftl">
|
||||
<script src="js/lazyload.js"></script>
|
||||
<style>
|
||||
body {
|
||||
background-color: #404040;
|
||||
}
|
||||
.container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.img-area {
|
||||
text-align: center;
|
||||
}
|
||||
.my-photo {
|
||||
max-width: 98%;
|
||||
margin:0 auto;
|
||||
border-radius:3px;
|
||||
box-shadow:rgba(0,0,0,0.15) 0 0 8px;
|
||||
background:#FBFBFB;
|
||||
border:1px solid #ddd;
|
||||
margin:1px auto;
|
||||
padding:5px;
|
||||
}
|
||||
|
||||
</style>
|
||||
<link rel="stylesheet" href="css/officePicture.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<#list imgUrls as img>
|
||||
<div class="img-area">
|
||||
<img class="my-photo" alt="loading" data-src="${img}" src="images/loading.gif">
|
||||
<div class="img-area" id="imgArea${img_index+1}">
|
||||
<div class="image-container">
|
||||
<img class="my-photo" id="page${img_index+1}" src="images/loading.gif" guoyu-src="${img}">
|
||||
<div class="button-container">
|
||||
<button class="sszImg" >${img_index+1}/${imgUrls?size}页</button>
|
||||
<button class="nszImg" onclick="rotateImg('page${img_index+1}', false)">逆时针</button>
|
||||
<button class="sszImg" onclick="rotateImg('page${img_index+1}', true)">顺时针</button>
|
||||
<button onclick="recoveryImg('page${img_index+1}')">恢复</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</#list>
|
||||
|
||||
</div>
|
||||
<#if "false" == switchDisabled>
|
||||
<img src="images/pdf.svg" width="48" height="48" style="position: fixed; cursor: pointer; top: 40%; right: 48px; z-index: 999;" alt="使用PDF预览" title="使用PDF预览" onclick="changePreviewType('pdf')"/>
|
||||
</#if>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
/*初始化水印*/
|
||||
initWaterMark();
|
||||
checkImgs();
|
||||
};
|
||||
window.onscroll = throttle(checkImgs);
|
||||
function changePreviewType(previewType) {
|
||||
var url = window.location.href;
|
||||
if (url.indexOf("officePreviewType=image") !== -1) {
|
||||
url = url.replace("officePreviewType=image", "officePreviewType="+previewType);
|
||||
} else {
|
||||
url = url + "&officePreviewType="+previewType;
|
||||
}
|
||||
if ('allImages' === previewType) {
|
||||
window.open(url)
|
||||
} else {
|
||||
window.location.href = url;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- 页码跳转输入框 -->
|
||||
<div id="pageJumpBox" style="position: fixed; top: 20px; right: 120px; background: white; padding: 10px; border: 1px solid #ddd; box-shadow: 0 2px 5px rgba(0,0,0,0.2); display: none; z-index: 1000;">
|
||||
<div style="margin-bottom: 5px; font-size: 14px;">跳转到第几页?</div>
|
||||
<input type="number" id="jumpPageInput" style="width: 60px; padding: 5px; border: 1px solid #ccc;" min="1" max="${imgUrls?size}" value="1">
|
||||
<button onclick="jumpToPage()" style="margin-left: 5px; padding: 5px 10px; background: #007bff; color: white; border: none; cursor: pointer;">跳转</button>
|
||||
<button onclick="hidePageJumpBox()" style="margin-left: 5px; padding: 5px 10px; background: #6c757d; color: white; border: none; cursor: pointer;">关闭</button>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
// 获取页码参数,默认为1
|
||||
var targetPage = ${page!1};
|
||||
var totalPages = ${imgUrls?size};
|
||||
|
||||
// 如果页码超出范围,设为第一页
|
||||
if (targetPage < 1 || targetPage > totalPages) {
|
||||
targetPage = 1;
|
||||
}
|
||||
|
||||
// 页面加载完成后跳转到指定页码
|
||||
window.onload = function() {
|
||||
// 延迟执行,确保DOM完全加载
|
||||
setTimeout(function() {
|
||||
// 滚动到指定页码
|
||||
scrollToPage(targetPage);
|
||||
|
||||
// 显示当前页码指示器
|
||||
showPageIndicator(targetPage);
|
||||
}, 100);
|
||||
|
||||
// 初始化懒加载
|
||||
initLazyLoad();
|
||||
|
||||
// 初始化水印
|
||||
initWaterMark();
|
||||
|
||||
// 为每个图片区域添加点击事件,显示当前页码
|
||||
var imgAreas = document.querySelectorAll('.img-area');
|
||||
imgAreas.forEach(function(area, index) {
|
||||
area.onclick = function() {
|
||||
showPageIndicator(index + 1);
|
||||
};
|
||||
});
|
||||
|
||||
// 添加键盘事件支持
|
||||
document.addEventListener('keydown', function(e) {
|
||||
var currentPage = getCurrentPage();
|
||||
|
||||
// 右箭头或空格键翻到下一页
|
||||
if (e.key === 'ArrowRight' || e.key === ' ' || e.key === 'PageDown') {
|
||||
e.preventDefault();
|
||||
var nextPage = Math.min(currentPage + 1, totalPages);
|
||||
scrollToPage(nextPage);
|
||||
}
|
||||
// 左箭头翻到上一页
|
||||
else if (e.key === 'ArrowLeft' || e.key === 'PageUp') {
|
||||
e.preventDefault();
|
||||
var prevPage = Math.max(currentPage - 1, 1);
|
||||
scrollToPage(prevPage);
|
||||
}
|
||||
// J键打开跳转框
|
||||
else if (e.key === 'j' || e.key === 'J') {
|
||||
e.preventDefault();
|
||||
showPageJumpBox();
|
||||
}
|
||||
// ESC键关闭跳转框
|
||||
else if (e.key === 'Escape') {
|
||||
hidePageJumpBox();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 滚动到指定页码
|
||||
function scrollToPage(pageNum) {
|
||||
var targetElement = document.getElementById('imgArea' + pageNum);
|
||||
if (targetElement) {
|
||||
// 确保图片已加载
|
||||
var targetImg = document.getElementById('page' + pageNum);
|
||||
if (targetImg && targetImg.src.includes('loading.gif')) {
|
||||
targetImg.src = targetImg.getAttribute('guoyu-src');
|
||||
}
|
||||
|
||||
// 滚动到目标位置
|
||||
targetElement.scrollIntoView({behavior: 'smooth', block: 'start'});
|
||||
|
||||
// 显示页码指示器
|
||||
showPageIndicator(pageNum);
|
||||
}
|
||||
}
|
||||
|
||||
// 显示页码指示器
|
||||
function showPageIndicator(pageNum) {
|
||||
var indicator = document.getElementById('pageIndicator');
|
||||
var currentPageSpan = document.getElementById('currentPageNum');
|
||||
|
||||
currentPageSpan.textContent = pageNum;
|
||||
indicator.style.display = 'block';
|
||||
|
||||
// 3秒后自动隐藏
|
||||
clearTimeout(window.indicatorTimeout);
|
||||
window.indicatorTimeout = setTimeout(function() {
|
||||
indicator.style.display = 'none';
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// 获取当前可见的页码
|
||||
function getCurrentPage() {
|
||||
var imgAreas = document.querySelectorAll('.img-area');
|
||||
var viewportHeight = window.innerHeight || document.documentElement.clientHeight;
|
||||
|
||||
for (var i = 0; i < imgAreas.length; i++) {
|
||||
var rect = imgAreas[i].getBoundingClientRect();
|
||||
// 如果元素顶部在视口中且高度超过一定阈值
|
||||
if (rect.top >= 0 && rect.top <= viewportHeight * 0.3) {
|
||||
return i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 显示页码跳转框
|
||||
function showPageJumpBox() {
|
||||
var jumpBox = document.getElementById('pageJumpBox');
|
||||
var currentPage = getCurrentPage();
|
||||
document.getElementById('jumpPageInput').value = currentPage;
|
||||
jumpBox.style.display = 'block';
|
||||
document.getElementById('jumpPageInput').focus();
|
||||
document.getElementById('jumpPageInput').select();
|
||||
}
|
||||
|
||||
// 隐藏页码跳转框
|
||||
function hidePageJumpBox() {
|
||||
document.getElementById('pageJumpBox').style.display = 'none';
|
||||
}
|
||||
|
||||
// 跳转到输入的页码
|
||||
function jumpToPage() {
|
||||
var pageInput = document.getElementById('jumpPageInput');
|
||||
var pageNum = parseInt(pageInput.value);
|
||||
|
||||
if (isNaN(pageNum) || pageNum < 1 || pageNum > totalPages) {
|
||||
alert('请输入有效的页码 (1-' + totalPages + ')');
|
||||
return;
|
||||
}
|
||||
|
||||
scrollToPage(pageNum);
|
||||
hidePageJumpBox();
|
||||
}
|
||||
|
||||
// 初始化懒加载
|
||||
function initLazyLoad() {
|
||||
var aImg = document.querySelectorAll('.my-photo');
|
||||
var len = aImg.length;
|
||||
var n = 0;
|
||||
|
||||
function lazyLoad() {
|
||||
var seeHeight = document.documentElement.clientHeight;
|
||||
var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
|
||||
|
||||
for (var i = n; i < len; i++) {
|
||||
var rect = aImg[i].getBoundingClientRect();
|
||||
var top = rect.top;
|
||||
|
||||
// 如果图片进入可视区域或附近区域
|
||||
if (top < seeHeight + scrollTop + 500) {
|
||||
var src = aImg[i].getAttribute('guoyu-src');
|
||||
if (src && aImg[i].src.includes('loading.gif')) {
|
||||
aImg[i].src = src;
|
||||
}
|
||||
n = i + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 初始加载当前页码及附近的图片
|
||||
setTimeout(function() {
|
||||
// 加载当前页码图片
|
||||
var currentImg = document.getElementById('page' + targetPage);
|
||||
if (currentImg && currentImg.src.includes('loading.gif')) {
|
||||
currentImg.src = currentImg.getAttribute('guoyu-src');
|
||||
}
|
||||
|
||||
// 加载前后几页图片
|
||||
for (var i = Math.max(1, targetPage - 2); i <= Math.min(totalPages, targetPage + 2); i++) {
|
||||
if (i === targetPage) continue;
|
||||
var img = document.getElementById('page' + i);
|
||||
if (img && img.src.includes('loading.gif')) {
|
||||
img.src = img.getAttribute('guoyu-src');
|
||||
}
|
||||
}
|
||||
}, 300);
|
||||
|
||||
window.onscroll = function() {
|
||||
lazyLoad();
|
||||
initWaterMark();
|
||||
};
|
||||
|
||||
// 初始加载一次
|
||||
lazyLoad();
|
||||
}
|
||||
|
||||
var aImg = document.querySelectorAll('.my-photo');
|
||||
var len = aImg.length;
|
||||
var n = 0;
|
||||
window.onscroll = function() {
|
||||
var seeHeight = document.documentElement.clientHeight-30;
|
||||
var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
|
||||
for (var i = n; i < len; i++) {
|
||||
var rect = aImg[i].getBoundingClientRect();
|
||||
var top = rect.top;
|
||||
if (top < seeHeight + scrollTop) {
|
||||
aImg[i].src = aImg[i].getAttribute('guoyu-src');
|
||||
n = i + 1;
|
||||
}
|
||||
}
|
||||
initWaterMark();
|
||||
};
|
||||
|
||||
function changePreviewType(previewType) {
|
||||
var url = window.location.href;
|
||||
if (url.indexOf("officePreviewType=image") !== -1) {
|
||||
url = url.replace("officePreviewType=image", "officePreviewType="+previewType);
|
||||
} else {
|
||||
url = url + "&officePreviewType="+previewType;
|
||||
}
|
||||
window.location.href = url;
|
||||
}
|
||||
|
||||
function rotateImg(imgId, isRotate) {
|
||||
var img = document.querySelector("#" + imgId);
|
||||
|
||||
if (img.classList.contains("imgT90")) {
|
||||
img.classList.remove("imgT90");
|
||||
if (isRotate) {
|
||||
img.classList.add("imgT180");
|
||||
}
|
||||
} else if (img.classList.contains("imgT-90")) {
|
||||
img.classList.remove("imgT-90");
|
||||
if (!isRotate) {
|
||||
img.classList.add("imgT-180");
|
||||
}
|
||||
} else if (img.classList.contains("imgT180")) {
|
||||
img.classList.remove("imgT180");
|
||||
if (isRotate) {
|
||||
img.classList.add("imgT270");
|
||||
} else {
|
||||
img.classList.add("imgT90");
|
||||
}
|
||||
} else if (img.classList.contains("imgT-180")) {
|
||||
img.classList.remove("imgT-180");
|
||||
if (isRotate) {
|
||||
img.classList.add("imgT-90");
|
||||
} else {
|
||||
img.classList.add("imgT-270");
|
||||
}
|
||||
} else if (img.classList.contains("imgT270")) {
|
||||
img.classList.remove("imgT270");
|
||||
if (!isRotate) {
|
||||
img.classList.add("imgT180");
|
||||
}
|
||||
} else if (img.classList.contains("imgT-270")) {
|
||||
img.classList.remove("imgT-270");
|
||||
if (isRotate) {
|
||||
img.classList.add("imgT-180");
|
||||
}
|
||||
} else {
|
||||
if (isRotate) {
|
||||
img.classList.add("imgT90");
|
||||
} else {
|
||||
img.classList.add("imgT-90");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function recoveryImg(imgId) {
|
||||
document.querySelector("#" + imgId).classList.remove("imgT90", "imgT180", "imgT270", "imgT-90", "imgT-180", "imgT-270");
|
||||
}
|
||||
|
||||
// 滚动监听更新页码指示器
|
||||
window.addEventListener('scroll', function() {
|
||||
var currentPage = getCurrentPage();
|
||||
showPageIndicator(currentPage);
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
@@ -22,7 +22,6 @@
|
||||
* 初始化水印
|
||||
*/
|
||||
function initWaterMark() {
|
||||
|
||||
let watermarkTxt = '${watermarkTxt}';
|
||||
if (watermarkTxt !== '') {
|
||||
watermark.init({
|
||||
@@ -44,6 +43,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 添加加载状态管理
|
||||
let isLoading = false;
|
||||
let loadingTask = null;
|
||||
|
||||
</script>
|
||||
<style>
|
||||
* {
|
||||
@@ -54,13 +57,93 @@
|
||||
html, body {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#loading-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
z-index: 9999;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
#loading-progress {
|
||||
width: 300px;
|
||||
height: 20px;
|
||||
background: #f0f0f0;
|
||||
border-radius: 10px;
|
||||
margin-top: 20px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#loading-bar {
|
||||
width: 0%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #4CAF50, #8BC34A);
|
||||
transition: width 0.3s ease;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border: 5px solid #f3f3f3;
|
||||
border-top: 5px solid #4CAF50;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
margin-top: 20px;
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
display: none;
|
||||
background: #ffebee;
|
||||
border: 1px solid #ffcdd2;
|
||||
border-radius: 4px;
|
||||
padding: 20px;
|
||||
margin: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
</style>
|
||||
<body>
|
||||
<!-- 添加加载遮罩层 -->
|
||||
<div id="loading-overlay">
|
||||
<div class="spinner"></div>
|
||||
<div class="loading-text">正在加载Excel文件...</div>
|
||||
<div id="loading-progress">
|
||||
<div id="loading-bar"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 错误提示 -->
|
||||
<div id="error-message" class="error-message">
|
||||
<h3>加载失败</h3>
|
||||
<p id="error-detail"></p>
|
||||
<button onclick="retryLoad()" style="margin-top: 10px; padding: 8px 16px;">重试</button>
|
||||
</div>
|
||||
|
||||
<div id="lucky-mask-demo" style="position: absolute;z-index: 1000000;left: 0px;top: 0px;bottom: 0px;right: 0px; background: rgba(255, 255, 255, 0.8); text-align: center;font-size: 40px;align-items:center;justify-content: center;display: none;">加载中</div>
|
||||
|
||||
<p style="text-align:center;">
|
||||
<div id="button-area">
|
||||
<div id="button-area" style="display: none;">
|
||||
<label><button onclick="tiaozhuan()">跳转HTML预览</button></label>
|
||||
<button id="confirm-button" onclick="print()">打印</button>
|
||||
</div>
|
||||
@@ -74,59 +157,218 @@
|
||||
test = test+'&officePreviewType=html';
|
||||
window.location.href=test;
|
||||
}
|
||||
|
||||
var url = '${finalUrl}';
|
||||
var baseUrl = '${baseUrl}'.endsWith('/') ? '${baseUrl}' : '${baseUrl}' + '/';
|
||||
if (!url.startsWith(baseUrl)) {
|
||||
url = baseUrl + 'getCorsFile?urlPath=' + encodeURIComponent(Base64.encode(url));
|
||||
}
|
||||
|
||||
let mask = document.getElementById("lucky-mask-demo");
|
||||
function loadText() {
|
||||
initWaterMark(); // 是否显示水印
|
||||
var value = url;
|
||||
var name = '${file.name}';
|
||||
if(value==""){
|
||||
return;
|
||||
let loadingOverlay = document.getElementById("loading-overlay");
|
||||
let loadingBar = document.getElementById("loading-bar");
|
||||
let errorMessage = document.getElementById("error-message");
|
||||
|
||||
// 更新加载进度
|
||||
function updateProgress(percent) {
|
||||
if (loadingBar) {
|
||||
loadingBar.style.width = percent + '%';
|
||||
}
|
||||
// mask.style.display = "flex";
|
||||
LuckyExcel.transformExcelToLuckyByUrl(value, name, function(exportJson, luckysheetfile){
|
||||
if(exportJson.sheets==null || exportJson.sheets.length==0){
|
||||
alert("读取excel文件内容失败!");
|
||||
}
|
||||
|
||||
// 显示错误信息
|
||||
function showError(message) {
|
||||
hideLoading();
|
||||
errorMessage.style.display = 'block';
|
||||
document.getElementById('error-detail').textContent = message;
|
||||
}
|
||||
|
||||
// 隐藏加载动画
|
||||
function hideLoading() {
|
||||
if (loadingOverlay) {
|
||||
loadingOverlay.style.opacity = '0';
|
||||
setTimeout(() => {
|
||||
loadingOverlay.style.display = 'none';
|
||||
document.getElementById('button-area').style.display = 'block';
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
|
||||
// 重试加载
|
||||
function retryLoad() {
|
||||
errorMessage.style.display = 'none';
|
||||
loadingOverlay.style.display = 'flex';
|
||||
loadingOverlay.style.opacity = '1';
|
||||
loadTextAsync();
|
||||
}
|
||||
|
||||
// 异步加载Excel文件
|
||||
async function loadTextAsync() {
|
||||
if (isLoading) return;
|
||||
|
||||
isLoading = true;
|
||||
updateProgress(10);
|
||||
|
||||
try {
|
||||
initWaterMark();
|
||||
|
||||
const value = url;
|
||||
const name = '${file.name}';
|
||||
|
||||
if (!value) {
|
||||
showError('文件URL为空');
|
||||
return;
|
||||
}
|
||||
mask.style.display = "none";
|
||||
window.luckysheet.destroy();
|
||||
window.luckysheet.create({
|
||||
container: 'luckysheet', //luckysheet is the container id
|
||||
lang: "zh",
|
||||
showtoolbarConfig:{
|
||||
image: true,
|
||||
print: true, //关闭打印按钮 启用也不能用 等以后看情况而定
|
||||
exportXlsx: true, //关闭导出按钮 启用也不能用 等以后看情况而定
|
||||
},
|
||||
allowCopy: true, // 是否允许拷贝
|
||||
showtoolbar: true, // 是否显示工具栏
|
||||
showinfobar: false, // 是否显示顶部信息栏
|
||||
// myFolderUrl: "/",//作用:左上角<返回按钮的链接
|
||||
showsheetbar: true, // 是否显示底部sheet页按钮
|
||||
showstatisticBar: true, // 是否显示底部计数栏
|
||||
sheetBottomConfig: true, // sheet页下方的添加行按钮和回到顶部按钮配置
|
||||
allowEdit: true, // 是否允许前台编辑
|
||||
enableAddRow: false, // 允许增加行
|
||||
enableAddCol: false, // 允许增加列
|
||||
userInfo: false, // 右上角的用户信息展示样式
|
||||
showRowBar: true, // 是否显示行号区域
|
||||
showColumnBar: false, // 是否显示列号区域
|
||||
sheetFormulaBar: false, // 是否显示公式栏
|
||||
enableAddBackTop: true,//返回头部按钮
|
||||
forceCalculation: false, //下面是导出插件 默认关闭
|
||||
data: exportJson.sheets,
|
||||
title: exportJson.info.name,
|
||||
userInfo: exportJson.info.name.creator,
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
updateProgress(30);
|
||||
|
||||
// 使用异步方式加载
|
||||
await new Promise(resolve => setTimeout(resolve, 100)); // 给UI更新一点时间
|
||||
|
||||
|
||||
|
||||
// 或者使用现有的同步方法,但放在setTimeout中避免阻塞
|
||||
await transformWithTimeout(value, name);
|
||||
|
||||
updateProgress(100);
|
||||
|
||||
// 延迟隐藏加载界面,让用户看到加载完成
|
||||
setTimeout(() => {
|
||||
hideLoading();
|
||||
isLoading = false;
|
||||
}, 500);
|
||||
|
||||
} catch (error) {
|
||||
console.error('加载Excel失败:', error);
|
||||
showError('加载失败: ' + error.message);
|
||||
isLoading = false;
|
||||
}
|
||||
}
|
||||
loadText();
|
||||
// 打印时,获取luckysheet指定区域html内容,拼接至div,隐藏luckysheet容器并显示打印区域html
|
||||
|
||||
// 使用setTimeout将同步任务拆分
|
||||
function transformWithTimeout(value, name) {
|
||||
return new Promise((resolve, reject) => {
|
||||
updateProgress(50);
|
||||
|
||||
// 将转换过程放在setTimeout中,避免阻塞主线程
|
||||
setTimeout(() => {
|
||||
try {
|
||||
LuckyExcel.transformExcelToLuckyByUrl(value, name, function(exportJson, luckysheetfile){
|
||||
if(exportJson.sheets==null || exportJson.sheets.length==0){
|
||||
reject(new Error("读取excel文件内容失败!"));
|
||||
return;
|
||||
}
|
||||
|
||||
updateProgress(80);
|
||||
|
||||
// 使用requestAnimationFrame来更新UI,避免阻塞
|
||||
requestAnimationFrame(() => {
|
||||
try {
|
||||
window.luckysheet.destroy();
|
||||
window.luckysheet.create({
|
||||
container: 'luckysheet',
|
||||
lang: "zh",
|
||||
showtoolbarConfig:{
|
||||
image: true,
|
||||
print: true,
|
||||
exportXlsx: true,
|
||||
},
|
||||
allowCopy: true,
|
||||
showtoolbar: true,
|
||||
showinfobar: false,
|
||||
showsheetbar: true,
|
||||
showstatisticBar: true,
|
||||
sheetBottomConfig: true,
|
||||
allowEdit: true,
|
||||
enableAddRow: false,
|
||||
enableAddCol: false,
|
||||
userInfo: false,
|
||||
showRowBar: true,
|
||||
showColumnBar: false,
|
||||
sheetFormulaBar: false,
|
||||
enableAddBackTop: true,
|
||||
forceCalculation: false,
|
||||
data: exportJson.sheets,
|
||||
title: exportJson.info.name,
|
||||
userInfo: exportJson.info.name.creator,
|
||||
// 添加加载完成的回调
|
||||
hook: {
|
||||
workbookCreateAfter: function() {
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
updateProgress(90);
|
||||
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
}, 100);
|
||||
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 初始化Luckysheet
|
||||
function initializeLuckysheet(exportJson) {
|
||||
if (!exportJson.sheets || exportJson.sheets.length === 0) {
|
||||
throw new Error("读取excel文件内容失败!");
|
||||
}
|
||||
|
||||
window.luckysheet.destroy();
|
||||
window.luckysheet.create({
|
||||
container: 'luckysheet',
|
||||
lang: "zh",
|
||||
showtoolbarConfig:{
|
||||
image: true,
|
||||
print: true,
|
||||
exportXlsx: true,
|
||||
},
|
||||
allowCopy: true,
|
||||
showtoolbar: true,
|
||||
showinfobar: false,
|
||||
showsheetbar: true,
|
||||
showstatisticBar: true,
|
||||
sheetBottomConfig: true,
|
||||
allowEdit: true,
|
||||
enableAddRow: false,
|
||||
enableAddCol: false,
|
||||
userInfo: false,
|
||||
showRowBar: true,
|
||||
showColumnBar: false,
|
||||
sheetFormulaBar: false,
|
||||
enableAddBackTop: true,
|
||||
forceCalculation: false,
|
||||
data: exportJson.sheets,
|
||||
title: exportJson.info.name,
|
||||
userInfo: exportJson.info.name.creator,
|
||||
});
|
||||
}
|
||||
|
||||
// 页面加载完成后开始异步加载
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// 延迟一点时间开始加载,确保DOM完全加载
|
||||
setTimeout(() => {
|
||||
loadTextAsync();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
// 添加取消加载的功能(按ESC键)
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape' && isLoading) {
|
||||
// 可以在这里添加取消加载的逻辑
|
||||
console.log('用户取消了加载');
|
||||
}
|
||||
});
|
||||
|
||||
// 打印时,获取luckysheet指定区域html内容
|
||||
function to_print() {
|
||||
const html = luckysheet.getRangeHtml();
|
||||
document.querySelector('#print-html').innerHTML = html;
|
||||
|
||||
@@ -21,9 +21,10 @@
|
||||
</body>
|
||||
|
||||
<script type="text/javascript">
|
||||
var url = '${finalUrl}';
|
||||
var url = '${finalUrl}';
|
||||
var kkagent = '${kkagent}';
|
||||
var baseUrl = '${baseUrl}'.endsWith('/') ? '${baseUrl}' : '${baseUrl}' + '/';
|
||||
if (!url.startsWith(baseUrl)) {
|
||||
if (kkagent === 'true' || !url.startsWith(baseUrl)) {
|
||||
url = baseUrl + 'getCorsFile?urlPath=' + encodeURIComponent(Base64.encode(url));
|
||||
}
|
||||
document.getElementsByTagName('iframe')[0].src = "${baseUrl}pdfjs/web/viewer.html?file=" + encodeURIComponent(url) + "&disablepresentationmode=${pdfPresentationModeDisable}&disableopenfile=${pdfOpenFileDisable}&disableprint=${pdfPrintDisable}&disabledownload=${pdfDownloadDisable}&disablebookmark=${pdfBookmarkDisable}&disableediting=${pdfDisableEditing}";
|
||||
|
||||
@@ -19,25 +19,25 @@
|
||||
|
||||
<ul id="image">
|
||||
<#list imgUrls as img>
|
||||
<#if img?contains("http://") || img?contains("https://")>
|
||||
<#assign img="${img}">
|
||||
<#else>
|
||||
<#assign img="${baseUrl}${img}">
|
||||
</#if>
|
||||
<li><img id="${img}" url="${img}" src="${img}" style="display: none"></li>
|
||||
<#if img?contains("http://") || img?contains("https://")|| img?contains("ftp://")|| img?contains("file://")>
|
||||
<#assign finalUrl="${img}">
|
||||
<#else>
|
||||
<#assign finalUrl="${baseUrl}${img}">
|
||||
</#if>
|
||||
<li><div src="${finalUrl}" style="display: none"></li>
|
||||
</#list>
|
||||
</ul>
|
||||
|
||||
<script>
|
||||
var viewer = new Viewer(document.getElementById('image'), {
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var viewer = new Viewer(document.getElementById('image'), {
|
||||
url: 'src',
|
||||
navbar: false,
|
||||
button: false,
|
||||
backdrop: false,
|
||||
loop : true
|
||||
});
|
||||
document.getElementById("${currentUrl}").click();
|
||||
|
||||
loop : true,
|
||||
});
|
||||
viewer.view(0); // 0 是图片的索引,如果你想点击第一张图片,索引为 0
|
||||
});
|
||||
/*初始化水印*/
|
||||
window.onload = function() {
|
||||
initWaterMark();
|
||||
|
||||
@@ -1,14 +1,103 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>${file.name}文件预览</title>
|
||||
<title><#if file.name??>${file.name}<#else>文件预览</#if></title>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
|
||||
<#include "*/commonHeader.ftl">
|
||||
<script src="js/jquery-3.6.1.min.js" type="text/javascript"></script>
|
||||
<script src="js/svg-pan-zoom.js"></script>
|
||||
<script src="js/base64.min.js"></script>
|
||||
<#if currentUrl?contains("http://") || currentUrl?contains("https://") || currentUrl?contains("ftp://")>
|
||||
<style>
|
||||
#container {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
#svg-container {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
#svg-container svg {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.controls {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
z-index: 1000;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.control-btn {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
background: #007bff;
|
||||
color: white;
|
||||
font-size: 18px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.control-btn:hover {
|
||||
background: #0056b3;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.control-btn:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.control-btn.reset {
|
||||
background: #6c757d;
|
||||
}
|
||||
|
||||
.control-btn.reset:hover {
|
||||
background: #545b62;
|
||||
}
|
||||
|
||||
.zoom-display {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
padding: 8px 16px;
|
||||
border-radius: 20px;
|
||||
font-size: 14px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.loading {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
font-size: 18px;
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
<#if currentUrl?contains("http://") || currentUrl?contains("https://") || currentUrl?contains("file://")|| currentUrl?contains("ftp://")>
|
||||
<#assign finalUrl="${currentUrl}">
|
||||
<#else>
|
||||
<#assign finalUrl="${baseUrl}${currentUrl}">
|
||||
@@ -16,38 +105,320 @@
|
||||
</head>
|
||||
<body>
|
||||
<div id="container">
|
||||
<div id="svg-container"></div>
|
||||
<div class="zoom-display">缩放: 100%</div>
|
||||
<div class="loading">正在加载SVG...</div>
|
||||
<div class="controls">
|
||||
<button class="control-btn" onclick="zoomIn()" title="放大">+</button>
|
||||
<button class="control-btn" onclick="zoomOut()" title="缩小">-</button>
|
||||
<button class="control-btn" onclick="rotateLeft()" title="向左旋转">↶</button>
|
||||
<button class="control-btn" onclick="rotateRight()" title="向右旋转">↷</button>
|
||||
<button class="control-btn reset" onclick="resetView()" title="重置视图">⟳</button>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
|
||||
<script type="text/javascript">
|
||||
// 初始化变量
|
||||
let svgElement = null;
|
||||
let svgContainer = document.getElementById('svg-container');
|
||||
let zoomLevel = 1;
|
||||
let rotationAngle = 0;
|
||||
let minZoom = 0.1;
|
||||
let maxZoom = 10;
|
||||
let zoomStep = 0.2;
|
||||
let isDragging = false;
|
||||
let startX, startY, startLeft, startTop;
|
||||
let panStartX, panStartY;
|
||||
var url = '${finalUrl}';
|
||||
var kkagent = '${kkagent}';
|
||||
var baseUrl = '${baseUrl}'.endsWith('/') ? '${baseUrl}' : '${baseUrl}' + '/';
|
||||
if (!url.startsWith(baseUrl)) {
|
||||
if (kkagent === 'true' || !url.startsWith(baseUrl)) {
|
||||
url = baseUrl + 'getCorsFile?urlPath=' + encodeURIComponent(Base64.encode(url));
|
||||
}
|
||||
|
||||
function createNewEmbed(src){
|
||||
var lastEventListener = null;
|
||||
var gaodu1 =$(document).height();
|
||||
var gaodu=gaodu1-5;
|
||||
var embed = document.createElement('embed');
|
||||
embed.setAttribute('style', 'width: 99%; height: '+gaodu+'px; border:1px solid black;');
|
||||
embed.setAttribute('type', 'image/svg+xml');
|
||||
embed.setAttribute('src', src);
|
||||
$('#container').html(embed);
|
||||
lastEventListener = function(){
|
||||
svgPanZoom(embed, {
|
||||
zoomEnabled: true,
|
||||
controlIconsEnabled: true
|
||||
});
|
||||
}
|
||||
embed.addEventListener('load', lastEventListener)
|
||||
return embed;
|
||||
// 加载并显示SVG
|
||||
function loadSVG() {
|
||||
if (!url) {
|
||||
showError('URL参数缺失');
|
||||
return;
|
||||
}
|
||||
createNewEmbed(url);
|
||||
/*初始化水印*/
|
||||
|
||||
fetch(url)
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error('网络响应不正常');
|
||||
}
|
||||
return response.text();
|
||||
})
|
||||
.then(svgText => {
|
||||
document.querySelector('.loading').style.display = 'none';
|
||||
svgContainer.innerHTML = svgText;
|
||||
svgElement = svgContainer.querySelector('svg');
|
||||
|
||||
if (svgElement) {
|
||||
// 设置初始属性
|
||||
svgElement.style.transformOrigin = 'center center';
|
||||
svgElement.style.width = '100%';
|
||||
svgElement.style.height = '100%';
|
||||
|
||||
// 添加拖拽功能
|
||||
setupDragAndDrop();
|
||||
|
||||
// 添加鼠标滚轮缩放
|
||||
setupWheelZoom();
|
||||
|
||||
// 添加键盘快捷键
|
||||
setupKeyboardShortcuts();
|
||||
|
||||
// 添加触摸事件支持
|
||||
setupTouchEvents();
|
||||
|
||||
// 初始更新显示
|
||||
updateDisplay();
|
||||
} else {
|
||||
showError('SVG解析失败');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('加载SVG失败:', error);
|
||||
showError('加载SVG文件失败: ' + error.message);
|
||||
});
|
||||
}
|
||||
|
||||
// 显示错误信息
|
||||
function showError(message) {
|
||||
document.querySelector('.loading').style.display = 'none';
|
||||
svgContainer.innerHTML = '<div style="color: red; text-align: center; padding: 50px; font-size: 16px;">' + message + '</div>';
|
||||
}
|
||||
|
||||
// 设置拖拽功能
|
||||
function setupDragAndDrop() {
|
||||
svgContainer.addEventListener('mousedown', startDrag);
|
||||
document.addEventListener('mousemove', drag);
|
||||
document.addEventListener('mouseup', stopDrag);
|
||||
}
|
||||
|
||||
// 设置触摸事件
|
||||
function setupTouchEvents() {
|
||||
svgContainer.addEventListener('touchstart', handleTouchStart, { passive: false });
|
||||
svgContainer.addEventListener('touchmove', handleTouchMove, { passive: false });
|
||||
svgContainer.addEventListener('touchend', handleTouchEnd);
|
||||
}
|
||||
|
||||
// 处理触摸开始
|
||||
function handleTouchStart(e) {
|
||||
if (e.touches.length === 1) {
|
||||
isDragging = true;
|
||||
panStartX = e.touches[0].clientX;
|
||||
panStartY = e.touches[0].clientY;
|
||||
startLeft = parseInt(svgContainer.style.left) || 0;
|
||||
startTop = parseInt(svgContainer.style.top) || 0;
|
||||
e.preventDefault();
|
||||
} else if (e.touches.length === 2) {
|
||||
// 双指缩放处理
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
// 处理触摸移动
|
||||
function handleTouchMove(e) {
|
||||
if (!isDragging || e.touches.length !== 1) return;
|
||||
|
||||
const dx = e.touches[0].clientX - panStartX;
|
||||
const dy = e.touches[0].clientY - panStartY;
|
||||
|
||||
svgContainer.style.left = (startLeft + dx) + 'px';
|
||||
svgContainer.style.top = (startTop + dy) + 'px';
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
// 处理触摸结束
|
||||
function handleTouchEnd(e) {
|
||||
isDragging = false;
|
||||
}
|
||||
|
||||
// 鼠标拖拽开始
|
||||
function startDrag(e) {
|
||||
isDragging = true;
|
||||
startX = e.clientX;
|
||||
startY = e.clientY;
|
||||
startLeft = parseInt(svgContainer.style.left) || 0;
|
||||
startTop = parseInt(svgContainer.style.top) || 0;
|
||||
svgContainer.style.cursor = 'grabbing';
|
||||
}
|
||||
|
||||
// 拖拽中
|
||||
function drag(e) {
|
||||
if (!isDragging) return;
|
||||
|
||||
const dx = e.clientX - startX;
|
||||
const dy = e.clientY - startY;
|
||||
|
||||
svgContainer.style.left = (startLeft + dx) + 'px';
|
||||
svgContainer.style.top = (startTop + dy) + 'px';
|
||||
}
|
||||
|
||||
// 停止拖拽
|
||||
function stopDrag() {
|
||||
isDragging = false;
|
||||
svgContainer.style.cursor = 'grab';
|
||||
}
|
||||
|
||||
// 设置鼠标滚轮缩放
|
||||
function setupWheelZoom() {
|
||||
svgContainer.addEventListener('wheel', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const rect = svgContainer.getBoundingClientRect();
|
||||
const mouseX = e.clientX - rect.left;
|
||||
const mouseY = e.clientY - rect.top;
|
||||
|
||||
const delta = e.deltaY > 0 ? -zoomStep : zoomStep;
|
||||
const newZoom = Math.min(maxZoom, Math.max(minZoom, zoomLevel + delta));
|
||||
|
||||
// 计算缩放中心点相对于容器的位置
|
||||
const containerX = mouseX - rect.width / 2;
|
||||
const containerY = mouseY - rect.height / 2;
|
||||
|
||||
// 更新缩放级别
|
||||
const zoomChange = newZoom / zoomLevel;
|
||||
zoomLevel = newZoom;
|
||||
|
||||
// 调整位置以保持鼠标点位置不变
|
||||
const currentLeft = parseInt(svgContainer.style.left) || 0;
|
||||
const currentTop = parseInt(svgContainer.style.top) || 0;
|
||||
|
||||
svgContainer.style.left = (currentLeft - containerX * (zoomChange - 1)) + 'px';
|
||||
svgContainer.style.top = (currentTop - containerY * (zoomChange - 1)) + 'px';
|
||||
|
||||
updateTransform();
|
||||
updateDisplay();
|
||||
});
|
||||
}
|
||||
|
||||
// 设置键盘快捷键
|
||||
function setupKeyboardShortcuts() {
|
||||
document.addEventListener('keydown', function(e) {
|
||||
// 避免在输入框中触发快捷键
|
||||
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
|
||||
|
||||
switch(e.key) {
|
||||
case '+':
|
||||
case '=':
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
e.preventDefault();
|
||||
zoomIn();
|
||||
}
|
||||
break;
|
||||
case '-':
|
||||
case '_':
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
e.preventDefault();
|
||||
zoomOut();
|
||||
}
|
||||
break;
|
||||
case '0':
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
e.preventDefault();
|
||||
resetView();
|
||||
}
|
||||
break;
|
||||
case '[':
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
e.preventDefault();
|
||||
rotateLeft();
|
||||
}
|
||||
break;
|
||||
case ']':
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
e.preventDefault();
|
||||
rotateRight();
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 放大
|
||||
function zoomIn() {
|
||||
zoomLevel = Math.min(maxZoom, zoomLevel + zoomStep);
|
||||
updateTransform();
|
||||
updateDisplay();
|
||||
}
|
||||
|
||||
// 缩小
|
||||
function zoomOut() {
|
||||
zoomLevel = Math.max(minZoom, zoomLevel - zoomStep);
|
||||
updateTransform();
|
||||
updateDisplay();
|
||||
}
|
||||
|
||||
// 向左旋转
|
||||
function rotateLeft() {
|
||||
rotationAngle -= 90;
|
||||
updateTransform();
|
||||
}
|
||||
|
||||
// 向右旋转
|
||||
function rotateRight() {
|
||||
rotationAngle += 90;
|
||||
updateTransform();
|
||||
}
|
||||
|
||||
// 重置视图
|
||||
function resetView() {
|
||||
zoomLevel = 1;
|
||||
rotationAngle = 0;
|
||||
svgContainer.style.left = '50%';
|
||||
svgContainer.style.top = '50%';
|
||||
updateTransform();
|
||||
updateDisplay();
|
||||
}
|
||||
|
||||
// 更新变换
|
||||
function updateTransform() {
|
||||
let transform = 'translate(-50%, -50%)';
|
||||
|
||||
// 先应用缩放
|
||||
if (zoomLevel !== 1) {
|
||||
transform += ' scale(' + zoomLevel + ')';
|
||||
}
|
||||
|
||||
// 再应用旋转
|
||||
if (rotationAngle !== 0) {
|
||||
transform += ' rotate(' + rotationAngle + 'deg)';
|
||||
}
|
||||
|
||||
svgContainer.style.transform = transform;
|
||||
}
|
||||
|
||||
// 更新显示
|
||||
function updateDisplay() {
|
||||
var zoomDisplay = document.querySelector('.zoom-display');
|
||||
if (zoomDisplay) {
|
||||
var displayText = '缩放: ' + Math.round(zoomLevel * 100) + '%';
|
||||
if (rotationAngle !== 0) {
|
||||
// 将角度规范到0-360度
|
||||
var normalizedAngle = ((rotationAngle % 360) + 360) % 360;
|
||||
displayText += ' | 旋转: ' + normalizedAngle + '°';
|
||||
}
|
||||
zoomDisplay.textContent = displayText;
|
||||
}
|
||||
}
|
||||
|
||||
// 页面加载完成后初始化
|
||||
window.onload = function () {
|
||||
initWaterMark();
|
||||
// 设置初始光标
|
||||
svgContainer.style.cursor = 'grab';
|
||||
|
||||
// 加载SVG
|
||||
loadSVG();
|
||||
|
||||
// 如果有水印初始化函数,调用它
|
||||
if (typeof initWaterMark === 'function') {
|
||||
initWaterMark();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
@@ -4,7 +4,7 @@
|
||||
<meta charset="utf-8"/>
|
||||
<title>Tiff 图片预览</title>
|
||||
<#include "*/commonHeader.ftl">
|
||||
<link rel="stylesheet" href="css/viewer.min.css">
|
||||
<link rel="stylesheet" href="css/officePicture.css"/>
|
||||
<script src="js/UTIF.js"></script>
|
||||
<script src="js/base64.min.js" type="text/javascript"></script>
|
||||
<#if currentUrl?contains("http://") || currentUrl?contains("https://") || currentUrl?contains("file://")|| currentUrl?contains("ftp://")>
|
||||
@@ -13,13 +13,6 @@
|
||||
<#assign finalUrl="${baseUrl}${currentUrl}">
|
||||
</#if>
|
||||
</head>
|
||||
<style type="text/css">
|
||||
body{ text-align:center}
|
||||
img{max-width: 100%;
|
||||
margin:0 auto;
|
||||
border: 2px solid #ddd;
|
||||
}
|
||||
</style>
|
||||
<body>
|
||||
<#if "false" == pdfDownloadDisable>
|
||||
<!--endprint-->
|
||||
@@ -38,8 +31,9 @@
|
||||
return reg.test(this);
|
||||
}
|
||||
var url = '${finalUrl}';
|
||||
var baseUrl = '${baseUrl}'.endsWithh('/') ? '${baseUrl}' : '${baseUrl}' + '/';
|
||||
if (!url.startsWithh(baseUrl)) {
|
||||
var kkagent = '${kkagent}';
|
||||
var baseUrl = '${baseUrl}'.endsWith('/') ? '${baseUrl}' : '${baseUrl}' + '/';
|
||||
if (kkagent === 'true' || !url.startsWith(baseUrl)) {
|
||||
url = baseUrl + 'getCorsFile?urlPath=' + encodeURIComponent(Base64.encode(url));
|
||||
}
|
||||
var myp = document.getElementById('tiff');
|
||||
@@ -59,11 +53,17 @@
|
||||
} catch(e){
|
||||
if (e.message.indexOf("CanvasRenderingContext2D"))
|
||||
{
|
||||
var imgObjj = new Image();
|
||||
imgObjj.src = url;
|
||||
myp.appendChild(imgObjj);
|
||||
console.log("错误:" + e);
|
||||
return;
|
||||
var html = "";
|
||||
html += "<div class=\"img-area\">";
|
||||
html += "<div class=\"image-container\">";
|
||||
html += '<img class="my-photo" id="page1" src="'+url+'">';
|
||||
html += "<div class=\"button-container\">";
|
||||
html += "<button onclick=\"rotateImg('page1', false)\">旋转</button>";
|
||||
html += "</div>";
|
||||
html += "</div>";
|
||||
html += "</div>";
|
||||
myp.innerHTML = html;
|
||||
return;
|
||||
}
|
||||
console.log("错误:" + e);
|
||||
var html = "";
|
||||
@@ -109,24 +109,42 @@ html += " <p>有任何疑问,请加入kk开源社区知识星球咨询:<a
|
||||
html += "</div>";
|
||||
html += "</body>";
|
||||
html += "</html>";
|
||||
document.write(html);
|
||||
document.close();
|
||||
return;
|
||||
document.write(html);
|
||||
document.close();
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < rgba.length; i++) {
|
||||
imageData.data[i] = rgba[i];
|
||||
}
|
||||
ctx.putImageData(imageData, 0, 0);
|
||||
const imgObj = document.createElement('img');
|
||||
// imgObj.id = 'page${img_index+1}';
|
||||
// imgObj.className = "my-photo";
|
||||
imgObj.src = canvas.toDataURL('image/png');
|
||||
if (++p < pages.length) {
|
||||
imgObj.onload = loadOne;
|
||||
}
|
||||
myp.appendChild(imgObj);
|
||||
console.log(p);
|
||||
|
||||
var html = "";
|
||||
html += "<div class=\"img-area\">";
|
||||
html += "<div class=\"image-container\">";
|
||||
html += '<img class="my-photo" id="page'+p+'" src="'+canvas.toDataURL('image/png')+'">';
|
||||
html += " <div class=\"button-container\">";
|
||||
html += "<button class=\"nszImg\">"+p+"/"+pages.length+"页</button>";
|
||||
html += "<button class=\"nszImg\" onclick=\"rotateImg('page"+p+"', false)\">逆时针</button>";
|
||||
html += "<button class=\"sszImg\" onclick=\"rotateImg('page"+p+"', true)\">顺时针</button>";
|
||||
html += "<button class=\"sszImg\" onclick=\"recoveryImg('page"+p+"', true)\">恢复</button>";
|
||||
html += "</div>";
|
||||
html += "</div>";
|
||||
html += "</div>";
|
||||
const child = document.createElement('div');
|
||||
child.innerHTML = html;
|
||||
myp.appendChild(child);
|
||||
}
|
||||
|
||||
function imgLoaded(e) {
|
||||
resp = e.target.response;
|
||||
resp = e;
|
||||
pages = UTIF.decode(resp);
|
||||
p = 0;
|
||||
loadOne();
|
||||
@@ -134,8 +152,62 @@ html += "</html>";
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', url);
|
||||
xhr.responseType = 'arraybuffer';
|
||||
xhr.onload = imgLoaded;
|
||||
xhr.onload = function() {
|
||||
if (xhr.status === 200) {
|
||||
imgLoaded(xhr.response);
|
||||
} else {
|
||||
console.log(`Error ${xhr.status}: ${xhr.statusText}`)
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
function rotateImg(imgId, isRotate) {
|
||||
var img = document.querySelector("#" + imgId);
|
||||
|
||||
if (img.classList.contains("imgT90")) {
|
||||
img.classList.remove("imgT90");
|
||||
if (isRotate) {
|
||||
img.classList.add("imgT180");
|
||||
}
|
||||
} else if (img.classList.contains("imgT-90")) {
|
||||
img.classList.remove("imgT-90");
|
||||
if (!isRotate) {
|
||||
img.classList.add("imgT-180");
|
||||
}
|
||||
} else if (img.classList.contains("imgT180")) {
|
||||
img.classList.remove("imgT180");
|
||||
if (isRotate) {
|
||||
img.classList.add("imgT270");
|
||||
} else {
|
||||
img.classList.add("imgT90");
|
||||
}
|
||||
} else if (img.classList.contains("imgT-180")) {
|
||||
img.classList.remove("imgT-180");
|
||||
if (isRotate) {
|
||||
img.classList.add("imgT-90");
|
||||
} else {
|
||||
img.classList.add("imgT-270");
|
||||
}
|
||||
} else if (img.classList.contains("imgT270")) {
|
||||
img.classList.remove("imgT270");
|
||||
if (!isRotate) {
|
||||
img.classList.add("imgT180");
|
||||
}
|
||||
} else if (img.classList.contains("imgT-270")) {
|
||||
img.classList.remove("imgT-270");
|
||||
if (isRotate) {
|
||||
img.classList.add("imgT-180");
|
||||
}
|
||||
} else {
|
||||
if (isRotate) {
|
||||
img.classList.add("imgT90");
|
||||
} else {
|
||||
img.classList.add("imgT-90");
|
||||
}
|
||||
}
|
||||
}
|
||||
function recoveryImg(imgId) {
|
||||
document.querySelector("#" + imgId).classList.remove("imgT90", "imgT180", "imgT270", "imgT-90", "imgT-180", "imgT-270")
|
||||
}
|
||||
|
||||
/*初始化水印*/
|
||||
window.onload = function () {
|
||||
|
||||
@@ -12,286 +12,89 @@
|
||||
</head>
|
||||
<body>
|
||||
<input hidden id="textData" value="${textData}"/>
|
||||
<#if "${file.suffix?html}" == "txt" || "${file.suffix?html}" == "log" || "${file.suffix?html}" == "TXT" || "${file.suffix?html}" == "LOG">
|
||||
<style type="text/css">
|
||||
DIV.black{line-height:25px;PADDING-RIGHT:1px;PADDING-LEFT:1px;FONT-SIZE:100%;MARGIN:1px;COLOR:#909090;BACKGROUND-COLOR:#000;TEXT-ALIGN:left}
|
||||
DIV.black A{BORDER-RIGHT:#909090 1px solid;PADDING-RIGHT:5px;BACKGROUND-POSITION:50% bottom;BORDER-TOP:#909090 1px solid;PADDING-LEFT:5px;BACKGROUND-IMAGE:url();PADDING-BOTTOM:2px;BORDER-LEFT:#909090 1px solid;COLOR:#fff;MARGIN-RIGHT:3px;PADDING-TOP:2px;BORDER-BOTTOM:#909090 1px solid;TEXT-DECORATION:none}
|
||||
DIV.black A:hover{BORDER-RIGHT:#f0f0f0 1px solid;BORDER-TOP:#f0f0f0 1px solid;BACKGROUND-IMAGE:BORDER-LEFT:#f0f0f0 1px solid;COLOR:#ffffff;BORDER-BOTTOM:#f0f0f0 1px solid;BACKGROUND-COLOR:#404040}
|
||||
DIV.black A:active{BORDER-RIGHT:#f0f0f0 1px solid;BORDER-TOP:#f0f0f0 1px solid;BACKGROUND-IMAGE:BORDER-LEFT:#f0f0f0 1px solid;COLOR:#ffffff;BORDER-BOTTOM:#f0f0f0 1px solid;BACKGROUND-COLOR:#404040}
|
||||
.divContent{color:#fff;font-size:30px;
|
||||
line-height:30px;
|
||||
font-family:“SimHei”;
|
||||
text-indent:2em;padding-bottom:10px;white-space:pre-wrap;white-space:-moz-pre-wrap;white-space:-pre-wrap;white-space:-o-pre-wrap;word-wrap:break-word;background-color:#000}
|
||||
input{
|
||||
color:#ffffff;
|
||||
background-color:#000000
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
<div class="container">
|
||||
<#if "${file.suffix?html}" == "htm" || "${file.suffix?html}" == "html" || "${file.suffix?html}" == "shtml" >
|
||||
<div class="container">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title">
|
||||
<a data-toggle="collapse" data-parent="#accordion" href="#collapseOne">
|
||||
${file.name}
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title"> <strong><font color="red"><input class="GLOkBtn" type="button" value="运行html" onclick="loadXmlData();" /></font></strong>
|
||||
<a data-toggle="collapse" data-parent="#accordion" onclick="loadText();">
|
||||
${file.name}
|
||||
</a>
|
||||
</h4>
|
||||
</h4>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div id="divPagenation" class="black" >
|
||||
|
||||
</div>
|
||||
<div id="divContent" class="panel-body">
|
||||
</div>
|
||||
<div id="text"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
<script>
|
||||
// 将Freemarker的布尔值传递给JavaScript
|
||||
var scriptjs = ${scriptjs?c}; // ?c 将布尔值转换为字符串true/false
|
||||
|
||||
/**
|
||||
*加载普通文本
|
||||
*/
|
||||
function loadText() {
|
||||
var base64data = $("#textData").val()
|
||||
var s = Base64.decode(base64data);
|
||||
var imgReg = /(<img\s+src='\S+'\s*(\/)?>)/gi;
|
||||
matchContent = s.match(imgReg);
|
||||
imgContent = s;
|
||||
if(imgReg.test(s))
|
||||
{
|
||||
//将img标签替换为❈
|
||||
imgContent = s.replace(imgReg,"❈");
|
||||
}
|
||||
var Length= 20000;
|
||||
// 封装DHTMLpagenation
|
||||
function DHTMLpagenation(content)
|
||||
{
|
||||
this.content=content; // 内容
|
||||
this.contentLength=imgContent.length; // 内容长度
|
||||
this.pageSizeCount; // 总页数
|
||||
this.perpageLength= Length; //default perpage byte length.
|
||||
this.currentPage=1; // 起始页为第1页
|
||||
this.regularExp=/\d+/; // 建立正则表达式,匹配数字型字符串。
|
||||
this.divDisplayContent;
|
||||
this.contentStyle=null;
|
||||
this.strDisplayContent="";
|
||||
this.divDisplayPagenation;
|
||||
this.strDisplayPagenation="";
|
||||
|
||||
// 把第二个参数赋给perpageLength;
|
||||
arguments.length==2 ? perpageLength = arguments[1] : '';
|
||||
|
||||
try {
|
||||
//创建要显示的DIV
|
||||
divExecuteTime=document.createElement("DIV");
|
||||
document.body.appendChild(divExecuteTime);
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
}
|
||||
|
||||
// 得到divPagenation容器。
|
||||
if(document.getElementById("divPagenation"))
|
||||
{
|
||||
divDisplayPagenation=document.getElementById("divPagenation");
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
//创建分页信息
|
||||
divDisplayPagenation=document.createElement("DIV");
|
||||
divDisplayPagenation.id="divPagenation";
|
||||
document.body.appendChild(divDisplayPagenation);
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 得到divContent容器
|
||||
if(document.getElementById("divContent"))
|
||||
{
|
||||
divDisplayContent=document.getElementById("divContent");
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
//创建每页显示内容的消息的DIV
|
||||
divDisplayContent=document.createElement("DIV");
|
||||
divDisplayContent.id="divContent";
|
||||
document.body.appendChild(divDisplayContent);
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
DHTMLpagenation.initialize();
|
||||
return this;
|
||||
|
||||
};
|
||||
|
||||
//初始化分页;
|
||||
//包括把加入CSS,检查是否需要分页
|
||||
DHTMLpagenation.initialize=function()
|
||||
{
|
||||
divDisplayContent.className= contentStyle != null ? contentStyle : "divContent";
|
||||
|
||||
if(contentLength<=perpageLength)
|
||||
{
|
||||
strDisplayContent=content;
|
||||
divDisplayContent.innerHTML=strDisplayContent;
|
||||
return null;
|
||||
}
|
||||
|
||||
pageSizeCount=Math.ceil((contentLength/perpageLength));
|
||||
|
||||
DHTMLpagenation.goto(currentPage);
|
||||
|
||||
DHTMLpagenation.displayContent();
|
||||
};
|
||||
|
||||
//显示分页栏
|
||||
DHTMLpagenation.displayPage=function()
|
||||
{
|
||||
strDisplayPagenation="";
|
||||
if(currentPage && currentPage !=1)
|
||||
{
|
||||
|
||||
strDisplayPagenation+='<button onclick="DHTMLpagenation.previous()">上一页</button>';
|
||||
}
|
||||
else
|
||||
{
|
||||
strDisplayPagenation+="上一页 ";
|
||||
}
|
||||
|
||||
for(var i=1;i<=pageSizeCount;i++)
|
||||
{
|
||||
if(i!=currentPage)
|
||||
{
|
||||
|
||||
strDisplayPagenation+='<button onclick="DHTMLpagenation.goto('+i+');">'+i+'</button>';
|
||||
}
|
||||
else
|
||||
{
|
||||
strDisplayPagenation+=i+" ";
|
||||
}
|
||||
}
|
||||
|
||||
if(currentPage && currentPage!=pageSizeCount)
|
||||
{
|
||||
strDisplayPagenation+='<button onclick="DHTMLpagenation.next()">下一页</button>';
|
||||
}
|
||||
else
|
||||
{
|
||||
strDisplayPagenation+="下一页 ";
|
||||
}
|
||||
strDisplayPagenation+="共 " + pageSizeCount + " 页。<br>每页" + perpageLength + " 字符,调整字符数:<input type='text' value='"+perpageLength+"' id='ctlPerpageLength' /><input type='button' value='确定' onclick='DHTMLpagenation.change()' />";
|
||||
divDisplayPagenation.innerHTML=strDisplayPagenation;
|
||||
};
|
||||
|
||||
//上一页
|
||||
DHTMLpagenation.previous=function()
|
||||
{
|
||||
DHTMLpagenation.goto(currentPage-1);
|
||||
};
|
||||
|
||||
//下一页
|
||||
DHTMLpagenation.next=function()
|
||||
{
|
||||
|
||||
DHTMLpagenation.goto(currentPage+1);
|
||||
};
|
||||
|
||||
//跳转至某一页
|
||||
DHTMLpagenation.goto=function(iCurrentPage)
|
||||
{
|
||||
if(regularExp.test(iCurrentPage))
|
||||
{
|
||||
currentPage=iCurrentPage;
|
||||
|
||||
var tempContent = "";
|
||||
//获取当前的内容 里面包含 ❈
|
||||
var currentContent = imgContent.substr((currentPage-1)*perpageLength,perpageLength);
|
||||
tempContent = currentContent;
|
||||
//当前页是否有 ❈ 获取最后一个 ❈ 的位置
|
||||
var indexOf = currentContent.indexOf("❈");
|
||||
if(indexOf >= 0)
|
||||
{
|
||||
//获取从开始位置到当前页位置的内容
|
||||
var beginToEndContent = imgContent.substr(0,currentPage*perpageLength);
|
||||
|
||||
//获取开始到当前页位置的内容 中的 * 的最后的下标
|
||||
var reCount = beginToEndContent.split("❈").length - 1;
|
||||
|
||||
var contentArray = currentContent.split("❈");
|
||||
|
||||
tempContent = replaceStr(contentArray,reCount,matchContent);
|
||||
|
||||
}
|
||||
|
||||
strDisplayContent=tempContent;
|
||||
}
|
||||
else
|
||||
{
|
||||
alert("页面参数错误");
|
||||
}
|
||||
DHTMLpagenation.displayPage();
|
||||
DHTMLpagenation.displayContent();
|
||||
};
|
||||
//显示当前页内容
|
||||
DHTMLpagenation.displayContent=function()
|
||||
{
|
||||
divDisplayContent.innerHTML=strDisplayContent;
|
||||
};
|
||||
|
||||
//改变每页的字节数
|
||||
DHTMLpagenation.change=function()
|
||||
{
|
||||
|
||||
var iPerpageLength = document.getElementById("ctlPerpageLength").value;
|
||||
if(regularExp.test(iPerpageLength))
|
||||
{
|
||||
|
||||
DHTMLpagenation(s,iPerpageLength);
|
||||
}
|
||||
else
|
||||
{
|
||||
alert("请输入数字");
|
||||
}
|
||||
};
|
||||
|
||||
/* currentArray:当前页以 * 分割后的数组
|
||||
replaceCount:从开始内容到当前页的内容 * 的个数
|
||||
matchArray : img标签的匹配的内容
|
||||
*/
|
||||
function replaceStr(currentArray,replaceCount,matchArray)
|
||||
{
|
||||
var result = "";
|
||||
for(var i=currentArray.length -1,j = replaceCount-1 ;i>=1; i--)
|
||||
{
|
||||
|
||||
var temp = (matchArray[j] + currentArray[i]);
|
||||
|
||||
result = temp + result;
|
||||
|
||||
j--;
|
||||
}
|
||||
|
||||
result = currentArray[0] + result ;
|
||||
|
||||
return result;
|
||||
var div = document.getElementById("text");
|
||||
div.innerHTML = ""; //
|
||||
var textData = Base64.decode(base64data);
|
||||
textData = htmlttt(textData,1);
|
||||
var textPreData = "<xmp style='background-color: #FFFFFF;overflow-y: scroll;border:none'>" + textData + "</xmp>";
|
||||
$("#text").append(textPreData);
|
||||
}
|
||||
|
||||
function htmlttt (str,txt){
|
||||
var s = "";
|
||||
if(str.length == 0) return "";
|
||||
s = str.replace(/&/gi,"&");
|
||||
s = s.replace(/</gi,"<");
|
||||
s = s.replace(/>/gi,">");
|
||||
s = s.replace(/ /gi," ");
|
||||
s = s.replace(/'/gi,"\'");
|
||||
s = s.replace(/"/gi,"\"");
|
||||
s = s.replace(/javascript/g,"javascript ");
|
||||
if (txt === 2){
|
||||
s = s.replace(/<script/gi, "<script ");
|
||||
s = s.replace(/javascript/g,"javascript ");
|
||||
s = s.replace(/<\/script/gi, "</script ");
|
||||
s = s.replace(/<iframe/gi, "<iframe ");
|
||||
s = s.replace(/<\/iframe/gi, "</iframe ");
|
||||
s = s.replace(/confirm/gi, "c&onfirm");
|
||||
s = s.replace(/alert/gi, "a&lert");
|
||||
s = s.replace(/eval/gi, "e&val");
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
*加载运行
|
||||
*/
|
||||
function loadXmlData() {
|
||||
var base64data = $("#textData").val();
|
||||
var textData = Base64.decode(base64data);
|
||||
|
||||
// 直接使用JavaScript变量进行判断
|
||||
if (scriptjs) {
|
||||
textData = htmlttt(textData, 1);
|
||||
} else {
|
||||
textData = htmlttt(textData, 2);
|
||||
}
|
||||
|
||||
DHTMLpagenation(s,Length);
|
||||
|
||||
/**
|
||||
|
||||
$('#text').html(textData);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化
|
||||
*/
|
||||
window.onload = function () {
|
||||
initWaterMark();
|
||||
loadText();
|
||||
}
|
||||
</script>
|
||||
<#else/>
|
||||
<#else/>
|
||||
<link rel="stylesheet" href="highlight/highlight.css">
|
||||
<script src="js/fenye.js" type="text/javascript"></script>
|
||||
<div class="container">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
@@ -301,31 +104,30 @@ background-color:#000000
|
||||
</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div id="text"></div>
|
||||
<div id="divPagenation" class="black" >
|
||||
</div>
|
||||
<div id="divContent" class="panel-body">
|
||||
</div>
|
||||
<div id="divPagenationx" class="black" >
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
||||
<script type="text/javascript">
|
||||
var base64data = $("#textData").val()
|
||||
var s = Base64.decode(base64data);
|
||||
var kkkeyword = '${highlightall}';
|
||||
var Length = 20000;
|
||||
var page = '${page}';
|
||||
var txt = "txt";
|
||||
DHTMLpagenation(s, kkkeyword, Length, page, txt);
|
||||
|
||||
/**
|
||||
*加载普通文本
|
||||
*/
|
||||
function loadText() {
|
||||
var base64data = $("#textData").val()
|
||||
var textData = Base64.decode(base64data);
|
||||
var textPreData = "<xmp style='background-color: #FFFFFF;overflow-y: scroll;border:none'>" + textData + "</xmp>";
|
||||
$("#text").append(textPreData);
|
||||
}
|
||||
/**
|
||||
* 初始化
|
||||
*/
|
||||
window.onload = function () {
|
||||
initWaterMark();
|
||||
loadText();
|
||||
}
|
||||
</script>
|
||||
</#if>
|
||||
</#if>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
@@ -20,10 +20,11 @@
|
||||
|
||||
<script src="xmind/xmind.js"></script>
|
||||
<script type="text/javascript">
|
||||
var url = '${finalUrl}';
|
||||
var url = '${finalUrl}';
|
||||
var kkagent = '${kkagent}';
|
||||
var baseUrl = '${baseUrl}'.endsWith('/') ? '${baseUrl}' : '${baseUrl}' + '/';
|
||||
if (!url.startsWith(baseUrl)) {
|
||||
url = baseUrl + 'getCorsFile?urlPath=' + encodeURIComponent(Base64.encode(url));
|
||||
if (kkagent === 'true' || !url.startsWith(baseUrl)) {
|
||||
url = baseUrl + 'getCorsFile?urlPath=' + encodeURIComponent(Base64.encode(url));
|
||||
}
|
||||
const init = async () => {
|
||||
var windowWidth = document.documentElement.clientWidth || document.body.clientWidth;
|
||||
|
||||
Reference in New Issue
Block a user