1 files modified
3 files added
953 ■■■■■ changed files
src/main/java/cc/mrbird/febs/ai/controller/fileUpload/FileUploadController.java 237 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/ai/controller/fileUpload/ViewController.java 27 ●●●●● patch | view | raw | blame | history
src/main/resources/application.yml 2 ●●● patch | view | raw | blame | history
src/main/resources/templates/febs/views/modules/ai/fileUpload/index.html 687 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/ai/controller/fileUpload/FileUploadController.java
New file
@@ -0,0 +1,237 @@
package cc.mrbird.febs.ai.controller.fileUpload;
import cc.mrbird.febs.common.controller.BaseController;
import cc.mrbird.febs.common.entity.FebsResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;
/**
 * @author Administrator
 */
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping(value = "/fileUpload")
public class FileUploadController extends BaseController {
    // @Value("${file.upload.dir}")
    private String uploadDir = "d:/upload/files";
    // @Value("${file.chunk.dir}")
    private String chunkDir = "d:/upload/chunks";
    /**
     * 上传文件分片
     */
    @PostMapping("/uploadChunk")
    public FebsResponse uploadChunk(@RequestParam("file") MultipartFile file,
                                    @RequestParam("fileName") String fileName,
                                    @RequestParam("chunk") int chunk,
                                    @RequestParam("chunks") int chunks,
                                    @RequestParam("fileMd5") String fileMd5) {
        try {
            // 确保分片目录存在
            Path chunkPath = Paths.get(chunkDir, fileMd5);
            if (!Files.exists(chunkPath)) {
                Files.createDirectories(chunkPath);
            }
            // 保存分片文件
            Path chunkFilePath = chunkPath.resolve(chunk + ".part");
            Files.write(chunkFilePath, file.getBytes());
            return new FebsResponse().success().message("分片上传成功");
        } catch (Exception e) {
            e.printStackTrace();
            return new FebsResponse().fail().message("分片上传失败: " + e.getMessage());
        }
    }
    /**
     * 合并文件分片
     */
    @PostMapping("/mergeChunks")
    public FebsResponse mergeChunks(@RequestParam("fileName") String fileName,
                                    @RequestParam("fileMd5") String fileMd5,
                                    @RequestParam("chunks") int chunks) {
        try {
            // 确保上传目录存在
            Path uploadPath = Paths.get(uploadDir);
            if (!Files.exists(uploadPath)) {
                Files.createDirectories(uploadPath);
            }
            // 生成唯一文件名
            String uniqueFileName = UUID.randomUUID().toString() + "_" + fileName;
            Path targetFilePath = uploadPath.resolve(uniqueFileName);
            // 确保分片目录存在
            Path chunkPath = Paths.get(chunkDir, fileMd5);
            if (!Files.exists(chunkPath)) {
                return new FebsResponse().fail().message("分片目录不存在");
            }
            // 合并分片
            try (FileOutputStream outputStream = new FileOutputStream(targetFilePath.toFile())) {
                for (int i = 0; i < chunks; i++) {
                    Path chunkFilePath = chunkPath.resolve(i + ".part");
                    if (!Files.exists(chunkFilePath)) {
                        return new FebsResponse().fail().message("分片文件不存在: " + chunkFilePath);
                    }
                    byte[] chunkBytes = Files.readAllBytes(chunkFilePath);
                    outputStream.write(chunkBytes);
                    // 删除已合并的分片
                    Files.deleteIfExists(chunkFilePath);
                }
            }
            // 删除分片目录
            Files.deleteIfExists(chunkPath);
            // 保存文件信息到数据库或文件系统(这里简化处理)
            saveFileInfo(uniqueFileName, targetFilePath.toFile().length());
            return new FebsResponse().success().message("文件上传成功").data(uniqueFileName);
        } catch (Exception e) {
            e.printStackTrace();
            return new FebsResponse().fail().message("文件合并失败: " + e.getMessage());
        }
    }
    /**
     * 播放视频文件
     */
    @GetMapping("/play/{fileName}")
    public void playVideo(@PathVariable("fileName") String fileName, HttpServletResponse response) {
        try {
            Path filePath = Paths.get(uploadDir, fileName);
            if (!Files.exists(filePath)) {
                response.setStatus(HttpStatus.NOT_FOUND.value());
                return;
            }
            // 设置响应头
            response.setContentType("video/mp4");
            response.setContentLengthLong(Files.size(filePath));
            response.setHeader("Content-Disposition", "inline; filename=\"" + fileName + "\"");
            // 流式传输文件
            try (InputStream inputStream = Files.newInputStream(filePath);
                 OutputStream outputStream = response.getOutputStream()) {
                byte[] buffer = new byte[8192];
                int bytesRead;
                while ((bytesRead = inputStream.read(buffer)) != -1) {
                    outputStream.write(buffer, 0, bytesRead);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
        }
    }
    /**
     * 获取文件列表
     */
    @GetMapping("/list")
    public FebsResponse getFileList() {
        try {
            Path uploadPath = Paths.get(uploadDir);
            if (!Files.exists(uploadPath)) {
                return new FebsResponse().data(new ArrayList<>());
            }
            List<FileInfo> fileList = new ArrayList<>();
            Files.list(uploadPath).forEach(path -> {
                if (Files.isRegularFile(path)) {
                    try {
                        FileInfo fileInfo = new FileInfo();
                        fileInfo.setFileName(path.getFileName().toString());
                        fileInfo.setFileSize(Files.size(path));
                        fileInfo.setUploadTime(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(Files.getLastModifiedTime(path).toMillis())));
                        fileList.add(fileInfo);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            });
            return new FebsResponse().success().data(fileList);
        } catch (Exception e) {
            e.printStackTrace();
            return new FebsResponse().fail().message("获取文件列表失败: " + e.getMessage());
        }
    }
    /**
     * 删除文件
     */
    @PostMapping("/delete")
    public FebsResponse deleteFile(@RequestParam("fileName") String fileName) {
        try {
            Path filePath = Paths.get(uploadDir, fileName);
            if (Files.exists(filePath)) {
                Files.delete(filePath);
            }
            return new FebsResponse().success().message("文件删除成功");
        } catch (Exception e) {
            e.printStackTrace();
            return new FebsResponse().fail().message("文件删除失败: " + e.getMessage());
        }
    }
    /**
     * 保存文件信息
     */
    private void saveFileInfo(String fileName, long fileSize) {
        // 这里可以实现保存文件信息到数据库的逻辑
        // 简化处理,暂时不做数据库操作
    }
    /**
     * 文件信息实体类
     */
    public static class FileInfo {
        private String fileName;
        private long fileSize;
        private String uploadTime;
        public String getFileName() {
            return fileName;
        }
        public void setFileName(String fileName) {
            this.fileName = fileName;
        }
        public long getFileSize() {
            return fileSize;
        }
        public void setFileSize(long fileSize) {
            this.fileSize = fileSize;
        }
        public String getUploadTime() {
            return uploadTime;
        }
        public void setUploadTime(String uploadTime) {
            this.uploadTime = uploadTime;
        }
    }
}
src/main/java/cc/mrbird/febs/ai/controller/fileUpload/ViewController.java
New file
@@ -0,0 +1,27 @@
package cc.mrbird.febs.ai.controller.fileUpload;
import cc.mrbird.febs.ai.service.AiCompanyMemberApplyService;
import cc.mrbird.febs.common.entity.FebsConstant;
import cc.mrbird.febs.common.utils.FebsUtil;
import lombok.RequiredArgsConstructor;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
/**
 * @author Administrator
 */
@Controller("fileUpload")
@RequestMapping(FebsConstant.VIEW_PREFIX + "modules/ai/fileUpload")
@RequiredArgsConstructor
public class ViewController {
    @GetMapping("index")
    @RequiresPermissions("fileUpload:index")
    public String index() {
        return FebsUtil.view("modules/ai/fileUpload/index");
    }
}
src/main/resources/application.yml
@@ -1,5 +1,5 @@
server:
  port: 8085
  port: 8095
  tomcat:
    uri-encoding: utf-8
src/main/resources/templates/febs/views/modules/ai/fileUpload/index.html
New file
@@ -0,0 +1,687 @@
<div class="layui-fluid layui-anim febs-anim" id="febs-aiCompany-Info" lay-title="编辑">
    <div class="layui-row febs-container">
        <div class="layui-col-md12">
            <div class="layui-fluid" id="aiCompany-info">
                <form class="layui-form" action="" lay-filter="aiCompany-info-form">
                    <div class="layui-tab layui-tab-brief" lay-filter="docDemoTabBrief">
                        <ul class="layui-tab-title">
                            <li class="layui-this">基础信息</li>
                        </ul>
                        <div class="layui-tab-content">
                            <div class="upload-container">
                                <div class="upload-header">
                                    <h2>大文件上传系统</h2>
                                    <p>支持分片上传,最大支持10GB文件</p>
                                </div>
                                <!-- 上传区域 -->
                                <div class="upload-area" id="uploadArea">
                                    <i class="layui-icon layui-icon-upload-drag upload-icon"></i>
                                    <div class="upload-text">
                                        <p>点击或拖拽文件到此处上传</p>
                                        <small>支持视频文件上传</small>
                                    </div>
                                </div>
                                <!-- 文件信息 -->
                                <div class="file-info" style="display: none;" id="fileInfo">
                                    <div class="layui-row">
                                        <div class="layui-col-xs6">
                                            <p><strong>文件名:</strong><span id="fileName"></span></p>
                                            <p><strong>文件大小:</strong><span id="fileSize"></span></p>
                                            <p><strong>分片数量:</strong><span id="chunkCount"></span></p>
                                        </div>
                                        <div class="layui-col-xs6">
                                            <p><strong>上传进度:</strong><span id="uploadProgress">0%</span></p>
                                            <p><strong>状态:</strong><span id="uploadStatus">等待上传</span></p>
                                            <p><strong>速度:</strong><span id="uploadSpeed">0KB/s</span></p>
                                        </div>
                                    </div>
                                </div>
                                <!-- 进度条 -->
                                <div class="progress-container" style="display: none;" id="progressContainer">
                                    <div class="progress-bar">
                                        <div class="progress-fill" id="progressFill">0%</div>
                                    </div>
                                </div>
                                <!-- 操作按钮 -->
                                <div class="btn-group" id="btnGroup">
                                    <button class="layui-btn layui-btn-normal" id="startUpload">开始上传</button>
                                    <button class="layui-btn layui-btn-danger" id="cancelUpload">取消上传</button>
                                </div>
                                <!-- 播放区域 -->
                                <div class="play-container" id="playContainer">
                                    <h3 style="margin-bottom: 20px; color: #333;">视频播放</h3>
                                    <video id="videoPlayer" controls>
                                        <source id="videoSource" src="" type="video/mp4">
                                        您的浏览器不支持视频播放
                                    </video>
                                    <div class="layui-row layui-col-space10" style="margin-top: 20px;">
                                        <div class="layui-col-xs12">
                                            <button class="layui-btn layui-btn-primary" id="refreshList">刷新文件列表</button>
                                        </div>
                                    </div>
                                    <!-- 服务器文件列表 -->
                                    <div style="margin-top: 20px;">
                                        <h4 style="margin-bottom: 10px; color: #666;">服务器文件列表</h4>
                                        <table class="layui-table" id="fileList">
                                            <thead>
                                            <tr>
                                                <th>文件名</th>
                                                <th>大小</th>
                                                <th>上传时间</th>
                                                <th>操作</th>
                                            </tr>
                                            </thead>
                                            <tbody>
                                            <!-- 文件列表将通过JS动态生成 -->
                                            </tbody>
                                        </table>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                    <div class="layui-form-item febs-hide">
                        <button class="layui-btn" lay-submit="" lay-filter="aiCompany-info-form-submit" id="submit">保存</button>
                    </div>
                </form>
            </div>
        </div>
    </div>
</div>
<style>
    .upload-container {
        max-width: 900px;
        margin: 30px auto;
        padding: 30px;
        background: #f8f8f8;
        border-radius: 8px;
        box-shadow: 0 2px 10px rgba(0,0,0,0.1);
    }
    .upload-header {
        text-align: center;
        margin-bottom: 40px;
    }
    .upload-header h2 {
        color: #333;
        margin-bottom: 10px;
        font-size: 24px;
    }
    .upload-header p {
        color: #666;
        font-size: 14px;
    }
    .upload-area {
        border: 2px dashed #1E9FFF;
        border-radius: 8px;
        padding: 50px;
        text-align: center;
        margin-bottom: 30px;
        cursor: pointer;
        transition: all 0.3s;
        background: #fff;
    }
    .upload-area:hover {
        border-color: #009688;
        background: #f0f9ff;
    }
    .upload-icon {
        font-size: 80px;
        color: #1E9FFF;
        margin-bottom: 20px;
    }
    .upload-text {
        font-size: 18px;
        color: #666;
    }
    .upload-text small {
        font-size: 14px;
        color: #999;
        display: block;
        margin-top: 10px;
    }
    .file-info {
        margin-top: 20px;
        padding: 20px;
        background: #fff;
        border-radius: 6px;
        border-left: 4px solid #1E9FFF;
        box-shadow: 0 1px 3px rgba(0,0,0,0.1);
    }
    .file-info .layui-row {
        margin: 0;
    }
    .file-info p {
        margin: 5px 0;
        color: #333;
    }
    .file-info strong {
        color: #666;
    }
    .progress-container {
        margin: 20px 0;
        background: #fff;
        padding: 20px;
        border-radius: 6px;
        box-shadow: 0 1px 3px rgba(0,0,0,0.1);
    }
    .progress-bar {
        height: 24px;
        background: #f0f0f0;
        border-radius: 12px;
        overflow: hidden;
        box-shadow: inset 0 1px 3px rgba(0,0,0,0.1);
    }
    .progress-fill {
        height: 100%;
        background: linear-gradient(90deg, #1E9FFF, #009688);
        border-radius: 12px;
        width: 0%;
        transition: width 0.3s;
        text-align: center;
        line-height: 24px;
        color: #fff;
        font-size: 12px;
        font-weight: bold;
    }
    .btn-group {
        margin: 20px 0;
        text-align: center;
    }
    .btn-group .layui-btn {
        margin: 0 10px;
        padding: 0 30px;
        height: 40px;
        line-height: 40px;
        font-size: 16px;
    }
    .play-container {
        margin-top: 40px;
        padding: 30px;
        background: #fff;
        border-radius: 8px;
        box-shadow: 0 2px 8px rgba(0,0,0,0.1);
    }
    .play-container h3 {
        margin-bottom: 20px;
        color: #333;
        font-size: 20px;
    }
    video {
        width: 100%;
        max-width: 800px;
        height: auto;
        border-radius: 4px;
        box-shadow: 0 2px 8px rgba(0,0,0,0.1);
    }
    .list-controls {
        margin: 20px 0;
        text-align: center;
    }
    .file-list-section {
        margin-top: 30px;
    }
    .file-list-section h4 {
        margin-bottom: 15px;
        color: #666;
        font-size: 16px;
        border-bottom: 1px solid #f0f0f0;
        padding-bottom: 10px;
    }
    .layui-table {
        box-shadow: 0 1px 3px rgba(0,0,0,0.1);
    }
    .layui-table th {
        background: #f8f8f8;
        font-weight: bold;
    }
    .layui-table tr:hover {
        background: #f9f9f9;
    }
    .layui-table .play-btn,
    .layui-table .delete-btn {
        margin: 0 5px;
    }
    @media (max-width: 768px) {
        .upload-container {
            margin: 20px;
            padding: 20px;
        }
        .upload-area {
            padding: 30px;
        }
        .upload-icon {
            font-size: 60px;
        }
        .btn-group .layui-btn {
            margin: 5px;
            padding: 0 20px;
        }
    }
</style>
<!-- 表格操作栏 end -->
<script data-th-inline="javascript">
        layui.use(['layer', 'jquery'], function() {
            var layer = layui.layer,
            $ = layui.jquery;
            // 全局变量
            var file = null;
            var fileMd5 = '';
            var chunkSize = 5 * 1024 * 1024; // 5MB分片
            var chunks = 0;
            var currentChunk = 0;
            var isUploading = false;
            var startTime = 0;
            var uploadedSize = 0;
            // 初始化
            init();
            function init() {
                // 拖拽上传
                var uploadArea = document.getElementById('uploadArea');
                uploadArea.addEventListener('dragover', function(e) {
                    e.preventDefault();
                    $(this).css('border-color', '#009688');
                });
                uploadArea.addEventListener('dragleave', function(e) {
                    e.preventDefault();
                    $(this).css('border-color', '#1E9FFF');
                });
                uploadArea.addEventListener('drop', function(e) {
                    e.preventDefault();
                    $(this).css('border-color', '#1E9FFF');
                    handleFile(e.dataTransfer.files[0]);
                });
                // 点击上传
                uploadArea.addEventListener('click', function(e) {
                    // 阻止事件冒泡,避免干扰其他按钮
                    e.stopPropagation();
                    // 确保点击的是上传区域本身,而不是其他元素
                    if (e.target === uploadArea || e.target.classList.contains('upload-icon') || e.target.classList.contains('upload-text')) {
                        console.log('上传区域被点击');
                        var input = document.createElement('input');
                        input.type = 'file';
                        input.accept = 'video/*';
                        input.onchange = function() {
                            if (this.files.length > 0) {
                                console.log('选择了文件:', this.files[0].name);
                                handleFile(this.files[0]);
                            }
                        };
                        input.click();
                    }
                });
                // 按钮事件 - 使用事件委托确保每次都能正确绑定
                // 移除旧的事件绑定,避免重复绑定
                $(document).off('click', '#startUpload');
                $(document).off('click', '#cancelUpload');
                $(document).off('click', '#refreshList');
                // 重新绑定事件
                $(document).on('click', '#startUpload', function(e) {
                    e.preventDefault();
                    e.stopPropagation();
                    console.log('开始上传按钮被点击 - 新绑定');
                    startUpload();
                });
                // 为取消上传按钮添加更强大的事件绑定
                $(document).on('click', '#cancelUpload', function(e) {
                    e.preventDefault();
                    e.stopPropagation();
                    console.log('取消上传按钮被点击 - 新事件委托');
                    console.log('按钮状态:', $(this).prop('disabled'));
                    console.log('按钮可见性:', $(this).is(':visible'));
                    cancelUpload();
                });
                $(document).on('click', '#refreshList', function(e) {
                    e.preventDefault();
                    e.stopPropagation();
                    console.log('刷新列表按钮被点击 - 新绑定');
                    refreshFileList();
                });
                // 初始化文件列表
                refreshFileList();
            }
            // 处理文件
            function handleFile(selectedFile) {
                if (!selectedFile) return;
                if (!selectedFile.type.startsWith('video/')) {
                    layer.msg('请选择视频文件', {icon: 2});
                    return;
                }
                // 重置之前的状态
                currentChunk = 0;
                uploadedSize = 0;
                $('#uploadProgress').text('0%');
                $('#uploadStatus').text('等待上传');
                $('#uploadSpeed').text('0KB/s');
                $('#progressFill').css('width', '0%').text('0%');
                file = selectedFile;
                chunks = Math.ceil(file.size / chunkSize);
                // 显示文件信息
                $('#fileName').text(file.name);
                $('#fileSize').text(formatFileSize(file.size));
                $('#chunkCount').text(chunks);
                $('#fileInfo').show();
                $('#progressContainer').show();
                $('#btnGroup').show();
                // 计算文件MD5(用于断点续传和文件标识)
                calculateFileMd5();
            }
            // 计算文件MD5
            function calculateFileMd5() {
                layer.msg('正在计算文件MD5...', {icon: 16, time: 0});
                var fileReader = new FileReader();
                var blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
                var chunk = blobSlice.call(file, 0, Math.min(file.size, 10 * 1024 * 1024)); // 只取前10MB计算MD5
                fileReader.onload = function(e) {
                    var spark = new SparkMD5.ArrayBuffer();
                    spark.append(e.target.result);
                    fileMd5 = spark.end();
                    layer.closeAll();
                    layer.msg('文件MD5计算完成', {icon: 1});
                };
                fileReader.readAsArrayBuffer(chunk);
            }
            // 开始上传
            function startUpload() {
                if (!file) {
                    layer.msg('请先选择文件', {icon: 2});
                    return;
                }
                if (isUploading) {
                    layer.msg('上传已在进行中', {icon: 2});
                    return;
                }
                isUploading = true;
                startTime = Date.now();
                uploadedSize = 0;
                currentChunk = 0;
                $('#uploadStatus').text('上传中');
                $('#startUpload').prop('disabled', true);
                $('#cancelUpload').prop('disabled', true);
                uploadNextChunk();
            }
            // 上传下一个分片
            function uploadNextChunk() {
                if (!isUploading) return;
                if (currentChunk >= chunks) {
                    // 所有分片上传完成,请求合并
                    mergeChunks();
                    return;
                }
                var start = currentChunk * chunkSize;
                var end = Math.min(start + chunkSize, file.size);
                var chunk = file.slice(start, end);
                var formData = new FormData();
                formData.append('file', chunk);
                formData.append('fileName', file.name);
                formData.append('chunk', currentChunk);
                formData.append('chunks', chunks);
                formData.append('fileMd5', fileMd5);
                $.ajax({
                    url: '/fileUpload/uploadChunk',
                    type: 'POST',
                    data: formData,
                    processData: false,
                    contentType: false,
                    xhr: function() {
                        var xhr = new XMLHttpRequest();
                        xhr.upload.addEventListener('progress', function(e) {
                            if (e.lengthComputable) {
                                var chunkProgress = e.loaded / e.total;
                                var totalProgress = (currentChunk + chunkProgress) / chunks;
                                updateProgress(totalProgress);
                                // 计算上传速度
                                var elapsed = (Date.now() - startTime) / 1000;
                                var speed = (uploadedSize + e.loaded) / elapsed;
                                $('#uploadSpeed').text(formatFileSize(speed) + '/s');
                            }
                        });
                        return xhr;
                    },
                    success: function(response) {
                        if (response.code === 200 || response.success) {
                            uploadedSize += (end - start);
                            currentChunk++;
                            uploadNextChunk();
                        } else {
                            layer.msg('分片上传失败: ' + response.message, {icon: 2});
                            isUploading = false;
                            $('#uploadStatus').text('上传失败');
                            $('#startUpload').prop('disabled', false);
                        }
                    },
                    error: function() {
                        layer.msg('网络错误,请重试', {icon: 2});
                        isUploading = false;
                        $('#uploadStatus').text('上传失败');
                        $('#startUpload').prop('disabled', false);
                    }
                });
            }
            // 合并分片
            function mergeChunks() {
                $.ajax({
                    url: '/fileUpload/mergeChunks',
                    type: 'POST',
                    data: {
                        fileName: file.name,
                        fileMd5: fileMd5,
                        chunks: chunks
                    },
                    success: function(response) {
                        if (response.code === 200 || response.success) {
                            layer.msg('文件上传成功,页面将刷新', {icon: 1, time: 1000});
                            $('#uploadStatus').text('上传成功');
                            $('#uploadProgress').text('100%');
                            $('#progressFill').css('width', '100%').text('100%');
                            // 刷新页面
                            setTimeout(function() {
                                location.reload();
                            }, 1000);
                        } else {
                            layer.msg('文件合并失败: ' + response.message, {icon: 2});
                            $('#uploadStatus').text('上传失败');
                        }
                    },
                    error: function() {
                        layer.msg('网络错误,请重试', {icon: 2});
                        $('#uploadStatus').text('上传失败');
                    },
                    complete: function() {
                        isUploading = false;
                        $('#startUpload').prop('disabled', false);
                        $('#cancelUpload').prop('disabled', true);
                    }
                });
            }
            // 取消上传
            function cancelUpload() {
                console.log('取消上传函数被调用');
                // 显示成功消息
                layer.msg('上传已取消,页面将刷新', {icon: 1, time: 1000});
                // 刷新页面
                setTimeout(function() {
                    location.reload();
                }, 1000);
            }
            // 更新进度
            function updateProgress(progress) {
                var percent = Math.round(progress * 100);
                $('#uploadProgress').text(percent + '%');
                $('#progressFill').css('width', percent + '%').text(percent + '%');
            }
            // 刷新文件列表
            function refreshFileList() {
                layer.msg('正在获取文件列表...', {icon: 16, time: 0});
                $.ajax({
                    url: '/fileUpload/list',
                    type: 'GET',
                    success: function(response) {
                        layer.closeAll();
                        if (response.code === 200 || response.success || response.data) {
                            var fileList = response.data || [];
                            var tbody = $('#fileList tbody');
                            tbody.empty();
                            if (fileList.length === 0) {
                                var emptyTr = $('<tr></tr>');
                                emptyTr.html(`
                                    <td colspan="4" style="text-align: center; color: #999;">暂无文件</td>
                                `);
                                tbody.append(emptyTr);
                            } else {
                                fileList.forEach(function(item) {
                                    var tr = $('<tr></tr>');
                                    tr.html(`
                                        <td>
                                            <input type="radio" name="fileRadio" value="${item.fileName}">
                                            ${item.fileName}
                                        </td>
                                        <td>${formatFileSize(item.fileSize)}</td>
                                        <td>${item.uploadTime}</td>
                                        <td>
                                            <button class="layui-btn layui-btn-xs layui-btn-normal play-btn" data-file="${item.fileName}">
                                                播放
                                            </button>
                                            <button class="layui-btn layui-btn-xs layui-btn-danger delete-btn" data-file="${item.fileName}">
                                                删除
                                            </button>
                                        </td>
                                    `);
                                    tbody.append(tr);
                                });
                                // 绑定播放按钮事件
                                $('.play-btn').click(function() {
                                    var fileName = $(this).data('file');
                                    var videoPlayer = document.getElementById('videoPlayer');
                                    var videoSource = document.getElementById('videoSource');
                                    videoSource.src = '/fileUpload/play/' + encodeURIComponent(fileName);
                                    videoPlayer.load();
                                    videoPlayer.play();
                                });
                                // 绑定删除按钮事件
                                $('.delete-btn').click(function() {
                                    var fileName = $(this).data('file');
                                    layer.confirm('确定要删除此文件吗?', {
                                        btn: ['确定', '取消']
                                    }, function() {
                                        $.ajax({
                                            url: '/fileUpload/delete',
                                            type: 'POST',
                                            data: {fileName: fileName},
                                            success: function(response) {
                                                if (response.code === 200 || response.success) {
                                                    layer.msg('删除成功', {icon: 1});
                                                    refreshFileList();
                                                } else {
                                                    layer.msg('删除失败: ' + (response.message || response.msg), {icon: 2});
                                                }
                                            },
                                            error: function() {
                                                layer.msg('网络错误,请重试', {icon: 2});
                                            }
                                        });
                                    });
                                });
                            }
                        } else {
                            layer.msg('获取文件列表失败: ' + (response.message || response.msg), {icon: 2});
                        }
                    },
                    error: function() {
                        layer.closeAll();
                        layer.msg('网络错误,请重试', {icon: 2});
                    }
                });
            }
            // 格式化文件大小
            function formatFileSize(bytes) {
                if (bytes === 0) return '0 B';
                var k = 1024;
                var sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
                var i = Math.floor(Math.log(bytes) / Math.log(k));
                return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
            }
        });
</script>
<script src="https://cdn.jsdelivr.net/npm/spark-md5@3.0.2/spark-md5.min.js"></script>