1 1 месяц назад
Родитель
Сommit
b918499ce8

+ 36 - 0
imwork-windows/imwork-silos/src/main/java/top/imwork/window/silos/processor/ChapterContent.java

@@ -0,0 +1,36 @@
+package top.imwork.window.silos.processor;
+
+import lombok.Data;
+
+/**
+ * Copyright (C), 2015-2025
+ * FileName: ChapterContent
+ * Author<作者姓名>:   stars
+ * CreateTime<创建时间>:   2025/12/16 16:56
+ * UpdateTime<修改时间>:   2025/12/16 16:56
+ * Description〈功能简述〉: 章节内容
+ * History<历史描述>:
+ * Since<版本号>: 1.0.0
+ */
+@Data
+public class ChapterContent {
+    private final int number;
+    private final String title;
+    private final String content;
+
+    public ChapterContent(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());
+    }
+}

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

@@ -32,6 +32,19 @@ public class ChapterSplitter {
         this.useVirtualThreads = useVirtualThreads;
     }
 
+    /**
+     * 检查是否支持虚拟线程
+     * @return
+     */
+    public static boolean checkVirtualThreadSupport() {
+        try {
+            Executors.class.getMethod("newVirtualThreadPerTaskExecutor");
+            return true;
+        } catch (NoSuchMethodException e) {
+            return false;
+        }
+    }
+
     /**
      * 使用虚拟线程处理章节(Java 21+)
      */

+ 176 - 0
imwork-windows/imwork-silos/src/main/java/top/imwork/window/silos/processor/ChapterSplitterProcessor.java

@@ -0,0 +1,176 @@
+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.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.regex.MatchResult;
+import java.util.regex.Pattern;
+
+/**
+ * Copyright (C), 2015-2025
+ * FileName: ChapterSplitterProcessor
+ * Author<作者姓名>:   stars
+ * CreateTime<创建时间>:   2025/12/16 16:48
+ * UpdateTime<修改时间>:   2025/12/16 16:48
+ * Description〈功能简述〉: 大文件文章分割处理器
+ * History<历史描述>:
+ * Since<版本号>: 1.0.0
+ */
+public class ChapterSplitterProcessor {
+    /**
+     * 预定义的章节分割模式
+     * 中文章节
+     */
+    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);
+    }
+
+    /**
+     * 使用虚拟线程拆分章节
+     */
+    public static List<ChapterContent> splitChaptersWithVirtualThreads(String content) throws IOException {
+        // 定义章节分割模式(可根据实际文件格式调整)
+        Pattern chapterPattern = getChineseChapterPattern();
+
+        // 查找所有章节标题位置
+        List<Integer> chapterStarts = new ArrayList<>();
+        var matcher = chapterPattern.matcher(content);
+        while (matcher.find()) {
+            chapterStarts.add(matcher.start());
+        }
+
+        List<ChapterContent> chapters = new ArrayList<>();
+        List<CompletableFuture<ChapterContent>> 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<ChapterContent> future = CompletableFuture.supplyAsync(() -> {
+                    try {
+                        String chapterContent = content.substring(startIndex, endIndex).trim();
+                        return new ChapterContent(chapterNumber, extractChapterTitle(chapterContent), chapterContent);
+                    } catch (Exception e) {
+                        System.err.println("处理章节 " + chapterNumber + " 时出错: " + e.getMessage());
+                        return new ChapterContent(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;
+    }
+
+    /**
+     * 保存章节到单独的文件
+     */
+    public static void saveChaptersToFiles(List<ChapterContent> chapters) {
+        try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
+            List<CompletableFuture<Void>> saveFutures = new ArrayList<>();
+
+            for (ChapterContent 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();
+        }
+    }
+
+    /**
+     * 使用虚拟线程处理章节分割的高级方法
+     */
+    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(MatchResult::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();
+    }
+}

+ 54 - 25
imwork-windows/imwork-silos/src/main/java/top/imwork/window/silos/service/impl/InfoServiceImpl.java

@@ -9,32 +9,40 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 
+import top.imwork.commons.core.constants.Constants;
 import top.imwork.commons.core.utils.StringUtils;
+import top.imwork.window.silos.dao.ContentsDao;
 import top.imwork.window.silos.dao.InfoDao;
+import top.imwork.window.silos.entity.Contents;
 import top.imwork.window.silos.entity.Info;
 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.ChapterContent;
 import top.imwork.window.silos.processor.ChapterSplitter;
+import top.imwork.window.silos.processor.ChapterSplitterProcessor;
 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.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Date;
 import java.util.List;
-import java.util.Scanner;
-import java.util.concurrent.Executors;
 
 @Service("infoService")
-public class InfoServiceImpl extends ServiceImpl<InfoDao,Info> implements IInfoService {
+public class InfoServiceImpl extends ServiceImpl<InfoDao, Info> implements IInfoService {
     @Resource
     private InfoDao infoDao;
 
+    @Resource
+    private ContentsDao contentsDao;
+
     /**
      * 根据id获取信息
+     *
      * @param id 用户id
      * @return InfoBO 响应信息
      */
@@ -46,6 +54,7 @@ public class InfoServiceImpl extends ServiceImpl<InfoDao,Info> implements IInfoS
 
     /**
      * 新增/更新信息
+     *
      * @param infoDTO 信息
      * @return infoBO 更新后信息
      */
@@ -54,10 +63,12 @@ public class InfoServiceImpl extends ServiceImpl<InfoDao,Info> implements IInfoS
         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);
+            if (StringUtils.isNotEmpty(infoDTO.getFilePaths())) {
+                try {
+                    runWithVirtualThreads(info, infoDTO.getFilePaths());
+                } catch (Exception e) {
+                    throw new RuntimeException(e);
+                }
             }
         } else {
             infoDao.updateById(info);
@@ -65,36 +76,54 @@ public class InfoServiceImpl extends ServiceImpl<InfoDao,Info> implements IInfoS
         return InfoConvert.infoDoToBo(info);
     }
 
-    private static void runWithVirtualThreads(Info info,String filePath) throws Exception {
-        if (!checkVirtualThreadSupport()) {
+    private void runWithVirtualThreads(Info info, String filePath) throws Exception {
+        if (!ChapterSplitter.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("开始使用虚拟线程处理章节...");
+        try {
+            // 读取文件内容
+            String content = Files.readString(Path.of(filePath));
 
-        List<Chapter> chapters = splitter.splitWithVirtualThreads();
+            // 使用虚拟线程池处理章节拆分
+            List<ChapterContent> chapters = ChapterSplitterProcessor.splitChaptersWithVirtualThreads(content);
 
-        splitter.printStatistics(chapters);
-        splitter.saveChaptersToFiles(chapters, fileAndOutput[1]);
+            // 输出结果
+            System.out.println("总共拆分成 " + chapters.size() + " 个章节");
 
-        System.out.println("处理完成!");
-    }
-    private static boolean checkVirtualThreadSupport() {
-        try {
-            Executors.class.getMethod("newVirtualThreadPerTaskExecutor");
-            return true;
-        } catch (NoSuchMethodException e) {
-            return false;
+            // 保存章节到文件
+            //ChapterSplitterProcessor.saveChaptersToFiles(chapters);
+            this.saveContents(chapters, info);
+        } catch (IOException e) {
+            System.err.println("读取文件出错: " + e.getMessage());
         }
     }
 
+    private void saveContents(List<ChapterContent> chapters, Info info) {
+        List<Contents> contentsList = new ArrayList<>();
+        for (ChapterContent chapterContent : chapters) {
+            Contents contents = new Contents();
+            contents.setBookInfoId(info.getIsbn());
+            contents.setDelFlag(Constants.DEL_FLAG_N);
+            contents.setCreateTime(new Date());
+            contents.setUpdateTime(new Date());
+            contents.setContentType("text");
+            contents.setContentText(chapterContent.getContent());
+            contents.setChapterNumber(String.valueOf(chapterContent.getNumber()));
+            contents.setChapterTitle(chapterContent.getTitle());
+            contents.setVersion("v1.0");
+            contents.setIsPublic(1);
+            contents.setWordCount(chapterContent.getContent().length());
+            contentsList.add(contents);
+        }
+        contentsDao.insert(contentsList);
+    }
 
     /**
      * 根据id删除信息
+     *
      * @param id id
      * @return true/false 是否删除成功
      */

+ 24 - 24
imwork-windows/imwork-silos/src/main/resources/static/business/cms/book/chapter/chapter.js

@@ -74,12 +74,12 @@ layui.config({
 
     // 工具函数
     const utils = {
-        showError: function(title, msg) {
+        showError: function (title, msg) {
             console.error(title, msg);
             // 这里可以添加更友好的错误提示,如使用layer
         },
 
-        getFontFamily: function(font) {
+        getFontFamily: function (font) {
             const fontMap = {
                 'serif': "'Noto Serif SC', 'Times New Roman', serif",
                 'sans-serif': "'Segoe UI', 'Microsoft YaHei', sans-serif",
@@ -89,7 +89,7 @@ layui.config({
             return fontMap[font] || fontMap.system;
         },
 
-        debounce: function(func, wait) {
+        debounce: function (func, wait) {
             let timeout;
             return function executedFunction(...args) {
                 const later = () => {
@@ -105,7 +105,7 @@ layui.config({
     // 章节管理
     const chapterManager = {
         // 加载章节数据
-        loadChapters: function() {
+        loadChapters: function () {
             if (state.isChaptersLoaded) {
                 return Promise.resolve(state.chapters);
             }
@@ -127,7 +127,7 @@ layui.config({
                                 id: chapter.id,
                                 number: chapter.chapterNumber,
                                 title: chapter.chapterTitle,
-                                content: chapter.contentHtml
+                                content: chapter.contentHtml ? chapter.contentHtml : chapter.contentText
                             }));
                             state.isChaptersLoaded = true;
                             resolve(state.chapters);
@@ -146,7 +146,7 @@ layui.config({
         },
 
         // 渲染章节列表(只渲染一次,后续只更新激活状态)
-        renderChaptersList: function() {
+        renderChaptersList: function () {
             if (state.isRendering || !state.isChaptersLoaded) return;
 
             state.isRendering = true;
@@ -163,7 +163,7 @@ layui.config({
         },
 
         // 更新章节激活状态(避免重新渲染整个列表)
-        updateActiveChapter: function() {
+        updateActiveChapter: function () {
             if (!DOM.chaptersList) return;
 
             // 移除所有激活状态
@@ -184,7 +184,7 @@ layui.config({
         },
 
         // 显示指定章节
-        showChapter: function(index) {
+        showChapter: function (index) {
             if (index < 0 || index >= state.chapters.length) return;
 
             state.currentChapterIndex = index;
@@ -207,7 +207,7 @@ layui.config({
         },
 
         // 更新阅读进度
-        updateProgress: function() {
+        updateProgress: function () {
             const progress = ((state.currentChapterIndex + 1) / state.chapters.length * 100).toFixed(0);
             DOM.progressPercent.textContent = `${progress}%`;
         }
@@ -216,7 +216,7 @@ layui.config({
     // 阅读设置管理
     const settingsManager = {
         // 应用阅读设置
-        applyReadingSettings: function() {
+        applyReadingSettings: function () {
             // 字体大小
             DOM.readingArea.className = DOM.readingArea.className.replace(/\bfont-\w+\b/g, '');
             DOM.readingArea.classList.add(`font-${state.fontSize}`);
@@ -239,10 +239,10 @@ layui.config({
         },
 
         // 初始化设置面板
-        initSettingsPanel: function() {
+        initSettingsPanel: function () {
             // 字体大小设置
             document.querySelectorAll('.font-size-btn').forEach(btn => {
-                btn.addEventListener('click', function() {
+                btn.addEventListener('click', function () {
                     document.querySelectorAll('.font-size-btn').forEach(b => b.classList.remove('active'));
                     this.classList.add('active');
                     state.fontSize = this.getAttribute('data-size');
@@ -252,7 +252,7 @@ layui.config({
 
             // 主题设置
             document.querySelectorAll('.theme-btn').forEach(btn => {
-                btn.addEventListener('click', function() {
+                btn.addEventListener('click', function () {
                     document.querySelectorAll('.theme-btn').forEach(b => b.classList.remove('active'));
                     this.classList.add('active');
                     state.theme = this.getAttribute('data-theme');
@@ -262,7 +262,7 @@ layui.config({
 
             // 行高设置
             document.querySelectorAll('[data-lineheight]').forEach(btn => {
-                btn.addEventListener('click', function() {
+                btn.addEventListener('click', function () {
                     document.querySelectorAll('[data-lineheight]').forEach(b => b.classList.remove('active'));
                     this.classList.add('active');
                     state.lineHeight = this.getAttribute('data-lineheight');
@@ -271,7 +271,7 @@ layui.config({
             });
 
             // 字体设置
-            DOM.fontSelect.addEventListener('change', function() {
+            DOM.fontSelect.addEventListener('change', function () {
                 state.fontFamily = this.value;
                 settingsManager.applyReadingSettings();
             });
@@ -280,9 +280,9 @@ layui.config({
 
     // 事件处理
     const eventHandler = {
-        initEvents: function() {
+        initEvents: function () {
             // 切换侧边栏
-            DOM.toggleSidebar.addEventListener('click', function() {
+            DOM.toggleSidebar.addEventListener('click', function () {
                 DOM.sidebar.classList.toggle('collapsed');
                 DOM.mainContent.classList.toggle('expanded');
             });
@@ -313,7 +313,7 @@ layui.config({
             });
 
             // 章节列表点击事件(使用事件委托)
-            DOM.chaptersList.addEventListener('click', utils.debounce(function(e) {
+            DOM.chaptersList.addEventListener('click', utils.debounce(function (e) {
                 const chapterItem = e.target.closest('.chapter-item');
                 if (chapterItem) {
                     const index = parseInt(chapterItem.getAttribute('data-index'));
@@ -327,29 +327,29 @@ layui.config({
             }, 100));
 
             // 设置面板
-            DOM.settingsBtn.addEventListener('click', function() {
+            DOM.settingsBtn.addEventListener('click', function () {
                 DOM.settingsPanel.classList.add('open');
             });
 
-            DOM.closeSettings.addEventListener('click', function() {
+            DOM.closeSettings.addEventListener('click', function () {
                 DOM.settingsPanel.classList.remove('open');
             });
 
             // 书签功能
-            DOM.bookmarkBtn.addEventListener('click', function() {
+            DOM.bookmarkBtn.addEventListener('click', function () {
                 const chapter = state.chapters[state.currentChapterIndex];
                 layer.msg(`已将 "${chapter.number} ${chapter.title}" 添加到书签`);
             });
 
             // 搜索功能
-            DOM.searchBtn.addEventListener('click', function() {
+            DOM.searchBtn.addEventListener('click', function () {
                 layer.msg('搜索功能即将推出');
             });
         }
     };
 
     // 添加CSS类来处理字体大小和行高
-    const addStyle = function() {
+    const addStyle = function () {
         const style = document.createElement('style');
         style.textContent = `
             .font-small { font-size: 0.9rem; }
@@ -372,7 +372,7 @@ layui.config({
     };
 
     // 初始化
-    const init = function() {
+    const init = function () {
         addStyle();
 
         // 加载章节数据