Skip to content

📄 运行与设计报告(核心提纲参考) ​

一、 架构设计说明 ​

本系统采用 Scala 典型的函数式编程与面向对象相结合的架构设计:

  1. 数据模型层 (Model):使用 case class Order 封装基础订单属性,利用案例类天生的不可变性、自带 toString 和支持模式匹配的特性,简化了中间数据的流转。
  2. 业务逻辑层 (Service/Analyzer):将所有计算和操作抽离在单例对象 OrderAnalyzer 中,纯化计算逻辑。
    • 不可变状态流转:摒弃全局 var 变量,添加新订单时通过返回全新 List[Order] 实现“状态无副作用流转”。
    • 类型安全控制:查询操作返回 Option[Order],通过类型系统在编译期规避 NullPointerException 风险。
    • 性能优化思想:引入 List.view(惰性求值视图),将多阶段的过滤操作合并至一次迭代中完成,模拟大数据环境下的“延迟算子触发”机制。
  3. 交互与持久化层 (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] 向上抛出。
基础 6App 特质入口程序与测试初始化100% 完成主类继承 App 特质,初始化 5 条默认数据。
扩展 1交互式循环菜单与模式匹配分发100% 完成采用 @tailrec 控制无栈开销的循环,对用户数字做 match-case 分发。
扩展 2金额区间过滤与 View 懒加载优化100% 完成在 filterByAmountRange 中显式调用 .view 建立流式计算链。
扩展 3数据持久化(导出和导入读取 CSV)100% 完成使用 PrintWriter 导出格式文件,并用 BufferedReader 跨周期恢复。

基于 Vite 强力驱动 | 纯静态轻量托管