news 2026/9/22 13:27:59

搞定飞机托运行李价格计算,这3个实战项目细节救了我

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
搞定飞机托运行李价格计算,这3个实战项目细节救了我

搞定飞机托运行李价格计算,这3个实战项目细节救了我

很多兄弟都卡在同一个地方:语法背得滚瓜烂熟,LeetCode 算法题也能刷过,但真让你搭个完整的项目,脑子就一片空白。别急,这种“手残”不是你的问题,是缺乏实战项目的打磨。今天我们就拿一个看似简单但逻辑坑很多的业务场景——飞机托运行李价格计算,从零手撸一个完整的计算模块。

别小看这个需求,它涵盖了数据清洗、多规则判断、异常处理、单元测试以及性能优化,是一个极佳的入门级全栈练手题。我曾在掘金技术社区看到很多大厂面试真题里,类似的计费逻辑(如快递费、打车费)都是高频考点。咱们不整虚的,直接上代码,把坑填平。

项目目标与需求拆解

先搞清楚我们要干什么。航空公司托运行李的计费规则通常不是线性的,而是分段计价或者按重量档位收费。为了贴近真实场景,我们设定以下规则:

  1. 免费额度:经济舱 20kg,公务舱 30kg,头等舱 40kg。
  2. 超重部分:超过免费额度后,每 1kg(不足 1kg 按 1kg 计)收取固定费用。不同舱位费率不同。
  3. 特殊件:如果行李包含易碎品或精密仪器,需额外支付保价费,且单件重量超过 30kg 时,费率翻倍。
  4. 输入校验:必须处理非法输入(如负数重量、非数字字符串)。

我们的目标不是写一个 if-else 就完事,而是构建一个可扩展、可测试、高内聚的计算引擎。这能帮你从“写代码”进阶到“设计系统”。

目录结构设计

在动手写代码前,先规划好目录。好的结构能让后续维护变得轻松,这也是很多新手容易忽略的工程化细节。

luggage_calculator/
├── __init__.py
├── config.py          # 配置管理:舱位费率、免费额度
├── exceptions.py      # 自定义异常
├── core/
│   ├── __init__.py
│   ├── calculator.py  # 核心计算逻辑
│   └── validator.py   # 输入校验模块
├── tests/
│   ├── __init__.py
│   └── test_calculator.py # 单元测试
└── main.py            # 入口文件/演示脚本

这里采用了分层架构:config 存静态数据,core 存业务逻辑,tests 存测试用例。这种分离思想在任何中大型项目中都是通用的,养成习惯,受益终身。

核心代码实现

接下来是重头戏。我们将核心逻辑封装在 calculator.py 中,并引入策略模式的思想,虽然这里为了简洁用字典映射代替了复杂的策略类,但逻辑是一样的:将变化的规则与不变的逻辑分离

1. 配置与异常定义

# config.py
CABIN_RATES = {"economy": {"free_limit": 20, "rate_per_kg": 15.0, "premium_rate_multiplier": 2.0},"business": {"free_limit": 30, "rate_per_kg": 25.0, "premium_rate_multiplier": 2.0},"first": {"free_limit": 40, "rate_per_kg": 40.0, "premium_rate_multiplier": 2.0}
}# exceptions.py
class LuggageError(Exception):"""行李计算基础异常"""passclass InvalidWeightError(LuggageError):"""无效重量异常"""passclass UnknownCabinError(LuggageError):"""未知舱位异常"""pass

2. 输入校验模块

很多 Bug 都源于对输入的不信任。在掘金技术社区的许多分享中,防御性编程被反复提及。我们单独写一个 validator.py

# core/validator.py
from exceptions import InvalidWeightError, UnknownCabinError
from config import CABIN_RATESdef validate_input(weight: float, cabin_type: str, is_premium: bool = False):"""校验输入参数的合法性:param weight: 行李重量 (kg):param cabin_type: 舱位类型:param is_premium: 是否特殊件"""# 检查重量是否为数字if not isinstance(weight, (int, float)):raise InvalidWeightError("重量必须是数字类型")# 检查重量是否为正数if weight <= 0:raise InvalidWeightError("重量必须大于0")# 检查舱位是否支持if cabin_type not in CABIN_RATES:raise UnknownCabinError(f"不支持的舱位类型: {cabin_type}")# 检查特殊件标志if not isinstance(is_premium, bool):raise ValueError("is_premium 必须是布尔值")

3. 核心计算逻辑

这是整个模块的心脏。注意看注释,每一步都在处理边界情况。

# core/calculator.py
from config import CABIN_RATES
from core.validator import validate_input
import mathclass LuggageCalculator:def __init__(self):self.config = CABIN_RATESdef calculate_fee(self, weight: float, cabin_type: str, is_premium: bool = False) -> dict:"""计算托运行李费用:return: 包含详细费用的字典"""# 1. 前置校验validate_input(weight, cabin_type, is_premium)# 2. 获取当前舱位配置cabin_config = self.config[cabin_type]free_limit = cabin_config["free_limit"]base_rate = cabin_config["rate_per_kg"]# 3. 计算超重重量# 向上取整是关键:10.1kg 超重部分按 11kg 计算?不,是超出部分向上取整。# 假设免费额度内不收费,超出部分按 1kg 为单位向上取整excess_weight = max(0, weight - free_limit)billable_kg = math.ceil(excess_weight) # 向上取整# 4. 判断是否应用特殊费率# 规则:单件超过30kg 且 是特殊件,费率翻倍multiplier = 1.0if is_premium and weight > 30:multiplier = cabin_config["premium_rate_multiplier"]final_rate = base_rate * multiplier# 5. 计算总费用total_fee = billable_kg * final_rate# 6. 返回结构化数据,便于前端展示或日志记录return {"total_fee": round(total_fee, 2),"billable_kg": billable_kg,"rate_per_kg": round(final_rate, 2),"free_limit_used": min(weight, free_limit),"cabin_type": cabin_type,"is_premium_applied": bool(multiplier > 1.0)}

这段代码有几个亮点:

  • math.ceil 的使用:模拟了现实世界中“不足 1kg 按 1kg 计”的规则。
  • 结构化返回:没有直接返回一个浮点数,而是返回一个包含明细的字典。这在调试和对账时极其重要。
  • 逻辑解耦:校验、配置获取、计算逻辑清晰分开。

运行与测试

写代码不写测试,等于没写。我们用 pytest 来编写单元测试,确保我们的逻辑在各种边界情况下都是正确的。

# tests/test_calculator.py
import pytest
from core.calculator import LuggageCalculator
from exceptions import InvalidWeightError, UnknownCabinError@pytest.fixture
def calculator():return LuggageCalculator()def test_economy_within_limit(calculator):# 经济舱 15kg,未超重,费用应为 0result = calculator.calculate_fee(15, "economy")assert result["total_fee"] == 0assert result["billable_kg"] == 0def test_economy_over_limit(calculator):# 经济舱 25kg,超重 5kg,费率 15元/kgresult = calculator.calculate_fee(25, "economy")assert result["total_fee"] == 75.0assert result["billable_kg"] == 5def test_premium_heavy_item(calculator):# 公务舱 35kg,特殊件。免费30kg,超重5kg。# 基础费率25元,因为>30kg且特殊件,费率翻倍为50元。result = calculator.calculate_fee(35, "business", is_premium=True)assert result["total_fee"] == 250.0assert result["rate_per_kg"] == 50.0def test_invalid_weight(calculator):with pytest.raises(InvalidWeightError):calculator.calculate_fee(-10, "economy")def test_unknown_cabin(calculator):with pytest.raises(UnknownCabinError):calculator.calculate_fee(10, "vip")

运行测试命令:

pytest tests/ -v

如果看到所有测试通过,恭喜你,核心逻辑已经跑通。这个环节的价值在于,当你后续修改费率逻辑时,测试会立刻告诉你哪里改错了,而不是等上线后用户投诉才发现问题。

优化扩展与避坑指南

代码跑通了,但这只是开始。在实际生产环境中,还有几个坑需要你提前踩平:

  1. 并发与线程安全: 如果这是一个 Web 服务的一部分,LuggageCalculator 实例如果被多个线程共享,目前的代码是安全的,因为它没有修改实例状态(无状态计算)。但如果你引入了缓存(比如缓存计算结果以提升性能),就必须加锁或使用线程本地存储。

  2. 费率动态化: 目前费率是硬编码在 config.py 里的。在真实业务中,费率可能会随季节、航线变化。建议将配置从代码中剥离,放入数据库或 Redis,并设计一个配置加载器,支持热更新。

  3. 日志记录: 在 calculate_fee 方法中加入 logging 模块,记录每次计算的输入、输出和耗时。当用户投诉“为什么我要付这么多钱”时,你可以通过日志还原当时的计算过程,这是排查问题的救命稻草。

  4. 国际化考虑: 货币单位、重量单位(磅 vs 公斤)都需要抽象出来。不要硬编码 15.0,而应该定义一个 CurrencyUnit 枚举,方便后续扩展多币种和多单位支持。

  5. API 封装: 如果用 Flask 或 FastAPI 封装,记得对返回的 JSON 数据进行序列化优化,避免暴露内部配置细节。

小结

通过这个飞机托运行李价格计算的实战项目,我们不仅解决了一个具体的业务问题,更锻炼了工程化的思维。从目录规划、异常处理、单元测试到性能优化,每一步都是工业级开发的缩影。

很多开发者觉得业务逻辑简单,不屑于深入,但正是这些看似简单的 CRUD 和计算逻辑,构成了系统的基石。把基础打牢,比盲目追逐新技术框架更重要。

在掘金技术社区的讨论区,经常有朋友问:在涉及金额计算时,是用 float 还是 Decimal?这是一个经典争议。我目前的做法是在前端展示时用 float,在后端核心计费时用 Python 的 decimal 模块,以避免二进制浮点数精度丢失问题。

你公司项目里是怎么处理这类计费逻辑的?有没有遇到过精度丢失或者规则冲突的坑?欢迎在评论区分享你的经验,我们一起避坑。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/22 13:27:40

行测题型完整示例:大厂面试官拆解高频坑点

行测题型完整示例:大厂面试官拆解高频坑点 看到满屏的 java.lang.NullPointerException 和层层叠叠的 StackTrace,你是不是也头大?别慌,我见过太多人在面试时因为没搞懂这些底层逻辑,直接卡在“报错一堆看不懂”的尴尬局面。今天咱们不整虚的,直接上 行测题型 的…

作者头像 李华
网站建设 2026/9/22 13:27:37

十佳笔记本电脑选型速查手册:告别版本API变动陷阱

十佳笔记本电脑选型速查手册:告别版本API变动陷阱 版本升级后 API 全变了,这种崩溃感谁懂?昨天还能跑的代码,今天报一堆 TypeError ,查文档发现接口签名全改,连参数顺序都换了。这时候你急需一份 速查手册 ,而不是重新啃一遍官方文档。…

作者头像 李华
网站建设 2026/9/22 13:27:29

第三波外汇源码解析:3个致命Bug与避坑实录

第三波外汇源码解析:3个致命Bug与避坑实录 官方文档像天书,翻了两页就劝退?别急,咱们直接扒开 第三波外汇 的源码,看看那些藏在代码深处的坑。很多新手在对接接口时,因为没看清底层逻辑,导致交易指令丢失或状态错乱,最后背了一锅黑锅。今天不讲虚的,直接上干货,带你从 源码解析…

作者头像 李华
网站建设 2026/9/22 13:27:25

多伦多大学官网证书下载报错?一文搞懂性能优化实战

多伦多大学官网证书下载报错?一文搞懂性能优化实战 打开浏览器,输入 https://www.utoronto.ca ,点击“Student Records”里的“Degree Search”,结果页面转了五分钟,最后弹出一个红色大叉。控制台里 Traceback 堆成山, 502 Bad…

作者头像 李华
网站建设 2026/9/22 13:27:18

绝地求生卡运行3步搞定,从入门到精通避坑指南

绝地求生卡运行3步搞定,从入门到精通避坑指南 面试被问原理答不上来,这种尴尬场景谁没经历过?明明代码跑通了,一问底层逻辑就卡壳,这种“伪熟练”在技术圈太常见了。想真正从入门到精通,光靠背八股文没用,得把问题拆解到最细粒度,像处理绝地求生卡运行这类具体故障一样,一步步排查、定位、解决。…

作者头像 李华
网站建设 2026/9/22 13:27:16

603527高频面试题:选型对比与避坑指南

603527高频面试题:选型对比与避坑指南 面试被问原理答不上来,那种脑子一片空白的感觉太痛苦了。尤其是面对【603527】这类看似简单实则暗藏玄机的 高频面试题 ,很多开发者只知其然不知其所以然。…

作者头像 李华