diff --git a/w10/ARCHITECTURE_AUDIT.md b/w10/ARCHITECTURE_AUDIT.md new file mode 100644 index 0000000..7ba18e4 --- /dev/null +++ b/w10/ARCHITECTURE_AUDIT.md @@ -0,0 +1,135 @@ +# AI 架构审计:策略模式解耦与封装审查 + +## 一、类签名汇总 + +### 1. Article(数据模型) +```java +public class Article { + private String title; + private String url; + private String content; + private long timestamp; + + public Article(String title, String url) // 防御性参数校验 + public void setTitle(String title) // 防御性参数校验 + public void setUrl(String url) // 防御性参数校验 + // Getter方法 +} +``` + +### 2. ArticleRepository(仓储层) +```java +public class ArticleRepository { + private final List
articles; + + public void add(Article article) // 防御 null + public void addAll(Collection
) // 防御 null 和空集合 + public List
getAll() // 返回不可变视图 + public Article findByTitle(String) // 防御 null + public int count() + public void clear() + public boolean contains(Article) // 防御 null +} +``` + +### 3. ArticleAnalyzer(策略接口) +```java +public interface ArticleAnalyzer { + Map analyze(List
articles); + String getName(); +} +``` + +### 4. ArticleCountAnalyzer(具体策略1) +```java +public class ArticleCountAnalyzer implements ArticleAnalyzer { + public Map analyze(List
); + public String getName(); +} +``` + +### 5. TitleLengthAnalyzer(具体策略2) +```java +public class TitleLengthAnalyzer implements ArticleAnalyzer { + public Map analyze(List
); + public String getName(); +} +``` + +### 6. AnalyzeCommand(命令类) +```java +public class AnalyzeCommand { + private final List
articles; + private final List analyzers; + + public AnalyzeCommand(List
, List); + public void execute(); // 复用策略解析,不存储结果 + public void addAnalyzer(ArticleAnalyzer); +} +``` + +## 二、AI 架构审计请求 + +> 作为 Java 架构师,请审查我的代码设计: +> +> 1. **策略解耦**: +> - ArticleAnalyzer 接口是否定义清晰? +> - 具体策略(ArticleCountAnalyzer、TitleLengthAnalyzer)是否与上下文解耦? +> - 添加新策略是否无需修改现有代码? +> +> 2. **封装性**: +> - ArticleRepository 的内部状态是否得到保护? +> - getAll() 返回不可变视图是否正确? +> - 防御性编程是否过度或不足? +> +> 3. **单一职责**: +> - AnalyzeCommand 是否只负责协调执行,不承担存储职责? +> - 各分析器是否只关注单一分析维度? +> +> 4. **依赖注入**: +> - 依赖是否通过构造函数注入? +> - 是否符合依赖倒置原则? +> +> 请给出改进建议和优化方案。 + +## 三、设计原则应用 + +### 已应用的设计原则 + +| 原则 | 应用位置 | 说明 | +|------|---------|------| +| 单一职责 | ArticleAnalyzer 接口 | 每个分析器只负责一种分析 | +| 开闭原则 | 策略模式 | 添加新分析器无需修改现有代码 | +| 依赖倒置 | AnalyzeCommand | 依赖抽象接口而非具体实现 | +| 里氏替换 | 策略实现 | 所有分析器可互换使用 | + +### 防御性编程 + +| 防御点 | 位置 | 方式 | +|--------|------|------| +| null检查 | Article构造器 | IllegalArgumentException | +| null检查 | Repository.add() | Objects.requireNonNull | +| null检查 | Repository.addAll() | 遍历检查每个元素 | +| 不可变返回 | Repository.getAll() | Collections.unmodifiableList | + +## 四、文件列表 + +- [Article.java](file:///D:\嘻嘻哈哈\Git\java\w10\Article.java) +- [ArticleRepository.java](file:///D:\嘻嘻哈哈\Git\java\w10\ArticleRepository.java) +- [ArticleAnalyzer.java](file:///D:\嘻嘻哈哈\Git\java\w10\ArticleAnalyzer.java) +- [ArticleCountAnalyzer.java](file:///D:\嘻嘻哈哈\Git\java\w10\ArticleCountAnalyzer.java) +- [TitleLengthAnalyzer.java](file:///D:\嘻嘻哈哈\Git\java\w10\TitleLengthAnalyzer.java) +- [AnalyzeCommand.java](file:///D:\嘻嘻哈哈\Git\java\w10\AnalyzeCommand.java) +- [TestMain.java](file:///D:\嘻嘻哈哈\Git\java\w10\TestMain.java) + +## 五、运行方式 + +```bash +# 编译 +javac Article.java ArticleRepository.java ArticleAnalyzer.java \ + ArticleCountAnalyzer.java TitleLengthAnalyzer.java \ + AnalyzeCommand.java TestMain.java + +# 运行 +java TestMain +``` \ No newline at end of file diff --git a/w10/AnalyzeCommand.class b/w10/AnalyzeCommand.class new file mode 100644 index 0000000..98334d5 Binary files /dev/null and b/w10/AnalyzeCommand.class differ diff --git a/w10/AnalyzeCommand.java b/w10/AnalyzeCommand.java new file mode 100644 index 0000000..59191db --- /dev/null +++ b/w10/AnalyzeCommand.java @@ -0,0 +1,85 @@ +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * 分析命令类 + * 复用策略解析但不存储,只输出统计信息 + */ +public class AnalyzeCommand { + private final List
articles; + private final List analyzers; + + /** + * 构造函数,接收文章列表和分析器列表 + * @param articles 要分析的文章列表 + * @param analyzers 分析器列表 + */ + public AnalyzeCommand(List
articles, List analyzers) { + Objects.requireNonNull(articles, "文章列表不能为null"); + Objects.requireNonNull(analyzers, "分析器列表不能为null"); + + this.articles = new ArrayList<>(articles); + this.analyzers = new ArrayList<>(analyzers); + } + + /** + * 执行分析并输出结果(不存储) + */ + public void execute() { + System.out.println("================================================"); + System.out.println(" 文章分析报告"); + System.out.println("================================================"); + System.out.println("分析文章数量: " + articles.size()); + System.out.println("分析器数量: " + analyzers.size()); + System.out.println("------------------------------------------------"); + + for (ArticleAnalyzer analyzer : analyzers) { + System.out.println("\n【" + analyzer.getName() + "】"); + Map result = analyzer.analyze(articles); + + // 输出分析结果(不存储) + for (Map.Entry entry : result.entrySet()) { + if (!"analyzer".equals(entry.getKey())) { + System.out.println(" " + formatKey(entry.getKey()) + ": " + formatValue(entry.getValue())); + } + } + } + + System.out.println("\n================================================"); + System.out.println(" 分析报告结束"); + System.out.println("================================================"); + } + + /** + * 格式化键名 + */ + private String formatKey(String key) { + switch (key) { + case "totalCount": return "文章总数"; + case "averageLength": return "平均标题长度"; + case "maxLength": return "最长标题长度"; + case "minLength": return "最短标题长度"; + default: return key; + } + } + + /** + * 格式化值 + */ + private String formatValue(Object value) { + if (value instanceof Double) { + return String.format("%.2f", value); + } + return value.toString(); + } + + /** + * 添加分析器 + */ + public void addAnalyzer(ArticleAnalyzer analyzer) { + Objects.requireNonNull(analyzer, "分析器不能为null"); + analyzers.add(analyzer); + } +} \ No newline at end of file diff --git a/w10/Article.class b/w10/Article.class new file mode 100644 index 0000000..c68a2dd Binary files /dev/null and b/w10/Article.class differ diff --git a/w10/Article.java b/w10/Article.java new file mode 100644 index 0000000..49f40ba --- /dev/null +++ b/w10/Article.java @@ -0,0 +1,65 @@ +public class Article { + private String title; + private String url; + private String content; + private long timestamp; + + public Article(String title, String url) { + if (title == null || title.trim().isEmpty()) { + throw new IllegalArgumentException("标题不能为空"); + } + if (url == null || url.trim().isEmpty()) { + throw new IllegalArgumentException("URL不能为空"); + } + this.title = title; + this.url = url; + this.timestamp = System.currentTimeMillis(); + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + if (title == null || title.trim().isEmpty()) { + throw new IllegalArgumentException("标题不能为空"); + } + this.title = title; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + if (url == null || url.trim().isEmpty()) { + throw new IllegalArgumentException("URL不能为空"); + } + this.url = url; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public long getTimestamp() { + return timestamp; + } + + public void setTimestamp(long timestamp) { + this.timestamp = timestamp; + } + + @Override + public String toString() { + return "Article{" + + "title='" + title + '\'' + + ", url='" + url + '\'' + + ", timestamp=" + timestamp + + '}'; + } +} \ No newline at end of file diff --git a/w10/ArticleAnalyzer.class b/w10/ArticleAnalyzer.class new file mode 100644 index 0000000..25389bb Binary files /dev/null and b/w10/ArticleAnalyzer.class differ diff --git a/w10/ArticleAnalyzer.java b/w10/ArticleAnalyzer.java new file mode 100644 index 0000000..aa948a4 --- /dev/null +++ b/w10/ArticleAnalyzer.java @@ -0,0 +1,21 @@ +import java.util.List; +import java.util.Map; + +/** + * 文章分析策略接口 + * 策略模式:定义分析算法的接口,不同的分析算法可以独立变化 + */ +public interface ArticleAnalyzer { + /** + * 分析文章并返回统计结果 + * @param articles 文章列表 + * @return 统计结果映射 + */ + Map analyze(List
articles); + + /** + * 获取分析器名称 + * @return 分析器名称 + */ + String getName(); +} \ No newline at end of file diff --git a/w10/ArticleCountAnalyzer.class b/w10/ArticleCountAnalyzer.class new file mode 100644 index 0000000..c18f137 Binary files /dev/null and b/w10/ArticleCountAnalyzer.class differ diff --git a/w10/ArticleCountAnalyzer.java b/w10/ArticleCountAnalyzer.java new file mode 100644 index 0000000..55a2b6a --- /dev/null +++ b/w10/ArticleCountAnalyzer.java @@ -0,0 +1,18 @@ +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class ArticleCountAnalyzer implements ArticleAnalyzer { + @Override + public Map analyze(List
articles) { + Map result = new HashMap<>(); + result.put("analyzer", getName()); + result.put("totalCount", articles.size()); + return result; + } + + @Override + public String getName() { + return "文章数量分析器"; + } +} \ No newline at end of file diff --git a/w10/ArticleRepository.class b/w10/ArticleRepository.class new file mode 100644 index 0000000..4926efc Binary files /dev/null and b/w10/ArticleRepository.class differ diff --git a/w10/ArticleRepository.java b/w10/ArticleRepository.java new file mode 100644 index 0000000..471bf69 --- /dev/null +++ b/w10/ArticleRepository.java @@ -0,0 +1,73 @@ +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +public class ArticleRepository { + private final List
articles; + + public ArticleRepository() { + this.articles = new ArrayList<>(); + } + + /** + * 添加单个文章,防御 null + */ + public void add(Article article) { + Objects.requireNonNull(article, "文章不能为null"); + articles.add(article); + } + + /** + * 添加多个文章,防御 null 和空集合 + */ + public void addAll(Collection
articlesToAdd) { + Objects.requireNonNull(articlesToAdd, "文章集合不能为null"); + + for (Article article : articlesToAdd) { + Objects.requireNonNull(article, "集合中包含null文章"); + articles.add(article); + } + } + + /** + * 获取所有文章的不可变视图 + */ + public List
getAll() { + return Collections.unmodifiableList(articles); + } + + /** + * 根据标题查找文章 + */ + public Article findByTitle(String title) { + Objects.requireNonNull(title, "标题不能为null"); + return articles.stream() + .filter(a -> title.equals(a.getTitle())) + .findFirst() + .orElse(null); + } + + /** + * 获取文章数量 + */ + public int count() { + return articles.size(); + } + + /** + * 清空所有文章 + */ + public void clear() { + articles.clear(); + } + + /** + * 检查是否包含指定文章 + */ + public boolean contains(Article article) { + Objects.requireNonNull(article, "文章不能为null"); + return articles.contains(article); + } +} \ No newline at end of file diff --git a/w10/TestMain.class b/w10/TestMain.class new file mode 100644 index 0000000..a009fb6 Binary files /dev/null and b/w10/TestMain.class differ diff --git a/w10/TestMain.java b/w10/TestMain.java new file mode 100644 index 0000000..f7215b6 --- /dev/null +++ b/w10/TestMain.java @@ -0,0 +1,95 @@ +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class TestMain { + public static void main(String[] args) { + System.out.println("===== 测试 ArticleRepository ====="); + testRepository(); + + System.out.println("\n===== 测试 AnalyzeCommand ====="); + testAnalyzeCommand(); + + System.out.println("\n===== 测试防御性编程 ====="); + testDefensiveProgramming(); + } + + private static void testRepository() { + ArticleRepository repo = new ArticleRepository(); + + Article article1 = new Article("Java编程入门", "https://example.com/java"); + Article article2 = new Article("Python数据分析", "https://example.com/python"); + Article article3 = new Article("JavaScript高级", "https://example.com/js"); + + // 测试添加单个文章 + repo.add(article1); + repo.add(article2); + System.out.println("添加2篇文章后数量: " + repo.count()); + + // 测试添加多个文章 + repo.addAll(Arrays.asList(article3)); + System.out.println("批量添加后数量: " + repo.count()); + + // 测试查找 + Article found = repo.findByTitle("Java编程入门"); + System.out.println("查找结果: " + (found != null ? found.getTitle() : "未找到")); + + // 测试获取所有文章(不可变视图) + List
all = repo.getAll(); + System.out.println("获取文章列表大小: " + all.size()); + } + + private static void testAnalyzeCommand() { + // 创建示例文章 + List
articles = new ArrayList<>(); + articles.add(new Article("Java编程入门教程", "https://example.com/java")); + articles.add(new Article("Python数据分析实战", "https://example.com/python")); + articles.add(new Article("JavaScript高级编程", "https://example.com/js")); + articles.add(new Article("Spring Boot指南", "https://example.com/spring")); + articles.add(new Article("Docker容器化部署", "https://example.com/docker")); + + // 创建分析器列表 + List analyzers = new ArrayList<>(); + analyzers.add(new ArticleCountAnalyzer()); + analyzers.add(new TitleLengthAnalyzer()); + + // 创建并执行分析命令 + AnalyzeCommand command = new AnalyzeCommand(articles, analyzers); + command.execute(); + } + + private static void testDefensiveProgramming() { + ArticleRepository repo = new ArticleRepository(); + + try { + repo.add(null); + System.out.println("ERROR: 应该抛出异常!"); + } catch (NullPointerException e) { + System.out.println("正确: 成功捕获 null 文章异常"); + } + + try { + repo.addAll(null); + System.out.println("ERROR: 应该抛出异常!"); + } catch (NullPointerException e) { + System.out.println("正确: 成功捕获 null 集合异常"); + } + + try { + List
listWithNull = new ArrayList<>(); + listWithNull.add(new Article("Test", "https://test.com")); + listWithNull.add(null); + repo.addAll(listWithNull); + System.out.println("ERROR: 应该抛出异常!"); + } catch (NullPointerException e) { + System.out.println("正确: 成功捕获集合中包含 null 异常"); + } + + try { + new Article(null, "https://test.com"); + System.out.println("ERROR: 应该抛出异常!"); + } catch (IllegalArgumentException e) { + System.out.println("正确: 成功捕获空标题异常"); + } + } +} \ No newline at end of file diff --git a/w10/TitleLengthAnalyzer.class b/w10/TitleLengthAnalyzer.class new file mode 100644 index 0000000..a31a9d2 Binary files /dev/null and b/w10/TitleLengthAnalyzer.class differ diff --git a/w10/TitleLengthAnalyzer.java b/w10/TitleLengthAnalyzer.java new file mode 100644 index 0000000..4f64cc9 --- /dev/null +++ b/w10/TitleLengthAnalyzer.java @@ -0,0 +1,44 @@ +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class TitleLengthAnalyzer implements ArticleAnalyzer { + @Override + public Map analyze(List
articles) { + Map result = new HashMap<>(); + + if (articles.isEmpty()) { + result.put("analyzer", getName()); + result.put("averageLength", 0); + result.put("maxLength", 0); + result.put("minLength", 0); + return result; + } + + int totalLength = articles.stream() + .mapToInt(a -> a.getTitle().length()) + .sum(); + + int maxLength = articles.stream() + .mapToInt(a -> a.getTitle().length()) + .max() + .orElse(0); + + int minLength = articles.stream() + .mapToInt(a -> a.getTitle().length()) + .min() + .orElse(0); + + result.put("analyzer", getName()); + result.put("averageLength", (double) totalLength / articles.size()); + result.put("maxLength", maxLength); + result.put("minLength", minLength); + + return result; + } + + @Override + public String getName() { + return "标题长度分析器"; + } +} \ No newline at end of file diff --git a/w11/ExceptionDemo.class b/w11/ExceptionDemo.class new file mode 100644 index 0000000..4391fad Binary files /dev/null and b/w11/ExceptionDemo.class differ diff --git a/w11/ExceptionDemo.java b/w11/ExceptionDemo.java new file mode 100644 index 0000000..e5f1ab2 --- /dev/null +++ b/w11/ExceptionDemo.java @@ -0,0 +1,54 @@ +import java.util.concurrent.atomic.AtomicInteger; + +public class ExceptionDemo { + public static void main(String[] args) { + System.out.println("===== 测试 URL 校验测试 ====="); + testUrlValidation(); + + System.out.println("\n===== 测试重试机制测试 ====="); + testRetryMechanism(); + } + + private static void testUrlValidation() { + String[] testUrls = { + "https://www.example.com", + "invalid-url", + null + }; + + for (String url : testUrls) { + try { + System.out.println("正在校验 URL: " + url); + UrlValidator.validateUrl(url); + System.out.println("URL 格式有效!"); + } catch (UrlFormatException e) { + System.err.println("错误: " + e.getMessage()); + System.err.println("无效的 URL: " + e.getInvalidUrl()); + } + } + } + + private static void testRetryMechanism() { + // 模拟一个会失败几次的操作 + AtomicInteger attemptCounter = new AtomicInteger(0); + + try { + String result = RetryUtils.executeWithRetry(() -> { + int currentAttempt = attemptCounter.incrementAndGet(); + System.out.println("执行操作... (尝试次数: " + currentAttempt + ")"); + + // 模拟前3次失败,第4次成功 + if (currentAttempt < 4) { + throw new RuntimeException("模拟失败"); + } + + return "操作成功完成!"; + }); + + System.out.println("结果: " + result); + } catch (NetworkException e) { + System.err.println("错误: " + e.getMessage()); + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/w11/NetworkException.class b/w11/NetworkException.class new file mode 100644 index 0000000..31353b0 Binary files /dev/null and b/w11/NetworkException.class differ diff --git a/w11/NetworkException.java b/w11/NetworkException.java new file mode 100644 index 0000000..81dce05 --- /dev/null +++ b/w11/NetworkException.java @@ -0,0 +1,9 @@ +public class NetworkException extends Exception { + public NetworkException(String message) { + super(message); + } + + public NetworkException(String message, Throwable cause) { + super(message, cause); + } +} \ No newline at end of file diff --git a/w11/README.md b/w11/README.md new file mode 100644 index 0000000..2ab467b --- /dev/null +++ b/w11/README.md @@ -0,0 +1,48 @@ +# Java 异常体系设计文档 + +## 1. 异常层次设计 + +### 1.1 未检查异常 (RuntimeException) +- **UrlFormatException**:继承自 RuntimeException,用于处理 URL 格式校验失败 + - 包含无效 URL 的信息 + - 提供有意义的错误消息 + +### 1.2 已检查异常 (Exception) +- **NetworkException**:继承自 Exception,用于处理网络相关问题 + - 支持链式异常 (cause) + - 提供网络操作的上下文信息 + +## 2. 异常设计原则 + +### 2.1 Checked/Unchecked 选择 +- **UrlFormatException (Unchecked)**:因为 URL 格式校验是编程错误,可以通过预检查避免,使用 Unchecked 异常更合理 +- **NetworkException (Checked)**:因为网络问题是不可预测的外部因素,使用 Checked 异常强制调用者处理 + +### 2.2 异常包装 +- 所有异常都保留原始异常信息 (cause) +- 使用链式异常避免根因丢失 +- 提供构造函数支持 cause 链 + +### 2.3 上下文信息 +- UrlFormatException 保存无效的 URL +- NetworkException 支持传递详细的错误信息 +- 异常消息包含足够的上下文帮助调试 + +## 3. 重试机制 + +### 3.1 指数退避策略 +- 基础等待时间:500ms +- 计算公式:`wait = base * 2^attempt` +- 最大重试次数:5次 +- 等待时间示例:500ms, 1000ms, 2000ms, 4000ms, 8000ms + +## 4. AI 架构审计请求 + +> 作为Java架构师,请审查我的异常层次设计:Checked/Unchecked选择是否合理?异常包装是否丢失根因?日志是否包含足够上下文? + +## 5. 文件列表 +- [UrlFormatException.java](file:///D:\嘻嘻哈哈\Git\java\w11\UrlFormatException.java) +- [NetworkException.java](file:///D:\嘻嘻哈哈\Git\java\w11\NetworkException.java) +- [RetryUtils.java](file:///D:\嘻嘻哈哈\Git\java\w11\RetryUtils.java) +- [UrlValidator.java](file:///D:\嘻嘻哈哈\Git\java\w11\UrlValidator.java) +- [ExceptionDemo.java](file:///D:\嘻嘻哈哈\Git\java\w11\ExceptionDemo.java) \ No newline at end of file diff --git a/w11/RetryUtils.class b/w11/RetryUtils.class new file mode 100644 index 0000000..278b07c Binary files /dev/null and b/w11/RetryUtils.class differ diff --git a/w11/RetryUtils.java b/w11/RetryUtils.java new file mode 100644 index 0000000..b1c313b --- /dev/null +++ b/w11/RetryUtils.java @@ -0,0 +1,36 @@ +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +public class RetryUtils { + private static final long BASE_WAIT_MS = 500; + private static final int MAX_ATTEMPTS = 5; + + public static T executeWithRetry(Supplier supplier) throws NetworkException { + int attempt = 0; + while (true) { + try { + return supplier.get(); + } catch (Exception e) { + attempt++; + if (attempt >= MAX_ATTEMPTS) { + throw new NetworkException("重试失败,已达最大重试次数", e); + } + + long waitTime = calculateWaitTime(attempt); + System.out.println("重试失败,等待 " + waitTime + "ms 后重试... (第 " + attempt + " 次尝试)"); + + try { + TimeUnit.MILLISECONDS.sleep(waitTime); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + throw new NetworkException("重试被中断", ie); + } + } + } + } + + private static long calculateWaitTime(int attempt) { + // 指数退避公式:wait = base * 2^attempt + return BASE_WAIT_MS * (long) Math.pow(2, attempt); + } +} \ No newline at end of file diff --git a/w11/UrlFormatException.class b/w11/UrlFormatException.class new file mode 100644 index 0000000..b529038 Binary files /dev/null and b/w11/UrlFormatException.class differ diff --git a/w11/UrlFormatException.java b/w11/UrlFormatException.java new file mode 100644 index 0000000..c0c26e5 --- /dev/null +++ b/w11/UrlFormatException.java @@ -0,0 +1,17 @@ +public class UrlFormatException extends RuntimeException { + private String invalidUrl; + + public UrlFormatException(String message, String invalidUrl) { + super(message); + this.invalidUrl = invalidUrl; + } + + public UrlFormatException(String message, Throwable cause, String invalidUrl) { + super(message, cause); + this.invalidUrl = invalidUrl; + } + + public String getInvalidUrl() { + return invalidUrl; + } +} \ No newline at end of file diff --git a/w11/UrlValidator.class b/w11/UrlValidator.class new file mode 100644 index 0000000..87db143 Binary files /dev/null and b/w11/UrlValidator.class differ diff --git a/w11/UrlValidator.java b/w11/UrlValidator.java new file mode 100644 index 0000000..c16a810 --- /dev/null +++ b/w11/UrlValidator.java @@ -0,0 +1,16 @@ +import java.net.MalformedURLException; +import java.net.URL; + +public class UrlValidator { + public static void validateUrl(String urlStr) { + if (urlStr == null || urlStr.trim().isEmpty()) { + throw new UrlFormatException("URL不能为空", urlStr); + } + + try { + new URL(urlStr); + } catch (MalformedURLException e) { + throw new UrlFormatException("URL格式无效", e, urlStr); + } + } +} \ No newline at end of file diff --git a/w5/Circle.class b/w5/Circle.class new file mode 100644 index 0000000..ff17390 Binary files /dev/null and b/w5/Circle.class differ diff --git a/w5/Circle.java b/w5/Circle.java new file mode 100644 index 0000000..c1a7445 --- /dev/null +++ b/w5/Circle.java @@ -0,0 +1,6 @@ +public class Circle extends Shape { + @Override + public void draw() { + System.out.println("绘制圆形"); + } +} \ No newline at end of file diff --git a/w5/Rectangle.class b/w5/Rectangle.class new file mode 100644 index 0000000..3c8522d Binary files /dev/null and b/w5/Rectangle.class differ diff --git a/w5/Rectangle.java b/w5/Rectangle.java new file mode 100644 index 0000000..b1133b8 --- /dev/null +++ b/w5/Rectangle.java @@ -0,0 +1,6 @@ +public class Rectangle extends Shape { + @Override + public void draw() { + System.out.println("绘制矩形"); + } +} \ No newline at end of file diff --git a/w5/Shape.class b/w5/Shape.class new file mode 100644 index 0000000..5cd038f Binary files /dev/null and b/w5/Shape.class differ diff --git a/w5/Shape.java b/w5/Shape.java new file mode 100644 index 0000000..cb0c768 --- /dev/null +++ b/w5/Shape.java @@ -0,0 +1,5 @@ +public class Shape { + public void draw() { + System.out.println("绘制形状"); + } +} \ No newline at end of file diff --git a/w5/ShapeTest.class b/w5/ShapeTest.class new file mode 100644 index 0000000..56dbf71 Binary files /dev/null and b/w5/ShapeTest.class differ diff --git a/w5/ShapeTest.java b/w5/ShapeTest.java index 287ef36..3b805c4 100644 --- a/w5/ShapeTest.java +++ b/w5/ShapeTest.java @@ -1,4 +1,22 @@ -package PACKAGE_NAME; - public class ShapeTest { -} + public static void drawShape(Shape s) { + s.draw(); + } + + public static void main(String[] args) { + // 创建形状对象 + Shape myShape = new Shape(); + Shape myCircle = new Circle(); + Shape myRectangle = new Rectangle(); + + // 测试 drawShape 方法 + System.out.println("测试 Shape 基类:"); + drawShape(myShape); + + System.out.println("\n测试 Circle 子类:"); + drawShape(myCircle); + + System.out.println("\n测试 Rectangle 子类:"); + drawShape(myRectangle); + } +} \ No newline at end of file diff --git a/w6/Animal.class b/w6/Animal.class new file mode 100644 index 0000000..b05489d Binary files /dev/null and b/w6/Animal.class differ diff --git a/w6/Animal.java b/w6/Animal.java new file mode 100644 index 0000000..b07d6fa --- /dev/null +++ b/w6/Animal.java @@ -0,0 +1,3 @@ +public abstract class Animal { + public abstract void makeSound(); +} \ No newline at end of file diff --git a/w6/AnimalTest.class b/w6/AnimalTest.class new file mode 100644 index 0000000..3f3ae46 Binary files /dev/null and b/w6/AnimalTest.class differ diff --git a/w6/AnimalTest.java b/w6/AnimalTest.java index d0428eb..588bcbb 100644 --- a/w6/AnimalTest.java +++ b/w6/AnimalTest.java @@ -1,4 +1,25 @@ -package PACKAGE_NAME; - public class AnimalTest { -} + public static void main(String[] args) { + // 多态:使用 Animal 类型引用指向子类对象 + Animal dog = new Dog(); + Animal cat = new Cat(); + + System.out.println("===== 测试动物叫声 ====="); + dog.makeSound(); + cat.makeSound(); + + System.out.println("\n===== 测试接口能力 ====="); + // 使用 instanceof 检查对象是否实现了 Swimmable 接口 + if (dog instanceof Swimmable) { + ((Swimmable) dog).swim(); + } else { + System.out.println("这个动物不会游泳"); + } + + if (cat instanceof Swimmable) { + ((Swimmable) cat).swim(); + } else { + System.out.println("这个动物不会游泳"); + } + } +} \ No newline at end of file diff --git a/w6/Cat.class b/w6/Cat.class new file mode 100644 index 0000000..76bbd3b Binary files /dev/null and b/w6/Cat.class differ diff --git a/w6/Cat.java b/w6/Cat.java new file mode 100644 index 0000000..07d0e2a --- /dev/null +++ b/w6/Cat.java @@ -0,0 +1,6 @@ +public class Cat extends Animal { + @Override + public void makeSound() { + System.out.println("猫叫:喵喵喵!"); + } +} \ No newline at end of file diff --git a/w6/Dog.class b/w6/Dog.class new file mode 100644 index 0000000..006d7a0 Binary files /dev/null and b/w6/Dog.class differ diff --git a/w6/Dog.java b/w6/Dog.java new file mode 100644 index 0000000..b0d86cd --- /dev/null +++ b/w6/Dog.java @@ -0,0 +1,11 @@ +public class Dog extends Animal implements Swimmable { + @Override + public void makeSound() { + System.out.println("狗叫:汪汪汪!"); + } + + @Override + public void swim() { + System.out.println("狗在游泳"); + } +} \ No newline at end of file diff --git a/w6/Swimmable.class b/w6/Swimmable.class new file mode 100644 index 0000000..5650772 Binary files /dev/null and b/w6/Swimmable.class differ diff --git a/w6/Swimmable.java b/w6/Swimmable.java new file mode 100644 index 0000000..b7a501e --- /dev/null +++ b/w6/Swimmable.java @@ -0,0 +1,3 @@ +public interface Swimmable { + void swim(); +} \ No newline at end of file diff --git a/w7/Account.class b/w7/Account.class new file mode 100644 index 0000000..bfcab57 Binary files /dev/null and b/w7/Account.class differ diff --git a/w7/Account.java b/w7/Account.java new file mode 100644 index 0000000..fb3efaa --- /dev/null +++ b/w7/Account.java @@ -0,0 +1,19 @@ +public class Account { + private double balance; + + public Account(double initialBalance) { + this.balance = initialBalance; + } + + public void withdraw(double amount) throws InsufficientFundsException { + if (amount > balance) { + throw new InsufficientFundsException(balance); + } + balance -= amount; + System.out.println("取款成功!当前余额:" + balance); + } + + public double getBalance() { + return balance; + } +} \ No newline at end of file diff --git a/w7/AccountTest.class b/w7/AccountTest.class new file mode 100644 index 0000000..863f98a Binary files /dev/null and b/w7/AccountTest.class differ diff --git a/w7/AccountTest.java b/w7/AccountTest.java new file mode 100644 index 0000000..1f0eab4 --- /dev/null +++ b/w7/AccountTest.java @@ -0,0 +1,24 @@ +public class AccountTest { + public static void main(String[] args) { + // 创建账户,初始余额 1000 元 + Account account = new Account(1000.0); + System.out.println("当前账户余额:" + account.getBalance()); + + // 测试取款功能 + System.out.println("\n===== 尝试取款 500 元 ====="); + try { + account.withdraw(500.0); + } catch (InsufficientFundsException e) { + System.out.println("错误:" + e.getMessage()); + System.out.println("当前余额:" + e.getBalance()); + } + + System.out.println("\n===== 尝试取款 700 元 ====="); + try { + account.withdraw(700.0); + } catch (InsufficientFundsException e) { + System.out.println("错误:" + e.getMessage()); + System.out.println("当前余额:" + e.getBalance()); + } + } +} \ No newline at end of file diff --git a/w7/InsufficientFundsException.class b/w7/InsufficientFundsException.class new file mode 100644 index 0000000..486f301 Binary files /dev/null and b/w7/InsufficientFundsException.class differ diff --git a/w7/InsufficientFundsException.java b/w7/InsufficientFundsException.java new file mode 100644 index 0000000..e422129 --- /dev/null +++ b/w7/InsufficientFundsException.java @@ -0,0 +1,12 @@ +public class InsufficientFundsException extends Exception { + private double balance; + + public InsufficientFundsException(double balance) { + super("余额不足,当前余额为:" + balance); + this.balance = balance; + } + + public double getBalance() { + return balance; + } +} \ No newline at end of file diff --git a/w8/Box.class b/w8/Box.class new file mode 100644 index 0000000..95d3acd Binary files /dev/null and b/w8/Box.class differ diff --git a/w8/Box.java b/w8/Box.java new file mode 100644 index 0000000..9a5f7ae --- /dev/null +++ b/w8/Box.java @@ -0,0 +1,26 @@ +public class Box { + private T content; + + public Box() {} + + public Box(T content) { + this.content = content; + } + + public T getContent() { + return content; + } + + public void setContent(T content) { + this.content = content; + } + + public boolean isEmpty() { + return content == null; + } + + @Override + public String toString() { + return "Box{" + "content=" + content + '}'; + } +} \ No newline at end of file diff --git a/w8/GenericDemo.class b/w8/GenericDemo.class new file mode 100644 index 0000000..8ed7147 Binary files /dev/null and b/w8/GenericDemo.class differ diff --git a/w8/GenericDemo.java b/w8/GenericDemo.java new file mode 100644 index 0000000..2c320a7 --- /dev/null +++ b/w8/GenericDemo.java @@ -0,0 +1,51 @@ +import java.util.ArrayList; +import java.util.List; + +public class GenericDemo { + public static void main(String[] args) { + // 测试 Box 泛型类 + System.out.println("===== 测试 Box 泛型类 ====="); + Box stringBox = new Box<>("Hello, Generics!"); + System.out.println(stringBox); + + Box integerBox = new Box<>(100); + System.out.println(integerBox); + + integerBox.setContent(200); + System.out.println("修改后:" + integerBox); + + Box doubleBox = new Box<>(); + System.out.println("空盒子:" + doubleBox); + System.out.println("是否为空:" + doubleBox.isEmpty()); + + // 测试 Pair 泛型类 + System.out.println("\n===== 测试 Pair 泛型类 ====="); + Pair nameAgePair = new Pair<>("张三", 25); + System.out.println(nameAgePair); + System.out.println("姓名:" + nameAgePair.getKey()); + System.out.println("年龄:" + nameAgePair.getValue()); + + Pair countryCapitalPair = new Pair<>("中国", "北京"); + System.out.println(countryCapitalPair); + + // 测试泛型方法 + System.out.println("\n===== 测试泛型方法 ====="); + String[] strArray = {"Java", "Python", "C++", "JavaScript"}; + System.out.print("字符串数组:"); + GenericUtils.printArray(strArray); + + Integer[] intArray = {1, 2, 3, 4, 5}; + System.out.print("原始数组:"); + GenericUtils.printArray(intArray); + + GenericUtils.swap(intArray, 0, 4); + System.out.print("交换后数组:"); + GenericUtils.printArray(intArray); + + List doubleList = new ArrayList<>(); + doubleList.add(1.5); + doubleList.add(2.5); + doubleList.add(3.5); + System.out.println("列表元素数量:" + GenericUtils.countListElements(doubleList)); + } +} \ No newline at end of file diff --git a/w8/GenericUtils.class b/w8/GenericUtils.class new file mode 100644 index 0000000..8721196 Binary files /dev/null and b/w8/GenericUtils.class differ diff --git a/w8/GenericUtils.java b/w8/GenericUtils.java new file mode 100644 index 0000000..c2e2b14 --- /dev/null +++ b/w8/GenericUtils.java @@ -0,0 +1,24 @@ +import java.util.ArrayList; +import java.util.List; + +public class GenericUtils { + // 泛型方法:打印数组元素 + public static void printArray(E[] array) { + for (E element : array) { + System.out.print(element + " "); + } + System.out.println(); + } + + // 泛型方法:交换数组中的两个元素 + public static void swap(T[] array, int i, int j) { + T temp = array[i]; + array[i] = array[j]; + array[j] = temp; + } + + // 泛型方法:计算列表中的元素数量 + public static int countListElements(List list) { + return list.size(); + } +} \ No newline at end of file diff --git a/w8/Pair.class b/w8/Pair.class new file mode 100644 index 0000000..68c441d Binary files /dev/null and b/w8/Pair.class differ diff --git a/w8/Pair.java b/w8/Pair.java new file mode 100644 index 0000000..6b35ffd --- /dev/null +++ b/w8/Pair.java @@ -0,0 +1,30 @@ +public class Pair { + private K key; + private V value; + + public Pair(K key, V value) { + this.key = key; + this.value = value; + } + + public K getKey() { + return key; + } + + public void setKey(K key) { + this.key = key; + } + + public V getValue() { + return value; + } + + public void setValue(V value) { + this.value = value; + } + + @Override + public String toString() { + return "Pair{" + "key=" + key + ", value=" + value + '}'; + } +} \ No newline at end of file diff --git a/w8/README.md b/w8/README.md new file mode 100644 index 0000000..78ca508 --- /dev/null +++ b/w8/README.md @@ -0,0 +1,54 @@ +# Java 泛型类练习 + +本项目包含几个典型的 Java 泛型类练习,展示了泛型的使用方法。 + +## 文件结构 + +1. [Box.java](file:///D:\嘻嘻哈哈\Git\java\w8\Box.java) - 简单的泛型容器类 +2. [Pair.java](file:///D:\嘻嘻哈哈\Git\java\w8\Pair.java) - 存储键值对的泛型类 +3. [GenericUtils.java](file:///D:\嘻嘻哈哈\Git\java\w8\GenericUtils.java) - 包含泛型方法的工具类 +4. [GenericDemo.java](file:///D:\嘻嘻哈哈\Git\java\w8\GenericDemo.java) - 演示程序 +5. README.md - 本文档 + +## 泛型类概述 + +### Box +一个简单的泛型容器类,可以存储任意类型的对象。 +- 使用单个类型参数 T +- 提供构造函数、getter、setter 方法 +- 判断是否为空的方法 + +### Pair +一个存储键值对的泛型类,可以存储两个不同类型的对象。 +- 使用两个类型参数 K 和 V +- 提供构造函数和 getter/setter 方法 + +### GenericUtils +一个包含泛型方法的工具类,展示了如何创建和使用泛型方法。 +- printArray: 打印任意类型数组的元素 +- swap: 交换数组中的任意两个元素 +- countListElements: 计算列表中的元素数量 + +## 编译运行 + +```bash +# 编译所有 Java 文件 +javac Box.java Pair.java GenericUtils.java GenericDemo.java + +# 运行演示程序 +java GenericDemo +``` + +## 运行结果 + +程序会展示泛型类的各种用法,包括: +- Box 类存储不同类型的数据 +- Pair 类存储键值对 +- GenericUtils 的泛型方法的使用 + +## 泛型的主要优点 + +1. **类型安全**:编译时检查,避免运行时类型错误 +2. **代码复用**:一套代码支持多种数据类型 +3. **可读性**:更清晰的代码,更易理解 +4. **避免强制类型转换**:减少代码中的类型转换 \ No newline at end of file diff --git a/w9/README.md b/w9/README.md new file mode 100644 index 0000000..bf0d58f --- /dev/null +++ b/w9/README.md @@ -0,0 +1,127 @@ +# 文章爬虫系统 + +这是一个使用 Maven 创建的 Java 项目,实现了命令行界面的文章爬虫系统。 + +## 项目结构 + +``` +w9/ +├── pom.xml # Maven 配置文件 +├── README.md # 项目文档 +└── src/ + └── main/ + └── java/ + └── com/ + └── crawler/ + ├── Main.java # 主程序入口 + ├── model/ + │ └── Article.java # 文章数据模型 + ├── view/ + │ └── ConsoleView.java # 控制台视图 + ├── command/ + │ ├── Command.java # 命令接口 + │ ├── HelpCommand.java # 帮助命令 + │ ├── ListCommand.java # 列表命令 + │ ├── CrawlCommand.java # 爬取命令 + │ └── ExitCommand.java # 退出命令 + └── controller/ + └── CommandController.java # 命令控制器 +``` + +## 功能特性 + +### 已实现的命令 + +1. **help** - 显示帮助信息 +2. **list** - 显示已抓取的文章列表 +3. **crawl** - 开始抓取文章(目前为存根实现) +4. **exit** - 退出程序 + +### 项目架构 + +- **model**: 数据模型层 - 定义文章数据结构 +- **view**: 视图层 - 处理控制台输出 +- **command**: 命令层 - 实现各种命令 +- **controller**: 控制层 - 管理命令执行 + +## 编译运行 + +### 编译项目 + +```bash +cd w9 +mvn clean compile +``` + +### 运行项目 + +```bash +mvn exec:java +``` + +或者先打包再运行: + +```bash +mvn package +java -jar target/crawler-app-1.0-SNAPSHOT.jar +``` + +## 使用示例 + +``` +================================================ + 欢迎使用文章爬虫系统 +================================================ + +可用命令: + help - 显示帮助信息 + list - 显示已抓取的文章列表 + crawl - 开始抓取文章 + exit - 退出程序 + +请输入命令 > help + +可用命令: + help - 显示帮助信息 + list - 显示已抓取的文章列表 + crawl - 开始抓取文章 + exit - 退出程序 + +请输入命令 > crawl + +正在开始抓取文章... +抓取完成,共抓取了 3 篇文章 + +请输入命令 > list + +已抓取的文章列表: +---------------------------------------- +1. 示例文章1 + URL: https://example.com/article1 +---------------------------------------- +2. 示例文章2 + URL: https://example.com/article2 +---------------------------------------- +3. 示例文章3 + URL: https://example.com/article3 +---------------------------------------- +共 3 篇文章 + +请输入命令 > exit + +感谢使用,再见! +``` + +## 技术栈 + +- Java 8 +- Maven 3.x + +## 扩展功能 + +当前 crawl 命令是存根实现,可以扩展为: + +1. 连接实际的文章网站 +2. 解析 HTML 内容 +3. 提取文章标题、URL、内容等 +4. 保存到数据库或文件 \ No newline at end of file diff --git a/w9/RUNNING.md b/w9/RUNNING.md new file mode 100644 index 0000000..f0916d9 --- /dev/null +++ b/w9/RUNNING.md @@ -0,0 +1,167 @@ +# Maven 项目运行说明 + +## 项目已成功创建并测试完成 + +### 项目信息 + +- **项目类型**: Maven Java 项目 +- **包结构**: com.crawler +- **功能**: 命令行文章爬虫系统 + +### 已实现的包结构 + +``` +w9/ +├── pom.xml # Maven 配置文件 +├── README.md # 详细项目文档 +├── test.bat # Windows 测试脚本 +└── src/ + └── main/ + └── java/ + └── com/ + └── crawler/ + ├── Main.java # 主程序入口,包含命令循环 + ├── model/ + │ └── Article.java # 文章数据模型 + ├── view/ + │ └── ConsoleView.java # 控制台视图(用户界面) + ├── command/ + │ ├── Command.java # 命令接口 + │ ├── HelpCommand.java # help 命令实现 + │ ├── ListCommand.java # list 命令实现 + │ ├── CrawlCommand.java # crawl 命令实现 + │ └── ExitCommand.java # exit 命令实现 + └── controller/ + └── CommandController.java # 命令控制器 +``` + +### 已实现的 4 个命令 + +1. **help** - 显示所有可用命令的帮助信息 +2. **list** - 显示已抓取的文章列表(目前为存根) +3. **crawl** - 开始抓取文章(目前为存根实现,添加3篇示例文章) +4. **exit** - 退出程序 + +### 运行方法 + +#### 方法一:使用 Java 编译器直接运行(已测试) + +```bash +# 1. 编译项目 +cd D:\嘻嘻哈哈\Git\java\w9\src\main\java +javac -d ../../../../target/classes -cp . com/crawler/model/Article.java com/crawler/view/ConsoleView.java com/crawler/command/*.java com/crawler/controller/*.java com/crawler/Main.java + +# 2. 运行程序 +java -cp ../../../../target/classes com.crawler.Main +``` + +#### 方法二:使用 Maven 运行(需要配置 JAVA_HOME) + +```bash +# 设置 JAVA_HOME +set JAVA_HOME=你的Java安装路径 + +# 编译项目 +cd w9 +mvn clean compile + +# 运行程序 +mvn exec:java +``` + +### 测试结果 + +程序已成功运行,所有命令都正常工作: + +``` +================================================ + 欢迎使用文章爬虫系统 +================================================ + +可用命令: + help - 显示帮助信息 + list - 显示已抓取的文章列表 + crawl - 开始抓取文章 + exit - 退出程序 + +请输入命令 > help +可用命令: + help - 显示帮助信息 + list - 显示已抓取的文章列表 + crawl - 开始抓取文章 + exit - 退出程序 + +请输入命令 > list +已抓取的文章列表: +---------------------------------------- +暂无已抓取的文章 +共 0 篇文章 + +请输入命令 > crawl +正在开始抓取文章... +抓取完成,共抓取了 3 篇文章 + +请输入命令 > list +已抓取的文章列表: +---------------------------------------- +1. 示例文章1 + URL: https://example.com/article1 +---------------------------------------- +2. 示例文章2 + URL: https://example.com/article2 +---------------------------------------- +3. 示例文章3 + URL: https://example.com/article3 +---------------------------------------- +共 3 篇文章 + +请输入命令 > exit +感谢使用,再见! +``` + +### 项目架构说明 + +#### MVC 架构模式 + +- **Model (模型层)**: `model/Article.java` - 数据模型 +- **View (视图层)**: `view/ConsoleView.java` - 用户界面输出 +- **Controller (控制层)**: `controller/CommandController.java` - 命令调度 + +#### 命令模式 + +- **Command 接口**: 定义命令的基本结构 +- **具体命令类**: HelpCommand, ListCommand, CrawlCommand, ExitCommand +- **命令控制器**: 管理命令的注册和执行 + +### 扩展建议 + +当前 crawl 命令是存根实现,可以扩展为: + +1. **网络爬虫**: 添加 Jsoup 依赖,实现真实的网页爬取 +2. **数据存储**: 将抓取的文章保存到文件或数据库 +3. **多线程**: 使用 ExecutorService 实现并行爬取 +4. **配置管理**: 添加配置文件管理爬取规则 +5. **日志系统**: 使用 SLF4J 添加日志记录 + +### Maven 依赖配置 + +如需添加 Jsoup 依赖,可以在 pom.xml 中添加: + +```xml + + + org.jsoup + jsoup + 1.17.2 + + +``` + +### 总结 + +✅ Maven 项目结构已创建 +✅ 完整的包结构 (model/view/command/controller) 已实现 +✅ 4 个命令已全部实现并测试通过 +✅ list 命令可展示已抓取的文章 +✅ 命令循环运行正常 +✅ 所有文件已保存在 w9 目录中 \ No newline at end of file diff --git a/w9/pom.xml b/w9/pom.xml new file mode 100644 index 0000000..5373944 --- /dev/null +++ b/w9/pom.xml @@ -0,0 +1,40 @@ + + + 4.0.0 + + com.example + crawler-app + 1.0-SNAPSHOT + jar + + + 1.8 + 1.8 + UTF-8 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 1.8 + 1.8 + + + + org.codehaus.mojo + exec-maven-plugin + 1.6.0 + + com.crawler.Main + + + + + \ No newline at end of file diff --git a/w9/src/main/java/com/crawler/Main.java b/w9/src/main/java/com/crawler/Main.java new file mode 100644 index 0000000..2f8f83c --- /dev/null +++ b/w9/src/main/java/com/crawler/Main.java @@ -0,0 +1,43 @@ +package com.crawler; + +import com.crawler.command.*; +import com.crawler.controller.CommandController; +import com.crawler.model.Article; +import com.crawler.view.ConsoleView; +import java.util.ArrayList; +import java.util.List; +import java.util.Scanner; + +public class Main { + public static void main(String[] args) { + // 初始化组件 + ConsoleView view = new ConsoleView(); + List
articles = new ArrayList<>(); + boolean[] running = {true}; + + // 创建控制器 + CommandController controller = new CommandController(view); + + // 注册命令 + controller.registerCommand(new HelpCommand(view)); + controller.registerCommand(new ListCommand(view, articles)); + controller.registerCommand(new CrawlCommand(view, articles)); + controller.registerCommand(new ExitCommand(view, running)); + + // 显示欢迎信息 + view.showWelcome(); + view.showHelp(); + + // 创建扫描器读取用户输入 + Scanner scanner = new Scanner(System.in); + + // 命令循环 + while (running[0]) { + view.showPrompt(); + String input = scanner.nextLine(); + controller.executeCommand(input); + } + + scanner.close(); + } +} \ No newline at end of file diff --git a/w9/src/main/java/com/crawler/command/Command.java b/w9/src/main/java/com/crawler/command/Command.java new file mode 100644 index 0000000..2450b15 --- /dev/null +++ b/w9/src/main/java/com/crawler/command/Command.java @@ -0,0 +1,6 @@ +package com.crawler.command; + +public interface Command { + String getName(); + void execute(); +} \ No newline at end of file diff --git a/w9/src/main/java/com/crawler/command/CrawlCommand.java b/w9/src/main/java/com/crawler/command/CrawlCommand.java new file mode 100644 index 0000000..4365246 --- /dev/null +++ b/w9/src/main/java/com/crawler/command/CrawlCommand.java @@ -0,0 +1,36 @@ +package com.crawler.command; + +import com.crawler.model.Article; +import com.crawler.view.ConsoleView; +import java.util.List; + +public class CrawlCommand implements Command { + private ConsoleView view; + private List
articles; + + public CrawlCommand(ConsoleView view, List
articles) { + this.view = view; + this.articles = articles; + } + + @Override + public String getName() { + return "crawl"; + } + + @Override + public void execute() { + view.showCrawlStart(); + + // 这里是存根实现,实际可以在这里调用爬虫逻辑 + Article article1 = new Article("示例文章1", "https://example.com/article1"); + Article article2 = new Article("示例文章2", "https://example.com/article2"); + Article article3 = new Article("示例文章3", "https://example.com/article3"); + + articles.add(article1); + articles.add(article2); + articles.add(article3); + + view.showCrawlComplete(articles.size()); + } +} \ No newline at end of file diff --git a/w9/src/main/java/com/crawler/command/ExitCommand.java b/w9/src/main/java/com/crawler/command/ExitCommand.java new file mode 100644 index 0000000..f5b5e9b --- /dev/null +++ b/w9/src/main/java/com/crawler/command/ExitCommand.java @@ -0,0 +1,24 @@ +package com.crawler.command; + +import com.crawler.view.ConsoleView; + +public class ExitCommand implements Command { + private ConsoleView view; + private boolean[] running; + + public ExitCommand(ConsoleView view, boolean[] running) { + this.view = view; + this.running = running; + } + + @Override + public String getName() { + return "exit"; + } + + @Override + public void execute() { + view.showGoodbye(); + running[0] = false; + } +} \ No newline at end of file diff --git a/w9/src/main/java/com/crawler/command/HelpCommand.java b/w9/src/main/java/com/crawler/command/HelpCommand.java new file mode 100644 index 0000000..5cdf753 --- /dev/null +++ b/w9/src/main/java/com/crawler/command/HelpCommand.java @@ -0,0 +1,21 @@ +package com.crawler.command; + +import com.crawler.view.ConsoleView; + +public class HelpCommand implements Command { + private ConsoleView view; + + public HelpCommand(ConsoleView view) { + this.view = view; + } + + @Override + public String getName() { + return "help"; + } + + @Override + public void execute() { + view.showHelp(); + } +} \ No newline at end of file diff --git a/w9/src/main/java/com/crawler/command/ListCommand.java b/w9/src/main/java/com/crawler/command/ListCommand.java new file mode 100644 index 0000000..51b3484 --- /dev/null +++ b/w9/src/main/java/com/crawler/command/ListCommand.java @@ -0,0 +1,25 @@ +package com.crawler.command; + +import com.crawler.model.Article; +import com.crawler.view.ConsoleView; +import java.util.List; + +public class ListCommand implements Command { + private ConsoleView view; + private List
articles; + + public ListCommand(ConsoleView view, List
articles) { + this.view = view; + this.articles = articles; + } + + @Override + public String getName() { + return "list"; + } + + @Override + public void execute() { + view.showArticles(articles); + } +} \ No newline at end of file diff --git a/w9/src/main/java/com/crawler/controller/CommandController.java b/w9/src/main/java/com/crawler/controller/CommandController.java new file mode 100644 index 0000000..f311555 --- /dev/null +++ b/w9/src/main/java/com/crawler/controller/CommandController.java @@ -0,0 +1,39 @@ +package com.crawler.controller; + +import com.crawler.command.Command; +import com.crawler.view.ConsoleView; +import java.util.HashMap; +import java.util.Map; + +public class CommandController { + private Map commands; + private ConsoleView view; + + public CommandController(ConsoleView view) { + this.view = view; + this.commands = new HashMap<>(); + } + + public void registerCommand(Command command) { + commands.put(command.getName(), command); + } + + public void executeCommand(String input) { + String commandName = input.trim().toLowerCase(); + + if (commandName.isEmpty()) { + return; + } + + Command command = commands.get(commandName); + if (command != null) { + command.execute(); + } else { + view.showUnknownCommand(commandName); + } + } + + public boolean hasCommand(String commandName) { + return commands.containsKey(commandName); + } +} \ No newline at end of file diff --git a/w9/src/main/java/com/crawler/model/Article.java b/w9/src/main/java/com/crawler/model/Article.java new file mode 100644 index 0000000..63e63b5 --- /dev/null +++ b/w9/src/main/java/com/crawler/model/Article.java @@ -0,0 +1,55 @@ +package com.crawler.model; + +public class Article { + private String title; + private String url; + private String content; + private long timestamp; + + public Article(String title, String url) { + this.title = title; + this.url = url; + this.timestamp = System.currentTimeMillis(); + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public long getTimestamp() { + return timestamp; + } + + public void setTimestamp(long timestamp) { + this.timestamp = timestamp; + } + + @Override + public String toString() { + return "Article{" + + "title='" + title + '\'' + + ", url='" + url + '\'' + + ", timestamp=" + timestamp + + '}'; + } +} \ No newline at end of file diff --git a/w9/src/main/java/com/crawler/view/ConsoleView.java b/w9/src/main/java/com/crawler/view/ConsoleView.java new file mode 100644 index 0000000..6ea93db --- /dev/null +++ b/w9/src/main/java/com/crawler/view/ConsoleView.java @@ -0,0 +1,60 @@ +package com.crawler.view; + +import com.crawler.model.Article; +import java.util.List; + +public class ConsoleView { + public void showWelcome() { + System.out.println("================================================"); + System.out.println(" 欢迎使用文章爬虫系统"); + System.out.println("================================================"); + System.out.println(); + } + + public void showPrompt() { + System.out.print("请输入命令 > "); + } + + public void showHelp() { + System.out.println("\n可用命令:"); + System.out.println(" help - 显示帮助信息"); + System.out.println(" list - 显示已抓取的文章列表"); + System.out.println(" crawl - 开始抓取文章"); + System.out.println(" exit - 退出程序"); + System.out.println(); + } + + public void showArticles(List
articles) { + System.out.println("\n已抓取的文章列表:"); + System.out.println("----------------------------------------"); + + if (articles.isEmpty()) { + System.out.println("暂无已抓取的文章"); + } else { + for (int i = 0; i < articles.size(); i++) { + Article article = articles.get(i); + System.out.println((i + 1) + ". " + article.getTitle()); + System.out.println(" URL: " + article.getUrl()); + System.out.println("----------------------------------------"); + } + } + System.out.println("共 " + articles.size() + " 篇文章\n"); + } + + public void showCrawlStart() { + System.out.println("\n正在开始抓取文章..."); + } + + public void showCrawlComplete(int count) { + System.out.println("抓取完成,共抓取了 " + count + " 篇文章\n"); + } + + public void showGoodbye() { + System.out.println("\n感谢使用,再见!"); + } + + public void showUnknownCommand(String command) { + System.out.println("未知命令: " + command); + System.out.println("输入 'help' 查看可用命令\n"); + } +} \ No newline at end of file diff --git a/w9/test.bat b/w9/test.bat new file mode 100644 index 0000000..b0a9a20 --- /dev/null +++ b/w9/test.bat @@ -0,0 +1,10 @@ +@echo off +cd /d "%~dp0src\main\java" +echo help > input.txt +echo list >> input.txt +echo crawl >> input.txt +echo list >> input.txt +echo exit >> input.txt + +java -cp ..\..\..\..\target\classes com.crawler.Main < input.txt +del input.txt \ No newline at end of file