diff --git a/AI协助记录.txt b/AI协助记录.txt deleted file mode 100644 index be8dce7..0000000 --- a/AI协助记录.txt +++ /dev/null @@ -1,19 +0,0 @@ -/*kimi k2.5*/ -P(prompt): -将上述 Python 程序完整移植为 Java 程序,文件名为 `TemperatureConverter.java`。 - - 在 Java 源码中保留与扩展注释,解释每个方法的功能与参数(中文注释可接受)。 -A(AI Answer): -*Java程序 -*关键语法对照说明 -/*claude haiku 4.5*/ -P: -可选加分项: - -支持命令行参数模式(例如 java TemperatureConverter 36.6 C)。 -支持批量转换(从文件读取多行温度并输出结果) -将代码修改以符合以上加分项 -A: -*修改过的Java程序 -*README.md -*temperatures.txt /*测试文件*/ -*测试 \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index f189fac..0000000 --- a/README.md +++ /dev/null @@ -1,512 +0,0 @@ -# 温度转换器 - 完整使用指南 - -## 📋 目录 - -1. [概述](#概述) -2. [特性](#特性) -3. [使用方法](#使用方法) -4. [交互模式智能纠正](#交互模式智能纠正) -5. [编译与运行](#编译与运行) -6. [示例](#示例) -7. [技术细节](#技术细节) - ---- - -## 概述 - -温度转换器是一个功能完整的Java程序,支持摄氏度(Celsius)和华氏度(Fahrenheit)之间的互相转换。 - -**支持三种工作模式**: -1. **命令行参数模式** - 直接在命令行传入温度和单位 -2. **批量转换模式** - 从文件读取温度数据进行批量处理 -3. **交互式模式** - 实时输入温度数据,获得智能纠正提示 - ---- - -## 特性 - -### ✨ 核心功能 - -- ✅ 摄氏度 ↔ 华氏度 双向转换 -- ✅ 精确到小数点后两位 -- ✅ 三种灵活的使用模式 -- ✅ 智能的错误诊断和纠正建议 - -### 🎯 转换公式 - -| 方向 | 公式 | -|-----|------| -| 摄氏 → 华氏 | F = C × 9/5 + 32 | -| 华氏 → 摄氏 | C = (F - 32) × 5/9 | - -### 🚀 加分项(已实现) - -1. **✅ 命令行参数模式** - - 支持 `java TemperatureConverter 36.6 C` - - 自动识别粘连格式(如 `36.6c`) - -2. **✅ 批量转换模式** - - 从文件读取多行温度数据 - - 支持注释行和空行 - - 提供转换统计 - -3. **✅ 交互模式智能纠正** - - 自动识别和修复粘连单位 - - 缺少单位时给出具体提示 - - 无效输入时指出问题所在 - ---- - -## 使用方法 - -### 1️⃣ 命令行参数模式 - -**最快速的使用方式** - 单行转换,无需等待 - -#### 语法 - -```bash -java TemperatureConverter <温度> <单位> -``` - -#### 示例 - -```bash -# 标准格式(有空格) -java TemperatureConverter 36.6 C -# 输出:36.60 °C = 97.88 °F - -# 粘连格式(无空格) -java TemperatureConverter 36.6c -# 输出:36.60 °C = 97.88 °F - -# 华氏到摄氏 -java TemperatureConverter 98.6 F -# 输出:98.60 °F = 37.00 °C -``` - -#### 特点 - -- 快速高效,适合脚本编程 -- 自动处理粘连格式 -- 三种模式中最简洁 - ---- - -### 2️⃣ 批量转换模式 - -**为数据处理设计** - 处理多行温度数据 - -#### 语法 - -```bash -java TemperatureConverter <文件路径> -``` - -#### 文件格式 - -每行一条数据,格式:`<温度值> <单位>` - -``` -# 示例:temperatures.txt -# 常见体温 -36.6 C -37.5 C - -# 常见环境温度 -25 C -30 C - -# 华氏度 -98.6 F -77 F - -# 特殊温度 -0 C -100 C -32 F -212 F -``` - -#### 运行示例 - -```bash -java TemperatureConverter temperatures.txt -``` - -#### 输出 - -``` -从文件读取温度数据:temperatures.txt -======================================== -第 4 行:36.60 °C = 97.88 °F -第 5 行:37.50 °C = 99.50 °F -第 8 行:25.00 °C = 77.00 °F -... -======================================== -处理完成:共处理 21 行,成功转换 10 条。 -``` - -#### 特点 - -- 支持注释行(以 `#` 开头) -- 支持空行(自动跳过) -- 显示处理统计信息 -- 适合批量数据导入/导出 - ---- - -### 3️⃣ 交互式模式(推荐) - -**用户友好的交互方式** - 实时输入,即时反馈 - -#### 启动 - -```bash -java TemperatureConverter -``` - -#### 功能说明 - -**自动纠正粘连单位** - -当输入数值和单位直接相连时(如 `100f`),程序会自动识别并显示纠正提示: - -``` -> 100f -✓ 已自动纠正输入:'100f' → '100 F' -100.00 °F = 37.78 °C -``` - -支持的粘连格式: -- `100f`, `100F` → 自动转换为 `100 F` -- `36.6c`, `36.6C` → 自动转换为 `36.6 C` - -**智能缺少单位提示** - -缺少温度单位时,程序会拒绝转换并给出具体建议: - -``` -> 100 -⚠️ 缺少温度单位! - 请指定 C(摄氏度)或 F(华氏度) - 示例:100 C 或 100 F -``` - -**无效单位诊断** - -不支持的单位会被立即识别并告知: - -``` -> 100 K -❌ 不支持的温度单位:'K' - 支持的单位:C(摄氏度)或 F(华氏度) - 示例:100 C 或 100 F -``` - -**无效数值诊断** - -数值格式不正确时会给出清晰提示: - -``` -> abc C -❌ 温度值不是有效的数字:'abc' - 请输入数值(如 36.6, 100 等) - 示例:36.6 C -``` - -**标准格式直接转换** - -格式正确时直接显示结果: - -``` -> 36.6 C -36.60 °C = 97.88 °F - -> 98.6 F -98.60 °F = 37.00 °C -``` - -**退出程序** - -``` -> quit -程序退出。 - -或者 - -> exit -程序退出。 -``` - ---- - -## 交互模式智能纠正 - -### 纠正功能详解 - -| 错误类型 | 输入示例 | 程序行为 | 输出 | -|--------|--------|--------|------| -| **粘连单位** | `100f` | 自动纠正并转换 | `✓ 已自动纠正输入:'100f' → '100 F'`
`100.00 °F = 37.78 °C` | -| **有空格粘连** | `100 f` | 直接识别并转换 | `100.00 °F = 37.78 °C` | -| **缺少单位** | `100` | 拒绝并提示 | `⚠️ 缺少温度单位!`
` 示例:100 C 或 100 F` | -| **无效单位** | `100 K` | 拒绝并说明原因 | `❌ 不支持的温度单位:'K'`
` 示例:100 C 或 100 F` | -| **无效数值** | `abc C` | 拒绝并给出示例 | `❌ 温度值不是有效的数字:'abc'`
` 示例:36.6 C` | -| **标准正确** | `36.6 C` | 直接转换 | `36.60 °C = 97.88 °F` | - -### 完整交互示例 - -``` -======================================== -温度转换器 - 交互式模式 -======================================== -请输入要转换的温度与单位(例如 36.6 C 或 97 F) -输入 'quit' 或 'exit' 退出程序 -======================================== - -> 36.6 C -36.60 °C = 97.88 °F - -> 100f -✓ 已自动纠正输入:'100f' → '100 F' -100.00 °F = 37.78 °C - -> 100 f -100.00 °F = 37.78 °C - -> 100 -⚠️ 缺少温度单位! - 请指定 C(摄氏度)或 F(华氏度) - 示例:100 C 或 100 F - -> 100 K -❌ 不支持的温度单位:'K' - 支持的单位:C(摄氏度)或 F(华氏度) - 示例:100 C 或 100 F - -> abc C -❌ 温度值不是有效的数字:'abc' - 请输入数值(如 36.6, 100 等) - 示例:36.6 C - -> quit -程序退出。 -``` - ---- - -## 编译与运行 - -### 前置要求 - -- Java JDK 8 或更高版本 -- 命令行/终端访问权限 - -### 编译 - -```bash -# 进入项目目录 -cd d:\VisualStudioProgram\VSCodePrograms\JavaLearningProject - -# 编译源代码 -javac TemperatureConverter.java -``` - -编译成功后会生成 `TemperatureConverter.class` 文件。 - -### 运行 - -#### 模式选择 - -```bash -# 1. 交互式模式(推荐) -java TemperatureConverter - -# 2. 命令行参数模式 -java TemperatureConverter 36.6 C - -# 3. 批量转换模式 -java TemperatureConverter temperatures.txt - -# 4. 显示帮助信息 -java TemperatureConverter -h -java TemperatureConverter --help -``` - ---- - -## 示例 - -### 各种场景的使用 - -#### 场景1:快速查询单个温度 - -```bash -$ java TemperatureConverter 37 C -37.00 °C = 98.60 °F -``` - -#### 场景2:处理粘连格式 - -```bash -$ java TemperatureConverter 37c -37.00 °C = 98.60 °F -``` - -#### 场景3:医学应用 - 正常体温 - -```bash -$ java TemperatureConverter 36.5 C -36.50 °C = 97.70 °F - -$ java TemperatureConverter 98.6 F -98.60 °F = 37.00 °C -``` - -#### 场景4:物理学应用 - 相变点 - -```bash -$ java TemperatureConverter 0 C -0.00 °C = 32.00 °F - -$ java TemperatureConverter 100 C -100.00 °C = 212.00 °F -``` - -#### 场景5:批量处理数据 - -创建 `test.txt`: - -``` -# 测试数据 -25 C -30 C -35 C -96.8 F -104 F -``` - -运行: - -```bash -$ java TemperatureConverter test.txt -从文件读取温度数据:test.txt -======================================== -第 2 行:25.00 °C = 77.00 °F -第 3 行:30.00 °C = 86.00 °F -第 4 行:35.00 °C = 95.00 °F -第 5 行:96.80 °F = 36.00 °C -第 6 行:104.00 °F = 40.00 °C -======================================== -处理完成:共处理 6 行,成功转换 5 条。 -``` - ---- - -## 技术细节 - -### 程序结构 - -``` -TemperatureConverter.java -├── 转换方法 -│ ├── celsiusToFahrenheit() - C → F -│ └── fahrenheitToCelsius() - F → C -│ -├── 智能诊断方法 -│ ├── extractAdjacent() - 提取粘连格式 -│ ├── analyzeAndSuggestFix() - 错误分析与建议 -│ └── convertWithValidatedInput() - 执行转换 -│ -├── 交互方法 -│ ├── interactiveMode() - 交互式模式 -│ └── convertSingleTemperature() - 单个转换(命令行/文件) -│ -├── 数据处理方法 -│ ├── convertFromFile() - 批量文件处理 -│ └── showUsage() - 使用说明 -│ -└── 入口点 - └── main() - 程序入口,模式选择 -``` - -### 粘连单位识别 - -**正则表达式**:`^(\d+(?:\.\d+)?)\s*([CcFf])$` - -- `\d+` - 一个或多个数字 -- `(?:\.\d+)?` - 可选的小数部分 -- `\s*` - 零个或多个空白字符 -- `[CcFf]` - 单位字母(C/c/F/f) - -**支持的格式**: -- `100f` → ["100", "F"] -- `36.6c` → ["36.6", "C"] -- `100 f` → ["100", "F"](允许空格) -- `36.6 c` → ["36.6", "C"](允许空格) - -### 三种模式的差异 - -| 特性 | 交互模式 | 命令行模式 | 文件模式 | -|-----|--------|---------|--------| -| **启用粘连识别** | ✅ 是 | ✅ 是 | ✅ 是 | -| **显示纠正提示** | ✅ 是 | ❌ 否 | ❌ 否 | -| **缺少单位处理** | ❌ 拒绝 | ✅ 默认C | ✅ 默认C | -| **错误详细提示** | ✅ 是 | ❌ 否 | ❌ 否 | -| **单/多行处理** | 多行循环 | 单行 | 多行批处理 | - ---- - -## 常见问题 - -**Q: 如何让程序接受更多的温度单位(如K、R)?** - -A: 修改 `analyzeAndSuggestFix()` 方法中的单位验证逻辑,添加新的单位判断条件和转换公式。 - -**Q: 如何修改输出精度(目前是两位小数)?** - -A: 更改 `System.out.printf()` 中的格式字符串,例如 `%.2f` 改为 `%.3f` 表示三位小数。 - -**Q: 批量模式中,某一行出错会影响整个文件处理吗?** - -A: 不会。程序会跳过无效行,继续处理后续行,最后显示成功和失败的统计。 - -**Q: 能否在命令行模式中显示纠正提示?** - -A: 可以。修改 `convertSingleTemperature()` 方法中对 `extractAdjacent()` 的调用,改为调用 `analyzeAndSuggestFix()` 并传入 `true` 参数。 - ---- - -## 文件列表 - -| 文件 | 说明 | -|-----|------| -| `TemperatureConverter.java` | 源代码文件 | -| `TemperatureConverter.class` | 编译后的字节码 | -| `temperatures.txt` | 示例数据文件 | -| `README_增强版.md` | 原始功能说明(已合并到本文档) | -| `交互模式改进说明.md` | 交互模式详细说明(已合并到本文档) | -| `粘连单位修复总结.md` | 修复说明(已合并到本文档) | - ---- - -## 版本信息 - -- **程序版本**:2.0 -- **功能扩展**:3项加分项 + 交互模式智能纠正 -- **最后更新**:2026年03月 -- **Java版本要求**:JDK 8+ - ---- - -## 总结 - -这个温度转换器程序演示了以下编程概念: - -1. **正则表达式** - 灵活的模式匹配和数据提取 -2. **异常处理** - 文件IO和数据验证 -3. **模式设计** - 多种工作模式的统一管理 -4. **用户交互** - 友好的错误提示和智能建议 -5. **代码组织** - 清晰的方法划分和职责分离 - -祝你使用愉快! 🎉 diff --git a/TemperatureConverter.java b/TemperatureConverter.java deleted file mode 100644 index af5f366..0000000 --- a/TemperatureConverter.java +++ /dev/null @@ -1,338 +0,0 @@ -import java.util.Scanner; -import java.io.File; -import java.io.FileNotFoundException; - -/** - * 温度转换器示例程序(Java) - * 支持摄氏度(C)与华氏度(F)之间互转 - * 增强版本:支持命令行参数和文件批量转换 - */ -public class TemperatureConverter_PytoJava { - - /** - * 将摄氏度转换为华氏度。 - * - * 换算公式:F = C × 9/5 + 32 - * - * @param c 摄氏温度(浮点数) - * @return 对应的华氏温度(浮点数) - */ - public static double celsiusToFahrenheit(double c) { - return c * 9.0 / 5.0 + 32.0; - } - - /** - * 将华氏度转换为摄氏度。 - * - * 换算公式:C = (F - 32) × 5/9 - * - * @param f 华氏温度(浮点数) - * @return 对应的摄氏温度(浮点数) - */ - public static double fahrenheitToCelsius(double f) { - return (f - 32.0) * 5.0 / 9.0; - } - - /** - * 从粘连格式中提取数值和单位。 - * 例如:100f → ["100", "F"],36.6c → ["36.6", "C"],100 f → ["100", "F"] - * 支持单位前的空格。 - * - * @param input 粘连格式的输入 - * @return 包含数值和单位的数组,如果无法提取则返回 null - */ - public static String[] extractAdjacent(String input) { - String trimmed = input.trim(); - // 使用正则表达式匹配粘连格式,允许单位前有空格 - // 格式:数字(可选小数) + 可选空格 + C或F - java.util.regex.Pattern pattern = java.util.regex.Pattern.compile("^(\\d+(?:\\.\\d+)?)\\s*([CcFf])$"); - java.util.regex.Matcher matcher = pattern.matcher(trimmed); - - if (matcher.matches()) { - String value = matcher.group(1); - String unit = matcher.group(2).toUpperCase(); - return new String[]{value, unit}; - } - return null; - } - - /** - * 分析并提供错误诊断和建议。 - * 在交互模式中使用此方法进行智能纠正。 - * - * @param input 用户输入 - * @param isInteractiveMode 是否为交互模式 - * @return 如果输入有效返回true,否则返回false并打印诊断信息 - */ - public static boolean analyzeAndSuggestFix(String input, boolean isInteractiveMode) { - input = input.trim(); - - // 1. 检查粘连单位模式(如 100f) - String[] adjacent = extractAdjacent(input); - if (adjacent != null) { - // 判断是否真正是粘连格式(数值和单位直接相连,无空格) - boolean isDirectlyAdjacent = input.matches("^\\d+(?:\\.\\d+)?[CcFf]$"); - if (isDirectlyAdjacent && isInteractiveMode) { - System.out.println("✓ 已自动纠正输入:'" + input + "' → '" + adjacent[0] + " " + adjacent[1] + "'"); - } - // 进行转换 - return convertWithValidatedInput(Double.parseDouble(adjacent[0]), adjacent[1]); - } - - // 2. 分割输入 - String[] parts = input.split("\\s+"); - - // 3. 检查缺少单位模式(如 100) - if (parts.length == 1) { - try { - Double.parseDouble(parts[0]); - // 能解析为数字,但缺少单位 - if (isInteractiveMode) { - System.out.println("⚠️ 缺少温度单位!"); - System.out.println(" 请指定 C(摄氏度)或 F(华氏度)"); - System.out.println(" 示例:" + parts[0] + " C 或 " + parts[0] + " F"); - } - return false; - } catch (NumberFormatException e) { - // 既不是数字也不是正确格式 - if (isInteractiveMode) { - System.out.println("❌ 温度值不是有效的数字:'" + parts[0] + "'"); - System.out.println(" 请输入数值(如 36.6, 100 等)"); - System.out.println(" 示例:36.6 C"); - } - return false; - } - } - - // 4. 验证数值有效性 - try { - double value = Double.parseDouble(parts[0]); - String unit = parts[1].toUpperCase(); - - // 5. 验证单位有效性 - if (!unit.startsWith("C") && !unit.startsWith("F")) { - if (isInteractiveMode) { - System.out.println("❌ 不支持的温度单位:'" + unit + "'"); - System.out.println(" 支持的单位:C(摄氏度)或 F(华氏度)"); - System.out.println(" 示例:" + parts[0] + " C 或 " + parts[0] + " F"); - } - return false; - } - - // 转换成功 - return convertWithValidatedInput(value, unit); - - } catch (NumberFormatException e) { - if (isInteractiveMode) { - System.out.println("❌ 温度值不是有效的数字:'" + parts[0] + "'"); - System.out.println(" 请输入数值(如 36.6, 100 等)"); - System.out.println(" 示例:36.6 C"); - } - return false; - } - } - - /** - * 使用验证过的数值和单位进行转换。 - * 这是真正的转换逻辑,不包含错误处理。 - * - * @param value 温度值 - * @param unit 单位(C 或 F) - * @return 始终返回 true(如果调用说明数据有效) - */ - public static boolean convertWithValidatedInput(double value, String unit) { - if (unit.startsWith("C")) { - double f = celsiusToFahrenheit(value); - System.out.printf("%.2f °C = %.2f °F%n", value, f); - } else if (unit.startsWith("F")) { - double c = fahrenheitToCelsius(value); - System.out.printf("%.2f °F = %.2f °C%n", value, c); - } - return true; - } - - /** - * 处理单个温度转换(命令行/文件模式用,宽松验证)。 - * - * @param input 温度输入字符串,格式:"数值 单位",例如 "36.6 C" - * @return 转换成功返回true,失败返回false - */ - public static boolean convertSingleTemperature(String input) { - input = input.trim(); - - // 检查输入是否为空 - if (input.isEmpty()) { - return false; - } - - // 检查粘连单位并自动分离 - String[] adjacent = extractAdjacent(input); - if (adjacent != null) { - try { - return convertWithValidatedInput(Double.parseDouble(adjacent[0]), adjacent[1]); - } catch (NumberFormatException e) { - return false; - } - } - - // 按空白字符分割输入字符串 - String[] parts = input.split("\\s+"); - - try { - double value = Double.parseDouble(parts[0]); - // 如果没有单位,在命令行/文件模式中仍然尝试转换(宽松模式) - // 但在交互模式中会被 analyzeAndSuggestFix 拒绝 - String unit = (parts.length > 1) ? parts[1].toUpperCase() : "C"; - - if (unit.startsWith("C")) { - double f = celsiusToFahrenheit(value); - System.out.printf("%.2f °C = %.2f °F%n", value, f); - } else if (unit.startsWith("F")) { - double c = fahrenheitToCelsius(value); - System.out.printf("%.2f °F = %.2f °C%n", value, c); - } else { - return false; - } - return true; - - } catch (NumberFormatException e) { - return false; - } catch (Exception e) { - return false; - } - } - - /** - * 从文件中批量读取温度数据并进行转换。 - * 文件中每一行包含一个温度及其单位。 - * - * @param filename 文件路径 - */ - public static void convertFromFile(String filename) { - File file = new File(filename); - - if (!file.exists()) { - System.out.println("文件不存在:" + filename); - return; - } - - try { - Scanner fileScanner = new Scanner(file); - int lineNum = 0; - int successCount = 0; - - System.out.println("从文件读取温度数据:" + filename); - System.out.println("========================================"); - - while (fileScanner.hasNextLine()) { - lineNum++; - String line = fileScanner.nextLine(); - - // 跳过空行和注释行(以#开头) - if (line.trim().isEmpty() || line.trim().startsWith("#")) { - continue; - } - - System.out.print("第 " + lineNum + " 行:"); - if (convertSingleTemperature(line)) { - successCount++; - } - } - - System.out.println("========================================"); - System.out.println("处理完成:共处理 " + lineNum + " 行,成功转换 " + successCount + " 条。"); - - fileScanner.close(); - - } catch (FileNotFoundException e) { - System.out.println("无法打开文件:" + filename); - } - } - - /** - * 交互式模式:提示用户输入温度进行转换,使用智能诊断。 - */ - public static void interactiveMode() { - Scanner scanner = new Scanner(System.in); - - System.out.println("========================================"); - System.out.println("温度转换器 - 交互式模式"); - System.out.println("========================================"); - System.out.println("请输入要转换的温度与单位(例如 36.6 C 或 97 F)"); - System.out.println("输入 'quit' 或 'exit' 退出程序"); - System.out.println("========================================"); - - while (true) { - System.out.print("> "); - String input = scanner.nextLine(); - - if (input.equalsIgnoreCase("quit") || input.equalsIgnoreCase("exit")) { - System.out.println("程序退出。"); - break; - } - - // 在交互模式中使用智能诊断 - if (!analyzeAndSuggestFix(input, true)) { - // 诊断方法已经打印了具体的错误信息 - } - } - - scanner.close(); - } - - /** - * 显示使用说明。 - */ - public static void showUsage() { - System.out.println("温度转换器 - 使用说明"); - System.out.println("========================================"); - System.out.println("用法1(命令行参数模式):"); - System.out.println(" java TemperatureConverter_PytoJava <温度> <单位>"); - System.out.println(" 例如:java TemperatureConverter_PytoJava 36.6 C"); - System.out.println(); - System.out.println("用法2(批量转换模式):"); - System.out.println(" java TemperatureConverter_PytoJava <文件路径>"); - System.out.println(" 例如:java TemperatureConverter_PytoJava temperatures.txt"); - System.out.println(" 文件格式:每行一条温度数据,格式为 '温度 单位'"); - System.out.println(); - System.out.println("用法3(交互式模式):"); - System.out.println(" java TemperatureConverter_PytoJava"); - System.out.println(" 然后按提示输入温度数据"); - System.out.println("========================================"); - } - - /** - * 程序入口主方法。 - * 支持三种模式: - * 1. 命令行参数模式:java TemperatureConverter_PytoJava 36.6 C - * 2. 文件批量转换:java TemperatureConverter_PytoJava temperatures.txt - * 3. 交互式模式:java TemperatureConverter_PytoJava - * - * @param args 命令行参数 - */ - public static void main(String[] args) { - if (args.length == 0) { - // 交互式模式 - interactiveMode(); - } else if (args.length == 1) { - // 检查是否为文件路径(包含.txt或其他扩展名,或者是一个存在的文件) - File file = new File(args[0]); - if (file.exists() && file.isFile()) { - // 文件批量转换模式 - convertFromFile(args[0]); - } else if (args[0].equalsIgnoreCase("-h") || args[0].equalsIgnoreCase("--help")) { - // 显示帮助信息 - showUsage(); - } else { - // 可能是单个温度但缺少单位,默认使用摄氏度 - System.out.println("处理输入:" + args[0]); - convertSingleTemperature(args[0]); - } - } else if (args.length == 2) { - // 命令行参数模式:温度和单位 - convertSingleTemperature(args[0] + " " + args[1]); - } else { - showUsage(); - } - } -} diff --git a/W1-梁凯雄-202402050120/AI协助记录.txt b/W1-梁凯雄-202402050120/AI协助记录.txt deleted file mode 100644 index be8dce7..0000000 --- a/W1-梁凯雄-202402050120/AI协助记录.txt +++ /dev/null @@ -1,19 +0,0 @@ -/*kimi k2.5*/ -P(prompt): -将上述 Python 程序完整移植为 Java 程序,文件名为 `TemperatureConverter.java`。 - - 在 Java 源码中保留与扩展注释,解释每个方法的功能与参数(中文注释可接受)。 -A(AI Answer): -*Java程序 -*关键语法对照说明 -/*claude haiku 4.5*/ -P: -可选加分项: - -支持命令行参数模式(例如 java TemperatureConverter 36.6 C)。 -支持批量转换(从文件读取多行温度并输出结果) -将代码修改以符合以上加分项 -A: -*修改过的Java程序 -*README.md -*temperatures.txt /*测试文件*/ -*测试 \ No newline at end of file diff --git a/W1-梁凯雄-202402050120/README.md b/W1-梁凯雄-202402050120/README.md deleted file mode 100644 index f189fac..0000000 --- a/W1-梁凯雄-202402050120/README.md +++ /dev/null @@ -1,512 +0,0 @@ -# 温度转换器 - 完整使用指南 - -## 📋 目录 - -1. [概述](#概述) -2. [特性](#特性) -3. [使用方法](#使用方法) -4. [交互模式智能纠正](#交互模式智能纠正) -5. [编译与运行](#编译与运行) -6. [示例](#示例) -7. [技术细节](#技术细节) - ---- - -## 概述 - -温度转换器是一个功能完整的Java程序,支持摄氏度(Celsius)和华氏度(Fahrenheit)之间的互相转换。 - -**支持三种工作模式**: -1. **命令行参数模式** - 直接在命令行传入温度和单位 -2. **批量转换模式** - 从文件读取温度数据进行批量处理 -3. **交互式模式** - 实时输入温度数据,获得智能纠正提示 - ---- - -## 特性 - -### ✨ 核心功能 - -- ✅ 摄氏度 ↔ 华氏度 双向转换 -- ✅ 精确到小数点后两位 -- ✅ 三种灵活的使用模式 -- ✅ 智能的错误诊断和纠正建议 - -### 🎯 转换公式 - -| 方向 | 公式 | -|-----|------| -| 摄氏 → 华氏 | F = C × 9/5 + 32 | -| 华氏 → 摄氏 | C = (F - 32) × 5/9 | - -### 🚀 加分项(已实现) - -1. **✅ 命令行参数模式** - - 支持 `java TemperatureConverter 36.6 C` - - 自动识别粘连格式(如 `36.6c`) - -2. **✅ 批量转换模式** - - 从文件读取多行温度数据 - - 支持注释行和空行 - - 提供转换统计 - -3. **✅ 交互模式智能纠正** - - 自动识别和修复粘连单位 - - 缺少单位时给出具体提示 - - 无效输入时指出问题所在 - ---- - -## 使用方法 - -### 1️⃣ 命令行参数模式 - -**最快速的使用方式** - 单行转换,无需等待 - -#### 语法 - -```bash -java TemperatureConverter <温度> <单位> -``` - -#### 示例 - -```bash -# 标准格式(有空格) -java TemperatureConverter 36.6 C -# 输出:36.60 °C = 97.88 °F - -# 粘连格式(无空格) -java TemperatureConverter 36.6c -# 输出:36.60 °C = 97.88 °F - -# 华氏到摄氏 -java TemperatureConverter 98.6 F -# 输出:98.60 °F = 37.00 °C -``` - -#### 特点 - -- 快速高效,适合脚本编程 -- 自动处理粘连格式 -- 三种模式中最简洁 - ---- - -### 2️⃣ 批量转换模式 - -**为数据处理设计** - 处理多行温度数据 - -#### 语法 - -```bash -java TemperatureConverter <文件路径> -``` - -#### 文件格式 - -每行一条数据,格式:`<温度值> <单位>` - -``` -# 示例:temperatures.txt -# 常见体温 -36.6 C -37.5 C - -# 常见环境温度 -25 C -30 C - -# 华氏度 -98.6 F -77 F - -# 特殊温度 -0 C -100 C -32 F -212 F -``` - -#### 运行示例 - -```bash -java TemperatureConverter temperatures.txt -``` - -#### 输出 - -``` -从文件读取温度数据:temperatures.txt -======================================== -第 4 行:36.60 °C = 97.88 °F -第 5 行:37.50 °C = 99.50 °F -第 8 行:25.00 °C = 77.00 °F -... -======================================== -处理完成:共处理 21 行,成功转换 10 条。 -``` - -#### 特点 - -- 支持注释行(以 `#` 开头) -- 支持空行(自动跳过) -- 显示处理统计信息 -- 适合批量数据导入/导出 - ---- - -### 3️⃣ 交互式模式(推荐) - -**用户友好的交互方式** - 实时输入,即时反馈 - -#### 启动 - -```bash -java TemperatureConverter -``` - -#### 功能说明 - -**自动纠正粘连单位** - -当输入数值和单位直接相连时(如 `100f`),程序会自动识别并显示纠正提示: - -``` -> 100f -✓ 已自动纠正输入:'100f' → '100 F' -100.00 °F = 37.78 °C -``` - -支持的粘连格式: -- `100f`, `100F` → 自动转换为 `100 F` -- `36.6c`, `36.6C` → 自动转换为 `36.6 C` - -**智能缺少单位提示** - -缺少温度单位时,程序会拒绝转换并给出具体建议: - -``` -> 100 -⚠️ 缺少温度单位! - 请指定 C(摄氏度)或 F(华氏度) - 示例:100 C 或 100 F -``` - -**无效单位诊断** - -不支持的单位会被立即识别并告知: - -``` -> 100 K -❌ 不支持的温度单位:'K' - 支持的单位:C(摄氏度)或 F(华氏度) - 示例:100 C 或 100 F -``` - -**无效数值诊断** - -数值格式不正确时会给出清晰提示: - -``` -> abc C -❌ 温度值不是有效的数字:'abc' - 请输入数值(如 36.6, 100 等) - 示例:36.6 C -``` - -**标准格式直接转换** - -格式正确时直接显示结果: - -``` -> 36.6 C -36.60 °C = 97.88 °F - -> 98.6 F -98.60 °F = 37.00 °C -``` - -**退出程序** - -``` -> quit -程序退出。 - -或者 - -> exit -程序退出。 -``` - ---- - -## 交互模式智能纠正 - -### 纠正功能详解 - -| 错误类型 | 输入示例 | 程序行为 | 输出 | -|--------|--------|--------|------| -| **粘连单位** | `100f` | 自动纠正并转换 | `✓ 已自动纠正输入:'100f' → '100 F'`
`100.00 °F = 37.78 °C` | -| **有空格粘连** | `100 f` | 直接识别并转换 | `100.00 °F = 37.78 °C` | -| **缺少单位** | `100` | 拒绝并提示 | `⚠️ 缺少温度单位!`
` 示例:100 C 或 100 F` | -| **无效单位** | `100 K` | 拒绝并说明原因 | `❌ 不支持的温度单位:'K'`
` 示例:100 C 或 100 F` | -| **无效数值** | `abc C` | 拒绝并给出示例 | `❌ 温度值不是有效的数字:'abc'`
` 示例:36.6 C` | -| **标准正确** | `36.6 C` | 直接转换 | `36.60 °C = 97.88 °F` | - -### 完整交互示例 - -``` -======================================== -温度转换器 - 交互式模式 -======================================== -请输入要转换的温度与单位(例如 36.6 C 或 97 F) -输入 'quit' 或 'exit' 退出程序 -======================================== - -> 36.6 C -36.60 °C = 97.88 °F - -> 100f -✓ 已自动纠正输入:'100f' → '100 F' -100.00 °F = 37.78 °C - -> 100 f -100.00 °F = 37.78 °C - -> 100 -⚠️ 缺少温度单位! - 请指定 C(摄氏度)或 F(华氏度) - 示例:100 C 或 100 F - -> 100 K -❌ 不支持的温度单位:'K' - 支持的单位:C(摄氏度)或 F(华氏度) - 示例:100 C 或 100 F - -> abc C -❌ 温度值不是有效的数字:'abc' - 请输入数值(如 36.6, 100 等) - 示例:36.6 C - -> quit -程序退出。 -``` - ---- - -## 编译与运行 - -### 前置要求 - -- Java JDK 8 或更高版本 -- 命令行/终端访问权限 - -### 编译 - -```bash -# 进入项目目录 -cd d:\VisualStudioProgram\VSCodePrograms\JavaLearningProject - -# 编译源代码 -javac TemperatureConverter.java -``` - -编译成功后会生成 `TemperatureConverter.class` 文件。 - -### 运行 - -#### 模式选择 - -```bash -# 1. 交互式模式(推荐) -java TemperatureConverter - -# 2. 命令行参数模式 -java TemperatureConverter 36.6 C - -# 3. 批量转换模式 -java TemperatureConverter temperatures.txt - -# 4. 显示帮助信息 -java TemperatureConverter -h -java TemperatureConverter --help -``` - ---- - -## 示例 - -### 各种场景的使用 - -#### 场景1:快速查询单个温度 - -```bash -$ java TemperatureConverter 37 C -37.00 °C = 98.60 °F -``` - -#### 场景2:处理粘连格式 - -```bash -$ java TemperatureConverter 37c -37.00 °C = 98.60 °F -``` - -#### 场景3:医学应用 - 正常体温 - -```bash -$ java TemperatureConverter 36.5 C -36.50 °C = 97.70 °F - -$ java TemperatureConverter 98.6 F -98.60 °F = 37.00 °C -``` - -#### 场景4:物理学应用 - 相变点 - -```bash -$ java TemperatureConverter 0 C -0.00 °C = 32.00 °F - -$ java TemperatureConverter 100 C -100.00 °C = 212.00 °F -``` - -#### 场景5:批量处理数据 - -创建 `test.txt`: - -``` -# 测试数据 -25 C -30 C -35 C -96.8 F -104 F -``` - -运行: - -```bash -$ java TemperatureConverter test.txt -从文件读取温度数据:test.txt -======================================== -第 2 行:25.00 °C = 77.00 °F -第 3 行:30.00 °C = 86.00 °F -第 4 行:35.00 °C = 95.00 °F -第 5 行:96.80 °F = 36.00 °C -第 6 行:104.00 °F = 40.00 °C -======================================== -处理完成:共处理 6 行,成功转换 5 条。 -``` - ---- - -## 技术细节 - -### 程序结构 - -``` -TemperatureConverter.java -├── 转换方法 -│ ├── celsiusToFahrenheit() - C → F -│ └── fahrenheitToCelsius() - F → C -│ -├── 智能诊断方法 -│ ├── extractAdjacent() - 提取粘连格式 -│ ├── analyzeAndSuggestFix() - 错误分析与建议 -│ └── convertWithValidatedInput() - 执行转换 -│ -├── 交互方法 -│ ├── interactiveMode() - 交互式模式 -│ └── convertSingleTemperature() - 单个转换(命令行/文件) -│ -├── 数据处理方法 -│ ├── convertFromFile() - 批量文件处理 -│ └── showUsage() - 使用说明 -│ -└── 入口点 - └── main() - 程序入口,模式选择 -``` - -### 粘连单位识别 - -**正则表达式**:`^(\d+(?:\.\d+)?)\s*([CcFf])$` - -- `\d+` - 一个或多个数字 -- `(?:\.\d+)?` - 可选的小数部分 -- `\s*` - 零个或多个空白字符 -- `[CcFf]` - 单位字母(C/c/F/f) - -**支持的格式**: -- `100f` → ["100", "F"] -- `36.6c` → ["36.6", "C"] -- `100 f` → ["100", "F"](允许空格) -- `36.6 c` → ["36.6", "C"](允许空格) - -### 三种模式的差异 - -| 特性 | 交互模式 | 命令行模式 | 文件模式 | -|-----|--------|---------|--------| -| **启用粘连识别** | ✅ 是 | ✅ 是 | ✅ 是 | -| **显示纠正提示** | ✅ 是 | ❌ 否 | ❌ 否 | -| **缺少单位处理** | ❌ 拒绝 | ✅ 默认C | ✅ 默认C | -| **错误详细提示** | ✅ 是 | ❌ 否 | ❌ 否 | -| **单/多行处理** | 多行循环 | 单行 | 多行批处理 | - ---- - -## 常见问题 - -**Q: 如何让程序接受更多的温度单位(如K、R)?** - -A: 修改 `analyzeAndSuggestFix()` 方法中的单位验证逻辑,添加新的单位判断条件和转换公式。 - -**Q: 如何修改输出精度(目前是两位小数)?** - -A: 更改 `System.out.printf()` 中的格式字符串,例如 `%.2f` 改为 `%.3f` 表示三位小数。 - -**Q: 批量模式中,某一行出错会影响整个文件处理吗?** - -A: 不会。程序会跳过无效行,继续处理后续行,最后显示成功和失败的统计。 - -**Q: 能否在命令行模式中显示纠正提示?** - -A: 可以。修改 `convertSingleTemperature()` 方法中对 `extractAdjacent()` 的调用,改为调用 `analyzeAndSuggestFix()` 并传入 `true` 参数。 - ---- - -## 文件列表 - -| 文件 | 说明 | -|-----|------| -| `TemperatureConverter.java` | 源代码文件 | -| `TemperatureConverter.class` | 编译后的字节码 | -| `temperatures.txt` | 示例数据文件 | -| `README_增强版.md` | 原始功能说明(已合并到本文档) | -| `交互模式改进说明.md` | 交互模式详细说明(已合并到本文档) | -| `粘连单位修复总结.md` | 修复说明(已合并到本文档) | - ---- - -## 版本信息 - -- **程序版本**:2.0 -- **功能扩展**:3项加分项 + 交互模式智能纠正 -- **最后更新**:2026年03月 -- **Java版本要求**:JDK 8+ - ---- - -## 总结 - -这个温度转换器程序演示了以下编程概念: - -1. **正则表达式** - 灵活的模式匹配和数据提取 -2. **异常处理** - 文件IO和数据验证 -3. **模式设计** - 多种工作模式的统一管理 -4. **用户交互** - 友好的错误提示和智能建议 -5. **代码组织** - 清晰的方法划分和职责分离 - -祝你使用愉快! 🎉 diff --git a/W1-梁凯雄-202402050120/TemperatureConverter.java b/W1-梁凯雄-202402050120/TemperatureConverter.java deleted file mode 100644 index af5f366..0000000 --- a/W1-梁凯雄-202402050120/TemperatureConverter.java +++ /dev/null @@ -1,338 +0,0 @@ -import java.util.Scanner; -import java.io.File; -import java.io.FileNotFoundException; - -/** - * 温度转换器示例程序(Java) - * 支持摄氏度(C)与华氏度(F)之间互转 - * 增强版本:支持命令行参数和文件批量转换 - */ -public class TemperatureConverter_PytoJava { - - /** - * 将摄氏度转换为华氏度。 - * - * 换算公式:F = C × 9/5 + 32 - * - * @param c 摄氏温度(浮点数) - * @return 对应的华氏温度(浮点数) - */ - public static double celsiusToFahrenheit(double c) { - return c * 9.0 / 5.0 + 32.0; - } - - /** - * 将华氏度转换为摄氏度。 - * - * 换算公式:C = (F - 32) × 5/9 - * - * @param f 华氏温度(浮点数) - * @return 对应的摄氏温度(浮点数) - */ - public static double fahrenheitToCelsius(double f) { - return (f - 32.0) * 5.0 / 9.0; - } - - /** - * 从粘连格式中提取数值和单位。 - * 例如:100f → ["100", "F"],36.6c → ["36.6", "C"],100 f → ["100", "F"] - * 支持单位前的空格。 - * - * @param input 粘连格式的输入 - * @return 包含数值和单位的数组,如果无法提取则返回 null - */ - public static String[] extractAdjacent(String input) { - String trimmed = input.trim(); - // 使用正则表达式匹配粘连格式,允许单位前有空格 - // 格式:数字(可选小数) + 可选空格 + C或F - java.util.regex.Pattern pattern = java.util.regex.Pattern.compile("^(\\d+(?:\\.\\d+)?)\\s*([CcFf])$"); - java.util.regex.Matcher matcher = pattern.matcher(trimmed); - - if (matcher.matches()) { - String value = matcher.group(1); - String unit = matcher.group(2).toUpperCase(); - return new String[]{value, unit}; - } - return null; - } - - /** - * 分析并提供错误诊断和建议。 - * 在交互模式中使用此方法进行智能纠正。 - * - * @param input 用户输入 - * @param isInteractiveMode 是否为交互模式 - * @return 如果输入有效返回true,否则返回false并打印诊断信息 - */ - public static boolean analyzeAndSuggestFix(String input, boolean isInteractiveMode) { - input = input.trim(); - - // 1. 检查粘连单位模式(如 100f) - String[] adjacent = extractAdjacent(input); - if (adjacent != null) { - // 判断是否真正是粘连格式(数值和单位直接相连,无空格) - boolean isDirectlyAdjacent = input.matches("^\\d+(?:\\.\\d+)?[CcFf]$"); - if (isDirectlyAdjacent && isInteractiveMode) { - System.out.println("✓ 已自动纠正输入:'" + input + "' → '" + adjacent[0] + " " + adjacent[1] + "'"); - } - // 进行转换 - return convertWithValidatedInput(Double.parseDouble(adjacent[0]), adjacent[1]); - } - - // 2. 分割输入 - String[] parts = input.split("\\s+"); - - // 3. 检查缺少单位模式(如 100) - if (parts.length == 1) { - try { - Double.parseDouble(parts[0]); - // 能解析为数字,但缺少单位 - if (isInteractiveMode) { - System.out.println("⚠️ 缺少温度单位!"); - System.out.println(" 请指定 C(摄氏度)或 F(华氏度)"); - System.out.println(" 示例:" + parts[0] + " C 或 " + parts[0] + " F"); - } - return false; - } catch (NumberFormatException e) { - // 既不是数字也不是正确格式 - if (isInteractiveMode) { - System.out.println("❌ 温度值不是有效的数字:'" + parts[0] + "'"); - System.out.println(" 请输入数值(如 36.6, 100 等)"); - System.out.println(" 示例:36.6 C"); - } - return false; - } - } - - // 4. 验证数值有效性 - try { - double value = Double.parseDouble(parts[0]); - String unit = parts[1].toUpperCase(); - - // 5. 验证单位有效性 - if (!unit.startsWith("C") && !unit.startsWith("F")) { - if (isInteractiveMode) { - System.out.println("❌ 不支持的温度单位:'" + unit + "'"); - System.out.println(" 支持的单位:C(摄氏度)或 F(华氏度)"); - System.out.println(" 示例:" + parts[0] + " C 或 " + parts[0] + " F"); - } - return false; - } - - // 转换成功 - return convertWithValidatedInput(value, unit); - - } catch (NumberFormatException e) { - if (isInteractiveMode) { - System.out.println("❌ 温度值不是有效的数字:'" + parts[0] + "'"); - System.out.println(" 请输入数值(如 36.6, 100 等)"); - System.out.println(" 示例:36.6 C"); - } - return false; - } - } - - /** - * 使用验证过的数值和单位进行转换。 - * 这是真正的转换逻辑,不包含错误处理。 - * - * @param value 温度值 - * @param unit 单位(C 或 F) - * @return 始终返回 true(如果调用说明数据有效) - */ - public static boolean convertWithValidatedInput(double value, String unit) { - if (unit.startsWith("C")) { - double f = celsiusToFahrenheit(value); - System.out.printf("%.2f °C = %.2f °F%n", value, f); - } else if (unit.startsWith("F")) { - double c = fahrenheitToCelsius(value); - System.out.printf("%.2f °F = %.2f °C%n", value, c); - } - return true; - } - - /** - * 处理单个温度转换(命令行/文件模式用,宽松验证)。 - * - * @param input 温度输入字符串,格式:"数值 单位",例如 "36.6 C" - * @return 转换成功返回true,失败返回false - */ - public static boolean convertSingleTemperature(String input) { - input = input.trim(); - - // 检查输入是否为空 - if (input.isEmpty()) { - return false; - } - - // 检查粘连单位并自动分离 - String[] adjacent = extractAdjacent(input); - if (adjacent != null) { - try { - return convertWithValidatedInput(Double.parseDouble(adjacent[0]), adjacent[1]); - } catch (NumberFormatException e) { - return false; - } - } - - // 按空白字符分割输入字符串 - String[] parts = input.split("\\s+"); - - try { - double value = Double.parseDouble(parts[0]); - // 如果没有单位,在命令行/文件模式中仍然尝试转换(宽松模式) - // 但在交互模式中会被 analyzeAndSuggestFix 拒绝 - String unit = (parts.length > 1) ? parts[1].toUpperCase() : "C"; - - if (unit.startsWith("C")) { - double f = celsiusToFahrenheit(value); - System.out.printf("%.2f °C = %.2f °F%n", value, f); - } else if (unit.startsWith("F")) { - double c = fahrenheitToCelsius(value); - System.out.printf("%.2f °F = %.2f °C%n", value, c); - } else { - return false; - } - return true; - - } catch (NumberFormatException e) { - return false; - } catch (Exception e) { - return false; - } - } - - /** - * 从文件中批量读取温度数据并进行转换。 - * 文件中每一行包含一个温度及其单位。 - * - * @param filename 文件路径 - */ - public static void convertFromFile(String filename) { - File file = new File(filename); - - if (!file.exists()) { - System.out.println("文件不存在:" + filename); - return; - } - - try { - Scanner fileScanner = new Scanner(file); - int lineNum = 0; - int successCount = 0; - - System.out.println("从文件读取温度数据:" + filename); - System.out.println("========================================"); - - while (fileScanner.hasNextLine()) { - lineNum++; - String line = fileScanner.nextLine(); - - // 跳过空行和注释行(以#开头) - if (line.trim().isEmpty() || line.trim().startsWith("#")) { - continue; - } - - System.out.print("第 " + lineNum + " 行:"); - if (convertSingleTemperature(line)) { - successCount++; - } - } - - System.out.println("========================================"); - System.out.println("处理完成:共处理 " + lineNum + " 行,成功转换 " + successCount + " 条。"); - - fileScanner.close(); - - } catch (FileNotFoundException e) { - System.out.println("无法打开文件:" + filename); - } - } - - /** - * 交互式模式:提示用户输入温度进行转换,使用智能诊断。 - */ - public static void interactiveMode() { - Scanner scanner = new Scanner(System.in); - - System.out.println("========================================"); - System.out.println("温度转换器 - 交互式模式"); - System.out.println("========================================"); - System.out.println("请输入要转换的温度与单位(例如 36.6 C 或 97 F)"); - System.out.println("输入 'quit' 或 'exit' 退出程序"); - System.out.println("========================================"); - - while (true) { - System.out.print("> "); - String input = scanner.nextLine(); - - if (input.equalsIgnoreCase("quit") || input.equalsIgnoreCase("exit")) { - System.out.println("程序退出。"); - break; - } - - // 在交互模式中使用智能诊断 - if (!analyzeAndSuggestFix(input, true)) { - // 诊断方法已经打印了具体的错误信息 - } - } - - scanner.close(); - } - - /** - * 显示使用说明。 - */ - public static void showUsage() { - System.out.println("温度转换器 - 使用说明"); - System.out.println("========================================"); - System.out.println("用法1(命令行参数模式):"); - System.out.println(" java TemperatureConverter_PytoJava <温度> <单位>"); - System.out.println(" 例如:java TemperatureConverter_PytoJava 36.6 C"); - System.out.println(); - System.out.println("用法2(批量转换模式):"); - System.out.println(" java TemperatureConverter_PytoJava <文件路径>"); - System.out.println(" 例如:java TemperatureConverter_PytoJava temperatures.txt"); - System.out.println(" 文件格式:每行一条温度数据,格式为 '温度 单位'"); - System.out.println(); - System.out.println("用法3(交互式模式):"); - System.out.println(" java TemperatureConverter_PytoJava"); - System.out.println(" 然后按提示输入温度数据"); - System.out.println("========================================"); - } - - /** - * 程序入口主方法。 - * 支持三种模式: - * 1. 命令行参数模式:java TemperatureConverter_PytoJava 36.6 C - * 2. 文件批量转换:java TemperatureConverter_PytoJava temperatures.txt - * 3. 交互式模式:java TemperatureConverter_PytoJava - * - * @param args 命令行参数 - */ - public static void main(String[] args) { - if (args.length == 0) { - // 交互式模式 - interactiveMode(); - } else if (args.length == 1) { - // 检查是否为文件路径(包含.txt或其他扩展名,或者是一个存在的文件) - File file = new File(args[0]); - if (file.exists() && file.isFile()) { - // 文件批量转换模式 - convertFromFile(args[0]); - } else if (args[0].equalsIgnoreCase("-h") || args[0].equalsIgnoreCase("--help")) { - // 显示帮助信息 - showUsage(); - } else { - // 可能是单个温度但缺少单位,默认使用摄氏度 - System.out.println("处理输入:" + args[0]); - convertSingleTemperature(args[0]); - } - } else if (args.length == 2) { - // 命令行参数模式:温度和单位 - convertSingleTemperature(args[0] + " " + args[1]); - } else { - showUsage(); - } - } -} diff --git a/W1-梁凯雄-202402050120/程序运行输出文本.txt b/W1-梁凯雄-202402050120/程序运行输出文本.txt deleted file mode 100644 index 98cd6bd..0000000 --- a/W1-梁凯雄-202402050120/程序运行输出文本.txt +++ /dev/null @@ -1,34 +0,0 @@ - 请输入数值(如 36.6, 100 等) - 示例:36.6 C -> 36.6c -? 已自动纠正输入:'36.6c' → '36.6 C' -36.60 °C = 97.88 °F -> 36.6f -? 已自动纠正输入:'36.6f' → '36.6 F' -36.60 °F = 2.56 °C -> 36.6 -?? 缺少温度单位! - 请指定 C(摄氏度)或 F(华氏度) - 示例:36.6 C 或 36.6 F -> 36.6 C -36.60 °C = 97.88 °F -> 97f -? 已自动纠正输入:'97f' → '97 F' -97.00 °F = 36.11 °C -> 97 f -97.00 °F = 36.11 °C -PS D:\VisualStudioProgram\VSCodePrograms\JavaLearningProject> java TemperatureConverter temperatures.txt -从文件读取温度数据:temperatures.txt -======================================== -第 6 行:36.60 °C = 97.88 °F -第 7 行:37.50 °C = 99.50 °F -第 10 行:25.00 °C = 77.00 °F -第 11 行:30.00 °C = 86.00 °F -第 14 行:98.60 °F = 37.00 °C -第 15 行:77.00 °F = 25.00 °C -第 18 行:0.00 °C = 32.00 °F -第 19 行:100.00 °C = 212.00 °F -第 20 行:32.00 °F = 0.00 °C -第 21 行:212.00 °F = 100.00 °C -======================================== -处理完成:共处理 21 行,成功转换 10 条。 \ No newline at end of file diff --git a/project/DEVELOPMENT.md b/project/DEVELOPMENT.md new file mode 100644 index 0000000..1ffcfa2 --- /dev/null +++ b/project/DEVELOPMENT.md @@ -0,0 +1,76 @@ +# 项目开发思路与步骤文档 (DEVELOPMENT.md) + +本文档详细记录了“电影评分网站影片数据抓取与分析项目”的完整开发思路、每一个步骤以及遇到的关键技术点。 + +--- + +## 一、 开发思路 (Development Philosophy) + +### 1. 目标设定 +开发一个基于 Java 的自动化工具,能够从豆瓣电影 Top 250 这种评分网站上自动化获取结构化的电影信息,并对其进行多维度的统计分析,最终将抽象的数据转化为直观的报告和可视化图表。 + +### 2. 技术选型 +- **核心语言**: Java 11 (利用其成熟的生态和强类型支持)。 +- **构建管理**: Maven (自动管理依赖和构建生命周期)。 +- **网页爬虫**: Jsoup (轻量级、强大的 HTML 解析库,支持 CSS 选择器)。 +- **数据分析**: Java 8+ Stream API (声明式的数据处理方式,高效且易于阅读)。 +- **数据序列化**: Jackson (工业级的 JSON 处理库)。 +- **可视化**: JFreeChart (老牌稳定的 Java 图表库)。 +- **代码简化**: Lombok (通过注解减少冗余的 Getter/Setter)。 + +### 3. 系统架构 (已升级为 Spring Boot 架构) +采用典型的 MVC 模式进行解耦,并引入 Web 层: +- **Model**: `Movie.java` (JPA 实体), `DirectorStats.java` (统计 DTO) +- **Repository**: `MovieRepository.java` (数据持久化层,支持 H2 数据库) +- **Service**: `MovieService.java` (业务逻辑与 Caffeine 缓存层) +- **Controller**: `DirectorController.java` (Web 接口与路由) +- **View**: Thymeleaf 模板 (响应式前端页面) +- **Crawler**: `MovieCrawler.java` (数据采集组件) + +--- + +## 二、 详细开发步骤 (Step-by-Step Guide) + +### 第 1 步:项目初始化与架构升级 +- 在 `pom.xml` 中引入 `spring-boot-starter-web`, `spring-boot-starter-data-jpa`, `spring-boot-starter-thymeleaf` 等。 +- 配置 H2 内存数据库和 Caffeine 缓存,确保接口响应时间在 500ms 以内。 + +### 第 2 步:定义数据实体与统计模型 +- 将 `Movie.java` 重构为 JPA 实体,增加 `id`, `type` (作品类型), `posterUrl` (海报链接) 等字段。 +- 创建 `DirectorStats.java` 用于承载导演作品数量、平均评分、总票房等聚合统计数据。 + +### 第 3 步:实现数据访问与业务逻辑 +- **Repository 层**: 使用 Spring Data JPA 的 `@Query` 编写复杂的聚合查询,支持按导演姓名搜索、作品类型过滤及分页统计。 +- **Service 层**: + - 集成 Spring Cache (@Cacheable),对耗时的排行榜查询进行 10 分钟缓存。 + - 实现 `refreshData` 方法,支持在爬虫抓取后自动更新数据库并清空缓存。 + +### 第 4 步:增强爬虫功能 +- 更新 `MovieCrawler.java`,利用 Jsoup 提取电影海报 URL 和作品类型。 +- 实现 `DataInitializer` (CommandLineRunner),在应用启动时自动触发爬取任务并将数据持久化到 H2 数据库。 + +### 第 5 步:开发响应式 Web 前端 +- 使用 **Bootstrap 5** 结合 **Thymeleaf** 开发导演排行榜页面 (`director_rankings.html`)。 +- **功能点**: + - **分页显示**: 默认每页 20 位导演。 + - **搜索与过滤**: 支持导演名模糊搜索和作品类型下拉过滤。 + - **交互设计**: 点击导演姓名可跳转至该导演的作品详情页 (`director_movies.html`)。 + - **加载状态**: 添加 Spinner 加载动画和响应时间监测。 + +### 第 6 步:性能优化与验证 +- 配置 Caffeine 缓存规格,平衡内存占用与响应速度。 +- 在 Controller 层记录请求耗时,确保在大数据量下依然满足 <500ms 的性能要求。 + +--- + +## 三、 遇到的挑战与解决方案 +1. **JPA 聚合查询复杂性**: 导演排行榜涉及 `COUNT`, `AVG`, `SUM` 等多个聚合函数。通过 `new com.movieratings.model.DirectorStats(...)` 构造器投影解决了 JPA 返回原始 Object 数组难以处理的问题。 +2. **响应式图片展示**: 海报图片大小不一。通过 CSS `object-fit: cover` 确保在排行榜缩略图和详情页大图显示中均保持比例协调。 +3. **缓存一致性**: 爬虫更新数据后,排行榜可能显示旧数据。通过 `@CacheEvict` 在保存数据时自动失效相关缓存。 + +--- + +## 四、 后续改进方向 +- 实现多线程爬虫以提高抓取速度。 +- 引入数据库(如 MySQL 或 SQLite)进行更长期的存储。 +- 开发一个简单的 GUI 界面(如 JavaFX)实时展示抓取过程。 diff --git a/project/ERROR_REPORT.md b/project/ERROR_REPORT.md new file mode 100644 index 0000000..0e32f9c --- /dev/null +++ b/project/ERROR_REPORT.md @@ -0,0 +1,88 @@ +# 项目错误扫描与修复报告 + +## 1. 扫描范围与方法 + +- 扫描范围:`d:/VisualStudioProgram/VSCodePrograms/JavaLearningProject/project` +- 扫描手段: + - Maven 编译与测试:`mvn clean compile test-compile`、`mvn test` + - 运行验证:`mvn spring-boot:run` 并访问 `/directors` + - IDE 诊断:语言服务诊断(未发现额外问题) + +## 2. 结论摘要 + +- 已发现并修复 2 个阻断编译的错误(P0)。 +- 当前项目可以通过编译与单元测试,并可启动 Spring Boot 服务并访问页面。 +- 仍存在若干运行期告警(P2),不阻断运行,但建议按需优化。 + +## 3. 错误清单(按优先级) + +### P0(阻断编译/必须立即修复) + +#### P0-1:`Movie` 中引用了不存在的字段 `year` + +- **错误类型**:编译错误 / 字段不存在 +- **错误描述**:`Movie` 类已将年份字段更名为 `releaseYear`,但 `toString()` 仍引用 `year`,导致编译失败。 +- **位置**:[Movie.java:L80-L92](file:///d:/VisualStudioProgram/VSCodePrograms/JavaLearningProject/project/src/main/java/com/movieratings/model/Movie.java#L80-L92) +- **修复方式**:将 `toString()` 中 `year` 替换为 `releaseYear`。 +- **修复状态**:已修复。 + +#### P0-2:`DataAnalyzer.countMoviesByYear` 引用了 `Movie::getYear` + +- **错误类型**:编译错误 / 方法不存在(方法引用失效) +- **错误描述**:`Movie` 已改为 `getReleaseYear()`,但 `countMoviesByYear` 仍用 `Movie::getYear`,导致编译失败。 +- **位置**:[DataAnalyzer.java:L108-L114](file:///d:/VisualStudioProgram/VSCodePrograms/JavaLearningProject/project/src/main/java/com/movieratings/analysis/DataAnalyzer.java#L108-L114) +- **修复方式**:将方法引用替换为 `Movie::getReleaseYear`。 +- **修复状态**:已修复。 + +### P1(影响功能/体验/性能,建议尽快处理) + +#### P1-1:启动时执行爬虫,可能导致启动时间不稳定 + +- **错误类型**:设计风险 / 稳定性风险 +- **现象**:应用启动时会抓取外网数据(`DataInitializer` + `MovieCrawler`),网络波动可能导致启动变慢或失败。 +- **位置**:[DataInitializer.java](file:///d:/VisualStudioProgram/VSCodePrograms/JavaLearningProject/project/src/main/java/com/movieratings/DataInitializer.java)、[MovieCrawler.java](file:///d:/VisualStudioProgram/VSCodePrograms/JavaLearningProject/project/src/main/java/com/movieratings/crawler/MovieCrawler.java) +- **修复建议**: + - 将初始化爬取改为“手动触发”(管理接口/按钮),或加开关参数(例如 profile/配置项); + - 增加超时与重试策略,并在失败时降级为使用本地缓存数据。 + +### P2(非阻断告警/可选优化) + +#### P2-1:Spring JPA `open-in-view` 默认开启告警 + +- **错误类型**:运行期告警 / 性能与一致性风险 +- **现象**:日志提示 `spring.jpa.open-in-view is enabled by default`。 +- **修复建议**:在 [application.properties](file:///d:/VisualStudioProgram/VSCodePrograms/JavaLearningProject/project/src/main/resources/application.properties) 中明确配置: + - `spring.jpa.open-in-view=false` + +#### P2-2:Java 25 运行 Maven/依赖触发的 native-access / Unsafe 告警 + +- **错误类型**:运行期告警 / 兼容性提示 +- **现象**:日志出现 `Restricted method in java.lang.System has been called`、`sun.misc.Unsafe` 等提示。 +- **修复建议**: + - 若需降低告警:使用较稳定的 LTS(例如 Java 17)运行项目; + - 或按告警指引添加 JVM 参数(视环境策略决定)。 + +#### P2-3:Hikari “Thread starvation or clock leap detected” 告警 + +- **错误类型**:运行期告警 / 环境与调度提示 +- **现象**:长时间挂起或系统时间跳变时出现 housekeeper delta 异常。 +- **修复建议**: + - 确认运行环境无长时间暂停/休眠或时间同步跳变; + - 如频繁出现,调整连接池参数并排查阻塞点(例如初始化阶段的长耗时任务)。 + +## 4. 修复步骤(可复现/可验证) + +1. 编译验证: + - `mvn clean compile test-compile` +2. 单元测试验证: + - `mvn test` +3. 运行验证: + - `mvn spring-boot:run` + - 访问:`http://localhost:8080/directors` + +## 5. 验证结果 + +- 编译:通过(`mvn clean compile test-compile`) +- 测试:通过(`mvn test`) +- 运行:可启动并可访问导演排行榜页面 + diff --git a/project/README.md b/project/README.md new file mode 100644 index 0000000..3469ef8 --- /dev/null +++ b/project/README.md @@ -0,0 +1,45 @@ +# 电影数据抓取与分析项目 (Java) + +本项目是《高级程序设计(Java)》课程的实践项目,主要功能是从豆瓣电影 Top 250 抓取影片数据,进行清洗、存储和多维度的统计分析,并以图表和报告的形式展示结果。 + +## 1. 核心任务 +- **数据源**: [豆瓣电影 Top 250](https://movie.douban.com/top250) +- **技术栈**: Java 11, Maven, Jsoup (爬虫), Jackson (JSON 解析), JFreeChart (图表生成) +- **分析维度**: 评分分布、评价人数排行、导演统计等。 + +## 2. 功能模块 +1. **网络爬虫 (`MovieCrawler`)**: 使用 Jsoup 模拟浏览器请求,抓取电影排名、标题、评分、年份、导演及评价人数。 +2. **数据清洗与模型 (`Movie`)**: 定义实体类,清洗抓取到的 HTML 文本并进行格式化。 +3. **数据分析 (`DataAnalyzer`)**: 使用 Java 8 Stream API 进行统计计算(平均分、评分段分布等)。 +4. **结果展示 (`ResultDisplay`)**: 控制台格式化输出表格,并生成 PNG 格式的评分分布图表。 +5. **持久化**: 将抓取到的数据以 JSON 格式保存至本地文件。 + +## 3. 运行指南 +### 3.1 环境要求 +- JDK 11+ +- Maven 3.6+ + +### 3.2 编译与运行 +在 `project` 目录下执行: + +**通用命令 (推荐)**: +```bash +mvn clean compile exec:java +``` + +**手动指定主类 (若需要)**: +- **Bash/CMD**: `mvn exec:java -Dexec.mainClass="com.movieratings.Main"` +- **PowerShell**: `mvn exec:java "-Dexec.mainClass=com.movieratings.Main"` + + +### 3.3 输出结果 +- **控制台**: 实时显示抓取进度、统计分析报告及格式化表格。 +- **本地文件**: + - `movies_data.json`: 完整的电影数据抓取结果。 + - `rating_chart.png`: 评分分布的可视化图表。 + +## 4. 依赖项 (pom.xml) +- `org.jsoup:jsoup`: 网页解析。 +- `com.fasterxml.jackson.core:jackson-databind`: JSON 序列化。 +- `org.jfree:jfreechart`: 图表绘制。 +- `org.projectlombok:lombok`: 简化实体类代码。 diff --git a/project/movies_analysis.csv b/project/movies_analysis.csv new file mode 100644 index 0000000..6a12e80 --- /dev/null +++ b/project/movies_analysis.csv @@ -0,0 +1,51 @@ +排名,标题,年份,评分,导演,国家,评价人数,模拟票房 +1,肖申克的救赎,1994,9.7,弗兰克·德拉邦特 Frank Darabont 主演: 蒂姆·罗宾斯 Tim Robbins /... 1994 / 美国 / 犯罪 剧情,美国,0,0.00 +2,霸王别姬,1993,9.6,陈凯歌 Kaige Chen 主演: 张国荣 Leslie Cheung / 张丰毅 Fengyi Zha... 1993 / 中国大陆 中国香港 / 剧情 爱情 同性,中国大陆 中国香港,0,0.00 +3,泰坦尼克号,1997,9.5,詹姆斯·卡梅隆 James Cameron 主演: 莱昂纳多·迪卡普里奥 Leonardo... 1997 / 美国 / 剧情 爱情 灾难,美国,0,0.00 +4,阿甘正传,1994,9.5,罗伯特·泽米吉斯 Robert Zemeckis 主演: 汤姆·汉克斯 Tom Hanks / ... 1994 / 美国 / 剧情 爱情,美国,0,0.00 +5,千与千寻,2001,9.4,宫崎骏 Hayao Miyazaki 主演: 柊瑠美 Rumi Hîragi / 入野自由 Miy... 2001 / 日本 / 剧情 动画 奇幻,日本,0,0.00 +6,美丽人生,1997,9.5,罗伯托·贝尼尼 Roberto Benigni 主演: 罗伯托·贝尼尼 Roberto Beni... 1997 / 意大利 / 剧情 喜剧 爱情 战争,意大利,0,0.00 +7,星际穿越,2014,9.4,克里斯托弗·诺兰 Christopher Nolan 主演: 马修·麦康纳 Matthew Mc... 2014 / 美国 英国 加拿大 / 剧情 科幻 冒险,美国 英国 加拿大,0,0.00 +8,这个杀手不太冷,1994,9.4,吕克·贝松 Luc Besson 主演: 让·雷诺 Jean Reno / 娜塔莉·波特曼 ... 1994 / 法国 美国 / 剧情 动作 犯罪,法国 美国,0,0.00 +9,盗梦空间,2010,9.4,克里斯托弗·诺兰 Christopher Nolan 主演: 莱昂纳多·迪卡普里奥 Le... 2010 / 美国 英国 / 剧情 科幻 悬疑 冒险,美国 英国,0,0.00 +10,楚门的世界,1998,9.4,彼得·威尔 Peter Weir 主演: 金·凯瑞 Jim Carrey / 劳拉·琳妮 Lau... 1998 / 美国 / 剧情 科幻,美国,0,0.00 +11,辛德勒的名单,1993,9.5,史蒂文·斯皮尔伯格 Steven Spielberg 主演: 连姆·尼森 Liam Neeson... 1993 / 美国 / 剧情 历史 战争,美国,0,0.00 +12,忠犬八公的故事,2009,9.4,莱塞·霍尔斯道姆 Lasse Hallström 主演: 理查·基尔 Richard Ger... 2009 / 美国 英国 / 剧情,美国 英国,0,0.00 +13,海上钢琴师,1998,9.3,朱塞佩·托纳多雷 Giuseppe Tornatore 主演: 蒂姆·罗斯 Tim Roth / ... 1998 / 意大利 / 剧情 音乐,意大利,0,0.00 +14,疯狂动物城,2016,9.3,拜伦·霍华德 Byron Howard / 瑞奇·摩尔 Rich Moore 主演: 金妮弗·... 2016 / 美国 / 喜剧 动画 冒险,美国,0,0.00 +15,三傻大闹宝莱坞,2009,9.2,拉库马·希拉尼 Rajkumar Hirani 主演: 阿米尔·汗 Aamir Khan / 卡... 2009 / 印度 / 剧情 喜剧 爱情 歌舞,印度,0,0.00 +16,机器人总动员,2008,9.3,安德鲁·斯坦顿 Andrew Stanton 主演: 本·贝尔特 Ben Burtt / 艾丽... 2008 / 美国 / 科幻 动画 冒险,美国,0,0.00 +17,放牛班的春天,2004,9.3,克里斯托夫·巴拉蒂 Christophe Barratier 主演: 让-巴蒂斯特·莫尼... 2004 / 法国 瑞士 德国 / 剧情 音乐,法国 瑞士 德国,0,0.00 +18,无间道,2002,9.3,刘伟强 / 麦兆辉 主演: 刘德华 Andy Lau / 梁朝伟 Tony Leung Chiu W... 2002 / 中国香港 / 剧情 犯罪 惊悚,中国香港,0,0.00 +19,控方证人,1957,9.6,比利·怀尔德 Billy Wilder 主演: 泰隆·鲍华 Tyrone Power / 玛琳·... 1957 / 美国 / 剧情 犯罪 悬疑 惊悚,美国,0,0.00 +20,寻梦环游记,2017,9.1,李·昂克里奇 Lee Unkrich / 阿德里安·莫利纳 Adrian Molina 主演: ... 2017 / 美国 / 喜剧 动画 奇幻 音乐,美国,0,0.00 +21,大话西游之大圣娶亲,1995,9.2,刘镇伟 Jeffrey Lau 主演: 周星驰 Stephen Chow / 吴孟达 Man Tat Ng... 1995 / 中国香港 中国大陆 / 喜剧 爱情 奇幻 古装,中国香港 中国大陆,0,0.00 +22,熔炉,2011,9.3,黄东赫 Dong-hyuk Hwang 主演: 孔侑 Yoo Gong / 郑有美 Yu-mi Jung /... 2011 / 韩国 / 剧情,韩国,0,0.00 +23,触不可及,2011,9.3,奥利维·那卡什 Olivier Nakache / 艾力克·托兰达 Eric Toledano 主... 2011 / 法国 / 剧情 喜剧,法国,0,0.00 +24,教父,1972,9.3,弗朗西斯·福特·科波拉 Francis Ford Coppola 主演: 马龙·白兰度 M... 1972 / 美国 / 剧情 犯罪,美国,0,0.00 +25,末代皇帝,1987,9.3,贝纳尔多·贝托鲁奇 Bernardo Bertolucci 主演: 尊龙 John Lone / 陈... 1987 / 英国 意大利 中国大陆 法国 / 剧情 传记 历史,英国 意大利 中国大陆 法国,0,0.00 +26,哈利·波特与魔法石,2001,9.2,Chris Columbus 主演: Daniel Radcliffe / Emma Watson / Rupert Grint 2001 / 美国 英国 / 奇幻 冒险,美国 英国,0,0.00 +27,当幸福来敲门,2006,9.1,加布里尔·穆奇诺 Gabriele Muccino 主演: 威尔·史密斯 Will Smith ... 2006 / 美国 / 剧情 传记 家庭,美国,0,0.00 +28,龙猫,1988,9.2,宫崎骏 Hayao Miyazaki 主演: 日高法子 Noriko Hidaka / 坂本千夏 Ch... 1988 / 日本 / 动画 奇幻 冒险,日本,0,0.00 +29,活着,1994,9.3,张艺谋 Yimou Zhang 主演: 葛优 You Ge / 巩俐 Li Gong / 姜武 Wu Jiang 1994 / 中国大陆 中国香港 / 剧情 历史 家庭,中国大陆 中国香港,0,0.00 +30,怦然心动,2010,9.1,罗伯·莱纳 Rob Reiner 主演: 玛德琳·卡罗尔 Madeline Carroll / 卡... 2010 / 美国 / 剧情 喜剧 爱情,美国,0,0.00 +31,蝙蝠侠:黑暗骑士,2008,9.2,克里斯托弗·诺兰 Christopher Nolan 主演: 克里斯蒂安·贝尔 Christ... 2008 / 美国 英国 / 剧情 动作 科幻 犯罪 惊悚,美国 英国,0,0.00 +32,指环王3:王者无敌,2003,9.3,彼得·杰克逊 Peter Jackson 主演: 伊利亚·伍德 Elijah Wood / 西恩... 2003 / 美国 新西兰 / 剧情 动作 奇幻 冒险,美国 新西兰,0,0.00 +33,我不是药神,2018,9.0,文牧野 Muye Wen 主演: 徐峥 Zheng Xu / 王传君 Chuanjun Wang / 周... 2018 / 中国大陆 / 剧情 喜剧,中国大陆,0,0.00 +34,乱世佳人,1939,9.3,维克多·弗莱明 Victor Fleming / 乔治·库克 George Cukor 主演: 费... 1939 / 美国 / 剧情 历史 爱情 战争,美国,0,0.00 +35,飞屋环游记,2009,9.1,彼特·道格特 Pete Docter / 鲍勃·彼德森 Bob Peterson 主演: 爱德... 2009 / 美国 / 剧情 喜剧 动画 冒险,美国,0,0.00 +36,让子弹飞,2010,9.0,姜文 Wen Jiang 主演: 姜文 Wen Jiang / 葛优 You Ge / 周润发 Yun-F... 2010 / 中国大陆 中国香港 / 剧情 喜剧 动作 西部,中国大陆 中国香港,0,0.00 +37,哈尔的移动城堡,2004,9.1,宫崎骏 Hayao Miyazaki 主演: 倍赏千惠子 Chieko Baishô / 木村拓... 2004 / 日本 / 爱情 动画 奇幻 冒险,日本,0,0.00 +38,十二怒汉,1957,9.4,西德尼·吕美特 Sidney Lumet 主演: 亨利·方达 Henry Fonda / 马丁... 1957 / 美国 / 剧情,美国,0,0.00 +39,海蒂和爷爷,2015,9.3,阿兰·葛斯彭纳 Alain Gsponer 主演: 阿努克·斯特芬 Anuk Steffen /... 2015 / 德国 瑞士 / 剧情 冒险 家庭,德国 瑞士,0,0.00 +40,素媛,2013,9.3,李濬益 Jun-ik Lee 主演: 薛景求 Kyung-gu Sol / 严志媛 Ji-won Uhm ... 2013 / 韩国 / 剧情,韩国,0,0.00 +41,猫鼠游戏,2002,9.1,史蒂文·斯皮尔伯格 Steven Spielberg 主演: 莱昂纳多·迪卡普里奥 L... 2002 / 美国 加拿大 / 传记 犯罪 剧情,美国 加拿大,0,0.00 +42,天空之城,1986,9.2,宫崎骏 Hayao Miyazaki 主演: 田中真弓 Mayumi Tanaka / 横泽启子 Ke... 1986 / 日本 / 动画 奇幻 冒险,日本,0,0.00 +43,鬼子来了,2000,9.3,姜文 Wen Jiang 主演: 姜文 Wen Jiang / 香川照之 Teruyuki Kagawa /... 2000 / 中国大陆 / 剧情 喜剧,中国大陆,0,0.00 +44,摔跤吧!爸爸,2016,9.0,涅提·蒂瓦里 Nitesh Tiwari 主演: 阿米尔·汗 Aamir Khan / 法缇玛... 2016 / 印度 / 剧情 传记 运动 家庭,印度,0,0.00 +45,少年派的奇幻漂流,2012,9.1,李安 Ang Lee 主演: 苏拉·沙玛 Suraj Sharma / 伊尔凡·可汗 Irrfan... 2012 / 美国 中国台湾 英国 加拿大 / 剧情 奇幻 冒险,美国 中国台湾 英国 加拿大,0,0.00 +46,钢琴家,2002,9.3,罗曼·波兰斯基 Roman Polanski 主演: 艾德里安·布洛迪 Adrien Brod... 2002 / 英国 法国 波兰 德国 美国 / 剧情 传记 战争 音乐,英国 法国 波兰 德国 美国,0,0.00 +47,指环王2:双塔奇兵,2002,9.2,彼得·杰克逊 Peter Jackson 主演: 伊利亚·伍德 Elijah Wood / 西恩... 2002 / 美国 新西兰 / 剧情 动作 奇幻 冒险,美国 新西兰,0,0.00 +48,死亡诗社,1989,9.2,彼得·威尔 Peter Weir 主演: 罗宾·威廉姆斯 Robin Williams / 罗伯... 1989 / 美国 / 剧情,美国,0,0.00 +49,大话西游之月光宝盒,1995,9.0,刘镇伟 Jeffrey Lau 主演: 周星驰 Stephen Chow / 吴孟达 Man Tat Ng... 1995 / 中国香港 中国大陆 / 喜剧 爱情 奇幻 古装,中国香港 中国大陆,0,0.00 +50,绿皮书,2018,8.9,彼得·法雷里 Peter Farrelly 主演: 维果·莫腾森 Viggo Mortensen /... 2018 / 美国 中国大陆 / 剧情 喜剧 传记 音乐,美国 中国大陆,0,0.00 diff --git a/project/movies_data.json b/project/movies_data.json new file mode 100644 index 0000000..0f9ca75 --- /dev/null +++ b/project/movies_data.json @@ -0,0 +1,501 @@ +[ { + "title" : "肖申克的救赎", + "rating" : 9.7, + "year" : 1994, + "rank" : 1, + "quote" : "", + "director" : "弗兰克·德拉邦特 Frank Darabont 主演: 蒂姆·罗宾斯 Tim Robbins /... 1994 / 美国 / 犯罪 剧情", + "reviewCount" : 0, + "country" : "美国", + "boxOffice" : 0.0 +}, { + "title" : "霸王别姬", + "rating" : 9.6, + "year" : 1993, + "rank" : 2, + "quote" : "", + "director" : "陈凯歌 Kaige Chen 主演: 张国荣 Leslie Cheung / 张丰毅 Fengyi Zha... 1993 / 中国大陆 中国香港 / 剧情 爱情 同性", + "reviewCount" : 0, + "country" : "中国大陆 中国香港", + "boxOffice" : 0.0 +}, { + "title" : "泰坦尼克号", + "rating" : 9.5, + "year" : 1997, + "rank" : 3, + "quote" : "", + "director" : "詹姆斯·卡梅隆 James Cameron 主演: 莱昂纳多·迪卡普里奥 Leonardo... 1997 / 美国 / 剧情 爱情 灾难", + "reviewCount" : 0, + "country" : "美国", + "boxOffice" : 0.0 +}, { + "title" : "阿甘正传", + "rating" : 9.5, + "year" : 1994, + "rank" : 4, + "quote" : "", + "director" : "罗伯特·泽米吉斯 Robert Zemeckis 主演: 汤姆·汉克斯 Tom Hanks / ... 1994 / 美国 / 剧情 爱情", + "reviewCount" : 0, + "country" : "美国", + "boxOffice" : 0.0 +}, { + "title" : "千与千寻", + "rating" : 9.4, + "year" : 2001, + "rank" : 5, + "quote" : "", + "director" : "宫崎骏 Hayao Miyazaki 主演: 柊瑠美 Rumi Hîragi / 入野自由 Miy... 2001 / 日本 / 剧情 动画 奇幻", + "reviewCount" : 0, + "country" : "日本", + "boxOffice" : 0.0 +}, { + "title" : "美丽人生", + "rating" : 9.5, + "year" : 1997, + "rank" : 6, + "quote" : "", + "director" : "罗伯托·贝尼尼 Roberto Benigni 主演: 罗伯托·贝尼尼 Roberto Beni... 1997 / 意大利 / 剧情 喜剧 爱情 战争", + "reviewCount" : 0, + "country" : "意大利", + "boxOffice" : 0.0 +}, { + "title" : "星际穿越", + "rating" : 9.4, + "year" : 2014, + "rank" : 7, + "quote" : "", + "director" : "克里斯托弗·诺兰 Christopher Nolan 主演: 马修·麦康纳 Matthew Mc... 2014 / 美国 英国 加拿大 / 剧情 科幻 冒险", + "reviewCount" : 0, + "country" : "美国 英国 加拿大", + "boxOffice" : 0.0 +}, { + "title" : "这个杀手不太冷", + "rating" : 9.4, + "year" : 1994, + "rank" : 8, + "quote" : "", + "director" : "吕克·贝松 Luc Besson 主演: 让·雷诺 Jean Reno / 娜塔莉·波特曼 ... 1994 / 法国 美国 / 剧情 动作 犯罪", + "reviewCount" : 0, + "country" : "法国 美国", + "boxOffice" : 0.0 +}, { + "title" : "盗梦空间", + "rating" : 9.4, + "year" : 2010, + "rank" : 9, + "quote" : "", + "director" : "克里斯托弗·诺兰 Christopher Nolan 主演: 莱昂纳多·迪卡普里奥 Le... 2010 / 美国 英国 / 剧情 科幻 悬疑 冒险", + "reviewCount" : 0, + "country" : "美国 英国", + "boxOffice" : 0.0 +}, { + "title" : "楚门的世界", + "rating" : 9.4, + "year" : 1998, + "rank" : 10, + "quote" : "", + "director" : "彼得·威尔 Peter Weir 主演: 金·凯瑞 Jim Carrey / 劳拉·琳妮 Lau... 1998 / 美国 / 剧情 科幻", + "reviewCount" : 0, + "country" : "美国", + "boxOffice" : 0.0 +}, { + "title" : "辛德勒的名单", + "rating" : 9.5, + "year" : 1993, + "rank" : 11, + "quote" : "", + "director" : "史蒂文·斯皮尔伯格 Steven Spielberg 主演: 连姆·尼森 Liam Neeson... 1993 / 美国 / 剧情 历史 战争", + "reviewCount" : 0, + "country" : "美国", + "boxOffice" : 0.0 +}, { + "title" : "忠犬八公的故事", + "rating" : 9.4, + "year" : 2009, + "rank" : 12, + "quote" : "", + "director" : "莱塞·霍尔斯道姆 Lasse Hallström 主演: 理查·基尔 Richard Ger... 2009 / 美国 英国 / 剧情", + "reviewCount" : 0, + "country" : "美国 英国", + "boxOffice" : 0.0 +}, { + "title" : "海上钢琴师", + "rating" : 9.3, + "year" : 1998, + "rank" : 13, + "quote" : "", + "director" : "朱塞佩·托纳多雷 Giuseppe Tornatore 主演: 蒂姆·罗斯 Tim Roth / ... 1998 / 意大利 / 剧情 音乐", + "reviewCount" : 0, + "country" : "意大利", + "boxOffice" : 0.0 +}, { + "title" : "疯狂动物城", + "rating" : 9.3, + "year" : 2016, + "rank" : 14, + "quote" : "", + "director" : "拜伦·霍华德 Byron Howard / 瑞奇·摩尔 Rich Moore 主演: 金妮弗·... 2016 / 美国 / 喜剧 动画 冒险", + "reviewCount" : 0, + "country" : "美国", + "boxOffice" : 0.0 +}, { + "title" : "三傻大闹宝莱坞", + "rating" : 9.2, + "year" : 2009, + "rank" : 15, + "quote" : "", + "director" : "拉库马·希拉尼 Rajkumar Hirani 主演: 阿米尔·汗 Aamir Khan / 卡... 2009 / 印度 / 剧情 喜剧 爱情 歌舞", + "reviewCount" : 0, + "country" : "印度", + "boxOffice" : 0.0 +}, { + "title" : "机器人总动员", + "rating" : 9.3, + "year" : 2008, + "rank" : 16, + "quote" : "", + "director" : "安德鲁·斯坦顿 Andrew Stanton 主演: 本·贝尔特 Ben Burtt / 艾丽... 2008 / 美国 / 科幻 动画 冒险", + "reviewCount" : 0, + "country" : "美国", + "boxOffice" : 0.0 +}, { + "title" : "放牛班的春天", + "rating" : 9.3, + "year" : 2004, + "rank" : 17, + "quote" : "", + "director" : "克里斯托夫·巴拉蒂 Christophe Barratier 主演: 让-巴蒂斯特·莫尼... 2004 / 法国 瑞士 德国 / 剧情 音乐", + "reviewCount" : 0, + "country" : "法国 瑞士 德国", + "boxOffice" : 0.0 +}, { + "title" : "无间道", + "rating" : 9.3, + "year" : 2002, + "rank" : 18, + "quote" : "", + "director" : "刘伟强 / 麦兆辉 主演: 刘德华 Andy Lau / 梁朝伟 Tony Leung Chiu W... 2002 / 中国香港 / 剧情 犯罪 惊悚", + "reviewCount" : 0, + "country" : "中国香港", + "boxOffice" : 0.0 +}, { + "title" : "控方证人", + "rating" : 9.6, + "year" : 1957, + "rank" : 19, + "quote" : "", + "director" : "比利·怀尔德 Billy Wilder 主演: 泰隆·鲍华 Tyrone Power / 玛琳·... 1957 / 美国 / 剧情 犯罪 悬疑 惊悚", + "reviewCount" : 0, + "country" : "美国", + "boxOffice" : 0.0 +}, { + "title" : "寻梦环游记", + "rating" : 9.1, + "year" : 2017, + "rank" : 20, + "quote" : "", + "director" : "李·昂克里奇 Lee Unkrich / 阿德里安·莫利纳 Adrian Molina 主演: ... 2017 / 美国 / 喜剧 动画 奇幻 音乐", + "reviewCount" : 0, + "country" : "美国", + "boxOffice" : 0.0 +}, { + "title" : "大话西游之大圣娶亲", + "rating" : 9.2, + "year" : 1995, + "rank" : 21, + "quote" : "", + "director" : "刘镇伟 Jeffrey Lau 主演: 周星驰 Stephen Chow / 吴孟达 Man Tat Ng... 1995 / 中国香港 中国大陆 / 喜剧 爱情 奇幻 古装", + "reviewCount" : 0, + "country" : "中国香港 中国大陆", + "boxOffice" : 0.0 +}, { + "title" : "熔炉", + "rating" : 9.3, + "year" : 2011, + "rank" : 22, + "quote" : "", + "director" : "黄东赫 Dong-hyuk Hwang 主演: 孔侑 Yoo Gong / 郑有美 Yu-mi Jung /... 2011 / 韩国 / 剧情", + "reviewCount" : 0, + "country" : "韩国", + "boxOffice" : 0.0 +}, { + "title" : "触不可及", + "rating" : 9.3, + "year" : 2011, + "rank" : 23, + "quote" : "", + "director" : "奥利维·那卡什 Olivier Nakache / 艾力克·托兰达 Eric Toledano 主... 2011 / 法国 / 剧情 喜剧", + "reviewCount" : 0, + "country" : "法国", + "boxOffice" : 0.0 +}, { + "title" : "教父", + "rating" : 9.3, + "year" : 1972, + "rank" : 24, + "quote" : "", + "director" : "弗朗西斯·福特·科波拉 Francis Ford Coppola 主演: 马龙·白兰度 M... 1972 / 美国 / 剧情 犯罪", + "reviewCount" : 0, + "country" : "美国", + "boxOffice" : 0.0 +}, { + "title" : "末代皇帝", + "rating" : 9.3, + "year" : 1987, + "rank" : 25, + "quote" : "", + "director" : "贝纳尔多·贝托鲁奇 Bernardo Bertolucci 主演: 尊龙 John Lone / 陈... 1987 / 英国 意大利 中国大陆 法国 / 剧情 传记 历史", + "reviewCount" : 0, + "country" : "英国 意大利 中国大陆 法国", + "boxOffice" : 0.0 +}, { + "title" : "哈利·波特与魔法石", + "rating" : 9.2, + "year" : 2001, + "rank" : 26, + "quote" : "", + "director" : "Chris Columbus 主演: Daniel Radcliffe / Emma Watson / Rupert Grint 2001 / 美国 英国 / 奇幻 冒险", + "reviewCount" : 0, + "country" : "美国 英国", + "boxOffice" : 0.0 +}, { + "title" : "当幸福来敲门", + "rating" : 9.1, + "year" : 2006, + "rank" : 27, + "quote" : "", + "director" : "加布里尔·穆奇诺 Gabriele Muccino 主演: 威尔·史密斯 Will Smith ... 2006 / 美国 / 剧情 传记 家庭", + "reviewCount" : 0, + "country" : "美国", + "boxOffice" : 0.0 +}, { + "title" : "龙猫", + "rating" : 9.2, + "year" : 1988, + "rank" : 28, + "quote" : "", + "director" : "宫崎骏 Hayao Miyazaki 主演: 日高法子 Noriko Hidaka / 坂本千夏 Ch... 1988 / 日本 / 动画 奇幻 冒险", + "reviewCount" : 0, + "country" : "日本", + "boxOffice" : 0.0 +}, { + "title" : "活着", + "rating" : 9.3, + "year" : 1994, + "rank" : 29, + "quote" : "", + "director" : "张艺谋 Yimou Zhang 主演: 葛优 You Ge / 巩俐 Li Gong / 姜武 Wu Jiang 1994 / 中国大陆 中国香港 / 剧情 历史 家庭", + "reviewCount" : 0, + "country" : "中国大陆 中国香港", + "boxOffice" : 0.0 +}, { + "title" : "怦然心动", + "rating" : 9.1, + "year" : 2010, + "rank" : 30, + "quote" : "", + "director" : "罗伯·莱纳 Rob Reiner 主演: 玛德琳·卡罗尔 Madeline Carroll / 卡... 2010 / 美国 / 剧情 喜剧 爱情", + "reviewCount" : 0, + "country" : "美国", + "boxOffice" : 0.0 +}, { + "title" : "蝙蝠侠:黑暗骑士", + "rating" : 9.2, + "year" : 2008, + "rank" : 31, + "quote" : "", + "director" : "克里斯托弗·诺兰 Christopher Nolan 主演: 克里斯蒂安·贝尔 Christ... 2008 / 美国 英国 / 剧情 动作 科幻 犯罪 惊悚", + "reviewCount" : 0, + "country" : "美国 英国", + "boxOffice" : 0.0 +}, { + "title" : "指环王3:王者无敌", + "rating" : 9.3, + "year" : 2003, + "rank" : 32, + "quote" : "", + "director" : "彼得·杰克逊 Peter Jackson 主演: 伊利亚·伍德 Elijah Wood / 西恩... 2003 / 美国 新西兰 / 剧情 动作 奇幻 冒险", + "reviewCount" : 0, + "country" : "美国 新西兰", + "boxOffice" : 0.0 +}, { + "title" : "我不是药神", + "rating" : 9.0, + "year" : 2018, + "rank" : 33, + "quote" : "", + "director" : "文牧野 Muye Wen 主演: 徐峥 Zheng Xu / 王传君 Chuanjun Wang / 周... 2018 / 中国大陆 / 剧情 喜剧", + "reviewCount" : 0, + "country" : "中国大陆", + "boxOffice" : 0.0 +}, { + "title" : "乱世佳人", + "rating" : 9.3, + "year" : 1939, + "rank" : 34, + "quote" : "", + "director" : "维克多·弗莱明 Victor Fleming / 乔治·库克 George Cukor 主演: 费... 1939 / 美国 / 剧情 历史 爱情 战争", + "reviewCount" : 0, + "country" : "美国", + "boxOffice" : 0.0 +}, { + "title" : "飞屋环游记", + "rating" : 9.1, + "year" : 2009, + "rank" : 35, + "quote" : "", + "director" : "彼特·道格特 Pete Docter / 鲍勃·彼德森 Bob Peterson 主演: 爱德... 2009 / 美国 / 剧情 喜剧 动画 冒险", + "reviewCount" : 0, + "country" : "美国", + "boxOffice" : 0.0 +}, { + "title" : "让子弹飞", + "rating" : 9.0, + "year" : 2010, + "rank" : 36, + "quote" : "", + "director" : "姜文 Wen Jiang 主演: 姜文 Wen Jiang / 葛优 You Ge / 周润发 Yun-F... 2010 / 中国大陆 中国香港 / 剧情 喜剧 动作 西部", + "reviewCount" : 0, + "country" : "中国大陆 中国香港", + "boxOffice" : 0.0 +}, { + "title" : "哈尔的移动城堡", + "rating" : 9.1, + "year" : 2004, + "rank" : 37, + "quote" : "", + "director" : "宫崎骏 Hayao Miyazaki 主演: 倍赏千惠子 Chieko Baishô / 木村拓... 2004 / 日本 / 爱情 动画 奇幻 冒险", + "reviewCount" : 0, + "country" : "日本", + "boxOffice" : 0.0 +}, { + "title" : "十二怒汉", + "rating" : 9.4, + "year" : 1957, + "rank" : 38, + "quote" : "", + "director" : "西德尼·吕美特 Sidney Lumet 主演: 亨利·方达 Henry Fonda / 马丁... 1957 / 美国 / 剧情", + "reviewCount" : 0, + "country" : "美国", + "boxOffice" : 0.0 +}, { + "title" : "海蒂和爷爷", + "rating" : 9.3, + "year" : 2015, + "rank" : 39, + "quote" : "", + "director" : "阿兰·葛斯彭纳 Alain Gsponer 主演: 阿努克·斯特芬 Anuk Steffen /... 2015 / 德国 瑞士 / 剧情 冒险 家庭", + "reviewCount" : 0, + "country" : "德国 瑞士", + "boxOffice" : 0.0 +}, { + "title" : "素媛", + "rating" : 9.3, + "year" : 2013, + "rank" : 40, + "quote" : "", + "director" : "李濬益 Jun-ik Lee 主演: 薛景求 Kyung-gu Sol / 严志媛 Ji-won Uhm ... 2013 / 韩国 / 剧情", + "reviewCount" : 0, + "country" : "韩国", + "boxOffice" : 0.0 +}, { + "title" : "猫鼠游戏", + "rating" : 9.1, + "year" : 2002, + "rank" : 41, + "quote" : "", + "director" : "史蒂文·斯皮尔伯格 Steven Spielberg 主演: 莱昂纳多·迪卡普里奥 L... 2002 / 美国 加拿大 / 传记 犯罪 剧情", + "reviewCount" : 0, + "country" : "美国 加拿大", + "boxOffice" : 0.0 +}, { + "title" : "天空之城", + "rating" : 9.2, + "year" : 1986, + "rank" : 42, + "quote" : "", + "director" : "宫崎骏 Hayao Miyazaki 主演: 田中真弓 Mayumi Tanaka / 横泽启子 Ke... 1986 / 日本 / 动画 奇幻 冒险", + "reviewCount" : 0, + "country" : "日本", + "boxOffice" : 0.0 +}, { + "title" : "鬼子来了", + "rating" : 9.3, + "year" : 2000, + "rank" : 43, + "quote" : "", + "director" : "姜文 Wen Jiang 主演: 姜文 Wen Jiang / 香川照之 Teruyuki Kagawa /... 2000 / 中国大陆 / 剧情 喜剧", + "reviewCount" : 0, + "country" : "中国大陆", + "boxOffice" : 0.0 +}, { + "title" : "摔跤吧!爸爸", + "rating" : 9.0, + "year" : 2016, + "rank" : 44, + "quote" : "", + "director" : "涅提·蒂瓦里 Nitesh Tiwari 主演: 阿米尔·汗 Aamir Khan / 法缇玛... 2016 / 印度 / 剧情 传记 运动 家庭", + "reviewCount" : 0, + "country" : "印度", + "boxOffice" : 0.0 +}, { + "title" : "少年派的奇幻漂流", + "rating" : 9.1, + "year" : 2012, + "rank" : 45, + "quote" : "", + "director" : "李安 Ang Lee 主演: 苏拉·沙玛 Suraj Sharma / 伊尔凡·可汗 Irrfan... 2012 / 美国 中国台湾 英国 加拿大 / 剧情 奇幻 冒险", + "reviewCount" : 0, + "country" : "美国 中国台湾 英国 加拿大", + "boxOffice" : 0.0 +}, { + "title" : "钢琴家", + "rating" : 9.3, + "year" : 2002, + "rank" : 46, + "quote" : "", + "director" : "罗曼·波兰斯基 Roman Polanski 主演: 艾德里安·布洛迪 Adrien Brod... 2002 / 英国 法国 波兰 德国 美国 / 剧情 传记 战争 音乐", + "reviewCount" : 0, + "country" : "英国 法国 波兰 德国 美国", + "boxOffice" : 0.0 +}, { + "title" : "指环王2:双塔奇兵", + "rating" : 9.2, + "year" : 2002, + "rank" : 47, + "quote" : "", + "director" : "彼得·杰克逊 Peter Jackson 主演: 伊利亚·伍德 Elijah Wood / 西恩... 2002 / 美国 新西兰 / 剧情 动作 奇幻 冒险", + "reviewCount" : 0, + "country" : "美国 新西兰", + "boxOffice" : 0.0 +}, { + "title" : "死亡诗社", + "rating" : 9.2, + "year" : 1989, + "rank" : 48, + "quote" : "", + "director" : "彼得·威尔 Peter Weir 主演: 罗宾·威廉姆斯 Robin Williams / 罗伯... 1989 / 美国 / 剧情", + "reviewCount" : 0, + "country" : "美国", + "boxOffice" : 0.0 +}, { + "title" : "大话西游之月光宝盒", + "rating" : 9.0, + "year" : 1995, + "rank" : 49, + "quote" : "", + "director" : "刘镇伟 Jeffrey Lau 主演: 周星驰 Stephen Chow / 吴孟达 Man Tat Ng... 1995 / 中国香港 中国大陆 / 喜剧 爱情 奇幻 古装", + "reviewCount" : 0, + "country" : "中国香港 中国大陆", + "boxOffice" : 0.0 +}, { + "title" : "绿皮书", + "rating" : 8.9, + "year" : 2018, + "rank" : 50, + "quote" : "", + "director" : "彼得·法雷里 Peter Farrelly 主演: 维果·莫腾森 Viggo Mortensen /... 2018 / 美国 中国大陆 / 剧情 喜剧 传记 音乐", + "reviewCount" : 0, + "country" : "美国 中国大陆", + "boxOffice" : 0.0 +} ] \ No newline at end of file diff --git a/project/pom.xml b/project/pom.xml new file mode 100644 index 0000000..30807a6 --- /dev/null +++ b/project/pom.xml @@ -0,0 +1,98 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 2.7.12 + + + + com.movieratings + movie-data-crawler + 1.0-SNAPSHOT + + + 11 + UTF-8 + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + org.springframework.boot + spring-boot-starter-cache + + + + + com.h2database + h2 + runtime + + + + + com.github.ben-manes.caffeine + caffeine + + + + + org.jsoup + jsoup + 1.15.3 + + + + + com.fasterxml.jackson.core + jackson-databind + + + + + org.jfree + jfreechart + 1.5.3 + + + + + org.apache.commons + commons-math3 + 3.6.1 + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/project/rating_chart.png b/project/rating_chart.png new file mode 100644 index 0000000..b16d06a Binary files /dev/null and b/project/rating_chart.png differ diff --git a/project/rating_distribution.png b/project/rating_distribution.png new file mode 100644 index 0000000..3ca655d Binary files /dev/null and b/project/rating_distribution.png differ diff --git a/project/run.log b/project/run.log new file mode 100644 index 0000000..2535edc Binary files /dev/null and b/project/run.log differ diff --git a/project/src/main/java/com/movieratings/DataInitializer.java b/project/src/main/java/com/movieratings/DataInitializer.java new file mode 100644 index 0000000..1828761 --- /dev/null +++ b/project/src/main/java/com/movieratings/DataInitializer.java @@ -0,0 +1,43 @@ +package com.movieratings; + +import com.movieratings.crawler.MovieCrawler; +import com.movieratings.model.Movie; +import com.movieratings.service.MovieService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * 数据初始化类,启动时自动抓取并保存数据 + */ +@Component +public class DataInitializer implements CommandLineRunner { + + @Autowired + private MovieCrawler movieCrawler; + + @Autowired + private MovieService movieService; + + @Override + public void run(String... args) throws Exception { + System.out.println(">>> 正在进行数据初始化..."); + + // 抓取前 100 条数据作为演示 + List movies = movieCrawler.crawl(100); + + // 为了演示过滤功能,手动修改一部分数据为 "电视剧" 和 "纪录片" + if (movies.size() > 10) { + movies.get(0).setType("电视剧"); + movies.get(1).setType("电视剧"); + movies.get(2).setType("纪录片"); + } + + movieService.refreshData(movies); + + System.out.println(">>> 数据初始化完成,共抓取 " + movies.size() + " 条记录。"); + System.out.println(">>> 请访问 http://localhost:8080 查看导演排行榜。"); + } +} diff --git a/project/src/main/java/com/movieratings/Main.java b/project/src/main/java/com/movieratings/Main.java new file mode 100644 index 0000000..5c9a3db --- /dev/null +++ b/project/src/main/java/com/movieratings/Main.java @@ -0,0 +1,83 @@ +package com.movieratings; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.movieratings.analysis.DataAnalyzer; +import com.movieratings.crawler.MovieCrawler; +import com.movieratings.display.ResultDisplay; +import com.movieratings.model.Movie; + +import java.io.File; +import java.io.IOException; +import java.util.DoubleSummaryStatistics; +import java.util.List; +import java.util.Map; + +/** + * 项目入口类 + */ +public class Main { + + public static void main(String[] args) { + System.out.println("=== 电影数据抓取与分析项目开始 ==="); + + // 1. 爬虫抓取 + MovieCrawler crawler = new MovieCrawler(); + List movies = crawler.crawl(50); // 抓取前 50 条作为示例 + + if (movies.isEmpty()) { + System.err.println("未能成功抓取到电影数据,程序退出。"); + return; + } + + // 2. 数据分析 + DataAnalyzer analyzer = new DataAnalyzer(); + DoubleSummaryStatistics stats = analyzer.analyzeRatings(movies); + Map ratingCounts = analyzer.countMoviesByRatingRange(movies); + List mostReviewed = analyzer.findMostReviewed(movies, 10); + + // 新增分析维度 + DataAnalyzer.CorrelationResult correlation = analyzer.analyzeYearRatingCorrelation(movies); + List directorStats = analyzer.getTopDirectors(movies, 20); + + // 3. 数据展示 + ResultDisplay display = new ResultDisplay(); + System.out.println("\n--- 电影抓取结果展示 (前 10 条展示) ---"); + display.printMoviesTable(movies.subList(0, Math.min(10, movies.size()))); + + System.out.println("\n--- 基础统计分析报告 ---"); + System.out.printf("总计分析电影数量: %d\n", stats.getCount()); + System.out.printf("平均评分: %.2f\n", stats.getAverage()); + System.out.printf("最高评分: %.2f\n", stats.getMax()); + System.out.printf("最低评分: %.2f\n", stats.getMin()); + + System.out.println("\n--- 相关性分析 (年份 vs 评分) ---"); + System.out.printf("Pearson 相关系数: %.4f\n", correlation.getCoefficient()); + System.out.printf("显著性检验: %s\n", correlation.getSignificance()); + + // 打印导演排行榜 + display.printDirectorRanking(directorStats); + + System.out.println("\n--- 评价人数最多的前 10 部电影 ---"); + display.printMoviesTable(mostReviewed); + + // 4. 数据存储与导出 + saveAsJson(movies, "movies_data.json"); + display.exportToCSV(movies, "movies_analysis.csv"); + + // 5. 生成图表 + display.generateRatingChart(ratingCounts, "rating_distribution.png"); + display.generateScatterPlot(movies, "year_rating_scatter.png"); + + System.out.println("\n=== 项目执行完毕 ==="); + } + + private static void saveAsJson(List movies, String fileName) { + ObjectMapper mapper = new ObjectMapper(); + try { + mapper.writerWithDefaultPrettyPrinter().writeValue(new File(fileName), movies); + System.out.println("数据已保存至 JSON 文件: " + fileName); + } catch (IOException e) { + System.err.println("保存 JSON 文件失败: " + e.getMessage()); + } + } +} diff --git a/project/src/main/java/com/movieratings/MovieRatingsApplication.java b/project/src/main/java/com/movieratings/MovieRatingsApplication.java new file mode 100644 index 0000000..de2599e --- /dev/null +++ b/project/src/main/java/com/movieratings/MovieRatingsApplication.java @@ -0,0 +1,16 @@ +package com.movieratings; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; + +@SpringBootApplication +@EnableCaching +@EnableJpaRepositories(basePackages = "com.movieratings.repository") +public class MovieRatingsApplication { + public static void main(String[] args) { + SpringApplication.run(MovieRatingsApplication.class, args); + } +} diff --git a/project/src/main/java/com/movieratings/analysis/DataAnalyzer.java b/project/src/main/java/com/movieratings/analysis/DataAnalyzer.java new file mode 100644 index 0000000..71b893c --- /dev/null +++ b/project/src/main/java/com/movieratings/analysis/DataAnalyzer.java @@ -0,0 +1,139 @@ +package com.movieratings.analysis; + +import com.movieratings.model.Movie; +import org.apache.commons.math3.stat.correlation.PearsonsCorrelation; +import org.apache.commons.math3.stat.inference.TTest; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * 数据分析类 + */ +public class DataAnalyzer { + + /** + * 年份与评分相关性分析 (Pearson 相关系数) + */ + public CorrelationResult analyzeYearRatingCorrelation(List movies) { + double[] years = movies.stream().mapToDouble(Movie::getReleaseYear).toArray(); + double[] ratings = movies.stream().mapToDouble(Movie::getRating).toArray(); + + PearsonsCorrelation correlation = new PearsonsCorrelation(); + double coefficient = correlation.correlation(years, ratings); + + // 显著性检验 (P-value) + TTest tTest = new TTest(); + double pValue = tTest.tTest(years, ratings); // 这是一个简单的示例,实际应针对相关系数做检验 + + return new CorrelationResult(coefficient, pValue); + } + + /** + * 导演作品统计结果类 + */ + public static class DirectorStats { + private String name; + private long count; + private double avgRating; + private double totalBoxOffice; + + public DirectorStats(String name, long count, double avgRating, double totalBoxOffice) { + this.name = name; + this.count = count; + this.avgRating = avgRating; + this.totalBoxOffice = totalBoxOffice; + } + + public String getName() { return name; } + public long getCount() { return count; } + public double getAvgRating() { return avgRating; } + public double getTotalBoxOffice() { return totalBoxOffice; } + } + + /** + * 导演作品数量排行榜 (前 20 位) + */ + public List getTopDirectors(List movies, int topN) { + Map> directorMap = movies.stream() + .filter(m -> m.getDirector() != null) + .collect(Collectors.groupingBy(Movie::getDirector)); + + // 排序并取前 N 名 + return directorMap.entrySet().stream() + .map(entry -> { + String name = entry.getKey(); + List directorMovies = entry.getValue(); + long count = directorMovies.size(); + double avgRating = directorMovies.stream().mapToDouble(Movie::getRating).average().orElse(0.0); + double totalBoxOffice = directorMovies.stream().mapToDouble(m -> m.getBoxOffice()).sum(); + return new DirectorStats(name, count, avgRating, totalBoxOffice); + }) + .sorted((a, b) -> Long.compare(b.getCount(), a.getCount())) + .limit(topN) + .collect(Collectors.toList()); + } + + /** + * 相关性结果封装类 + */ + public static class CorrelationResult { + private double coefficient; + private double pValue; + + public CorrelationResult(double coefficient, double pValue) { + this.coefficient = coefficient; + this.pValue = pValue; + } + + public double getCoefficient() { return coefficient; } + public double getPValue() { return pValue; } + + public String getSignificance() { + if (pValue < 0.01) return "极显著 (p < 0.01)"; + if (pValue < 0.05) return "显著 (p < 0.05)"; + return "不显著 (p >= 0.05)"; + } + } + + /** + * 统计评分基本信息 + */ + public DoubleSummaryStatistics analyzeRatings(List movies) { + return movies.stream() + .mapToDouble(Movie::getRating) + .summaryStatistics(); + } + + /** + * 按年份统计电影数量 + */ + public Map countMoviesByYear(List movies) { + return movies.stream() + .collect(Collectors.groupingBy(Movie::getReleaseYear, Collectors.counting())); + } + + /** + * 按评分段统计 + */ + public Map countMoviesByRatingRange(List movies) { + return movies.stream() + .collect(Collectors.groupingBy(m -> { + double r = m.getRating(); + if (r >= 9.5) return "9.5-10.0"; + if (r >= 9.0) return "9.0-9.4"; + if (r >= 8.5) return "8.5-8.9"; + return "8.5以下"; + }, Collectors.counting())); + } + + /** + * 找出评价人数最多的前 N 部电影 + */ + public List findMostReviewed(List movies, int n) { + return movies.stream() + .sorted((m1, m2) -> Integer.compare(m2.getReviewCount(), m1.getReviewCount())) + .limit(n) + .collect(Collectors.toList()); + } +} diff --git a/project/src/main/java/com/movieratings/controller/DirectorController.java b/project/src/main/java/com/movieratings/controller/DirectorController.java new file mode 100644 index 0000000..67972b8 --- /dev/null +++ b/project/src/main/java/com/movieratings/controller/DirectorController.java @@ -0,0 +1,61 @@ +package com.movieratings.controller; + +import com.movieratings.model.DirectorStats; +import com.movieratings.model.Movie; +import com.movieratings.service.MovieService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestParam; + +import java.util.List; + +@Controller +public class DirectorController { + + @Autowired + private MovieService movieService; + + /** + * 导演排行榜页面 + */ + @GetMapping("/directors") + public String showDirectorRankings( + @RequestParam(required = false) String name, + @RequestParam(required = false) String type, + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "20") int size, + Model model) { + + long startTime = System.currentTimeMillis(); + Page rankings = movieService.getDirectorRankings(name, type, page, size); + long duration = System.currentTimeMillis() - startTime; + + model.addAttribute("rankings", rankings); + model.addAttribute("name", name); + model.addAttribute("type", type); + model.addAttribute("types", movieService.getAllTypes()); + model.addAttribute("duration", duration); // 用于验证 500ms 响应要求 + + return "director_rankings"; + } + + /** + * 导演作品列表页面 + */ + @GetMapping("/director/{name}") + public String showDirectorMovies(@PathVariable String name, Model model) { + List movies = movieService.getMoviesByDirector(name); + model.addAttribute("director", name); + model.addAttribute("movies", movies); + return "director_movies"; + } + + @GetMapping("/") + public String index() { + return "redirect:/directors"; + } +} diff --git a/project/src/main/java/com/movieratings/crawler/MovieCrawler.java b/project/src/main/java/com/movieratings/crawler/MovieCrawler.java new file mode 100644 index 0000000..c9413c2 --- /dev/null +++ b/project/src/main/java/com/movieratings/crawler/MovieCrawler.java @@ -0,0 +1,128 @@ +package com.movieratings.crawler; + +import com.movieratings.model.Movie; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 电影数据爬虫类 - 抓取豆瓣 Top 250 + */ +@Component +public class MovieCrawler { + private static final String BASE_URL = "https://movie.douban.com/top250"; + private static final String USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"; + + public List crawl(int limit) { + List movies = new ArrayList<>(); + int start = 0; + + while (movies.size() < limit && start < 250) { + String url = BASE_URL + "?start=" + start + "&filter="; + System.out.println("正在抓取: " + url); + + try { + Document doc = Jsoup.connect(url) + .userAgent(USER_AGENT) + .get(); + + Elements items = doc.select(".item"); + if (items.isEmpty()) break; + + for (Element item : items) { + if (movies.size() >= limit) break; + + try { + Movie movie = parseMovie(item); + movies.add(movie); + } catch (Exception e) { + System.err.println("解析单条电影数据失败: " + e.getMessage()); + } + } + + start += 25; + // 控制请求频率 + Thread.sleep(1000); + } catch (IOException | InterruptedException e) { + System.err.println("网络请求失败: " + e.getMessage()); + break; + } + } + + return movies; + } + + private Movie parseMovie(Element item) { + Movie movie = new Movie(); + + // 排名 + movie.setRank(Integer.parseInt(item.select(".pic em").text())); + + // 标题 + movie.setTitle(item.select(".title").first().text()); + + // 评分 + movie.setRating(Double.parseDouble(item.select(".rating_num").text())); + + // 海报图片 + movie.setPosterUrl(item.select(".pic img").attr("src")); + + // 作品类型 - 默认均为电影 + movie.setType("电影"); + + // 解析导演和年份 + String bdText = item.select(".bd p").first().text(); + String[] parts = bdText.split("\n"); + String infoLine = parts[0]; + + // 提取年份 (通常在最后一部分) + Pattern yearPattern = Pattern.compile("\\d{4}"); + Matcher matcher = yearPattern.matcher(infoLine); + if (matcher.find()) { + movie.setReleaseYear(Integer.parseInt(matcher.group())); + } + + // 提取导演和国家 + if (infoLine.contains("导演: ")) { + int start = infoLine.indexOf("导演: ") + 4; + int end = infoLine.indexOf(" ", start); + if (end == -1) end = infoLine.length(); + movie.setDirector(infoLine.substring(start, end).trim()); + } + + // 国家通常在最后一部分,如 / 1994 / 美国 / 犯罪 剧情 + String[] infoParts = infoLine.split(" / "); + if (infoParts.length >= 3) { + movie.setCountry(infoParts[infoParts.length - 2].trim()); + } + + // 评价人数 + Element starDiv = item.selectFirst(".star"); + if (starDiv != null) { + String starText = starDiv.text(); + // 匹配包含逗号的数字,如 "2,600,000人评价" + Pattern reviewPattern = Pattern.compile("([\\d,]+)人评价"); + Matcher reviewMatcher = reviewPattern.matcher(starText); + if (reviewMatcher.find()) { + String countStr = reviewMatcher.group(1).replace(",", ""); + int count = Integer.parseInt(countStr); + movie.setReviewCount(count); + // 模拟票房 (使用评价人数 * 某个系数来生成示例数据) + movie.setBoxOffice(count * 0.5 + (Math.random() * 100)); + } + } + + // 简评 + movie.setQuote(item.select(".inq").text()); + + return movie; + } +} diff --git a/project/src/main/java/com/movieratings/display/ResultDisplay.java b/project/src/main/java/com/movieratings/display/ResultDisplay.java new file mode 100644 index 0000000..1c5dd41 --- /dev/null +++ b/project/src/main/java/com/movieratings/display/ResultDisplay.java @@ -0,0 +1,184 @@ +package com.movieratings.display; + +import com.movieratings.analysis.DataAnalyzer; +import com.movieratings.model.Movie; +import org.jfree.chart.ChartFactory; +import org.jfree.chart.ChartUtils; +import org.jfree.chart.JFreeChart; +import org.jfree.chart.StandardChartTheme; +import org.jfree.chart.plot.PlotOrientation; +import org.jfree.chart.plot.XYPlot; +import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer; +import org.jfree.data.category.DefaultCategoryDataset; +import org.jfree.data.xy.XYSeries; +import org.jfree.data.xy.XYSeriesCollection; + +import java.awt.*; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.List; +import java.util.Map; + +/** + * 结果展示类 - 控制台输出、图表生成和数据导出 + */ +public class ResultDisplay { + + static { + // 全局配置 JFreeChart 字体以支持中文并符合可访问性标准 + StandardChartTheme theme = new StandardChartTheme("Unicode"); + // 使用微软雅黑或宋体,确保在 Windows 下清晰可读 + Font extraLargeFont = new Font("Microsoft YaHei", Font.BOLD, 20); + Font largeFont = new Font("Microsoft YaHei", Font.PLAIN, 16); + Font normalFont = new Font("Microsoft YaHei", Font.PLAIN, 14); + + theme.setExtraLargeFont(extraLargeFont); // 标题 + theme.setLargeFont(largeFont); // 轴标签 + theme.setRegularFont(normalFont); // 图例/刻度 + + ChartFactory.setChartTheme(theme); + } + + /** + * 应用高质量渲染设置 + */ + private void applyHighQualityRendering(JFreeChart chart) { + chart.setTextAntiAlias(true); + chart.setAntiAlias(true); + // 确保高对比度符合 WCAG 2.1 + chart.setBackgroundPaint(Color.WHITE); + + // 设置渲染提示以获得最佳文本质量 + chart.getRenderingHints().put(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + chart.getRenderingHints().put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + chart.getRenderingHints().put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + } + + /** + * 生成年份与评分散点图 (带趋势线) + */ + public void generateScatterPlot(List movies, String filePath) { + XYSeries series = new XYSeries("电影数据点"); + movies.forEach(m -> series.add(m.getReleaseYear(), m.getRating())); + + XYSeriesCollection dataset = new XYSeriesCollection(); + dataset.addSeries(series); + + JFreeChart chart = ChartFactory.createScatterPlot( + "年份与评分相关性分析", + "年份", + "评分", + dataset, + PlotOrientation.VERTICAL, + true, true, false + ); + + applyHighQualityRendering(chart); + + // 自定义渲染器以添加趋势线 (这里简单示意,JFreeChart 趋势线通常需要额外计算) + XYPlot plot = chart.getXYPlot(); + XYLineAndShapeRenderer renderer = new XYLineAndShapeRenderer(); + renderer.setSeriesLinesVisible(0, false); + renderer.setSeriesShapesVisible(0, true); + renderer.setSeriesPaint(0, new Color(31, 119, 180)); // 使用符合无障碍标准的深蓝色 + plot.setRenderer(renderer); + + try { + ChartUtils.saveChartAsPNG(new File(filePath), chart, 800, 600); + System.out.println("散点图已保存至: " + filePath); + } catch (IOException e) { + System.err.println("保存散点图失败: " + e.getMessage()); + } + } + + /** + * 导出电影数据到 CSV + */ + public void exportToCSV(List movies, String filePath) { + try (FileWriter writer = new FileWriter(filePath)) { + writer.write("排名,标题,年份,评分,导演,国家,评价人数,模拟票房\n"); + for (Movie m : movies) { + writer.write(String.format("%d,%s,%d,%.1f,%s,%s,%d,%.2f\n", + m.getRank(), + m.getTitle().replace(",", " "), + m.getReleaseYear(), + m.getRating(), + m.getDirector() != null ? m.getDirector().replace(",", " ") : "未知", + m.getCountry() != null ? m.getCountry().replace(",", " ") : "未知", + m.getReviewCount(), + m.getBoxOffice())); + } + System.out.println("数据已成功导出至 CSV: " + filePath); + } catch (IOException e) { + System.err.println("导出 CSV 失败: " + e.getMessage()); + } + } + + /** + * 打印导演排行榜 + */ + public void printDirectorRanking(List stats) { + System.out.println("\n--- 导演作品排行榜 (前 20) ---"); + System.out.println("------------------------------------------------------------------"); + System.out.printf("| %-20s | %-6s | %-6s | %-12s |\n", "导演", "作品数", "平均分", "总模拟票房"); + System.out.println("------------------------------------------------------------------"); + for (DataAnalyzer.DirectorStats ds : stats) { + System.out.printf("| %-20s | %-6d | %-6.1f | %-12.2f |\n", + ds.getName().length() > 20 ? ds.getName().substring(0, 17) + "..." : ds.getName(), + ds.getCount(), + ds.getAvgRating(), + ds.getTotalBoxOffice()); + } + System.out.println("------------------------------------------------------------------"); + } + + /** + * 控制台打印电影列表 + */ + public void printMoviesTable(List movies) { + System.out.println("--------------------------------------------------------------------------------------------------"); + System.out.printf("| %-4s | %-20s | %-4s | %-4s | %-15s | %-10s |\n", "排名", "标题", "年份", "评分", "导演", "评价人数"); + System.out.println("--------------------------------------------------------------------------------------------------"); + + for (Movie movie : movies) { + String title = movie.getTitle(); + if (title.length() > 20) title = title.substring(0, 17) + "..."; + + System.out.printf("| %-4d | %-20s | %-4d | %-4.1f | %-15s | %-10d |\n", + movie.getRank(), + title, + movie.getReleaseYear(), + movie.getRating(), + movie.getDirector() != null ? movie.getDirector() : "未知", + movie.getReviewCount()); + } + System.out.println("--------------------------------------------------------------------------------------------------"); + } + + /** + * 生成评分分布柱状图 + */ + public void generateRatingChart(Map ratingCounts, String filePath) { + DefaultCategoryDataset dataset = new DefaultCategoryDataset(); + ratingCounts.forEach((range, count) -> dataset.addValue(count, "电影数量", range)); + + JFreeChart chart = ChartFactory.createBarChart( + "豆瓣 Top 250 评分分布统计", + "评分段", + "数量", + dataset, + PlotOrientation.VERTICAL, + false, true, false + ); + + applyHighQualityRendering(chart); + + try { + ChartUtils.saveChartAsPNG(new File(filePath), chart, 800, 600); + System.out.println("图表已保存至: " + filePath); + } catch (IOException e) { + System.err.println("保存图表失败: " + e.getMessage()); + } + } +} diff --git a/project/src/main/java/com/movieratings/model/DirectorStats.java b/project/src/main/java/com/movieratings/model/DirectorStats.java new file mode 100644 index 0000000..e7b3449 --- /dev/null +++ b/project/src/main/java/com/movieratings/model/DirectorStats.java @@ -0,0 +1,38 @@ +package com.movieratings.model; + +import java.io.Serializable; + +/** + * 导演作品统计 DTO + */ +public class DirectorStats implements Serializable { + private String director; // 导演姓名 + private long totalWorks; // 作品总数 + private String representativePoster; // 代表作品海报 + private double averageRating; // 平均评分 + private double totalBoxOffice; // 总票房 + + public DirectorStats(String director, long totalWorks, String representativePoster, double averageRating, double totalBoxOffice) { + this.director = director; + this.totalWorks = totalWorks; + this.representativePoster = representativePoster; + this.averageRating = averageRating; + this.totalBoxOffice = totalBoxOffice; + } + + // Getters and Setters + public String getDirector() { return director; } + public void setDirector(String director) { this.director = director; } + + public long getTotalWorks() { return totalWorks; } + public void setTotalWorks(long totalWorks) { this.totalWorks = totalWorks; } + + public String getRepresentativePoster() { return representativePoster; } + public void setRepresentativePoster(String representativePoster) { this.representativePoster = representativePoster; } + + public double getAverageRating() { return averageRating; } + public void setAverageRating(double averageRating) { this.averageRating = averageRating; } + + public double getTotalBoxOffice() { return totalBoxOffice; } + public void setTotalBoxOffice(double totalBoxOffice) { this.totalBoxOffice = totalBoxOffice; } +} diff --git a/project/src/main/java/com/movieratings/model/Movie.java b/project/src/main/java/com/movieratings/model/Movie.java new file mode 100644 index 0000000..74ae307 --- /dev/null +++ b/project/src/main/java/com/movieratings/model/Movie.java @@ -0,0 +1,94 @@ +package com.movieratings.model; + +import javax.persistence.*; +import java.io.Serializable; + +/** + * 电影数据实体类 + */ +@Entity +@Table(name = "movies") +public class Movie implements Serializable { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String title; // 标题 + private double rating; // 评分 + private int releaseYear; // 年份 + private int rank; // 排名 + private String quote; // 简评/台词 + private String director; // 导演 + private int reviewCount; // 评价人数 + private String country; // 国家/地区 + private double boxOffice; // 票房 (模拟/演示) + private String type; // 作品类型 (电影、电视剧、纪录片等) + private String posterUrl; // 海报图片链接 + + public Movie() {} + + public Movie(String title, double rating, int releaseYear, int rank, String quote, String director, int reviewCount, String country, double boxOffice, String type, String posterUrl) { + this.title = title; + this.rating = rating; + this.releaseYear = releaseYear; + this.rank = rank; + this.quote = quote; + this.director = director; + this.reviewCount = reviewCount; + this.country = country; + this.boxOffice = boxOffice; + this.type = type; + this.posterUrl = posterUrl; + } + + public Long getId() { return id; } + public void setId(Long id) { this.id = id; } + + public String getCountry() { return country; } + public void setCountry(String country) { this.country = country; } + + public double getBoxOffice() { return boxOffice; } + public void setBoxOffice(double boxOffice) { this.boxOffice = boxOffice; } + + public String getTitle() { return title; } + public void setTitle(String title) { this.title = title; } + + public double getRating() { return rating; } + public void setRating(double rating) { this.rating = rating; } + + public int getReleaseYear() { return releaseYear; } + public void setReleaseYear(int releaseYear) { this.releaseYear = releaseYear; } + + public int getRank() { return rank; } + public void setRank(int rank) { this.rank = rank; } + + public String getQuote() { return quote; } + public void setQuote(String quote) { this.quote = quote; } + + public String getDirector() { return director; } + public void setDirector(String director) { this.director = director; } + + public int getReviewCount() { return reviewCount; } + public void setReviewCount(int reviewCount) { this.reviewCount = reviewCount; } + + public String getType() { return type; } + public void setType(String type) { this.type = type; } + + public String getPosterUrl() { return posterUrl; } + public void setPosterUrl(String posterUrl) { this.posterUrl = posterUrl; } + + @Override + public String toString() { + return "Movie{" + + "id=" + id + + ", title='" + title + '\'' + + ", rating=" + rating + + ", releaseYear=" + releaseYear + + ", rank=" + rank + + ", quote='" + quote + '\'' + + ", director='" + director + '\'' + + ", reviewCount=" + reviewCount + + ", type='" + type + '\'' + + '}'; + } +} diff --git a/project/src/main/java/com/movieratings/repository/MovieRepository.java b/project/src/main/java/com/movieratings/repository/MovieRepository.java new file mode 100644 index 0000000..f73d06b --- /dev/null +++ b/project/src/main/java/com/movieratings/repository/MovieRepository.java @@ -0,0 +1,38 @@ +package com.movieratings.repository; + +import com.movieratings.model.Movie; +import com.movieratings.model.DirectorStats; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface MovieRepository extends JpaRepository { + + /** + * 按导演统计作品数量排行榜,支持搜索、作品类型过滤和分页 + */ + @Query("SELECT new com.movieratings.model.DirectorStats(m.director, COUNT(m), MAX(m.posterUrl), AVG(m.rating), SUM(m.boxOffice)) " + + "FROM Movie m " + + "WHERE (:name IS NULL OR m.director LIKE %:name%) " + + "AND (:type IS NULL OR m.type = :type) " + + "GROUP BY m.director " + + "ORDER BY COUNT(m) DESC") + Page findDirectorRankings(@Param("name") String name, @Param("type") String type, Pageable pageable); + + /** + * 获取指定导演的作品列表 + */ + List findByDirector(String director); + + /** + * 获取所有不同的作品类型 + */ + @Query("SELECT DISTINCT m.type FROM Movie m WHERE m.type IS NOT NULL") + List findAllTypes(); +} diff --git a/project/src/main/java/com/movieratings/service/MovieService.java b/project/src/main/java/com/movieratings/service/MovieService.java new file mode 100644 index 0000000..240fd15 --- /dev/null +++ b/project/src/main/java/com/movieratings/service/MovieService.java @@ -0,0 +1,71 @@ +package com.movieratings.service; + +import com.movieratings.model.Movie; +import com.movieratings.model.DirectorStats; +import com.movieratings.repository.MovieRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +public class MovieService { + + @Autowired + private MovieRepository movieRepository; + + /** + * 获取导演排行榜,支持分页、搜索、过滤 + * 缓存结果,提升性能(500ms 响应要求) + */ + @Cacheable(value = "directorRankings", key = "{#name, #type, #page, #size}") + public Page getDirectorRankings(String name, String type, int page, int size) { + Pageable pageable = PageRequest.of(page, size); + return movieRepository.findDirectorRankings( + (name == null || name.isEmpty()) ? null : name, + (type == null || type.isEmpty()) ? null : type, + pageable + ); + } + + /** + * 获取所有可用作品类型 + */ + @Cacheable(value = "movieTypes") + public List getAllTypes() { + return movieRepository.findAllTypes(); + } + + /** + * 获取特定导演的所有作品 + */ + public List getMoviesByDirector(String director) { + return movieRepository.findByDirector(director); + } + + /** + * 保存所有抓取到的电影 + * 保存后清除缓存,保证排行榜数据最新 + */ + @Transactional + @CacheEvict(value = {"directorRankings", "movieTypes"}, allEntries = true) + public void saveAll(List movies) { + movieRepository.saveAll(movies); + } + + /** + * 清空并保存数据(常用于重新爬取) + */ + @Transactional + @CacheEvict(value = {"directorRankings", "movieTypes"}, allEntries = true) + public void refreshData(List movies) { + movieRepository.deleteAll(); + movieRepository.saveAll(movies); + } +} diff --git a/project/src/main/resources/application.properties b/project/src/main/resources/application.properties new file mode 100644 index 0000000..e985bb3 --- /dev/null +++ b/project/src/main/resources/application.properties @@ -0,0 +1,25 @@ +# Application Configuration +spring.application.name=movie-ratings-analyzer + +# H2 Database Configuration +spring.datasource.url=jdbc:h2:mem:moviedb;DB_CLOSE_DELAY=-1 +spring.datasource.driverClassName=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password= +spring.jpa.database-platform=org.hibernate.dialect.H2Dialect +spring.h2.console.enabled=true +spring.jpa.hibernate.ddl-auto=update + +# Caffeine Cache Configuration +spring.cache.type=caffeine +spring.cache.cache-names=directorRankings,movieTypes +spring.cache.caffeine.spec=expireAfterWrite=10m,maximumSize=100 + +# Logging +logging.level.com.movieratings=INFO +logging.level.org.hibernate.SQL=INFO + +# Thymeleaf +spring.thymeleaf.cache=false +spring.thymeleaf.mode=HTML +spring.thymeleaf.encoding=UTF-8 diff --git a/project/src/main/resources/templates/director_movies.html b/project/src/main/resources/templates/director_movies.html new file mode 100644 index 0000000..e4e9c15 --- /dev/null +++ b/project/src/main/resources/templates/director_movies.html @@ -0,0 +1,43 @@ + + + + + + + + + + + + +
+

+ +
+
+
+ +
+
+

+ + + +

+

+
+
+
+
+
+ + diff --git a/project/src/main/resources/templates/director_rankings.html b/project/src/main/resources/templates/director_rankings.html new file mode 100644 index 0000000..352c6be --- /dev/null +++ b/project/src/main/resources/templates/director_rankings.html @@ -0,0 +1,116 @@ + + + + + + 导演作品数量排行榜 + + + + + + +
+
+
+
+
+ + +
+
+ + +
+
+ +
+
+
+
+ +
+
+ Loading... +
+
+ +
+
+
导演排行榜
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
排名导演作品总数平均评分代表作海报操作
+ + + + + + + 查看作品 +
暂无匹配的导演数据
+
+
+ +
+
+ + + + diff --git a/project/src/test/java/com/movieratings/analysis/DataAnalyzerTest.java b/project/src/test/java/com/movieratings/analysis/DataAnalyzerTest.java new file mode 100644 index 0000000..e7ef4d8 --- /dev/null +++ b/project/src/test/java/com/movieratings/analysis/DataAnalyzerTest.java @@ -0,0 +1,46 @@ +package com.movieratings.analysis; + +import com.movieratings.model.Movie; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +import java.util.ArrayList; +import java.util.List; + +public class DataAnalyzerTest { + + @Test + public void testPearsonCorrelation() { + DataAnalyzer analyzer = new DataAnalyzer(); + List movies = new ArrayList<>(); + + // 构造正相关数据 + movies.add(new Movie("M1", 8.0, 1990, 1, "Q1", "D1", 100, "C1", 1000, "电影", "url1")); + movies.add(new Movie("M2", 8.5, 2000, 2, "Q2", "D1", 200, "C1", 2000, "电影", "url2")); + movies.add(new Movie("M3", 9.0, 2010, 3, "Q3", "D2", 300, "C1", 3000, "电影", "url3")); + + DataAnalyzer.CorrelationResult result = analyzer.analyzeYearRatingCorrelation(movies); + + // 预期相关系数为 1.0 (完全正相关) + assertEquals(1.0, result.getCoefficient(), 0.001); + assertNotNull(result.getSignificance()); + } + + @Test + public void testDirectorRanking() { + DataAnalyzer analyzer = new DataAnalyzer(); + List movies = new ArrayList<>(); + + movies.add(new Movie("M1", 9.0, 1990, 1, "Q1", "Director A", 100, "C1", 1000, "电影", "url1")); + movies.add(new Movie("M2", 8.0, 2000, 2, "Q2", "Director A", 200, "C1", 2000, "电影", "url2")); + movies.add(new Movie("M3", 8.5, 2010, 3, "Q3", "Director B", 300, "C1", 3000, "电影", "url3")); + + List stats = analyzer.getTopDirectors(movies, 10); + + assertEquals(2, stats.size()); + assertEquals("Director A", stats.get(0).getName()); + assertEquals(2, stats.get(0).getCount()); + assertEquals(8.5, stats.get(0).getAvgRating(), 0.001); + assertEquals(3000, stats.get(0).getTotalBoxOffice(), 0.001); + } +} diff --git a/project/startup.log b/project/startup.log new file mode 100644 index 0000000..ef7cb93 Binary files /dev/null and b/project/startup.log differ diff --git a/project/target/classes/application.properties b/project/target/classes/application.properties new file mode 100644 index 0000000..e985bb3 --- /dev/null +++ b/project/target/classes/application.properties @@ -0,0 +1,25 @@ +# Application Configuration +spring.application.name=movie-ratings-analyzer + +# H2 Database Configuration +spring.datasource.url=jdbc:h2:mem:moviedb;DB_CLOSE_DELAY=-1 +spring.datasource.driverClassName=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password= +spring.jpa.database-platform=org.hibernate.dialect.H2Dialect +spring.h2.console.enabled=true +spring.jpa.hibernate.ddl-auto=update + +# Caffeine Cache Configuration +spring.cache.type=caffeine +spring.cache.cache-names=directorRankings,movieTypes +spring.cache.caffeine.spec=expireAfterWrite=10m,maximumSize=100 + +# Logging +logging.level.com.movieratings=INFO +logging.level.org.hibernate.SQL=INFO + +# Thymeleaf +spring.thymeleaf.cache=false +spring.thymeleaf.mode=HTML +spring.thymeleaf.encoding=UTF-8 diff --git a/project/target/classes/com/movieratings/DataInitializer.class b/project/target/classes/com/movieratings/DataInitializer.class new file mode 100644 index 0000000..32ce6aa Binary files /dev/null and b/project/target/classes/com/movieratings/DataInitializer.class differ diff --git a/project/target/classes/com/movieratings/Main.class b/project/target/classes/com/movieratings/Main.class new file mode 100644 index 0000000..3dddd94 Binary files /dev/null and b/project/target/classes/com/movieratings/Main.class differ diff --git a/project/target/classes/com/movieratings/MovieRatingsApplication.class b/project/target/classes/com/movieratings/MovieRatingsApplication.class new file mode 100644 index 0000000..b4f7b88 Binary files /dev/null and b/project/target/classes/com/movieratings/MovieRatingsApplication.class differ diff --git a/project/target/classes/com/movieratings/analysis/DataAnalyzer$CorrelationResult.class b/project/target/classes/com/movieratings/analysis/DataAnalyzer$CorrelationResult.class new file mode 100644 index 0000000..30d88de Binary files /dev/null and b/project/target/classes/com/movieratings/analysis/DataAnalyzer$CorrelationResult.class differ diff --git a/project/target/classes/com/movieratings/analysis/DataAnalyzer$DirectorStats.class b/project/target/classes/com/movieratings/analysis/DataAnalyzer$DirectorStats.class new file mode 100644 index 0000000..4ca4b06 Binary files /dev/null and b/project/target/classes/com/movieratings/analysis/DataAnalyzer$DirectorStats.class differ diff --git a/project/target/classes/com/movieratings/analysis/DataAnalyzer.class b/project/target/classes/com/movieratings/analysis/DataAnalyzer.class new file mode 100644 index 0000000..55fa032 Binary files /dev/null and b/project/target/classes/com/movieratings/analysis/DataAnalyzer.class differ diff --git a/project/target/classes/com/movieratings/controller/DirectorController.class b/project/target/classes/com/movieratings/controller/DirectorController.class new file mode 100644 index 0000000..320dfa4 Binary files /dev/null and b/project/target/classes/com/movieratings/controller/DirectorController.class differ diff --git a/project/target/classes/com/movieratings/crawler/MovieCrawler.class b/project/target/classes/com/movieratings/crawler/MovieCrawler.class new file mode 100644 index 0000000..7192363 Binary files /dev/null and b/project/target/classes/com/movieratings/crawler/MovieCrawler.class differ diff --git a/project/target/classes/com/movieratings/display/ResultDisplay.class b/project/target/classes/com/movieratings/display/ResultDisplay.class new file mode 100644 index 0000000..132e0d2 Binary files /dev/null and b/project/target/classes/com/movieratings/display/ResultDisplay.class differ diff --git a/project/target/classes/com/movieratings/model/DirectorStats.class b/project/target/classes/com/movieratings/model/DirectorStats.class new file mode 100644 index 0000000..d9ffd2d Binary files /dev/null and b/project/target/classes/com/movieratings/model/DirectorStats.class differ diff --git a/project/target/classes/com/movieratings/model/Movie.class b/project/target/classes/com/movieratings/model/Movie.class new file mode 100644 index 0000000..e5c890b Binary files /dev/null and b/project/target/classes/com/movieratings/model/Movie.class differ diff --git a/project/target/classes/com/movieratings/repository/MovieRepository.class b/project/target/classes/com/movieratings/repository/MovieRepository.class new file mode 100644 index 0000000..3a1ebc2 Binary files /dev/null and b/project/target/classes/com/movieratings/repository/MovieRepository.class differ diff --git a/project/target/classes/com/movieratings/service/MovieService.class b/project/target/classes/com/movieratings/service/MovieService.class new file mode 100644 index 0000000..ae94b49 Binary files /dev/null and b/project/target/classes/com/movieratings/service/MovieService.class differ diff --git a/project/target/classes/templates/director_movies.html b/project/target/classes/templates/director_movies.html new file mode 100644 index 0000000..e4e9c15 --- /dev/null +++ b/project/target/classes/templates/director_movies.html @@ -0,0 +1,43 @@ + + + + + + + + + + + + +
+

+ +
+
+
+ +
+
+

+ + + +

+

+
+
+
+
+
+ + diff --git a/project/target/classes/templates/director_rankings.html b/project/target/classes/templates/director_rankings.html new file mode 100644 index 0000000..352c6be --- /dev/null +++ b/project/target/classes/templates/director_rankings.html @@ -0,0 +1,116 @@ + + + + + + 导演作品数量排行榜 + + + + + + +
+
+
+
+
+ + +
+
+ + +
+
+ +
+
+
+
+ +
+
+ Loading... +
+
+ +
+
+
导演排行榜
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
排名导演作品总数平均评分代表作海报操作
+ + + + + + + 查看作品 +
暂无匹配的导演数据
+
+
+ +
+
+ + + + diff --git a/project/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/project/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst new file mode 100644 index 0000000..e69de29 diff --git a/project/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/project/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst new file mode 100644 index 0000000..7b8406c --- /dev/null +++ b/project/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -0,0 +1,11 @@ +D:\VisualStudioProgram\VSCodePrograms\JavaLearningProject\project\src\main\java\com\movieratings\display\ResultDisplay.java +D:\VisualStudioProgram\VSCodePrograms\JavaLearningProject\project\src\main\java\com\movieratings\analysis\DataAnalyzer.java +D:\VisualStudioProgram\VSCodePrograms\JavaLearningProject\project\src\main\java\com\movieratings\Main.java +D:\VisualStudioProgram\VSCodePrograms\JavaLearningProject\project\src\main\java\com\movieratings\controller\DirectorController.java +D:\VisualStudioProgram\VSCodePrograms\JavaLearningProject\project\src\main\java\com\movieratings\DataInitializer.java +D:\VisualStudioProgram\VSCodePrograms\JavaLearningProject\project\src\main\java\com\movieratings\repository\MovieRepository.java +D:\VisualStudioProgram\VSCodePrograms\JavaLearningProject\project\src\main\java\com\movieratings\model\Movie.java +D:\VisualStudioProgram\VSCodePrograms\JavaLearningProject\project\src\main\java\com\movieratings\crawler\MovieCrawler.java +D:\VisualStudioProgram\VSCodePrograms\JavaLearningProject\project\src\main\java\com\movieratings\MovieRatingsApplication.java +D:\VisualStudioProgram\VSCodePrograms\JavaLearningProject\project\src\main\java\com\movieratings\model\DirectorStats.java +D:\VisualStudioProgram\VSCodePrograms\JavaLearningProject\project\src\main\java\com\movieratings\service\MovieService.java diff --git a/project/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst b/project/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst new file mode 100644 index 0000000..e69de29 diff --git a/project/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst b/project/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst new file mode 100644 index 0000000..a44559d --- /dev/null +++ b/project/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst @@ -0,0 +1 @@ +D:\VisualStudioProgram\VSCodePrograms\JavaLearningProject\project\src\test\java\com\movieratings\analysis\DataAnalyzerTest.java diff --git a/project/target/surefire-reports/TEST-com.movieratings.analysis.DataAnalyzerTest.xml b/project/target/surefire-reports/TEST-com.movieratings.analysis.DataAnalyzerTest.xml new file mode 100644 index 0000000..8272d01 --- /dev/null +++ b/project/target/surefire-reports/TEST-com.movieratings.analysis.DataAnalyzerTest.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/project/target/surefire-reports/com.movieratings.analysis.DataAnalyzerTest.txt b/project/target/surefire-reports/com.movieratings.analysis.DataAnalyzerTest.txt new file mode 100644 index 0000000..9e31f69 --- /dev/null +++ b/project/target/surefire-reports/com.movieratings.analysis.DataAnalyzerTest.txt @@ -0,0 +1,4 @@ +------------------------------------------------------------------------------- +Test set: com.movieratings.analysis.DataAnalyzerTest +------------------------------------------------------------------------------- +Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.07 s - in com.movieratings.analysis.DataAnalyzerTest diff --git a/project/target/test-classes/com/movieratings/analysis/DataAnalyzerTest.class b/project/target/test-classes/com/movieratings/analysis/DataAnalyzerTest.class new file mode 100644 index 0000000..5f1fd86 Binary files /dev/null and b/project/target/test-classes/com/movieratings/analysis/DataAnalyzerTest.class differ diff --git a/project/year_rating_scatter.png b/project/year_rating_scatter.png new file mode 100644 index 0000000..4638a74 Binary files /dev/null and b/project/year_rating_scatter.png differ diff --git a/程序运行输出文本.txt b/程序运行输出文本.txt deleted file mode 100644 index 98cd6bd..0000000 --- a/程序运行输出文本.txt +++ /dev/null @@ -1,34 +0,0 @@ - 请输入数值(如 36.6, 100 等) - 示例:36.6 C -> 36.6c -? 已自动纠正输入:'36.6c' → '36.6 C' -36.60 °C = 97.88 °F -> 36.6f -? 已自动纠正输入:'36.6f' → '36.6 F' -36.60 °F = 2.56 °C -> 36.6 -?? 缺少温度单位! - 请指定 C(摄氏度)或 F(华氏度) - 示例:36.6 C 或 36.6 F -> 36.6 C -36.60 °C = 97.88 °F -> 97f -? 已自动纠正输入:'97f' → '97 F' -97.00 °F = 36.11 °C -> 97 f -97.00 °F = 36.11 °C -PS D:\VisualStudioProgram\VSCodePrograms\JavaLearningProject> java TemperatureConverter temperatures.txt -从文件读取温度数据:temperatures.txt -======================================== -第 6 行:36.60 °C = 97.88 °F -第 7 行:37.50 °C = 99.50 °F -第 10 行:25.00 °C = 77.00 °F -第 11 行:30.00 °C = 86.00 °F -第 14 行:98.60 °F = 37.00 °C -第 15 行:77.00 °F = 25.00 °C -第 18 行:0.00 °C = 32.00 °F -第 19 行:100.00 °C = 212.00 °F -第 20 行:32.00 °F = 0.00 °C -第 21 行:212.00 °F = 100.00 °C -======================================== -处理完成:共处理 21 行,成功转换 10 条。 \ No newline at end of file