Skip to content

Instantly share code, notes, and snippets.

@vinurs
Created September 12, 2024 08:57
Show Gist options
  • Save vinurs/c96e2bdc9aad8c75c264d6bbf7716599 to your computer and use it in GitHub Desktop.
Save vinurs/c96e2bdc9aad8c75c264d6bbf7716599 to your computer and use it in GitHub Desktop.
clj ts mode 卡住
(ns server.app.web.routes.hotel-sync.elong
(:refer-clojure :exclude [])
(:require
[clj-commons.digest :as digest]
[clj-http.client :as http-client]
[clojure.data.xml :as xml]
[clojure.data.zip.xml :as zx]
[clojure.java.io :as io]
[clojure.string :as str]
[clojure.tools.logging :as log]
[clojure.zip :as zip]
[jsonista.core :as json]
[java-time.api :as java-time]
[ring.util.http-response :as http-resp]
[server.app.app-data :refer [dev-env? query-fn snip-fn]]
[server.app.web.controllers.health :as health]
[server.app.web.middleware.auth :as auth-middleware]
[server.app.web.routes.custom-schemas :as custom-schemas]
[server.app.web.routes.errcode :refer [app-errcodes default-resp err-info]]
[server.app.web.routes.hotel-sync.dossen :refer [check-hotel-price-and-cnt]]
[server.app.web.routes.errcode :refer [default-resp]])
(:import
[javax.xml.parsers DocumentBuilderFactory]))
;;
(comment
(log/info "hello")
)
(defn xml->clj
"将 XML 解析为 Clojure 的数据结构,保留层次结构、属性和重复标签。"
[xml-str]
(letfn [(parse-node [node]
(let [tag (keyword (:tag node))
attrs (:attrs node)
children (filter map? (:content node))
text (first (filter string? (:content node)))
child-content (if (seq children)
(if (apply = (map :tag children))
(mapv parse-node children)
(reduce (fn [m child]
(let [child-tag (keyword (:tag child))
parsed-child (parse-node child)]
(update m child-tag (fnil conj []) parsed-child)))
{} children))
nil)]
(cond-> {}
(seq attrs) (assoc :attrs attrs)
(seq child-content) (merge child-content)
text (assoc tag (str/trim text))
(and (empty? child-content) (empty? text)) (assoc tag nil))))]
(-> xml-str
xml/parse-str
parse-node)))
(defn parse-xml-to-map
"将 XML 字符串解析为 Clojure map,正确处理数组情况"
[xml-string]
(letfn [(node->map [node]
(let [tag (keyword (:tag node))
content (:content node)
attrs (:attrs node)
child-elements (filter map? content)
text-content (first (filter string? content))]
(cond-> {}
(seq attrs) (merge attrs)
(seq child-elements) (assoc tag
(if (every? #(= (:tag %) (:tag (first child-elements))) child-elements)
(mapv node->map child-elements)
(into {} (map (fn [child] [(keyword (:tag child)) (node->map child)]) child-elements))))
text-content (assoc tag text-content))))]
(-> xml-string
xml/parse-str
node->map)))
(comment
;; 使用示例
(let [xml-string "<root> <RoomTypes><RoomType RoomTypeCode=\"48678253\" />
<RoomType RoomTypeCode=\"48678255\" />
</RoomTypes><child1 attr1='value1'>text1</child1><child2><grandchild>text2</grandchild></child2></root>"
result (parse-xml-to-map xml-string)]
(println result))
;; 输出: {:root {:child1 {:attr1 "value1", :child1 "text1"}, :child2 {:grandchild "text2"}}}
)
;; 解析 XML 根标签的通用函数
(defn get-root-tag
"通过只解析 XML 的根标签来判断请求类型。"
[xml-string]
(let [factory (DocumentBuilderFactory/newInstance)
builder (.newDocumentBuilder factory)
input-stream (java.io.ByteArrayInputStream. (.getBytes xml-string "UTF-8"))
document (.parse builder input-stream)
root-element (.getDocumentElement document) ; 获取根元素
root-tag (keyword (.getTagName root-element)) ; 获取根标签名
req-name (cond
(= root-tag :OTA_HotelResRQ) "创建订单"
(= root-tag :OTA_CancelRQ) "取消订单"
(= root-tag :OTA_ModifyResRQ) "变更订单"
(= root-tag :OTA_InventoryCheckRQ) "可订检查"
(= root-tag :OTA_GetRerStatusRQ) "轮询获取订单状态"
:else "未知请求类型")]
(log/info "请求类型:" root-tag req-name)
root-tag))
;; 调试辅助函数:用于打印调试信息
(defn- debug-extract-attr
[zip path attr-name]
(let [value (apply zx/xml1-> zip (concat path [(zx/attr attr-name)]))]
(log/info "Extracting" attr-name "from" path ":" value)
value))
(defn- debug-extract
"从给定的路径中提取文本内容并记录日志"
[zip path]
(let [value (apply zx/xml1-> zip (concat path [zx/text]))]
(log/info "Extracting text from" path ":" value)
value))
;; 构建 OTA_HotelResRS 响应 XML
(defn build-hotel-res-response
[]
(let [response (xml/element :OTA_HotelResRS
{:TimeStamp "2013-02-28 16:07:22"
:Version "1.0"
:EchoToken "e9246d3e-2807-449c-bbc5-affedee739c2"}
;; POS 元素
(xml/element :POS
{}
(xml/element :Source
{}
(xml/element :RequestorID
{:Type "13"
:ID "elong"})))
;; Success 元素
(xml/element :Success {} "success")
;; UniqueID 节点1
(xml/element :UniqueID
{:Type "14"
:ID "43143215"}
(xml/element :CompanyName {} "elong"))
;; UniqueID 节点2
(xml/element :UniqueID
{:Type "10"
:ID "s1302280038"}
(xml/element :CompanyName {} "Jltour")))]
;; 输出为 XML 字符串
(xml/emit-str response)))
(comment
comment
(log/info (build-hotel-res-response)))
;; 独立的根标签信息解析函数
(defn parse-root-tag-info
"解析根标签信息,返回包含根标签名及其属性的 map"
[zip-xml]
(let [root-tag (:tag (zip/node zip-xml))]
{;; 请求类型
:root-tag root-tag
;; 时间戳
:timestamp (debug-extract-attr zip-xml [root-tag] :TimeStamp)
;; 接口版本,默认 2.000
:version (debug-extract-attr zip-xml [root-tag] :Version)
;; 请求验证码,要和response返回值相同
:echo-token (debug-extract-attr zip-xml [root-tag] :EchoToken)
;; 用户名
:username (debug-extract-attr zip-xml [root-tag] :UserName)
;; 密码
:password (debug-extract-attr zip-xml [root-tag] :Password)
;; 语言编码,默认en-us
:primary-lang (debug-extract-attr zip-xml [root-tag] :PrimaryLangID)
;; 提取通用的 RequestorID 信息
:requestor-type (debug-extract-attr zip-xml [:POS :Source :RequestorID] :Type)
:requestor-id (debug-extract-attr zip-xml [:POS :Source :RequestorID] :ID)}))
(defn parse-inventory-check-rq
"可订检查请求——用于下单前检验价格和库存,确认产品是否可订"
[xml-string]
(let [parsed-xml (xml/parse-str xml-string)
zip-xml (zip/xml-zip parsed-xml)]
;; 打印 XML 结构,确认正确解析
(log/info "Parsed XML structure:" parsed-xml)
(let [root-tag-info (parse-root-tag-info zip-xml)]
;; 提取关键的字段信息
{:root-tag-info root-tag-info
;; 供应商房型 ID
:room-types (mapv #(hash-map :room-type-code (zx/attr % :RoomTypeCode))
(zx/xml-> zip-xml
:HotelReservations :HotelReservation
:RoomStay :RoomTypes :RoomType))
;; 供应商 RP ID
:rate-plans (mapv #(hash-map :rate-plan-code (zx/attr % :RatePlanCode))
(zx/xml-> zip-xml
:HotelReservations :HotelReservation
:RoomStay :RatePlans :RatePlan))
;; 住客人数
:guest-counts (mapv #(hash-map :age-qualifying-code (zx/attr % :AgeQualifyingCode)
:count (zx/attr % :Count))
(zx/xml-> zip-xml
:HotelReservations
:HotelReservation :RoomStay
:GuestCounts :GuestCount))
;; 供应商酒店 ID
:hotel-code (zx/xml1-> zip-xml
:HotelReservations
:HotelReservation :RoomStay
:BasicPropertyInfo (zx/attr :HotelCode))
;; 房间数量
:room-count (zx/xml1-> zip-xml
:HotelReservations
:HotelReservation :ResGlobalInfo
:RoomCount zx/text)
;; elong 会员等级
:member-level (zx/xml1-> zip-xml
:HotelReservations
:HotelReservation :ResGlobalInfo
:MemberLevel zx/text)
;; 到店日期,2007-06-29
:start-date (zx/xml1-> zip-xml
:HotelReservations
:HotelReservation :ResGlobalInfo
:TimeSpan (zx/attr :Start))
;; 离店日期,2007-06-30
:end-date (zx/xml1-> zip-xml
:HotelReservations
:HotelReservation :ResGlobalInfo
:TimeSpan (zx/attr :End))})))
(defn parse-hotel-res-rq
"下单"
[xml-string]
(let [parsed-xml (xml/parse-str xml-string)
zip-xml (zip/xml-zip parsed-xml)]
(log/info "解析的XML结构:" parsed-xml)
(letfn [(extract [path] (debug-extract zip-xml path))
(extract-attr [path attr] (debug-extract-attr zip-xml path attr))]
(let [root-tag-info (parse-root-tag-info zip-xml)]
{:root-tag-info root-tag-info
:unique-id (zx/xml1-> zip-xml
:HotelReservations :HotelReservation
:UniqueID (zx/attr :ID))
:room-type-code (zx/xml1-> zip-xml
:HotelReservations :HotelReservation :RoomStays :RoomStay
:RoomTypes :RoomType (zx/attr :RoomTypeCode))
:rate-plan-code (zx/xml1-> zip-xml
:HotelReservations :HotelReservation :RoomStays :RoomStay
:RatePlans :RatePlan (zx/attr :RatePlanCode))
:base-amount (zx/xml1-> zip-xml
:HotelReservations :HotelReservation :RoomStays :RoomStay
:RoomRates :RoomRate :Rates :Rate :Base (zx/attr :AmountAfterTax))
:total-amount (zx/xml1-> zip-xml
:HotelReservations :HotelReservation :RoomStays :RoomStay
:RoomRates :RoomRate :Rates :Rate :Total (zx/attr :AmountAfterTax))
:guest-count (zx/xml1-> zip-xml
:HotelReservations :HotelReservation :RoomStays :RoomStay
:GuestCounts :GuestCount (zx/attr :Count))
:hotel-code (zx/xml1-> zip-xml
:HotelReservations :HotelReservation :RoomStays :RoomStay
:BasicPropertyInfo (zx/attr :HotelCode))
:guests (mapv #(hash-map :given-name (zx/xml1-> % :GivenName zx/text)
:middle-name (zx/xml1-> % :MiddleName zx/text)
:surname (zx/xml1-> % :Surname zx/text))
(zx/xml-> zip-xml
:HotelReservations :HotelReservation :ResGuests :ResGuest
:Profiles :ProfileInfo :Profile :Customer :PersonName :RoomGuest))
:room-num (zx/xml1-> zip-xml
:HotelReservations :HotelReservation :ResGlobalInfo
:RoomNum zx/text)
:start-date (zx/xml1-> zip-xml
:HotelReservations :HotelReservation :ResGlobalInfo
:TimeSpan (zx/attr :Start))
:end-date (zx/xml1-> zip-xml
:HotelReservations :HotelReservation :ResGlobalInfo
:TimeSpan (zx/attr :End))
:earliest-check-in-time (zx/xml1-> zip-xml
:HotelReservations :HotelReservation :ResGlobalInfo
:EarliestCheckInTime zx/text)
:latest-check-in-time (zx/xml1-> zip-xml
:HotelReservations :HotelReservation :ResGlobalInfo
:LatestCheckInTime zx/text)
:remark (zx/xml1-> zip-xml
:HotelReservations :HotelReservation :ResGlobalInfo
:Remark zx/text)
:guarantee-type (zx/xml1-> zip-xml
:HotelReservations :HotelReservation :ResGlobalInfo
:Guarantee (zx/attr :GuaranteeType))
:can-be-canceled (zx/xml1-> zip-xml
:RatePlanPolicy :CanBeCanceled zx/text)
:cancel-before-days (zx/xml1-> zip-xml
:RatePlanPolicy :CancelBeforeDays zx/text)
:cancel-before-time (zx/xml1-> zip-xml
:RatePlanPolicy :CancelBeforeTime zx/text)
:cash-scale-type (zx/xml1-> zip-xml
:RatePlanPolicy :CashScaleType zx/text)
:elong-inventory-type (zx/xml1-> zip-xml
:ElongInventoryType zx/text)}))))
(defn build-hotel-reservation-success-response
"构建酒店预订成功的响应 XML"
[{:keys [timestamp version echo-token elong-order-id supplier-order-id]}]
(xml/element
:OTA_HotelResRS
{:TimeStamp timestamp
:Version version
:EchoToken echo-token}
(xml/element
:POS
{}
(xml/element
:Source
{}
(xml/element :RequestorID {:Type "13" :ID "elong"})))
(xml/element :Success {} "success")
(xml/element
:UniqueID
{:Type "14" :ID elong-order-id}
(xml/element :CompanyName {} "elong"))
(xml/element
:UniqueID
{:Type "10" :ID supplier-order-id}
(xml/element :CompanyName {} "Jltour"))))
(comment
;; 使用示例
(let [response-xml (build-hotel-reservation-success-response
{:timestamp "2023-05-15 10:30:00"
:version "1.0"
:echo-token "e9246d3e-2807-449c-bbc5-affedee739c2"
:elong-order-id "43143215"
:supplier-order-id "s2305150001"})]
(println (xml/emit-str response-xml)))
)
(defn parse-cancel-order-request
"解析取消订单请求的 XML"
[xml-string]
(let [parsed-xml (xml/parse-str xml-string)
cancel-rq (zx/xml1-> parsed-xml :OTA_CancelRQ)]
{:cancel-type (zx/attr cancel-rq :CancelType)
:echo-token (zx/attr cancel-rq :EchoToken)
:username (zx/attr cancel-rq :UserName)
:password (zx/attr cancel-rq :Password)
:primary-lang-id (zx/attr cancel-rq :PrimaryLangID)
:timestamp (zx/attr cancel-rq :TimeStamp)
:version (zx/attr cancel-rq :Version)
:requestor-id (zx/xml1-> cancel-rq :POS :Source :RequestorID (zx/attr :ID))
:cancel-out-of-rule (when-let [cor (zx/xml1-> cancel-rq :CancelOutOfRule)]
{:rule (zx/attr cor :Rule)
:pay-hotel-amount (zx/xml1-> cor :PayHotelAmount zx/text)
:client-unique-id (zx/xml1-> cor :clientUniqueId zx/text)
:deduct-hotel-amount (zx/xml1-> cor :deductHotelAmount zx/text)
:notes (zx/xml1-> cor :Notes zx/text)})
:elong-order-id (zx/xml1-> cancel-rq :UniqueID [(zx/attr= :Type "14")] (zx/attr :ID))
:supplier-order-id (zx/xml1-> cancel-rq :UniqueID [(zx/attr= :Type "10")] (zx/attr :ID))
:reasons (zx/xml1-> cancel-rq :Reasons :Reason zx/text)}))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment