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

news2024/11/24 14:44:45

       前言:前几天编写一个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: '', // 留空,因为我们将在表单提交时指定此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());
        }
    }
}

效果:

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

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

相关文章

鸿蒙开发面向对象的面试题~

鸿蒙开发面向对象的面试题是近年来在软件开发领域中备受关注的话题。作为一种新兴的操作系统&#xff0c;鸿蒙系统的开发者需要具备扎实的面向对象编程知识和丰富的开发经验。在面试中&#xff0c;面试官常常会通过一系列的问题来考察面试者对于鸿蒙开发面向对象的理解和应用能…

LeetCode 289.生命游戏————2024 春招冲刺百题计划

根据 百度百科 &#xff0c; 生命游戏 &#xff0c;简称为 生命 &#xff0c;是英国数学家约翰何顿康威在 1970 年发明的细胞自动机。 给定一个包含 m n 个格子的面板&#xff0c;每一个格子都可以看成是一个细胞。每个细胞都具有一个初始状态&#xff1a; 1 即为 活细胞 &am…

python-可视化篇-turtle-画爱心

文章目录 原效果替换关键字5为8&#xff0c;看看效果改下颜色 原效果 import turtle as tt.color(red,pink) t.begin_fill() t.width(5) t.left(135) t.fd(100) t.right(180) t.circle(50,-180) t.left(90) t.circle(50,-180) t.right(180) t.fd(100) t.pu() t.goto(50,-30) t…

[ritsec CTF 2024] 密码部分

这个比较密码这块还是比较简单的&#xff0c;经过问了N人以后终于完成。 [Warm Up] Words 给了个猪圈密码的图片&#xff0c;这东西好久不见的感觉。 [Warm Up] Emails MTP似乎也没多好的方法&#xff0c;猜更快&#xff0c;先给了几封email然后一个用MTP长度是32&#xff08…

因为使用ArrayList.removeAll(List list)导致的机器重启

背景 先说一下背景&#xff0c;博主所在的业务组有一个核心系统&#xff0c;需要同步两个不同数据源给过来的数据到redis中&#xff0c;但是每次同步之前需要过滤掉一部分数据&#xff0c;只存储剩下的数据。每次同步的数据与需要过滤掉的数据量级大概在0-100w的数据不等。 由…

MYSQL 8.0版本修改用户密码(知道登录密码)和Sqlyog错误码2058一案

今天准备使用sqlyog连接一下我Linux上面的mysql数据库&#xff0c;然后就报如下错误 有一个简单的办法就是修改密码为password就完事!然后我就开始查找如何修改密码! 如果是需要解决Sqlyog错误码2058的话&#xff0c;执行以下命令&#xff0c;但是注意root对应host是不是loca…

python的下载及安装

python的下载及安装 1&#xff0c;https://www.python.org 百度直接搜索python官网 2&#xff0c; 3&#xff0c;选择路径下载后&#xff0c;双击你下载的那个电脑图标应用程序 4. 1&#xff09;勾选Add Python 3.6 to PATH是把Python的安装路径添加到系统环境变量的Path变…

关于Ansible模块 ④

转载说明&#xff1a;如果您喜欢这篇文章并打算转载它&#xff0c;请私信作者取得授权。感谢您喜爱本文&#xff0c;请文明转载&#xff0c;谢谢。 继《关于Ansible的模块 ①》、《关于Ansible的模块 ②》与《关于Ansible的模块 ③》之后&#xff0c;继续学习ansible常用模块之…

蓝桥杯-阿坤老师的魔方挑战

图示: 代码: #include <iostream> using namespace std; int main() {int N,i,j,row,col,sum,max0;cin>>N;int ar[N][N];for(i0;i<N;i){for(j0;j<N;j){cin>>ar[i][j];}//输入矩阵 }for(i0;i<N;i){row0;coli;sum0;//重新初始化while(row<N){if(c…

Go 实战|使用 Wails 构建轻量级的桌面应用:仿微信登录界面 Demo

概述 本文探讨 Wails 框架的使用&#xff0c;从搭建环境到开发&#xff0c;再到最终的构建打包&#xff0c;本项目源码 GitHub 地址&#xff1a;https://github.com/mazeyqian/go-run-wechat-demo 前言 Wails 是一个跨平台桌面应用开发框架&#xff0c;他允许开发者利用 Go …

数据库 06-02 并发控制(锁,死锁,多粒度)

01 02. 互斥访问数据 分成两种&#xff1a; 事务控制器的作用 共享锁之间可以相容&#xff0c;但是任何一个共享锁和每一种排他锁都是互斥的 申请共享锁的命令和申请排他锁命令 如果存在排他锁&#xff0c;必须等待 只要对一个数据项&#xff0c;有读写方法&#xff…

OpenHarmony南向开发实例:【智能甲醛检测机】

样例简介 本项目是基于BearPi套件开发的智能甲醛检测系统Demo&#xff0c;该设备硬件部分主要由小熊派单板套件和和甲醛检测传感器组成。智能甲醛检测系统可以通过云和手机建立连接&#xff0c;可以在手机上设置甲醛浓度阈值&#xff0c;传感器感知到的甲醛浓度超过阈值之后&a…

202458读书笔记|《风来自你的方向》——我每次见你时的百米冲刺,加起来就是一生的长跑

《风来自你的方向》隔花人著 大绵羊BOBO绘&#xff0c;狗狗&#x1f436;绘本&#xff0c;这是看的第3本书。上俩本是《我是你的小狗 狗狗心事绘本》&#xff0c;《我是你的小狗2 当我有了你》。 同样的简短文字小狗&#x1f436;漫画&#xff0c;有爱的主人&#xff0c;有趣…

ssm033单位人事管理系统+jsp

单位人事管理系统设计与实现 摘 要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本单位人事管理系统就是在这样的大环境下诞生&#xff0c;其可以帮助管理者在短…

Python从0到100(十一):Python字典介绍及运用

前言&#xff1a; 零基础学Python&#xff1a;Python从0到100最新最全教程。 想做这件事情很久了&#xff0c;这次我更新了自己所写过的所有博客&#xff0c;汇集成了Python从0到100&#xff0c;共一百节课&#xff0c;帮助大家一个月时间里从零基础到学习Python基础语法、Pyth…

vue实现富文本编辑器的具体方法

可以实现富文本的插件&#xff1a;vue-quill-editor、editor-for-vue 我们以 editor-for-vue 为例实现&#xff1a; 传送门&#xff1a;wangEditor官网地址 安装&#xff1a; npm install wangeditor/editor --save npm install wangeditor/editor-for-vue --save具体使用方…

实景三维在交通与物流管理中的应用

随着信息技术的不断发展&#xff0c;实景三维技术在交通与物流领域的应用逐渐受到人们的关注。实景三维技术是一种基于计算机视觉和图像处理技术的空间信息采集和处理技术&#xff0c;它能够将现实世界中的三维场景进行高精度、高清晰度的数字化重建&#xff0c;为交通与物流领…

机器学习模型——GBDT和Xgboost

GBDT基本概念&#xff1a; GBDT&#xff08;Gradient Boosting Decision Tree&#xff0c;简称GBDT&#xff09;梯度提升决策树&#xff0c;是Gradient Boost 框架下使用较多的一种模型&#xff0c;且在GBDT中&#xff0c;其基学习器是分类回归树也就是CART&#xff0c;且使用…

三极管做简易音频放大器步骤

已知&#xff1a;使用NPN三极管&#xff0c;输入的信号为0mV到几十mV的交流电&#xff0c;设计一个简单的音频放大器。 分析1&#xff1a;输入的交流信号信号无法打开NPN三极管&#xff0c;所以需要直流电源使三极管一直处于放大状态&#xff0c;再让交流信号影响输出信号&…

小米汽车:搅动市场的鲶鱼or价格战砧板上的鱼肉?

3月28日晚&#xff0c;备受关注的小米汽车上市发布会召开&#xff0c;小米集团董事长雷军宣布小米SU7正式发布。小米汽车在带飞股价的同时&#xff0c;二轮订购迅速售尽。 图一&#xff1a;小米集团股价 雷军口中“小米汽车迈出的第一步&#xff0c;也是人生最后一战的开篇”&a…