Created
September 12, 2024 08:57
-
-
Save vinurs/c96e2bdc9aad8c75c264d6bbf7716599 to your computer and use it in GitHub Desktop.
clj ts mode 卡住
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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