16 changed files with 1524 additions and 9 deletions
@ -1,2 +1,3 @@ |
|||
*.class |
|||
target/* |
|||
target/* |
|||
target/classes/com/example/App.class |
|||
|
|||
@ -0,0 +1,227 @@ |
|||
package com.example.model; |
|||
|
|||
import java.time.LocalDateTime; |
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
import java.util.Objects; |
|||
import java.util.UUID; |
|||
|
|||
/** |
|||
* 笔记实体类 |
|||
* 表示系统中的一条笔记记录 |
|||
*/ |
|||
public class Note { |
|||
private String id; // UUID
|
|||
private String title; // 笔记标题
|
|||
private String content; // 笔记内容
|
|||
private List<String> tags; // 标签列表
|
|||
private LocalDateTime createdAt; // 创建时间
|
|||
private LocalDateTime updatedAt; // 更新时间
|
|||
|
|||
/** |
|||
* 默认构造方法 |
|||
*/ |
|||
public Note() { |
|||
this.id = UUID.randomUUID().toString(); |
|||
this.tags = new ArrayList<>(); |
|||
this.createdAt = LocalDateTime.now(); |
|||
this.updatedAt = LocalDateTime.now(); |
|||
} |
|||
|
|||
/** |
|||
* 带参数的构造方法 |
|||
*/ |
|||
public Note(String title, String content) { |
|||
this(); |
|||
this.title = title; |
|||
this.content = content; |
|||
} |
|||
|
|||
/** |
|||
* 完整构造方法 |
|||
*/ |
|||
public Note(String id, String title, String content, List<String> tags, |
|||
LocalDateTime createdAt, LocalDateTime updatedAt) { |
|||
this.id = id; |
|||
this.title = title; |
|||
this.content = content; |
|||
this.tags = tags != null ? new ArrayList<>(tags) : new ArrayList<>(); |
|||
this.createdAt = createdAt; |
|||
this.updatedAt = updatedAt; |
|||
} |
|||
|
|||
// Getter和Setter方法
|
|||
public String getId() { |
|||
return id; |
|||
} |
|||
|
|||
public void setId(String id) { |
|||
this.id = id; |
|||
} |
|||
|
|||
public String getTitle() { |
|||
return title; |
|||
} |
|||
|
|||
public void setTitle(String title) { |
|||
this.title = title; |
|||
this.updatedAt = LocalDateTime.now(); |
|||
} |
|||
|
|||
public String getContent() { |
|||
return content; |
|||
} |
|||
|
|||
public void setContent(String content) { |
|||
this.content = content; |
|||
this.updatedAt = LocalDateTime.now(); |
|||
} |
|||
|
|||
public List<String> getTags() { |
|||
return new ArrayList<>(tags); |
|||
} |
|||
|
|||
public void setTags(List<String> tags) { |
|||
this.tags = tags != null ? new ArrayList<>(tags) : new ArrayList<>(); |
|||
this.updatedAt = LocalDateTime.now(); |
|||
} |
|||
|
|||
public LocalDateTime getCreatedAt() { |
|||
return createdAt; |
|||
} |
|||
|
|||
public void setCreatedAt(LocalDateTime createdAt) { |
|||
this.createdAt = createdAt; |
|||
} |
|||
|
|||
public LocalDateTime getUpdatedAt() { |
|||
return updatedAt; |
|||
} |
|||
|
|||
public void setUpdatedAt(LocalDateTime updatedAt) { |
|||
this.updatedAt = updatedAt; |
|||
} |
|||
|
|||
// 标签操作方法
|
|||
/** |
|||
* 添加标签 |
|||
*/ |
|||
public void addTag(String tag) { |
|||
if (tag != null && !tag.trim().isEmpty() && !tags.contains(tag.trim())) { |
|||
tags.add(tag.trim()); |
|||
this.updatedAt = LocalDateTime.now(); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 移除标签 |
|||
*/ |
|||
public boolean removeTag(String tag) { |
|||
if (tag != null && tags.remove(tag.trim())) { |
|||
this.updatedAt = LocalDateTime.now(); |
|||
return true; |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
/** |
|||
* 检查是否包含指定标签 |
|||
*/ |
|||
public boolean hasTag(String tag) { |
|||
return tag != null && tags.contains(tag.trim()); |
|||
} |
|||
|
|||
/** |
|||
* 获取标签数量 |
|||
*/ |
|||
public int getTagCount() { |
|||
return tags.size(); |
|||
} |
|||
|
|||
/** |
|||
* 更新笔记内容 |
|||
*/ |
|||
public void update(String newTitle, String newContent) { |
|||
if (newTitle != null) { |
|||
this.title = newTitle; |
|||
} |
|||
if (newContent != null) { |
|||
this.content = newContent; |
|||
} |
|||
this.updatedAt = LocalDateTime.now(); |
|||
} |
|||
|
|||
/** |
|||
* 检查笔记是否包含关键词 |
|||
*/ |
|||
public boolean containsKeyword(String keyword) { |
|||
if (keyword == null || keyword.trim().isEmpty()) { |
|||
return false; |
|||
} |
|||
|
|||
String lowerKeyword = keyword.toLowerCase(); |
|||
return (title != null && title.toLowerCase().contains(lowerKeyword)) || |
|||
(content != null && content.toLowerCase().contains(lowerKeyword)); |
|||
} |
|||
|
|||
/** |
|||
* 获取笔记摘要(前100个字符) |
|||
*/ |
|||
public String getSummary() { |
|||
if (content == null || content.isEmpty()) { |
|||
return ""; |
|||
} |
|||
|
|||
String cleanContent = content.replaceAll("\\s+", " ").trim(); |
|||
if (cleanContent.length() <= 100) { |
|||
return cleanContent; |
|||
} |
|||
|
|||
return cleanContent.substring(0, 97) + "..."; |
|||
} |
|||
|
|||
/** |
|||
* 复制笔记(深拷贝) |
|||
*/ |
|||
public Note copy() { |
|||
return new Note( |
|||
UUID.randomUUID().toString(), // 新的ID
|
|||
this.title, |
|||
this.content, |
|||
new ArrayList<>(this.tags), |
|||
LocalDateTime.now(), // 新的创建时间
|
|||
LocalDateTime.now() // 新的更新时间
|
|||
); |
|||
} |
|||
|
|||
@Override |
|||
public boolean equals(Object obj) { |
|||
if (this == obj) return true; |
|||
if (obj == null || getClass() != obj.getClass()) return false; |
|||
|
|||
Note note = (Note) obj; |
|||
return Objects.equals(id, note.id); |
|||
} |
|||
|
|||
@Override |
|||
public int hashCode() { |
|||
return Objects.hash(id); |
|||
} |
|||
|
|||
@Override |
|||
public String toString() { |
|||
return String.format("Note{id='%s', title='%s', tags=%s, created=%s, updated=%s}", |
|||
id, title, tags, createdAt, updatedAt); |
|||
} |
|||
|
|||
/** |
|||
* 获取格式化的显示字符串 |
|||
*/ |
|||
public String getDisplayString() { |
|||
return String.format("[%s] %s (%s) %s", |
|||
id.substring(0, 8), |
|||
title, |
|||
createdAt.toLocalDate(), |
|||
tags); |
|||
} |
|||
} |
|||
@ -0,0 +1,40 @@ |
|||
package com.example.service.export; |
|||
|
|||
/** |
|||
* 导出操作异常类 |
|||
* 当导出操作失败时抛出此异常 |
|||
*/ |
|||
public class ExportException extends Exception { |
|||
|
|||
/** |
|||
* 默认构造方法 |
|||
*/ |
|||
public ExportException() { |
|||
super(); |
|||
} |
|||
|
|||
/** |
|||
* 带消息的构造方法 |
|||
* @param message 异常消息 |
|||
*/ |
|||
public ExportException(String message) { |
|||
super(message); |
|||
} |
|||
|
|||
/** |
|||
* 带消息和原因的构造方法 |
|||
* @param message 异常消息 |
|||
* @param cause 异常原因 |
|||
*/ |
|||
public ExportException(String message, Throwable cause) { |
|||
super(message, cause); |
|||
} |
|||
|
|||
/** |
|||
* 带原因的构造方法 |
|||
* @param cause 异常原因 |
|||
*/ |
|||
public ExportException(Throwable cause) { |
|||
super(cause); |
|||
} |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
package com.example.service.export; |
|||
|
|||
import com.example.model.Note; |
|||
import java.util.List; |
|||
|
|||
/** |
|||
* 导出器接口 |
|||
* 定义笔记导出功能 |
|||
*/ |
|||
public interface Exporter { |
|||
|
|||
/** |
|||
* 导出单个笔记 |
|||
* @param note 要导出的笔记 |
|||
* @param filePath 导出文件路径 |
|||
* @throws ExportException 当导出失败时抛出 |
|||
*/ |
|||
void export(Note note, String filePath) throws ExportException; |
|||
|
|||
/** |
|||
* 导出多个笔记 |
|||
* @param notes 要导出的笔记列表 |
|||
* @param filePath 导出文件路径 |
|||
* @throws ExportException 当导出失败时抛出 |
|||
*/ |
|||
void exportAll(List<Note> notes, String filePath) throws ExportException; |
|||
|
|||
/** |
|||
* 获取支持的文件扩展名 |
|||
* @return 文件扩展名(如 ".txt", ".json") |
|||
*/ |
|||
String getFileExtension(); |
|||
|
|||
/** |
|||
* 获取导出格式描述 |
|||
* @return 格式描述 |
|||
*/ |
|||
String getFormatDescription(); |
|||
} |
|||
@ -0,0 +1,83 @@ |
|||
package com.example.service.export; |
|||
|
|||
import com.example.model.ExportFormat; |
|||
|
|||
/** |
|||
* 导出器工厂类 |
|||
* 使用工厂模式创建不同格式的导出器 |
|||
*/ |
|||
public class ExporterFactory { |
|||
|
|||
/** |
|||
* 根据导出格式创建对应的导出器 |
|||
* @param format 导出格式 |
|||
* @return 对应的导出器实例 |
|||
* @throws IllegalArgumentException 当格式不支持时抛出 |
|||
*/ |
|||
public static Exporter createExporter(ExportFormat format) { |
|||
if (format == null) { |
|||
throw new IllegalArgumentException("导出格式不能为空"); |
|||
} |
|||
|
|||
return switch (format) { |
|||
case TXT -> new TxtExporter(); |
|||
case JSON -> new JsonExporter(); |
|||
case PDF -> throw new IllegalArgumentException("PDF导出功能暂未实现"); |
|||
default -> throw new IllegalArgumentException("不支持的导出格式: " + format); |
|||
}; |
|||
} |
|||
|
|||
/** |
|||
* 根据格式名称字符串创建导出器 |
|||
* @param formatName 格式名称(如 "txt", "json") |
|||
* @return 对应的导出器实例 |
|||
* @throws IllegalArgumentException 当格式不支持时抛出 |
|||
*/ |
|||
public static Exporter createExporter(String formatName) { |
|||
if (formatName == null || formatName.trim().isEmpty()) { |
|||
throw new IllegalArgumentException("导出格式名称不能为空"); |
|||
} |
|||
|
|||
try { |
|||
ExportFormat format = ExportFormat.valueOf(formatName.toUpperCase()); |
|||
return createExporter(format); |
|||
} catch (IllegalArgumentException e) { |
|||
throw new IllegalArgumentException("不支持的导出格式: " + formatName); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 获取所有支持的导出格式 |
|||
* @return 支持的导出格式数组 |
|||
*/ |
|||
public static ExportFormat[] getSupportedFormats() { |
|||
return new ExportFormat[]{ExportFormat.TXT, ExportFormat.JSON}; |
|||
} |
|||
|
|||
/** |
|||
* 检查格式是否支持 |
|||
* @param format 要检查的格式 |
|||
* @return 是否支持 |
|||
*/ |
|||
public static boolean isFormatSupported(ExportFormat format) { |
|||
return format == ExportFormat.TXT || format == ExportFormat.JSON; |
|||
} |
|||
|
|||
/** |
|||
* 检查格式名称是否支持 |
|||
* @param formatName 格式名称 |
|||
* @return 是否支持 |
|||
*/ |
|||
public static boolean isFormatSupported(String formatName) { |
|||
if (formatName == null || formatName.trim().isEmpty()) { |
|||
return false; |
|||
} |
|||
|
|||
try { |
|||
ExportFormat format = ExportFormat.valueOf(formatName.toUpperCase()); |
|||
return isFormatSupported(format); |
|||
} catch (IllegalArgumentException e) { |
|||
return false; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,128 @@ |
|||
package com.example.service.export; |
|||
|
|||
import com.example.model.Note; |
|||
|
|||
import java.io.FileWriter; |
|||
import java.io.IOException; |
|||
import java.io.PrintWriter; |
|||
import java.time.format.DateTimeFormatter; |
|||
import java.util.List; |
|||
|
|||
/** |
|||
* JSON格式导出器 |
|||
* 将笔记导出为JSON格式(简化实现,不依赖外部库) |
|||
*/ |
|||
public class JsonExporter implements Exporter { |
|||
|
|||
private static final DateTimeFormatter DATE_FORMATTER = |
|||
DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss"); |
|||
|
|||
@Override |
|||
public void export(Note note, String filePath) throws ExportException { |
|||
if (note == null) { |
|||
throw new ExportException("笔记不能为空"); |
|||
} |
|||
|
|||
if (filePath == null || filePath.trim().isEmpty()) { |
|||
throw new ExportException("文件路径不能为空"); |
|||
} |
|||
|
|||
try (PrintWriter writer = new PrintWriter(new FileWriter(filePath))) { |
|||
writer.print(noteToJson(note)); |
|||
} catch (IOException e) { |
|||
throw new ExportException("导出JSON文件失败: " + e.getMessage(), e); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void exportAll(List<Note> notes, String filePath) throws ExportException { |
|||
if (notes == null) { |
|||
throw new ExportException("笔记列表不能为空"); |
|||
} |
|||
|
|||
if (filePath == null || filePath.trim().isEmpty()) { |
|||
throw new ExportException("文件路径不能为空"); |
|||
} |
|||
|
|||
try (PrintWriter writer = new PrintWriter(new FileWriter(filePath))) { |
|||
writer.println("["); |
|||
|
|||
for (int i = 0; i < notes.size(); i++) { |
|||
Note note = notes.get(i); |
|||
String json = noteToJson(note); |
|||
|
|||
// 缩进JSON内容
|
|||
String[] lines = json.split("\n"); |
|||
for (String line : lines) { |
|||
writer.println(" " + line); |
|||
} |
|||
|
|||
// 如果不是最后一个元素,添加逗号
|
|||
if (i < notes.size() - 1) { |
|||
writer.println(","); |
|||
} else { |
|||
writer.println(); |
|||
} |
|||
} |
|||
|
|||
writer.println("]"); |
|||
} catch (IOException e) { |
|||
throw new ExportException("导出JSON文件失败: " + e.getMessage(), e); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public String getFileExtension() { |
|||
return ".json"; |
|||
} |
|||
|
|||
@Override |
|||
public String getFormatDescription() { |
|||
return "JSON格式"; |
|||
} |
|||
|
|||
/** |
|||
* 将笔记对象转换为JSON字符串 |
|||
*/ |
|||
private String noteToJson(Note note) { |
|||
StringBuilder json = new StringBuilder(); |
|||
json.append("{\n"); |
|||
json.append(" \"id\": \"").append(escapeJson(note.getId())).append("\",\n"); |
|||
json.append(" \"title\": \"").append(escapeJson(note.getTitle())).append("\",\n"); |
|||
json.append(" \"content\": \"").append(escapeJson(note.getContent())).append("\",\n"); |
|||
|
|||
// 标签数组
|
|||
json.append(" \"tags\": ["); |
|||
List<String> tags = note.getTags(); |
|||
for (int i = 0; i < tags.size(); i++) { |
|||
json.append("\"").append(escapeJson(tags.get(i))).append("\""); |
|||
if (i < tags.size() - 1) { |
|||
json.append(", "); |
|||
} |
|||
} |
|||
json.append("],\n"); |
|||
|
|||
json.append(" \"createdAt\": \"").append(note.getCreatedAt().format(DATE_FORMATTER)).append("\",\n"); |
|||
json.append(" \"updatedAt\": \"").append(note.getUpdatedAt().format(DATE_FORMATTER)).append("\"\n"); |
|||
json.append("}"); |
|||
|
|||
return json.toString(); |
|||
} |
|||
|
|||
/** |
|||
* 转义JSON字符串中的特殊字符 |
|||
*/ |
|||
private String escapeJson(String input) { |
|||
if (input == null) { |
|||
return ""; |
|||
} |
|||
|
|||
return input.replace("\\", "\\\\") |
|||
.replace("\"", "\\\"") |
|||
.replace("\n", "\\n") |
|||
.replace("\r", "\\r") |
|||
.replace("\t", "\\t") |
|||
.replace("\b", "\\b") |
|||
.replace("\f", "\\f"); |
|||
} |
|||
} |
|||
@ -0,0 +1,117 @@ |
|||
package com.example.service.export; |
|||
|
|||
import com.example.model.Note; |
|||
|
|||
import java.io.FileWriter; |
|||
import java.io.IOException; |
|||
import java.io.PrintWriter; |
|||
import java.time.format.DateTimeFormatter; |
|||
import java.util.List; |
|||
|
|||
/** |
|||
* TXT格式导出器 |
|||
* 将笔记导出为纯文本格式 |
|||
*/ |
|||
public class TxtExporter implements Exporter { |
|||
|
|||
private static final DateTimeFormatter DATE_FORMATTER = |
|||
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); |
|||
|
|||
@Override |
|||
public void export(Note note, String filePath) throws ExportException { |
|||
if (note == null) { |
|||
throw new ExportException("笔记不能为空"); |
|||
} |
|||
|
|||
if (filePath == null || filePath.trim().isEmpty()) { |
|||
throw new ExportException("文件路径不能为空"); |
|||
} |
|||
|
|||
try (PrintWriter writer = new PrintWriter(new FileWriter(filePath))) { |
|||
writeNoteToTxt(writer, note); |
|||
} catch (IOException e) { |
|||
throw new ExportException("导出TXT文件失败: " + e.getMessage(), e); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void exportAll(List<Note> notes, String filePath) throws ExportException { |
|||
if (notes == null) { |
|||
throw new ExportException("笔记列表不能为空"); |
|||
} |
|||
|
|||
if (filePath == null || filePath.trim().isEmpty()) { |
|||
throw new ExportException("文件路径不能为空"); |
|||
} |
|||
|
|||
try (PrintWriter writer = new PrintWriter(new FileWriter(filePath))) { |
|||
writer.println("========================================"); |
|||
writer.println(" 个人知识管理系统"); |
|||
writer.println(" 笔记导出文件"); |
|||
writer.println("========================================"); |
|||
writer.println("导出时间: " + java.time.LocalDateTime.now().format(DATE_FORMATTER)); |
|||
writer.println("笔记总数: " + notes.size()); |
|||
writer.println("========================================"); |
|||
writer.println(); |
|||
|
|||
for (int i = 0; i < notes.size(); i++) { |
|||
Note note = notes.get(i); |
|||
writer.println("========== 笔记 " + (i + 1) + " =========="); |
|||
writeNoteToTxt(writer, note); |
|||
writer.println(); |
|||
writer.println("----------------------------------------"); |
|||
writer.println(); |
|||
} |
|||
|
|||
writer.println("========================================"); |
|||
writer.println(" 导出完成"); |
|||
writer.println("========================================"); |
|||
} catch (IOException e) { |
|||
throw new ExportException("导出TXT文件失败: " + e.getMessage(), e); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public String getFileExtension() { |
|||
return ".txt"; |
|||
} |
|||
|
|||
@Override |
|||
public String getFormatDescription() { |
|||
return "纯文本格式 (TXT)"; |
|||
} |
|||
|
|||
/** |
|||
* 将单个笔记写入TXT格式 |
|||
*/ |
|||
private void writeNoteToTxt(PrintWriter writer, Note note) { |
|||
writer.println("笔记ID: " + note.getId()); |
|||
writer.println("标题: " + (note.getTitle() != null ? note.getTitle() : "无标题")); |
|||
writer.println("创建时间: " + note.getCreatedAt().format(DATE_FORMATTER)); |
|||
writer.println("更新时间: " + note.getUpdatedAt().format(DATE_FORMATTER)); |
|||
|
|||
// 标签
|
|||
if (note.getTags().isEmpty()) { |
|||
writer.println("标签: 无"); |
|||
} else { |
|||
writer.println("标签: " + String.join(", ", note.getTags())); |
|||
} |
|||
|
|||
writer.println(); |
|||
writer.println("内容:"); |
|||
writer.println("----------------------------------------"); |
|||
|
|||
String content = note.getContent(); |
|||
if (content == null || content.trim().isEmpty()) { |
|||
writer.println("(无内容)"); |
|||
} else { |
|||
// 确保内容正确换行
|
|||
String[] lines = content.split("\n"); |
|||
for (String line : lines) { |
|||
writer.println(line); |
|||
} |
|||
} |
|||
|
|||
writer.println("----------------------------------------"); |
|||
} |
|||
} |
|||
@ -0,0 +1,192 @@ |
|||
package com.example.service.search; |
|||
|
|||
import com.example.model.Note; |
|||
import java.util.List; |
|||
import java.util.stream.Collectors; |
|||
|
|||
/** |
|||
* 搜索服务类 |
|||
* 提供笔记搜索功能 |
|||
*/ |
|||
public class SearchService { |
|||
|
|||
/** |
|||
* 根据关键词搜索笔记 |
|||
* @param notes 要搜索的笔记列表 |
|||
* @param keyword 搜索关键词 |
|||
* @return 匹配的笔记列表 |
|||
*/ |
|||
public List<Note> searchByKeyword(List<Note> notes, String keyword) { |
|||
if (notes == null || keyword == null || keyword.trim().isEmpty()) { |
|||
return List.of(); |
|||
} |
|||
|
|||
String lowerKeyword = keyword.toLowerCase().trim(); |
|||
|
|||
return notes.stream() |
|||
.filter(note -> noteContainsKeyword(note, lowerKeyword)) |
|||
.collect(Collectors.toList()); |
|||
} |
|||
|
|||
/** |
|||
* 根据标签搜索笔记 |
|||
* @param notes 要搜索的笔记列表 |
|||
* @param tag 搜索标签 |
|||
* @return 匹配的笔记列表 |
|||
*/ |
|||
public List<Note> searchByTag(List<Note> notes, String tag) { |
|||
if (notes == null || tag == null || tag.trim().isEmpty()) { |
|||
return List.of(); |
|||
} |
|||
|
|||
String trimmedTag = tag.trim(); |
|||
|
|||
return notes.stream() |
|||
.filter(note -> note.getTags().contains(trimmedTag)) |
|||
.collect(Collectors.toList()); |
|||
} |
|||
|
|||
/** |
|||
* 根据标题搜索笔记 |
|||
* @param notes 要搜索的笔记列表 |
|||
* @param title 搜索标题 |
|||
* @return 匹配的笔记列表 |
|||
*/ |
|||
public List<Note> searchByTitle(List<Note> notes, String title) { |
|||
if (notes == null || title == null || title.trim().isEmpty()) { |
|||
return List.of(); |
|||
} |
|||
|
|||
String lowerTitle = title.toLowerCase().trim(); |
|||
|
|||
return notes.stream() |
|||
.filter(note -> note.getTitle() != null && |
|||
note.getTitle().toLowerCase().contains(lowerTitle)) |
|||
.collect(Collectors.toList()); |
|||
} |
|||
|
|||
/** |
|||
* 根据内容搜索笔记 |
|||
* @param notes 要搜索的笔记列表 |
|||
* @param content 搜索内容 |
|||
* @return 匹配的笔记列表 |
|||
*/ |
|||
public List<Note> searchByContent(List<Note> notes, String content) { |
|||
if (notes == null || content == null || content.trim().isEmpty()) { |
|||
return List.of(); |
|||
} |
|||
|
|||
String lowerContent = content.toLowerCase().trim(); |
|||
|
|||
return notes.stream() |
|||
.filter(note -> note.getContent() != null && |
|||
note.getContent().toLowerCase().contains(lowerContent)) |
|||
.collect(Collectors.toList()); |
|||
} |
|||
|
|||
/** |
|||
* 组合搜索:同时匹配关键词和标签 |
|||
* @param notes 要搜索的笔记列表 |
|||
* @param keyword 关键词 |
|||
* @param tag 标签 |
|||
* @return 匹配的笔记列表 |
|||
*/ |
|||
public List<Note> searchByKeywordAndTag(List<Note> notes, String keyword, String tag) { |
|||
if (notes == null) { |
|||
return List.of(); |
|||
} |
|||
|
|||
return notes.stream() |
|||
.filter(note -> { |
|||
boolean keywordMatch = keyword == null || keyword.trim().isEmpty() || |
|||
noteContainsKeyword(note, keyword.toLowerCase().trim()); |
|||
boolean tagMatch = tag == null || tag.trim().isEmpty() || |
|||
note.getTags().contains(tag.trim()); |
|||
return keywordMatch && tagMatch; |
|||
}) |
|||
.collect(Collectors.toList()); |
|||
} |
|||
|
|||
/** |
|||
* 模糊搜索:在标题、内容和标签中搜索 |
|||
* @param notes 要搜索的笔记列表 |
|||
* @param query 搜索查询 |
|||
* @return 匹配的笔记列表 |
|||
*/ |
|||
public List<Note> fuzzySearch(List<Note> notes, String query) { |
|||
if (notes == null || query == null || query.trim().isEmpty()) { |
|||
return List.of(); |
|||
} |
|||
|
|||
String lowerQuery = query.toLowerCase().trim(); |
|||
|
|||
return notes.stream() |
|||
.filter(note -> { |
|||
// 检查标题
|
|||
if (note.getTitle() != null && |
|||
note.getTitle().toLowerCase().contains(lowerQuery)) { |
|||
return true; |
|||
} |
|||
|
|||
// 检查内容
|
|||
if (note.getContent() != null && |
|||
note.getContent().toLowerCase().contains(lowerQuery)) { |
|||
return true; |
|||
} |
|||
|
|||
// 检查标签
|
|||
return note.getTags().stream() |
|||
.anyMatch(tag -> tag.toLowerCase().contains(lowerQuery)); |
|||
}) |
|||
.collect(Collectors.toList()); |
|||
} |
|||
|
|||
/** |
|||
* 获取包含指定标签的所有笔记 |
|||
* @param notes 要搜索的笔记列表 |
|||
* @return 按标签分组的笔记 |
|||
*/ |
|||
public List<Note> getNotesWithAnyTag(List<Note> notes) { |
|||
if (notes == null) { |
|||
return List.of(); |
|||
} |
|||
|
|||
return notes.stream() |
|||
.filter(note -> !note.getTags().isEmpty()) |
|||
.collect(Collectors.toList()); |
|||
} |
|||
|
|||
/** |
|||
* 获取没有标签的笔记 |
|||
* @param notes 要搜索的笔记列表 |
|||
* @return 没有标签的笔记列表 |
|||
*/ |
|||
public List<Note> getNotesWithoutTags(List<Note> notes) { |
|||
if (notes == null) { |
|||
return List.of(); |
|||
} |
|||
|
|||
return notes.stream() |
|||
.filter(note -> note.getTags().isEmpty()) |
|||
.collect(Collectors.toList()); |
|||
} |
|||
|
|||
/** |
|||
* 检查笔记是否包含关键词(在标题或内容中) |
|||
*/ |
|||
private boolean noteContainsKeyword(Note note, String lowerKeyword) { |
|||
// 检查标题
|
|||
if (note.getTitle() != null && |
|||
note.getTitle().toLowerCase().contains(lowerKeyword)) { |
|||
return true; |
|||
} |
|||
|
|||
// 检查内容
|
|||
if (note.getContent() != null && |
|||
note.getContent().toLowerCase().contains(lowerKeyword)) { |
|||
return true; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
} |
|||
@ -0,0 +1,343 @@ |
|||
package com.example.service.storage; |
|||
|
|||
import com.example.model.Note; |
|||
|
|||
import java.io.*; |
|||
import java.nio.file.Files; |
|||
import java.nio.file.Path; |
|||
import java.nio.file.Paths; |
|||
import java.nio.file.StandardCopyOption; |
|||
import java.time.LocalDateTime; |
|||
import java.time.format.DateTimeFormatter; |
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
import java.util.Optional; |
|||
import java.util.concurrent.locks.ReadWriteLock; |
|||
import java.util.concurrent.locks.ReentrantReadWriteLock; |
|||
|
|||
/** |
|||
* 简化的JSON文件存储服务实现 |
|||
* 使用简单的文本格式在本地文件系统中存储笔记数据 |
|||
*/ |
|||
public class JsonStorageService implements StorageService { |
|||
|
|||
private static final String DEFAULT_FILE_PATH = "notes.txt"; |
|||
private static final String BACKUP_EXTENSION = ".backup"; |
|||
private static final String SEPARATOR = "===NOTE_SEPARATOR==="; |
|||
|
|||
private final String filePath; |
|||
private final ReadWriteLock lock = new ReentrantReadWriteLock(); |
|||
|
|||
/** |
|||
* 默认构造方法,使用默认文件路径 |
|||
*/ |
|||
public JsonStorageService() { |
|||
this(DEFAULT_FILE_PATH); |
|||
} |
|||
|
|||
/** |
|||
* 带文件路径的构造方法 |
|||
* @param filePath 数据文件路径 |
|||
*/ |
|||
public JsonStorageService(String filePath) { |
|||
this.filePath = filePath; |
|||
|
|||
// 确保数据目录存在
|
|||
createDataDirectoryIfNotExists(); |
|||
|
|||
// 如果文件不存在,创建空文件
|
|||
if (!fileExists()) { |
|||
try { |
|||
saveAll(new ArrayList<>()); |
|||
} catch (StorageException e) { |
|||
System.err.println("初始化存储文件失败: " + e.getMessage()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void saveNote(Note note) throws StorageException { |
|||
if (note == null) { |
|||
throw new StorageException("笔记不能为空"); |
|||
} |
|||
|
|||
lock.writeLock().lock(); |
|||
try { |
|||
List<Note> notes = findAllNotesInternal(); |
|||
|
|||
// 查找是否已存在相同ID的笔记
|
|||
boolean found = false; |
|||
for (int i = 0; i < notes.size(); i++) { |
|||
if (notes.get(i).getId().equals(note.getId())) { |
|||
notes.set(i, note); |
|||
found = true; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
// 如果不存在,添加新笔记
|
|||
if (!found) { |
|||
notes.add(note); |
|||
} |
|||
|
|||
saveAllInternal(notes); |
|||
} finally { |
|||
lock.writeLock().unlock(); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public boolean deleteNote(String id) throws StorageException { |
|||
if (id == null || id.trim().isEmpty()) { |
|||
throw new StorageException("笔记ID不能为空"); |
|||
} |
|||
|
|||
lock.writeLock().lock(); |
|||
try { |
|||
List<Note> notes = findAllNotesInternal(); |
|||
boolean removed = notes.removeIf(note -> note.getId().equals(id)); |
|||
|
|||
if (removed) { |
|||
saveAllInternal(notes); |
|||
} |
|||
|
|||
return removed; |
|||
} finally { |
|||
lock.writeLock().unlock(); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public Optional<Note> findNoteById(String id) throws StorageException { |
|||
if (id == null || id.trim().isEmpty()) { |
|||
return Optional.empty(); |
|||
} |
|||
|
|||
lock.readLock().lock(); |
|||
try { |
|||
List<Note> notes = findAllNotesInternal(); |
|||
return notes.stream() |
|||
.filter(note -> note.getId().equals(id)) |
|||
.findFirst(); |
|||
} finally { |
|||
lock.readLock().unlock(); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public List<Note> findAllNotes() throws StorageException { |
|||
lock.readLock().lock(); |
|||
try { |
|||
return findAllNotesInternal(); |
|||
} finally { |
|||
lock.readLock().unlock(); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void saveAll(List<Note> notes) throws StorageException { |
|||
lock.writeLock().lock(); |
|||
try { |
|||
saveAllInternal(notes); |
|||
} finally { |
|||
lock.writeLock().unlock(); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public boolean noteExists(String id) throws StorageException { |
|||
return findNoteById(id).isPresent(); |
|||
} |
|||
|
|||
@Override |
|||
public long getNotesCount() throws StorageException { |
|||
return findAllNotes().size(); |
|||
} |
|||
|
|||
@Override |
|||
public void clearAll() throws StorageException { |
|||
saveAll(new ArrayList<>()); |
|||
} |
|||
|
|||
@Override |
|||
public void backup(String backupPath) throws StorageException { |
|||
if (backupPath == null || backupPath.trim().isEmpty()) { |
|||
// 使用默认备份路径
|
|||
LocalDateTime now = LocalDateTime.now(); |
|||
String timestamp = now.format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss")); |
|||
backupPath = filePath + "." + timestamp + BACKUP_EXTENSION; |
|||
} |
|||
|
|||
try { |
|||
Path source = Paths.get(filePath); |
|||
Path target = Paths.get(backupPath); |
|||
|
|||
if (Files.exists(source)) { |
|||
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING); |
|||
} else { |
|||
throw new StorageException("源文件不存在: " + filePath); |
|||
} |
|||
} catch (IOException e) { |
|||
throw new StorageException("备份失败: " + e.getMessage(), e); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void restore(String backupPath) throws StorageException { |
|||
if (backupPath == null || backupPath.trim().isEmpty()) { |
|||
throw new StorageException("备份文件路径不能为空"); |
|||
} |
|||
|
|||
try { |
|||
Path source = Paths.get(backupPath); |
|||
Path target = Paths.get(filePath); |
|||
|
|||
if (Files.exists(source)) { |
|||
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING); |
|||
} else { |
|||
throw new StorageException("备份文件不存在: " + backupPath); |
|||
} |
|||
} catch (IOException e) { |
|||
throw new StorageException("恢复失败: " + e.getMessage(), e); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 内部方法:读取所有笔记(不加锁) |
|||
*/ |
|||
private List<Note> findAllNotesInternal() throws StorageException { |
|||
try { |
|||
File file = new File(filePath); |
|||
if (!file.exists() || file.length() == 0) { |
|||
return new ArrayList<>(); |
|||
} |
|||
|
|||
List<Note> notes = new ArrayList<>(); |
|||
try (BufferedReader reader = new BufferedReader(new FileReader(file))) { |
|||
String line; |
|||
Note currentNote = null; |
|||
StringBuilder contentBuilder = new StringBuilder(); |
|||
String field = null; |
|||
|
|||
while ((line = reader.readLine()) != null) { |
|||
if (line.equals(SEPARATOR)) { |
|||
if (currentNote != null) { |
|||
if ("CONTENT".equals(field)) { |
|||
currentNote.setContent(contentBuilder.toString().trim()); |
|||
} |
|||
notes.add(currentNote); |
|||
} |
|||
currentNote = new Note(); |
|||
contentBuilder.setLength(0); |
|||
field = null; |
|||
} else if (line.startsWith("ID:")) { |
|||
if (currentNote != null) { |
|||
currentNote.setId(line.substring(3).trim()); |
|||
} |
|||
field = "ID"; |
|||
} else if (line.startsWith("TITLE:")) { |
|||
if (currentNote != null) { |
|||
currentNote.setTitle(line.substring(6).trim()); |
|||
} |
|||
field = "TITLE"; |
|||
} else if (line.startsWith("TAGS:")) { |
|||
if (currentNote != null) { |
|||
String tagsStr = line.substring(5).trim(); |
|||
if (!tagsStr.isEmpty()) { |
|||
String[] tagArray = tagsStr.split(","); |
|||
for (String tag : tagArray) { |
|||
currentNote.addTag(tag.trim()); |
|||
} |
|||
} |
|||
} |
|||
field = "TAGS"; |
|||
} else if (line.startsWith("CREATED:")) { |
|||
if (currentNote != null) { |
|||
try { |
|||
currentNote.setCreatedAt(LocalDateTime.parse(line.substring(8).trim())); |
|||
} catch (Exception e) { |
|||
currentNote.setCreatedAt(LocalDateTime.now()); |
|||
} |
|||
} |
|||
field = "CREATED"; |
|||
} else if (line.startsWith("UPDATED:")) { |
|||
if (currentNote != null) { |
|||
try { |
|||
currentNote.setUpdatedAt(LocalDateTime.parse(line.substring(8).trim())); |
|||
} catch (Exception e) { |
|||
currentNote.setUpdatedAt(LocalDateTime.now()); |
|||
} |
|||
} |
|||
field = "UPDATED"; |
|||
} else if (line.startsWith("CONTENT:")) { |
|||
field = "CONTENT"; |
|||
contentBuilder.append(line.substring(8)); |
|||
if (line.length() > 8) { |
|||
contentBuilder.append("\n"); |
|||
} |
|||
} else if ("CONTENT".equals(field)) { |
|||
contentBuilder.append(line).append("\n"); |
|||
} |
|||
} |
|||
|
|||
// 处理最后一个笔记
|
|||
if (currentNote != null) { |
|||
if ("CONTENT".equals(field)) { |
|||
currentNote.setContent(contentBuilder.toString().trim()); |
|||
} |
|||
notes.add(currentNote); |
|||
} |
|||
} |
|||
|
|||
return notes; |
|||
} catch (IOException e) { |
|||
throw new StorageException("读取笔记数据失败: " + e.getMessage(), e); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 内部方法:保存所有笔记(不加锁) |
|||
*/ |
|||
private void saveAllInternal(List<Note> notes) throws StorageException { |
|||
try (PrintWriter writer = new PrintWriter(new FileWriter(filePath))) { |
|||
if (notes != null) { |
|||
for (Note note : notes) { |
|||
writer.println("ID:" + note.getId()); |
|||
writer.println("TITLE:" + (note.getTitle() != null ? note.getTitle() : "")); |
|||
writer.println("TAGS:" + String.join(",", note.getTags())); |
|||
writer.println("CREATED:" + note.getCreatedAt()); |
|||
writer.println("UPDATED:" + note.getUpdatedAt()); |
|||
writer.println("CONTENT:" + (note.getContent() != null ? note.getContent() : "")); |
|||
writer.println(SEPARATOR); |
|||
} |
|||
} |
|||
} catch (IOException e) { |
|||
throw new StorageException("保存笔记数据失败: " + e.getMessage(), e); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 检查文件是否存在 |
|||
*/ |
|||
private boolean fileExists() { |
|||
return new File(filePath).exists(); |
|||
} |
|||
|
|||
/** |
|||
* 创建数据目录(如果不存在) |
|||
*/ |
|||
private void createDataDirectoryIfNotExists() { |
|||
File file = new File(filePath); |
|||
File parentDir = file.getParentFile(); |
|||
if (parentDir != null && !parentDir.exists()) { |
|||
parentDir.mkdirs(); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 获取文件路径 |
|||
*/ |
|||
public String getFilePath() { |
|||
return filePath; |
|||
} |
|||
} |
|||
@ -0,0 +1,40 @@ |
|||
package com.example.service.storage; |
|||
|
|||
/** |
|||
* 存储操作异常类 |
|||
* 当存储操作失败时抛出此异常 |
|||
*/ |
|||
public class StorageException extends Exception { |
|||
|
|||
/** |
|||
* 默认构造方法 |
|||
*/ |
|||
public StorageException() { |
|||
super(); |
|||
} |
|||
|
|||
/** |
|||
* 带消息的构造方法 |
|||
* @param message 异常消息 |
|||
*/ |
|||
public StorageException(String message) { |
|||
super(message); |
|||
} |
|||
|
|||
/** |
|||
* 带消息和原因的构造方法 |
|||
* @param message 异常消息 |
|||
* @param cause 异常原因 |
|||
*/ |
|||
public StorageException(String message, Throwable cause) { |
|||
super(message, cause); |
|||
} |
|||
|
|||
/** |
|||
* 带原因的构造方法 |
|||
* @param cause 异常原因 |
|||
*/ |
|||
public StorageException(Throwable cause) { |
|||
super(cause); |
|||
} |
|||
} |
|||
@ -0,0 +1,84 @@ |
|||
package com.example.service.storage; |
|||
|
|||
import com.example.model.Note; |
|||
import java.util.List; |
|||
import java.util.Optional; |
|||
|
|||
/** |
|||
* 存储服务接口 |
|||
* 定义笔记数据的持久化操作 |
|||
*/ |
|||
public interface StorageService { |
|||
|
|||
/** |
|||
* 保存笔记 |
|||
* @param note 要保存的笔记 |
|||
* @throws StorageException 当保存失败时抛出 |
|||
*/ |
|||
void saveNote(Note note) throws StorageException; |
|||
|
|||
/** |
|||
* 根据ID删除笔记 |
|||
* @param id 笔记ID |
|||
* @return 是否删除成功 |
|||
* @throws StorageException 当删除失败时抛出 |
|||
*/ |
|||
boolean deleteNote(String id) throws StorageException; |
|||
|
|||
/** |
|||
* 根据ID查找笔记 |
|||
* @param id 笔记ID |
|||
* @return 找到的笔记,如果不存在则返回Optional.empty() |
|||
* @throws StorageException 当查找失败时抛出 |
|||
*/ |
|||
Optional<Note> findNoteById(String id) throws StorageException; |
|||
|
|||
/** |
|||
* 查找所有笔记 |
|||
* @return 所有笔记的列表 |
|||
* @throws StorageException 当查找失败时抛出 |
|||
*/ |
|||
List<Note> findAllNotes() throws StorageException; |
|||
|
|||
/** |
|||
* 批量保存笔记 |
|||
* @param notes 要保存的笔记列表 |
|||
* @throws StorageException 当保存失败时抛出 |
|||
*/ |
|||
void saveAll(List<Note> notes) throws StorageException; |
|||
|
|||
/** |
|||
* 检查笔记是否存在 |
|||
* @param id 笔记ID |
|||
* @return 笔记是否存在 |
|||
* @throws StorageException 当检查失败时抛出 |
|||
*/ |
|||
boolean noteExists(String id) throws StorageException; |
|||
|
|||
/** |
|||
* 获取笔记总数 |
|||
* @return 笔记总数 |
|||
* @throws StorageException 当获取失败时抛出 |
|||
*/ |
|||
long getNotesCount() throws StorageException; |
|||
|
|||
/** |
|||
* 清空所有笔记 |
|||
* @throws StorageException 当清空失败时抛出 |
|||
*/ |
|||
void clearAll() throws StorageException; |
|||
|
|||
/** |
|||
* 备份数据 |
|||
* @param backupPath 备份文件路径 |
|||
* @throws StorageException 当备份失败时抛出 |
|||
*/ |
|||
void backup(String backupPath) throws StorageException; |
|||
|
|||
/** |
|||
* 从备份恢复数据 |
|||
* @param backupPath 备份文件路径 |
|||
* @throws StorageException 当恢复失败时抛出 |
|||
*/ |
|||
void restore(String backupPath) throws StorageException; |
|||
} |
|||
@ -0,0 +1,158 @@ |
|||
package com.example.test; |
|||
|
|||
import com.example.model.Note; |
|||
import com.example.model.ExportFormat; |
|||
import com.example.service.storage.JsonStorageService; |
|||
import com.example.service.storage.StorageException; |
|||
import com.example.service.export.ExporterFactory; |
|||
import com.example.service.export.Exporter; |
|||
import com.example.service.export.ExportException; |
|||
import com.example.service.search.SearchService; |
|||
|
|||
import java.util.List; |
|||
|
|||
/** |
|||
* 实体类测试程序 |
|||
* 验证各个实体类和服务类的基本功能 |
|||
*/ |
|||
public class EntityTest { |
|||
|
|||
public static void main(String[] args) { |
|||
System.out.println("========================================"); |
|||
System.out.println(" 个人知识管理系统实体类测试"); |
|||
System.out.println("========================================\n"); |
|||
|
|||
try { |
|||
testNoteEntity(); |
|||
testStorageService(); |
|||
testExportService(); |
|||
testSearchService(); |
|||
|
|||
System.out.println("✅ 所有测试通过!"); |
|||
} catch (Exception e) { |
|||
System.err.println("❌ 测试失败: " + e.getMessage()); |
|||
e.printStackTrace(); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 测试Note实体类 |
|||
*/ |
|||
private static void testNoteEntity() { |
|||
System.out.println("🧪 测试Note实体类..."); |
|||
|
|||
// 创建笔记
|
|||
Note note = new Note("Java学习笔记", "面向对象编程的三大特性:封装、继承、多态"); |
|||
note.addTag("编程"); |
|||
note.addTag("Java"); |
|||
note.addTag("学习"); |
|||
|
|||
System.out.println(" 创建笔记: " + note.getTitle()); |
|||
System.out.println(" 笔记ID: " + note.getId()); |
|||
System.out.println(" 标签数量: " + note.getTagCount()); |
|||
System.out.println(" 包含关键词'面向对象': " + note.containsKeyword("面向对象")); |
|||
System.out.println(" 摘要: " + note.getSummary()); |
|||
|
|||
// 测试标签操作
|
|||
note.removeTag("学习"); |
|||
System.out.println(" 移除标签后数量: " + note.getTagCount()); |
|||
|
|||
System.out.println("✅ Note实体类测试通过\n"); |
|||
} |
|||
|
|||
/** |
|||
* 测试存储服务 |
|||
*/ |
|||
private static void testStorageService() throws StorageException { |
|||
System.out.println("🧪 测试存储服务..."); |
|||
|
|||
JsonStorageService storage = new JsonStorageService("test_notes.txt"); |
|||
|
|||
// 清空现有数据
|
|||
storage.clearAll(); |
|||
|
|||
// 创建测试笔记
|
|||
Note note1 = new Note("设计模式", "单例模式确保一个类只有一个实例"); |
|||
note1.addTag("编程"); |
|||
note1.addTag("设计模式"); |
|||
|
|||
Note note2 = new Note("数据结构", "栈是后进先出的数据结构"); |
|||
note2.addTag("编程"); |
|||
note2.addTag("算法"); |
|||
|
|||
// 保存笔记
|
|||
storage.saveNote(note1); |
|||
storage.saveNote(note2); |
|||
|
|||
System.out.println(" 保存了2条笔记"); |
|||
System.out.println(" 笔记总数: " + storage.getNotesCount()); |
|||
|
|||
// 查找笔记
|
|||
List<Note> allNotes = storage.findAllNotes(); |
|||
System.out.println(" 读取到 " + allNotes.size() + " 条笔记"); |
|||
|
|||
// 查找单个笔记
|
|||
var foundNote = storage.findNoteById(note1.getId()); |
|||
System.out.println(" 根据ID查找笔记: " + (foundNote.isPresent() ? "成功" : "失败")); |
|||
|
|||
System.out.println("✅ 存储服务测试通过\n"); |
|||
} |
|||
|
|||
/** |
|||
* 测试导出服务 |
|||
*/ |
|||
private static void testExportService() throws StorageException, ExportException { |
|||
System.out.println("🧪 测试导出服务..."); |
|||
|
|||
// 创建测试笔记
|
|||
Note note = new Note("测试导出", "这是一个用于测试导出功能的笔记"); |
|||
note.addTag("测试"); |
|||
note.addTag("导出"); |
|||
|
|||
// 测试TXT导出
|
|||
Exporter txtExporter = ExporterFactory.createExporter(ExportFormat.TXT); |
|||
txtExporter.export(note, "test_export.txt"); |
|||
System.out.println(" TXT导出: " + txtExporter.getFormatDescription()); |
|||
|
|||
// 测试JSON导出
|
|||
Exporter jsonExporter = ExporterFactory.createExporter(ExportFormat.JSON); |
|||
jsonExporter.export(note, "test_export.json"); |
|||
System.out.println(" JSON导出: " + jsonExporter.getFormatDescription()); |
|||
|
|||
// 测试工厂方法
|
|||
System.out.println(" 支持的格式: " + ExporterFactory.getSupportedFormats().length + " 种"); |
|||
System.out.println(" TXT格式支持: " + ExporterFactory.isFormatSupported("txt")); |
|||
|
|||
System.out.println("✅ 导出服务测试通过\n"); |
|||
} |
|||
|
|||
/** |
|||
* 测试搜索服务 |
|||
*/ |
|||
private static void testSearchService() throws StorageException { |
|||
System.out.println("🧪 测试搜索服务..."); |
|||
|
|||
JsonStorageService storage = new JsonStorageService("test_notes.txt"); |
|||
SearchService searchService = new SearchService(); |
|||
|
|||
List<Note> allNotes = storage.findAllNotes(); |
|||
|
|||
// 按关键词搜索
|
|||
List<Note> keywordResults = searchService.searchByKeyword(allNotes, "设计"); |
|||
System.out.println(" 关键词'设计'搜索结果: " + keywordResults.size() + " 条"); |
|||
|
|||
// 按标签搜索
|
|||
List<Note> tagResults = searchService.searchByTag(allNotes, "编程"); |
|||
System.out.println(" 标签'编程'搜索结果: " + tagResults.size() + " 条"); |
|||
|
|||
// 模糊搜索
|
|||
List<Note> fuzzyResults = searchService.fuzzySearch(allNotes, "模式"); |
|||
System.out.println(" 模糊搜索'模式'结果: " + fuzzyResults.size() + " 条"); |
|||
|
|||
// 无标签笔记
|
|||
List<Note> untaggedNotes = searchService.getNotesWithoutTags(allNotes); |
|||
System.out.println(" 无标签笔记: " + untaggedNotes.size() + " 条"); |
|||
|
|||
System.out.println("✅ 搜索服务测试通过\n"); |
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
{ |
|||
"id": "35e2aab6-dba7-4b78-ae4d-cb123d44059b", |
|||
"title": "测试导出", |
|||
"content": "这是一个用于测试导出功能的笔记", |
|||
"tags": ["测试", "导出"], |
|||
"createdAt": "2025-07-13T23:13:51", |
|||
"updatedAt": "2025-07-13T23:13:51" |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
笔记ID: 35e2aab6-dba7-4b78-ae4d-cb123d44059b |
|||
标题: 测试导出 |
|||
创建时间: 2025-07-13 23:13:51 |
|||
更新时间: 2025-07-13 23:13:51 |
|||
标签: 测试, 导出 |
|||
|
|||
内容: |
|||
---------------------------------------- |
|||
这是一个用于测试导出功能的笔记 |
|||
---------------------------------------- |
|||
@ -0,0 +1,14 @@ |
|||
ID:960b330f-f7d5-4c31-b0fe-ce48d81ae4ed |
|||
TITLE: |
|||
TAGS: |
|||
CREATED:2025-07-13T23:13:51.844655200 |
|||
UPDATED:2025-07-13T23:13:51.844655200 |
|||
CONTENT: |
|||
===NOTE_SEPARATOR=== |
|||
ID:60cf8fb5-05af-4501-bce2-2b2da1cd7219 |
|||
TITLE:数据结构 |
|||
TAGS:编程,算法 |
|||
CREATED:2025-07-13T23:13:51.836669200 |
|||
UPDATED:2025-07-13T23:13:51.836669200 |
|||
CONTENT:栈是后进先出的数据结构 |
|||
===NOTE_SEPARATOR=== |
|||
Loading…
Reference in new issue