OTA 订单搜索字段设计:公共字段与业务扩展字段的实践思考
本文是一份 OTA(在线旅游)电商订单搜索功能字段设计的实践笔记,涵盖公共字段与酒店、机票、门票、特色游四类业务的特有字段,重点讨论敏感字段非明文存储下的搜索方案、多值字段与语种设计等关键问题。
引言:为什么订单搜索这么难做
在 OTA 电商体系中,订单搜索是一个"看似简单、实则处处是坑"的功能。它的用户角色非常多元:客服要按乘客手机号快速定位订单处理售后,用户要在"我的订单"里按酒店名、航班号找历史订单,运营要按时间段、按城市统计订单量,风控则可能按证件号关联用户。同一个搜索入口,承载的是完全不同的检索语义。
更麻烦的是,订单数据天然具备三个特征:
- 结构多变:同一笔订单里可能同时包含机票和酒店(打包售卖),也可能只有一张门票,字段横跨多个业务域;
- 多值普遍:一个订单可以有多名乘客、多段行程、多间房,几乎所有关键属性都是列表;
- 敏感信息密集:手机号、证件号属于个人敏感信息,按合规要求不能明文存储,但客服又必须靠这些信息反查订单。
本文记录的是我在实践中沉淀的一套字段设计方案:把"所有订单都具备的属性"抽成公共字段,把"只有某类订单才有的属性"做成业务扩展字段,并针对敏感字段搜索、语种、城市等细节给出思考。内容框架如下:
- 公共字段:乘客信息、价格、时间等全订单通用属性;
- 业务扩展字段:酒店、机票、门票、特色游各自的独有属性;
- 关键设计考量:敏感字段加密存储与可搜索性的权衡、多值字段的索引策略、下单语种问题。
一、公共字段:所有订单的"通用语言"
无论订单属于哪个业务线,总有一部分属性是共通的——谁买的、花了多少钱、什么时候下的单。这些字段是订单搜索的地基,建议设计如下:
| key | value | remark |
|---|---|---|
| pg_name_list | 张三,李四 | |
| pg_phone_list | 130xxx,150xxx | 是否明文存储,非明文存储搜索怎么解决? |
| pg_card_number_list | 440xxx,EXDxxx | 是否明文存储,非明文存储搜索怎么解决? |
| total_price | 1466 | |
| item_price_list | 200,1266 | |
| create_time | 2024-10-11 10:10:10 | yyyy-MM-dd HH:mm:ss |
| pay_time | ||
| refund_time |
逐一说明设计意图:
pg_name_list(乘客姓名列表):一个订单可能包含多位乘客,用_list后缀明确表达"这是多值字段"。乘客姓名是搜索的高频入口,用户和客服都习惯直接搜名字。pg_phone_list(乘客手机号列表):这是订单搜索最核心、也最敏感的入口之一。客服处理售后时,第一反应就是问"您留的手机号是多少"。手机号通常作为精确匹配条件使用。pg_card_number_list(乘客证件号列表):证件号(身份证、护照、港澳通行证等)同样是强关联字段,常用于风控与实名制校验场景。注意示例中440xxx(大陆身份证)与EXDxxx(通行证)并存,说明同一列表里可以混合不同证件类型,这给搜索和展示都带来额外复杂度。total_price/item_price_list(总价 / 明细价格列表):总价用于快速展示和金额区间筛选,明细价格列表则对应订单内各细分项(如机票 200 元 + 酒店 1266 元)。两套价格并存,是为了满足"总额"与"明细"两种不同的查询语义。create_time/pay_time/refund_time(创建 / 支付 / 退款时间):三个时间戳覆盖了订单的完整生命周期。时间筛选几乎是所有搜索场景的必选条件,建议在存储层就为它们建立索引。
敏感字段:明文存储还是加密存储?
这是设计表中两个 remark 直指的核心问题。手机号和证件号,明文存不存?
明文存储的风险是显而易见的:一旦数据库被拖库或日志泄露,海量个人敏感信息将直接暴露。在《个人信息保护法》和《数据安全法》落地后,对敏感信息做加密或脱敏存储已经成为合规底线,明文存储基本不可接受。
但加密存储带来的直接后果是:搜索怎么做? 这是本文最想展开的技术点,常见方案有几种:
- 哈希 + 精确匹配:对手机号做不可逆哈希(如 HMAC-SHA256,配合项目级密钥),搜索时将用户输入做同样的哈希处理后精确匹配。优点是存储无明文、检索速度快(可以直接走索引);缺点是只能精确匹配,无法支持模糊查询,一旦用户报错一位数字就找不到订单。
- 可逆加密 + 解密后匹配:数据用 AES 等可逆算法加密存储,搜索时把候选集解密后在内存中匹配。优点是支持模糊搜索;缺点是数据量大时性能灾难,且解密动作本身就扩大了敏感数据的暴露面,一般不推荐作为主方案。
- 分词 + 盲索引(blind index):对手机号按固定窗口切分(如 11 位号码拆成若干 4~6 位的子串),每个子串单独哈希建索引。搜索时对输入号码做同样的切分与哈希,取交集召回候选,再对少量候选做精确校验。这是在"可搜索"与"不泄露"之间的常用折中。
- 保留格式加密(FPE):加密后仍保持手机号 / 证件号的字符格式,理论上可以直接在密文上做前缀匹配。实现复杂、对算法库有依赖,实际落地较少。
实践中比较稳妥的落地方案是 「加密存储 + 盲索引精确匹配 + 支持尾号等固定维度检索」:客服输入完整手机号时走索引精确命中;支持按"尾号 4 位"这类固定的非敏感维度查询时,可以单独维护一个尾号索引字段。无论如何,方案都应在设计阶段定下来,因为字段存储格式一旦确定,后续改造数据迁移的成本极高。
顺带一提,展示层还有一道必须做的工序:即便内部存储加密,接口返回给前端的数据也必须脱敏(如 130****1234),避免敏感信息在日志、监控、抓包链路中二次泄露。
二、业务扩展字段:四类业务的"专属属性"
公共字段之外,不同业务线的订单差异巨大。酒店需要房型,机票需要航班号,门票需要景区,特色游需要线路名。如果把这些字段全部平铺进一张大宽表,字段数量会爆炸且大部分为空;因此按业务线拆分扩展字段是更优雅的做法。
1. 酒店订单
| key | value | remark |
|---|---|---|
| hotel_name_list | 维也纳 | zh_CN,下单时语种 |
| address_list | 福海xxx | |
| room_name_list | 大床房 |
酒店搜索的典型场景是"帮家人订过哪家酒店"“在某个城市住过哪家店”。hotel_name_list 是核心入口,address_list 支持按地址模糊检索(例如只记得"在福海那边"),room_name_list(房型)则用于区分同店不同订单。
remark 里的 zh_CN,下单时语种 是一个很容易被忽视的细节:酒店名称按用户下单时使用的语言存储。如果用户用英文界面下单,存的就是英文酒店名。这意味着同一家酒店在不同订单里可能出现中英文两种写法,搜索时必须做多语种归一化(见下文"语种问题")。
2. 机票订单
| key | value | remark |
|---|---|---|
| airline_name_list | 中国南方航空 | zh_CN,下单时语种 |
| airline_code_list | CZ | |
| flight_no_list | CZ8029 | |
| airport_name_list | 大兴国际机场,浦东国际机场 | |
| airport_code_list | PEK,SHA | |
| actually_city_list | 北京,上海;ps:不含中转 |
机票是字段最丰富的业务线,因为一段行程涉及"航司—航班—机场—城市"多个维度,且同一订单可能包含多段航程(去程 + 回程),所以几乎所有字段都是列表。
设计上明显遵循了"名称与代码分离“的原则:既有 airline_name_list(中国南方航空)也有 airline_code_list(CZ),既有 airport_name_list(大兴国际机场)也有 airport_code_list(PEK)。这样做有三个好处:
- 用户习惯搜中文名,客服可能只记得三字码,两类入口都要支持;
- 代码是稳定标识,名称会随语言变化,用代码做关联、用名称做展示,各司其职;
- 三字码在检索时天然规避了中文分词问题。
flight_no_list(航班号)是机票订单最高频的精确检索条件,用户普遍用航班号找行程。
actually_city_list 的 remark “不含中转"同样值得玩味:它的语义是"乘客实际出发和到达的城市”,排除了中转经停城市。比如"北京—上海"的航班经停郑州,actually_city_list 应存 北京,上海,而不是 北京,郑州,上海。这样设计的原因在于,中转城市对用户没有业务意义——用户搜"上海"期望的是最终目的地是上海,而不应被"经停上海"的航班干扰命中。
3. 门票订单
| key | value | remark |
|---|---|---|
| scenic_name_list | 青青世界 | zh_CN,下单时语种 |
| scenic_address_list | 福海xxx | |
| scenic_ticket_name_list | 成人票 | |
| scenic_city_list | 深圳 |
门票订单相对简单,围绕"景区"展开:scenic_name_list(景区名)、scenic_address_list(景区地址)、scenic_city_list(所在城市),外加 scenic_ticket_name_list(票种,如成人票 / 儿童票)。scenic_city_list 单独抽出来的意义在于,城市维度在门票场景中具有独立的检索价值——“帮我查查在深圳买过哪些门票”。
4. 特色游订单
| key | value | remark |
|---|---|---|
| name_list | 中国企业家戈壁徒步行 | zh_CN,下单时语种 |
| city_list | 酒泉 |
特色游(主题游、定制游、徒步团等)字段最精简,只有 name_list(线路名称)和 city_list(目的地城市)。它体现了一个朴素的设计原则:新增业务线时,只沉淀真正用于检索的字段,不要提前设计用不上的属性。
三、四个值得深挖的设计考量
1. 多值字段的索引策略
几乎所有字段都是 _list 结尾的列表,这给搜索引擎(如 ES)的索引设计带来直接要求。以 ES 为例,推荐策略:
- 列表字段映射为
keyword类型的数组(array of keyword),保证精确匹配与 term 查询; - 需要模糊搜索的字段(酒店名、景区名)单独建
text类型子字段,配合 ik 中文分词; - 名称与代码双字段(航司、机场)在建索引时应做映射归一化,例如在写入时同步生成一个
airport_code_keyword字段,避免查询时中文分词带来的不稳定性。
2. 语种问题:下单时语种与搜索语种不一致
zh_CN,下单时语种 背后是一个完整的问题链:用户下单时用的语言决定了数据的存储语言,而搜索时的语言可能不同。比如用户用中文下单,酒店名存的是"维也纳酒店”,后来用英文界面搜索 “Vienna” 就搜不到了。
可行的解法:
- 存储侧:保存下单语种字段,同时冗余一份"标准名称"(如 POI ID 或统一的中文名),检索时以标准名为主;
- 查询侧:对搜索词做多语种翻译 / 同义词扩展后再检索;
- 最彻底的做法:直接建立"酒店 ID / 航司代码"这类稳定标识的检索,让搜索与展示解耦,展示名称再按当前语种本地化渲染。
3. 时间字段与搜索边界
create_time / pay_time / refund_time 三个时间点覆盖订单全生命周期,但搜索语义上要小心:用户说"我上个月的订单",可能指创建时间,也可能指出行时间(旅游订单更关心出行)。建议在搜索界面明确标注时间条件对应的字段(如"下单时间"“出行时间”),避免语义歧义。此外,未支付订单只有 create_time,支付后退款才会填充 refund_time,空字段本身就是一种状态信号,可用于状态维度的过滤。
4. 城市字段的语义边界
机票的 actually_city_list(不含中转)和门票的 scenic_city_list、特色游的 city_list,三个"城市"字段语义各不相同:前者是行程起终点,中间是景区所在地,后者是线路目的地。设计时务必在字段命名上体现差异,并在文档中明确每个城市字段的定义边界,否则后续维护者很容易混用,导致"按城市搜机票把经停城市也搜出来了"这类线上问题。
四、个人观点与延伸思考
回顾整套字段设计,有几个点是我特别想强调的:
1. “名称 + 代码"双字段是 OTA 行业的高性价比实践。 航司、机场这类实体有全球标准化的代码体系(IATA),用代码做检索、用名称做展示,既绕开了中文分词的坑,也为国际化埋好了伏笔。这套思路同样可以迁移到酒店(PMS 编码)、景区(景区 ID)上。
2. 敏感字段的存储方案一定要设计期定稿。 手机号、证件号这类字段,一旦上线后发现要加密存储,历史数据迁移、搜索逻辑重构的成本远高于开发期的实现成本。我的建议是:即使业务初期不强制合规,也按"加密存储 + 盲索引"的标准来设计,留好密钥轮换的扩展点。
3. 字段文档即接口契约。 本文这套字段表最大的价值不是字段本身,而是每行后面的 remark——语种、不含中转、敏感字段方案,这些备注才是真正的设计决策记录。建议把这类文档沉淀到代码仓库中并持续维护,任何字段变更都同步更新,避免"代码改了文档没改"的经典事故。
4. 面向未来的扩展性。 目前四类业务各有扩展字段,未来若新增"火车票"“邮轮"“接送机"等业务线,遵循同样的模式即可快速接入:公共字段复用,业务字段新增一张表。这套"公共 + 扩展"的结构,本质上是一种轻量的垂直切分,相比把所有字段塞进一张超级大表,更利于各业务的独立演进。
结语
订单搜索的字段设计,表面上是定义几个字段、建几个索引,实际上是在回答三个问题:用户会怎么找订单?哪些信息能合法地存?如何让检索在不同业务、不同语种下保持稳定? 本文以公共字段 + 业务扩展字段的结构,给出了 OTA 场景下的一份参考答案:公共字段保证跨业务的统一检索能力,扩展字段承载各业务线的差异化属性,而敏感字段的加密存储与盲索引方案,则是合规与体验之间的平衡点。
当然,任何字段设计都不是一劳永逸的。随着业务演进(多语种覆盖、更复杂的中转行程、新增业务线),这套方案也需要持续迭代。如果你正在设计或重构订单搜索,希望这份笔记能给你一些参考,也欢迎交流不同的做法。