4 changed files with 140 additions and 0 deletions
@ -0,0 +1,63 @@ |
|||||
|
public class AnalyzeCommand implements Command { |
||||
|
|
||||
|
private static final Pattern URL_PATTERN = |
||||
|
Pattern.compile("^(https?://)?([\\w-]+\\.)+[\\w-]+(/[\\w-./?%&=]*)*$"); |
||||
|
|
||||
|
private final ConsoleView view; |
||||
|
private final StrategyFactory strategyFactory; |
||||
|
|
||||
|
public AnalyzeCommand(ConsoleView view, StrategyFactory strategyFactory) { |
||||
|
this.view = view; |
||||
|
this.strategyFactory = strategyFactory; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public String getName() { |
||||
|
return "analyze"; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void execute(String[] args, List<Article> unused) { |
||||
|
|
||||
|
if (args.length < 2) { |
||||
|
view.printError("用法:analyze <url>"); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
String url = args[1]; |
||||
|
|
||||
|
|
||||
|
if (!isValidUrl(url)) { |
||||
|
view.printError("无效的URL格式:" + url); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
|
||||
|
List<Article> parsedArticles = strategyFactory.getStrategy(url).crawl(url); |
||||
|
|
||||
|
|
||||
|
printAnalysisResult(url, parsedArticles); |
||||
|
|
||||
|
} catch (Exception e) { |
||||
|
view.printError("解析失败:" + e.getMessage()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void printAnalysisResult(String url, List<Article> articles) { |
||||
|
view.printInfo("解析统计结果 "); |
||||
|
view.printInfo("目标 URL:" + url); |
||||
|
view.printInfo("解析到文章数量:" + articles.size()); |
||||
|
|
||||
|
if (!articles.isEmpty()) { |
||||
|
Article first = articles.get(0); |
||||
|
view.printInfo("首篇文章标题:" + first.getTitle()); |
||||
|
view.printInfo("首篇文章作者:" + first.getAuthor()); |
||||
|
view.printInfo("首篇发布日期:" + first.getPublishDate()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private boolean isValidUrl(String url) { |
||||
|
return url != null && URL_PATTERN.matcher(url).matches(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,35 @@ |
|||||
|
package w10; |
||||
|
public class ArticleRepository { |
||||
|
private final List<Article> articles = new ArrayList<>(); |
||||
|
|
||||
|
public void add(Article article) { |
||||
|
if (article == null) { |
||||
|
throw new IllegalArgumentException("Article cannot be null"); |
||||
|
} |
||||
|
articles.add(article); |
||||
|
} |
||||
|
|
||||
|
public void addAll(List<Article> newArticles) { |
||||
|
if (newArticles == null) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
for (Article article : newArticles) { |
||||
|
if (article != null) { |
||||
|
articles.add(article); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public List<Article> getAll() { |
||||
|
return Collections.unmodifiableList(articles); |
||||
|
} |
||||
|
|
||||
|
public int size() { |
||||
|
return articles.size(); |
||||
|
} |
||||
|
|
||||
|
public void clear() { |
||||
|
articles.clear(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,30 @@ |
|||||
|
package w11; |
||||
|
|
||||
|
public class RetryUtils { |
||||
|
|
||||
|
|
||||
|
private static final long BASE_DELAY_MS = 500; |
||||
|
|
||||
|
@FunctionalInterface |
||||
|
public interface RetryTask<T> { |
||||
|
T run() throws Exception; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
public static <T> T retry(int maxRetries, RetryTask<T> task) throws Exception { |
||||
|
int attempt = 0; |
||||
|
while (true) { |
||||
|
try { |
||||
|
return task.run(); |
||||
|
} catch (Exception e) { |
||||
|
if (attempt >= maxRetries) { |
||||
|
throw e; |
||||
|
} |
||||
|
|
||||
|
long delay = BASE_DELAY_MS * (1L << attempt); |
||||
|
Thread.sleep(delay); |
||||
|
attempt++; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
package w11; |
||||
|
|
||||
|
|
||||
|
public class UrlFormatException extends RuntimeException { |
||||
|
|
||||
|
public UrlFormatException(String message) { |
||||
|
super(message); |
||||
|
} |
||||
|
|
||||
|
public UrlFormatException(String message, Throwable cause) { |
||||
|
super(message, cause); |
||||
|
} |
||||
Loading…
Reference in new issue