机票+火车票聚合查询与预订系统

📅 发布时间:2026/7/5 13:03:07 👁️ 浏览次数:
机票+火车票聚合查询与预订系统
核心需求与技术拆解在动手前把整个系统劈成了两条线:明线(业务层):在一个页面内无缝切换“机票/火车票”处理单程/往返逻辑、多坐席选择、以及不同接口字段的UI抹平。暗线(数据层):抓包发现真实接口的Header里藏了三个动态参数:token、transactionid和 sign。拿不到这三个大爷浏览器连根毛都请求不到。针对这堆问题我设计了四个核心模块。下面咱们挨个解剖。模块一:基于原生JS的“纯手工状态机”解决痛点:无框架下的跨业务线状态污染用原生JS写复杂交互最怕的就是写出一坨”面条代码”(DOM操作和数据逻辑搅在一起)。我的解法是手动捍一个全局state对象强制约束:所有点击事件只准修改数据不准碰DOM;修改完后统一调用render()重绘。但这里有个大坑:当用户在查机票时突然点了一下上方的“火车票”Tab如果状态清理不干净下方的列表就会出现“火车票列表里混着飞机航班号”的严重事故。源码剖析(前端 UI状态控制):// 全局唯一的数据源 (SSOT) const state { activeTab: flight, // 当前业务线 flightList: [], // 承载搜索结果机票/火车票复用 flightStep: depart // 往返逻辑状态去程/返程 // ... }; // 状态修改动作严防数据污染 window.switchTab (tab) { state.activeTab tab; state.hasSearched false; // 核心点切业务线时必须强制清空上一业务的脏数据 state.flightList []; state.apiError null; // 智能容错切火车票自动填高铁站切机票自动填城市 if (tab train) { state.fromCity 北京南; state.toCity 上海虹桥; } else { state.fromCity 北京; state.toCity 上海; } render(); // 触发全量重绘 };这种手工状态机的精髓在于对数据的绝对控制。哪怕是选坐席(二等座/头等舱)我也把具体价格动态绑到了 state.selectedDepTicket里保证往返票在最后结算时金额绝对不会算错。模块二:BFF代理与协议组装模块解决痛点:CORS跨域死锁与加密入参组装如果我们直接在前端用fetch请求某程的接口浏览器直接就会爆红(CORS跨域拦截)。而且那些签名字段放在前端太容易被扒了所以我引入了Node四.js作为BFF层(前端专属后端)。前端只管传出发地、目的地Node.js负责去拼装transactionld等校验参数。源码剖析(Python/Node 协议组装):# main.py (业务组装层片段) def getFlightInfo(departureCity, arrivalCity, departureDate): # 1. 预请求获取交易ID与核心配置参数 json_data getTransactionId(departureCity[code], arrivalCity[code], departureDate) # 2. 从返回的 json_data 中提取 transactionID transactionId json_data[transactionID] # 3. 拼装原生字符串并 MD5 加密生成 sign signSrc transactionId departureCityCode arrivalCityCode departureDate sign hashlib.md5(signSrc.encode(utf-8)).hexdigest() # 4. 调用 JS 虚拟机获取变态级别的风控 token token ctx.call(getToken, json.dumps(json_data)) # 5. 带上这三兄弟正式发起 batchSearch 请求 flights getFly(sign, token, transactionId, json_data)复盘:在这个流程里transactionlD算是白给的在初始化页面的JSON 或者预检请求里就能截获;sign只是简单的参数拼接MD5。真正的大头是那个靠JS混淆生成的token模块三:JS虚拟环境补全(风控对抗核心)解决痛点:绕过浏览器指纹与高度混淆的点开生成token的源文件我直接好家伙:一整个几万行的自执行函数变量名全是 unknown xxx里面充斥着类似RozanHyakuryuHa(庐山升龙霸)这种恶搞函数名实际上底层走的是控制流平坦化(AST混淆)。强行解混淆太耗时间最好的办法是黑盒调用(RPC/补环境)。大厂的风控脚本一定会疯狂检测执行环境是否是真实浏览器。为此我搞了一个巨大的env.js硬生生在Node环境里伪造了一个浏览器。源码剖析(env.js 补环境片段):// env.js (Node.js 补环境伪装浏览器) var v_new_toggle true Object.freeze(console) // 阻断风控脚本检测控制台状态 // 伪造极其逼真的 Navigator 指纹 Navigator v_saf(function Navigator(){ this._plugins typeof PluginArrayundefined?[]:v_new(PluginArray); }) Object.defineProperties(Navigator.prototype, { userAgent: {get(){ return Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/131.0.0.0 }}, platform: {get(){ return Win32 }}, webdriver: {get(){ return false }}, // 重点反爬检测的核心指标 hardwareConcurrency: {get(){ return 32 }}, }) // 连底层的 WebGL 显卡渲染指纹都必须造假 WebGLRenderingContext v_saf(function WebGLRenderingContext(){ this.getParameter function(key){ if (this._toggle[key]){ // 伪装 NVIDIA 显卡信息 if (key 37445){ return Google Inc. (NVIDIA) } if (key 37446){ return ANGLE (NVIDIA, NVIDIA GeForce GTX 1050 Ti Direct3D11 vs_5_0) } } } })复盘:风控不仅查 window、document,连PerformanceTiming(页面加载性能耗时)、Crypto.getRandomValues (硬件随机数)、甚至Canvas渲染出来的toDataURL()base64图像特征都在查。环境补得越像生成的token存活率就越高。模块四:沙箱执行与内存隔离解决痛点:加密脚本导致Node.js内存泄漏在服务端高频调用混淆JS时最容易翻车的地方就是全局变量污染。那段“庐山升龙霸”代码会在window上挂载一堆加密状态如果多次请求复用同一个JS上下文很快就会报错或被风控封锁。源码剖析(sdt.js沙箱运行):// sdt.js const fs require(fs); const vm require(vm); // 1. 创建一个干净的全局上下文隔离区 const context vm.createContext(global); // 2. 将补好的环境 (env.js) 丢进沙箱执行防止污染宿主 Node 进程 vm.runInThisContext(fs.readFileSync(./env.js)); // 3. 混淆代码执行向沙箱的 window 对象中注入 signature 函数 (function() { function RozanHyakuryuHa() { // ... (几万行控制流平坦化混淆代码) var _unknown_a6536 decode(_unknown_c9301.b).split().reduce(...) } try { RozanHyakuryuHa()(window, {b: PwEEAQUw..., d: [...]}); } catch(e){} })(); // 4. 暴露出干净的调用入口 function getToken(param){ // 此时的 window 是 env.js 伪造的signature 是混淆脚本挂载的 return window.signature(param) }复盘:利用Node自带的vm模块我们让这坨来路不明的混淆代码跑在了一个受限的沙箱里。拿完token就走人既保证了并发时的线程安全也防住了内存泄漏。踩坑最多的地方除了逆向环境前端业务其实有个极大的坑:数据结构聚合适配。机票接口查回来的对象叫 deptAirportName坐席叫 cabin;但火车票查回来叫dept_ station_name坐席是一个嵌套数组 cabin_info。为了不写两套重复的UI渲染代码我在请求拿到数据后立刻做了一层适配器模式(Adapter)。无论是飞机还是火车统统洗成包含idtrainNo/fightNo,depTime,seats的标准对象。最后在订单结算页只用一段模板字符串结合短路求值info.depAirportllinfo.depStation就完美兼容了双端展示。总结这次不依赖ReactVue强行手撸聚合平台加上跟大厂风控硬碰硬的逆向对抗整个过程虽然蛋疼但极其锻炼内功。当你不再是个纯粹的APl调用侠而是被迫去思考:浏览器指纹是如何生成的?沙箱内存该如何隔离?没有虚拟DOM状态流转该怎么闭环?你对整个Web生态的理解就会发生质的变化。希望这次的复盘能给你在做架构设计或对抗风控时提供一点不一样的思路。