Compare commits

...

14 Commits
return ... io

37 changed files with 84021 additions and 409 deletions

2
.gitignore vendored
View File

@ -17,7 +17,6 @@ target/
### NetBeans ###
nbproject/private/
build/
nbbuild/
dist/
nbdist/
@ -29,4 +28,3 @@ nbdist/
server/src/main/cache/
server/src/main/file/
server/src/main/log

View File

@ -28,5 +28,5 @@ ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
ENV PATH $PATH:$JAVA_HOME/bin
ENV LANG zh_CN.UTF-8
ENV LC_ALL zh_CN.UTF-8
ENV KKFILEVIEW_BIN_FOLDER /opt/kkFileView-2.2.1/bin
ENTRYPOINT ["java","-Dfile.encoding=UTF-8","-Dsun.java2d.cmm=sun.java2d.cmm.kcms.KcmsServiceProvider","-Dspring.config.location=/opt/kkFileView-2.2.1/config/application.properties","-jar","/opt/kkFileView-2.2.1/bin/kkFileView-2.2.1.jar"]
ENV KKFILEVIEW_BIN_FOLDER /opt/kkFileView-3.3.0/bin
ENTRYPOINT ["java","-Dfile.encoding=UTF-8","-Dsun.java2d.cmm=sun.java2d.cmm.kcms.KcmsServiceProvider","-Dspring.config.location=/opt/kkFileView-3.3.0/config/application.properties","-jar","/opt/kkFileView-3.3.0/bin/kkFileView-3.3.0.jar"]

View File

@ -109,6 +109,26 @@ pdf预览模式预览效果如下
### 历史更新记录
> 2020年12月27日
2020年年终大版本更新架构全面设计代码全面重构代码质量全面提升二次开发更便捷欢迎拉源码品鉴提issuepr共同建设
1. 架构模块调整,大量的代码重构代码质量提升N个等级欢迎品鉴
2. 增强XML文件预览效果新增XML文档数结构预览
3. 新增markdown文件预览支持预览支持md渲染和源文本切换支持
4. 切换底层web server为jetty解决这个issuehttps://github.com/kekingcn/kkFileView/issues/168
5. 引入cpdetector解决文件编码识别问题
6. url采用base64+urlencode双编码彻底解决各种奇葩文件名预览问题
7. 新增配置项office.preview.switch.disabled控制offic文件预览切换开关
8. 优化文本类型文件预览逻辑采用Base64传输内容避免预览时再次请求文件内容
9. office预览图片模式禁用图片放大效果达到图片和pdf预览效果一致的体验
10. 直接代码静态设置pdfbox兼容低版本jdk在IDEA中运行也不会有警告提示
11. 移除guavahutool等非必须的工具包减少代码体积
12. Office组件加载异步化提速应用启动速度最快到5秒内
13. 合理设置预览消费队列的线程数
14. 修复压缩包里文件再次预览失败的bug
15. 修复图片预览的bug
> 2020年05月20日
1. 新增支持全局水印并支持通过参数动态改变水印内容
2. 新增支持CAD文件预览

View File

@ -5,7 +5,7 @@
<groupId>cn.keking</groupId>
<artifactId>filepreview</artifactId>
<version>2.2.1</version>
<version>3.3.0</version>
<modules>
<module>office-plugin</module>
<module>server</module>

View File

@ -12,7 +12,7 @@
<groupId>cn.keking</groupId>
<artifactId>kkFileView</artifactId>
<version>2.2.1</version>
<version>3.3.0</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@ -201,6 +201,12 @@
<scope>system</scope>
<systemPath>${basedir}/lib/cpdetector-1.04.jar</systemPath>
</dependency>
<!-- url 规范化 -->
<dependency>
<groupId>io.mola.galimatias</groupId>
<artifactId>galimatias</artifactId>
<version>0.2.1</version>
</dependency>
</dependencies>
<build>
<resources>

View File

@ -6,4 +6,4 @@ echo Starting kkFileView...
echo Please check log file in ../log/kkFileView.log for more information
echo You can get help in our official homesite: https://kkFileView.keking.cn
echo If this project is helpful to you, please star it on https://gitee.com/kekingcn/file-online-preview/stargazers
java -Dsun.java2d.cmm=sun.java2d.cmm.kcms.KcmsServiceProvider -Dspring.config.location=..\config\application.properties -jar kkFileView-2.2.1.jar -> ..\log\kkFileView.log
java -Dsun.java2d.cmm=sun.java2d.cmm.kcms.KcmsServiceProvider -Dspring.config.location=..\config\application.properties -jar kkFileView-3.3.0.jar -> ..\log\kkFileView.log

View File

@ -29,4 +29,4 @@ echo "Starting kkFileView..."
echo "Please execute ./showlog.sh to check log for more information"
echo "You can get help in our official homesite: https://kkFileView.keking.cn"
echo "If this project is helpful to you, please star it on https://gitee.com/kekingcn/file-online-preview/stargazers"
nohup java -Dfile.encoding=UTF-8 -Dsun.java2d.cmm=sun.java2d.cmm.kcms.KcmsServiceProvider -Dspring.config.location=../config/application.properties -jar kkFileView-2.2.1.jar > ../log/kkFileView.log 2>&1 &
nohup java -Dfile.encoding=UTF-8 -Dsun.java2d.cmm=sun.java2d.cmm.kcms.KcmsServiceProvider -Dspring.config.location=../config/application.properties -jar kkFileView-3.3.0.jar > ../log/kkFileView.log 2>&1 &

View File

@ -1,5 +1,7 @@
[#ftl]
[#-- @implicitly included --]
[#-- @ftlvariable name="file" type="cn.keking.model.FileAttribute" --]
[#-- @ftlvariable name="fileName" type="java.lang.String" --]
[#-- @ftlvariable name="fileTree" type="java.lang.String" --]
[#-- @ftlvariable name="baseUrl" type="java.lang.String" --]
[#-- @ftlvariable name="imgUrls" type="String" --]

View File

@ -1,7 +1,7 @@
package cn.keking.config;
import cn.keking.service.cache.CacheService;
import cn.keking.utils.FileUtils;
import cn.keking.utils.KkFileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
@ -31,7 +31,7 @@ public class SchedulerCleanConfig {
public void clean() {
logger.info("Cache clean start");
cacheService.cleanCache();
FileUtils.deleteDirectory(fileDir);
KkFileUtils.deleteDirectory(fileDir);
logger.info("Cache clean end");
}
}

View File

@ -10,6 +10,7 @@ import java.util.Map;
* Content :文件类型文本office压缩包等等
*/
public enum FileType {
picture("pictureFilePreviewImpl"),
compress("compressFilePreviewImpl"),
office("officeFilePreviewImpl"),
@ -19,12 +20,13 @@ public enum FileType {
media("mediaFilePreviewImpl"),
markdown("markdownFilePreviewImpl"),
xml("xmlFilePreviewImpl"),
flv("flvFilePreviewImpl"),
cad("cadFilePreviewImpl");
private static final String[] OFFICE_TYPES = {"docx", "doc", "xls", "xlsx", "ppt", "pptx"};
private static final String[] PICTURE_TYPES = {"jpg", "jpeg", "png", "gif", "bmp", "ico", "RAW"};
private static final String[] ARCHIVE_TYPES = {"rar", "zip", "jar", "7-zip", "tar", "gzip", "7z"};
private static final String[] SIMTEXT_TYPES = ConfigConstants.getSimText();
private static final String[] SSIM_TEXT_TYPES = ConfigConstants.getSimText();
private static final String[] MEDIA_TYPES = ConfigConstants.getMedia();
private static final Map<String, FileType> FILE_TYPE_MAPPER = new HashMap<>();
@ -38,7 +40,7 @@ public enum FileType {
for (String archive : ARCHIVE_TYPES) {
FILE_TYPE_MAPPER.put(archive, FileType.compress);
}
for (String text : SIMTEXT_TYPES) {
for (String text : SSIM_TEXT_TYPES) {
FILE_TYPE_MAPPER.put(text, FileType.simText);
}
for (String media : MEDIA_TYPES) {
@ -48,6 +50,8 @@ public enum FileType {
FILE_TYPE_MAPPER.put("xml", FileType.xml);
FILE_TYPE_MAPPER.put("pdf", FileType.pdf);
FILE_TYPE_MAPPER.put("dwg", FileType.cad);
FILE_TYPE_MAPPER.put("flv", FileType.flv);
}
private static FileType to(String fileType){

View File

@ -2,7 +2,7 @@ package cn.keking.service;
import cn.keking.config.ConfigConstants;
import cn.keking.model.FileType;
import cn.keking.utils.FileUtils;
import cn.keking.utils.KkFileUtils;
import cn.keking.web.filter.BaseUrlFilter;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
@ -48,7 +48,7 @@ public class CompressFileReader {
String baseUrl = BaseUrlFilter.getBaseUrl();
String archiveFileName = fileHandlerService.getFileNameFromPath(filePath);
try {
ZipFile zipFile = new ZipFile(filePath, FileUtils.getFileEncode(filePath));
ZipFile zipFile = new ZipFile(filePath, KkFileUtils.getFileEncode(filePath));
Enumeration<ZipArchiveEntry> entries = zipFile.getEntries();
// 排序
entries = sortZipEntries(entries);
@ -382,7 +382,7 @@ public class CompressFileReader {
} catch (IOException e) {
e.printStackTrace();
}
FileUtils.deleteFileByPath(filePath);
KkFileUtils.deleteFileByPath(filePath);
}
private void extractZipFile(String childName, InputStream zipFile) {
@ -439,7 +439,7 @@ public class CompressFileReader {
} catch (IOException e) {
e.printStackTrace();
}
FileUtils.deleteFileByPath(filePath);
KkFileUtils.deleteFileByPath(filePath);
}
}
@ -468,7 +468,7 @@ public class CompressFileReader {
} catch (IOException e) {
e.printStackTrace();
}
FileUtils.deleteFileByPath(filePath);
KkFileUtils.deleteFileByPath(filePath);
}
private void extractRarFile(String childName, FileHeader header, Archive archive) {

View File

@ -4,7 +4,7 @@ import cn.keking.config.ConfigConstants;
import cn.keking.model.FileAttribute;
import cn.keking.model.FileType;
import cn.keking.service.cache.CacheService;
import cn.keking.utils.FileUtils;
import cn.keking.utils.KkFileUtils;
import cn.keking.utils.WebUtils;
import com.aspose.cad.Color;
import com.aspose.cad.fileformats.cad.CadDrawTypeMode;
@ -263,7 +263,7 @@ public class FileHandlerService {
if (StringUtils.hasText(fullFileName)) {
fileName = fullFileName;
type = FileType.typeFromFileName(fullFileName);
suffix = FileUtils.suffixFromFileName(fullFileName);
suffix = KkFileUtils.suffixFromFileName(fullFileName);
} else {
fileName = WebUtils.getFileNameFromURL(url);
type = FileType.typeFromUrl(url);

View File

@ -8,5 +8,18 @@ import org.springframework.ui.Model;
* Content :
*/
public interface FilePreview {
String FLV_FILE_PREVIEW_PAGE = "flv";
String PDF_FILE_PREVIEW_PAGE = "pdf";
String COMPRESS_FILE_PREVIEW_PAGE = "compress";
String MEDIA_FILE_PREVIEW_PAGE = "media";
String PICTURE_FILE_PREVIEW_PAGE = "picture";
String OFFICE_PICTURE_FILE_PREVIEW_PAGE = "officePicture";
String TXT_FILE_PREVIEW_PAGE = "txt";
String EXEL_FILE_PREVIEW_PAGE = "html";
String XML_FILE_PREVIEW_PAGE = "xml";
String MARKDOWN_FILE_PREVIEW_PAGE = "markdown";
String NOT_SUPPORTED_FILE_PAGE = "fileNotSupported";
String filePreviewHandle(String url, Model model, FileAttribute fileAttribute);
}

View File

@ -25,9 +25,11 @@ public class CadFilePreviewImpl implements FilePreview {
private static final String FILE_DIR = ConfigConstants.getFileDir();
private final FileHandlerService fileHandlerService;
private final OtherFilePreviewImpl otherFilePreview;
public CadFilePreviewImpl(FileHandlerService fileHandlerService) {
public CadFilePreviewImpl(FileHandlerService fileHandlerService, OtherFilePreviewImpl otherFilePreview) {
this.fileHandlerService = fileHandlerService;
this.otherFilePreview = otherFilePreview;
}
@Override
@ -35,7 +37,6 @@ public class CadFilePreviewImpl implements FilePreview {
// 预览Type参数传了就取参数的没传取系统默认
String officePreviewType = model.asMap().get("officePreviewType") == null ? ConfigConstants.getOfficePreviewType() : model.asMap().get("officePreviewType").toString();
String baseUrl = BaseUrlFilter.getBaseUrl();
String suffix = fileAttribute.getSuffix();
String fileName = fileAttribute.getName();
String pdfName = fileName.substring(0, fileName.lastIndexOf(".") + 1) + "pdf";
String outFilePath = FILE_DIR + pdfName;
@ -44,17 +45,13 @@ public class CadFilePreviewImpl implements FilePreview {
String filePath;
ReturnResponse<String> response = DownloadUtils.downLoad(fileAttribute, null);
if (response.isFailure()) {
model.addAttribute("fileType", suffix);
model.addAttribute("msg", response.getMsg());
return "fileNotSupported";
return otherFilePreview.notSupportedFile(model, fileAttribute, response.getMsg());
}
filePath = response.getContent();
if (StringUtils.hasText(outFilePath)) {
boolean convertResult = fileHandlerService.cadToPdf(filePath, outFilePath);
if (!convertResult) {
model.addAttribute("fileType", suffix);
model.addAttribute("msg", "cad文件转换异常请联系管理员");
return "fileNotSupported";
return otherFilePreview.notSupportedFile(model, fileAttribute, "cad文件转换异常请联系管理员");
}
if (ConfigConstants.isCacheEnabled()) {
// 加入缓存
@ -63,10 +60,10 @@ public class CadFilePreviewImpl implements FilePreview {
}
}
if (baseUrl != null && (OFFICE_PREVIEW_TYPE_IMAGE.equals(officePreviewType) || OFFICE_PREVIEW_TYPE_ALL_IMAGES.equals(officePreviewType))) {
return getPreviewType(model, fileAttribute, officePreviewType, baseUrl, pdfName, outFilePath, fileHandlerService, OFFICE_PREVIEW_TYPE_IMAGE);
return getPreviewType(model, fileAttribute, officePreviewType, baseUrl, pdfName, outFilePath, fileHandlerService, OFFICE_PREVIEW_TYPE_IMAGE,otherFilePreview);
}
model.addAttribute("pdfUrl", pdfName);
return "pdf";
return PDF_FILE_PREVIEW_PAGE;
}

View File

@ -20,10 +20,12 @@ public class CompressFilePreviewImpl implements FilePreview {
private final FileHandlerService fileHandlerService;
private final CompressFileReader compressFileReader;
private final OtherFilePreviewImpl otherFilePreview;
public CompressFilePreviewImpl(FileHandlerService fileHandlerService, CompressFileReader compressFileReader) {
public CompressFilePreviewImpl(FileHandlerService fileHandlerService, CompressFileReader compressFileReader, OtherFilePreviewImpl otherFilePreview) {
this.fileHandlerService = fileHandlerService;
this.compressFileReader = compressFileReader;
this.otherFilePreview = otherFilePreview;
}
@Override
@ -35,9 +37,7 @@ public class CompressFilePreviewImpl implements FilePreview {
if (!StringUtils.hasText(fileHandlerService.getConvertedFile(fileName)) || !ConfigConstants.isCacheEnabled()) {
ReturnResponse<String> response = DownloadUtils.downLoad(fileAttribute, fileName);
if (response.isFailure()) {
model.addAttribute("fileType", suffix);
model.addAttribute("msg", response.getMsg());
return "fileNotSupported";
return otherFilePreview.notSupportedFile(model, fileAttribute, response.getMsg());
}
String filePath = response.getContent();
if ("zip".equalsIgnoreCase(suffix) || "jar".equalsIgnoreCase(suffix) || "gzip".equalsIgnoreCase(suffix)) {
@ -55,11 +55,9 @@ public class CompressFilePreviewImpl implements FilePreview {
}
if (fileTree != null && !"null".equals(fileTree)) {
model.addAttribute("fileTree", fileTree);
return "compress";
return COMPRESS_FILE_PREVIEW_PAGE;
} else {
model.addAttribute("fileType", suffix);
model.addAttribute("msg", "压缩文件类型不受支持尝试在压缩的时候选择RAR4格式");
return "fileNotSupported";
return otherFilePreview.notSupportedFile(model, fileAttribute, "压缩文件类型不受支持尝试在压缩的时候选择RAR4格式");
}
}
}

View File

@ -0,0 +1,27 @@
package cn.keking.service.impl;
import cn.keking.model.FileAttribute;
import cn.keking.service.FilePreview;
import org.springframework.stereotype.Service;
import org.springframework.ui.Model;
/**
* @author : kl
* create : 2020-12-27 2:50 下午
* flv文件预览处理实现
**/
@Service
public class FlvFilePreviewImpl implements FilePreview {
private final MediaFilePreviewImpl mediaFilePreview;
public FlvFilePreviewImpl(MediaFilePreviewImpl mediaFilePreview) {
this.mediaFilePreview = mediaFilePreview;
}
@Override
public String filePreviewHandle(String url, Model model, FileAttribute fileAttribute) {
mediaFilePreview.filePreviewHandle(url,model,fileAttribute);
return FLV_FILE_PREVIEW_PAGE;
}
}

View File

@ -5,8 +5,6 @@ import cn.keking.service.FilePreview;
import org.springframework.stereotype.Service;
import org.springframework.ui.Model;
import static com.sun.glass.ui.Clipboard.TEXT_TYPE;
/**
* @author kl (http://kailing.pub)
* @since 2020/12/25
@ -23,7 +21,7 @@ public class MarkdownFilePreviewImpl implements FilePreview {
@Override
public String filePreviewHandle(String url, Model model, FileAttribute fileAttribute) {
model.addAttribute(TEXT_TYPE,"markdown");
return simTextFilePreview.filePreviewHandle(url, model, fileAttribute);
simTextFilePreview.filePreviewHandle(url, model, fileAttribute);
return MARKDOWN_FILE_PREVIEW_PAGE;
}
}

View File

@ -19,9 +19,11 @@ import org.springframework.ui.Model;
public class MediaFilePreviewImpl implements FilePreview {
private final FileHandlerService fileHandlerService;
private final OtherFilePreviewImpl otherFilePreview;
public MediaFilePreviewImpl(FileHandlerService fileHandlerService) {
public MediaFilePreviewImpl(FileHandlerService fileHandlerService, OtherFilePreviewImpl otherFilePreview) {
this.fileHandlerService = fileHandlerService;
this.otherFilePreview = otherFilePreview;
}
@Override
@ -29,10 +31,8 @@ public class MediaFilePreviewImpl implements FilePreview {
// 不是http开头浏览器不能直接访问需下载到本地
if (url != null && !url.toLowerCase().startsWith("http")) {
ReturnResponse<String> response = DownloadUtils.downLoad(fileAttribute, fileAttribute.getName());
if (!response.isSuccess()) {
model.addAttribute("fileType", fileAttribute.getSuffix());
model.addAttribute("msg", response.getMsg());
return "fileNotSupported";
if (response.isFailure()) {
return otherFilePreview.notSupportedFile(model, fileAttribute, response.getMsg());
} else {
model.addAttribute("mediaUrl", BaseUrlFilter.getBaseUrl() + fileHandlerService.getRelativePath(response.getContent()));
}
@ -40,11 +40,7 @@ public class MediaFilePreviewImpl implements FilePreview {
model.addAttribute("mediaUrl", url);
}
model.addAttribute("mediaUrl", url);
String suffix = fileAttribute.getSuffix();
if ("flv".equalsIgnoreCase(suffix)) {
return "flv";
}
return "media";
return MEDIA_FILE_PREVIEW_PAGE;
}

View File

@ -27,10 +27,12 @@ public class OfficeFilePreviewImpl implements FilePreview {
private final FileHandlerService fileHandlerService;
private final OfficeToPdfService officeToPdfService;
private final OtherFilePreviewImpl otherFilePreview;
public OfficeFilePreviewImpl(FileHandlerService fileHandlerService, OfficeToPdfService officeToPdfService) {
public OfficeFilePreviewImpl(FileHandlerService fileHandlerService, OfficeToPdfService officeToPdfService, OtherFilePreviewImpl otherFilePreview) {
this.fileHandlerService = fileHandlerService;
this.officeToPdfService = officeToPdfService;
this.otherFilePreview = otherFilePreview;
}
@Override
@ -38,8 +40,8 @@ public class OfficeFilePreviewImpl implements FilePreview {
// 预览Type参数传了就取参数的没传取系统默认
String officePreviewType = fileAttribute.getOfficePreviewType();
String baseUrl = BaseUrlFilter.getBaseUrl();
String suffix=fileAttribute.getSuffix();
String fileName=fileAttribute.getName();
String suffix = fileAttribute.getSuffix();
String fileName = fileAttribute.getName();
boolean isHtml = suffix.equalsIgnoreCase("xls") || suffix.equalsIgnoreCase("xlsx");
String pdfName = fileName.substring(0, fileName.lastIndexOf(".") + 1) + (isHtml ? "html" : "pdf");
String outFilePath = FILE_DIR + pdfName;
@ -48,9 +50,7 @@ public class OfficeFilePreviewImpl implements FilePreview {
String filePath;
ReturnResponse<String> response = DownloadUtils.downLoad(fileAttribute, null);
if (response.isFailure()) {
model.addAttribute("fileType", suffix);
model.addAttribute("msg", response.getMsg());
return "fileNotSupported";
return otherFilePreview.notSupportedFile(model, fileAttribute, response.getMsg());
}
filePath = response.getContent();
if (StringUtils.hasText(outFilePath)) {
@ -66,25 +66,23 @@ public class OfficeFilePreviewImpl implements FilePreview {
}
}
if (!isHtml && baseUrl != null && (OFFICE_PREVIEW_TYPE_IMAGE.equals(officePreviewType) || OFFICE_PREVIEW_TYPE_ALL_IMAGES.equals(officePreviewType))) {
return getPreviewType(model, fileAttribute, officePreviewType, baseUrl, pdfName, outFilePath, fileHandlerService, OFFICE_PREVIEW_TYPE_IMAGE);
return getPreviewType(model, fileAttribute, officePreviewType, baseUrl, pdfName, outFilePath, fileHandlerService, OFFICE_PREVIEW_TYPE_IMAGE, otherFilePreview);
}
model.addAttribute("pdfUrl", pdfName);
return isHtml ? "html" : "pdf";
return isHtml ? EXEL_FILE_PREVIEW_PAGE : PDF_FILE_PREVIEW_PAGE;
}
static String getPreviewType(Model model, FileAttribute fileAttribute, String officePreviewType, String baseUrl, String pdfName, String outFilePath, FileHandlerService fileHandlerService, String officePreviewTypeImage) {
static String getPreviewType(Model model, FileAttribute fileAttribute, String officePreviewType, String baseUrl, String pdfName, String outFilePath, FileHandlerService fileHandlerService, String officePreviewTypeImage, OtherFilePreviewImpl otherFilePreview) {
List<String> imageUrls = fileHandlerService.pdf2jpg(outFilePath, pdfName, baseUrl);
if (imageUrls == null || imageUrls.size() < 1) {
model.addAttribute("msg", "office转图片异常请联系管理员");
model.addAttribute("fileType",fileAttribute.getSuffix());
return "fileNotSupported";
return otherFilePreview.notSupportedFile(model, fileAttribute, "office转图片异常请联系管理员");
}
model.addAttribute("imgurls", imageUrls);
model.addAttribute("currentUrl", imageUrls.get(0));
if (officePreviewTypeImage.equals(officePreviewType)) {
return "officePicture";
return OFFICE_PICTURE_FILE_PREVIEW_PAGE;
} else {
return "picture";
return PICTURE_FILE_PREVIEW_PAGE;
}
}
}

View File

@ -11,10 +11,41 @@ import org.springframework.ui.Model;
*/
@Service
public class OtherFilePreviewImpl implements FilePreview {
@Override
public String filePreviewHandle(String url, Model model, FileAttribute fileAttribute) {
model.addAttribute("fileType",fileAttribute.getSuffix());
model.addAttribute("msg", "系统还不支持该格式文件的在线预览");
return "fileNotSupported";
return this.notSupportedFile(model, fileAttribute, "系统还不支持该格式文件的在线预览");
}
/**
* 通用的预览失败,导向到不支持的文件响应页面
*
* @return 页面
*/
public String notSupportedFile(Model model, FileAttribute fileAttribute, String errMsg) {
return this.notSupportedFile(model, fileAttribute.getSuffix(), errMsg);
}
/**
* 通用的预览失败,导向到不支持的文件响应页面
*
* @return 页面
*/
public String notSupportedFile(Model model, String errMsg) {
return this.notSupportedFile(model, "未知", errMsg);
}
/**
* 通用的预览失败,导向到不支持的文件响应页面
*
* @return 页面
*/
public String notSupportedFile(Model model, String fileType, String errMsg) {
model.addAttribute("fileType", fileType);
model.addAttribute("msg", errMsg);
return NOT_SUPPORTED_FILE_PAGE;
}
}

View File

@ -20,16 +20,17 @@ import java.util.List;
public class PdfFilePreviewImpl implements FilePreview {
private final FileHandlerService fileHandlerService;
private final OtherFilePreviewImpl otherFilePreview;
private static final String FILE_DIR = ConfigConstants.getFileDir();
public PdfFilePreviewImpl(FileHandlerService fileHandlerService) {
public PdfFilePreviewImpl(FileHandlerService fileHandlerService, OtherFilePreviewImpl otherFilePreview) {
this.fileHandlerService = fileHandlerService;
this.otherFilePreview = otherFilePreview;
}
@Override
public String filePreviewHandle(String url, Model model, FileAttribute fileAttribute) {
String suffix=fileAttribute.getSuffix();
String fileName=fileAttribute.getName();
String fileName = fileAttribute.getName();
String officePreviewType = fileAttribute.getOfficePreviewType();
String baseUrl = BaseUrlFilter.getBaseUrl();
String pdfName = fileName.substring(0, fileName.lastIndexOf(".") + 1) + "pdf";
@ -39,9 +40,7 @@ public class PdfFilePreviewImpl implements FilePreview {
if (!fileHandlerService.listConvertedFiles().containsKey(pdfName) || !ConfigConstants.isCacheEnabled()) {
ReturnResponse<String> response = DownloadUtils.downLoad(fileAttribute, fileName);
if (response.isFailure()) {
model.addAttribute("fileType", suffix);
model.addAttribute("msg", response.getMsg());
return "fileNotSupported";
return otherFilePreview.notSupportedFile(model, fileAttribute, response.getMsg());
}
outFilePath = response.getContent();
if (ConfigConstants.isCacheEnabled()) {
@ -51,16 +50,14 @@ public class PdfFilePreviewImpl implements FilePreview {
}
List<String> imageUrls = fileHandlerService.pdf2jpg(outFilePath, pdfName, baseUrl);
if (imageUrls == null || imageUrls.size() < 1) {
model.addAttribute("msg", "pdf转图片异常请联系管理员");
model.addAttribute("fileType",fileAttribute.getSuffix());
return "fileNotSupported";
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 "officePicture";
return OFFICE_PICTURE_FILE_PREVIEW_PAGE;
} else {
return "picture";
return PICTURE_FILE_PREVIEW_PAGE;
}
} else {
// 不是http开头浏览器不能直接访问需下载到本地
@ -68,9 +65,7 @@ public class PdfFilePreviewImpl implements FilePreview {
if (!fileHandlerService.listConvertedFiles().containsKey(pdfName) || !ConfigConstants.isCacheEnabled()) {
ReturnResponse<String> response = DownloadUtils.downLoad(fileAttribute, pdfName);
if (response.isFailure()) {
model.addAttribute("fileType", suffix);
model.addAttribute("msg", response.getMsg());
return "fileNotSupported";
return otherFilePreview.notSupportedFile(model, fileAttribute, response.getMsg());
}
model.addAttribute("pdfUrl", fileHandlerService.getRelativePath(response.getContent()));
if (ConfigConstants.isCacheEnabled()) {
@ -84,6 +79,6 @@ public class PdfFilePreviewImpl implements FilePreview {
model.addAttribute("pdfUrl", url);
}
}
return "pdf";
return PDF_FILE_PREVIEW_PAGE;
}
}

View File

@ -19,9 +19,11 @@ import java.util.List;
public class PictureFilePreviewImpl implements FilePreview {
private final FileHandlerService fileHandlerService;
private final OtherFilePreviewImpl otherFilePreview;
public PictureFilePreviewImpl(FileHandlerService fileHandlerService) {
public PictureFilePreviewImpl(FileHandlerService fileHandlerService, OtherFilePreviewImpl otherFilePreview) {
this.fileHandlerService = fileHandlerService;
this.otherFilePreview = otherFilePreview;
}
@Override
@ -37,20 +39,18 @@ public class PictureFilePreviewImpl implements FilePreview {
if (url != null && !url.toLowerCase().startsWith("http")) {
ReturnResponse<String> response = DownloadUtils.downLoad(fileAttribute, null);
if (response.isFailure()) {
model.addAttribute("fileType", fileAttribute.getSuffix());
model.addAttribute("msg", response.getMsg());
return "fileNotSupported";
return otherFilePreview.notSupportedFile(model, fileAttribute, response.getMsg());
} else {
String file = fileHandlerService.getRelativePath(response.getContent());
imgUrls.clear();
imgUrls.add(file);
model.addAttribute("imgurls", imgUrls);
model.addAttribute("imgUrls", imgUrls);
model.addAttribute("currentUrl", file);
}
} else {
model.addAttribute("imgurls", imgUrls);
model.addAttribute("imgUrls", imgUrls);
model.addAttribute("currentUrl", url);
}
return "picture";
return PICTURE_FILE_PREVIEW_PAGE;
}
}

View File

@ -4,6 +4,7 @@ import cn.keking.model.FileAttribute;
import cn.keking.model.ReturnResponse;
import cn.keking.service.FilePreview;
import cn.keking.utils.DownloadUtils;
import cn.keking.utils.KkFileUtils;
import org.apache.commons.io.FileUtils;
import org.springframework.stereotype.Service;
import org.springframework.ui.Model;
@ -11,7 +12,6 @@ import org.springframework.util.Base64Utils;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* Created by kl on 2018/1/17.
@ -20,31 +20,28 @@ import java.nio.charset.StandardCharsets;
@Service
public class SimTextFilePreviewImpl implements FilePreview {
public static final String TEXT_TYPE = "textType";
public static final String DEFAULT_TEXT_TYPE = "simText";
private final OtherFilePreviewImpl otherFilePreview;
public SimTextFilePreviewImpl(OtherFilePreviewImpl otherFilePreview) {
this.otherFilePreview = otherFilePreview;
}
@Override
public String filePreviewHandle(String url, Model model, FileAttribute fileAttribute) {
String fileName = fileAttribute.getName();
ReturnResponse<String> response = DownloadUtils.downLoad(fileAttribute, fileName);
if (response.isFailure()) {
model.addAttribute("msg", response.getMsg());
model.addAttribute("fileType", fileAttribute.getSuffix());
return "fileNotSupported";
return otherFilePreview.notSupportedFile(model, fileAttribute, response.getMsg());
}
try {
File originFile = new File(response.getContent());
String xmlString = FileUtils.readFileToString(originFile, StandardCharsets.UTF_8);
model.addAttribute("textData", Base64Utils.encodeToString(xmlString.getBytes()));
String charset = KkFileUtils.getFileEncode(originFile);
String xmlString = FileUtils.readFileToString(originFile, charset);
model.addAttribute("textData", Base64Utils.encodeToString(xmlString.getBytes(charset)));
} catch (IOException e) {
model.addAttribute("msg", e.getLocalizedMessage());
model.addAttribute("fileType", fileAttribute.getSuffix());
return "fileNotSupported";
return otherFilePreview.notSupportedFile(model, fileAttribute, e.getLocalizedMessage());
}
if (!model.containsAttribute(TEXT_TYPE)) {
model.addAttribute(TEXT_TYPE, DEFAULT_TEXT_TYPE);
}
return "txt";
return TXT_FILE_PREVIEW_PAGE;
}
}

View File

@ -5,8 +5,6 @@ import cn.keking.service.FilePreview;
import org.springframework.stereotype.Service;
import org.springframework.ui.Model;
import static com.sun.glass.ui.Clipboard.TEXT_TYPE;
/**
* @author kl (http://kailing.pub)
* @since 2020/12/25
@ -20,10 +18,9 @@ public class XmlFilePreviewImpl implements FilePreview {
this.simTextFilePreview = simTextFilePreview;
}
@Override
public String filePreviewHandle(String url, Model model, FileAttribute fileAttribute) {
model.addAttribute(TEXT_TYPE,"xml");
return simTextFilePreview.filePreviewHandle(url, model, fileAttribute);
simTextFilePreview.filePreviewHandle(url, model, fileAttribute);
return XML_FILE_PREVIEW_PAGE;
}
}

View File

@ -2,16 +2,19 @@ package cn.keking.utils;
import cn.keking.config.ConfigConstants;
import cn.keking.model.FileAttribute;
import cn.keking.model.FileType;
import cn.keking.model.ReturnResponse;
import io.mola.galimatias.GalimatiasParseException;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
import static cn.keking.utils.KkFileUtils.isFtpUrl;
import static cn.keking.utils.KkFileUtils.isHttpUrl;
/**
* @author yudian-it
*/
@ -30,42 +33,26 @@ public class DownloadUtils {
*/
public static ReturnResponse<String> downLoad(FileAttribute fileAttribute, String fileName) {
String urlStr = fileAttribute.getUrl();
String type = fileAttribute.getSuffix();
ReturnResponse<String> response = new ReturnResponse<>(0, "下载成功!!!", "");
UUID uuid = UUID.randomUUID();
if (null == fileName) {
fileName = uuid + "." + type;
} else { // 文件后缀不一致时以type为准(针对simText【将类txt文件转为txt】)
fileName = fileName.replace(fileName.substring(fileName.lastIndexOf(".") + 1), type);
}
String realPath = fileDir + fileName;
File dirFile = new File(fileDir);
if (!dirFile.exists() && !dirFile.mkdirs()) {
logger.error("创建目录【{}】失败,可能是权限不够,请检查", fileDir);
}
String realPath = DownloadUtils.getRelFilePath(fileName, fileAttribute);
try {
URL url = new URL(urlStr);
if (url.getProtocol() != null && (url.getProtocol().toLowerCase().startsWith("file") || url.getProtocol().toLowerCase().startsWith("http"))) {
byte[] bytes = getBytesFromUrl(urlStr);
OutputStream os = new FileOutputStream(realPath);
saveBytesToOutStream(bytes, os);
} else if (url.getProtocol() != null && "ftp".equalsIgnoreCase(url.getProtocol())) {
URL url = WebUtils.normalizedURL(urlStr);
if (isHttpUrl(url)) {
File realFile = new File(realPath);
FileUtils.copyURLToFile(url, realFile);
} else if (isFtpUrl(url)) {
String ftpUsername = WebUtils.getUrlParameterReg(fileAttribute.getUrl(), URL_PARAM_FTP_USERNAME);
String ftpPassword = WebUtils.getUrlParameterReg(fileAttribute.getUrl(), URL_PARAM_FTP_PASSWORD);
String ftpControlEncoding = WebUtils.getUrlParameterReg(fileAttribute.getUrl(), URL_PARAM_FTP_CONTROL_ENCODING);
FtpUtils.download(fileAttribute.getUrl(), realPath, ftpUsername, ftpPassword, ftpControlEncoding);
} else {
response.setCode(1);
response.setContent(null);
response.setMsg("url不能识别url" + urlStr);
}
response.setContent(realPath);
response.setMsg(fileName);
if (FileType.simText.equals(fileAttribute.getType())) {
convertTextPlainFileCharsetToUtf8(realPath);
}
return response;
} catch (IOException e) {
} catch (IOException | GalimatiasParseException e) {
logger.error("文件下载失败url{}", urlStr, e);
response.setCode(1);
response.setContent(null);
@ -78,82 +65,27 @@ public class DownloadUtils {
}
}
public static byte[] getBytesFromUrl(String urlStr) throws IOException {
InputStream is = getInputStreamFromUrl(urlStr);
if (is == null) {
// urlStr = URLUtil.normalize(urlStr, true, true);
is = getInputStreamFromUrl(urlStr);
if (is == null) {
logger.error("文件下载异常url{}", urlStr);
throw new IOException("文件下载异常url" + urlStr);
}
}
return getBytesFromStream(is);
}
public static void saveBytesToOutStream(byte[] b, OutputStream os) throws IOException {
os.write(b);
os.close();
}
private static InputStream getInputStreamFromUrl(String urlStr) {
try {
URL url = new URL(urlStr);
URLConnection connection = url.openConnection();
if (connection instanceof HttpURLConnection) {
connection.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)");
}
return connection.getInputStream();
} catch (IOException e) {
logger.warn("连接url异常url{}", urlStr);
return null;
}
}
private static byte[] getBytesFromStream(InputStream is) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
byte[] b = baos.toByteArray();
is.close();
baos.close();
return b;
}
/**
* 转换文本文件编码为utf8
* 探测源文件编码,探测到编码切不为utf8则进行转码
* 获取真实文件绝对路径
*
* @param filePath 文件路径
* @param fileName 文件
* @return 文件路径
*/
private static void convertTextPlainFileCharsetToUtf8(String filePath) throws IOException {
File sourceFile = new File(filePath);
if (sourceFile.exists() && sourceFile.isFile() && sourceFile.canRead()) {
String encoding = FileUtils.getFileEncode(filePath);
if (!FileUtils.DEFAULT_FILE_ENCODING.equals(encoding)) {
// 不为utf8,进行转码
File tmpUtf8File = new File(filePath + ".utf8");
Writer writer = new OutputStreamWriter(new FileOutputStream(tmpUtf8File), StandardCharsets.UTF_8);
Reader reader = new BufferedReader(new InputStreamReader(new FileInputStream(sourceFile), encoding));
char[] buf = new char[1024];
int read;
while ((read = reader.read(buf)) > 0) {
writer.write(buf, 0, read);
}
reader.close();
writer.close();
// 删除源文件
if (!sourceFile.delete()) {
logger.error("源文件【{}】删除失败,请检查文件目录权限!", filePath);
}
// 重命名
if (tmpUtf8File.renameTo(sourceFile)) {
logger.error("临时文件【{}】重命名失败,请检查文件路径权限!", tmpUtf8File.getPath());
}
}
private static String getRelFilePath(String fileName, FileAttribute fileAttribute) {
String type = fileAttribute.getSuffix();
if (null == fileName) {
UUID uuid = UUID.randomUUID();
fileName = uuid + "." + type;
} else { // 文件后缀不一致时以type为准(针对simText【将类txt文件转为txt】)
fileName = fileName.replace(fileName.substring(fileName.lastIndexOf(".") + 1), type);
}
String realPath = fileDir + fileName;
File dirFile = new File(fileDir);
if (!dirFile.exists() && !dirFile.mkdirs()) {
logger.error("创建目录【{}】失败,可能是权限不够,请检查", fileDir);
}
return realPath;
}
}

View File

@ -6,19 +6,39 @@ import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.Objects;
public class FileUtils {
public class KkFileUtils {
private static final Logger LOGGER = LoggerFactory.getLogger(FileUtils.class);
private static final Logger LOGGER = LoggerFactory.getLogger(KkFileUtils.class);
public static final String DEFAULT_FILE_ENCODING = "UTF-8";
/**
* 判断url是否是http资源
*
* @param url url
* @return 是否http
*/
public static boolean isHttpUrl(URL url) {
return url.getProtocol().toLowerCase().startsWith("file") || url.getProtocol().toLowerCase().startsWith("http");
}
/**
* 判断url是否是ftp资源
*
* @param url url
* @return 是否ftp
*/
public static boolean isFtpUrl(URL url) {
return "ftp".equalsIgnoreCase(url.getProtocol());
}
/**
* 删除单个文件
*
* @param fileName
* 要删除的文件的文件名
* @param fileName 要删除的文件的文件名
* @return 单个文件删除成功返回true否则返回false
*/
public static boolean deleteFileByName(String fileName) {
@ -39,17 +59,26 @@ public class FileUtils {
}
/**
* 判断文件编码格式
* 检测文件编码格式
*
* @param filePath 绝对路径
* @return 编码格式
*/
public static String getFileEncode(String filePath) {
File file = new File(filePath);
return getFileEncode(new File(filePath));
}
/**
* 检测文件编码格式
*
* @param file 检测的文件
* @return 编码格式
*/
public static String getFileEncode(File file) {
CharsetPrinter cp = new CharsetPrinter();
try {
String encoding = cp.guessEncoding(file);
LOGGER.info("检测到文件【{}】编码: {}", filePath, encoding);
LOGGER.info("检测到文件【{}】编码: {}", file.getAbsolutePath(), encoding);
return encoding;
} catch (IOException e) {
LOGGER.warn("文件编码获取失败采用默认的编码格式UTF-8", e);
@ -59,6 +88,7 @@ public class FileUtils {
/**
* 通过文件名获取文件后缀
*
* @param fileName 文件名称
* @return 文件后缀
*/
@ -82,8 +112,7 @@ public class FileUtils {
/**
* 删除目录及目录下的文件
*
* @param dir
* 要删除的目录的文件路径
* @param dir 要删除的目录的文件路径
* @return 目录删除成功返回true否则返回false
*/
public static boolean deleteDirectory(String dir) {
@ -103,13 +132,13 @@ public class FileUtils {
for (int i = 0; i < Objects.requireNonNull(files).length; i++) {
// 删除子文件
if (files[i].isFile()) {
flag = FileUtils.deleteFileByName(files[i].getAbsolutePath());
flag = KkFileUtils.deleteFileByName(files[i].getAbsolutePath());
if (!flag) {
break;
}
} else if (files[i].isDirectory()) {
} else if (files[i].isDirectory()) {
// 删除子目录
flag = FileUtils.deleteDirectory(files[i].getAbsolutePath());
flag = KkFileUtils.deleteDirectory(files[i].getAbsolutePath());
if (!flag) {
break;
}

View File

@ -1,5 +1,9 @@
package cn.keking.utils;
import io.mola.galimatias.GalimatiasParseException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
@ -8,6 +12,16 @@ import java.util.Map;
* create : 2020-12-27 1:30 上午
**/
public class WebUtils {
/**
* 获取标准的URL
* @param urlStr url
* @return 标准的URL
*/
public static URL normalizedURL(String urlStr) throws GalimatiasParseException, MalformedURLException {
return io.mola.galimatias.URL.parse(urlStr).toJavaURL();
}
/**
* 获取url中的参数
*
@ -81,6 +95,6 @@ public class WebUtils {
public static String suffixFromUrl(String url) {
String nonPramStr = url.substring(0, url.contains("?") ? url.indexOf("?") : url.length());
String fileName = nonPramStr.substring(nonPramStr.lastIndexOf("/") + 1);
return FileUtils.suffixFromFileName(fileName);
return KkFileUtils.suffixFromFileName(fileName);
}
}

View File

@ -5,14 +5,17 @@ import cn.keking.service.FilePreview;
import cn.keking.service.FilePreviewFactory;
import cn.keking.service.cache.CacheService;
import cn.keking.utils.DownloadUtils;
import cn.keking.service.impl.OtherFilePreviewImpl;
import cn.keking.service.FileHandlerService;
import com.thoughtworks.xstream.core.util.Base64JavaUtilCodec;
import cn.keking.utils.WebUtils;
import io.mola.galimatias.GalimatiasParseException;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.Base64Utils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
@ -20,73 +23,96 @@ import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLDecoder;
import java.net.URL;
import java.util.Arrays;
import java.util.List;
import static cn.keking.service.FilePreview.PICTURE_FILE_PREVIEW_PAGE;
/**
* @author yudian-it
*/
@Controller
public class OnlinePreviewController {
public static final String BASE64_DECODE_ERROR_MSG = "Base64解码失败请检查你的 %s 是否采用 Base64 + urlEncode 双重编码了!";
private final Logger logger = LoggerFactory.getLogger(OnlinePreviewController.class);
private final FilePreviewFactory previewFactory;
private final CacheService cacheService;
private final FileHandlerService fileHandlerService;
private final OtherFilePreviewImpl otherFilePreview;
public OnlinePreviewController(FilePreviewFactory filePreviewFactory, FileHandlerService fileHandlerService, CacheService cacheService) {
public OnlinePreviewController(FilePreviewFactory filePreviewFactory, FileHandlerService fileHandlerService, CacheService cacheService, OtherFilePreviewImpl otherFilePreview) {
this.previewFactory = filePreviewFactory;
this.fileHandlerService = fileHandlerService;
this.cacheService = cacheService;
this.otherFilePreview = otherFilePreview;
}
@RequestMapping(value = "/onlinePreview")
public String onlinePreview(String url, Model model, HttpServletRequest req) {
String fileUrl = new String(Base64Utils.decodeFromString(url));
FileAttribute fileAttribute = fileHandlerService.getFileAttribute(fileUrl,req);
String fileUrl;
try {
fileUrl = new String(Base64Utils.decodeFromString(url));
} catch (Exception ex) {
String errorMsg = String.format(BASE64_DECODE_ERROR_MSG, "url");
return otherFilePreview.notSupportedFile(model, errorMsg);
}
FileAttribute fileAttribute = fileHandlerService.getFileAttribute(fileUrl, req);
model.addAttribute("file", fileAttribute);
FilePreview filePreview = previewFactory.get(fileAttribute);
logger.info("预览文件url{}previewType{}", fileUrl, fileAttribute.getType());
return filePreview.filePreviewHandle(fileUrl, model, fileAttribute);
}
@RequestMapping(value = "/picturesPreview")
public String picturesPreview(Model model, HttpServletRequest req) throws UnsupportedEncodingException {
String urls = req.getParameter("urls");
String currentUrl = req.getParameter("currentUrl");
logger.info("预览文件url{}urls{}", currentUrl, urls);
// 路径转码
String decodedUrl = URLDecoder.decode(urls, "utf-8");
String decodedCurrentUrl = URLDecoder.decode(currentUrl, "utf-8");
public String picturesPreview(String urls, Model model, HttpServletRequest req) throws UnsupportedEncodingException {
String fileUrls;
try {
fileUrls = new String(Base64Utils.decodeFromString(urls));
} catch (Exception ex) {
String errorMsg = String.format(BASE64_DECODE_ERROR_MSG, "urls");
return otherFilePreview.notSupportedFile(model, errorMsg);
}
logger.info("预览文件url{}urls{}", fileUrls, urls);
// 抽取文件并返回文件列表
String[] imgs = decodedUrl.split("\\|");
List<String> imgUrls = Arrays.asList(imgs);
String[] images = fileUrls.split("\\|");
List<String> imgUrls = Arrays.asList(images);
model.addAttribute("imgUrls", imgUrls);
model.addAttribute("currentUrl",decodedCurrentUrl);
return "picture";
String currentUrl = req.getParameter("currentUrl");
if (StringUtils.hasText(currentUrl)) {
String decodedCurrentUrl = new String(Base64Utils.decodeFromString(currentUrl));
model.addAttribute("currentUrl", decodedCurrentUrl);
} else {
model.addAttribute("currentUrl", imgUrls.get(0));
}
return PICTURE_FILE_PREVIEW_PAGE;
}
/**
* 根据url获取文件内容
* 当pdfjs读取存在跨域问题的文件时将通过此接口读取
*
* @param urlPath url
* @param urlPath url
* @param response response
*/
@RequestMapping(value = "/getCorsFile", method = RequestMethod.GET)
public void getCorsFile(String urlPath, HttpServletResponse response) {
logger.info("下载跨域pdf文件url{}", urlPath);
try {
byte[] bytes = DownloadUtils.getBytesFromUrl(urlPath);
DownloadUtils.saveBytesToOutStream(bytes, response.getOutputStream());
} catch (IOException e) {
URL url = WebUtils.normalizedURL(urlPath);
byte[] bytes = IOUtils.toByteArray(url);
IOUtils.write(bytes, response.getOutputStream());
} catch (IOException | GalimatiasParseException e) {
logger.error("下载跨域pdf文件异常url{}", urlPath, e);
}
}
/**
* 通过api接口入队
*
* @param url 请编码后在入队
*/
@RequestMapping("/addTask")

View File

@ -0,0 +1,7 @@
README.txt
log目录是用来存放kkFileView.log的预览服务的运行情况最终都会反映到这个日志文件里
可以提供给开发运维排查系统问题如果通过kkFileView.log还无法定位问题所在请你在寻求
kk官方支持时QQ群一 613025121QQ群二 484680571将此日志文件一并携带并按照这个
格式重命名日志文件 kkFileView-QQ昵称-时间日期.logkkFileView-kl博主-2020-12-27.log
所有收集的日志文件我们都会存档供所有的kk用户作为排查案例使用所以在你提供日志前
先自行处理日志文件里的业务敏感内容

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -2,51 +2,49 @@
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, user-scalable=yes, initial-scale=1.0" />
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, user-scalable=yes, initial-scale=1.0"/>
<title>kkFileView演示首页</title>
<link rel="stylesheet" href="css/viewer.min.css" />
<link rel="stylesheet" href="css/loading.css" />
<link rel="stylesheet" href="bootstrap/css/bootstrap.min.css" />
<link rel="stylesheet" href="bootstrap-table/bootstrap-table.min.css" />
<link rel="stylesheet" href="gitalk/gitalk.css" />
<link rel="stylesheet" href="css/viewer.min.css"/>
<link rel="stylesheet" href="css/loading.css"/>
<link rel="stylesheet" href="bootstrap/css/bootstrap.min.css"/>
<link rel="stylesheet" href="bootstrap-table/bootstrap-table.min.css"/>
<link rel="stylesheet" href="gitalk/gitalk.css"/>
<script type="text/javascript" src="js/jquery-3.0.0.min.js"></script>
<script type="text/javascript" src="js/jquery.form.min.js"></script>
<script type="text/javascript" src="bootstrap/js/bootstrap.min.js"></script>
<script type="text/javascript" src="bootstrap-table/bootstrap-table.min.js"></script>
<script type="text/javascript" src="gitalk/gitalk.min.js"></script>
<script type="text/javascript" src="js/base64.min.js" ></script>
<script type="text/javascript" src="js/base64.min.js"></script>
</head>
<body>
<h1>文件预览项目接入和测试界面</h1>
<div class="panel-group" id="accordion">
<div class="panel-group container" id="accordion">
<h1>文件预览项目接入和测试界面</h1>
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" data-parent="#accordion"
href="#collapseOne">
<a data-toggle="collapse" data-parent="#accordion" href="#collapseOne">
接入说明
</a>
</h4>
</div>
<div id="collapseOne" class="panel-collapse collapse">
<div class="panel-body">
<div>
如果你的项目需要接入文件预览项目达到对docx、excel、ppt、jpg等文件的预览效果那么通过在你的项目中加入下面的代码就可以
成功实现:
<pre style="background-color: #2f332a;color: #cccccc">
var url = 'http://127.0.0.1:8080/file/test.txt'; //要预览文件的访问地址
window.open('http://127.0.0.1:8012/onlinePreview?url='+encodeURIComponent(base64Encode(url)));
</pre>
</div>
<div>
新增多图片同时预览功能,接口如下:
<pre style="background-color: #2f332a;color: #cccccc">
var fileUrl =url1+"|"+"url2";//多文件使用“|”字符隔开
window.open('http://127.0.0.1:8012/picturesPreview?urls='+encodeURIComponent(base64Encode(fileUrl)));
</pre>
</div>
<div class="panel-body">
<div>
如果你的项目需要接入文件预览项目达到对docx、excel、ppt、jpg等文件的预览效果那么通过在你的项目中加入下面的代码就可以
成功实现:
<pre style="background-color: #2f332a;color: #cccccc">
var url = 'http://127.0.0.1:8080/file/test.txt'; //要预览文件的访问地址
window.open('http://127.0.0.1:8012/onlinePreview?url='+encodeURIComponent(base64Encode(url)));
</pre>
</div>
<div>
新增多图片同时预览功能,接口如下:
<pre style="background-color: #2f332a;color: #cccccc">
var fileUrl =url1+"|"+"url2";//多文件使用“|”字符隔开
window.open('http://127.0.0.1:8012/picturesPreview?urls='+encodeURIComponent(base64Encode(fileUrl)));
</pre>
</div>
</div>
</div>
@ -59,86 +57,100 @@ window.open('http://127.0.0.1:8012/picturesPreview?urls='+encodeURIComponent(bas
</a>
</h4>
</div>
<div id="collapseTwo" class="panel-collapse collapse">
<div class="panel-body">
<div style="padding: 10px">
<form enctype="multipart/form-data" id="fileUpload">
<input type="file" name="file" />
<input type="button" id="btnsubmit" value=" " />
</form>
</div>
<div>
<table id="table" data-pagination="true"></table>
</div>
<div class="panel-body">
<div style="padding: 10px">
<form enctype="multipart/form-data" id="fileUpload">
<input type="file" name="file"/>
<input type="button" id="btnSubmit" value=" "/>
</form>
</div>
<div>
<table id="table" data-pagination="true"></table>
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" data-parent="#accordion"
href="#collapseThree">
更新记录
<a data-toggle="collapse" data-parent="#accordion" href="#collapseThree">
发版记录
</a>
</h4>
</div>
<div id="collapseThree" class="panel-collapse collapse in">
<div class="panel-body">
<div>
2020年05月20日 <br>
1. 新增支持全局水印,并支持通过参数动态改变水印内容<br>
2. 新增支持CAD文件预览<br>
3. 新增base.url配置支持使用nginx反向代理和使用context-path<br>
4. 支持所有配置项支持从环境变量里读取方便Docker镜像部署和集群中大规模使用<br>
5. 支持配置限信任站点(只能预览来自信任点的文件源),保护预览服务不被滥用<br>
6. 支持配置自定义缓存清理时间cron表达式<br>
7. 全部能识别的纯文本直接预览,不用再转跳下载,如.md .java .py等<br>
8. 支持配置限制转换后的PDF文件下载<br>
9. 优化maven打包配置解决 .sh 脚本可能出现换行符问题<br>
10. 将前端所有CDN依赖放到本地方便没有外网连接的用户使用<br>
11. 首页评论服务由搜狐畅言切换到Gitalk<br>
12. 修复url中包含特殊字符可能会引起的预览异常<br>
13. 修复转换文件队列addTask异常<br>
14. 修复其他已经问题<br>
15. 官网建设:<a href="https://kkfileview.keking.cn">https://kkfileview.keking.cn</a><br>
16. 官方Docker镜像仓库建设<a href="https://hub.docker.com/r/keking/kkfileview">https://hub.docker.com/r/keking/kkfileview</a><br><br>
<div class="panel-body">
<div>
2020年12月27日 <br>
2020年年终大版本更新架构全面设计代码全面重构代码质量全面提升二次开发更便捷欢迎拉源码品鉴提issue、pr共同建设<br>
1. 架构模块调整,大量的代码重构代码质量提升N个等级欢迎品鉴<br>
2. 增强XML文件预览效果新增XML文档数结构预览<br>
3. 新增markdown文件预览支持预览支持md渲染和源文本切换支持<br>
4. 切换底层web server为jetty解决这个issue<a href="https://github.com/kekingcn/kkFileView/issues/168">#issues/168</a><br>
5. 引入cpdetector解决文件编码识别问题<br>
6. url采用base64+urlencode双编码彻底解决各种奇葩文件名预览问题<br>
7. 新增配置项office.preview.switch.disabled控制offic文件预览切换开关<br>
8. 优化文本类型文件预览逻辑采用Base64传输内容避免预览时再次请求文件内容<br>
9. office预览图片模式禁用图片放大效果达到图片和pdf预览效果一致的体验<br>
10. 直接代码静态设置pdfbox兼容低版本jdk在IDEA中运行也不会有警告提示<br>
11. 移除guavahutool等非必须的工具包减少代码体积<br>
12. Office组件加载异步化提速应用启动速度最快到5秒内<br>
13. 合理设置预览消费队列的线程数<br>
14. 修复压缩包里文件再次预览失败的bug<br>
15. 修复图片预览的bug<br><br>
2019年06月18 <br>
1. 支持自动清理缓存及预览文件<br>
2. 支持http/https下载流url文件预览<br>
3. 支持FTP url文件预览<br>
4. 加入Docker构建<br><br>
2020年05月20 <br>
1. 新增支持全局水印并支持通过参数动态改变水印内容<br>
2. 新增支持CAD文件预览<br>
3. 新增base.url配置支持使用nginx反向代理和使用context-path<br>
4. 支持所有配置项支持从环境变量里读取方便Docker镜像部署和集群中大规模使用<br>
5. 支持配置限信任站点只能预览来自信任点的文件源保护预览服务不被滥用<br>
6. 支持配置自定义缓存清理时间cron表达式<br>
7. 全部能识别的纯文本直接预览不用再转跳下载.md .java .py等<br>
8. 支持配置限制转换后的PDF文件下载<br>
9. 优化maven打包配置解决 .sh 脚本可能出现换行符问题<br>
10. 将前端所有CDN依赖放到本地方便没有外网连接的用户使用<br>
11. 首页评论服务由搜狐畅言切换到Gitalk<br>
12. 修复url中包含特殊字符可能会引起的预览异常<br>
13. 修复转换文件队列addTask异常<br>
14. 修复其他已经问题<br>
15. 官网建设<a href="https://kkfileview.keking.cn">https://kkfileview.keking.cn</a><br>
16. 官方Docker镜像仓库建设<a href="https://hub.docker.com/r/keking/kkfileview">https://hub.docker.com/r/keking/kkfileview</a><br><br>
2019年04月08日 <br>
1. 缓存及队列实现抽象提供JDK和REDIS两种实现(REDIS成为可选依赖)<br>
2. 打包方式提供zip和tar.gz包并提供一键启动脚本<br><br>
2019年06月18日 <br>
1. 支持自动清理缓存及预览文件<br>
2. 支持http/https下载流url文件预览<br>
3. 支持FTP url文件预览<br>
4. 加入Docker构建<br><br>
2018年01月19 <br>
1. 大文件入队提前处理<br>
1. 新增addTask文件转换入队接口<br>
1. 采用redis队列支持kkFIleView接口和异构系统入队两种方式<br><br>
2019年04月08 <br>
1. 缓存及队列实现抽象提供JDK和REDIS两种实现(REDIS成为可选依赖)<br>
2. 打包方式提供zip和tar.gz包并提供一键启动脚本<br><br>
2018年01月15 <br>
1.首页新增社会化评论框<br><br>
2018年01月19 <br>
1. 大文件入队提前处理<br>
1. 新增addTask文件转换入队接口<br>
1. 采用redis队列支持kkFIleView接口和异构系统入队两种方式<br><br>
2018年01月12 <br>
1.新增多图片同时预览<br>
2.支持压缩包内图片轮番预览<br><br>
2018年01月15 <br>
1.首页新增社会化评论框<br><br>
2018年01月02日 <br>
1.修复txt等文本编码问题导致预览乱码<br>
2.修复项目模块依赖引入不到的问题<br>
3.新增spring boot profile支持多环境配置<br>
4.引入pdf.js预览doc等文件支持doc标题生成pdf预览菜单支持手机端预览<br><br>
2018年01月12日 <br>
1.新增多图片同时预览<br>
2.支持压缩包内图片轮番预览<br><br>
2017年12月12日<br>
1.项目gitee开源:<a href="https://gitee.com/kekingcn/file-online-preview" target="_blank">https://gitee.com/kekingcn/file-online-preview</a><br>
2.项目github开源:<a href="https://github.com/kekingcn/kkFileView" target="_blank">https://github.com/kekingcn/kkFileView</a>
</div>
2018年01月02日 <br>
1.修复txt等文本编码问题导致预览乱码<br>
2.修复项目模块依赖引入不到的问题<br>
3.新增spring boot profile支持多环境配置<br>
4.引入pdf.js预览doc等文件支持doc标题生成pdf预览菜单支持手机端预览<br><br>
2017年12月12日<br>
1.项目gitee开源:<a href="https://gitee.com/kekingcn/file-online-preview" target="_blank">https://gitee.com/kekingcn/file-online-preview</a><br>
2.项目github开源:<a href="https://github.com/kekingcn/kkFileView" target="_blank">https://github.com/kekingcn/kkFileView</a>
</div>
</div>
<div class="panel-body">
<div id = "comments"></div>
<div id="comments"></div>
</div>
</div>
@ -172,9 +184,9 @@ window.open('http://127.0.0.1:8012/picturesPreview?urls='+encodeURIComponent(bas
url: '${baseUrl}deleteFile?fileName=' + encodeURIComponent(fileName),
success: function (data) {
// 删除完成刷新table
if (1 == data.code) {
if (1 === data.code) {
alert(data.msg);
} else{
} else {
$('#table').bootstrapTable('refresh', {});
}
},
@ -183,6 +195,7 @@ window.open('http://127.0.0.1:8012/picturesPreview?urls='+encodeURIComponent(bas
}
})
}
$(function () {
$('#table').bootstrapTable({
url: 'listFiles',
@ -193,14 +206,14 @@ window.open('http://127.0.0.1:8012/picturesPreview?urls='+encodeURIComponent(bas
field: 'action',
title: '操作'
},]
}).on('pre-body.bs.table', function (e,data) {
}).on('pre-body.bs.table', function (e, data) {
// 每个data添加一列用来操作
$(data).each(function (index, item) {
item.action = "<a class='btn btn-default' target='_blank' href='${baseUrl}onlinePreview?url="+ encodeURIComponent(Base64.encode('${baseUrl}' + item.fileName)) +"'>预览</a>" +
"<a class='btn btn-default' href='javascript:void(0);' onclick='deleteFile(\""+item.fileName+"\")'>删除</a>";
item.action = "<a class='btn btn-default' target='_blank' href='${baseUrl}onlinePreview?url=" + encodeURIComponent(Base64.encode('${baseUrl}' + item.fileName)) + "'>预览</a>" +
"<a class='btn btn-default' href='javascript:void(0);' onclick='deleteFile(\"" + item.fileName + "\")'>删除</a>";
});
return data;
}).on('post-body.bs.table', function (e,data) {
}).on('post-body.bs.table', function (e, data) {
return data;
});
@ -210,12 +223,12 @@ window.open('http://127.0.0.1:8012/picturesPreview?urls='+encodeURIComponent(bas
$(".loading_container").css("height", height).show();
}
$("#btnsubmit").click(function () {
$("#btnSubmit").click(function () {
showLoadingDiv();
$("#fileUpload").ajaxSubmit({
success: function (data) {
// 上传完成刷新table
if (1 == data.code) {
if (1 === data.code) {
alert(data.msg);
} else {
$('#table').bootstrapTable('refresh', {});

View File

@ -0,0 +1,118 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, user-scalable=yes, initial-scale=1.0">
<title>普通文本预览</title>
</head>
<body>
<input hidden id="textData" value="${textData}"/>
<div class="container">
<div class="panel panel-default">
<div id="markdown_btn" class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" data-parent="#accordion" href="#collapseOne">
${file.name}
</a>
</h4>
</div>
<div id="text_btn" class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" data-parent="#accordion" href="#collapseOne">
${file.name}
</a>
</h4>
</div>
<div class="panel-body">
<div id="markdown"></div>
</div>
</div>
</div>
<link rel="stylesheet" href="bootstrap/css/bootstrap.min.css"/>
<script src="js/jquery-3.0.0.min.js" type="text/javascript"></script>
<script src="js/jquery.form.min.js" type="text/javascript"></script>
<script src="bootstrap/js/bootstrap.min.js" type="text/javascript"></script>
<script src="js/watermark.js" type="text/javascript"></script>
<script src="js/marked.min.js" type="text/javascript"></script>
<script src="js/base64.min.js" type="text/javascript"></script>
<script>
/**
* 初始化
*/
window.onload = function () {
$("#markdown_btn").hide()
initWaterMark();
loadMarkdown();
}
/**
* 初始化水印
*/
function initWaterMark() {
let watermarkTxt = '${watermarkTxt}';
if (watermarkTxt !== '') {
watermark.init({
watermark_txt: '${watermarkTxt}',
watermark_x: 0,
watermark_y: 0,
watermark_rows: 0,
watermark_cols: 0,
watermark_x_space: ${watermarkXSpace},
watermark_y_space: ${watermarkYSpace},
watermark_font: '${watermarkFont}',
watermark_fontsize: '${watermarkFontsize}',
watermark_color: '${watermarkColor}',
watermark_alpha: ${watermarkAlpha},
watermark_width: ${watermarkWidth},
watermark_height: ${watermarkHeight},
watermark_angle: ${watermarkAngle},
});
}
}
/**
* 加载markdown
*/
function loadMarkdown() {
var textData = Base64.decode($("#textData").val())
window.textPreData = "<pre style='background-color: #FFFFFF;border:none'>" + textData + "</pre>";
window.textMarkdownData = marked(textData);
$("#markdown").html(window.textMarkdownData);
}
$(function () {
$("#markdown_btn").click(function () {
$("#markdown").html(window.textMarkdownData);
$("#text_btn").show()
$("#markdown_btn").hide()
});
$("#text_btn").click(function () {
$("#markdown_btn").show()
$("#text_btn").hide();
$("#markdown").html(window.textPreData);
});
});
</script>
<style>
* {
margin: 0;
padding: 0;
}
html, body {
height: 100%;
width: 100%;
}
</style>
</body>
</html>

View File

@ -6,32 +6,29 @@
<title>普通文本预览</title>
</head>
<body>
<input hidden id="textType" value="${textType}"/>
<input hidden id="textData" value="${textData}"/>
<div class="container">
<#if textType?? && textType == "markdown">
<p>
<button id="markdown_btn" type="button" class="btn btn-primary">切换markdown</button>
<button id="text_btn" type="button" class="btn btn-primary">切换text</button>
</p>
<div id="markdown" style="padding: 18px;"></div>
<#elseif textType?? && textType == "xml" >
<div id="xml" style="padding: 18px;"></div>
<#else>
<div id="text"></div>
</#if>
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" data-parent="#accordion" href="#collapseOne">
${file.name}
</a>
</h4>
</div>
<div class="panel-body">
<div id="text"></div>
</div>
</div>
</div>
<link rel="stylesheet" href="css/xmlTreeViewer.css"/>
<link rel="stylesheet" href="bootstrap/css/bootstrap.min.css"/>
<script src="js/jquery-3.0.0.min.js" type="text/javascript"></script>
<script src="js/jquery.form.min.js" type="text/javascript"></script>
<script src="bootstrap/js/bootstrap.min.js" type="text/javascript"></script>
<script src="js/watermark.js" type="text/javascript"></script>
<script src="js/marked.min.js" type="text/javascript"></script>
<script src="js/xmlTreeViewer.js" type="text/javascript"></script>
<script src="js/base64.min.js" type="text/javascript"></script>
<script>
@ -39,12 +36,8 @@
* 初始化
*/
window.onload = function () {
$("#markdown_btn").hide()
initWaterMark();
fetchData();
loadText();
loadXmlData()
loadMarkdown();
}
/**
@ -72,56 +65,16 @@
}
}
/**
* 获取文本数据
*/
function fetchData() {
window.textData = Base64.decode($("#textData").val())
window.textPreData = "<pre>" + window.textData + "</pre>";
}
/**
*加载普通文本
*/
function loadText() {
$("#text").html(window.textPreData);
var textData = Base64.decode($("#textData").val())
var textPreData = "<pre style='background-color: #FFFFFF;border:none'>" + textData + "</pre>";
$("#text").html(textPreData);
}
/**
* 加载markdown
*/
function loadMarkdown() {
if ($("#textType").val() === "markdown") {
window.textMarkdownData = marked(window.textData);
$("#markdown").html(window.textMarkdownData);
}
}
/**
* 加载xml数据
*/
function loadXmlData() {
if ($("#textType").val() === "xml") {
var xmlNode = xmlTreeViewer.parseXML(window.textData);
var retNode = xmlTreeViewer.getXMLViewerNode(xmlNode.xml);
$("#xml").html(retNode);
}
}
$(function () {
$("#markdown_btn").click(function () {
$("#markdown").html(window.textMarkdownData);
$("#text_btn").show()
$("#markdown_btn").hide()
});
$("#text_btn").click(function () {
$("#markdown_btn").show()
$("#text_btn").hide();
$("#markdown").html(window.textPreData);
});
});
</script>
<style>
* {
@ -134,13 +87,6 @@
width: 100%;
}
#markdown, #xml {
height: 97%;
max-height: 97%;
border: 1px solid #ece7e7;
overflow-y: scroll;
width: 100%;
}
</style>
</body>

View File

@ -0,0 +1,97 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, user-scalable=yes, initial-scale=1.0">
<title>普通文本预览</title>
</head>
<body>
<input hidden id="textData" value="${textData}"/>
<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}
</a>
</h4>
</div>
<div class="panel-body">
<div id="xml"></div>
</div>
</div>
</div>
<link rel="stylesheet" href="css/xmlTreeViewer.css"/>
<link rel="stylesheet" href="bootstrap/css/bootstrap.min.css"/>
<script src="js/jquery-3.0.0.min.js" type="text/javascript"></script>
<script src="js/jquery.form.min.js" type="text/javascript"></script>
<script src="bootstrap/js/bootstrap.min.js" type="text/javascript"></script>
<script src="js/watermark.js" type="text/javascript"></script>
<script src="js/marked.min.js" type="text/javascript"></script>
<script src="js/xmlTreeViewer.js" type="text/javascript"></script>
<script src="js/base64.min.js" type="text/javascript"></script>
<script>
/**
* 初始化
*/
window.onload = function () {
initWaterMark();
loadXmlData()
}
/**
* 初始化水印
*/
function initWaterMark() {
let watermarkTxt = '${watermarkTxt}';
if (watermarkTxt !== '') {
watermark.init({
watermark_txt: '${watermarkTxt}',
watermark_x: 0,
watermark_y: 0,
watermark_rows: 0,
watermark_cols: 0,
watermark_x_space: ${watermarkXSpace},
watermark_y_space: ${watermarkYSpace},
watermark_font: '${watermarkFont}',
watermark_fontsize: '${watermarkFontsize}',
watermark_color: '${watermarkColor}',
watermark_alpha: ${watermarkAlpha},
watermark_width: ${watermarkWidth},
watermark_height: ${watermarkHeight},
watermark_angle: ${watermarkAngle},
});
}
}
/**
* 加载xml数据
*/
function loadXmlData() {
var textData = Base64.decode($("#textData").val())
var xmlNode = xmlTreeViewer.parseXML(textData);
var retNode = xmlTreeViewer.getXMLViewerNode(xmlNode.xml);
$("#xml").html(retNode);
}
</script>
<style>
* {
margin: 0;
padding: 0;
}
html, body {
height: 100%;
width: 100%;
}
</style>
</body>
</html>