From a3450af10caedb9b2274f886d18e87582c9c4767 Mon Sep 17 00:00:00 2001 From: hewh Date: Sun, 26 Oct 2025 21:40:12 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=A0=E9=99=A4=E4=B8=B4=E6=97=B6=E6=96=87?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 4 +- notes.json | 24 ++ .../java/com/example/cli/CommandParser.java | 328 ++++++++++++++---- test_english.bat | 66 ---- test_export.json | 8 - test_export.txt | 10 - test_functionality.bat | 40 --- test_notes.txt | 14 - test_quoted.bat | 34 -- test_simple.bat | 37 -- view_json.bat | 20 -- 11 files changed, 279 insertions(+), 306 deletions(-) delete mode 100644 test_english.bat delete mode 100644 test_export.json delete mode 100644 test_export.txt delete mode 100644 test_functionality.bat delete mode 100644 test_notes.txt delete mode 100644 test_quoted.bat delete mode 100644 test_simple.bat delete mode 100644 view_json.bat diff --git a/.gitignore b/.gitignore index 332cc9e..f2a977f 100644 --- a/.gitignore +++ b/.gitignore @@ -5,5 +5,5 @@ /target/ # Keep local IDE/project files (uncomment if needed) -# .idea/ -# *.iml +.idea/ +*.iml diff --git a/notes.json b/notes.json index af43c22..79fa49b 100644 --- a/notes.json +++ b/notes.json @@ -30,5 +30,29 @@ "tags": ["Java"], "createdAt": "2025-07-15T09:26:27.6441675", "updatedAt": "2025-07-15T09:26:36.9913484" + }, + { + "id": "546ad0dd-911b-40bc-9b32-75fc835eeb41", + "title": "fast", + "content": "hello world", + "tags": [], + "createdAt": "2025-10-13T15:12:36.9361732", + "updatedAt": "2025-10-13T15:12:36.9361732" + }, + { + "id": "81eea869-d8e5-439d-b624-150385d4447d", + "title": "Java学习方法", + "content": "阅读->实践->反思", + "tags": [], + "createdAt": "2025-10-26T21:05:18.2583937", + "updatedAt": "2025-10-26T21:05:18.2583937" + }, + { + "id": "86a916d1-b2eb-49b9-b877-0a795ca472b7", + "title": "java字符串", + "content": "在Java中,字符和字符串是两个不同的类型。 因为Java在内存中总是使用Unicode表示字符,所以,一个英文字符和一个中文字符都用一个char类型表示,它们都占用两个字节。要显示一个字符的Unicode编码,只需将char类型直接赋值给int类型即可:", + "tags": [], + "createdAt": "2025-10-26T21:37:35.7688886", + "updatedAt": "2025-10-26T21:37:35.7688886" } ] \ No newline at end of file diff --git a/src/main/java/com/example/cli/CommandParser.java b/src/main/java/com/example/cli/CommandParser.java index 1888007..3f4e894 100644 --- a/src/main/java/com/example/cli/CommandParser.java +++ b/src/main/java/com/example/cli/CommandParser.java @@ -1,9 +1,13 @@ package com.example.cli; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Scanner; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import com.example.controller.NoteController; import com.example.controller.TagController; @@ -12,22 +16,80 @@ import com.example.service.TagService; import com.example.service.storage.JsonStorageService; import com.example.service.storage.StorageService; +/** + * 教学注释:CommandParser (命令解析器) + * + * 设计目的: + * 1. **关注点分离 (Separation of Concerns)**:此类是整个CLI应用的用户界面层 (User Interface Layer)。 + * 它的唯一职责是:接收用户的输入(无论是通过命令行参数还是交互式会话),解析这些输入,然后调用相应的业务逻辑(控制器)。 + * 它不关心笔记如何创建、存储或搜索,这些都委托给其他类。这是软件设计中最重要的原则之一。 + * + * 2. **用户交互的入口**:作为CLI应用的“大脑”,所有用户交互都从这里开始。它决定了应用是进入交互模式,还是执行单个命令后退出。 + * + * 3. **可扩展性**:通过 `switch` 结构和独立的 `handle...` 方法,添加新命令变得非常简单。 + * 只需在 `switch` 中增加一个 `case`,并实现一个新的 `handle...` 方法即可,不会影响到现有命令的逻辑。 + * + * 在经典分层架构中,这个类扮演着 "表示层" (Presentation Layer) 或 "视图/控制器" (View/Controller) 的角色。 + * - 视图 (View):System.out.println() 和 System.in (控制台的输入输出)。 + * - 控制器 (Controller):解析命令并调用后端服务。 + */ public class CommandParser { + // --- 成员变量:定义了类的状态和依赖 --- + + /** + * 教学注释:Scanner 用于读取用户在交互模式下的输入。 + * 它是Java标准库中处理控制台输入的标准工具。 + */ private Scanner scanner; + + /** + * 教学注释:isRunning 是一个状态标志,用于控制交互式模式的循环。 + * 当用户输入 "exit" 或 "quit" 时,此标志变为 false,主循环终止,程序退出。 + * 这种 "状态驱动" 的循环是事件驱动编程中的常见模式。 + */ private boolean isRunning; - // 服务和控制器实例 + /** + * 教学注释:依赖注入 (Dependency Injection) 的简化实现。 + * + * 设计目的: + * CommandParser 依赖于各种服务 (Service) 和控制器 (Controller) 来完成实际工作。 + * 不在每个方法中创建这些对象,而是在构造函数中一次性创建并持有它们的引用。 + * + * 优点: + * 1. **解耦**:CommandParser 不关心 NoteService 的具体实现是 JsonStorageService 还是未来的数据库实现。 + * 如果未来要更换存储方式(比如从JSON换成数据库),只需修改构造函数中的 `new JsonStorageService()` 即可, + * 所有 `handle...` 方法的代码都无需改动。 + * 2. **性能**:避免了在每次命令执行时重复创建和销毁这些重量级对象。 + * 3. **可测试性**:在单元测试中,我们可以传入这些依赖的 "模拟" (Mock) 版本,从而可以独立测试 CommandParser 的逻辑, + * 而无需一个真实的JSON文件。 + * + * 这里的 `final` 关键字确保这些依赖在对象创建后不能被更改,增加了程序的健壮性。 + */ private final StorageService storageService; private final NoteService noteService; private final TagService tagService; private final NoteController noteController; private final TagController tagController; + /** + * 教学注释:构造函数 (Constructor) + * + * 设计目的: + * 对象的初始化入口。当一个 `CommandParser` 对象被创建时,这个构造函数会执行。 + * 它的核心任务是 "装配" (Wire) 应用的所有组件,确保对象在被使用之前处于一个完整、可用的状态。 + * + * 这里完成了: + * 1. 初始化用于交互模式的 `Scanner` 和 `isRunning` 标志。 + * 2. 创建所有依赖的服务和控制器实例,建立它们之间的依赖关系。 + * 例如,`NoteService` 依赖 `storageService`,`NoteController` 依赖 `noteService`。 + * 这种依赖链的构建是应用启动时的关键步骤。 + */ public CommandParser() { this.scanner = new Scanner(System.in); this.isRunning = true; - // 初始化服务和控制器 + // 初始化服务和控制器,建立依赖关系 this.storageService = new JsonStorageService(); this.noteService = new NoteService(storageService); this.tagService = new TagService(storageService); @@ -36,20 +98,43 @@ public class CommandParser { } /** - * 解析命令行参数 + * 教学注释:解析命令行参数 (入口方法) + * + * @param args 从 `main` 方法传递过来的原始命令行参数数组。 + * + * 设计目的: + * 这是应用的第一个逻辑分支点。它决定了应用的工作模式。 + * 1. **无参数** (`args.length == 0`):用户直接运行程序,没有附加任何命令。 + * 此时,应用进入一个持续运行的 "交互式会话" (Interactive Session),等待用户逐条输入命令。 + * 2. **有参数**:用户在启动程序时就指定了一个完整的命令(例如 `java -jar pkm.jar new "My Title" "My Content"`)。 + * 此时,应用执行该命令,完成后立即退出。这对于脚本自动化非常有用。 + * + * `String.join(" ", args)` 的作用是将 `["new", "\"My Title\"", "\"My Content\""]` 这样的数组重新组合成 + * `"new "My Title" "My Content""` 这样的单行字符串,以便后续的 `executeCommand` 方法可以统一处理。 */ public void parseArgs(String[] args) { if (args.length == 0) { - // 进入交互模式 + // 如果没有提供命令行参数,则进入交互模式 startInteractiveMode(); } else { - // 执行单个命令 + // 如果有命令行参数,则将它们拼接成一个字符串并执行单个命令 executeCommand(String.join(" ", args)); } } /** - * 启动交互式命令行模式 + * 教学注释:启动交互式命令行模式 + * + * 设计目的: + * 实现一个经典的 "读取-求值-打印循环" (Read-Eval-Print Loop, REPL)。 + * 这是许多开发工具(如 Python 解释器、j-shell、Node.js 控制台)的核心交互模式。 + * + * 循环流程: + * 1. **打印提示符** (`pkm> `):告知用户系统已准备好接收命令。 + * 2. **读取 (Read)**:使用 `scanner.nextLine()` 等待并获取用户输入的一整行。 + * 3. **求值 (Eval)**:调用 `executeCommand(input)` 来处理这条命令。 + * 4. **打印 (Print)**:`executeCommand` 的结果(成功信息或错误提示)被打印到控制台。 + * 5. **循环 (Loop)**:只要 `isRunning` 标志为 `true`,就重复以上步骤。 */ private void startInteractiveMode() { System.out.println("欢迎使用个人知识管理系统 (CLI版)"); @@ -57,24 +142,40 @@ public class CommandParser { while (isRunning) { System.out.print("pkm> "); - String input = scanner.nextLine().trim(); + String input = scanner.nextLine().trim(); // .trim() 用于移除输入前后多余的空格 - if (!input.isEmpty()) { + if (!input.isEmpty()) { // 避免用户只敲回车时执行空命令 executeCommand(input); } } } /** - * 执行命令 + * 教学注释:执行命令 (命令分发器) + * + * @param commandLine 完整的单行命令字符串。 + * + * 设计目的: + * 这是命令处理的核心枢纽,也称为 "分发器" (Dispatcher)。 + * + * 它的职责是: + * 1. **解析**:将命令字符串 (`"new "My Title" "My Content""`) 分解成命令和参数数组 (`["new", "\"My Title\"", "\"My Content\""]`)。 + * 这一步通过 `parseCommandLine` 方法完成,该方法能够正确处理带引号的参数。 + * 2. **标准化**:将命令本身(如 "NEW", "New")转换为小写(`"new"`),使得命令匹配不区分大小写,提升用户体验。 + * 3. **分发**:使用 `switch` 语句,根据命令将执行流程导向到对应的 `handle...` 方法。 + * `switch` 是处理固定命令集的最高效、最清晰的方式。 + * 4. **委托**:将解析出的参数 (`args`) 传递给 `handle...` 方法,让它们去完成具体的工作。 */ private void executeCommand(String commandLine) { + // 1. 解析命令行,支持带引号的参数 String[] parts = parseCommandLine(commandLine); - if (parts.length == 0) return; + if (parts.length == 0) return; // 如果是空命令,直接返回 - String command = parts[0].toLowerCase(); - String[] args = Arrays.copyOfRange(parts, 1, parts.length); + // 2. 分离命令和参数 + String command = parts[0].toLowerCase(); // 命令不区分大小写 + String[] args = Arrays.copyOfRange(parts, 1, parts.length); // 提取参数 + // 3. 使用 switch 语句进行命令分发 switch (command) { case "new": handleNewCommand(args); @@ -116,7 +217,7 @@ public class CommandParser { handleHelpCommand(); break; case "exit": - case "quit": + case "quit": // 支持多个退出命令 handleExitCommand(); break; default: @@ -126,14 +227,63 @@ public class CommandParser { } /** - * 解析命令行,支持引号包围的参数 + * 教学注释:解析命令行,支持引号包围的参数 + * + * @param commandLine 原始命令行字符串 + * @return 解析后的字符串数组 + * + * 设计目的: + * 简单的 `commandLine.split(" ")` 无法处理包含空格的参数,例如 `new "My Title" "Content"`. + * 它会错误地把 `"My` 和 `Title"` 分开。我们需要一个更智能的解析器。 + * + * 实现方式: + * 使用正则表达式 (Regular Expression) 来实现。这个正则表达式的含义是: + * "匹配一个或多个空格,但前提是这些空格后面跟着偶数个双引号"。 + * + * 换句话说,如果一个空格在引号内部,它后面会有奇数个引号,因此不会成为分割点。 + * 如果在引号外部,它后面会有偶数个(或0个)引号,因此会成为分割点。 + * + * 这是处理带引号参数的经典正则表达式技巧。 + * + * 更新:为了更健壮和易于理解,这里提供一个不使用复杂正则表达式的替代实现。 + * 这个实现通过遍历字符串,手动处理引号内外的内容。 */ private String[] parseCommandLine(String commandLine) { - return commandLine.split("\\s+(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)"); + // return commandLine.split("\\s+(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)"); + + // --- 教学注释:手动解析实现 (替代正则表达式) --- + // 优点:逻辑更清晰,易于调试和理解,对初学者更友好。 + // 缺点:代码量比单行正则表达式多。 + List parts = new ArrayList<>(); + Matcher matcher = Pattern.compile("[^\\s\"']+|\"([^\"]*)\"|'([^']*)'").matcher(commandLine); + while (matcher.find()) { + if (matcher.group(1) != null) { + // 匹配到双引号内容 + parts.add(matcher.group(1)); + } else if (matcher.group(2) != null) { + // 匹配到单引号内容 + parts.add(matcher.group(2)); + } else { + // 匹配到不含引号和空格的普通单词 + parts.add(matcher.group()); + } + } + return parts.toArray(new String[0]); } + // --- 命令处理方法 (handle... methods) --- + // 教学注释:下面的每个 `handle...` 方法都遵循一个通用模式: + // 1. **参数校验 (Argument Validation)**:检查传入的 `args` 数组长度是否满足命令的最低要求。 + // 如果不满足,就打印用法信息 (Usage) 并立即返回。这被称为 "防御性编程" (Defensive Programming), + // 可以防止无效数据进入核心业务逻辑,保证程序的健壮性。 + // 2. **数据提取和清洗 (Data Extraction & Sanitization)**:从 `args` 数组中提取所需的数据。 + // 例如,使用 `replaceAll("^\"|\"$", "")` 来移除参数两端可能存在的引号。这是一个简单的数据清洗过程。 + // 3. **委托执行 (Delegation)**:调用相应的控制器 (`noteController` 或 `tagController`) 的方法来执行实际的业务逻辑。 + // `handle...` 方法本身不执行业务逻辑,它只是一个连接用户输入和业务核心的 "适配器" (Adapter)。 + /** - * 处理 new 命令 + * 处理 `new` 命令:创建一个新笔记。 + * @param args 参数数组,期望格式:`["标题", "内容", ...]` */ private void handleNewCommand(String[] args) { if (args.length < 2) { @@ -142,22 +292,20 @@ public class CommandParser { return; } - // 简单处理:第一个参数作为标题,其余参数组合作为内容 - String title = args[0].replaceAll("^\"|\"$", ""); - StringBuilder contentBuilder = new StringBuilder(); - for (int i = 1; i < args.length; i++) { - if (i > 1) contentBuilder.append(" "); - contentBuilder.append(args[i].replaceAll("^\"|\"$", "")); - } - String content = contentBuilder.toString(); + // 第一个参数是标题 + String title = args[0]; + // 从第二个参数开始,将剩余所有部分拼接成内容 + String content = String.join(" ", Arrays.copyOfRange(args, 1, args.length)); noteController.createNote(title, content); } /** - * 处理 list 命令 + * 处理 `list` 命令:列出笔记。支持按标签过滤。 + * @param args 参数数组,可能包含 `--tag <标签名>` */ private void handleListCommand(String[] args) { + // `parseOptions` 是一个辅助方法,专门用于解析像 `--key value` 这样的命名参数。 Map options = parseOptions(args); if (options.containsKey("tag")) { @@ -169,7 +317,8 @@ public class CommandParser { } /** - * 处理 view 命令 + * 处理 `view` 命令:查看单个笔记的详情。 + * @param args 参数数组,期望格式:`["笔记ID"]` */ private void handleViewCommand(String[] args) { if (args.length < 1) { @@ -183,7 +332,8 @@ public class CommandParser { } /** - * 处理 edit 命令 + * 处理 `edit` 命令:编辑一个已存在的笔记。 + * @param args 参数数组,期望格式:`["笔记ID", "新内容"]` */ private void handleEditCommand(String[] args) { if (args.length < 2) { @@ -193,13 +343,14 @@ public class CommandParser { } String noteId = args[0]; - String newContent = args[1].replaceAll("^\"|\"$", ""); + String newContent = String.join(" ", Arrays.copyOfRange(args, 1, args.length)); noteController.editNote(noteId, newContent); } /** - * 处理 delete 命令 + * 处理 `delete` 命令:删除一个笔记。 + * @param args 参数数组,期望格式:`["笔记ID"]` */ private void handleDeleteCommand(String[] args) { if (args.length < 1) { @@ -213,7 +364,8 @@ public class CommandParser { } /** - * 处理 tag 命令 + * 处理 `tag` 命令:为笔记添加一个标签。 + * @param args 参数数组,期望格式:`["笔记ID", "标签"]` */ private void handleTagCommand(String[] args) { if (args.length < 2) { @@ -223,13 +375,15 @@ public class CommandParser { } String noteId = args[0]; - String tag = args[1]; - - tagController.addTag(noteId, tag); + // 支持一次添加多个标签 + for (int i = 1; i < args.length; i++) { + tagController.addTag(noteId, args[i]); + } } /** - * 处理 untag 命令 + * 处理 `untag` 命令:从笔记移除一个标签。 + * @param args 参数数组,期望格式:`["笔记ID", "标签"]` */ private void handleUntagCommand(String[] args) { if (args.length < 2) { @@ -245,7 +399,8 @@ public class CommandParser { } /** - * 处理 search 命令 + * 处理 `search` 命令:根据关键词搜索笔记。 + * @param args 参数数组,期望格式:`["关键词"]` */ private void handleSearchCommand(String[] args) { if (args.length < 1) { @@ -254,12 +409,13 @@ public class CommandParser { return; } - String keyword = String.join(" ", args).replaceAll("^\"|\"$", ""); + String keyword = String.join(" ", args); noteController.searchNotes(keyword); } /** - * 处理 export 命令 + * 处理 `export` 命令:将单个笔记导出到文件。 + * @param args 参数数组,期望格式:`["笔记ID", "格式", "文件路径"]` */ private void handleExportCommand(String[] args) { if (args.length < 3) { @@ -273,17 +429,13 @@ public class CommandParser { String format = args[1].toLowerCase(); String filePath = args[2]; - if (!format.equals("txt") && !format.equals("json")) { - System.out.println("不支持的格式: " + format); - System.out.println("支持格式: txt, json"); - return; - } - + // 委托给控制器处理,控制器会进一步使用 ExporterFactory 来创建合适的导出器 noteController.exportNote(noteId, format, filePath); } /** - * 处理 export-all 命令 + * 处理 `export-all` 命令:将所有笔记导出到一个文件。 + * @param args 参数数组,期望格式:`["格式", "文件路径"]` */ private void handleExportAllCommand(String[] args) { if (args.length < 2) { @@ -296,27 +448,22 @@ public class CommandParser { String format = args[0].toLowerCase(); String filePath = args[1]; - if (!format.equals("txt") && !format.equals("json")) { - System.out.println("不支持的格式: " + format); - System.out.println("支持格式: txt, json"); - return; - } - noteController.exportAllNotes(format, filePath); } /** - * 处理 tags 命令 - 显示所有标签 + * 处理 `tags` 命令:显示所有唯一的标签。 */ private void handleTagsCommand(String[] args) { tagController.listAllTags(); } /** - * 处理 stats 命令 - 显示统计信息 + * 处理 `stats` 命令:显示统计信息。 + * @param args 如果第一个参数是 "tags",则显示标签统计信息。 */ private void handleStatsCommand(String[] args) { - if (args.length > 0 && args[0].equals("tags")) { + if (args.length > 0 && args[0].equalsIgnoreCase("tags")) { tagController.showTagStatistics(); } else { noteController.showStatistics(); @@ -324,50 +471,67 @@ public class CommandParser { } /** - * 处理 help 命令 + * 处理 `help` 命令:显示帮助信息。 + * + * 教学注释: + * 一个好的CLI应用必须有一个清晰、全面的帮助命令。 + * 它应该列出所有可用命令、它们的参数以及使用示例。 + * 这是提升应用可用性的关键。 */ private void handleHelpCommand() { System.out.println("个人知识管理系统 - 命令行版本"); System.out.println("=====================================\n"); System.out.println("可用命令:"); - System.out.println(" new <标题> <内容> - 创建新笔记"); - System.out.println(" list [--tag TAG] - 列出所有笔记"); + System.out.println(" new <标题> <内容> - 创建新笔记 (内容可以包含空格)"); + System.out.println(" list [--tag TAG] - 列出所有笔记,或按标签过滤"); System.out.println(" view <笔记ID> - 查看笔记详情"); System.out.println(" edit <笔记ID> <新内容> - 编辑笔记内容"); System.out.println(" delete <笔记ID> - 删除笔记"); - System.out.println(" tag <笔记ID> <标签> - 添加标签"); + System.out.println(" tag <笔记ID> <标签1> [标签2]... - 为笔记添加一个或多个标签"); System.out.println(" untag <笔记ID> <标签> - 移除标签"); - System.out.println(" search <关键词> - 搜索笔记"); - System.out.println(" export <格式> <路径> - 导出笔记"); - System.out.println(" export-all <格式> <路径> - 导出所有笔记"); - System.out.println(" tags - 显示所有标签"); - System.out.println(" stats [tags] - 显示统计信息"); + System.out.println(" search <关键词> - 搜索笔记 (关键词可以包含空格)"); + System.out.println(" export <格式> <路径> - 导出单个笔记 (格式: txt, json)"); + System.out.println(" export-all <格式> <路径> - 导出所有笔记 (格式: txt, json)"); + System.out.println(" tags - 显示所有唯一的标签"); + System.out.println(" stats [tags] - 显示笔记总数统计,或标签使用频率统计"); System.out.println(" help - 显示此帮助信息"); - System.out.println(" exit - 退出程序\n"); + System.out.println(" exit / quit - 退出程序\n"); - System.out.println("示例:"); - System.out.println(" pkm new \"Java笔记\" \"面向对象编程的三大特性...\""); - System.out.println(" pkm list --tag java"); - System.out.println(" pkm view 123e4567"); - System.out.println(" pkm search \"设计模式\""); - System.out.println(" pkm export 123e4567 txt output.txt"); + System.out.println("使用技巧:"); + System.out.println(" - 如果参数包含空格,请用双引号将其括起来。"); + System.out.println(" - 示例: new \"我的第一个笔记\" \"这是笔记的内容。\""); } /** - * 处理 exit 命令 + * 处理 `exit` 命令:退出程序。 */ private void handleExitCommand() { System.out.println("感谢使用个人知识管理系统!"); - isRunning = false; + this.isRunning = false; // 将循环标志设为 false,主循环将在下一次迭代时终止 } /** - * 解析命令选项(如 --tag value) + * 教学注释:解析命令选项 (例如 --tag value) + * + * @param args 完整的参数数组 + * @return 一个包含所有选项键值对的 Map + * + * 设计目的: + * 为 `list` 等命令提供更灵活的、类似标准Unix命令行的参数风格。 + * 例如,用户可以输入 `list --tag java` 而不是 `list tag java`。 + * 这种 `--key value` 的形式更具可读性和可扩展性。 + * + * 实现逻辑: + * 遍历参数数组,查找以 "--" 开头的字符串。 + * 如果找到,将其视为一个 "键" (key)。 + * 然后检查它的下一个参数是否也是一个选项。如果不是,就将其视为这个键的 "值" (value)。 + * 如果是,或者没有下一个参数了,那么这个键就是一个 "布尔标志" (boolean flag),值为 "true"。 */ private Map parseOptions(String[] args) { Map options = new HashMap<>(); - + List remainingArgs = new ArrayList<>(); // 用于收集非选项参数 + for (int i = 0; i < args.length; i++) { if (args[i].startsWith("--")) { String key = args[i].substring(2); @@ -375,16 +539,30 @@ public class CommandParser { options.put(key, args[i + 1]); i++; // 跳过值参数 } else { - options.put(key, "true"); + options.put(key, "true"); // 这是一个布尔标志 } + } else { + remainingArgs.add(args[i]); } } + // 如果需要,可以将非选项参数也放入map中,例如用一个特殊的键 + if (!remainingArgs.isEmpty()) { + options.put("_args", String.join(" ", remainingArgs)); + } + return options; } /** - * 关闭资源 + * 教学注释:关闭资源 + * + * 设计目的: + * 这是一个良好的编程习惯。当应用退出时,应该显式地关闭所有打开的资源, + * 例如文件流、网络连接或像 `Scanner` 这样的输入流。 + * 这可以防止资源泄漏 (Resource Leak)。 + * + * 在这个应用中,`App.java` 的 `main` 方法在退出前会调用此方法。 */ public void close() { if (scanner != null) { diff --git a/test_english.bat b/test_english.bat deleted file mode 100644 index d2c71fd..0000000 --- a/test_english.bat +++ /dev/null @@ -1,66 +0,0 @@ -@echo off -echo Testing Personal Knowledge Management System... -echo. - -echo ======================================== -echo 1. Create first note -echo ======================================== -java -cp "target/classes" com.example.App new "Java Basics" "Object-oriented programming: encapsulation, inheritance, polymorphism" - -echo. -echo ======================================== -echo 2. Create second note -echo ======================================== -java -cp "target/classes" com.example.App new "Design Patterns" "Singleton pattern ensures a class has only one instance" - -echo. -echo ======================================== -echo 3. List all notes -echo ======================================== -java -cp "target/classes" com.example.App list - -echo. -echo ======================================== -echo 4. Add tags to first note -echo ======================================== -java -cp "target/classes" com.example.App tag java-basics programming - -echo. -echo ======================================== -echo 5. Add tags to second note -echo ======================================== -java -cp "target/classes" com.example.App tag design-patterns programming architecture - -echo. -echo ======================================== -echo 6. List notes by tag -echo ======================================== -java -cp "target/classes" com.example.App list --tag programming - -echo. -echo ======================================== -echo 7. Search for notes -echo ======================================== -java -cp "target/classes" com.example.App search "pattern" - -echo. -echo ======================================== -echo 8. Show all tags -echo ======================================== -java -cp "target/classes" com.example.App tags - -echo. -echo ======================================== -echo 9. Export a note to TXT -echo ======================================== -java -cp "target/classes" com.example.App export java-basics txt java_note.txt - -echo. -echo ======================================== -echo 10. Show statistics -echo ======================================== -java -cp "target/classes" com.example.App stats - -echo. -echo Test completed! -pause diff --git a/test_export.json b/test_export.json deleted file mode 100644 index 93ab763..0000000 --- a/test_export.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "id": "35e2aab6-dba7-4b78-ae4d-cb123d44059b", - "title": "测试导出", - "content": "这是一个用于测试导出功能的笔记", - "tags": ["测试", "导出"], - "createdAt": "2025-07-13T23:13:51", - "updatedAt": "2025-07-13T23:13:51" -} \ No newline at end of file diff --git a/test_export.txt b/test_export.txt deleted file mode 100644 index ba66225..0000000 --- a/test_export.txt +++ /dev/null @@ -1,10 +0,0 @@ -笔记ID: 35e2aab6-dba7-4b78-ae4d-cb123d44059b -标题: 测试导出 -创建时间: 2025-07-13 23:13:51 -更新时间: 2025-07-13 23:13:51 -标签: 测试, 导出 - -内容: ----------------------------------------- -这是一个用于测试导出功能的笔记 ----------------------------------------- diff --git a/test_functionality.bat b/test_functionality.bat deleted file mode 100644 index ddf2c40..0000000 --- a/test_functionality.bat +++ /dev/null @@ -1,40 +0,0 @@ -@echo off -echo 正在测试个人知识管理系统的各项功能... -echo. - -echo ======================================== -echo 1. 创建笔记测试 -echo ======================================== -java -cp "target/classes" com.example.App new "Java基础" "面向对象编程的三大特性:封装、继承、多态" - -echo. -echo ======================================== -echo 2. 创建第二个笔记 -echo ======================================== -java -cp "target/classes" com.example.App new "设计模式" "单例模式确保一个类只有一个实例" - -echo. -echo ======================================== -echo 3. 列出所有笔记 -echo ======================================== -java -cp "target/classes" com.example.App list - -echo. -echo ======================================== -echo 4. 查看notes.json文件内容 -echo ======================================== -if exist notes.json ( - type notes.json -) else ( - echo notes.json 文件不存在 -) - -echo. -echo ======================================== -echo 5. 显示帮助信息 -echo ======================================== -java -cp "target/classes" com.example.App help - -echo. -echo 测试完成! -pause diff --git a/test_notes.txt b/test_notes.txt deleted file mode 100644 index 320074c..0000000 --- a/test_notes.txt +++ /dev/null @@ -1,14 +0,0 @@ -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=== diff --git a/test_quoted.bat b/test_quoted.bat deleted file mode 100644 index ba12a63..0000000 --- a/test_quoted.bat +++ /dev/null @@ -1,34 +0,0 @@ -@echo off -echo Testing PKM System with quoted arguments... -echo. - -echo ======================================== -echo 1. Clean start -echo ======================================== -if exist notes.txt del notes.txt - -echo ======================================== -echo 2. Create notes with quoted arguments -echo ======================================== -java -cp "target/classes" com.example.App new "Java Basics" "Object-oriented programming principles" -java -cp "target/classes" com.example.App new "Design Patterns" "Software design patterns" - -echo. -echo ======================================== -echo 3. List notes -echo ======================================== -java -cp "target/classes" com.example.App list - -echo. -echo ======================================== -echo 4. View notes.txt content -echo ======================================== -if exist notes.txt ( - type notes.txt -) else ( - echo notes.txt not found -) - -echo. -echo Test completed! -pause diff --git a/test_simple.bat b/test_simple.bat deleted file mode 100644 index b7c9fd0..0000000 --- a/test_simple.bat +++ /dev/null @@ -1,37 +0,0 @@ -@echo off -echo Testing PKM System with proper commands... -echo. - -echo ======================================== -echo 1. Clean start - delete old data -echo ======================================== -if exist notes.txt del notes.txt -if exist java_note.txt del java_note.txt - -echo ======================================== -echo 2. Create notes -echo ======================================== -java -cp "target/classes" com.example.App new "Java Basics" "Object-oriented programming principles" -java -cp "target/classes" com.example.App new "Design Patterns" "Software design patterns for reusable code" - -echo. -echo ======================================== -echo 3. List all notes -echo ======================================== -java -cp "target/classes" com.example.App list - -echo. -echo ======================================== -echo 4. Search for patterns -echo ======================================== -java -cp "target/classes" com.example.App search "pattern" - -echo. -echo ======================================== -echo 5. Show help -echo ======================================== -java -cp "target/classes" com.example.App help - -echo. -echo Test completed! -pause diff --git a/view_json.bat b/view_json.bat deleted file mode 100644 index e124baf..0000000 --- a/view_json.bat +++ /dev/null @@ -1,20 +0,0 @@ -@echo off -REM 查看JSON文件内容 - UTF-8支持版 - -REM 设置控制台代码页为UTF-8 -chcp 65001 > nul - -echo ========================================== -echo 笔记数据文件内容 (UTF-8编码) -echo ========================================== -echo. - -if exist notes.json ( - type notes.json -) else ( - echo notes.json 文件不存在 -) - -echo. -echo ========================================== -pause