import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Pattern; // ───────────────────────────────────────────── // Command 接口 // ───────────────────────────────────────────── interface Command { String getName(); void execute(String[] args, List
articles); } // ───────────────────────────────────────────── // HelpCommand // ───────────────────────────────────────────── class HelpCommand implements Command { private final ConsoleView view; HelpCommand(ConsoleView v) { this.view = v; } @Override public String getName() { return "help"; } @Override public void execute(String[] args, List
articles) { view.printInfo("可用命令:"); view.printInfo(" crawl 抓取 URL(别名: c)"); view.printInfo(" list 列出所有文章"); view.printInfo(" analyze 策略命中统计"); view.printInfo(" history 历史命令记录"); view.printInfo(" help 显示帮助"); view.printInfo(" exit 退出"); } } // ───────────────────────────────────────────── // ListCommand // ───────────────────────────────────────────── class ListCommand implements Command { private final ConsoleView view; private final ArticleRepository repo; ListCommand(ConsoleView v, ArticleRepository r) { view = v; repo = r; } @Override public String getName() { return "list"; } @Override public void execute(String[] args, List
ignored) { view.display(repo.getAll()); } } // ───────────────────────────────────────────── // CrawlCommand // ───────────────────────────────────────────── class CrawlCommand implements Command { private final ConsoleView view; private final ArticleRepository repo; private final StrategySelector selector; private static final Pattern URL_PAT = Pattern.compile("^https?://[^\\s/$.?#].[^\\s]*$"); CrawlCommand(ConsoleView v, ArticleRepository r, StrategySelector s) { view = v; repo = r; selector = s; } @Override public String getName() { return "crawl"; } public String getAlias() { return "c"; } @Override public void execute(String[] args, List
ignored) { if (args.length < 2 || args[1].isBlank()) { view.printError("用法:crawl "); return; } String url = args[1].trim(); if (!URL_PAT.matcher(url).matches()) { view.printError("URL 格式不合法:" + url); return; } if (repo.findByUrl(url) != null) { view.printWarning("已抓取过,跳过:" + url); return; } ParseStrategy strategy = selector.select(url); if (strategy == null) { view.printError("无可用策略:" + url); return; } Article article = strategy.parse(url); repo.add(article); view.printSuccess("已抓取 [" + strategy.getName() + "]:" + article.getTitle()); } } // ───────────────────────────────────────────── // AnalyzeCommand // ───────────────────────────────────────────── class AnalyzeCommand implements Command { private final ConsoleView view; private final ArticleRepository repo; private final StrategySelector selector; AnalyzeCommand(ConsoleView v, ArticleRepository r, StrategySelector s) { view = v; repo = r; selector = s; } @Override public String getName() { return "analyze"; } @Override public void execute(String[] args, List
ignored) { List
all = repo.getAll(); if (all.isEmpty()) { view.printWarning("暂无文章。"); return; } Map hits = new HashMap<>(); int unmatched = 0; for (Article a : all) { ParseStrategy s = selector.select(a.getUrl()); if (s == null) unmatched++; else hits.merge(s.getName(), 1, Integer::sum); } view.printBold("── 策略分析报告(共 " + all.size() + " 篇)──"); for (ParseStrategy s : selector.getAll()) { view.printInfo(String.format(" %-25s priority=%-4d 命中 %d 篇", s.getName(), s.getPriority(), hits.getOrDefault(s.getName(), 0))); } if (unmatched > 0) view.printWarning("无策略匹配:" + unmatched + " 篇"); } } // ───────────────────────────────────────────── // HistoryCommand // ───────────────────────────────────────────── class HistoryCommand implements Command { private final ConsoleView view; private final List history; HistoryCommand(ConsoleView v, List h) { view = v; history = h; } @Override public String getName() { return "history"; } @Override public void execute(String[] args, List
ignored) { if (history.isEmpty()) { view.printInfo("暂无历史记录。"); return; } view.printBold("── 历史命令(共 " + history.size() + " 条)──"); for (int i = 0; i < history.size(); i++) view.printInfo(String.format("%3d %s", i + 1, history.get(i))); } } // ───────────────────────────────────────────── // ExitCommand // ───────────────────────────────────────────── class ExitCommand implements Command { private final ConsoleView view; ExitCommand(ConsoleView v) { this.view = v; } @Override public String getName() { return "exit"; } @Override public void execute(String[] args, List
ignored) { view.printSuccess("Bye!"); System.exit(0); } }