CodeMirror使用: 编写一个在线编辑HTML、JS、CSS文件,网页的模板页面-初实现

       前言:前几天编写一个UI模板控制的功能,根据上传的前端模板更换跳转入口主题页面;在编写的时候,突发奇想能不能在列表页面进行在线编辑刚刚上传的模板zip压缩包里的页面...于是经过学习研究有了这篇文章;当日记本一样记下来方便以后自己需要时取用。

CodeMirror介绍:

CodeMirror 是一个强大的基于浏览器的文本编辑器组件,主要用于网页中创建可编辑的源代码区域,特别适用于编写和展示程序代码。它支持多种编程语言的语法高亮、代码折叠、自动补全、查找替换等多种高级编辑特性。

 使用CodeMirror:

基本使用步骤:

  1. 引入资源

    • 引入 CodeMirror 的 JavaScript 文件以及对应的 CSS 样式文件。这可以通过 <script> 和 <link> 标签在 HTML 中直接引用,或者使用模块打包工具如 Webpack 进行管理。
    Html
    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>
  2. 初始化编辑器

    • 将一个 textarea 元素转换为 CodeMirror 编辑器。
    Javascript
    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});
  3. 可选扩展和配置

    • 如果需要特定的功能,比如自动补全或linting,可能需要额外引入相关插件,并在初始化时进行配置。
  4. 刷新编辑器: 当编辑器所在的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: '', // 留空,因为我们将在表单提交时指定此URLaccept: '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-typeprocessData: 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 + '');}}});});}});// 定义对应的操作函数,如删除模板函数deleteTemplatefunction deleteTemplate(templateId) {// 发起删除请求的逻辑}} else {layer.msg('获取数据失败:' + res.message);}},error: function (err) {layer.msg('请求失败,请检查网络');}});}var codeEditor;//edit this pagesfunction 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', // 设置语言模式,这里是 JavaScripttheme: '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', // 设置请求体内容类型为JSONsuccess: 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']=filevar 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 {/*** 服务对象*/@Resourceprivate UiTemplateService uiTemplateService;private String rootPath;/*** 分页查询所有数据** @param page       分页对象* @param uiTemplate 查询实体* @return 所有数据*/@GetMappingpublic 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@ResponseBodypublic 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());// 检查数据库中是否已存在同名的UiTemplateboolean 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@ResponseBodypublic 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 删除结果*/@DeleteMappingpublic 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); // 不包括当前记录的idthis.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 OKreturn success("保存成功");} catch (Exception e) {logger.error("保存文件失败:{}", e.getMessage());// 文件保存失败,返回500 Internal Server Errorreturn failed("Error,保存文件失败"+e.getMessage());}}
}

效果:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/819860.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

图灵《模仿游戏》论文学习

文章目录 1. 写在最前面2. 核心观点学习2.1 脑图观点记录2.2 经典观点记录 3. 感受4. 碎碎念5. 参考资料 1. 写在最前面 3 月看了一部以图灵为原型拍摄的人物传记类电影《模仿游戏》&#xff0c;里面反复提及到的论文《COMPUTING MACHINERY AND INTELLIGENCE》&#xff0c;引起…

计算机丢失VCRUNTIME140_1.dll处理办法

一、打开 下面连接地址&#xff0c;下载Visual Studio 2015, 2017, 2019, and 2022 https://learn.microsoft.com/en-US/cpp/windows/latest-supported-vc-redist?viewmsvc-170#visual-studio-2015-2017-2019-and-2022 二、下载系统对应的版本 32位系统下载X86 64位系统下载X…

win10 鼠标箭头自己乱动解决方案

我这里只说我碰到的然后我的解决方案&#xff0c;不一定对其他问题有效&#xff1b; 1. 首先拔掉鼠标线查看鼠标箭头是否仍然在乱动&#xff0c;如果是则非鼠标问题&#xff0c;如果不再乱动则是鼠标的问题&#xff1b;验证非鼠标问题&#xff1b; 2. 因为鼠标乱动跟鼠标无关…

【深度学习】AI修图——DragGAN原理解析

1、前言 上一篇&#xff0c;我们讲述了StyleGAN2。这一篇&#xff0c;我们就来讲一个把StyleGAN2作为基底架构的DragGAN。DragGAN的作用主要是对图片进行编辑&#xff0c;说厉害点&#xff0c;可能和AI修图差不多。这篇论文比较新&#xff0c;发表自2023年 原论文&#xff1a…

韩顺平 | 零基础快速学Python(16) 文件处理

文件 输入与输出 输入&#xff1a;数据从数据源(文件)到程序(内存)&#xff1b; 输出&#xff1a;数据从程序(内存)到数据源(文件)。 #mermaid-svg-06PG6JZq4jJMV1oH {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-sv…

数据库查询:查询入参类型和数据库字段类型不匹配导致的问题

问题&#xff1a;假设我们现在有这样的一张表 CREATE TABLE test_person (id int(20) NOT NULL COMMENT 主键,name varchar(20) DEFAULT NULL COMMENT 姓名,gender char(2) DEFAULT NULL COMMENT 性别,birthday date DEFAULT NULL COMMENT 生日,created_time timestamp NULL D…

JS-33-jQuery02-选择器

一、单个选择器 选择器是jQuery的核心。 一个选择器写出来类似$(#dom-id)。 美元符号定义 jQuery 为什么jQuery要发明选择器&#xff1f;回顾一下DOM操作中我们经常使用的代码&#xff1a; // 按ID查找&#xff1a; var a document.getElementById(dom-id);// 按tag查找&am…

Java的maven项目导入本地jar包的三种方式

文章目录 Java的maven项目导入本地jar包的三种方式1、在项目中创建一个lib文件夹&#xff0c;将想要使用的本地jar包放进去2、方法一&#xff1a;直接在pom.xml中添加下列依赖&#xff08;项目协作推荐&#xff09;3、方法二&#xff1a;在项目结构中引用lib文件夹&#xff08;…

ATA-2048高压放大器在铁电材料中有什么应用

铁电材料是一类具有特殊电学性质的材料&#xff0c;它们能够在外加电场的作用下产生可逆的电极化&#xff0c;这种电极化可以在没有外加电场时保持。这使得铁电材料在许多应用中具有重要价值&#xff0c;特别是在电子设备和传感器领域。高压放大器作为一种电子设备&#xff0c;…

C++:Hash应用【位图与布隆过滤器】

什么是位图&#xff1f; 我们先来看一个问题&#xff1a; 给40亿个不重复的无符号整数&#xff0c;没排过序。给一个无符号整数&#xff0c;如何快速判断一个数是否在 这40亿个数中。【腾讯】 如果我们使用unordered_set容器来解决&#xff0c;40亿个数据&#xff0c;每个数据…

MaxCompute 近实时增全量处理一体化新架构和使用场景介绍

随着当前数据处理业务场景日趋复杂&#xff0c;对于大数据处理平台基础架构的能力要求也越来越高&#xff0c;既要求数据湖的大存储能力&#xff0c;也要求具备海量数据高效批处理能力&#xff0c;同时还可能对延时敏感的近实时链路有强需求&#xff0c;本文主要介基于 MaxComp…

python将pdf转为docx

如何使用python实现将pdf文件转为docx文件 1.首先要安装pdf2docx库 pip install pdf2docx2.实现转换 from pdf2docx import Converterdef convert_pdf_to_docx(input_pdf, output_docx):# 创建一个PDF转换器对象pdf_converter Converter(input_pdf)# 将PDF转换为docx文件pdf…

护眼台灯哪个牌子好?护眼灯十大品牌推荐,谁用谁真香

对于有子女的家庭&#xff0c;特别是那些热爱阅读的&#xff0c;晚上看书时更应该注重光线的问题&#xff0c;不然一旦光线过暗就容易导致视觉疲劳&#xff0c;进而演化为近视。因此&#xff0c;除了打开房间的灯&#xff0c;在桌面上放置一台护眼台灯更加能够保护眼睛。然而&a…

社交网络与Web3:数字社交的下一阶段

随着信息技术的飞速发展&#xff0c;人们的社交方式也发生了巨大的变化。从最初的互联网聊天室到如今的社交网络平台&#xff0c;我们已经见证了数字社交的不断演变和发展。而随着区块链技术的兴起&#xff0c;Web3时代的到来将为数字社交带来全新的可能性和挑战。本文将探讨社…

【JAVA基础篇教学】第十四篇:Java中设计模式

博主打算从0-1讲解下java基础教学&#xff0c;今天教学第十四篇&#xff1a;Java中设计模式。 设计模式是解决软件设计中常见问题的可重复利用的解决方案。在 Java 中&#xff0c;常见的设计模式包括单例模式、工厂模式、观察者模式等。目前在基础教学篇中只展示常见的几种模…

STM32笔记---CAN采样点设置和报错

STM32笔记---CAN采样点设置和报错 采样点设置再同步补偿宽度&#xff08;SJW&#xff09;设置 报错分析CAN中断使能寄存器CAN错误状态寄存器 采样点设置 以前配置CAN参数的BS1和BS2参数时认为总线波特率符合要求就可以了&#xff0c;其实同一个波特率可能对应多组参数设置的情…

开发公司 or 个人开发者?软件开发如何选择?

引言 随着科技的发展&#xff0c;软件开发已成为一个相对复杂的行业&#xff0c;需要专业的技能和经验来保证项目的成功。许多企业、组织和个人都纷纷加入到了软件开发的队伍中。在选择软件开发人员时&#xff0c;您可能会面临一个选择&#xff1a;是找个人开发人员还是找专业的…

访问者模式【行为模式C++】

1.概述 访问者模式是一种行为设计模式&#xff0c; 它能将算法与其所作用的对象隔离开来。 访问者模式主要解决的是数据与算法的耦合问题&#xff0c;尤其是在数据结构比较稳定&#xff0c;而算法多变的情况下。为了不污染数据本身&#xff0c;访问者会将多种算法独立归档&…

数据结构与算法——22.哈希算法

这篇文章我们来讲一下哈希表中较为关键的部分——哈希算法 目录 1.哈希算法的介绍 2.hash算法的使用 2.1 Object.hashCode 2.2 String.hashCode 3.关于哈希表及哈希算法的一些思考 1.哈希算法的介绍 问题&#xff1a;什么是哈希算法&#xff1f;哈希算法有哪些&#xff…

软件杯 深度学习图像修复算法 - opencv python 机器视觉

文章目录 0 前言2 什么是图像内容填充修复3 原理分析3.1 第一步&#xff1a;将图像理解为一个概率分布的样本3.2 补全图像 3.3 快速生成假图像3.4 生成对抗网络(Generative Adversarial Net, GAN) 的架构3.5 使用G(z)生成伪图像 4 在Tensorflow上构建DCGANs最后 0 前言 &#…