4 changed files with 660 additions and 0 deletions
@ -0,0 +1,33 @@ |
|||||
|
package com.example; |
||||
|
|
||||
|
import java.io.BufferedWriter; |
||||
|
import java.io.FileWriter; |
||||
|
import java.io.IOException; |
||||
|
import java.util.List; |
||||
|
|
||||
|
/** |
||||
|
* JSON格式保存策略 - 策略模式实现 |
||||
|
*/ |
||||
|
public class JsonSaveStrategy implements SaveStrategy { |
||||
|
@Override |
||||
|
public void save(Object data, String filename) throws IOException { |
||||
|
try (BufferedWriter writer = new BufferedWriter(new FileWriter(filename))) { |
||||
|
if (data instanceof List) { |
||||
|
List<?> list = (List<?>) data; |
||||
|
writer.write("["); |
||||
|
for (int i = 0; i < list.size(); i++) { |
||||
|
if (i > 0) writer.write(","); |
||||
|
writer.write("\"" + list.get(i).toString().replace("\"", "\\\"") + "\""); |
||||
|
} |
||||
|
writer.write("]"); |
||||
|
} else { |
||||
|
writer.write("\"" + data.toString().replace("\"", "\\\"") + "\""); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public String getStrategyName() { |
||||
|
return "JSON格式"; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
package com.example; |
||||
|
|
||||
|
/** |
||||
|
* 数据保存策略接口 - 策略模式 |
||||
|
* 课堂知识点:接口、策略模式 |
||||
|
*/ |
||||
|
public interface SaveStrategy { |
||||
|
void save(Object data, String filename) throws Exception; |
||||
|
String getStrategyName(); |
||||
|
} |
||||
@ -0,0 +1,587 @@ |
|||||
|
package com.example; |
||||
|
|
||||
|
import org.jsoup.Jsoup; |
||||
|
import org.jsoup.nodes.Document; |
||||
|
import org.jsoup.nodes.Element; |
||||
|
import org.jsoup.select.Elements; |
||||
|
|
||||
|
import java.io.BufferedWriter; |
||||
|
import java.io.FileWriter; |
||||
|
import java.io.IOException; |
||||
|
import java.sql.Connection; |
||||
|
import java.sql.PreparedStatement; |
||||
|
import java.sql.SQLException; |
||||
|
import java.util.ArrayList; |
||||
|
import java.util.List; |
||||
|
|
||||
|
/** |
||||
|
* Steam游戏爬虫 |
||||
|
* 课堂知识点:类与对象、封装、继承、多态、集合框架、异常处理、文件IO、字符串处理 |
||||
|
*/ |
||||
|
public class SteamCrawler extends Crawler { |
||||
|
|
||||
|
private static final String USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"; |
||||
|
|
||||
|
/** |
||||
|
* 爬取数据 - 重写父类方法 |
||||
|
* 课堂知识点:方法重写(多态) |
||||
|
*/ |
||||
|
@Override |
||||
|
public void crawl() { |
||||
|
crawlGameInfo("3564740"); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 打印结果 - 重写父类方法 |
||||
|
* 课堂知识点:方法重写(多态) |
||||
|
*/ |
||||
|
@Override |
||||
|
public void printResults() { |
||||
|
printGameInfo(); |
||||
|
} |
||||
|
|
||||
|
// 私有属性 - 课堂知识点:封装
|
||||
|
private String appName; |
||||
|
private String price; |
||||
|
private String releaseDate; |
||||
|
private String reviewSummary; |
||||
|
private List<String> tags; |
||||
|
|
||||
|
/** |
||||
|
* 构造方法 - 初始化游戏标签列表 |
||||
|
*/ |
||||
|
public SteamCrawler() { |
||||
|
this.tags = new ArrayList<>(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 爬取游戏信息 |
||||
|
* @param appId Steam游戏ID |
||||
|
*/ |
||||
|
public void crawlGameInfo(String appId) { |
||||
|
String apiUrl = "https://store.steampowered.com/api/appdetails?appids=" + appId + "&l=schinese&cc=us"; |
||||
|
System.out.println("通过API获取游戏信息: " + apiUrl); |
||||
|
System.out.println("正在连接Steam API..."); |
||||
|
|
||||
|
// 先检查网络状态 - 使用父类方法
|
||||
|
if (!isNetworkAvailable()) { |
||||
|
System.err.println("❌ 网络连接不可用!请检查网络设置"); |
||||
|
System.out.println("使用默认游戏信息..."); |
||||
|
setDefaultValues(); |
||||
|
setSuccess(true); |
||||
|
setDataCount(1); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
// 使用父类的延迟方法
|
||||
|
delay(); |
||||
|
|
||||
|
System.out.println("开始发送HTTP请求..."); |
||||
|
Document doc = HttpCrawler.get(apiUrl); |
||||
|
|
||||
|
System.out.println("成功获取API响应"); |
||||
|
String json = doc.text(); |
||||
|
System.out.println("API返回数据长度: " + (json != null ? json.length() : 0)); |
||||
|
|
||||
|
if (json == null || json.isEmpty()) { |
||||
|
System.err.println("API返回空数据"); |
||||
|
setDefaultValues(); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
System.out.println("开始解析游戏信息..."); |
||||
|
parseGameInfoFromJson(json, appId); |
||||
|
|
||||
|
System.out.println("开始获取评价信息..."); |
||||
|
fetchReviewInfo(appId); |
||||
|
|
||||
|
setSuccess(true); |
||||
|
setDataCount(1); |
||||
|
} catch (java.io.IOException e) { |
||||
|
System.err.println("❌ 请求失败: " + e.getMessage()); |
||||
|
System.out.println("使用默认游戏信息..."); |
||||
|
setDefaultValues(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 设置默认游戏信息(离线备用) |
||||
|
*/ |
||||
|
private void setDefaultValues() { |
||||
|
appName = "燕云十六声"; |
||||
|
price = "免费游玩"; |
||||
|
releaseDate = "2025 年 11 月"; |
||||
|
reviewSummary = "暂无用户评测"; |
||||
|
tags.clear(); |
||||
|
tags.add("动作"); |
||||
|
tags.add("冒险"); |
||||
|
tags.add("角色扮演"); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取评价信息 |
||||
|
* @param appId Steam游戏ID |
||||
|
*/ |
||||
|
private void fetchReviewInfo(String appId) { |
||||
|
String reviewUrl = "https://store.steampowered.com/appreviews/" + appId + "?json=1&language=schinese"; |
||||
|
System.out.println("获取评价信息: " + reviewUrl); |
||||
|
|
||||
|
try { |
||||
|
delay(); |
||||
|
|
||||
|
Document doc = Jsoup.connect(reviewUrl) |
||||
|
.userAgent(USER_AGENT) |
||||
|
.header("Accept", "application/json") |
||||
|
.timeout(15000) |
||||
|
.ignoreContentType(true) |
||||
|
.get(); |
||||
|
|
||||
|
String json = doc.text(); |
||||
|
parseReviewInfo(json); |
||||
|
} catch (Exception e) { |
||||
|
System.err.println("获取评价信息失败: " + e.getMessage()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 解析评价信息 |
||||
|
* @param json JSON字符串 |
||||
|
*/ |
||||
|
private void parseReviewInfo(String json) { |
||||
|
String reviewScoreDesc = extractJsonValue(json, "review_score_desc"); |
||||
|
String totalReviews = extractJsonValue(json, "total_reviews"); |
||||
|
String positive = extractJsonValue(json, "total_positive"); |
||||
|
|
||||
|
if (reviewScoreDesc != null) { |
||||
|
String localizedDesc = translateReviewDesc(reviewScoreDesc); |
||||
|
if (totalReviews != null && !"0".equals(totalReviews)) { |
||||
|
int total = Integer.parseInt(totalReviews); |
||||
|
int pos = positive != null ? Integer.parseInt(positive) : 0; |
||||
|
double positivePercent = total > 0 ? (pos * 100.0 / total) : 0; |
||||
|
reviewSummary = String.format("%s (%.1f%% 好评, %d 条评测)", |
||||
|
localizedDesc, positivePercent, total); |
||||
|
} else { |
||||
|
reviewSummary = localizedDesc; |
||||
|
} |
||||
|
} else if (totalReviews != null && !"0".equals(totalReviews)) { |
||||
|
int total = Integer.parseInt(totalReviews); |
||||
|
int pos = positive != null ? Integer.parseInt(positive) : 0; |
||||
|
double positivePercent = total > 0 ? (pos * 100.0 / total) : 0; |
||||
|
|
||||
|
String ratingText = "褒贬不一"; |
||||
|
// 课堂知识点:if-else条件判断
|
||||
|
if (positivePercent >= 95) ratingText = "好评如潮"; |
||||
|
else if (positivePercent >= 80) ratingText = "特别好评"; |
||||
|
else if (positivePercent >= 70) ratingText = "多半好评"; |
||||
|
else if (positivePercent >= 50) ratingText = "褒贬不一"; |
||||
|
else if (positivePercent >= 30) ratingText = "多半差评"; |
||||
|
else ratingText = "差评如潮"; |
||||
|
|
||||
|
reviewSummary = String.format("%s (%.1f%% 好评, %d 条评测)", |
||||
|
ratingText, positivePercent, total); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 翻译评价描述 |
||||
|
* @param desc 英文描述 |
||||
|
* @return 中文描述 |
||||
|
*/ |
||||
|
private String translateReviewDesc(String desc) { |
||||
|
// 课堂知识点:switch语句
|
||||
|
switch (desc) { |
||||
|
case "Overwhelmingly Positive": return "好评如潮"; |
||||
|
case "Very Positive": return "特别好评"; |
||||
|
case "Positive": return "好评"; |
||||
|
case "Mostly Positive": return "多半好评"; |
||||
|
case "Mixed": return "褒贬不一"; |
||||
|
case "Mostly Negative": return "多半差评"; |
||||
|
case "Negative": return "差评"; |
||||
|
case "Very Negative": return "多半差评"; |
||||
|
case "Overwhelmingly Negative": return "差评如潮"; |
||||
|
case "No user reviews": return "暂无用户评测"; |
||||
|
default: return desc; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 从JSON中解析游戏信息 |
||||
|
* @param json JSON字符串 |
||||
|
* @param appId Steam游戏ID |
||||
|
*/ |
||||
|
private void parseGameInfoFromJson(String json, String appId) { |
||||
|
String[] prefixes = { |
||||
|
appId + ":", |
||||
|
"\"" + appId + "\"" |
||||
|
}; |
||||
|
|
||||
|
int startIndex = -1; |
||||
|
for (String prefix : prefixes) { |
||||
|
startIndex = json.indexOf(prefix); |
||||
|
if (startIndex != -1) { |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (startIndex == -1) { |
||||
|
appName = extractJsonValue(json, "name"); |
||||
|
if (appName == null) { |
||||
|
appName = "未找到游戏"; |
||||
|
price = "暂无价格信息"; |
||||
|
releaseDate = "暂无发行日期"; |
||||
|
reviewSummary = "暂无评价信息"; |
||||
|
return; |
||||
|
} |
||||
|
} else { |
||||
|
json = json.substring(startIndex); |
||||
|
} |
||||
|
|
||||
|
appName = extractJsonValue(json, "name"); |
||||
|
if (appName == null) appName = "未知"; |
||||
|
|
||||
|
String isFree = extractJsonValue(json, "is_free"); |
||||
|
if ("true".equals(isFree)) { |
||||
|
price = "免费游玩"; |
||||
|
} else { |
||||
|
String priceOverview = extractJsonValue(json, "final_formatted"); |
||||
|
if (priceOverview != null) { |
||||
|
price = priceOverview; |
||||
|
} else { |
||||
|
int priceStart = json.indexOf("price_overview"); |
||||
|
if (priceStart != -1) { |
||||
|
String priceSection = json.substring(priceStart); |
||||
|
priceOverview = extractJsonValue(priceSection, "final_formatted"); |
||||
|
price = priceOverview != null ? priceOverview : "暂无价格信息"; |
||||
|
} else { |
||||
|
price = "暂无价格信息"; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
String releaseDateStr = extractJsonValue(json, "date"); |
||||
|
if (releaseDateStr == null) { |
||||
|
int releaseStart = json.indexOf("release_date"); |
||||
|
if (releaseStart != -1) { |
||||
|
String releaseSection = json.substring(releaseStart); |
||||
|
releaseDateStr = extractJsonValue(releaseSection, "date"); |
||||
|
if (releaseDateStr == null) { |
||||
|
String comingSoon = extractJsonValue(releaseSection, "coming_soon"); |
||||
|
releaseDate = "true".equals(comingSoon) ? "即将推出" : "暂无发行日期"; |
||||
|
} |
||||
|
} else { |
||||
|
releaseDate = "暂无发行日期"; |
||||
|
} |
||||
|
} |
||||
|
if (releaseDateStr != null) { |
||||
|
releaseDate = releaseDateStr; |
||||
|
} |
||||
|
|
||||
|
String recommendations = extractJsonValue(json, "total_reviews"); |
||||
|
if (recommendations != null) { |
||||
|
reviewSummary = "总评测数: " + recommendations; |
||||
|
} else { |
||||
|
reviewSummary = "暂无评价信息"; |
||||
|
} |
||||
|
|
||||
|
String genresSection = extractJsonSection(json, "genres"); |
||||
|
if (genresSection != null) { |
||||
|
tags = extractGenres(genresSection); |
||||
|
} |
||||
|
if (tags.isEmpty()) { |
||||
|
String categoriesSection = extractJsonSection(json, "categories"); |
||||
|
if (categoriesSection != null) { |
||||
|
tags = extractCategories(categoriesSection); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 从JSON中提取值 |
||||
|
* @param json JSON字符串 |
||||
|
* @param key 键名 |
||||
|
* @return 值 |
||||
|
*/ |
||||
|
private String extractJsonValue(String json, String key) { |
||||
|
String searchKey = "\"" + key + "\"" + ":"; |
||||
|
int startIndex = json.indexOf(searchKey); |
||||
|
if (startIndex == -1) return null; |
||||
|
|
||||
|
startIndex += searchKey.length(); |
||||
|
while (startIndex < json.length() && (json.charAt(startIndex) == ' ' || json.charAt(startIndex) == '\n')) { |
||||
|
startIndex++; |
||||
|
} |
||||
|
|
||||
|
if (startIndex >= json.length()) return null; |
||||
|
|
||||
|
if (json.charAt(startIndex) == '"') { |
||||
|
startIndex++; |
||||
|
int endIndex = json.indexOf("\"", startIndex); |
||||
|
if (endIndex != -1) { |
||||
|
return json.substring(startIndex, endIndex); |
||||
|
} |
||||
|
} else if (json.charAt(startIndex) == '-' || Character.isDigit(json.charAt(startIndex))) { |
||||
|
int endIndex = startIndex; |
||||
|
while (endIndex < json.length() && (Character.isDigit(json.charAt(endIndex)) || json.charAt(endIndex) == '.' || json.charAt(endIndex) == '-')) { |
||||
|
endIndex++; |
||||
|
} |
||||
|
return json.substring(startIndex, endIndex); |
||||
|
} else if (json.substring(startIndex).startsWith("true")) { |
||||
|
return "true"; |
||||
|
} else if (json.substring(startIndex).startsWith("false")) { |
||||
|
return "false"; |
||||
|
} |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 从JSON中提取数组段 |
||||
|
* @param json JSON字符串 |
||||
|
* @param key 键名 |
||||
|
* @return 数组段 |
||||
|
*/ |
||||
|
private String extractJsonSection(String json, String key) { |
||||
|
String searchKey = "\"" + key + "\"" + ":"; |
||||
|
int startIndex = json.indexOf(searchKey); |
||||
|
if (startIndex == -1) return null; |
||||
|
|
||||
|
startIndex += searchKey.length(); |
||||
|
while (startIndex < json.length() && (json.charAt(startIndex) == ' ' || json.charAt(startIndex) == '\n')) { |
||||
|
startIndex++; |
||||
|
} |
||||
|
|
||||
|
if (startIndex >= json.length() || json.charAt(startIndex) != '[') return null; |
||||
|
|
||||
|
int bracketCount = 1; |
||||
|
int endIndex = startIndex + 1; |
||||
|
while (endIndex < json.length() && bracketCount > 0) { |
||||
|
char c = json.charAt(endIndex); |
||||
|
if (c == '[') bracketCount++; |
||||
|
else if (c == ']') bracketCount--; |
||||
|
endIndex++; |
||||
|
} |
||||
|
|
||||
|
return json.substring(startIndex, endIndex); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 提取游戏类型 |
||||
|
* @param genresSection JSON数组段 |
||||
|
* @return 类型列表 |
||||
|
*/ |
||||
|
private List<String> extractGenres(String genresSection) { |
||||
|
List<String> genreList = new ArrayList<>(); |
||||
|
int startIndex = 0; |
||||
|
while (startIndex < genresSection.length() && genreList.size() < 10) { |
||||
|
int descIndex = genresSection.indexOf("description\"", startIndex); |
||||
|
if (descIndex == -1) break; |
||||
|
|
||||
|
int colonIndex = genresSection.indexOf(":", descIndex); |
||||
|
if (colonIndex == -1) break; |
||||
|
|
||||
|
int quoteIndex = genresSection.indexOf("\"", colonIndex + 1); |
||||
|
if (quoteIndex == -1) break; |
||||
|
|
||||
|
int endQuoteIndex = genresSection.indexOf("\"", quoteIndex + 1); |
||||
|
if (endQuoteIndex == -1) break; |
||||
|
|
||||
|
String genre = genresSection.substring(quoteIndex + 1, endQuoteIndex); |
||||
|
genreList.add(genre); |
||||
|
startIndex = endQuoteIndex + 1; |
||||
|
} |
||||
|
return genreList; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 提取游戏分类 |
||||
|
* @param categoriesSection JSON数组段 |
||||
|
* @return 分类列表 |
||||
|
*/ |
||||
|
private List<String> extractCategories(String categoriesSection) { |
||||
|
List<String> categoryList = new ArrayList<>(); |
||||
|
int startIndex = 0; |
||||
|
while (startIndex < categoriesSection.length() && categoryList.size() < 10) { |
||||
|
int descIndex = categoriesSection.indexOf("description\"", startIndex); |
||||
|
if (descIndex == -1) break; |
||||
|
|
||||
|
int colonIndex = categoriesSection.indexOf(":", descIndex); |
||||
|
if (colonIndex == -1) break; |
||||
|
|
||||
|
int quoteIndex = categoriesSection.indexOf("\"", colonIndex + 1); |
||||
|
if (quoteIndex == -1) break; |
||||
|
|
||||
|
int endQuoteIndex = categoriesSection.indexOf("\"", quoteIndex + 1); |
||||
|
if (endQuoteIndex == -1) break; |
||||
|
|
||||
|
String category = categoriesSection.substring(quoteIndex + 1, endQuoteIndex); |
||||
|
categoryList.add(category); |
||||
|
startIndex = endQuoteIndex + 1; |
||||
|
} |
||||
|
return categoryList; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 通过API搜索游戏 |
||||
|
* @param gameName 游戏名称 |
||||
|
* @return App ID |
||||
|
*/ |
||||
|
public String searchGameByApi(String gameName) { |
||||
|
String apiUrl = "https://store.steampowered.com/api/storesearch/?term=" + |
||||
|
gameName.replace(" ", "%20") + "&l=schinese&cc=us"; |
||||
|
System.out.println("通过API搜索游戏: " + apiUrl); |
||||
|
|
||||
|
try { |
||||
|
delay(); |
||||
|
|
||||
|
Document doc = Jsoup.connect(apiUrl) |
||||
|
.userAgent(USER_AGENT) |
||||
|
.header("Accept", "application/json") |
||||
|
.timeout(15000) |
||||
|
.ignoreContentType(true) |
||||
|
.get(); |
||||
|
|
||||
|
String json = doc.text(); |
||||
|
if (json.contains("appid")) { |
||||
|
int appidIndex = json.indexOf("appid"); |
||||
|
if (appidIndex != -1) { |
||||
|
int colonIndex = json.indexOf(":", appidIndex); |
||||
|
if (colonIndex != -1) { |
||||
|
int start = colonIndex + 1; |
||||
|
while (start < json.length() && (json.charAt(start) == ' ' || json.charAt(start) == '\n')) { |
||||
|
start++; |
||||
|
} |
||||
|
int end = start; |
||||
|
while (end < json.length() && Character.isDigit(json.charAt(end))) { |
||||
|
end++; |
||||
|
} |
||||
|
if (end > start) { |
||||
|
return json.substring(start, end); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} catch (Exception e) { |
||||
|
System.err.println("API搜索失败: " + e.getMessage()); |
||||
|
} |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 通过网页搜索游戏 |
||||
|
* @param gameName 游戏名称 |
||||
|
* @return App ID |
||||
|
*/ |
||||
|
public String searchGame(String gameName) { |
||||
|
String searchUrl = "https://store.steampowered.com/search/?term=" + |
||||
|
gameName.replace(" ", "+") + "&cc=us&l=schinese"; |
||||
|
System.out.println("搜索游戏: " + searchUrl); |
||||
|
|
||||
|
try { |
||||
|
delay(); |
||||
|
|
||||
|
Document doc = Jsoup.connect(searchUrl) |
||||
|
.userAgent(USER_AGENT) |
||||
|
.header("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8") |
||||
|
.timeout(15000) |
||||
|
.get(); |
||||
|
|
||||
|
Elements results = doc.select("a.search_result_row"); |
||||
|
for (Element result : results) { |
||||
|
Element titleElement = result.selectFirst("span.title"); |
||||
|
if (titleElement != null) { |
||||
|
String titleText = titleElement.text().toLowerCase(); |
||||
|
if (titleText.contains("winds") || titleText.contains("燕云")) { |
||||
|
String href = result.attr("href"); |
||||
|
String appId = href.replaceAll(".*\\/app\\/(\\d+).*", "$1"); |
||||
|
System.out.println("找到匹配游戏: " + titleElement.text() + " (App ID: " + appId + ")"); |
||||
|
return appId; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (!results.isEmpty()) { |
||||
|
Element firstResult = results.first(); |
||||
|
String href = firstResult.attr("href"); |
||||
|
String appId = href.replaceAll(".*\\/app\\/(\\d+).*", "$1"); |
||||
|
Element titleElement = firstResult.selectFirst("span.title"); |
||||
|
System.out.println("返回第一个搜索结果: " + (titleElement != null ? titleElement.text() : "未知") + " (App ID: " + appId + ")"); |
||||
|
return appId; |
||||
|
} |
||||
|
} catch (Exception e) { |
||||
|
System.err.println("网页搜索失败: " + e.getMessage()); |
||||
|
} |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 打印游戏信息 - 课堂知识点:视图展示(MVC中的View) |
||||
|
*/ |
||||
|
public void printGameInfo() { |
||||
|
System.out.println("\n========== Steam游戏信息 =========="); |
||||
|
System.out.println("游戏名称: " + appName); |
||||
|
System.out.println("价格: " + price); |
||||
|
System.out.println("发行日期: " + releaseDate); |
||||
|
System.out.println("好评率: " + reviewSummary); |
||||
|
System.out.println("游戏标签: " + String.join(", ", tags)); |
||||
|
System.out.println("==================================="); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 保存游戏数据到文件 - 课堂知识点:文件IO |
||||
|
* @param filename 文件名 |
||||
|
*/ |
||||
|
public void saveToFile(String filename) { |
||||
|
try (BufferedWriter writer = new BufferedWriter(new FileWriter(filename))) { |
||||
|
writer.write("游戏名称: " + appName); |
||||
|
writer.newLine(); |
||||
|
writer.write("价格: " + price); |
||||
|
writer.newLine(); |
||||
|
writer.write("发行日期: " + releaseDate); |
||||
|
writer.newLine(); |
||||
|
writer.write("好评率: " + reviewSummary); |
||||
|
writer.newLine(); |
||||
|
writer.write("游戏标签: " + String.join(", ", tags)); |
||||
|
writer.newLine(); |
||||
|
|
||||
|
System.out.println("✅ 游戏数据已保存到文件: " + filename); |
||||
|
} catch (IOException e) { |
||||
|
System.err.println("保存游戏数据失败: " + e.getMessage()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// getter方法 - 课堂知识点:封装的访问接口
|
||||
|
public String getAppName() { return appName; } |
||||
|
public String getPrice() { return price; } |
||||
|
public String getReleaseDate() { return releaseDate; } |
||||
|
public String getReviewSummary() { return reviewSummary; } |
||||
|
public List<String> getTags() { return tags; } |
||||
|
|
||||
|
/** |
||||
|
* 保存游戏数据到数据库 - 课堂知识点:JDBC、数据库持久化 |
||||
|
*/ |
||||
|
@Override |
||||
|
public void saveToDatabase() { |
||||
|
String sql = |
||||
|
"INSERT INTO games (name, price, discount, originalPrice, releaseDate, tags, reviewScore, crawlTime) " + |
||||
|
"VALUES (?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)"; |
||||
|
|
||||
|
try (Connection conn = DatabaseManager.getInstance().getConnection(); |
||||
|
PreparedStatement pstmt = conn.prepareStatement(sql)) { |
||||
|
|
||||
|
pstmt.setString(1, appName); |
||||
|
pstmt.setString(2, price); |
||||
|
pstmt.setString(3, null); // discount
|
||||
|
pstmt.setString(4, null); // originalPrice
|
||||
|
pstmt.setString(5, releaseDate); |
||||
|
pstmt.setString(6, String.join(", ", tags)); |
||||
|
pstmt.setString(7, reviewSummary); |
||||
|
|
||||
|
int rowsAffected = pstmt.executeUpdate(); |
||||
|
if (rowsAffected > 0) { |
||||
|
System.out.println("✅ 游戏数据已保存到数据库"); |
||||
|
} |
||||
|
} catch (SQLException e) { |
||||
|
System.err.println("❌ 保存游戏数据到数据库失败: " + e.getMessage()); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,30 @@ |
|||||
|
package com.example; |
||||
|
|
||||
|
import java.io.BufferedWriter; |
||||
|
import java.io.FileWriter; |
||||
|
import java.io.IOException; |
||||
|
import java.util.List; |
||||
|
|
||||
|
/** |
||||
|
* 文本格式保存策略 - 策略模式实现 |
||||
|
*/ |
||||
|
public class TextSaveStrategy implements SaveStrategy { |
||||
|
@Override |
||||
|
public void save(Object data, String filename) throws IOException { |
||||
|
try (BufferedWriter writer = new BufferedWriter(new FileWriter(filename))) { |
||||
|
if (data instanceof List) { |
||||
|
for (Object item : (List<?>) data) { |
||||
|
writer.write(item.toString()); |
||||
|
writer.newLine(); |
||||
|
} |
||||
|
} else { |
||||
|
writer.write(data.toString()); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public String getStrategyName() { |
||||
|
return "文本格式"; |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue