diff --git a/biji-houdaun/src/main/java/com/test/bijihoudaun/BijiHoudaunApplication.java b/biji-houdaun/src/main/java/com/test/bijihoudaun/BijiHoudaunApplication.java index 9943c2d..2f0d9f2 100644 --- a/biji-houdaun/src/main/java/com/test/bijihoudaun/BijiHoudaunApplication.java +++ b/biji-houdaun/src/main/java/com/test/bijihoudaun/BijiHoudaunApplication.java @@ -2,10 +2,12 @@ package com.test.bijihoudaun; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication @EnableScheduling +@EnableAsync public class BijiHoudaunApplication { public static void main(String[] args) { diff --git a/biji-houdaun/src/main/java/com/test/bijihoudaun/config/AsyncConfig.java b/biji-houdaun/src/main/java/com/test/bijihoudaun/config/AsyncConfig.java new file mode 100644 index 0000000..a04d12d --- /dev/null +++ b/biji-houdaun/src/main/java/com/test/bijihoudaun/config/AsyncConfig.java @@ -0,0 +1,24 @@ +package com.test.bijihoudaun.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +import java.util.concurrent.Executor; + +@Configuration +@EnableAsync +public class AsyncConfig { + + @Bean("imageNameSyncExecutor") + public Executor imageNameSyncExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(5); + executor.setMaxPoolSize(10); + executor.setQueueCapacity(100); + executor.setThreadNamePrefix("imageNameSync-"); + executor.initialize(); + return executor; + } +} diff --git a/biji-houdaun/src/main/java/com/test/bijihoudaun/entity/ImageName.java b/biji-houdaun/src/main/java/com/test/bijihoudaun/entity/ImageName.java new file mode 100644 index 0000000..34b0e0d --- /dev/null +++ b/biji-houdaun/src/main/java/com/test/bijihoudaun/entity/ImageName.java @@ -0,0 +1,24 @@ +package com.test.bijihoudaun.entity; + +import com.baomidou.mybatisplus.annotation.*; +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@Schema(name = "图片名称实体") +@TableName("image_name") +public class ImageName { + @Schema(description = "图片名称id", implementation = Long.class) + @TableId(type = IdType.AUTO) + @JsonFormat(shape = JsonFormat.Shape.STRING) + private Long id; + + @Schema(description = "关联的Markdown文件ID", implementation = Long.class) + @TableField("markdown_id") + private Long markdownId; + + @Schema(description = "文件名", implementation = String.class) + @TableField("file_name") + private String fileName; +} diff --git a/biji-houdaun/src/main/java/com/test/bijihoudaun/mapper/ImageNameMapper.java b/biji-houdaun/src/main/java/com/test/bijihoudaun/mapper/ImageNameMapper.java new file mode 100644 index 0000000..1bf88f4 --- /dev/null +++ b/biji-houdaun/src/main/java/com/test/bijihoudaun/mapper/ImageNameMapper.java @@ -0,0 +1,10 @@ +package com.test.bijihoudaun.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.test.bijihoudaun.entity.ImageName; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface ImageNameMapper extends BaseMapper { + // 可以在这里添加自定义方法 +} diff --git a/biji-houdaun/src/main/java/com/test/bijihoudaun/service/impl/MarkdownFileServiceImpl.java b/biji-houdaun/src/main/java/com/test/bijihoudaun/service/impl/MarkdownFileServiceImpl.java index 8e2e1ec..867a9dd 100644 --- a/biji-houdaun/src/main/java/com/test/bijihoudaun/service/impl/MarkdownFileServiceImpl.java +++ b/biji-houdaun/src/main/java/com/test/bijihoudaun/service/impl/MarkdownFileServiceImpl.java @@ -1,15 +1,20 @@ package com.test.bijihoudaun.service.impl; +import cn.hutool.core.collection.CollUtil; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.test.bijihoudaun.entity.ImageName; import com.test.bijihoudaun.entity.MarkdownFile; import com.test.bijihoudaun.entity.MarkdownFileVO; +import com.test.bijihoudaun.mapper.ImageNameMapper; import com.test.bijihoudaun.mapper.MarkdownFileMapper; import com.test.bijihoudaun.service.MarkdownFileService; +import com.test.bijihoudaun.util.MarkdownImageExtractor; import com.test.bijihoudaun.util.SnowflakeIdGenerator; import jakarta.annotation.Resource; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import java.util.Date; @@ -23,20 +28,30 @@ public class MarkdownFileServiceImpl @Autowired MarkdownFileMapper markdownFileMapper; @Resource + ImageNameMapper imageNameMapper; + @Resource SnowflakeIdGenerator snowflakeIdGenerator; @Override public MarkdownFile updateMarkdownContent(MarkdownFile markdownFile) { + long id; markdownFile.setUpdatedAt(new Date()); // 如果ID为空或0,则视为新文件 if (markdownFile.getId() == null || markdownFile.getId() == 0L) { - markdownFile.setId(snowflakeIdGenerator.nextId()); + long l = snowflakeIdGenerator.nextId(); + markdownFile.setId(l); markdownFile.setCreatedAt(new Date()); this.save(markdownFile); // 使用MyBatis-Plus的save方法 + id=l; } else { this.updateById(markdownFile); // 使用MyBatis-Plus的updateById方法 + id=markdownFile.getId(); } + + List strings = MarkdownImageExtractor.extractImageFilenames(markdownFile.getContent()); + // 异步处理图片文件名同步 + syncImageNames(id, strings); return markdownFile; } @@ -62,8 +77,7 @@ public class MarkdownFileServiceImpl @Override public List test() { - List markdownFiles = markdownFileMapper.selectList(null); - return markdownFiles; + return markdownFileMapper.selectList(null); } @Override @@ -93,4 +107,63 @@ public class MarkdownFileServiceImpl public List getRecentFiles(int limit) { return markdownFileMapper.selectRecentWithGrouping(limit); } + + + @Async("imageNameSyncExecutor") + public void syncImageNames(Long markdownId, List strings) { + // 查询数据库中已存在的文件名 + List imageNames = imageNameMapper.selectList(new LambdaUpdateWrapper() + .eq(ImageName::getMarkdownId, markdownId)); + + // 若是数据库中的数据为null,则插入 + if (CollUtil.isEmpty(imageNames)) { + if (CollUtil.isNotEmpty(strings)) { + List list = strings.stream().map(fileName -> { + ImageName imageName = new ImageName(); + imageName.setFileName(fileName); + imageName.setMarkdownId(markdownId); + return imageName; + }).toList(); + // 批量插入新的文件名 + list.forEach(imageName -> imageNameMapper.insert(imageName)); + } + } else { + // 数据库中已有记录,需要对比处理 + // 获取数据库中的文件名列表 + List dbFileNames = imageNames.stream() + .map(ImageName::getFileName) + .toList(); + + // 找出需要新增的文件名(在strings中但不在数据库中) + List toInsert = strings.stream() + .filter(fileName -> !dbFileNames.contains(fileName)) + .toList(); + + // 找出需要删除的记录(在数据库中但不在strings中) + List toDelete = imageNames.stream() + .filter(imageName -> !strings.contains(imageName.getFileName())) + .toList(); + + // 插入新增的文件名 + if (CollUtil.isNotEmpty(toInsert)) { + List insertList = toInsert.stream().map(fileName -> { + ImageName imageName = new ImageName(); + imageName.setFileName(fileName); + imageName.setMarkdownId(markdownId); + return imageName; + }).toList(); + + imageNameMapper.insert(insertList); + } + + // 删除不再需要的记录 + if (CollUtil.isNotEmpty(toDelete)) { + List deleteIds = toDelete.stream() + .map(ImageName::getId) + .toList(); + imageNameMapper.deleteByIds(deleteIds); + } + } + } + } diff --git a/biji-houdaun/src/main/java/com/test/bijihoudaun/util/MarkdownImageExtractor.java b/biji-houdaun/src/main/java/com/test/bijihoudaun/util/MarkdownImageExtractor.java new file mode 100644 index 0000000..a13061e --- /dev/null +++ b/biji-houdaun/src/main/java/com/test/bijihoudaun/util/MarkdownImageExtractor.java @@ -0,0 +1,79 @@ +package com.test.bijihoudaun.util; + +import java.util.regex.Pattern; +import java.util.regex.Matcher; +import java.util.List; +import java.util.ArrayList; + +/** + * Markdown图片提取工具类 + * 用于从Markdown文本中提取图片文件名 + */ +public class MarkdownImageExtractor { + + /** + * 从Markdown内容中提取图片文件名 + * 支持各种URL格式: + * - 绝对URL: http://example.com/path/uuid.png + * - 相对URL: /path/uuid.png + * - 协议相对URL: //example.com/path/uuid.png + * + * @param markdownContent 包含图片的Markdown文本 + * @return 图片文件名列表 + */ + public static List extractImageFilenames(String markdownContent) { + if (markdownContent == null || markdownContent.isEmpty()) { + return new ArrayList<>(); + } + + // 使用正则表达式匹配Markdown图片语法中的文件名 + // 模式: ![alt](url) 其中url以UUID格式的文件名结尾 + Pattern pattern = Pattern.compile("!\\[.*?\\]\\([^)]*?([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}\\.[a-zA-Z0-9]+)\\)"); + Matcher matcher = pattern.matcher(markdownContent); + + List filenames = new ArrayList<>(); + while (matcher.find()) { + filenames.add(matcher.group(1)); + } + + return filenames; + } + + /** + * 检查Markdown内容中是否包含指定的图片文件名 + * + * @param markdownContent Markdown文本 + * @param filename 要查找的文件名 + * @return 是否包含该文件名 + */ + public static boolean containsImage(String markdownContent, String filename) { + if (markdownContent == null || filename == null) { + return false; + } + + return extractImageFilenames(markdownContent).contains(filename); + } + + /** + * 从Markdown内容中提取图片URL + * + * @param markdownContent 包含图片的Markdown文本 + * @return 图片URL列表 + */ + public static List extractImageUrls(String markdownContent) { + if (markdownContent == null || markdownContent.isEmpty()) { + return new ArrayList<>(); + } + + // 匹配Markdown图片语法中的完整URL + Pattern pattern = Pattern.compile("!\\[.*?\\]\\(([^)]+)\\)"); + Matcher matcher = pattern.matcher(markdownContent); + + List urls = new ArrayList<>(); + while (matcher.find()) { + urls.add(matcher.group(1)); + } + + return urls; + } +} diff --git a/biji-houdaun/src/main/resources/application-dev.yml b/biji-houdaun/src/main/resources/application-dev.yml index 9f0f341..16c8b63 100644 --- a/biji-houdaun/src/main/resources/application-dev.yml +++ b/biji-houdaun/src/main/resources/application-dev.yml @@ -1,8 +1,8 @@ spring: datasource: driver-class-name: org.sqlite.JDBC -# url: jdbc:sqlite:C:\it\houtaigunli\biji\mydatabase.db - url: jdbc:sqlite:C:\KAIFA\2\mydatabase.db + url: jdbc:sqlite:C:\it\houtaigunli\biji\mydatabase.db +# url: jdbc:sqlite:C:\KAIFA\2\mydatabase.db jpa: hibernate: ddl-auto: none diff --git a/biji-qianduan/src/components/HomePage.vue b/biji-qianduan/src/components/HomePage.vue index c0c301c..6363922 100644 --- a/biji-qianduan/src/components/HomePage.vue +++ b/biji-qianduan/src/components/HomePage.vue @@ -64,7 +64,7 @@ -
+
@@ -350,7 +350,6 @@ const imageUrls = ref([]); const originalImages = ref([]); const vditor = ref(null); -const previewHtml = ref(''); const saveStatus = ref('空闲'); let debounceTimer = null; @@ -559,35 +558,22 @@ const renderMenu = (item) => { // 选择文件预览 const previewFile = async (file) => { - if (file.id === null){ - editData.value=file - selectedFile.value=null + if (file.id === null) { + editData.value = file; + selectedFile.value = null; return; } try { - const response = await Preview(file.id) - - // 确保内容为字符串 - const content = String(response.data || ''); - + const response = await Preview(file.id); + const content = String(response || ''); selectedFile.value = { ...file, content: content }; - await nextTick(); - const previewElement = document.querySelector('.markdown-preview'); - if (previewElement) { - Vditor.preview(previewElement, content, { - // 在这里提供一个基本的配置对象 - mode: 'light', // 或者 'dark',可以根据当前主题动态设置 - hljs: { - enable: true, - style: 'github' - } - }); - } + showEditor.value = false; // 确保进入预览模式 } catch (error) { ElMessage.error('获取笔记内容失败: ' + error.message); + selectedFile.value = null; } }; @@ -993,6 +979,23 @@ watch(activeMenu, (newVal) => { } }); +watch([selectedFile, showEditor], ([newFile, newShowEditor]) => { + if (newFile && !newShowEditor) { + nextTick(() => { + const previewElement = document.querySelector('.markdown-preview'); + if (previewElement) { + Vditor.preview(previewElement, newFile.content, { + mode: 'light', + hljs: { + enable: true, + style: 'github' + } + }); + } + }); + } +}, { deep: true }); + const handleToggleRegistration = async (value) => { try { await toggleRegistration(value); diff --git a/mydatabase.db b/mydatabase.db index c2ef10b..914edc9 100644 Binary files a/mydatabase.db and b/mydatabase.db differ diff --git a/sql/image_name.sql b/sql/image_name.sql new file mode 100644 index 0000000..56d739d --- /dev/null +++ b/sql/image_name.sql @@ -0,0 +1,29 @@ +/* + Navicat Premium Dump SQL + + Source Server : biji数据库 + Source Server Type : SQLite + Source Server Version : 3045000 (3.45.0) + Source Schema : main + + Target Server Type : SQLite + Target Server Version : 3045000 (3.45.0) + File Encoding : 65001 + + Date: 01/08/2025 21:41:44 +*/ + +PRAGMA foreign_keys = false; + +-- ---------------------------- +-- Table structure for image_name +-- ---------------------------- +DROP TABLE IF EXISTS "image_name"; +CREATE TABLE "image_name" ( + "id" INTEGER NOT NULL, + "markdown_id" INTEGER NOT NULL, + "file_name" text NOT NULL, + PRIMARY KEY ("id") +); + +PRAGMA foreign_keys = true;