Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion server/src/main/java/cn/keking/model/FileType.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public enum FileType {
MEDIACONVERT("mediaFilePreviewImpl"),
MARKDOWN("markdownFilePreviewImpl"),
XML("xmlFilePreviewImpl"),
JSON("jsonFilePreviewImpl"),
CAD("cadFilePreviewImpl"),
TIFF("tiffFilePreviewImpl"),
OFD("ofdFilePreviewImpl"),
Expand All @@ -44,12 +45,13 @@ public enum FileType {
private static final String[] DCM_TYPES = {"dcm"};
private static final String[] DRAWIO_TYPES = {"drawio"};
private static final String[] XML_TYPES = {"xml","xbrl"};
private static final String[] JSON_TYPES = {"json"};
private static final String[] TIFF_TYPES = {"tif", "tiff"};
private static final String[] OFD_TYPES = {"ofd"};
private static final String[] SVG_TYPES = {"svg"};
private static final String[] CAD_TYPES = {"dwg", "dxf", "dwf", "iges", "igs", "dwt", "dng", "ifc", "dwfx", "stl", "cf2", "plt"};
private static final String[] SSIM_TEXT_TYPES = ConfigConstants.getSimText();
private static final String[] CODES = {"java", "c", "php", "go", "python", "py", "js", "html", "ftl", "css", "lua", "sh", "rb", "yaml", "yml", "json", "h", "cpp", "cs", "aspx", "jsp", "sql"};
private static final String[] CODES = {"java", "c", "php", "go", "python", "py", "js", "html", "ftl", "css", "lua", "sh", "rb", "yaml", "yml", "h", "cpp", "cs", "aspx", "jsp", "sql"};
private static final String[] MEDIA_TYPES = ConfigConstants.getMedia();
public static final String[] MEDIA_CONVERT_TYPES = ConfigConstants.getConvertMedias();
private static final Map<String, FileType> FILE_TYPE_MAPPER = new HashMap<>();
Expand Down Expand Up @@ -109,6 +111,9 @@ public enum FileType {
for (String xml : XML_TYPES) {
FILE_TYPE_MAPPER.put(xml, FileType.XML);
}
for (String json : JSON_TYPES) {
FILE_TYPE_MAPPER.put(json, FileType.JSON);
}
FILE_TYPE_MAPPER.put("md", FileType.MARKDOWN);
FILE_TYPE_MAPPER.put("pdf", FileType.PDF);
FILE_TYPE_MAPPER.put("bpmn", FileType.BPMN);
Expand Down
1 change: 1 addition & 0 deletions server/src/main/java/cn/keking/service/FilePreview.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public interface FilePreview {
String CODE_FILE_PREVIEW_PAGE = "code";
String EXEL_FILE_PREVIEW_PAGE = "html";
String XML_FILE_PREVIEW_PAGE = "xml";
String JSON_FILE_PREVIEW_PAGE = "json";
String MARKDOWN_FILE_PREVIEW_PAGE = "markdown";
String BPMN_FILE_PREVIEW_PAGE = "bpmn";
String DCM_FILE_PREVIEW_PAGE = "dcm";
Expand Down
Original file line number Diff line number Diff line change
@@ -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 (http://kailing.pub)
* @since 2025/01/11
* JSON 文件预览处理实现
*/
@Service
public class JsonFilePreviewImpl implements FilePreview {

private final SimTextFilePreviewImpl simTextFilePreview;

public JsonFilePreviewImpl(SimTextFilePreviewImpl simTextFilePreview) {
this.simTextFilePreview = simTextFilePreview;
}

@Override
public String filePreviewHandle(String url, Model model, FileAttribute fileAttribute) {
simTextFilePreview.filePreviewHandle(url, model, fileAttribute);
return JSON_FILE_PREVIEW_PAGE;
}
}
214 changes: 214 additions & 0 deletions server/src/main/resources/web/json.ftl
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
<!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>JSON文件预览</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>
<script src="js/base64.min.js" type="text/javascript"></script>
<style>
body {
font-family: 'Courier New', Courier, monospace;
}
.container {
max-width: 100%;
padding: 20px;
}
.panel-body {
padding: 0;
}
#json {
padding: 20px;
background-color: #f8f9fa;
overflow-x: auto;
}
#text_view {
padding: 20px;
background-color: #ffffff;
overflow-x: auto;
}
pre {
margin: 0;
white-space: pre-wrap;
word-wrap: break-word;
font-size: 14px;
line-height: 1.6;
}
.json-key {
color: #881391;
font-weight: bold;
}
.json-string {
color: #1A1AA6;
}
.json-number {
color: #1C00CF;
}
.json-boolean {
color: #0D22FF;
font-weight: bold;
}
.json-null {
color: #808080;
font-weight: bold;
}
.btn-group {
margin-bottom: 10px;
}
.view-mode-btn {
min-width: 100px;
}
</style>
</head>
<body>

<input hidden id="textData" value="${textData}"/>
<div class="container">
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
${file.name}
</h4>
<div class="btn-group" role="group">
<button type="button" class="btn btn-primary view-mode-btn" id="formatted_btn">格式化视图</button>
<button type="button" class="btn btn-default view-mode-btn" id="raw_btn">原始文本</button>
</div>
</div>
<div class="panel-body">
<div id="json"></div>
<div id="text_view" style="display:none;"></div>
</div>
</div>
</div>

<script>
/**
* 初始化
*/
window.onload = function () {
initWaterMark();
loadJsonData();
}

/**
* HTML 反转义(用于还原后端转义的内容)
*/
function htmlUnescape(str) {
if (!str || str.length === 0) return "";
var s = str;
s = s.replace(/&quot;/g, '"');
s = s.replace(/&#39;/g, "'");
s = s.replace(/&lt;/g, "<");
s = s.replace(/&gt;/g, ">");
s = s.replace(/&amp;/g, "&");
return s;
}

/**
* HTML 转义(用于安全显示)
*/
function htmlEscape(str) {
if (!str || str.length === 0) return "";
var s = str;
s = s.replace(/&/g, "&amp;");
s = s.replace(/</g, "&lt;");
s = s.replace(/>/g, "&gt;");
s = s.replace(/"/g, "&quot;");
s = s.replace(/'/g, "&#39;");
return s;
}

/**
* 移除 BOM (Byte Order Mark)
*/
function removeBOM(str) {
if (str.charCodeAt(0) === 0xFEFF) {
return str.substring(1);
}
return str;
}

/**
* JSON 语法高亮
*/
function syntaxHighlight(json) {
json = json.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) {
var cls = 'json-number';
if (/^"/.test(match)) {
if (/:$/.test(match)) {
cls = 'json-key';
} else {
cls = 'json-string';
}
} else if (/true|false/.test(match)) {
cls = 'json-boolean';
} else if (/null/.test(match)) {
cls = 'json-null';
}
return '<span class="' + cls + '">' + match + '</span>';
});
}

/**
* 加载 JSON 数据
*/
function loadJsonData() {
try {
var textData = Base64.decode($("#textData").val());

// 1. 先反转义 HTML 实体(因为后端已经转义过)
textData = htmlUnescape(textData);

// 2. 移除 BOM
textData = removeBOM(textData);

// 保存原始文本(用于显示时再次转义以保证安全)
window.rawText = "<pre>" + htmlEscape(textData) + "</pre>";

// 尝试解析并格式化 JSON
try {
var jsonObj = JSON.parse(textData);
var formattedJson = JSON.stringify(jsonObj, null, 4);
window.formattedJson = "<pre>" + syntaxHighlight(formattedJson) + "</pre>";

// 默认显示格式化视图
$("#json").html(window.formattedJson);
} catch (e) {
// 如果不是有效的 JSON,显示错误并回退到原始文本
window.formattedJson = "<div class='alert alert-warning'>JSON 解析失败: " + htmlEscape(e.message) + "</div>" + window.rawText;
$("#json").html(window.formattedJson);
}

} catch (e) {
$("#json").html("<div class='alert alert-danger'>文件加载失败: " + htmlEscape(e.message) + "</div>");
}
}

/**
* 按钮点击事件
*/
$(function () {
$("#formatted_btn").click(function () {
$("#json").show();
$("#text_view").hide();
$("#json").html(window.formattedJson);
$("#formatted_btn").removeClass("btn-default").addClass("btn-primary");
$("#raw_btn").removeClass("btn-primary").addClass("btn-default");
});

$("#raw_btn").click(function () {
$("#json").hide();
$("#text_view").show();
$("#text_view").html(window.rawText);
$("#raw_btn").removeClass("btn-default").addClass("btn-primary");
$("#formatted_btn").removeClass("btn-primary").addClass("btn-default");
});
});
</script>
</body>

</html>
Loading