📄 运行与设计报告(核心提纲参考)
一、 架构设计说明
本系统采用 Scala 典型的函数式编程与面向对象相结合的架构设计:
- 数据模型层 (Model):使用
case class Order封装基础订单属性,利用案例类天生的不可变性、自带toString和支持模式匹配的特性,简化了中间数据的流转。 - 业务逻辑层 (Service/Analyzer):将所有计算和操作抽离在单例对象
OrderAnalyzer中,纯化计算逻辑。- 不可变状态流转:摒弃全局
var变量,添加新订单时通过返回全新List[Order]实现“状态无副作用流转”。 - 类型安全控制:查询操作返回
Option[Order],通过类型系统在编译期规避NullPointerException风险。 - 性能优化思想:引入
List.view(惰性求值视图),将多阶段的过滤操作合并至一次迭代中完成,模拟大数据环境下的“延迟算子触发”机制。
- 不可变状态流转:摒弃全局
- 交互与持久化层 (UI & I/O):
- 采用尾递归函数
@tailrec配合模式匹配控制台输入,构建无内存泄漏的死循环菜单。 - 基于 Java 的
BufferedReader和PrintWriter构建 CSV 纯文本持久化桥梁,实现基础的数据落地恢复。
- 采用尾递归函数
二、 核心源码实现(带规范考核注释)
scala
package Scala10
import java.io.{BufferedReader, FileReader, PrintWriter}
import scala.annotation.tailrec
import scala.io.StdIn
import scala.util.{Failure, Success, Try}
/**
* (1) 定义 Order 案例类
* 案例类自动具备不可变性、序列化及模式匹配提取器功能
*/
case class Order(orderId: String, userName: String, category: String, amount: Double, timestamp: Long)
/**
* (2) 实现 OrderAnalyzer 对象
* 聚集订单分析与存储的核心算子
*/
object OrderAnalyzer {
/**
* (3) & (6) 添加新订单与异常验证
* 使用条件守卫(Guard)进行业务逻辑拦截,并使用 Try 体系向上传递异常
*/
def addOrder(orders: List[Order], newOrder: Order): Try[List[Order]] = {
if (newOrder.amount < 0) {
Failure(new IllegalArgumentException(s"金额不能为负数 (${newOrder.amount})"))
} else if (orders.exists(_.orderId == newOrder.orderId)) {
Failure(new IllegalArgumentException(s"订单号重复 (${newOrder.orderId})"))
} else {
// 核心:使用 :+ 产生并返回一个包含新元素的全新不可变 List,体现不可变性
Success(orders :+ newOrder)
}
}
/**
* (4) 安全查询
* 使用 集合.find 算子检索,其默认返回 Option[Order],优雅处理找不到的边界情况
*/
def findByOrderId(orders: List[Order], orderId: String): Option[Order] = {
orders.find(_.orderId == orderId)
}
/**
* (5) 高阶统计 v1: 总销售额
* 必须使用 foldLeft 算子进行累加整合
*/
def totalSales(orders: List[Order]): Double = {
orders.foldLeft(0.0)((sum, order) => sum + order.amount)
}
/**
* (5) 高阶统计 v2: 统计各商品类别的订单数量
* 先用 groupBy 按类别分组,再用 map 转换 kv 对,提取 list 长度
*/
def countByCategory(orders: List[Order]): Map[String, Int] = {
orders.groupBy(_.category).map { case (category, list) => (category, list.size) }
}
/**
* (5) 高阶统计 v3: 找出消费总金额最高的用户
* 组合算子:按用户名分组 -> map汇总金额 -> maxBy求最大值
*/
def topConsumer(orders: List[Order]): Option[(String, Double)] = {
if (orders.isEmpty) None
else {
val userSalesMap = orders.groupBy(_.userName).map { case (user, list) =>
(user, list.map(_.amount).sum)
}
Some(userSalesMap.maxBy(_._2))
}
}
/**
* 扩展要求 (2): 复杂过滤与视图优化
* 使用 .view 转换为惰性求值视图,避免在海量数据清洗时产生连续的临时集合副本
*/
def filterByAmountRange(orders: List[Order], min: Double, max: Double): List[Order] = {
orders.view // 开启懒加载视图
.filter(o => o.amount >= min && o.amount <= max)
.toList // 强制触发生效,返回最终结果
}
/**
* 扩展要求 (3): 数据持久化 - 导出为 .csv 格式
*/
def exportToCsv(orders: List[Order], filePath: String): Unit = {
var writer: PrintWriter = null
try {
writer = new PrintWriter(filePath)
writer.println("orderId,userName,category,amount,timestamp") // 写入表头
orders.foreach { o =>
writer.println(s"${o.orderId},${o.userName},${o.category},${o.amount},${o.timestamp}")
}
} finally {
if (writer != null) writer.close()
}
}
/**
* 扩展要求 (3): 数据持久化 - 从 .csv 读取并还原
*/
def importFromCsv(filePath: String): List[Order] = {
var reader: BufferedReader = null
var result = List[Order]()
try {
reader = new BufferedReader(new FileReader(filePath))
reader.readLine() // 掠过首行表头
var line = reader.readLine()
while (line != null) {
val parts = line.split(",")
if (parts.length == 5) {
result = result :+ Order(parts(0), parts(1), parts(2), parts(3).toDouble, parts(4).toLong)
}
line = reader.readLine()
}
} catch {
case _: Exception => // 文件不存在时,静默处理返回空集合
} finally {
if (reader != null) reader.close()
}
result
}
}
/**
* (7) 入口程序:继承 App 特质运行主程序
*/
object OrderSystem extends App {
val csvPath = "orders.csv"
// 初始化存储:优先从持久化 CSV 加载,若无则初始化 5 条测试数据
var currentOrders: List[Order] = OrderAnalyzer.importFromCsv(csvPath)
if (currentOrders.isEmpty) {
val testData = List(
Order("1001", "张三", "电子产品", 5999.0, 1719120000L),
Order("1002", "李四", "服装鞋帽", 299.0, 1719123600L),
Order("1003", "张三", "生活用品", 99.5, 1719127200L),
Order("1004", "王五", "电子产品", 1299.0, 1719130800L),
Order("1005", "李四", "书籍办公", 150.0, 1719134400L)
)
testData.foreach { o =>
OrderAnalyzer.addOrder(currentOrders, o) match {
case Success(newList) => currentOrders = newList
case Failure(_) =>
}
}
}
/**
* 扩展要求 (1): 循环交互菜单
* 使用 @tailrec 尾递归注解,确保无限循环调用不会压栈导致 StackOverflowError
*/
@tailrec
def runMenu(): Unit = {
println(
"""
|======= 🛒 简易电商订单数据分析系统 =======
|1. 添加新订单
|2. 依订单号精确查询明细
|3. 查看全局高阶统计数据
|4. 按金额区间过滤订单(View 优化)
|5. 保存数据并退出系统
|=========================================
|请选择操作(1-5):""".stripMargin)
// 使用模式匹配处理用户的数字输入和操作分发
StdIn.readLine() match {
case "1" =>
println("请输入订单号:"); val id = StdIn.readLine()
println("请输入用户名:"); val user = StdIn.readLine()
println("请输入商品类别:"); val cate = StdIn.readLine()
println("请输入订单金额:"); val price = Try(StdIn.readDouble()).getOrElse(-1.0)
val timestamp = System.currentTimeMillis() / 1000
OrderAnalyzer.addOrder(currentOrders, Order(id, user, cate, price, timestamp)) match {
case Success(newList) =>
currentOrders = newList
println("🎉 订单添加成功!")
case Failure(e) =>
println(s"❌ 拦截到违规录入:${e.getMessage}")
}
runMenu()
case "2" =>
println("请输入要查询的订单号:")
val id = StdIn.readLine()
// 对 Option[Order] 安全类型进行提取与分支匹配
OrderAnalyzer.findByOrderId(currentOrders, id) match {
case Some(order) => println(s"🔍 检索结果:$order")
case None => println("❌ 检索失败:该订单号不存在。")
}
runMenu()
case "3" =>
println("\n📊 --- 聚合计算统计结果 ---")
println(f"💰 所有订单总销售额:${OrderAnalyzer.totalSales(currentOrders)}%.2f 元")
println("📦 各类商品订单计数:")
OrderAnalyzer.countByCategory(currentOrders).foreach { case (cate, count) =>
println(s" • [$cate]: $count 个订单")
}
OrderAnalyzer.topConsumer(currentOrders) match {
case Some((user, total)) => println(f"👑 高价值核心用户:$user (累计消费 $total%.2f 元)")
case None => println(" • 暂无消费数据")
}
runMenu()
case "4" =>
println("请输入区间最低金额:"); val min = Try(StdIn.readDouble()).getOrElse(0.0)
println("请输入区间最高金额:"); val max = Try(StdIn.readDouble()).getOrElse(Double.MaxValue)
val filtered = OrderAnalyzer.filterByAmountRange(currentOrders, min, max)
println(s"💡 符合金额区间 [$min - $max] 的懒加载处理结果:")
filtered.foreach(println)
runMenu()
case "5" =>
println("💾 正在执行底层 I/O 序列化,数据写入 CSV...")
OrderAnalyzer.exportToCsv(currentOrders, csvPath)
println("👋 系统文件保存成功,运行环境安全退出。")
case _ =>
println("⚠️ 无效指令,请输入数字 1-5。")
runMenu()
}
}
// 触发入口
runMenu()
}三、 功能自评表(Report 交付部分)
| 序号 | 考核细项要求 | 完成情况 | 对应源码/实现机制简述 |
|---|---|---|---|
| 基础 1 | 定义 Order 案例类 | 100% 完成 | case class Order 的声明与属性对齐。 |
| 基础 2 | 不变集合存储、支持函数式添加 | 100% 完成 | OrderAnalyzer.addOrder 内部通过 :+ 算子返回全新克隆对象。 |
| 基础 3 | 精确查询返回 Option[Order] | 100% 完成 | 利用 List.find 自动封装,并在控制台通过模式匹配进行解析。 |
| 基础 4 | 高阶统计(foldLeft 求和等) | 100% 完成 | 综合运用 foldLeft、groupBy、map、maxBy 算子组连串清洗。 |
| 基础 5 | 金额防负、单号防重异常处理 | 100% 完成 | 使用 if-else 条件守卫拦截阻断,并用 Try[Failure] 向上抛出。 |
| 基础 6 | App 特质入口程序与测试初始化 | 100% 完成 | 主类继承 App 特质,初始化 5 条默认数据。 |
| 扩展 1 | 交互式循环菜单与模式匹配分发 | 100% 完成 | 采用 @tailrec 控制无栈开销的循环,对用户数字做 match-case 分发。 |
| 扩展 2 | 金额区间过滤与 View 懒加载优化 | 100% 完成 | 在 filterByAmountRange 中显式调用 .view 建立流式计算链。 |
| 扩展 3 | 数据持久化(导出和导入读取 CSV) | 100% 完成 | 使用 PrintWriter 导出格式文件,并用 BufferedReader 跨周期恢复。 |