酒店api
    • 开发前必读
    • 接口对接流程图
    • 签名鉴权文档
    • 订单回调通知对接文档
    • 渠道账户接口
      • 渠道账户余额
        GET
    • 数据字典接口
      • 品牌数据
        GET
      • 城市商圈字典
        GET
      • 全国所有城市
        GET
    • 酒店接口
      • 酒店详情
        GET
      • 房型报价
        GET
      • 酒店列表
        GET
    • 酒店订单接口
      • 取消/退单
        POST
      • 创建酒店订单
        POST
      • 订单详情
        GET
    • 数据模型
      • CancelHotelOrderRequest
      • Hotel
      • CreateHotelOrderRequest
      • 产品信息
      • PageResponse«Hotel»
      • 入住人信息
      • 价格报价
      • 创建酒店订单请求体
      • 入住人分组
      • 取消条款
      • 取消酒店订单请求体
      • 品牌数据
      • 城市
      • 城市商圈
      • 小时房(钟点房)规则
      • 房型产品列表
      • 房型报价
      • 渠道账户
      • 搜索酒店列表请求体
      • 订单
      • 酒店订单
      • 酒店详情
      • 长隆门票场次
      • 间夜价格
      • 预订条款

    签名鉴权文档


    一、接入准备#

    项说明
    中台联系我方商务或技术开通测试环境
    接口域名以分配的环境为准
    渠道标识appId(在我方后台获取)
    渠道密钥secret(在我方后台获取)
    数据格式请求 application/json(POST)或 query 参数(GET);响应 application/json
    鉴权方式每个接口必须携带签名参数 appId / timestamp / nonce / sign,服务端 MD5 校验
    渠道与密钥由平台在后台配置(Channel + ChannelApi(酒店模块)),不会在接口中暴露,请妥善保管 secret,可以向技术要签名生成Java工具类。

    二、通用请求规范#

    2.1 通用参数(放 URL query,所有接口必带)#

    参数类型必填说明
    appIdString是渠道标识(后台分配的渠道编码)
    timestampString是请求时间戳,建议毫秒级(当前服务端仅参与签名,不校验有效期)
    nonceString是随机串(防重放标识,建议每次请求随机生成;当前服务端仅参与签名,不校验重复)
    signString是签名值(见第三节算法)
    ⚠️ 四个通用参数必须放 URL query(服务端从 request.getParameter 读取 appId/timestamp/nonce/sign)。POST 时 body 里不要重复放这四个参数。

    2.2 请求示例(GET)#

    GET /api/hotel/detail?hotelId=10001&checkInDate=2026-09-01&checkOutDate=2026-09-02&adultNum=1&appId=BM_HOTEL_TEST&timestamp=1725000000000&nonce=6f2a1c9d&sign=09d10b698cb42db31b4cdf0b700dbd24

    2.3 请求示例(POST)#

    POST /api/order/create?appId=BM_HOTEL_TEST&timestamp=1725000000000&nonce=8b31d4e7&sign=17a4f6cc7b97bf35c4094fbde593bb0c
    Content-Type: application/json
    
    { "channelOrderNo": "CH202609010001", "hotelId": "10001", "roomTypeId": "RM001",
      "keyId": "KP001", "checkInDate": "2026-09-01", "checkOutDate": "2026-09-02",
      "roomGroups": [{"adultNum":1,"childNum":0}],
      "dataList": [{"realName":"张三","idCard":"330100199001011234"}],
      "mobile": "13800000000", "memo": "无烟房" }

    三、签名算法(核心)#

    签名 = MD5( 参数串 + secret ),参数串按以下规则生成。

    3.1 参与签名的参数#

    1.
    取请求 JSON body(POST 场景)解析为对象;GET 场景为空对象;
    2.
    合并 URL query 全部参数(appId、timestamp、nonce 等)——query 参数覆盖 body 中的同名字段;
    3.
    移除 sign(sign 本身不参与签名)。

    3.2 参数扁平化(关键)#

    把嵌套的对象/数组压平成 key=value 键值对:
    结构规则示例
    顶层字段直接用字段名hotelId
    嵌套对象父键 + . + 子键order.user.name
    数组元素父键 + [下标]dataList[0].realName、roomGroups[0].adultNum

    3.3 排序#

    所有扁平化后的 key 按字典序升序排序(ASCII 序,同 Java TreeMap 自然序)。

    3.4 拼接#

    按排序后的顺序,以 key=value 形式拼接,多个用 & 连接,值为 null 或空字符串的键直接跳过(不参与拼接、不占 & 位置):
    k1=v1&k2=v2&k3=v3

    3.5 追加密钥并 MD5#

    在拼接串末尾直接追加 secret(中间无分隔符),对整串做 UTF-8 编码的 MD5,输出 32 位小写十六进制即 sign:
    sign = MD5( k1=v1&k2=v2&k3=v3 + secret )

    3.6 算法伪代码#

    合作方侧实现时请按同一规则生成签名,服务端 AuthorizationInterceptor 用相同算法重算并比对(不等则返回 code=101 签名不匹配)。

    四、签名示例(已用服务端真实类验证)#

    以下示例使用示意密钥:secret = 9f8e7d6c5b4a3e2f1d0c9b8a7f6e5d4c(实际以分配为准)。

    4.1 示例一:GET /api/hotel/detail#

    请求参数(query):appId=BM_HOTEL_TEST、timestamp=1725000000000、nonce=6f2a1c9d、hotelId=10001、checkInDate=2026-09-01、checkOutDate=2026-09-02、adultNum=1
    扁平化 + 排序后键:
    adultNum, appId, checkInDate, checkOutDate, hotelId, nonce, timestamp
    拼接串(不含 secret):
    adultNum=1&appId=BM_HOTEL_TEST&checkInDate=2026-09-01&checkOutDate=2026-09-02&hotelId=10001&nonce=6f2a1c9d&timestamp=1725000000000
    完整签名串(含 secret):
    adultNum=1&appId=BM_HOTEL_TEST&checkInDate=2026-09-01&checkOutDate=2026-09-02&hotelId=10001&nonce=6f2a1c9d&timestamp=1725000000000
    9f8e7d6c5b4a3e2f1d0c9b8a7f6e5d4c
    sign = 09d10b698cb42db31b4cdf0b700dbd24

    4.2 示例二:POST /api/order/create#

    URL query:appId=BM_HOTEL_TEST、timestamp=1725000000000、nonce=8b31d4e7
    请求 body:
    {
      "channelOrderNo": "CH202609010001",
      "hotelId": "10001",
      "roomTypeId": "RM001",
      "keyId": "KP001",
      "checkInDate": "2026-09-01",
      "checkOutDate": "2026-09-02",
      "roomGroups": [{ "adultNum": 1, "childNum": 0 }],
      "dataList": [{ "realName": "张三", "idCard": "330100199001011234" }],
      "mobile": "13800000000",
      "memo": "无烟房"
    }
    body + query 合并、移除 sign 后扁平化 + 排序键:
    appId, channelOrderNo, checkInDate, checkOutDate, dataList[0].idCard,
    dataList[0].realName, hotelId, keyId, memo, mobile, nonce,
    roomGroups[0].adultNum, roomGroups[0].childNum, roomTypeId, timestamp
    拼接串(不含 secret):
    appId=BM_HOTEL_TEST&channelOrderNo=CH202609010001&checkInDate=2026-09-01&checkOutDate=2026-09-02&dataList[0].idCard=330100199001011234&dataList[0].realName=张三&hotelId=10001&keyId=KP001&memo=无烟房&mobile=13800000000&nonce=8b31d4e7&roomGroups[0].adultNum=1&roomGroups[0].childNum=0&roomTypeId=RM001&timestamp=1725000000000
    完整签名串(含 secret):上述串末尾直接拼接 9f8e7d6c5b4a3e2f1d0c9b8a7f6e5d4c
    sign = 17a4f6cc7b97bf35c4094fbde593bb0c

    五、统一返回结构#

    所有接口成功/失败均返回统一结构(HTTP 200,data 为业务数据,失败时为 null):
    {
      "code": 0,
      "message": "成功",
      "data": { }
    }

    5.1 错误码表#

    code含义message 示例
    0成功成功
    101签名不匹配签名不匹配
    300参数格式错误参数错误 详细:xxx
    301参数校验失败参数(cityCode)错误:城市编码不能为空
    501运行时内部异常内部异常 详细:xxx
    502Thrift 服务异常未处理异常 详细:xxx
    503业务异常接口异常 详细:xxx(如"退款失败,如有特殊需求请联系客服处理")
    渠道相关错误:未查询到渠道 / 该渠道已被禁用 / 渠道接口已过期(均为 301 参数错误格式)。

    六、注意事项#

    1.
    通用参数必须在 query:appId / timestamp / nonce / sign 放在 URL query,签名服务端从 query 读取;body 中即使重复也以 query 覆盖。
    2.
    sign 不参与签名:计算签名前必须从参数集合中剔除 sign。
    3.
    空值不参与拼接:值为 null 或空字符串的字段会被跳过。建议请求中不放空字段,保持签名串稳定。
    4.
    数组/嵌套必须按扁平化规则展开:dataList[0].realName 这类键是签名串的一部分,不要漏。
    5.
    中文字符:body 中的中文(如入住人姓名)按 UTF-8 参与 MD5,请求 charset 必须为 UTF-8。
    6.
    timestamp/nonce:当前服务端仅参与签名,不校验有效期与重复;但建议每次请求重新生成,便于未来防重放升级。
    7.
    secret 保护:secret 是签名唯一密钥,请保存在服务端,严禁下发客户端或写入前端代码。
    8.
    URL 编码:query 参数值含特殊字符时需先 URL 编码(服务端解码后参与签名,解码后的原始值参与拼接)。
    修改于 2026-09-07 09:28:26
    上一页
    接口对接流程图
    下一页
    订单回调通知对接文档
    Built with