Browse Source

1.添加文件上传功能

1 1 day ago
parent
commit
48bf915c9f
24 changed files with 2866 additions and 354 deletions
  1. 43 0
      imwork-commons/imwork-commons-core/src/main/java/top/imwork/commons/core/pojo/ApiResponse.java
  2. 31 0
      imwork-commons/imwork-commons-core/src/main/java/top/imwork/commons/core/pojo/FileUploadResponse.java
  3. 63 0
      imwork-exercise/src/main/java/top/imwork/exercise/ChapterSplitter.java
  4. 307 0
      imwork-exercise/src/main/java/top/imwork/exercise/Test.java
  5. 159 0
      imwork-exercise/src/main/java/top/imwork/exercise/VirtualThreadFileProcessor.java
  6. 8 2
      imwork-windows/imwork-silos/pom.xml
  7. 1 0
      imwork-windows/imwork-silos/src/main/java/top/imwork/window/silos/config/WebMvcConfig.java
  8. 78 0
      imwork-windows/imwork-silos/src/main/java/top/imwork/window/silos/controller/oss/FilesUploadController.java
  9. 1 0
      imwork-windows/imwork-silos/src/main/java/top/imwork/window/silos/convert/InfoConvert.java
  10. 7 175
      imwork-windows/imwork-silos/src/main/java/top/imwork/window/silos/pojo/dto/InfoDTO.java
  11. 7 175
      imwork-windows/imwork-silos/src/main/java/top/imwork/window/silos/pojo/po/InfoParamVO.java
  12. 40 0
      imwork-windows/imwork-silos/src/main/java/top/imwork/window/silos/processor/Chapter.java
  13. 462 0
      imwork-windows/imwork-silos/src/main/java/top/imwork/window/silos/processor/ChapterSplitter.java
  14. 42 1
      imwork-windows/imwork-silos/src/main/java/top/imwork/window/silos/service/impl/InfoServiceImpl.java
  15. 80 0
      imwork-windows/imwork-silos/src/main/java/top/imwork/window/silos/service/oss/FileUploadService.java
  16. 58 0
      imwork-windows/imwork-silos/src/main/java/top/imwork/window/silos/utils/FileUtils.java
  17. 183 0
      imwork-windows/imwork-silos/src/main/java/top/imwork/window/silos/utils/SecurityUtils.java
  18. 9 0
      imwork-windows/imwork-silos/src/main/resources/application.yml
  19. 722 0
      imwork-windows/imwork-silos/src/main/resources/static/assets/silos/js/filesUpload.js
  20. 439 0
      imwork-windows/imwork-silos/src/main/resources/static/business/cms/book/info/edit.css
  21. 1 0
      imwork-windows/imwork-silos/src/main/resources/static/business/cms/book/info/edit.js
  22. 117 1
      imwork-windows/imwork-silos/src/main/resources/templates/cms/book/info/edit.html
  23. 1 0
      imwork-windows/imwork-silos/src/main/resources/uploads/说明.txt
  24. 7 0
      pom.xml

+ 43 - 0
imwork-commons/imwork-commons-core/src/main/java/top/imwork/commons/core/pojo/ApiResponse.java

@@ -0,0 +1,43 @@
+package top.imwork.commons.core.pojo;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+/**
+ * Copyright (C), 2015-2025
+ * FileName: ApiResponse
+ * Author<作者姓名>:   stars
+ * CreateTime<创建时间>:   2025/12/15 16:03
+ * UpdateTime<修改时间>:   2025/12/15 16:03
+ * Description〈功能简述〉: 用于文件上传响应
+ * History<历史描述>:
+ * Since<版本号>: 1.0.0
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ApiResponse<T> {
+    private boolean success;
+    private String message;
+    private T data;
+    private long timestamp;
+
+    public static <T> ApiResponse<T> success(T data) {
+        return ApiResponse.<T>builder()
+                .success(true)
+                .message("success")
+                .data(data)
+                .timestamp(System.currentTimeMillis())
+                .build();
+    }
+
+    public static <T> ApiResponse<T> error(String message) {
+        return ApiResponse.<T>builder()
+                .success(false)
+                .message(message)
+                .data(null)
+                .timestamp(System.currentTimeMillis())
+                .build();
+    }
+}

+ 31 - 0
imwork-commons/imwork-commons-core/src/main/java/top/imwork/commons/core/pojo/FileUploadResponse.java

@@ -0,0 +1,31 @@
+package top.imwork.commons.core.pojo;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.time.LocalDateTime;
+/**
+ * Copyright (C), 2015-2025
+ * FileName: FileUploadResponse
+ * Author<作者姓名>:   stars
+ * CreateTime<创建时间>:   2025/12/15 16:00
+ * UpdateTime<修改时间>:   2025/12/15 16:00
+ * Description〈功能简述〉: 文件上传响应对像
+ * History<历史描述>:
+ * Since<版本号>: 1.0.0
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class FileUploadResponse {
+    private String fileName;      // 原始文件名
+    private String filePath;      // 服务器存储路径
+    private String fileUrl;       // 访问URL
+    private String fileType;      // 文件类型
+    private Long fileSize;        // 文件大小
+    private String fileHash;      // 文件哈希值
+    private LocalDateTime uploadTime; // 上传时间
+}

+ 63 - 0
imwork-exercise/src/main/java/top/imwork/exercise/ChapterSplitter.java

@@ -0,0 +1,63 @@
+package top.imwork.exercise;
+
+import java.util.List;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.regex.Pattern;
+
+public class ChapterSplitter {
+
+    /**
+     * 使用虚拟线程处理章节分割的高级方法
+     */
+    public List<String> splitChapters(String content, Pattern delimiterPattern) {
+        ConcurrentLinkedQueue<String> chapters = new ConcurrentLinkedQueue<>();
+
+        try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
+
+            // 找到所有分隔符位置
+            var matcher = delimiterPattern.matcher(content);
+            List<Integer> positions = matcher.results()
+                    .map(match -> match.start())
+                    .toList();
+
+            // 创建并提交所有处理任务
+            for (int i = 0; i < positions.size(); i++) {
+                final int index = i;
+                executor.submit(() -> {
+                    int start = positions.get(index);
+                    int end = (index < positions.size() - 1)
+                            ? positions.get(index + 1)
+                            : content.length();
+
+                    String chapter = content.substring(start, end).trim();
+                    chapters.add(chapter);
+
+                    // 处理进度提示
+                    if (chapters.size() % 10 == 0) {
+                        System.out.println(Thread.currentThread() +
+                                " 已处理 " + chapters.size() + " 个章节");
+                    }
+                });
+            }
+        }
+
+        return chapters.stream().toList();
+    }
+
+    /**
+     * 预定义的章节分割模式
+     */
+    public static Pattern getChineseChapterPattern() {
+        return Pattern.compile("第[一二三四五六七八九十零百千万0-9]+章\\s*[::】]?\\s*.+");
+    }
+
+    public static Pattern getEnglishChapterPattern() {
+        return Pattern.compile("CHAPTER\\s+[0-9IVXL]+\\s*[:-]?\\s*.+", Pattern.CASE_INSENSITIVE);
+    }
+
+    public static Pattern getNumberChapterPattern() {
+        return Pattern.compile("^\\d+\\.[\\s\\u3000].+", Pattern.MULTILINE);
+    }
+}

+ 307 - 0
imwork-exercise/src/main/java/top/imwork/exercise/Test.java

@@ -0,0 +1,307 @@
+package top.imwork.exercise;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.Scanner;
+
+public class Main {
+    public static void main(String[] args) {
+        Scanner scanner = new Scanner(System.in);
+
+        System.out.println("=== TXT文件章节分割器 ===");
+        System.out.println("Java版本: " + System.getProperty("java.version"));
+        System.out.println("虚拟线程支持: " + (checkVirtualThreadSupport() ? "是" : "否"));
+        System.out.println("结构化并发支持: " + (checkStructuredConcurrencySupport() ? "是" : "否"));
+
+        System.out.println("\n请选择运行模式:");
+        System.out.println("1. 使用虚拟线程");
+        System.out.println("2. 使用传统线程池");
+        System.out.println("3. 使用并行流");
+        System.out.println("4. 使用结构化并发(预览功能)");
+        System.out.println("5. 生成示例文件并测试所有模式");
+        System.out.println("6. 性能对比测试");
+
+        System.out.print("请选择 (1-6): ");
+        int choice = scanner.nextInt();
+        scanner.nextLine(); // 消耗换行符
+
+        try {
+            switch (choice) {
+                case 1:
+                    runWithVirtualThreads(scanner);
+                    break;
+                case 2:
+                    runWithThreadPool(scanner);
+                    break;
+                case 3:
+                    runWithParallelStream(scanner);
+                    break;
+                case 4:
+                    runWithStructuredConcurrency(scanner);
+                    break;
+                case 5:
+                    generateSampleFileAndTest();
+                    break;
+                case 6:
+                    runPerformanceComparison();
+                    break;
+                default:
+                    System.out.println("无效选择,使用虚拟线程模式");
+                    runWithVirtualThreads(scanner);
+            }
+        } catch (Exception e) {
+            System.err.println("程序执行出错: " + e.getMessage());
+            e.printStackTrace();
+        } finally {
+            scanner.close();
+        }
+    }
+
+    private static boolean checkVirtualThreadSupport() {
+        try {
+            Executors.class.getMethod("newVirtualThreadPerTaskExecutor");
+            return true;
+        } catch (NoSuchMethodException e) {
+            return false;
+        }
+    }
+
+    private static boolean checkStructuredConcurrencySupport() {
+        try {
+            Class.forName("java.util.concurrent.StructuredTaskScope");
+            return true;
+        } catch (ClassNotFoundException e) {
+            return false;
+        }
+    }
+
+    private static void runWithVirtualThreads(Scanner scanner) throws Exception {
+        if (!checkVirtualThreadSupport()) {
+            System.out.println("当前Java版本不支持虚拟线程!");
+            System.out.println("请使用Java 21或更高版本,并添加--enable-preview参数");
+            return;
+        }
+
+        String[] fileAndOutput = getFileAndOutputPath(scanner);
+        ChapterSplitter splitter = new ChapterSplitter(fileAndOutput[0], true);
+        System.out.println("开始使用虚拟线程处理章节...");
+
+        List<Chapter> chapters = splitter.splitWithVirtualThreads();
+
+        splitter.printStatistics(chapters);
+        splitter.saveChaptersToFiles(chapters, fileAndOutput[1]);
+
+        System.out.println("处理完成!");
+    }
+
+    private static void runWithThreadPool(Scanner scanner) throws Exception {
+        System.out.print("请输入线程池大小(默认为CPU核心数): ");
+        String threadCountStr = scanner.nextLine();
+        int threadCount = threadCountStr.isEmpty()
+                ? Runtime.getRuntime().availableProcessors()
+                : Integer.parseInt(threadCountStr);
+
+        String[] fileAndOutput = getFileAndOutputPath(scanner);
+        ChapterSplitter splitter = new ChapterSplitter(fileAndOutput[0], false);
+        System.out.printf("开始使用线程池(%d线程)处理章节...%n", threadCount);
+
+        List<Chapter> chapters = splitter.splitWithThreadPool(threadCount);
+
+        splitter.printStatistics(chapters);
+        splitter.saveChaptersToFiles(chapters, fileAndOutput[1]);
+
+        System.out.println("处理完成!");
+    }
+
+    private static void runWithParallelStream(Scanner scanner) throws Exception {
+        String[] fileAndOutput = getFileAndOutputPath(scanner);
+        ChapterSplitter splitter = new ChapterSplitter(fileAndOutput[0], false);
+        System.out.println("开始使用并行流处理章节...");
+
+        List<Chapter> chapters = splitter.splitWithParallelStream();
+
+        splitter.printStatistics(chapters);
+        splitter.saveChaptersToFiles(chapters, fileAndOutput[1]);
+
+        System.out.println("处理完成!");
+    }
+
+    private static void runWithStructuredConcurrency(Scanner scanner) throws Exception {
+        if (!checkStructuredConcurrencySupport()) {
+            System.out.println("当前Java版本不支持结构化并发!");
+            System.out.println("请使用Java 21或更高版本,并添加--enable-preview参数");
+            return;
+        }
+
+        String[] fileAndOutput = getFileAndOutputPath(scanner);
+        ChapterSplitter splitter = new ChapterSplitter(fileAndOutput[0], true);
+        System.out.println("开始使用结构化并发处理章节...");
+
+        List<Chapter> chapters = splitter.splitWithStructuredConcurrency();
+
+        splitter.printStatistics(chapters);
+        splitter.saveChaptersToFiles(chapters, fileAndOutput[1]);
+
+        System.out.println("处理完成!");
+    }
+
+    private static String[] getFileAndOutputPath(Scanner scanner) throws IOException {
+        System.out.print("请输入TXT文件路径: ");
+        String filePath = scanner.nextLine();
+
+        if (!Files.exists(Paths.get(filePath))) {
+            System.out.println("文件不存在,使用示例文件");
+            filePath = "sample.txt";
+            if (!Files.exists(Paths.get(filePath))) {
+                generateSampleFile();
+            }
+        }
+
+        System.out.print("请输入输出目录(默认为output): ");
+        String outputDir = scanner.nextLine();
+        if (outputDir.isEmpty()) {
+            outputDir = "output";
+        }
+
+        return new String[]{filePath, outputDir};
+    }
+
+    private static void generateSampleFileAndTest() throws Exception {
+        generateSampleFile();
+
+        System.out.println("已生成示例文件,开始测试各种模式...\n");
+
+        String[] testCases = {
+                "虚拟线程", "传统线程池", "并行流", "结构化并发"
+        };
+
+        for (String testCase : testCases) {
+            System.out.println("\n=== 测试" + testCase + " ===");
+            try {
+                testProcessingMode(testCase);
+            } catch (Exception e) {
+                System.out.println(testCase + " 测试失败: " + e.getMessage());
+            }
+            System.out.println("等待3秒...\n");
+            Thread.sleep(3000);
+        }
+
+        System.out.println("所有测试完成!");
+    }
+
+    private static void testProcessingMode(String mode) throws Exception {
+        ChapterSplitter splitter = new ChapterSplitter("sample.txt", true);
+        List<Chapter> chapters;
+
+        switch (mode) {
+            case "虚拟线程":
+                if (!checkVirtualThreadSupport()) {
+                    System.out.println("跳过 - 不支持虚拟线程");
+                    return;
+                }
+                chapters = splitter.splitWithVirtualThreads();
+                break;
+            case "传统线程池":
+                chapters = splitter.splitWithThreadPool(4);
+                break;
+            case "并行流":
+                chapters = splitter.splitWithParallelStream();
+                break;
+            case "结构化并发":
+                if (!checkStructuredConcurrencySupport()) {
+                    System.out.println("跳过 - 不支持结构化并发");
+                    return;
+                }
+                chapters = splitter.splitWithStructuredConcurrency();
+                break;
+            default:
+                return;
+        }
+
+        splitter.printStatistics(chapters);
+        splitter.saveChaptersToFiles(chapters, "output_" + mode);
+    }
+
+    private static void runPerformanceComparison() throws Exception {
+        generateSampleFile();
+
+        System.out.println("开始性能对比测试...\n");
+
+        // 测试不同模式
+        String[][] testConfigs = {
+                {"虚拟线程", "true"},
+                {"传统线程池-4线程", "false"},
+                {"传统线程池-8线程", "false"},
+                {"并行流", "false"}
+        };
+
+        for (String[] config : testConfigs) {
+            String name = config[0];
+            boolean useVirtual = Boolean.parseBoolean(config[1]);
+
+            System.out.println("测试模式: " + name);
+
+            long startTime = System.currentTimeMillis();
+            ChapterSplitter splitter = new ChapterSplitter("sample.txt", useVirtual);
+
+            List<Chapter> chapters;
+            if (name.contains("虚拟线程")) {
+                chapters = splitter.splitWithVirtualThreads();
+            } else if (name.contains("传统线程池")) {
+                int threads = Integer.parseInt(name.split("-")[1].replace("线程", ""));
+                chapters = splitter.splitWithThreadPool(threads);
+            } else {
+                chapters = splitter.splitWithParallelStream();
+            }
+
+            long endTime = System.currentTimeMillis();
+            long duration = endTime - startTime;
+
+            System.out.printf("处理时间: %d ms, 章节数: %d, 总字数: %,d%n%n",
+                    duration, chapters.size(),
+                    chapters.stream().mapToInt(Chapter::getWordCount).sum());
+
+            Thread.sleep(1000); // 等待1秒
+        }
+    }
+
+    private static void generateSampleFile() throws IOException {
+        String sampleContent = buildSampleContent();
+        Files.writeString(Paths.get("sample.txt"), sampleContent);
+        System.out.println("已生成示例文件: sample.txt");
+        System.out.printf("文件大小: %,d 字节%n", Files.size(Paths.get("sample.txt")));
+    }
+
+    private static String buildSampleContent() {
+        StringBuilder sb = new StringBuilder();
+
+        sb.append("第零章 前言\n\n");
+        sb.append("这是一个示例文本文件,用于测试章节分割功能。\n");
+        sb.append("本文件包含多个章节,每个章节都有不同的内容。\n\n");
+
+        // 生成更多章节用于测试
+        for (int i = 1; i <= 100; i++) {
+            sb.append(String.format("第%d章 章节标题%d\n\n", i, i));
+            sb.append("这是第").append(i).append("章的内容。\n");
+            sb.append("本章节包含一些示例文本,用于演示多线程处理能力。\n");
+
+            // 添加一些变长内容
+            int paragraphCount = i % 20 + 1;
+            for (int j = 0; j < paragraphCount; j++) {
+                sb.append("虚拟线程是Java 21引入的重要特性,它使得高并发编程更加简单高效。");
+                sb.append("通过虚拟线程,我们可以创建数百万个线程而不用担心系统资源耗尽。");
+                sb.append("这种轻量级线程非常适合I/O密集型任务,如文件处理、网络请求等。");
+            }
+            sb.append("\n\n");
+        }
+
+        sb.append("第一百零一章 终章\n\n");
+        sb.append("这是最后一个章节,标志着本书的结束。\n");
+        sb.append("感谢阅读!\n");
+
+        return sb.toString();
+    }
+}

+ 159 - 0
imwork-exercise/src/main/java/top/imwork/exercise/VirtualThreadFileProcessor.java

@@ -0,0 +1,159 @@
+package top.imwork.exercise;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.regex.Pattern;
+
+public class VirtualThreadFileProcessor {
+
+    public static void main(String[] args) {
+        if (args.length < 1) {
+            System.out.println("用法: java VirtualThreadFileProcessor <文件路径>");
+            System.exit(1);
+        }
+
+        String filePath = args[0];
+        Path path = Paths.get(filePath);
+
+        try {
+            // 读取文件内容
+            String content = Files.readString(path);
+
+            // 使用虚拟线程池处理章节拆分
+            List<Chapter> chapters = splitChaptersWithVirtualThreads(content);
+
+            // 输出结果
+            System.out.println("总共拆分成 " + chapters.size() + " 个章节");
+
+            // 保存章节到文件
+            saveChaptersToFiles(chapters);
+
+        } catch (IOException e) {
+            System.err.println("读取文件出错: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 使用虚拟线程拆分章节
+     */
+    private static List<Chapter> splitChaptersWithVirtualThreads(String content) throws IOException {
+        // 定义章节分割模式(可根据实际文件格式调整)
+        Pattern chapterPattern = Pattern.compile(
+                "(?i)(第[一二三四五六七八九十零百千0-9]+章|第[0-9]+节|CHAPTER\\s+[0-9IVX]+)"
+        );
+
+        // 查找所有章节标题位置
+        List<Integer> chapterStarts = new ArrayList<>();
+        var matcher = chapterPattern.matcher(content);
+        while (matcher.find()) {
+            chapterStarts.add(matcher.start());
+        }
+
+        List<Chapter> chapters = new ArrayList<>();
+        List<CompletableFuture<Chapter>> futures = new ArrayList<>();
+
+        try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
+
+            // 为每个章节创建虚拟线程任务
+            for (int i = 0; i < chapterStarts.size(); i++) {
+                int startIndex = chapterStarts.get(i);
+                int endIndex = (i < chapterStarts.size() - 1)
+                        ? chapterStarts.get(i + 1)
+                        : content.length();
+                int chapterNumber = i + 1;
+
+                CompletableFuture<Chapter> future = CompletableFuture.supplyAsync(() -> {
+                    try {
+                        String chapterContent = content.substring(startIndex, endIndex).trim();
+                        return new Chapter(chapterNumber, extractChapterTitle(chapterContent), chapterContent);
+                    } catch (Exception e) {
+                        System.err.println("处理章节 " + chapterNumber + " 时出错: " + e.getMessage());
+                        return new Chapter(chapterNumber, "错误章节", "");
+                    }
+                }, executor);
+
+                futures.add(future);
+            }
+
+            // 等待所有任务完成
+            CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
+
+            // 收集结果
+            for (var future : futures) {
+                chapters.add(future.join());
+            }
+        }
+
+        return chapters;
+    }
+
+    /**
+     * 提取章节标题
+     */
+    private static String extractChapterTitle(String content) {
+        // 取前两行作为标题,或找到第一个换行符
+        int firstNewline = content.indexOf('\n');
+        if (firstNewline != -1) {
+            String title = content.substring(0, Math.min(firstNewline, 100)).trim();
+            return title.length() > 50 ? title.substring(0, 50) + "..." : title;
+        }
+        return content.length() > 50 ? content.substring(0, 50) + "..." : content;
+    }
+
+    /**
+     * 保存章节到单独的文件
+     */
+    private static void saveChaptersToFiles(List<Chapter> chapters) {
+        try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
+            List<CompletableFuture<Void>> saveFutures = new ArrayList<>();
+
+            for (Chapter chapter : chapters) {
+                saveFutures.add(CompletableFuture.runAsync(() -> {
+                    try {
+                        String fileName = String.format("chapter_%03d.txt", chapter.getNumber());
+                        Path outputPath = Paths.get("output", fileName);
+                        Files.createDirectories(outputPath.getParent());
+                        Files.writeString(outputPath, chapter.getContent());
+                        System.out.println("已保存: " + fileName);
+                    } catch (IOException e) {
+                        System.err.println("保存章节 " + chapter.getNumber() + " 失败: " + e.getMessage());
+                    }
+                }, executor));
+            }
+
+            CompletableFuture.allOf(saveFutures.toArray(new CompletableFuture[0])).join();
+        }
+    }
+
+    /**
+     * 章节数据类
+     */
+    static class Chapter {
+        private final int number;
+        private final String title;
+        private final String content;
+
+        public Chapter(int number, String title, String content) {
+            this.number = number;
+            this.title = title;
+            this.content = content;
+        }
+
+        public int getNumber() { return number; }
+        public String getTitle() { return title; }
+        public String getContent() { return content; }
+
+        @Override
+        public String toString() {
+            return String.format("第%d章: %s (长度: %d 字符)",
+                    number, title, content.length());
+        }
+    }
+}

+ 8 - 2
imwork-windows/imwork-silos/pom.xml

@@ -20,6 +20,11 @@
         <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
     </properties>
     <dependencies>
+        <!-- 用于文件类型检测 -->
+        <dependency>
+            <groupId>org.apache.tika</groupId>
+            <artifactId>tika-core</artifactId>
+        </dependency>
         <dependency>
             <groupId>org.projectlombok</groupId>
             <artifactId>lombok</artifactId>
@@ -103,8 +108,8 @@
                 <artifactId>maven-compiler-plugin</artifactId>
                 <version>3.14.1</version>
                 <configuration>
-                    <source>${java.version}</source>
-                    <target>${java.version}</target>
+                    <source>25</source>
+                    <target>25</target>
                     <release>${java.version}</release>
                     <encoding>UTF-8</encoding>
                     <annotationProcessorPaths>
@@ -114,6 +119,7 @@
                             <version>${lombok.version}</version>
                         </path>
                     </annotationProcessorPaths>
+                    <compilerArgs>--enable-preview</compilerArgs>
                 </configuration>
             </plugin>
             <plugin>

+ 1 - 0
imwork-windows/imwork-silos/src/main/java/top/imwork/window/silos/config/WebMvcConfig.java

@@ -17,6 +17,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
  */
 @Configuration
 public class WebMvcConfig implements WebMvcConfigurer {
+
     /**
      *
      * 登录页视图控制

+ 78 - 0
imwork-windows/imwork-silos/src/main/java/top/imwork/window/silos/controller/oss/FilesUploadController.java

@@ -0,0 +1,78 @@
+package top.imwork.window.silos.controller.oss;
+
+import jakarta.servlet.http.HttpServletRequest;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+import top.imwork.commons.core.pojo.ApiResponse;
+import top.imwork.commons.core.pojo.FileUploadResponse;
+import top.imwork.window.silos.service.oss.FileUploadService;
+import top.imwork.window.silos.utils.SecurityUtils;
+
+import java.util.List;
+
+/**
+ * Copyright (C), 2015-2025
+ * FileName: FilesUploadController
+ * Author<作者姓名>:   stars
+ * CreateTime<创建时间>:   2025/12/15 15:54
+ * UpdateTime<修改时间>:   2025/12/15 15:54
+ * Description〈功能简述〉: 文件上传控制器
+ * History<历史描述>:
+ * Since<版本号>: 1.0.0
+ */
+@Slf4j
+@RestController
+@RequestMapping("/api")
+@CrossOrigin(origins = "*")
+public class FilesUploadController {
+    @Autowired
+    private FileUploadService fileUploadService;
+
+    @Value("${upload.allowed-extensions}")
+    private List<String> allowedExtensions;
+
+    @PostMapping("/upload")
+    public ResponseEntity<ApiResponse<FileUploadResponse>> uploadFile(
+            @RequestParam("file") MultipartFile file,
+            @RequestParam(value = "fileName", required = false) String fileName,
+            @RequestParam(value = "fileHash", required = false) String fileHash,
+            HttpServletRequest request) {
+
+        try {
+            log.info("收到文件上传请求: 文件名={}, 大小={}, 类型={}",
+                    file.getOriginalFilename(),
+                    file.getSize(),
+                    file.getContentType());
+
+            // 1. 基础校验
+            if (file.isEmpty()) {
+                return ResponseEntity.badRequest()
+                        .body(ApiResponse.error("文件不能为空"));
+            }
+
+            // 2. 安全检查
+            String securityCheck = SecurityUtils.checkFileSecurity(file);
+            if (securityCheck != null) {
+                log.warn("文件安全检查失败: {}", securityCheck);
+                return ResponseEntity.badRequest()
+                        .body(ApiResponse.error(securityCheck));
+            }
+
+            // 3. 上传文件
+            FileUploadResponse uploadResponse = fileUploadService.uploadFile(file);
+
+            log.info("文件上传成功: {}", uploadResponse.getFilePath());
+
+            return ResponseEntity.ok(ApiResponse.success(uploadResponse));
+
+        } catch (Exception e) {
+            log.error("文件上传失败", e);
+            return ResponseEntity.internalServerError()
+                    .body(ApiResponse.error("文件上传失败: " + e.getMessage()));
+        }
+    }
+}

+ 1 - 0
imwork-windows/imwork-silos/src/main/java/top/imwork/window/silos/convert/InfoConvert.java

@@ -112,6 +112,7 @@ public class InfoConvert {
         infoDTO.setSource(infoParamVO.getSource());
         infoDTO.setCreatedAt(infoParamVO.getCreatedAt());
         infoDTO.setUpdatedAt(infoParamVO.getUpdatedAt());
+        infoDTO.setFilePaths(infoParamVO.getFilePaths());
         return infoDTO;
     }
 

+ 7 - 175
imwork-windows/imwork-silos/src/main/java/top/imwork/window/silos/pojo/dto/InfoDTO.java

@@ -1,5 +1,7 @@
 package top.imwork.window.silos.pojo.dto;
 
+import lombok.Data;
+
 import java.io.Serial;
 import java.io.Serializable;
 import java.math.BigDecimal;
@@ -12,6 +14,7 @@ import java.util.Date;
  * email: e-jiangxiaowei@outlook.com
  * date: 2025-11-04 13:16:26
  */
+@Data
 public class InfoDTO implements Serializable {
     @Serial
     private static final long serialVersionUID = 1L;
@@ -104,179 +107,8 @@ public class InfoDTO implements Serializable {
      */
     private Date updatedAt;
 
-    public Long getId() {
-        return id;
-    }
-
-    public void setId(Long id) {
-        this.id = id;
-    }
-
-    public String getIsbn() {
-        return isbn;
-    }
-
-    public void setIsbn(String isbn) {
-        this.isbn = isbn;
-    }
-
-    public String getTitle() {
-        return title;
-    }
-
-    public void setTitle(String title) {
-        this.title = title;
-    }
-
-    public String getSubtitle() {
-        return subtitle;
-    }
-
-    public void setSubtitle(String subtitle) {
-        this.subtitle = subtitle;
-    }
-
-    public String getAuthor() {
-        return author;
-    }
-
-    public void setAuthor(String author) {
-        this.author = author;
-    }
-
-    public String getOtherAuthors() {
-        return otherAuthors;
-    }
-
-    public void setOtherAuthors(String otherAuthors) {
-        this.otherAuthors = otherAuthors;
-    }
-
-    public String getPublisher() {
-        return publisher;
-    }
-
-    public void setPublisher(String publisher) {
-        this.publisher = publisher;
-    }
-
-    public Date getPublishDate() {
-        return publishDate;
-    }
-
-    public void setPublishDate(Date publishDate) {
-        this.publishDate = publishDate;
-    }
-
-    public String getEdition() {
-        return edition;
-    }
-
-    public void setEdition(String edition) {
-        this.edition = edition;
-    }
-
-    public String getCategoryCode() {
-        return categoryCode;
-    }
-
-    public void setCategoryCode(String categoryCode) {
-        this.categoryCode = categoryCode;
-    }
-
-    public String getLanguage() {
-        return language;
-    }
-
-    public void setLanguage(String language) {
-        this.language = language;
-    }
-
-    public String getCoverImage() {
-        return coverImage;
-    }
-
-    public void setCoverImage(String coverImage) {
-        this.coverImage = coverImage;
-    }
-
-    public String getSummary() {
-        return summary;
-    }
-
-    public void setSummary(String summary) {
-        this.summary = summary;
-    }
-
-    public String getToc() {
-        return toc;
-    }
-
-    public void setToc(String toc) {
-        this.toc = toc;
-    }
-
-    public BigDecimal getPrice() {
-        return price;
-    }
-
-    public void setPrice(BigDecimal price) {
-        this.price = price;
-    }
-
-    public Integer getPages() {
-        return pages;
-    }
-
-    public void setPages(Integer pages) {
-        this.pages = pages;
-    }
-
-    public String getDimensions() {
-        return dimensions;
-    }
-
-    public void setDimensions(String dimensions) {
-        this.dimensions = dimensions;
-    }
-
-    public String getBinding() {
-        return binding;
-    }
-
-    public void setBinding(String binding) {
-        this.binding = binding;
-    }
-
-    public String getSeries() {
-        return series;
-    }
-
-    public void setSeries(String series) {
-        this.series = series;
-    }
-
-    public String getSource() {
-        return source;
-    }
-
-    public void setSource(String source) {
-        this.source = source;
-    }
-
-    public Date getCreatedAt() {
-        return createdAt;
-    }
-
-    public void setCreatedAt(Date createdAt) {
-        this.createdAt = createdAt;
-    }
-
-    public Date getUpdatedAt() {
-        return updatedAt;
-    }
-
-    public void setUpdatedAt(Date updatedAt) {
-        this.updatedAt = updatedAt;
-    }
+    /**
+     * 导入文件的路径
+     */
+    private String filePaths;
 }

+ 7 - 175
imwork-windows/imwork-silos/src/main/java/top/imwork/window/silos/pojo/po/InfoParamVO.java

@@ -1,5 +1,7 @@
 package top.imwork.window.silos.pojo.po;
 
+import lombok.Data;
+
 import java.io.Serial;
 import java.io.Serializable;
 import java.math.BigDecimal;
@@ -12,6 +14,7 @@ import java.util.Date;
  * email: e-jiangxiaowei@outlook.com
  * date: 2025-11-04 13:16:26
  */
+@Data
 public class InfoParamVO implements Serializable {
     @Serial
     private static final long serialVersionUID = 1L;
@@ -104,179 +107,8 @@ public class InfoParamVO implements Serializable {
      */
     private Date updatedAt;
 
-    public Date getPublishDate() {
-        return publishDate;
-    }
-
-    public void setPublishDate(Date publishDate) {
-        this.publishDate = publishDate;
-    }
-
-    public Long getId() {
-        return id;
-    }
-
-    public void setId(Long id) {
-        this.id = id;
-    }
-
-    public String getIsbn() {
-        return isbn;
-    }
-
-    public void setIsbn(String isbn) {
-        this.isbn = isbn;
-    }
-
-    public String getTitle() {
-        return title;
-    }
-
-    public void setTitle(String title) {
-        this.title = title;
-    }
-
-    public String getSubtitle() {
-        return subtitle;
-    }
-
-    public void setSubtitle(String subtitle) {
-        this.subtitle = subtitle;
-    }
-
-    public String getAuthor() {
-        return author;
-    }
-
-    public void setAuthor(String author) {
-        this.author = author;
-    }
-
-    public String getOtherAuthors() {
-        return otherAuthors;
-    }
-
-    public void setOtherAuthors(String otherAuthors) {
-        this.otherAuthors = otherAuthors;
-    }
-
-    public String getPublisher() {
-        return publisher;
-    }
-
-    public void setPublisher(String publisher) {
-        this.publisher = publisher;
-    }
-
-    public String getEdition() {
-        return edition;
-    }
-
-    public void setEdition(String edition) {
-        this.edition = edition;
-    }
-
-    public String getCategoryCode() {
-        return categoryCode;
-    }
-
-    public void setCategoryCode(String categoryCode) {
-        this.categoryCode = categoryCode;
-    }
-
-    public String getLanguage() {
-        return language;
-    }
-
-    public void setLanguage(String language) {
-        this.language = language;
-    }
-
-    public String getCoverImage() {
-        return coverImage;
-    }
-
-    public void setCoverImage(String coverImage) {
-        this.coverImage = coverImage;
-    }
-
-    public String getSummary() {
-        return summary;
-    }
-
-    public void setSummary(String summary) {
-        this.summary = summary;
-    }
-
-    public String getToc() {
-        return toc;
-    }
-
-    public void setToc(String toc) {
-        this.toc = toc;
-    }
-
-    public BigDecimal getPrice() {
-        return price;
-    }
-
-    public void setPrice(BigDecimal price) {
-        this.price = price;
-    }
-
-    public Integer getPages() {
-        return pages;
-    }
-
-    public void setPages(Integer pages) {
-        this.pages = pages;
-    }
-
-    public String getDimensions() {
-        return dimensions;
-    }
-
-    public void setDimensions(String dimensions) {
-        this.dimensions = dimensions;
-    }
-
-    public String getBinding() {
-        return binding;
-    }
-
-    public void setBinding(String binding) {
-        this.binding = binding;
-    }
-
-    public String getSeries() {
-        return series;
-    }
-
-    public void setSeries(String series) {
-        this.series = series;
-    }
-
-    public String getSource() {
-        return source;
-    }
-
-    public void setSource(String source) {
-        this.source = source;
-    }
-
-    public Date getCreatedAt() {
-        return createdAt;
-    }
-
-    public void setCreatedAt(Date createdAt) {
-        this.createdAt = createdAt;
-    }
-
-    public Date getUpdatedAt() {
-        return updatedAt;
-    }
-
-    public void setUpdatedAt(Date updatedAt) {
-        this.updatedAt = updatedAt;
-    }
+    /**
+     * 导入文件的路径
+     */
+    private String filePaths;
 }

+ 40 - 0
imwork-windows/imwork-silos/src/main/java/top/imwork/window/silos/processor/Chapter.java

@@ -0,0 +1,40 @@
+package top.imwork.window.silos.processor;
+
+import java.time.LocalDateTime;
+
+public class Chapter {
+    private String title;
+    private String content;
+    private int chapterNumber;
+    private LocalDateTime processedTime;
+    private int wordCount;
+
+    public Chapter(String title, String content, int chapterNumber) {
+        this.title = title;
+        this.content = content;
+        this.chapterNumber = chapterNumber;
+        this.processedTime = LocalDateTime.now();
+        this.wordCount = content.split("\\s+").length;
+    }
+
+    // Getter 和 Setter 方法
+    public String getTitle() { return title; }
+    public void setTitle(String title) { this.title = title; }
+
+    public String getContent() { return content; }
+    public void setContent(String content) { this.content = content; }
+
+    public int getChapterNumber() { return chapterNumber; }
+    public void setChapterNumber(int chapterNumber) { this.chapterNumber = chapterNumber; }
+
+    public LocalDateTime getProcessedTime() { return processedTime; }
+    public void setProcessedTime(LocalDateTime processedTime) { this.processedTime = processedTime; }
+
+    public int getWordCount() { return wordCount; }
+
+    @Override
+    public String toString() {
+        return String.format("第%d章: %s (字数: %d, 处理时间: %s)",
+                chapterNumber, title, wordCount, processedTime);
+    }
+}

+ 462 - 0
imwork-windows/imwork-silos/src/main/java/top/imwork/window/silos/processor/ChapterSplitter.java

@@ -0,0 +1,462 @@
+package top.imwork.window.silos.processor;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+public class ChapterSplitter {
+    // 章节分隔符的正则表达式
+    private static final Pattern CHAPTER_PATTERN = Pattern.compile(
+            "^第[零一二三四五六七八九十百千万\\d]+章[\\s::]*[^\\n]*$",
+            Pattern.MULTILINE
+    );
+
+    private final Path filePath;
+    private final boolean useVirtualThreads;
+    private final AtomicInteger processedChapters = new AtomicInteger(0);
+    private final ConcurrentHashMap<Integer, Chapter> chapterMap = new ConcurrentHashMap<>();
+    private List<ChapterTask> tasks;
+
+    public ChapterSplitter(String filePath, boolean useVirtualThreads) {
+        this.filePath = Paths.get(filePath);
+        this.useVirtualThreads = useVirtualThreads;
+    }
+
+    /**
+     * 使用虚拟线程处理章节(Java 21+)
+     */
+    public List<Chapter> splitWithVirtualThreads() throws IOException, InterruptedException {
+        String content = Files.readString(filePath);
+        System.out.printf("文件读取完成,总字符数: %,d%n", content.length());
+
+        tasks = findChapterTasks(content);
+        System.out.printf("发现 %d 个章节%n", tasks.size());
+
+        Instant start = Instant.now();
+
+        // 方法1: 使用虚拟线程的Executor
+        try (ExecutorService executor = createVirtualThreadExecutor()) {
+            List<Future<Chapter>> futures = new ArrayList<>();
+
+            for (ChapterTask task : tasks) {
+                Future<Chapter> future = executor.submit(() -> processChapter(task));
+                futures.add(future);
+            }
+
+            // 收集结果
+            List<Chapter> chapters = new ArrayList<>();
+            for (Future<Chapter> future : futures) {
+                try {
+                    Chapter chapter = future.get();
+                    if (chapter != null) {
+                        chapters.add(chapter);
+                        chapterMap.put(chapter.getChapterNumber(), chapter);
+                    }
+                } catch (ExecutionException e) {
+                    System.err.printf("处理章节失败: %s%n", e.getCause().getMessage());
+                }
+            }
+
+            Instant end = Instant.now();
+            System.out.printf("所有章节处理完成,耗时: %.2f秒%n",
+                    Duration.between(start, end).toMillis() / 1000.0);
+
+            return chapters;
+        }
+    }
+
+    /**
+     * 使用结构化并发(需要Java 21+且启用预览功能)
+     * 编译时需添加:--enable-preview
+     * 运行时需添加:--enable-preview
+     */
+    public List<Chapter> splitWithStructuredConcurrency() throws IOException, InterruptedException {
+        // 检查是否支持结构化并发
+        try {
+            Class.forName("java.util.concurrent.StructuredTaskScope");
+        } catch (ClassNotFoundException e) {
+            System.out.println("当前Java版本不支持StructuredTaskScope,使用替代方案");
+            return splitWithVirtualThreads();
+        }
+
+        return splitWithStructuredTaskScope();
+    }
+
+    private List<Chapter> splitWithStructuredTaskScope() throws IOException, InterruptedException {
+        String content = Files.readString(filePath);
+        System.out.printf("文件读取完成,总字符数: %,d%n", content.length());
+
+        tasks = findChapterTasks(content);
+        System.out.printf("发现 %d 个章节%n", tasks.size());
+
+        Instant start = Instant.now();
+
+        // 使用反射调用StructuredTaskScope,避免编译错误
+        try {
+            return invokeStructuredTaskScope(tasks);
+        } catch (Exception e) {
+            System.err.println("结构化并发执行失败,使用传统方式: " + e.getMessage());
+            return splitWithTraditionalThreads();
+        }
+    }
+
+    private List<Chapter> invokeStructuredTaskScope(List<ChapterTask> tasks) throws Exception {
+        // 使用反射创建StructuredTaskScope实例
+        Class<?> scopeClass = Class.forName("java.util.concurrent.StructuredTaskScope");
+        Class<?> shutdownOnFailureClass = Class.forName("java.util.concurrent.StructuredTaskScope$ShutdownOnFailure");
+
+        // 创建StructuredTaskScope实例
+        Object scope = scopeClass.getDeclaredConstructor().newInstance();
+
+        List<Future<Chapter>> futures = new ArrayList<>();
+
+        // 提交任务
+        for (ChapterTask task : tasks) {
+            Callable<Chapter> callable = () -> processChapter(task);
+
+            // 使用反射调用fork方法
+            var method = scopeClass.getMethod("fork", Callable.class);
+            @SuppressWarnings("unchecked")
+            Future<Chapter> future = (Future<Chapter>) method.invoke(scope, callable);
+            futures.add(future);
+        }
+
+        // 使用反射调用join方法
+        var joinMethod = scopeClass.getMethod("join");
+        joinMethod.invoke(scope);
+
+        // 使用反射调用throwIfFailed方法
+        var throwMethod = scopeClass.getMethod("throwIfFailed");
+        throwMethod.invoke(scope);
+
+        // 关闭scope
+        var closeMethod = scopeClass.getMethod("close");
+        closeMethod.invoke(scope);
+
+        // 收集结果
+        List<Chapter> chapters = new ArrayList<>();
+        for (Future<Chapter> future : futures) {
+            try {
+                Chapter chapter = future.get();
+                if (chapter != null) {
+                    chapters.add(chapter);
+                    chapterMap.put(chapter.getChapterNumber(), chapter);
+                }
+            } catch (ExecutionException e) {
+                System.err.printf("处理章节失败: %s%n", e.getCause().getMessage());
+            }
+        }
+
+        return chapters;
+    }
+
+    /**
+     * 使用传统线程池处理章节
+     */
+    public List<Chapter> splitWithThreadPool(int threadCount) throws IOException, InterruptedException {
+        String content = Files.readString(filePath);
+        System.out.printf("文件读取完成,总字符数: %,d%n", content.length());
+
+        tasks = findChapterTasks(content);
+        System.out.printf("发现 %d 个章节%n", tasks.size());
+
+        Instant start = Instant.now();
+
+        try (ExecutorService executor = Executors.newFixedThreadPool(threadCount)) {
+            List<CompletableFuture<Chapter>> futures = new ArrayList<>();
+
+            for (ChapterTask task : tasks) {
+                CompletableFuture<Chapter> future = CompletableFuture.supplyAsync(() -> {
+                    try {
+                        return processChapter(task);
+                    } catch (Exception e) {
+                        System.err.printf("处理章节失败: %s%n", e.getMessage());
+                        return null;
+                    }
+                }, executor);
+
+                futures.add(future);
+            }
+
+            // 等待所有任务完成
+            CompletableFuture<Void> allFutures = CompletableFuture.allOf(
+                    futures.toArray(new CompletableFuture[0])
+            );
+            allFutures.join();
+
+            Instant end = Instant.now();
+            System.out.printf("所有章节处理完成,耗时: %.2f秒%n",
+                    Duration.between(start, end).toMillis() / 1000.0);
+
+            // 收集结果
+            List<Chapter> chapters = new ArrayList<>();
+            for (int i = 0; i < tasks.size(); i++) {
+                Chapter chapter = futures.get(i).join();
+                if (chapter != null) {
+                    chapters.add(chapter);
+                    chapterMap.put(chapter.getChapterNumber(), chapter);
+                }
+            }
+
+            return chapters;
+        }
+    }
+
+    /**
+     * 使用传统线程处理(不使用CompletableFuture)
+     */
+    public List<Chapter> splitWithTraditionalThreads() throws IOException, InterruptedException {
+        String content = Files.readString(filePath);
+        System.out.printf("文件读取完成,总字符数: %,d%n", content.length());
+
+        tasks = findChapterTasks(content);
+        System.out.printf("发现 %d 个章节%n", tasks.size());
+
+        Instant start = Instant.now();
+
+        ExecutorService executor = getExecutor();
+        List<Future<Chapter>> futures = new ArrayList<>();
+
+        for (ChapterTask task : tasks) {
+            Future<Chapter> future = executor.submit(() -> processChapter(task));
+            futures.add(future);
+        }
+
+        // 收集结果
+        List<Chapter> chapters = new ArrayList<>();
+        for (Future<Chapter> future : futures) {
+            try {
+                Chapter chapter = future.get();
+                if (chapter != null) {
+                    chapters.add(chapter);
+                    chapterMap.put(chapter.getChapterNumber(), chapter);
+                }
+            } catch (ExecutionException e) {
+                System.err.printf("处理章节失败: %s%n", e.getCause().getMessage());
+            }
+        }
+
+        executor.shutdown();
+        executor.awaitTermination(1, TimeUnit.MINUTES);
+
+        Instant end = Instant.now();
+        System.out.printf("所有章节处理完成,耗时: %.2f秒%n",
+                Duration.between(start, end).toMillis() / 1000.0);
+
+        return chapters;
+    }
+
+    /**
+     * 使用并行流处理(最简单的替代方案)
+     */
+    public List<Chapter> splitWithParallelStream() throws IOException {
+        String content = Files.readString(filePath);
+        System.out.printf("文件读取完成,总字符数: %,d%n", content.length());
+
+        tasks = findChapterTasks(content);
+        System.out.printf("发现 %d 个章节%n", tasks.size());
+
+        Instant start = Instant.now();
+
+        // 使用并行流处理
+        List<Chapter> chapters = tasks.parallelStream()
+                .map(this::processChapter)
+                .filter(chapter -> chapter != null)
+                .collect(Collectors.toList());
+
+        // 更新chapterMap
+        for (Chapter chapter : chapters) {
+            chapterMap.put(chapter.getChapterNumber(), chapter);
+        }
+
+        Instant end = Instant.now();
+        System.out.printf("所有章节处理完成,耗时: %.2f秒%n",
+                Duration.between(start, end).toMillis() / 1000.0);
+
+        return chapters;
+    }
+
+    private Chapter processChapter(ChapterTask task) {
+        try {
+            // 模拟处理工作
+            simulateProcessing();
+
+            Chapter chapter = new Chapter(task.title, task.content, task.chapterNumber);
+
+            int processed = processedChapters.incrementAndGet();
+            int total = tasks != null ? tasks.size() : 0;
+
+            System.out.printf("[线程: %s] 处理进度: %d/%d - %s%n",
+                    getThreadInfo(),
+                    processed,
+                    total,
+                    chapter.getTitle());
+
+            return chapter;
+        } catch (Exception e) {
+            System.err.printf("处理章节 %s 时出错: %s%n", task.title, e.getMessage());
+            return null;
+        }
+    }
+
+    private void simulateProcessing() {
+        try {
+            // 模拟处理时间(10-100毫秒)
+            Thread.sleep((long) (Math.random() * 90 + 10));
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            throw new RuntimeException("处理被中断", e);
+        }
+    }
+
+    private String getThreadInfo() {
+        Thread thread = Thread.currentThread();
+        if (thread.isVirtual()) {
+            return "虚拟线程-" + thread.threadId();
+        } else {
+            return "平台线程-" + thread.threadId();
+        }
+    }
+
+    private List<ChapterTask> findChapterTasks(String content) {
+        List<ChapterTask> tasks = new ArrayList<>();
+        Matcher matcher = CHAPTER_PATTERN.matcher(content);
+
+        int lastEnd = 0;
+        int chapterNumber = 1;
+        String lastTitle = "前言";
+
+        while (matcher.find()) {
+            int start = matcher.start();
+
+            // 获取上一个章节的内容
+            if (start > lastEnd) {
+                String chapterContent = content.substring(lastEnd, start).trim();
+                if (!chapterContent.isEmpty()) {
+                    tasks.add(new ChapterTask(lastTitle, chapterContent, chapterNumber));
+                    chapterNumber++;
+                }
+            }
+
+            lastTitle = matcher.group().trim();
+            lastEnd = matcher.end();
+        }
+
+        // 处理最后一个章节
+        if (lastEnd < content.length()) {
+            String lastContent = content.substring(lastEnd).trim();
+            if (!lastContent.isEmpty()) {
+                tasks.add(new ChapterTask(lastTitle, lastContent, chapterNumber));
+            }
+        }
+
+        return tasks;
+    }
+
+    private ExecutorService createVirtualThreadExecutor() {
+        if (useVirtualThreads && supportsVirtualThreads()) {
+            return Executors.newVirtualThreadPerTaskExecutor();
+        } else {
+            int cores = Runtime.getRuntime().availableProcessors();
+            return Executors.newFixedThreadPool(cores);
+        }
+    }
+
+    private ExecutorService getExecutor() {
+        if (useVirtualThreads && supportsVirtualThreads()) {
+            return Executors.newVirtualThreadPerTaskExecutor();
+        } else {
+            return Executors.newFixedThreadPool(Math.min(tasks.size(), 100));
+        }
+    }
+
+    private boolean supportsVirtualThreads() {
+        try {
+            // 检查是否支持虚拟线程
+            Executors.class.getMethod("newVirtualThreadPerTaskExecutor");
+            return true;
+        } catch (NoSuchMethodException e) {
+            return false;
+        }
+    }
+
+    public void saveChaptersToFiles(List<Chapter> chapters, String outputDir) throws IOException {
+        Path outputPath = Paths.get(outputDir);
+        Files.createDirectories(outputPath);
+
+        System.out.println("\n开始保存章节文件...");
+
+        for (Chapter chapter : chapters) {
+            String fileName = String.format("chapter_%03d_%s.txt",
+                    chapter.getChapterNumber(),
+                    sanitizeFileName(chapter.getTitle()));
+
+            Path filePath = outputPath.resolve(fileName);
+            String fileContent = String.format("标题: %s%n%n%s",
+                    chapter.getTitle(), chapter.getContent());
+
+            Files.writeString(filePath, fileContent);
+            System.out.printf("已保存: %s (%,d 字)%n", fileName, chapter.getWordCount());
+        }
+
+        System.out.printf("共保存 %d 个章节文件到目录: %s%n", chapters.size(), outputPath.toAbsolutePath());
+    }
+
+    private String sanitizeFileName(String title) {
+        // 移除文件名中的非法字符
+        return title.replaceAll("[\\\\/:*?\"<>|]", "_")
+                .replaceAll("\\s+", "_")
+                .substring(0, Math.min(title.length(), 50));
+    }
+
+    public void printStatistics(List<Chapter> chapters) {
+        System.out.println("\n=== 章节统计 ===");
+        System.out.printf("总章节数: %d%n", chapters.size());
+
+        int totalWords = chapters.stream().mapToInt(Chapter::getWordCount).sum();
+        System.out.printf("总字数: %,d%n", totalWords);
+
+        double avgWords = chapters.stream()
+                .mapToInt(Chapter::getWordCount)
+                .average()
+                .orElse(0);
+        System.out.printf("平均每章字数: %.0f%n", avgWords);
+
+        Chapter longest = chapters.stream()
+                .max((c1, c2) -> Integer.compare(c1.getWordCount(), c2.getWordCount()))
+                .orElse(null);
+        Chapter shortest = chapters.stream()
+                .min((c1, c2) -> Integer.compare(c1.getWordCount(), c2.getWordCount()))
+                .orElse(null);
+
+        if (longest != null) {
+            System.out.printf("最长章节: %s (%,d字)%n", longest.getTitle(), longest.getWordCount());
+        }
+        if (shortest != null) {
+            System.out.printf("最短章节: %s (%,d字)%n", shortest.getTitle(), shortest.getWordCount());
+        }
+    }
+
+    // 内部任务类
+    private static class ChapterTask {
+        final String title;
+        final String content;
+        final int chapterNumber;
+
+        ChapterTask(String title, String content, int chapterNumber) {
+            this.title = title;
+            this.content = content;
+            this.chapterNumber = chapterNumber;
+        }
+    }
+}

+ 42 - 1
imwork-windows/imwork-silos/src/main/java/top/imwork/window/silos/service/impl/InfoServiceImpl.java

@@ -16,9 +16,18 @@ import top.imwork.window.silos.pojo.bo.InfoBO;
 import top.imwork.window.silos.pojo.bo.InfoResultBO;
 import top.imwork.window.silos.pojo.dto.InfoDTO;
 import top.imwork.window.silos.pojo.dto.InfoListDTO;
+import top.imwork.window.silos.processor.Chapter;
+import top.imwork.window.silos.processor.ChapterSplitter;
 import top.imwork.window.silos.service.IInfoService;
 import top.imwork.window.silos.convert.InfoConvert;
 
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.Scanner;
+import java.util.concurrent.Executors;
+
 @Service("infoService")
 public class InfoServiceImpl extends ServiceImpl<InfoDao,Info> implements IInfoService {
     @Resource
@@ -44,14 +53,46 @@ public class InfoServiceImpl extends ServiceImpl<InfoDao,Info> implements IInfoS
     public InfoBO save(InfoDTO infoDTO) {
         Info info = InfoConvert.infoDtoToDo(infoDTO);
         if (ObjectUtils.isEmpty(info.getId())) {
-
             infoDao.insert(info);
+            try {
+                runWithVirtualThreads(info,infoDTO.getFilePaths());
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
         } else {
             infoDao.updateById(info);
         }
         return InfoConvert.infoDoToBo(info);
     }
 
+    private static void runWithVirtualThreads(Info info,String filePath) throws Exception {
+        if (!checkVirtualThreadSupport()) {
+            System.out.println("当前Java版本不支持虚拟线程!");
+            System.out.println("请使用Java 21或更高版本,并添加--enable-preview参数");
+            return;
+        }
+
+        String[] fileAndOutput = new String[]{filePath};
+        ChapterSplitter splitter = new ChapterSplitter(fileAndOutput[0], true);
+        System.out.println("开始使用虚拟线程处理章节...");
+
+        List<Chapter> chapters = splitter.splitWithVirtualThreads();
+
+        splitter.printStatistics(chapters);
+        splitter.saveChaptersToFiles(chapters, fileAndOutput[1]);
+
+        System.out.println("处理完成!");
+    }
+    private static boolean checkVirtualThreadSupport() {
+        try {
+            Executors.class.getMethod("newVirtualThreadPerTaskExecutor");
+            return true;
+        } catch (NoSuchMethodException e) {
+            return false;
+        }
+    }
+
+
     /**
      * 根据id删除信息
      * @param id id

+ 80 - 0
imwork-windows/imwork-silos/src/main/java/top/imwork/window/silos/service/oss/FileUploadService.java

@@ -0,0 +1,80 @@
+package top.imwork.window.silos.service.oss;
+
+// FileUploadService.java
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+import top.imwork.commons.core.pojo.FileUploadResponse;
+import top.imwork.window.silos.utils.FileUtils;
+import top.imwork.window.silos.utils.SecurityUtils;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.UUID;
+
+@Slf4j
+@Service
+public class FileUploadService {
+
+    @Value("${upload.path}")
+    private String uploadPath;
+
+    public FileUploadResponse uploadFile(MultipartFile file) throws IOException {
+        // 生成安全文件名
+        String originalFilename = file.getOriginalFilename();
+        String fileExtension = FileUtils.getFileExtension(originalFilename);
+        String safeFilename = generateSafeFilename(originalFilename);
+
+        // 创建日期目录
+        String dateDir = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd"));
+        String fullPath = uploadPath + dateDir + "/";
+
+        // 确保目录存在
+        Path directory = Paths.get(fullPath);
+        Files.createDirectories(directory);
+
+        // 保存文件
+        Path filePath = directory.resolve(safeFilename);
+        Files.copy(file.getInputStream(), filePath);
+
+        // 生成文件URL
+        String fileUrl = "/uploads/" + dateDir + "/" + safeFilename;
+
+        // 二次安全检查(文件内容检查)
+        if (!SecurityUtils.isFileContentSafe(filePath.toFile())) {
+            // 删除危险文件
+            Files.deleteIfExists(filePath);
+            throw new SecurityException("文件内容包含危险代码");
+        }
+
+        // 返回文件信息
+        return FileUploadResponse.builder()
+                .fileName(originalFilename)
+                .filePath(filePath.toString())
+                .fileUrl(fileUrl)
+                .fileType(file.getContentType())
+                .fileSize(file.getSize())
+                .fileHash(FileUtils.calculateFileHash(filePath.toFile()))
+                .uploadTime(LocalDateTime.now())
+                .build();
+    }
+
+    private String generateSafeFilename(String originalFilename) {
+        String extension = FileUtils.getFileExtension(originalFilename);
+        String baseName = originalFilename.substring(0, originalFilename.lastIndexOf('.'));
+
+        // 清理文件名中的非法字符
+        baseName = baseName.replaceAll("[^a-zA-Z0-9\u4e00-\u9fa5_-]", "");
+
+        // 生成唯一ID
+        String uniqueId = UUID.randomUUID().toString().substring(0, 8);
+
+        // 组合文件名
+        return baseName + "_" + uniqueId + "_" + System.currentTimeMillis() + extension;
+    }
+}

+ 58 - 0
imwork-windows/imwork-silos/src/main/java/top/imwork/window/silos/utils/FileUtils.java

@@ -0,0 +1,58 @@
+package top.imwork.window.silos.utils;
+
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+/**
+ * Copyright (C), 2015-2025
+ * FileName: FileUtils
+ * Author<作者姓名>:   stars
+ * CreateTime<创建时间>:   2025/12/15 15:59
+ * UpdateTime<修改时间>:   2025/12/15 15:59
+ * Description〈功能简述〉: 文件工具类
+ * History<历史描述>:
+ * Since<版本号>: 1.0.0
+ */
+public class FileUtils {
+
+    /**
+     * 获取文件扩展名
+     */
+    public static String getFileExtension(String filename) {
+        if (filename == null || filename.lastIndexOf('.') == -1) {
+            return "";
+        }
+        return filename.substring(filename.lastIndexOf('.')).toLowerCase();
+    }
+
+    /**
+     * 计算文件哈希值(SHA-256)
+     */
+    public static String calculateFileHash(File file) throws IOException {
+        try (FileInputStream fis = new FileInputStream(file)) {
+            MessageDigest digest = MessageDigest.getInstance("SHA-256");
+            byte[] buffer = new byte[8192];
+            int bytesRead;
+
+            while ((bytesRead = fis.read(buffer)) != -1) {
+                digest.update(buffer, 0, bytesRead);
+            }
+
+            byte[] hashBytes = digest.digest();
+            StringBuilder hexString = new StringBuilder();
+
+            for (byte hashByte : hashBytes) {
+                String hex = Integer.toHexString(0xff & hashByte);
+                if (hex.length() == 1) hexString.append('0');
+                hexString.append(hex);
+            }
+
+            return hexString.toString();
+        } catch (NoSuchAlgorithmException e) {
+            throw new IOException("不支持SHA-256算法", e);
+        }
+    }
+}

+ 183 - 0
imwork-windows/imwork-silos/src/main/java/top/imwork/window/silos/utils/SecurityUtils.java

@@ -0,0 +1,183 @@
+package top.imwork.window.silos.utils;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.tika.Tika;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.List;
+/**
+ * Copyright (C), 2015-2025
+ * FileName: SecurityUtils
+ * Author<作者姓名>:   stars
+ * CreateTime<创建时间>:   2025/12/15 15:58
+ * UpdateTime<修改时间>:   2025/12/15 15:58
+ * Description〈功能简述〉: 安全文件检查
+ * History<历史描述>:
+ * Since<版本号>: 1.0.0
+ */
+@Slf4j
+public class SecurityUtils {
+
+    // 危险文件扩展名
+    private static final List<String> DANGEROUS_EXTENSIONS = Arrays.asList(
+            ".exe", ".bat", ".cmd", ".ps1", ".vbs", ".js", ".jar",
+            ".msi", ".scr", ".pif", ".com", ".sh", ".bash", ".csh",
+            ".php", ".php3", ".php4", ".php5", ".phtml", ".py", ".pl",
+            ".dll", ".so", ".sys", ".drv"
+    );
+
+    // 危险文件MIME类型
+    private static final List<String> DANGEROUS_MIME_TYPES = Arrays.asList(
+            "application/x-msdownload",
+            "application/x-ms-installer",
+            "application/x-dosexec",
+            "application/x-executable",
+            "application/x-shellscript",
+            "application/java-archive",
+            "application/x-bat",
+            "application/x-msdos-program",
+            "application/x-sh",
+            "application/x-php"
+    );
+
+    // 危险文件魔数(文件签名)
+    private static final List<byte[]> DANGEROUS_MAGIC_NUMBERS = Arrays.asList(
+            new byte[]{(byte)0x4D, (byte)0x5A},          // MZ - Windows EXE
+            new byte[]{(byte)0x5A, (byte)0x4D},          // ZM - Windows EXE
+            new byte[]{(byte)0x23, (byte)0x21},          // #! - Shell脚本
+            new byte[]{(byte)0xCA, (byte)0xFE, (byte)0xBA, (byte)0xBE}, // Java Class
+            new byte[]{(byte)0x50, (byte)0x4B, (byte)0x03, (byte)0x04}  // ZIP/JAR
+    );
+
+    private static final Tika tika = new Tika();
+
+    /**
+     * 检查文件安全性
+     */
+    public static String checkFileSecurity(MultipartFile file) {
+        try {
+            // 1. 检查扩展名
+            String originalFilename = file.getOriginalFilename();
+            if (originalFilename != null) {
+                String extension = FileUtils.getFileExtension(originalFilename).toLowerCase();
+                if (DANGEROUS_EXTENSIONS.contains(extension)) {
+                    return "禁止上传 " + extension + " 类型的文件";
+                }
+            }
+
+            // 2. 检查MIME类型
+            String mimeType = file.getContentType();
+            if (mimeType != null && DANGEROUS_MIME_TYPES.contains(mimeType.toLowerCase())) {
+                return "禁止上传 " + mimeType + " 类型的文件";
+            }
+
+            // 3. 检查文件大小(如果需要的话)
+            if (file.getSize() <= 0) {
+                return "文件大小无效";
+            }
+
+            return null; // 安全检查通过
+
+        } catch (Exception e) {
+            log.error("文件安全检查异常", e);
+            return "文件安全检查失败: " + e.getMessage();
+        }
+    }
+
+    /**
+     * 检查文件内容安全性
+     */
+    public static boolean isFileContentSafe(File file) {
+        try {
+            // 1. 使用Tika检测真实文件类型
+            String detectedType = tika.detect(file);
+            if (detectedType != null) {
+                for (String dangerousType : DANGEROUS_MIME_TYPES) {
+                    if (detectedType.toLowerCase().contains(dangerousType.toLowerCase())) {
+                        log.warn("检测到危险文件类型: {}", detectedType);
+                        return false;
+                    }
+                }
+            }
+
+            // 2. 检查文件魔数
+            byte[] fileHeader = new byte[4];
+            try (var inputStream = Files.newInputStream(file.toPath())) {
+                int bytesRead = inputStream.read(fileHeader);
+                if (bytesRead >= 2) {
+                    for (byte[] magicNumber : DANGEROUS_MAGIC_NUMBERS) {
+                        if (startsWith(fileHeader, magicNumber)) {
+                            log.warn("检测到危险文件签名");
+                            return false;
+                        }
+                    }
+                }
+            }
+
+            // 3. 检查文件内容(简单文本检查)
+            if (isTextFile(file)) {
+                String content = Files.readString(file.toPath()).toLowerCase();
+                if (containsDangerousCode(content)) {
+                    log.warn("检测到危险代码内容");
+                    return false;
+                }
+            }
+
+            return true;
+
+        } catch (IOException e) {
+            log.error("文件内容检查失败", e);
+            return false;
+        }
+    }
+
+    /**
+     * 检查文件是否以指定字节数组开头
+     */
+    private static boolean startsWith(byte[] source, byte[] target) {
+        if (source.length < target.length) return false;
+        for (int i = 0; i < target.length; i++) {
+            if (source[i] != target[i]) return false;
+        }
+        return true;
+    }
+
+    /**
+     * 检查是否是文本文件
+     */
+    private static boolean isTextFile(File file) throws IOException {
+        String mimeType = Files.probeContentType(file.toPath());
+        return mimeType != null && (
+                mimeType.startsWith("text/") ||
+                        mimeType.contains("json") ||
+                        mimeType.contains("xml")
+        );
+    }
+
+    /**
+     * 检查是否包含危险代码
+     */
+    private static boolean containsDangerousCode(String content) {
+        // 危险代码模式
+        String[] dangerousPatterns = {
+                "exec(", "system(", "Runtime.getRuntime()",
+                "ProcessBuilder", "cmd.exe", "/bin/bash",
+                "eval(", "setTimeout(", "setInterval(",
+                "document.cookie", "localStorage", "sessionStorage",
+                "<script>", "javascript:", "onload=", "onerror="
+        };
+
+        for (String pattern : dangerousPatterns) {
+            if (content.contains(pattern)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+}

+ 9 - 0
imwork-windows/imwork-silos/src/main/resources/application.yml

@@ -1,6 +1,11 @@
 server:
   port: 80
 spring:
+  servlet:
+    multipart:
+      max-file-size: -1  # 不限制大小
+      max-request-size: -1
+      enabled: true
   main:
     allow-bean-definition-overriding: true
   application:
@@ -68,6 +73,10 @@ pagehelper:
   reasonable: true
   supportMethodsArguments: true
   params: count=countSql
+upload:
+  path: ./uploads/
+  allowed-extensions: .jpg,.jpeg,.png,.gif,.pdf,.doc,.docx,.xls,.xlsx,.txt,.zip,.rar
+  max-size: -1  # 不限制大小
 #日志配置
 logging:
   charset:

+ 722 - 0
imwork-windows/imwork-silos/src/main/resources/static/assets/silos/js/filesUpload.js

@@ -0,0 +1,722 @@
+$(document).ready(function() {
+    // 配置
+    const CONFIG = {
+        maxConcurrentUploads: 3,      // 最大并发上传数
+        uploadUrl: '/api/upload',     // 上传接口地址
+        maxRetries: 2                 // 最大重试次数
+    };
+
+    // 状态管理
+    const state = {
+        files: [],                   // 所有文件
+        queue: [],                   // 等待上传的文件ID
+        uploading: [],               // 正在上传的文件ID
+        completed: [],               // 已完成的文件ID
+        selectedFiles: new Set(),    // 选中的文件ID
+        uploadSpeed: 0               // 上传速度
+    };
+
+    // 危险文件类型配置
+    const DANGEROUS_EXTENSIONS = [
+        '.exe', '.bat', '.cmd', '.ps1', '.vbs', '.vbe', '.js', '.jse',
+        '.wsf', '.wsh', '.msi', '.msc', '.scr', '.pif', '.com',
+        '.sh', '.bash', '.csh', '.ksh', '.zsh', '.run',
+        '.jar', '.app', '.apk', '.dmg', '.pkg',
+        '.php', '.php3', '.php4', '.php5', '.php7', '.phtml',
+        '.py', '.pyc', '.pyo', '.pl', '.cgi',
+        '.dll', '.so', '.sys', '.drv', '.bin'
+    ];
+
+    const DANGEROUS_MIME_TYPES = [
+        'application/x-msdownload',
+        'application/x-ms-installer',
+        'application/x-dosexec',
+        'application/x-executable',
+        'application/x-shellscript',
+        'application/java-archive',
+        'application/x-bat',
+        'application/x-msdos-program',
+        'application/x-sh',
+        'application/x-csh',
+        'application/x-perl',
+        'application/x-python',
+        'application/x-php'
+    ];
+
+    // 初始化
+    init();
+
+    function init() {
+        setupEventListeners();
+        updateStats();
+    }
+
+    function setupEventListeners() {
+        // 选择文件按钮
+        $('#selectFilesBtn').on('click', function(e) {
+            e.stopPropagation();
+            $('#fileInput').click();
+        });
+
+        // 选择文件夹按钮
+        $('#selectFolderBtn').on('click', function(e) {
+            e.stopPropagation();
+            $('#folderInput').click();
+        });
+
+        // 文件选择变化
+        $('#fileInput').on('change', function(e) {
+            handleFiles(e.target.files);
+            $(this).val('');
+        });
+
+        // 文件夹选择变化
+        $('#folderInput').on('change', function(e) {
+            handleFiles(e.target.files);
+            $(this).val('');
+        });
+
+        // 拖放事件
+        $('#uploadArea')
+            .on('dragover', function(e) {
+                e.preventDefault();
+                e.stopPropagation();
+                $(this).addClass('dragover');
+            })
+            .on('dragleave', function(e) {
+                e.preventDefault();
+                e.stopPropagation();
+                $(this).removeClass('dragover');
+            })
+            .on('drop', function(e) {
+                e.preventDefault();
+                e.stopPropagation();
+                $(this).removeClass('dragover');
+
+                const files = e.originalEvent.dataTransfer.files;
+                if (files.length > 0) {
+                    handleFiles(files);
+                }
+            });
+
+        // 批量操作
+        $('#batchRemoveBtn').on('click', removeSelectedFiles);
+        $('#batchUploadBtn').on('click', uploadSelectedFiles);
+
+        // 关闭提示
+        $(document).on('click', '.alert .btn-close', function() {
+            $(this).closest('.alert').hide();
+        });
+    }
+
+    // 处理文件
+    async function handleFiles(fileList) {
+        const files = Array.from(fileList);
+        if (files.length === 0) return;
+
+        let addedCount = 0;
+
+        for (const file of files) {
+            try {
+                // 安全检查
+                const securityCheck = await checkFileSecurity(file);
+
+                if (!securityCheck.isSafe) {
+                    showError(`文件 "${file.name}" 安全检查失败: ${securityCheck.message}`);
+                    continue;
+                }
+
+                // 添加到文件列表
+                if (addFile(file)) {
+                    addedCount++;
+                }
+            } catch (error) {
+                console.error('处理文件失败:', error);
+                showError(`处理文件 "${file.name}" 时出错: ${error.message}`);
+            }
+        }
+
+        if (addedCount > 0) {
+            showSuccess(`成功添加 ${addedCount} 个文件到上传列表`);
+            updateStats();
+            processQueue();
+        }
+    }
+
+    // 安全检查
+    async function checkFileSecurity(file) {
+        // 1. 检查扩展名
+        const extension = '.' + file.name.toLowerCase().split('.').pop();
+        if (DANGEROUS_EXTENSIONS.includes(extension)) {
+            return {
+                isSafe: false,
+                message: `禁止上传 ${extension} 类型的文件`
+            };
+        }
+
+        // 2. 检查MIME类型
+        if (file.type) {
+            for (const mimeType of DANGEROUS_MIME_TYPES) {
+                if (file.type.includes(mimeType)) {
+                    return {
+                        isSafe: false,
+                        message: `禁止上传 ${file.type} 类型的文件`
+                    };
+                }
+            }
+        }
+
+        // 3. 检查文件签名
+        const signatureCheck = await checkFileSignature(file);
+        if (!signatureCheck.isSafe) {
+            return signatureCheck;
+        }
+
+        return {
+            isSafe: true,
+            message: '安全检查通过'
+        };
+    }
+
+    // 检查文件签名
+    function checkFileSignature(file) {
+        return new Promise((resolve) => {
+            // 如果文件很小,直接跳过签名检查
+            if (file.size < 4) {
+                resolve({ isSafe: true, message: '文件过小,跳过签名检查' });
+                return;
+            }
+
+            const reader = new FileReader();
+
+            reader.onload = function(e) {
+                try {
+                    const arr = new Uint8Array(e.target.result);
+                    const hexArray = Array.from(arr).map(b =>
+                        b.toString(16).padStart(2, '0').toUpperCase()
+                    );
+                    const hexString = hexArray.join('');
+
+                    // 危险文件签名
+                    const dangerousSignatures = [
+                        '4D5A',      // Windows EXE
+                        '5A4D',      // Windows EXE
+                        '2321',      // Shell脚本
+                        'CAFEBABE',  // Java类文件
+                        '504B0304',  // ZIP/JAR
+                        '7F454C46',  // ELF可执行文件
+                        'CFFAEDFE',  // Mach-O
+                        'FEEDFACE'   // Mach-O
+                    ];
+
+                    for (const signature of dangerousSignatures) {
+                        if (hexString.startsWith(signature)) {
+                            resolve({
+                                isSafe: false,
+                                message: `检测到危险文件签名 (${signature})`
+                            });
+                            return;
+                        }
+                    }
+
+                    resolve({
+                        isSafe: true,
+                        message: '文件签名检查通过'
+                    });
+                } catch (error) {
+                    resolve({
+                        isSafe: true,
+                        message: '签名检查出错,跳过检查'
+                    });
+                }
+            };
+
+            reader.onerror = function() {
+                resolve({
+                    isSafe: true,
+                    message: '读取文件失败,跳过签名检查'
+                });
+            };
+
+            // 读取文件前256字节
+            const blob = file.slice(0, 256);
+            reader.readAsArrayBuffer(blob);
+        });
+    }
+
+    // 添加文件到列表
+    function addFile(file) {
+        // 检查是否已存在
+        const existingFile = state.files.find(f =>
+            f.name === file.name &&
+            f.size === file.size &&
+            f.lastModified === file.lastModified
+        );
+
+        if (existingFile) {
+            showWarning(`文件 "${file.name}" 已存在`);
+            return false;
+        }
+
+        // 创建文件对象
+        const fileObj = {
+            id: 'file_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9),
+            file: file,
+            name: file.name,
+            size: file.size,
+            type: file.type || '未知类型',
+            status: 'pending',
+            progress: 0,
+            retries: 0,
+            error: null
+        };
+
+        // 添加到状态
+        state.files.push(fileObj);
+
+        // 创建DOM元素
+        createFileElement(fileObj);
+
+        return true;
+    }
+
+    // 创建文件元素
+    function createFileElement(fileObj) {
+        const fileElement = $(`
+                <div class="file-item ${fileObj.status}" id="${fileObj.id}">
+                    <div class="file-icon">
+                        ${getFileIcon(fileObj.type, fileObj.name)}
+                    </div>
+                    <div class="file-info">
+                        <div class="file-name">${escapeHtml(fileObj.name)}</div>
+                        <div class="file-details">
+                            <span class="file-size">${formatFileSize(fileObj.size)}</span>
+                            <span class="file-type">${fileObj.type}</span>
+                        </div>
+                        <div class="file-progress">
+                            <div class="progress-bar" id="${fileObj.id}_progress"></div>
+                        </div>
+                    </div>
+                    <div class="file-status" id="${fileObj.id}_status">
+                        <span class="status-${fileObj.status}">
+                            ${getStatusText(fileObj.status)}
+                        </span>
+                    </div>
+                    <div class="file-actions">
+                        <button class="btn btn-outline-primary btn-sm select-btn" title="选择文件">
+                            <i class="far fa-square"></i>
+                        </button>
+                        <button class="btn btn-outline-danger btn-sm remove-btn" title="移除文件">
+                            <i class="fas fa-times"></i>
+                        </button>
+                    </div>
+                </div>
+            `);
+
+        // 添加到列表
+        $('#filesList').append(fileElement);
+
+        // 绑定事件
+        bindFileEvents(fileObj.id);
+
+        updateListCount();
+    }
+
+    // 绑定文件事件
+    function bindFileEvents(fileId) {
+        const fileElement = $(`#${fileId}`);
+
+        // 选择按钮
+        fileElement.find('.select-btn').on('click', function(e) {
+            e.stopPropagation();
+            toggleFileSelection(fileId, $(this));
+        });
+
+        // 移除按钮
+        fileElement.find('.remove-btn').on('click', function(e) {
+            e.stopPropagation();
+            removeFile(fileId);
+        });
+
+        // 点击预览图片
+        if (fileElement.has('.file-icon .fa-image, .file-icon .fa-file-image').length) {
+            fileElement.on('click', '.file-icon, .file-name', function(e) {
+                if (!$(e.target).closest('button').length) {
+                    previewImage(fileId);
+                }
+            });
+        }
+    }
+
+    // 处理上传队列
+    async function processQueue() {
+        updateQueueInfo();
+
+        // 如果已达最大并发数或没有待上传文件,返回
+        if (state.uploading.length >= CONFIG.maxConcurrentUploads) {
+            return;
+        }
+
+        // 找出待上传的文件
+        const pendingFiles = state.files.filter(f =>
+            f.status === 'pending' &&
+            !state.queue.includes(f.id) &&
+            !state.uploading.includes(f.id)
+        );
+
+        if (pendingFiles.length === 0) {
+            return;
+        }
+
+        // 添加到队列
+        pendingFiles.forEach(file => {
+            state.queue.push(file.id);
+        });
+
+        // 处理队列
+        while (state.uploading.length < CONFIG.maxConcurrentUploads && state.queue.length > 0) {
+            const fileId = state.queue.shift();
+            const fileObj = state.files.find(f => f.id === fileId);
+
+            if (fileObj) {
+                state.uploading.push(fileId);
+                await uploadFile(fileObj);
+            }
+        }
+    }
+
+    // 上传文件
+    function uploadFile(fileObj) {
+        return new Promise((resolve, reject) => {
+            updateFileStatus(fileObj.id, 'uploading', '上传中...');
+
+            // 模拟上传过程(实际使用时替换为真实的上传逻辑)
+            // let progress = 0;
+            // const interval = setInterval(() => {
+            //     progress += Math.random() * 10;
+            //     if (progress >= 100) {
+            //         progress = 100;
+            //         clearInterval(interval);
+            //
+            //         // 模拟上传成功
+            //         setTimeout(() => {
+            //             state.uploading = state.uploading.filter(id => id !== fileObj.id);
+            //             state.completed.push(fileObj.id);
+            //
+            //             updateFileStatus(fileObj.id, 'success', '上传成功');
+            //             updateProgress(fileObj.id, 100);
+            //
+            //             // 更新隐藏字段
+            //             updateHiddenFields();
+            //             updateStats();
+            //
+            //             resolve();
+            //         }, 300);
+            //     } else {
+            //         updateProgress(fileObj.id, progress);
+            //     }
+            // }, 200);
+
+            // 实际的上传代码(注释掉,需要后端接口)
+            const formData = new FormData();
+            formData.append('file', fileObj.file);
+            formData.append('fileName', fileObj.name);
+            formData.append('fileType', fileObj.type);
+            formData.append('fileSize', fileObj.size);
+
+            $.ajax({
+                url: CONFIG.uploadUrl,
+                type: 'POST',
+                data: formData,
+                processData: false,
+                contentType: false,
+                xhr: function() {
+                    const xhr = new window.XMLHttpRequest();
+                    xhr.upload.addEventListener('progress', function(e) {
+                        if (e.lengthComputable) {
+                            const percent = Math.round((e.loaded / e.total) * 100);
+                            updateProgress(fileObj.id, percent);
+                        }
+                    }, false);
+                    return xhr;
+                },
+                success: function(response) {
+                    if (response.success) {
+                        state.uploading = state.uploading.filter(id => id !== fileObj.id);
+                        state.completed.push(fileObj.id);
+
+                        fileObj.serverPath = response.data.filePath;
+                        fileObj.fileHash = response.data.fileHash;
+
+                        updateFileStatus(fileObj.id, 'success', '上传成功');
+                        updateHiddenFields();
+                        updateStats();
+                        resolve();
+                    } else {
+                        reject(new Error(response.message));
+                    }
+                },
+                error: function(xhr, status, error) {
+                    reject(new Error('上传失败: ' + error));
+                }
+            });
+        });
+    }
+
+    // 更新文件状态
+    function updateFileStatus(fileId, status, message) {
+        const fileElement = $(`#${fileId}`);
+        const statusElement = $(`#${fileId}_status`);
+
+        // 更新状态类
+        fileElement
+            .removeClass('pending checking uploading success error')
+            .addClass(status);
+
+        // 更新状态文本
+        statusElement.html(`<span class="status-${status}">${message}</span>`);
+
+        // 更新状态对象
+        const fileObj = state.files.find(f => f.id === fileId);
+        if (fileObj) {
+            fileObj.status = status;
+        }
+    }
+
+    // 更新上传进度
+    function updateProgress(fileId, progress) {
+        const progressBar = $(`#${fileId}_progress`);
+        progressBar.css('width', progress + '%');
+
+        const fileObj = state.files.find(f => f.id === fileId);
+        if (fileObj) {
+            fileObj.progress = progress;
+
+            if (progress < 100) {
+                $(`#${fileId}_status`).html(`<span class="status-uploading">上传中 ${Math.round(progress)}%</span>`);
+            }
+        }
+    }
+
+    // 移除文件
+    function removeFile(fileId) {
+        // 从状态中移除
+        state.files = state.files.filter(f => f.id !== fileId);
+        state.queue = state.queue.filter(id => id !== fileId);
+        state.uploading = state.uploading.filter(id => id !== fileId);
+        state.selectedFiles.delete(fileId);
+
+        // 移除DOM元素
+        $(`#${fileId}`).remove();
+
+        // 更新统计
+        updateStats();
+        updateListCount();
+        updateBatchActions();
+        updateQueueInfo();
+    }
+
+    // 切换文件选择
+    function toggleFileSelection(fileId, button) {
+        if (state.selectedFiles.has(fileId)) {
+            state.selectedFiles.delete(fileId);
+            button.html('<i class="far fa-square"></i>');
+            button.removeClass('btn-primary').addClass('btn-outline-primary');
+        } else {
+            state.selectedFiles.add(fileId);
+            button.html('<i class="fas fa-check-square"></i>');
+            button.removeClass('btn-outline-primary').addClass('btn-primary');
+        }
+
+        updateBatchActions();
+    }
+
+    // 移除选中的文件
+    function removeSelectedFiles() {
+        state.selectedFiles.forEach(fileId => {
+            removeFile(fileId);
+        });
+        state.selectedFiles.clear();
+        updateBatchActions();
+    }
+
+    // 上传选中的文件
+    function uploadSelectedFiles() {
+        state.selectedFiles.forEach(fileId => {
+            const fileObj = state.files.find(f => f.id === fileId);
+            if (fileObj && fileObj.status === 'pending') {
+                updateFileStatus(fileId, 'pending', '等待上传');
+            }
+        });
+
+        state.selectedFiles.clear();
+        updateBatchActions();
+        processQueue();
+    }
+
+    // 预览图片
+    function previewImage(fileId) {
+        const fileObj = state.files.find(f => f.id === fileId);
+        if (!fileObj || !fileObj.file.type.startsWith('image/')) return;
+
+        const reader = new FileReader();
+        reader.onload = function(e) {
+            $('#previewImage').attr('src', e.target.result);
+            $('#previewContainer').show();
+        };
+        reader.readAsDataURL(fileObj.file);
+    }
+
+    // 更新隐藏字段
+    function updateHiddenFields() {
+        const completedFiles = state.files.filter(f => f.status === 'success');
+
+        const paths = completedFiles.map(f => f.serverPath || '').join('|');
+        const names = completedFiles.map(f => f.name).join('|');
+        const types = completedFiles.map(f => f.type).join('|');
+        const sizes = completedFiles.map(f => f.size).join('|');
+        const hashes = completedFiles.map(f => f.fileHash || '').join('|');
+
+        $('#filePaths').val(paths);
+        $('#fileNames').val(names);
+        $('#fileTypes').val(types);
+        $('#fileSizes').val(sizes);
+        $('#fileHashes').val(hashes);
+    }
+
+    // 更新统计信息
+    function updateStats() {
+        const total = state.files.length;
+        const success = state.files.filter(f => f.status === 'success').length;
+        const failed = state.files.filter(f => f.status === 'error').length;
+
+        $('#totalFiles').text(total);
+        $('#successFiles').text(success);
+        $('#failedFiles').text(failed);
+    }
+
+    // 更新列表计数
+    function updateListCount() {
+        const count = state.files.length;
+        $('#listCount').text(count);
+    }
+
+    // 更新批量操作栏
+    function updateBatchActions() {
+        const count = state.selectedFiles.size;
+        $('#selectedCount').text(count);
+
+        if (count > 0) {
+            $('#batchActions').show();
+        } else {
+            $('#batchActions').hide();
+        }
+    }
+
+    // 更新队列信息
+    function updateQueueInfo() {
+        const queueCount = state.queue.length;
+        const uploadingCount = state.uploading.length;
+
+        if (queueCount > 0 || uploadingCount > 0) {
+            $('#queueInfo').show();
+            $('#queueStatus').html(`队列中: <strong>${queueCount}</strong> | 上传中: <strong>${uploadingCount}</strong>`);
+        } else {
+            $('#queueInfo').hide();
+        }
+    }
+
+    // 显示成功提示
+    function showSuccess(message) {
+        $('#successAlert').html(`
+                <i class="fas fa-check-circle me-2"></i>${message}
+                <button type="button" class="btn-close float-end" aria-label="Close"></button>
+            `).show();
+
+        setTimeout(() => {
+            $('#successAlert').fadeOut();
+        }, 3000);
+    }
+
+    // 显示错误提示
+    function showError(message) {
+        $('#errorAlert').html(`
+                <i class="fas fa-exclamation-circle me-2"></i>${message}
+                <button type="button" class="btn-close float-end" aria-label="Close"></button>
+            `).show();
+    }
+
+    // 显示警告提示
+    function showWarning(message) {
+        $('#warningAlert').html(`
+                <i class="fas fa-exclamation-triangle me-2"></i>${message}
+                <button type="button" class="btn-close float-end" aria-label="Close"></button>
+            `).show();
+
+        setTimeout(() => {
+            $('#warningAlert').fadeOut();
+        }, 5000);
+    }
+
+    // 工具函数
+    function getFileIcon(mimeType, fileName) {
+        const ext = fileName.split('.').pop().toLowerCase();
+
+        if (mimeType) {
+            if (mimeType.startsWith('image/')) return '<i class="fas fa-file-image"></i>';
+            if (mimeType.startsWith('video/')) return '<i class="fas fa-file-video"></i>';
+            if (mimeType.startsWith('audio/')) return '<i class="fas fa-file-audio"></i>';
+            if (mimeType.includes('pdf')) return '<i class="fas fa-file-pdf"></i>';
+            if (mimeType.includes('word') || mimeType.includes('document')) return '<i class="fas fa-file-word"></i>';
+            if (mimeType.includes('excel') || mimeType.includes('spreadsheet')) return '<i class="fas fa-file-excel"></i>';
+            if (mimeType.includes('zip') || mimeType.includes('compressed')) return '<i class="fas fa-file-archive"></i>';
+            if (mimeType.includes('text')) return '<i class="fas fa-file-alt"></i>';
+        }
+
+        const iconMap = {
+            'jpg': 'fa-file-image', 'jpeg': 'fa-file-image', 'png': 'fa-file-image',
+            'gif': 'fa-file-image', 'bmp': 'fa-file-image', 'webp': 'fa-file-image',
+            'svg': 'fa-file-image',
+            'mp4': 'fa-file-video', 'avi': 'fa-file-video', 'mov': 'fa-file-video',
+            'wmv': 'fa-file-video', 'flv': 'fa-file-video', 'mkv': 'fa-file-video',
+            'mp3': 'fa-file-audio', 'wav': 'fa-file-audio', 'flac': 'fa-file-audio',
+            'pdf': 'fa-file-pdf',
+            'doc': 'fa-file-word', 'docx': 'fa-file-word',
+            'xls': 'fa-file-excel', 'xlsx': 'fa-file-excel',
+            'ppt': 'fa-file-powerpoint', 'pptx': 'fa-file-powerpoint',
+            'zip': 'fa-file-archive', 'rar': 'fa-file-archive', '7z': 'fa-file-archive',
+            'tar': 'fa-file-archive', 'gz': 'fa-file-archive',
+            'txt': 'fa-file-alt', 'md': 'fa-file-alt',
+            'js': 'fa-file-code', 'html': 'fa-file-code', 'css': 'fa-file-code',
+            'json': 'fa-file-code', 'xml': 'fa-file-code',
+            'exe': 'fa-file-exclamation', 'dll': 'fa-file-exclamation'
+        };
+
+        return `<i class="fas ${iconMap[ext] || 'fa-file'}"></i>`;
+    }
+
+    function getStatusText(status) {
+        const statusMap = {
+            'pending': '等待上传',
+            'checking': '检查中',
+            'uploading': '上传中',
+            'success': '上传成功',
+            'error': '上传失败'
+        };
+        return statusMap[status] || status;
+    }
+
+    function formatFileSize(bytes) {
+        if (bytes === 0) return '0 B';
+        const k = 1024;
+        const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
+        const i = Math.floor(Math.log(bytes) / Math.log(k));
+        return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
+    }
+
+    function escapeHtml(text) {
+        const div = document.createElement('div');
+        div.textContent = text;
+        return div.innerHTML;
+    }
+});

+ 439 - 0
imwork-windows/imwork-silos/src/main/resources/static/business/cms/book/info/edit.css

@@ -349,4 +349,443 @@ input:focus, select:focus, textarea:focus {
         flex-direction: column;
         align-items: flex-start;
     }
+}
+
+.upload-container {
+    background: white;
+    border-radius: 12px;
+    box-shadow: 0 5px 20px rgba(0, 0, 0, 0.1);
+    overflow: hidden;
+}
+
+.upload-header {
+    background: linear-gradient(135deg, var(--primary), #6610f2);
+    color: white;
+    padding: 30px;
+    text-align: center;
+}
+
+.upload-header h1 {
+    font-size: 2rem;
+    font-weight: 600;
+    margin-bottom: 10px;
+}
+
+.upload-header p {
+    opacity: 0.9;
+    margin-bottom: 20px;
+}
+
+.upload-stats {
+    display: flex;
+    justify-content: center;
+    gap: 30px;
+    margin-top: 20px;
+}
+
+.stat-item {
+    text-align: center;
+    background: rgba(255, 255, 255, 0.2);
+    padding: 15px 25px;
+    border-radius: 8px;
+    backdrop-filter: blur(10px);
+}
+
+.stat-value {
+    font-size: 1.8rem;
+    font-weight: bold;
+    margin-bottom: 5px;
+}
+
+.stat-label {
+    font-size: 0.9rem;
+    opacity: 0.9;
+}
+
+.upload-content {
+    padding: 30px;
+}
+
+/* 上传区域 */
+.upload-area {
+    border: 3px dashed #dee2e6;
+    border-radius: 10px;
+    padding: 50px 20px;
+    text-align: center;
+    background: var(--light);
+    cursor: pointer;
+    transition: all 0.3s ease;
+    margin-bottom: 25px;
+}
+
+.upload-area:hover {
+    border-color: var(--primary);
+    background: rgba(13, 110, 253, 0.05);
+}
+
+.upload-area.dragover {
+    border-color: var(--primary);
+    background: rgba(13, 110, 253, 0.1);
+}
+
+.upload-icon {
+    font-size: 4rem;
+    color: var(--primary);
+    margin-bottom: 20px;
+}
+
+.upload-text h3 {
+    color: var(--dark);
+    margin-bottom: 10px;
+    font-weight: 600;
+}
+
+.upload-text p {
+    color: #6c757d;
+    margin-bottom: 20px;
+}
+
+.security-warning {
+    background: rgba(255, 193, 7, 0.1);
+    border-left: 4px solid var(--warning);
+    padding: 15px;
+    margin: 20px 0;
+    text-align: left;
+    border-radius: 0 8px 8px 0;
+}
+
+.security-list {
+    list-style: none;
+    padding: 0;
+    margin: 0;
+}
+
+.security-list li {
+    padding: 5px 0;
+    color: #666;
+}
+
+.security-list li i {
+    color: var(--warning);
+    margin-right: 8px;
+}
+
+/* 文件列表 */
+.files-section {
+    margin-top: 30px;
+}
+
+.section-title {
+    font-size: 1.3rem;
+    color: var(--dark);
+    margin-bottom: 20px;
+    font-weight: 600;
+    display: flex;
+    align-items: center;
+    gap: 10px;
+}
+
+.files-list {
+    max-height: 400px;
+    overflow-y: auto;
+    padding-right: 10px;
+}
+
+.file-item {
+    background: white;
+    border: 1px solid #dee2e6;
+    border-radius: 8px;
+    padding: 15px;
+    margin-bottom: 12px;
+    display: flex;
+    align-items: center;
+    transition: all 0.3s ease;
+}
+
+.file-item:hover {
+    box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1);
+    transform: translateY(-2px);
+}
+
+.file-item.success {
+    border-left: 4px solid var(--success);
+}
+
+.file-item.error {
+    border-left: 4px solid var(--danger);
+}
+
+.file-item.uploading {
+    border-left: 4px solid var(--primary);
+}
+
+.file-item.checking {
+    border-left: 4px solid var(--warning);
+}
+
+.file-item.pending {
+    border-left: 4px solid #6c757d;
+}
+
+.file-icon {
+    font-size: 2rem;
+    color: var(--primary);
+    margin-right: 15px;
+    flex-shrink: 0;
+}
+
+.file-info {
+    flex: 1;
+    min-width: 0;
+}
+
+.file-name {
+    font-weight: 600;
+    color: var(--dark);
+    margin-bottom: 5px;
+    word-break: break-all;
+}
+
+.file-details {
+    display: flex;
+    gap: 15px;
+    color: #6c757d;
+    font-size: 0.85rem;
+    margin-bottom: 8px;
+}
+
+.file-progress {
+    width: 100%;
+    height: 6px;
+    background: #e9ecef;
+    border-radius: 3px;
+    overflow: hidden;
+    margin-top: 10px;
+}
+
+.progress-bar {
+    height: 100%;
+    background: var(--primary);
+    border-radius: 3px;
+    width: 0%;
+    transition: width 0.3s ease;
+}
+
+.file-status {
+    margin-left: 15px;
+    font-weight: 500;
+    min-width: 120px;
+    text-align: right;
+    flex-shrink: 0;
+}
+
+.status-checking {
+    color: var(--warning);
+}
+
+.status-uploading {
+    color: var(--primary);
+}
+
+.status-success {
+    color: var(--success);
+}
+
+.status-error {
+    color: var(--danger);
+}
+
+.status-pending {
+    color: #6c757d;
+}
+
+.file-actions {
+    margin-left: 15px;
+    display: flex;
+    gap: 8px;
+    flex-shrink: 0;
+}
+
+/* 按钮样式 */
+.btn {
+    padding: 10px 20px;
+    border-radius: 6px;
+    font-weight: 500;
+    transition: all 0.3s ease;
+    display: inline-flex;
+    align-items: center;
+    gap: 8px;
+}
+
+.btn-primary {
+    background: var(--primary);
+    border: none;
+}
+
+.btn-primary:hover {
+    background: #0b5ed7;
+    transform: translateY(-2px);
+    box-shadow: 0 4px 8px rgba(13, 110, 253, 0.2);
+}
+
+.btn-success {
+    background: var(--success);
+    border: none;
+}
+
+.btn-danger {
+    background: var(--danger);
+    border: none;
+}
+
+.btn-sm {
+    padding: 6px 12px;
+    font-size: 0.875rem;
+}
+
+/* 提示信息 */
+.alert {
+    border: none;
+    border-radius: 8px;
+    margin-bottom: 20px;
+    padding: 15px;
+    display: none;
+}
+
+.alert-success {
+    background: rgba(25, 135, 84, 0.1);
+    color: var(--success);
+    border-left: 4px solid var(--success);
+}
+
+.alert-danger {
+    background: rgba(220, 53, 69, 0.1);
+    color: var(--danger);
+    border-left: 4px solid var(--danger);
+}
+
+.alert-warning {
+    background: rgba(255, 193, 7, 0.1);
+    color: #856404;
+    border-left: 4px solid var(--warning);
+}
+
+/* 批量操作 */
+.batch-actions {
+    background: var(--light);
+    padding: 15px;
+    border-radius: 8px;
+    margin-bottom: 20px;
+    display: none;
+    justify-content: space-between;
+    align-items: center;
+}
+
+.selected-count {
+    font-weight: 500;
+    color: var(--dark);
+}
+
+/* 预览区域 */
+.preview-container {
+    margin-top: 25px;
+    padding-top: 25px;
+    border-top: 1px solid #dee2e6;
+    display: none;
+}
+
+.preview-image {
+    max-width: 100%;
+    max-height: 300px;
+    border-radius: 8px;
+    box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1);
+    display: block;
+    margin: 0 auto;
+}
+
+/* 队列信息 */
+.queue-info {
+    background: rgba(13, 110, 253, 0.1);
+    padding: 12px 15px;
+    border-radius: 8px;
+    margin-bottom: 20px;
+    display: none;
+    justify-content: space-between;
+    align-items: center;
+    font-size: 0.9rem;
+}
+
+/* 滚动条样式 */
+.files-list::-webkit-scrollbar {
+    width: 8px;
+}
+
+.files-list::-webkit-scrollbar-track {
+    background: #f1f1f1;
+    border-radius: 4px;
+}
+
+.files-list::-webkit-scrollbar-thumb {
+    background: #c1c1c1;
+    border-radius: 4px;
+}
+
+.files-list::-webkit-scrollbar-thumb:hover {
+    background: #a8a8a8;
+}
+
+/* 加载动画 */
+.spinner {
+    display: inline-block;
+    width: 1rem;
+    height: 1rem;
+    border: 2px solid currentColor;
+    border-right-color: transparent;
+    border-radius: 50%;
+    animation: spinner-border 0.75s linear infinite;
+    margin-right: 8px;
+}
+
+@keyframes spinner-border {
+    to { transform: rotate(360deg); }
+}
+
+/* 响应式 */
+@media (max-width: 768px) {
+    .upload-header {
+        padding: 20px;
+    }
+
+    .upload-content {
+        padding: 20px;
+    }
+
+    .upload-area {
+        padding: 30px 15px;
+    }
+
+    .upload-stats {
+        gap: 15px;
+    }
+
+    .stat-item {
+        padding: 10px 15px;
+    }
+
+    .file-item {
+        flex-direction: column;
+        align-items: flex-start;
+        gap: 10px;
+    }
+
+    .file-status {
+        text-align: left;
+        margin-left: 0;
+        min-width: auto;
+    }
+
+    .file-actions {
+        margin-left: 0;
+        align-self: flex-end;
+    }
 }

+ 1 - 0
imwork-windows/imwork-silos/src/main/resources/static/business/cms/book/info/edit.js

@@ -61,6 +61,7 @@ layui.use(['form', 'layer'], function () {
                 binding: $("#binding").val(),
                 series: $("#series").val(),
                 source: $("#source").val(),
+                filePaths: $("#filePaths").val()
             }),
             success : function(res) {
                 console.log(res);

+ 117 - 1
imwork-windows/imwork-silos/src/main/resources/templates/cms/book/info/edit.html

@@ -11,6 +11,7 @@
     <link rel="stylesheet" href="../../../../static/assets/lib/fonts/fontawesome4/css/font-awesome.css" th:href="@{/assets/lib/fonts/fontawesome4/css/font-awesome.css}"/>
     <link rel="stylesheet" href="../../../../static/assets/lib/fonts/fontawesome6/css/all.min.css" th:href="@{/assets/lib/fonts/fontawesome6/css/all.min.css}"/>
     <link rel="stylesheet" href="../../../../static/business/cms/book/info/edit.css" th:href="@{/business/cms/book/info/edit.css}"/>
+
 </head>
 <body>
 <div class="container">
@@ -162,7 +163,7 @@
                             <option value="gift">赠送</option>
                             <option value="exchange">交换</option>
                             <option value="donation">捐赠</option>
-                            <option value="other">其</option>
+                            <option value="other">其</option>
                         </select>
                     </div>
                     <div class="form-group">
@@ -178,6 +179,120 @@
                         </div>
                     </div>
                 </div>
+                <div class="from-row">
+                    <div class="upload-container">
+                        <!-- 头部 -->
+                        <div class="upload-header">
+                            <div class="upload-stats">
+                                <div class="stat-item">
+                                    <div class="stat-value" id="totalFiles">0</div>
+                                    <div class="stat-label">总文件</div>
+                                </div>
+                                <div class="stat-item">
+                                    <div class="stat-value" id="successFiles">0</div>
+                                    <div class="stat-label">成功</div>
+                                </div>
+                                <div class="stat-item">
+                                    <div class="stat-value" id="failedFiles">0</div>
+                                    <div class="stat-label">失败</div>
+                                </div>
+                            </div>
+                        </div>
+
+                        <!-- 内容区域 -->
+                        <div class="upload-content">
+                            <!-- 提示信息 -->
+                            <div class="alert alert-danger" id="errorAlert"></div>
+                            <div class="alert alert-success" id="successAlert"></div>
+                            <div class="alert alert-warning" id="warningAlert"></div>
+
+                            <!-- 批量操作 -->
+                            <div class="batch-actions" id="batchActions">
+                                <div class="selected-count">
+                                    <i class="fas fa-check-circle me-2"></i>
+                                    已选择 <span id="selectedCount">0</span> 个文件
+                                </div>
+                                <div class="d-flex gap-2">
+                                    <button class="btn btn-danger btn-sm" id="batchRemoveBtn">
+                                        <i class="fas fa-trash-alt me-1"></i>移除选中
+                                    </button>
+                                    <button class="btn btn-success btn-sm" id="batchUploadBtn">
+                                        <i class="fas fa-cloud-upload-alt me-1"></i>上传选中
+                                    </button>
+                                </div>
+                            </div>
+
+                            <!-- 上传区域 -->
+                            <div class="upload-area" id="uploadArea">
+                                <div class="upload-icon">
+                                    <i class="fas fa-cloud-upload-alt"></i>
+                                </div>
+                                <div class="upload-text">
+                                    <h3>点击选择文件或拖放到此处</h3>
+                                    <p class="mb-0">支持图片、文档、视频等文件类型(禁止上传可执行文件)</p>
+
+                                    <div class="security-warning mt-3">
+                                        <strong><i class="fas fa-exclamation-triangle me-1"></i>安全限制:</strong>
+                                        <ul class="security-list mt-2">
+                                            <li><i class="fas fa-ban"></i>禁止上传 .exe, .bat, .sh, .jar 等可执行文件</li>
+                                            <li><i class="fas fa-shield-alt"></i>前端安全检查确保上传安全</li>
+                                            <li><i class="fas fa-infinity"></i>不限制文件大小</li>
+                                        </ul>
+                                    </div>
+                                </div>
+                                <div class="d-flex gap-3 justify-content-center mt-4">
+                                    <button class="btn btn-primary" id="selectFilesBtn">
+                                        <i class="fas fa-folder-open me-2"></i>选择文件
+                                    </button>
+                                    <button class="btn btn-outline-primary" id="selectFolderBtn">
+                                        <i class="fas fa-folder me-2"></i>选择文件夹
+                                    </button>
+                                </div>
+                                <input type="file" id="fileInput" multiple style="display: none;">
+                                <input type="file" id="folderInput" webkitdirectory directory multiple style="display: none;">
+                            </div>
+
+                            <!-- 上传队列信息 -->
+                            <div class="queue-info" id="queueInfo">
+                                <div>
+                                    <i class="fas fa-tasks me-2"></i>
+                                    <span id="queueStatus">队列中: <strong>0</strong> | 上传中: <strong>0</strong></span>
+                                </div>
+                                <div class="text-muted">
+                                    <small id="uploadSpeed">速度: 0 KB/s</small>
+                                </div>
+                            </div>
+
+                            <!-- 文件列表 -->
+                            <div class="files-section">
+                                <h3 class="section-title">
+                                    <i class="fas fa-list me-2"></i>文件列表
+                                    <span class="badge bg-primary rounded-pill" id="listCount">0</span>
+                                </h3>
+                                <div class="files-list" id="filesList">
+                                    <!-- 文件项将动态添加到这里 -->
+                                </div>
+                            </div>
+
+                            <!-- 文件预览 -->
+                            <div class="preview-container" id="previewContainer">
+                                <h3 class="section-title">
+                                    <i class="fas fa-image me-2"></i>文件预览
+                                </h3>
+                                <img class="preview-image" id="previewImage" src="" alt="预览图片">
+                            </div>
+                        </div>
+
+                        <!-- 隐藏字段(用于表单提交) -->
+                        <div class="d-none">
+                            <input type="hidden" id="filePaths" name="file_paths" value="">
+                            <input type="hidden" id="fileNames" name="file_names" value="">
+                            <input type="hidden" id="fileTypes" name="file_types" value="">
+                            <input type="hidden" id="fileSizes" name="file_sizes" value="">
+                            <input type="hidden" id="fileHashes" name="file_hashes" value="">
+                        </div>
+                    </div>
+                </div>
             </div>
 
             <!-- 内容描述部分 -->
@@ -232,6 +347,7 @@
 <script src="../../../../static/assets/lib/jquery/jquery.js" th:src="@{/assets/lib/jquery/jquery.js}"></script>
 <script src="../../../../static/assets/lib/layui/layui.js" th:src="@{/assets/lib/layui/layui.js}"></script>
 <script src="../../../../static/assets/silos/js/SelectRenderer.js" th:src="@{/assets/silos/js/SelectRenderer.js}"></script>
+<script src="../../../../static/assets/silos/js/filesUpload.js" th:src="@{/assets/silos/js/filesUpload.js}"></script>
 <script src="../../../../static/business/cms/book/info/edit.js" th:src="@{/business/cms/book/info/edit.js}"></script>
 </body>
 </html>

+ 1 - 0
imwork-windows/imwork-silos/src/main/resources/uploads/说明.txt

@@ -0,0 +1 @@
+本地上传文件夹

+ 7 - 0
pom.xml

@@ -93,6 +93,7 @@
         <commons-io.version>2.21.0</commons-io.version>
         <commons-configuration.version>1.10</commons-configuration.version>
         <commons-configuration2.version>2.13.0</commons-configuration2.version>
+        <tika.version>3.2.3</tika.version>
         <!--apache组件end-->
 
         <!--其它-->
@@ -108,6 +109,12 @@
 
     <dependencyManagement>
         <dependencies>
+            <!-- 用于文件类型检测 -->
+            <dependency>
+                <groupId>org.apache.tika</groupId>
+                <artifactId>tika-core</artifactId>
+                <version>${tika.version}</version>
+            </dependency>
             <dependency>
                 <groupId>org.apache.poi</groupId>
                 <artifactId>poi-excelant</artifactId>