前言:前几天编写一个UI模板控制的功能,根据上传的前端模板更换跳转入口主题页面;在编写的时候,突发奇想能不能在列表页面进行在线编辑刚刚上传的模板zip压缩包里的页面...于是经过学习研究有了这篇文章;当日记本一样记下来方便以后自己需要时取用。
CodeMirror介绍:
CodeMirror 是一个强大的基于浏览器的文本编辑器组件,主要用于网页中创建可编辑的源代码区域,特别适用于编写和展示程序代码。它支持多种编程语言的语法高亮、代码折叠、自动补全、查找替换等多种高级编辑特性。
使用CodeMirror:
基本使用步骤:
-
引入资源:
- 引入 CodeMirror 的 JavaScript 文件以及对应的 CSS 样式文件。这可以通过
<script>
和<link>
标签在 HTML 中直接引用,或者使用模块打包工具如 Webpack 进行管理。
1<!-- 引入核心CSS --> 2<link rel="stylesheet" href="path/to/codemirror.css"> 3 4<!-- 引入CodeMirror的核心脚本 --> 5<script src="path/to/codemirror.js"></script> 6 7<!-- 引入对应语言模式的脚本,例如JavaScript模式 --> 8<script src="path/to/mode/javascript/javascript.js"></script>
- 引入 CodeMirror 的 JavaScript 文件以及对应的 CSS 样式文件。这可以通过
-
初始化编辑器:
- 将一个
textarea
元素转换为 CodeMirror 编辑器。
1// 获取HTML中的textarea元素 2var textarea = document.getElementById('code-editor'); 3 4// 创建CodeMirror实例并配置选项 5var editor = CodeMirror.fromTextArea(textarea, { 6 mode: 'text/javascript', // 设置语言模式 7 theme: 'default', // 设置主题样式,需确保已引入相应主题的CSS 8 lineNumbers: true, // 显示行号 9 styleActiveLine: true, // 高亮当前行 10 indentUnit: 4, // 缩进单位大小 11 smartIndent: true, // 智能缩进 12 // ... 更多配置项 13});
- 将一个
-
可选扩展和配置:
- 如果需要特定的功能,比如自动补全或linting,可能需要额外引入相关插件,并在初始化时进行配置。
-
刷新编辑器: 当编辑器所在的DOM环境发生变化时,需要调用
.refresh()
方法来更新编辑器的尺寸和布局。
我的编写案例:
<head>
<link rel="stylesheet" href="https://www.layuicdn.com/layui-v2.9.8/css/layui.css">
<style>
/* 基本样式 */
.left-pane .file-item {
cursor: pointer;
padding: 5px 10px;
border: 1px solid #d78080;
border-radius: 3px;
transition: background-color 0.2s ease-in-out;
font-size: 14px;
color: #333;
/* 增加内阴影、文字装饰线等效果 */
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1);
text-decoration: none;
outline: none;
}
/* 文件名一行显示不完时的溢出处理 */
.left-pane .file-name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.layout-container {
position: relative;
display: flex;
}
/* 默认状态下样式 */
.left-pane .file-item:not(.active):not(:hover) {
background-color: #fff;
}
/* 鼠标悬停时的样式 */
.left-pane .file-item:hover {
background-color: rgba(101, 152, 202, 0.8);
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.2);
}
#fileListContainer {
height: 700px;
overflow-y: auto;
}
/* 被选中(active)时的样式 */
.left-pane .file-item.active {
background-color: #e0e0e0;
font-weight: bold;
}
/* 边框高亮效果 */
.left-pane .file-item:focus,
.left-pane .file-item.focus-visible {
outline: none;
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.3);
}
.left-pane {
overflow-y: auto;
}
.right-pane {
display: flex;
flex-direction: column;
width: 100%;
height: calc(100% - 20px); /* 减去底部留白空间 */
overflow-y: hidden; /* 隐藏滚动条 */
}
/*.auto-resize-textarea {*/
/* width: 100%;*/
/* box-sizing: border-box;*/
/* resize: none;*/
/* padding: 10px;*/
/* font-family: monospace; !* 也可以换成你喜欢的字体 *!*/
/* border: 1px solid #ccc;*/
/* border-radius: 3px;*/
/*}*/
</style>
</head>
<div class="layui-container">
<div class="layui-row ">
<table id="templateTable" lay-filter="templateTable"></table>
<div class="layui-col-md12">
<div class="layui-card">
<div class="layui-card-header">上传UI模板</div>
<div class="layui-card-body">
<form class="layui-form" action="/codeEdit/uiTemplate" method="post"
enctype="multipart/form-data">
<div class="layui-form-item">
<label class="layui-form-label">选择ZIP文件</label>
<div class="layui-upload">
<div class="layui-upload-drag" style="display: block;" id="uploadBtn">
<i class="layui-icon layui-icon-upload"></i>
<div>点击上传,或将文件拖拽到此处</div>
<div class="layui-upload-list">
<p id="uploadDemoText"></p>
<input type="file" id="fileInput" style="display:none;" accept="application/zip"
lay-exts="zip">
</div>
</div>
</div>
</div>
<div class="layui-form-item " style="text-align: center">
<div class="layui-input-block ">
<button class="layui-btn " lay-submit lay-filter="formSubmit">立即提交</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
<div id="loadingLayer" style="display:none;"></div>
</div>
<!-- 引入 layui.js -->
<script src="//unpkg.com/layui@2.9.8/dist/layui.js"></script>
<script>
layui.use(['layer', 'form', 'upload', 'table'], function () {
var $ = layui.$,
layer = layui.layer,
form = layui.form,
upload = layui.upload,
table = layui.table;
console.log(111)
var fileArry = []
// 初始化上传按钮
var uploadInst = upload.render({
elem: '#uploadBtn',
url: '', // 留空,因为我们将在表单提交时指定此URL
accept: 'file',
exts: 'zip',
field: 'file', // 与表单中文件输入框的name属性一致
before: function (obj) {
fileArry = [];
obj.preview(function (index, file, result) {
$('#uploadDemoText').text(file.name);
fileArry.push(file)
// $('#fileInput').val(file.name); // 将文件名写入隐藏输入框
});
},
error: function () {
layer.msg('选择成功!');
}
});
// 表单提交事件监听
form.on('submit(formSubmit)', function (data) {
// 检查是否有文件被选中
if (!fileArry.length) {
layer.alert('请先选择一个ZIP文件再提交!');
return false;
}
// 创建FormData对象
var formData = new FormData();
// 将表单数据添加到FormData对象
// for (var key in data.field) {
// formData.append(key, data.field[key]);
// }
formData.append('userName', $.cookie("userName"))
// 将文件添加到FormData对象
formData.append('file', fileArry[0]);
console.log(formData)
// 发送POST请求
$.ajax({
url: '/codeEdit/uiTemplate',
type: 'POST',
data: formData,
cache: false,
contentType: false, // 必须设置为false,让浏览器自动处理content-type
processData: false, // 必须设置为false,防止jQuery对FormData进行序列化处理
success: function (response) {
if (response.status === 'success') {
// 提交成功后的操作
}
if (response.success === false) {
layer.msg(response.info)
} else {
layer.msg(response.msg);
}
refreshTableData();
},
error: function () {
layer.alert('提交失败,请稍候重试');
}
});
// 阻止表单默认提交
return false;
});
renderTableData();
//列表刷新
function renderTableData() {
// 假设从后端获取数据
$.ajax({
url: '/codeEdit/uiTemplate',
type: 'GET',
success: function (res) {
if (res.code === 0) { // 根据实际情况判断后端响应状态
var data = res.data.records; // 假设返回结果中的数据在records字段中
var data = res.data.records.map(item => ({
id: item.id,
name: item.name,
userName: item.userName,
createdDate: moment(item.createdDate).format('YYYY-MM-DD HH:mm:ss'),
updatedDate: moment(item.updatedDate).format('YYYY-MM-DD HH:mm:ss'), // 使用Moment.js格式化日期
status: item.status === 1 ? '启用' : '未启用',
}));
// 渲染表格
table.render({
elem: '#templateTable'
, cols: [[ // 表头
{field: 'id', align: 'center', title: 'ID', width: 200}
, {field: 'name', align: 'center', title: '模板名称', width: 200}
// , {field: 'userName', align: 'center', title: '模板创建人', width: 100}
, {field: 'createdDate', align: 'center', title: '模板创建日期', width: 210}
, {field: 'updatedDate', align: 'center', title: '模板修改日期', width: 210}
, {field: 'status', align: 'center', title: '启用状态', width: 100}
, {
fixed: 'right',
align: 'center',
width: 300,
title: '操作',
templet: function () {
return '' +
// '<a class="layui-btn layui-btn-sm layui-btn-normal edit" lay-event="edit">编辑</a>' +
'<a class="layui-btn layui-btn-sm layui-btn-normal edit" lay-event="update">更新</a>' +
// '<a class="layui-btn layui-btn-sm layui-btn-warm preview" lay-event="preview">预览</a>' +
'<a class="layui-btn layui-btn-sm" lay-event="enable" style="background-color: #5FB878;">启用</a>' +
'<a class="layui-btn layui-btn-danger layui-btn-sm" lay-event="del">删除</a>';
}
}
// 其他字段根据实际情况添加
]]
, data: data
, page: true // 开启分页
});
// 绑定按钮点击事件
table.on('tool(templateTable)', function (obj) {
var data = obj.data; // 获取当前行数据
var event = obj.event; // 获取事件名
if (event === 'edit') {
// 编辑操作的处理逻辑
editThisPage(data);
} else if (event === 'update') {
handleUpdateEvent(data)
// 更新操作的处理逻辑
} else if (event === 'preview') {
// 预览操作的处理逻辑
} else if (event === 'enable') {
// 启用操作的处理逻辑
layer.confirm('确定启用所选模板并禁用其他已启用模板吗?', function (index) {
var ids = data.id;
// 发送启用请求
$.ajax({
url: '/codeEdit/uiTemplate/startUi',
type: 'POST',// 根据你的后端接口决定
data: {id: ids}, // 将ids转换为字符串传给后端
success: function (res) {
if (res.code === 0) {
layer.close(index);
layer.msg('启用成功,其他模板已禁用');
// 可以在此处根据需要刷新表格数据
refreshTableData();
} else if (res.code === -1) {
layer.msg(res.msg);
} else {
layer.msg(res.info)
}
},
error: function (err) {
layer.msg('网络请求出错,请稍后再试');
}
});
});
} else if (event === 'del') {
// 删除操作的处理逻辑
layer.confirm('确定删除该模板吗?', function (index) {
var idList = []
idList.push(data.id)
// 发送删除请求
$.ajax({
url: '/codeEdit/uiTemplate?idList=' + idList + '', // 这里对应你之前定义的Java后端删除接口
type: 'DELETE',
success: function (res) {
if (res.code === 0) {
obj.del(); // 删除表格对应行(客户端)
layer.close(index);
layer.msg('删除成功');
} else if (res.code === -1) {
layer.msg('删除失败:' + res.msg + '');
} else {
layer.msg('删除失败:' + res.msg + '');
}
}
});
});
}
});
// 定义对应的操作函数,如删除模板函数deleteTemplate
function deleteTemplate(templateId) {
// 发起删除请求的逻辑
}
} else {
layer.msg('获取数据失败:' + res.message);
}
},
error: function (err) {
layer.msg('请求失败,请检查网络');
}
});
}
var codeEditor;
//edit this pages
function editThisPage(data) {
// 获取路径下的文件夹内容(这里假设你已经有了一个服务端接口可以返回文件列表)
$.getJSON('/codeEdit/uiTemplate/editThisPage?id=' + data.id + '', function (response) {
if (response.code === 0) {
var fileListData = response.data.fileList;
var fileList = '';
for (var i = 0; i < fileListData.length; i++) {
var item = fileListData[i];
fileList += '<div class="file-item" data-path="' + item + '">' + item.split('\\').pop() + '</div>';
}
// 创建左右布局的弹窗内容
var layout = '<div class="layout-container">' +
'<div class="left-pane" id="fileListContainer">' + fileList + '</div>' +
'<div class="right-pane"><textarea id="content-editor" class="auto-resize-textarea" style="display: none"></textarea></div>' +
'</div>';
var windowWidth = $(window).width(); // 获取当前窗口宽度
var windowHeight = $(window).height(); // 获取当前窗口高度
// 计算弹窗的宽度和高度
var dialogWidth = Math.floor(windowWidth * 0.7); // 页面宽度的70%
var dialogHeight = Math.floor(windowHeight * 0.8); // 页面高度的60%
var index = layer.open({
type: 1,
title: '' + data.name + '--选择文件进行编辑--在线编辑器',
content: layout,
area: [dialogWidth + 'px', dialogHeight + 'px'],
btn: ['保存', '关闭'], // 添加“保存”按钮
maxmin: true,
shade: 0,
yes: function (index, layero) {
// 获取所有处于活动状态的文件项元素
var activeFileItems = $('#fileListContainer .file-item.active');
// 通常情况下,假设只有一个文件项可以处于激活状态
var activeFileItem = activeFileItems.first();
// 获取并设置要保存的文件路径
var filePathActive = activeFileItem.data('path'); // 使用jQuery的data方法读取data-path属性
svaFile(filePathActive,data.id);
return false;
},
success: function (layero, index) {
// XhEdit();
codeMirror();
// 为文件列表项添加点击事件
$('#fileListContainer .file-item').on('click', function () {
$(this).toggleClass('active');
$('#content-editor').hide(); // 隐藏编辑区域内容
});
codeEditor.setValue("请选择需要修改的文件进行编辑,点击保存后会对该压缩包里的原始文件进行修改")
},
btn2: function (index, layero) {
layer.close(index);
},
});
// CSS样式(可在外部CSS文件中添加,这里仅作示例)
$('.layout-container').css({
display: 'flex',
flexDirection: 'row'
});
$('.left-pane').css({
width: '25%',
padding: '10px',
borderRight: '1px solid #ccc'
});
$('.right-pane').css({
width: '75%',
padding: '10px'
});
$('#fileListContainer').on('click', '.file-item', function () {
$('#fileListContainer .file-item').removeClass('active'); // 移除所有项的active类
$(this).addClass('active'); // 给当前点击的项添加active类
var selectedFile = $(this).attr('data-path');
let fileExtension = selectedFile.substring(selectedFile.lastIndexOf('.') + 1).toLowerCase();
if (localStorage.length > 20) {
clearOneCachedFile("cachedFile_");
}
if (['gif', 'png', 'svg','jpg'].includes(fileExtension)) {
layer.msg('图片格式无法编辑');
$('#fileListContainer .file-item').removeClass('active');
}else {
// 检查缓存
let cachedContent = localStorage.getItem(`cachedFile_${selectedFile}`);
if (cachedContent !== null) {
loadContentFromCache(cachedContent, codeEditor);
} else {
loadAndEditFile(selectedFile, codeEditor);
}
}
});
} else {
alert('获取文件列表失败,错误信息:' + response.msg);
}
});
}
function loadContentFromCache(fileContent, editorElement, onLoadCallback) {
editorElement.setValue('');
editorElement.setValue(fileContent);
if (onLoadCallback) {
onLoadCallback();
}
}
// 需要优化,请求逻辑,减少接口请求,考虑加载到本地缓存
function loadAndEditFile(filePath, editorElement, onLoadCallback) {
// 这里从服务器获取文件内容的代码,并将其填入editorElement(
$.get('/codeEdit/uiTemplate/fileContent?filePath=' + encodeURIComponent(filePath), function (fileContent) {
localStorage.setItem(`cachedFile_${filePath}`, fileContent); // 存储到缓存
// 清除编辑器中原有的内容
editorElement.setValue('');
// 将获取的文件内容填充到 CodeMirror 编辑器中
editorElement.setValue(fileContent);
if (onLoadCallback) {
onLoadCallback();
}
});
}
//老版 不美观难用 弃用
function XhEdit() {
// 初始化XHEditor
$("#content-editor").xheditor({
tools: 'simple',
skin: 'vista', // 皮肤
xheditor_lang: 'zh-cn', // 语言(这里是中文)
width: '100%', // 设置编辑器宽度为100%
height: '800px', // 自动适应高度,但这需要你自己实现,XHEditor本身可能不支持高度自适应
upImgUrl: '/upload.php', // 图片上传地址(如果有图片上传功能的话)
sourceMode: true,
backgroundColor: '#94cae8',
});
// 如果需要高度自适应,可以监听编辑器内容变化并手动调整高度
$("#content-editor").on('keyup change', function () {
var editor = $(this).xheditor(false);
var iframeBody = editor.xhe().document.body;
var newHeight = iframeBody.scrollHeight + 'px';
editor.height("800px");
});
};
//新版
function codeMirror() {
// 获取编辑器容器元素
const textarea = document.getElementById('content-editor');
// 初始化 CodeMirror 编辑器
codeEditor = CodeMirror.fromTextArea(textarea, {
mode: 'text/javascript', // 设置语言模式,这里是 JavaScript
theme: 'erlang-dark', // 设置主题
indentUnit: 4, // 缩进多少个空格
tabSize: 4, // 制表符宽度
lineNumbers: true, // 显示行号
lineWrapping: true, //代码折叠
foldGutter: true,
matchBrackets: true,
styleActiveLine: true, // 高亮行功能
scrollbarStyle: 'overlay',
gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"],
});
codeEditor.setSize('100%', '100%')
}
//编辑后保存
function svaFile(saveFilePath,itemId) {
if (typeof saveFilePath==='undefined'){
console.error('无法获取到激活文件项的路径');
layer.msg('没有选中文件!');
return false;
}
var editFileContent = codeEditor.getValue();
// 显示加载动画
var loadingIndex = layer.load(2, {shade: [0.1, '#fff']}); // 第二个参数是loading样式
$('#loadingLayer').show();
$.ajax({
url: '/codeEdit/uiTemplate/saveFileContent',
method: 'POST',
data:JSON.stringify({filePath: saveFilePath, fileContent: editFileContent,id:itemId}) ,
contentType: 'application/json', // 设置请求体内容类型为JSON
success: function (response) {
localStorage.setItem(`cachedFile_${saveFilePath}`, editFileContent); // 更新到缓存
if (response.code === 0) {
layer.msg('文件保存成功!');
} else {
layer.alert( response.msg);
}
// 关闭加载动画
layer.close(loadingIndex);
$('#loadingLayer').hide();
},
error: function (xhr, status, error) {
layer.alert('网络请求错误,请稍后重试。');
// 关闭加载动画
layer.close(loadingIndex);
$('#loadingLayer').hide();
},
complete: function () {
// 请求无论成功还是失败都关闭加载动画
layer.close(loadingIndex);
$('#loadingLayer').hide();
}
});
}
// 前端更新操作逻辑
function handleUpdateEvent(obj) {
var rowData = obj; // 获取当前行数据
var fileIdInput = document.createElement('input');
fileIdInput.type = 'file';
fileIdInput.style.display = 'none';
// 触发文件选择对话框
fileIdInput.click();
fileIdInput.onchange = function () {
var file = this.files[0];
if (file) {
var formData = new FormData();
formData.append('file', file);
// formData=rowData;
Object.keys(rowData).forEach(key => {
let timestamp;
if (key === 'createdDate' || key === 'updatedDate') {
let date = new Date(rowData[key].replace(/-/g, "/")); // JavaScript Date 对象需要"/"分隔符而不是"-"
// let timestamp = date.getTime(); // 获取时间戳(毫秒)
formData.append(key, date);
} else if (key === 'status') {
formData.append(key, rowData[key] === '启用' ? 1 : 2);
} else {
formData.append(key, rowData[key]);
}
});
// formData['file']=file
var xhr = new XMLHttpRequest();
xhr.open('PUT', '/codeEdit/uiTemplate', true);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
var response = xhr.responseText;
response = JSON.parse(response)
if (response.code === 0) {
layer.msg('模板更新成功');
refreshTableData();
} else {
layer.msg(response.msg);
}
} else if (xhr.status !== 200) {
layer.msg('模板更新失败,请稍后再试');
}
};
xhr.send(formData);
} else {
layer.msg('未选择任何文件');
}
};
// fileIdInput.click();
}
function refreshTableData() {
renderTableData();
}
//传入指定 key 删除所有缓存
function clearAllCachedFiles(cacheName) {
for (let i = localStorage.length - 1; i >= 0; i--) {
let key = localStorage.key(i);
if (key.startsWith(cacheName)) {
localStorage.removeItem(key);
}
}
}
//传入指定 key 随机删除key的缓存
function clearOneCachedFile(cacheName) {
let randomIndex = Math.floor(Math.random() * (20 - 1 + 1)) + 1;
for (let i = 0; i <= localStorage.length; i++) {
let key = localStorage.key(i);
if (key.startsWith(cacheName) && i === randomIndex) {
localStorage.removeItem(key);
console.log('删除缓存:' + key + '' + randomIndex + '');
break;
}
}
}
});
</script>
后端代码:用java实现的服务接口
/**
* @className: UiTemplateController
* @description: TODO 前台UI模板控制层
* @author: albertLuo
* @date: 2024/04/03
* @Company: Copyright© 2024/4/3 by LuoTao
**/
@RestController
@Slf4j
@RequestMapping("uiTemplate")
public class UiTemplateController extends ApiController {
/**
* 服务对象
*/
@Resource
private UiTemplateService uiTemplateService;
private String rootPath;
/**
* 分页查询所有数据
*
* @param page 分页对象
* @param uiTemplate 查询实体
* @return 所有数据
*/
@GetMapping
public R selectAll(Page<UiTemplate> page, UiTemplate uiTemplate, HttpServletRequest request) {
rootPath = request.getServletContext().getRealPath("/") + File.separator;
String userName = SessionInfo.getUserName();
QueryWrapper<UiTemplate> queryWrapper = new QueryWrapper<>(uiTemplate);
queryWrapper.eq("user_Name", userName);
return success(this.uiTemplateService.page(page, queryWrapper));
}
/**
* 通过主键查询单条数据
*
* @param id 主键
* @return 单条数据
*/
@GetMapping("{id}")
public R selectOne(@PathVariable Serializable id) {
return success(this.uiTemplateService.getById(id));
}
/**
* 新增数据
*
* @param uiTemplate 实体对象
* @return 新增结果
*/
@PostMapping
@ResponseBody
public R insert(UiTemplate uiTemplate, @RequestParam("file") MultipartFile uploadedFile, HttpServletRequest request) {
rootPath = request.getServletContext().getRealPath("/") + File.separator;
// 参数非空判断
if (uiTemplate == null || uploadedFile.isEmpty()) {
logger.info("缺少必要参数,请确保UiTemplate实体和文件都不为空");
return failed("缺少必要参数,请确保UiTemplate实体和文件都不为空");
}
// 获取当前时间戳(单位:毫秒)
long timestamp = System.currentTimeMillis();
uiTemplate.setId(timestamp);
uiTemplate.setPackageName(uploadedFile.getOriginalFilename());
// 检查数据库中是否已存在同名的UiTemplate
boolean exists = this.uiTemplateService.existsByTemplateName(uiTemplate.getPackageName());
if (exists) {
logger.info("该模板名称已存在,请更换后再试!");
return failed("该模板名称已存在,请更换后再试!");
}
String savePath = rootPath + "uiTemplate" + File.separator;
String fileName = uploadedFile.getOriginalFilename();
Path filePath = Paths.get(savePath, fileName);
// 创建目标目录(如果不存在)
File dir = filePath.getParent().toFile();
if (!dir.exists()) {
boolean mkdirsResult = dir.mkdirs();
if (!mkdirsResult) {
logger.info("无法创建目录: " + dir.getAbsolutePath());
return failed("无法创建目录: " + dir.getAbsolutePath());
}
}
uiTemplate.setPackageUrl(filePath.toString().split("webapp")[1]);
// File tempFile = null;
// 保存文件到指定路径
try {
// 将MultipartFile转换为临时文件以供ZipFile读取
// tempFile = File.createTempFile("temp-", ".zip");
// uploadedFile.transferTo(tempFile);
// ZipFile zipFile = new ZipFile(tempFile);
// List<FileHeader> fileHeaders = zipFile.getFileHeaders();
//
//
// // 检查ZIP文件内的目录结构是否符合标准
// for (FileHeader fileHeader : fileHeaders) {
// String entryName = fileHeader.getFileName();
// // 确保所有的条目都在一个特定的根目录下
// if (entryName.startsWith("static/")||entryName.endsWith(".html")) {
//
// }else{
// logger.info("上传的ZIP文件内部结构不符合标准");
// return failed("上传的ZIP文件内部结构不符合标准=>"+entryName);
// }
// }
// FileUtil.copy(tempFile.getAbsolutePath(),filePath.toAbsolutePath().toString(),true);
uploadedFile.transferTo(new File(filePath.toAbsolutePath().toString()));
} catch (IOException e) {
logger.info("文件保存失败:" + e.getMessage());
return failed("文件保存失败:" + e.getMessage());
}finally {
// 删除临时文件
// tempFile.deleteOnExit();
}
// 不存在则进行保存操作
boolean saveSuccess = this.uiTemplateService.save(uiTemplate);
if (saveSuccess) {
return success("新增成功");
} else {
logger.info("新增失败,请稍后重试");
return failed("新增失败,请稍后重试");
}
}
/**
* 修改数据
*
* @param uiTemplate 实体对象
* @return 修改结果
*/
@PutMapping
@ResponseBody
public R update(UiTemplate uiTemplate, @RequestParam("file") MultipartFile uploadedFile, HttpServletRequest request) {
rootPath = request.getServletContext().getRealPath("/") + File.separator;
// 参数非空判断
if (uiTemplate == null || uploadedFile.isEmpty()) {
logger.info("缺少必要参数,请确保UiTemplate实体和文件都不为空");
return failed("缺少必要参数,请确保UiTemplate实体和文件都不为空");
}
long timestamp = System.currentTimeMillis();
Date currentDate = new Date(timestamp);
uiTemplate.setUpdatedDate(currentDate);
String savePath = rootPath + "uiTemplate" + File.separator;
UiTemplate uiTemplateGet = this.uiTemplateService.getById(uiTemplate.getId());
if (uiTemplateGet == null) {
logger.info("该数据异常,或者已损坏!");
return failed("该数据异常,或者已损坏!");
}
String fileName = uploadedFile.getOriginalFilename();
if (!uiTemplateGet.getPackageName().equals(fileName)) {
return failed("请上传同名zip文件");
}
Path filePath = Paths.get(savePath, fileName);
// 创建目标目录(如果不存在)
File dir = filePath.getParent().toFile();
if (!dir.exists()) {
boolean mkdirsResult = dir.mkdirs();
if (!mkdirsResult) {
return failed("无法创建目录: " + dir.getAbsolutePath());
}
}
uiTemplate.setPackageUrl(filePath.toString().split("webapp")[1]);
// 保存文件到指定路径
try {
uploadedFile.transferTo(new File(filePath.toAbsolutePath().toString()));
} catch (IOException e) {
logger.info("文件保存失败:" + e.getMessage());
return failed("文件保存失败:" + e.getMessage());
}
return success(this.uiTemplateService.updateById(uiTemplate));
}
/**
* 删除数据
*
* @param idList 主键结合 目前只做一个删除
* @return 删除结果
*/
@DeleteMapping
public R delete(@RequestParam("idList") List<Long> idList, HttpServletRequest request) {
// 参数非空判断
if (idList == null) {
return failed("缺少必要参数,请确保id参数不为空");
}
rootPath = request.getServletContext().getRealPath("/") + File.separator;
UiTemplate uiTemplateGet = this.uiTemplateService.getById(idList.get(0));
if (uiTemplateGet == null) {
return failed("该数据异常,或者已损坏!,将自动删除");
}
if (fileUtils.deleteFile(new File((rootPath + uiTemplateGet.getPackageUrl())))) {
return success(this.uiTemplateService.removeByIds(idList));
} else {
return success("内部zip文件删除失败"+this.uiTemplateService.removeByIds(idList));
}
}
//todo 启用
@ResponseBody
@Transactional
@RequestMapping("/startUi")
public R startUiTemplate(@RequestParam("id") String id, HttpServletRequest request) {
rootPath = request.getServletContext().getRealPath("/") + File.separator;
// 参数非空判断
if (id == null) {
return failed("缺少必要参数,请确保id参数不为空");
}
UiTemplate uiTemplateGet = this.uiTemplateService.getById(id);
if (uiTemplateGet == null) {
return failed("该数据异常,或者已损坏!");
}
String savePath = rootPath + uiTemplateGet.getPackageUrl();
Path filePath = Paths.get(savePath);
File zipFile = filePath.toFile();
// 检查ZIP文件是否存在
if (!zipFile.exists()) {
return failed("此路径下的ZIP文件不存在:" + zipFile.getAbsolutePath());
}
String targetPath = rootPath + "pages" + File.separator + "student" + File.separator;
String staticTargetPath = rootPath + "static" + File.separator;
// 创建目标目录(如果不存在)
File targetDir = new File(targetPath);
if (!targetDir.exists()) {
boolean mkdirsSuccess = targetDir.mkdirs();
if (!mkdirsSuccess) {
return failed("无法创建目标目录:" + targetDir.getAbsolutePath());
}
}
try (ZipFile zip = new ZipFile(filePath.toFile())) {
ZipParameters parameters = new ZipParameters();
List<FileHeader> fileHeaders = zip.getFileHeaders();
String dirName = fileHeaders.get(0).getFileName().split("/")[0];
//一系列文件操作
zip.extractAll(targetDir.toString());
// fileUtils.copyDir(targetPath + File.separator + dirName, targetPath);
fileUtils.copyDir(targetPath + File.separator + "static", staticTargetPath);
File file2 = new File(targetPath + File.separator + dirName);
// fileUtils.deleteFile(file2);
file2 = new File(targetPath + File.separator + "static");
fileUtils.deleteFile(file2);
//
// for (FileHeader fileHeader : fileHeaders) {
// String entryName = fileHeader.getFileName();
//
// File entryDestination = entryDestination = new File(targetDir, entryName);
// System.out.println("entryName.split(\"/\")[1] = " + entryName);
//
// entryDestination.mkdirs();
// try (InputStream inputStream = zip.getInputStream(fileHeader);
// OutputStream outputStream = new FileOutputStream(entryDestination)) {
// IOUtils.copy(inputStream, outputStream);
// } catch (IOException e) {
// return failed("解压文件'" + entryName + "'时发生错误:" + e.getMessage());
// }
// }
} catch (IOException e) {
return failed("解压ZIP文件时发生错误:" + e.getMessage());
}
// 更新当前记录为启用状态
uiTemplateGet.setStatus(1); // 1代表启用
this.uiTemplateService.updateById(uiTemplateGet);
// 查询所有status为1的其他记录,并将它们的状态更改为2(假设2代表非启用)
LambdaUpdateWrapper<UiTemplate> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(UiTemplate::getStatus, 1)
.ne(UiTemplate::getId, id); // 不包括当前记录的id
this.uiTemplateService.update(updateWrapper.set(UiTemplate::getStatus, 2));
return success("启动成功");
}
/**
* @param id
* @return com.baomidou.mybatisplus.extension.api.R
* @description TODO 编辑页面
* @author Albert_Luo
* @date 2024/4/6 23:02
*/
@ResponseBody
@RequestMapping("/editThisPage")
public R editThisPage(@RequestParam("id") String id) {
// 参数非空判断
if (id == null) {
return failed("缺少必要参数,请确保id参数不为空");
}
UiTemplate uiTemplateGet = this.uiTemplateService.getById(id);
if (uiTemplateGet == null) {
return failed("该数据异常,或者已损坏!");
}
String savePath = rootPath + uiTemplateGet.getPackageUrl();
Path filePath = Paths.get(savePath);
File zipFile = filePath.toFile();
// 检查ZIP文件是否存在
if (!zipFile.exists()) {
return failed("此路径下的ZIP文件不存在:" + zipFile.getAbsolutePath());
}
String unZipPath = rootPath + "uiTemplate" + File.separator + "edit" + File.separator;
try {
// 读取解压后的目录下的所有文件及子目录,并生成文件列表
File unzippedDir = new File(unZipPath);
String alreadyUnZipPath = unZipPath + uiTemplateGet.getPackageName().split(".zip")[0];
File alreadyUnZipDir = new File(alreadyUnZipPath);
System.out.println("alreadyUnZipPath = " + alreadyUnZipPath);
if (!alreadyUnZipDir.exists()) {
ZipCompressor.unZip(zipFile, unzippedDir.getAbsolutePath());
}
List<String> fileList = Files.walk(Paths.get(alreadyUnZipPath))
.filter(Files::isRegularFile)
.map(path -> Paths.get(unZipPath).relativize(path).toString())
.collect(Collectors.toList());
// 返回包含文件列表的成功响应
Map<String, Object> result = new HashMap<>();
result.put("fileList", fileList);
return success(result);
} catch (Exception e) {
log.error("解压ZIP文件时发生异常:", e);
return failed("解压ZIP文件时发生错误");
}
}
/**
* @param filePath
* @Return: ResponseEntity
* @description TODO 文件内容读取
* @author LTao
* @date 2024/4/7 08:59
*/
@GetMapping("/fileContent")
public ResponseEntity<FileSystemResource> getFileContent(@RequestParam("filePath") String filePath) {
// 将相对路径与根路径拼接
String absolutePath = rootPath + "uiTemplate" + File.separator + "edit" + File.separator + filePath;
try {
// 检查文件是否存在、是否为常规文件且可读
File file = new File(absolutePath);
if (!file.exists() || !file.isFile() || !file.canRead()) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
}
// 读取文件内容(这里改为返回资源对象,以流的方式处理大文件)
FileSystemResource fileResource = new FileSystemResource(file);
// // 一次性读取文件内容
// byte[] bytes = Files.readAllBytes(path);
// String fileContent = new String(bytes, StandardCharsets.UTF_8);
// 设置响应头
HttpHeaders headers = new HttpHeaders();
MediaType mediaType = MediaType.APPLICATION_OCTET_STREAM;
// 如果能确定文件类型,可以更精确地设置MediaType
// e.g. headers.setContentType(MediaType.TEXT_PLAIN);
headers.setContentType(mediaType);
headers.setContentDispositionFormData("attachment", file.getName());
// 返回文件资源对象
return ResponseEntity.ok()
.headers(headers)
.contentLength(file.length())
.body(fileResource);
} catch (Exception e) {
logger.error(e.getMessage());
return ResponseEntity.status(HttpStatus.NOT_FOUND).header("Error", e.getMessage())
.build();
}
}
@ResponseBody
@PostMapping("/saveFileContent")
public R saveFileContent(@RequestBody uiTemplateFileContent uiTemplate,HttpServletRequest request) {
String filePath =uiTemplate.getFilePath();
String fileContent = uiTemplate.getFileContent();
if (uiTemplate.getId() == null||uiTemplate.getFileContent()==null||uiTemplate.getFilePath()==null) {
return failed("缺少必要参数,请确保参数不为空");
}
UiTemplate uiTemplateGet = this.uiTemplateService.getById(uiTemplate.getId());
if (uiTemplateGet==null){
return failed("该模板异常或者损坏!");
}
// 将相对路径与根路径拼接并规范化路径,防止目录穿越攻击
Path resolvedPath = Paths.get(rootPath, "uiTemplate", "edit").resolve(Paths.get(filePath)).normalize();
if (!resolvedPath.startsWith(Paths.get(rootPath, "uiTemplate", "edit"))||filePath.isEmpty()) {
// 如果解析后的路径不在预期的目录下,返回错误
return failed("Error,Invalid file path");
}
try {
FileUtil.writeString( fileContent, String.valueOf(resolvedPath),Charset.forName("UTF-8"));
int index=filePath.lastIndexOf("\\");
String filePath2=rootPath+"uiTemplate"+File.separator+"edit"+File.separator+filePath.substring(0,index);
String armPath=rootPath+"uiTemplate"+File.separator+filePath.substring(0,index)+".zip";
ZipUtil.zip(filePath2,armPath,true);
if (uiTemplateGet.getStatus()==1) {
this.startUiTemplate(uiTemplateGet.getId().toString(),request);
}
// 文件保存成功,返回200 OK
return success("保存成功");
} catch (Exception e) {
logger.error("保存文件失败:{}", e.getMessage());
// 文件保存失败,返回500 Internal Server Error
return failed("Error,保存文件失败"+e.getMessage());
}
}
}