爬虫&逆向--Day25&Day26--京东h5st案例解析
案例地址链接:https://search.jd.com/Search?keyword=%E7%99%BD%E9%85%92&enc=utf-8&pvid=53c39759079d4b92a3e6d3ea71974571
案例爬取链接:https://api.m.jd.com/api?appid=search-pc-java&t=1759021222491&client=pc&clientVersion=1.0.0&cthr=1&uuid=1759021192300961326017&loginType=3&keyword=%E7%99%BD%E9%85%92&functionId=pc_search_searchWare&body={%22enc%22:%22utf-8%22,%22pvid%22:%2253c39759079d4b92a3e6d3ea71974571%22,%22area%22:%221_72_55652_0%22,%22page%22:1,%22s%22:1}&x-api-eid-token=jdd03PNQPCXFNEW775N652WCJ7SFKNDCIVT3QCO7HIPIDZZQHITB6EHU2T7RJP4RAAXX474SZMRFKEOLKRYZDT5V4O4UONEAAAAMZRXKU7EIAAAAAC7FTSK4VTJR3OYX&h5st=20250928090024497;zm6g9g393pqjh336;f06cc;tk05w2f6134c941lMysxWXRhVG8yfZBUuVeH3caFu9uV68uIrt7DhhRhIrJsXabO_UOUYk9VCMaIW4sV8g_VqNeV8AeI;5079a9c20f5d8b943e2e27c6305df7d9;5.2;1759021222497;gt6f-JuVuB7HwgqVoVuIoF7U046ZB5_ZxI7ZBh-f1BeZnZ-G_U7ZBh-f1ZfIpR7V98OVuV7U8ULIpNLUAQOIrR_Utd7Vvd7V8c7IqZfZnZfFbwrI-MrE-hfZXx-Z9UuJwJuJ8M_J7ULIoRLTxdrUvJ_VvdbT_UuJwd_VqN_ZB5_Zuc7EzcrJ-hfZXx-ZxZfZnZfUsY7ZBh-f1ZfVzZ_WsJqK8wLH7kMU5YfZnZ-E-hfZXx-Z8YuHv98UwheV-h-T-trG9oLJvYfZB5hW-ZuVz8rM-h-T-JbF-hfZXxPCBh-f-J7Q-h-T-VOVsY7ZBhfZB5hWvh-T-dOVsY7ZBhfZB5hWtdeZnZfVwN6J-hfZBh-f1BOWB5_ZvdOE-YfZBhfZXx-ZNIqGLcbVuYOPPQaGuYfZnZPGyQ7GAY6ZBhfZB5hWxh-T-BOE-YfZBhfZXxfVB5_ZqN6J-hfZBh-f1d_VB5_ZrN6J-hfZBh-f1heZnZPUsY7ZBhfZB5hWxh-T-ROE-YfZBhfZXxvUth-T-VOE-YfZBhfZXx-ZrpPVzh_ZB5_ZwN6J-hfZBh-f1heZnZvHqYfZBhfZXxPUB5_Zuw7ZBhfZB5hWxh-T-x7ZBhfZB5hWxh-T-RrE-hfZBh-fmg-T-R7G8QaD8YfZB5hWkgfZXZPItR_JrJuJAMOV-YOUCQ7H-h-T-ZeF-hfZBh-fmg-T-haF-hfZXx-ZtJeDB1eUrpLHKgvTxpfVwhfMTgvFqkbIz8rM-h-T-dLEuYfZB5xD;81cf5c0c0b8d93f9a2363ab39520e008;gRaW989Gy8bE_oLE7w-Gy8rFvM7MtoLI4wrJ1R6G88bG_wPD9k7J1RLHxgKJ&t=1759021222505
一、案例【京东载荷h5st字段】
1.1、入口定位

这次的目标很明确,我们就是需要破解京东的搜索接口的h5st字段,首先我们先通过关键字进行搜索,搜索h5st


首先我们先找到我们的目标url复制到【https://curlconverter.com/】中生成基础爬虫代码

很明显,人家是做了加密处理,根本不给你返回任何的数据,所以我们只有破解h5st才可以获取到数据
1.2、代码分析

点击进入到代码内部

进来以后这里就两行代码,其中第二行【return _$tP.resolve(_$Ga);】就是包装,把这个带有h5st的值直接包装到promise中去,所以关键点还是第一行代码【var _$Ga = this._$sdnmd(_$Gf)】中的this._$sdnmd(_$Gf)就生成了h5st了
【这一步直接出结果就和promise没关系了,下一步就是包装promise】

window.PSign.sign(d).then(res=>{console.log(res)})
这行代码会生成一个promise,所以我们执行上面的这行代码就会生成promise的结果
1.3、扣JS

因为主要是this._$sdnmd(_$Gf); 这句代码生成的h5st所以把这句代码直接扣走
参数_$Gf是直接传递来的,所以我们拼接_$Gf,进行测试,缺什么补什么

由此可得d就是参数_$Gf,所以进行拼接即可

在扣完代码拼接完paramsH5sign和params以后,我们就需要确定调用this._$sdnmd中的this 其实就是window.ParamsSign = _$Gk,
所以我们可以进行替换,
参数加密处理的逻辑就是,先对参数params进行SHA256处理,然后把处理完的结果放到paramsH5sign.body中,然后在对paramsH5sign参数进行处理生成h5st
所以我们替换完this以后,我们需要找到window.ParamsSign

通过判断是new出来的,所以我们只需要扣try中的正常处理逻辑即可

当把所有的代码都扣出来完成以后,允许就会报window找不到,就补window
这次又报document找不到,我们可以在补document,补完了又会报Element
这样其实我们第一阶段就已经完成了,下面我们就需要开始链式补环境

当我们把window和document还有Element补完以后,结果就会出来了,就有h5st这个结果了,但是这个结果是错误的,在python中运行是得不到结果的,因为没过服务器的检测点,并且这个和h5st的长度也没关系,因为这个代码中他们做了原型链的检测,所以和长度什么的没关系,
所以下面我们还是需要分析原型链关系,根据原型链关系进行补环境

看这个div标签的属性和方法,在属性和方法中找它的原型链关系

通过查看可以得到Element继承Node ,Node继承EventTarget, EventTarget 继承Object

如果提示报错,我们不知道如何处理的时候,我们就复制报错的地方,在源码中找到,断点定位到该处,进行判断是什么报错找不到,处理使用代码以外还需要会断点调试什么的,进行查错,
1.4、补环境【 补环境框架,V8引擎】
有的时候在源码中,他们会判断,Element是否有,并且有的时候也会判断Element的原型链它的父亲是否有,Element它的爷爷是否有,所以我们需要通过原型链去补环境,而不能直接简单的补一下
// 补环境 window = {} window.document = {} function Element(){ }
有些网站它不检测原型链,我们按照上面的方式补环境是可以的,但是当有些网站检测原型链的时候,我们在按照上面的基础补环境就不行了



以上是一段准备代码,方便我们通过链式补环境,
// 二、补环境 // (1) window对象处理 global_val = globalThis // globalThis 其实就是global 就是node中的顶级变量 // 给global加一个window属性哈 给global全局配一个window属性 给global顶级变量挂载一个全局变量window Object.defineProperty(global_val, "window", { get: function () { return global_val }, set: function set_window(val) { global_val = val }, enumerable: true, configurable: true, }) // 无论代码调取那个都是指向这个window 做一个全局处理 window.top = window.self = window.window = window
以上是对window缺少找不到进行的补环境处理

小window --》 大Window --》 WindowProperties --》 EventTarget --》 Object
小window其实是来自于大Window的一个实例化对象,小window的原型对象其实就是大Window
所以我们如果都补全了最好就是四层,目前我们该案例就写了两层
// 四层都不齐全 createConstructor("EventTarget", true, [], {}) createConstructor("WindowProperties", true, [], {},"EventTarget" ) createConstructor("Window", true, [], {},"WindowProperties" ) // 只补两层 createConstructor("Window", true, [], {})

window.document 报document找不到,因为document中的东西太多,所以我们先简单的补一下
window.document = {} 先跳过

我们先window.document = {} 跳过document ,先处理Element
Element 首字母大写的,我们都可以当作是类来处理,查看它的原型链
直接dir(Element),查看一下它爹,它爷这些原型链即可

/* * Element --》 Node --》 EventTarget --》 Object * 因为Element的父类是Node,Node的父类是EventTarget,EventTarget的父类Object,而且在createConstructor方法中,每次在最后面也只能添加一层的父类 * 所以我们给Element设置完父类Node以后,还需要给Node以同样的方式设置父类EventTarget 连续三个createConstructor,这才完成了一个原型串 * */ createConstructor("EventTarget", true, [], {}) // EventTarget 后面就没爹了,没爹不用写,默认是Object createConstructor("Node", true, ["parentNode", "childNodes"], {}, "EventTarget") createConstructor("Element", true, ["childElementCount", "innerHTML"], {}, "Node")

到这里,已经没有任何缺少环境的信息提示了,报一个看不懂对象的错误,所以这里我们就需要去添加监控了

添加完监控以后,就会吐很多关于window相关的信息,但是这些window相关的信息都没什么用
所以我们还需要监听另外的一个,需要监控document。

实例化出错了
因为我们刚开始肯定是不知道,那个需要补,那个不需要补,但是现在假装我们已经知道了需要补那个环境,所以我们就挑重点的来补

首先我们看到document调用了all
所以我们还是先dir(document.all)


// 补document的爹和爷 document --》 HTMLDocument --》 Document --》 node createConstructor("Document", true, [], {}, "Node") createConstructor("HTMLDocument", true, [], {}, "Document")
// 这里是document实例化的时候传入的一些属性 // all:new HTMLAllCollection(null,null,"ljc") 其实这样就实例化一个对象就可以了,但是还有一种可能是document.all.xxx 所以我们也需要在实例化的同时也监控一下 // 所以我们就watch(new HTMLAllCollection(null,null,"ljc"),谁调用的呀)document.all.xxx,也就被监控了 all:watch(new HTMLAllCollection(null,null,"ljc"),"document.all")
下一个就是dir(document.documentElement)


// 补document.documentElement HTMLHtmlElement --》 HTMLElement --》 Element --》 Node --》 EventTarget --》 Object createConstructor("HTMLElement", true, [], {},"Element") createConstructor("HTMLHtmlElement", true, [], {},"HTMLElement")
//按照上面的方式补全documentElement documentElement:watch(new HTMLHtmlElement(null,null,"ljc"),"document.documentElement")
下面在补一个cookie属性

下一个dir(document.body)


// 补document.body HTMLBodyElement --> HTMLElement --> Element --> Node --> EventTarget --> Object createConstructor("HTMLBodyElement", true, [], {}, "HTMLElement")
//补body body: watch(new HTMLBodyElement(null, null, "ljc"), "document.body")
到目前为止,document的属性就都补完了,现在开始补方法了

经过dir查看他们的链接关系,发现这些方法都在document这个类中,所以这些方法在那个类中,我们就应该补到那个类中
其中第一个优先补script
我们需要先查看他的链式关系


//补script createConstructor("HTMLScriptElement", true, [], {}, "HTMLElement") //补parentNode createConstructor("HTMLHeadElement", true, [], {}, "HTMLElement")
createElement: function (tagName) { // 打印一下tagName 看一下创建的是那个标签 console.log("Document prototype createElement ", tagName) if (tagName === "script") { // 因为script后面也有可能调用了别的,所以我们在这里也需要进行监控 script = watch(new HTMLScriptElement({ parentNode: watch(new HTMLHeadElement(null, null, "ljc"), "script的parentNode") }, null, "ljc"), "script对象") return script } // canvas和上面的script就一样了 if (tagName === "canvas") { return {} } },

到这里,这个环境中已经有了script标签,虽然script调用这个parentNode找不到,但是至少不在报错了
所以我们这里就需要找这个parentNode是什么东西

因为这个ele是咱们自己创建的,没有放到任何的一个父级标签中,所以ele.parent是null


因为这个srcipt在调用parent之前调用了什么,比如text/javascript,所以这个script标签都在head中,但是body中可能也有,所以我们就需要多在源码中搜索一下确定一下
所以我们在补这个parent的时候,这个script.parentNode就是那个head标签,因为script标签在head中,所以我们就需要确定这个head标签中的第0元素,然后确定它的创建类,来创建这个parentNode


再往下就是补canvas 但是这个canvas可补,可不补,所以不需要关注
然后这个时候在运行,需要提前登录在运行,就会得到h5st 长度是556位,目前这个值是可以成功的



把paramsH5sign作为参数传递进去,然后通过开通的接口就会生成h5st,所以以后在想拿h5st就不需要在和Python打交道了直接做成一个接口,之前用Python拿数据都是使用import execjs或者import subprocess 之前都是用Python调度js代码,如果有了定时器什么的就比较麻烦,所以现在就直接把JS生成接口,如果Python要用,就直接自己传参自己拿结果,访问一次获取一个h5st,每次获取的h5st还都不一样,因为是时间作为了参数获取的h5st
1.5、代码文件
该案例需要导入:pip install curl_cffi npm install express
1.5.1、Python文件
import hashlib from curl_cffi import requests import time import os def sha256_hash(data: str) -> str: """ 计算字符串的 SHA-256 哈希值 :param data: 输入字符串 :return: 十六进制表示的哈希值字符串 """ hash_obj = hashlib.sha256(data.encode('utf-8')) return hash_obj.hexdigest() cookies = { '__jdv': '143920055|direct|-|none|-|1759198339989', '__jdu': '1759198339988240393195', '3AB9D23F7A4B3CSS': 'jdd0336CXCJW76HVLG5NN3HX7RRXSOAKV7PM4O2R2CEWREEYEERJ3MGNRRZZL6AKZQDEG6JSLQTBE5CCGNYDHCKPS4VKR4EAAAAMZTBSF5TAAAAAADED2AUUGHUOS44X', 'mba_muid': '1759198339988240393195', 'wlfstk_smdl': 'di09prnlss3k3bnvofmpwmkjo812881l', 'TrackID': '1RVWFVwuwegh6iaq2dUaSUyxtlv2W8ZsWmxP21tBeuzA6JLP01W25RW4ZNKGWhDFKBR_xMLfaTohtTorBRBpdNlxBRXHQ87UD-YA5CjBNN54', 'thor': '2DD9CF08CB217E996764A091B245EB29B43DEECE308DCF74BCBC66126340B7CC57EDD320176A72EA74043C4CDD7F4324D872C1E3813F23CFC94796AE1ABFB72BC0EB76990CBC9F42DD82C52859CD62E4DF2374343ED9606AFB4437FA8D8FACB1753AD87ACE74EDE1B970E782C73FFEFB25EBC28E00E7D303C43E5B5FDC1C3826210EFE4E1F9CC0F684515F5703EB90D7302414E086525715F186A95323BEAE81', 'light_key': 'AASBKE7rOxgWQziEhC_QY6yaKYl0-nkwHhCq7N-FMTwrP69jFjUruev7Rsm2eNMxzUBY6VIP', 'pinId': 'w8Ih7LOJnxwxhZalSpUGsbV9-x-f3wj7', 'pin': 'jd_52b15ac66b44e', 'unick': '%E5%91%86%E5%91%867115', 'ceshi3.com': '203', '_tp': 'oRxO98xexSXw5ZlTXC2fbDrx5URIy94AUL5RT79KEbs%3D', '_pst': 'jd_52b15ac66b44e', '__jdc': '143920055', 'areaId': '5', 'shshshfpa': 'b9a6c825-8808-1785-a164-cdcc6d85da4a-1759198380', 'shshshfpx': 'b9a6c825-8808-1785-a164-cdcc6d85da4a-1759198380', 'ipLoc-djd': '1-72-55653-0', 'shshshfpb': 'BApXSoJBtm_xAUGY1ek_5MROtpLvWQ2uQBhtYhK9o9xJ1MhEHxo62', '3AB9D23F7A4B3C9B': '36CXCJW76HVLG5NN3HX7RRXSOAKV7PM4O2R2CEWREEYEERJ3MGNRRZZL6AKZQDEG6JSLQTBE5CCGNYDHCKPS4VKR4E', 'flash': '3_6gpgATZhoG6k8czgYn2HnA7xPO3mh2cXAmEzCQ0z422kecKRpx89Biu_-2dtCY3V_jARZ4wIxT3UKzcCONXY1gr97YLyPbCHYjhWNhvMGQGBq_IB6Udnm-kw105qUMUkmMKnu5lY-1pCuNNxyfD0jxFU2_-xm1QimzdddFQTJfmsemqUvJBa3V**', '__jda': '143920055.1759198339988240393195.1759198340.1759198340.1759200581.2', '__jdb': '143920055.1.1759198339988240393195|2.1759200581', 'sdtoken': 'AAbEsBpEIOVjqTAKCQtvQu179H4TgU1sQiwY_jgIlLkfzub2EneuGP2sLpz488hVNgAiBKppDnR-9KGdRFMb2fGttHfYI6x6ZfMbttuK65GA90pBEalHNm_-LbXYXth3my7jHZ5D0UqmCIA', } headers = { 'accept': 'application/json, text/plain, */*', 'accept-language': 'zh-CN,zh;q=0.9', 'origin': 'https://search.jd.com', 'priority': 'u=1, i', 'referer': 'https://search.jd.com/', 'sec-ch-ua': '"Chromium";v="140", "Not=A?Brand";v="24", "Google Chrome";v="140"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': '"Windows"', 'sec-fetch-dest': 'empty', 'sec-fetch-mode': 'cors', 'sec-fetch-site': 'same-site', 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36', 'x-referer-page': 'https://search.jd.com/Search', 'x-rp-client': 'h5_1.0.0', # 'cookie': '__jdv=143920055|direct|-|none|-|1759198339989; __jdu=1759198339988240393195; 3AB9D23F7A4B3CSS=jdd0336CXCJW76HVLG5NN3HX7RRXSOAKV7PM4O2R2CEWREEYEERJ3MGNRRZZL6AKZQDEG6JSLQTBE5CCGNYDHCKPS4VKR4EAAAAMZTBSF5TAAAAAADED2AUUGHUOS44X; mba_muid=1759198339988240393195; wlfstk_smdl=di09prnlss3k3bnvofmpwmkjo812881l; TrackID=1RVWFVwuwegh6iaq2dUaSUyxtlv2W8ZsWmxP21tBeuzA6JLP01W25RW4ZNKGWhDFKBR_xMLfaTohtTorBRBpdNlxBRXHQ87UD-YA5CjBNN54; thor=2DD9CF08CB217E996764A091B245EB29B43DEECE308DCF74BCBC66126340B7CC57EDD320176A72EA74043C4CDD7F4324D872C1E3813F23CFC94796AE1ABFB72BC0EB76990CBC9F42DD82C52859CD62E4DF2374343ED9606AFB4437FA8D8FACB1753AD87ACE74EDE1B970E782C73FFEFB25EBC28E00E7D303C43E5B5FDC1C3826210EFE4E1F9CC0F684515F5703EB90D7302414E086525715F186A95323BEAE81; light_key=AASBKE7rOxgWQziEhC_QY6yaKYl0-nkwHhCq7N-FMTwrP69jFjUruev7Rsm2eNMxzUBY6VIP; pinId=w8Ih7LOJnxwxhZalSpUGsbV9-x-f3wj7; pin=jd_52b15ac66b44e; unick=%E5%91%86%E5%91%867115; ceshi3.com=203; _tp=oRxO98xexSXw5ZlTXC2fbDrx5URIy94AUL5RT79KEbs%3D; _pst=jd_52b15ac66b44e; __jdc=143920055; areaId=5; shshshfpa=b9a6c825-8808-1785-a164-cdcc6d85da4a-1759198380; shshshfpx=b9a6c825-8808-1785-a164-cdcc6d85da4a-1759198380; ipLoc-djd=1-72-55653-0; shshshfpb=BApXSoJBtm_xAUGY1ek_5MROtpLvWQ2uQBhtYhK9o9xJ1MhEHxo62; 3AB9D23F7A4B3C9B=36CXCJW76HVLG5NN3HX7RRXSOAKV7PM4O2R2CEWREEYEERJ3MGNRRZZL6AKZQDEG6JSLQTBE5CCGNYDHCKPS4VKR4E; flash=3_6gpgATZhoG6k8czgYn2HnA7xPO3mh2cXAmEzCQ0z422kecKRpx89Biu_-2dtCY3V_jARZ4wIxT3UKzcCONXY1gr97YLyPbCHYjhWNhvMGQGBq_IB6Udnm-kw105qUMUkmMKnu5lY-1pCuNNxyfD0jxFU2_-xm1QimzdddFQTJfmsemqUvJBa3V**; __jda=143920055.1759198339988240393195.1759198340.1759198340.1759200581.2; __jdb=143920055.1.1759198339988240393195|2.1759200581; sdtoken=AAbEsBpEIOVjqTAKCQtvQu179H4TgU1sQiwY_jgIlLkfzub2EneuGP2sLpz488hVNgAiBKppDnR-9KGdRFMb2fGttHfYI6x6ZfMbttuK65GA90pBEalHNm_-LbXYXth3my7jHZ5D0UqmCIA', } # 访问的接口 url = "https://api.m.jd.com/api" # body数据 就是一个json数据 有第几页 在这里修改page body = "\\{\"page\":1,\"area\":\"7_527_35108_56983\",\"pvid\":\"c7edfed410bb4172956eb62ce0ad72f4\"\\}" # 去我们通过express开放的接口去获取h5st response = requests.post("localhost:3000/api/data", json={ "params": {"appid": "search-pc-java", "functionId": "pc_search_adv_Search", "body": sha256_hash(body) # 把这个body做了一下sha256 } }) param = response.json()["result"] print("param:::",len(param['h5st'])) params = { "appid": "search-pc-java", "t": [ str(param["t"]), str(int(time.time() * 1000)) ], "client": "pc", "clientVersion": "1.0.0", "uuid": "1745841960334352365307", "keyword": "手机", # 在这里修改类型 "functionId": "pc_search_getShopAndWare", "body": body, "x-api-eid-token": "jdd03QJJ7DOUYP7T5O2IKSRFQANXZJYHALCU3ECRYXYVSULUXN7DODVWDAGUVYK2WLOTISQ3XQ7U7G5PP57CC2QFRLQFA5AAAAAMYYUR7TVIAAAAACORWVTOFTVSAUQX", "h5st": param["h5st"] } response = requests.get(url, headers=headers, cookies=cookies, params=params, impersonate='chrome101') print(response.text) print(response)
1.5.2、express文件
// 引入 express const express = require('express'); const app = express(); const fs = require('fs'); const { get_h5st } = require('./04 h5st'); ljc_log = console.log; // 支持 JSON 请求体解析 app.use(express.json()); // 一个 GET 接口 app.get('/api/hello', (req, res) => { res.json({ message: 'Hello from Node.js backend!' }); }); // 一个 POST 接口 app.post('/api/data', (req, res) => { // eval(fs.readFileSync('myserver\\jingdong\\5.2.0\\env.js', 'utf8')) ljc_log('接收到数据:', req.body["params"]); res.json({ status: 'success', result: get_h5st(req.body["params"]) }); }); // 监听端口 const PORT = 3000; app.listen(PORT, () => { console.log(`✅ Server is running at http://localhost:${PORT}`); }); /* * express 就是编程里面的web框架 就是对外开一个接口 * npm install express 需要安装一下这个 * */
1.5.3、JS文件

以上是:一、补环境2个工具 在任何的案例中都可以拿过去用

以上是:三、源码

cryptoJs = require("crypto-js")
// 一、补环境2个工具  在任何的案例中都可以拿过去用
// (1)监控函数
// ***************************第一个补环境工具**************************
// (2)函数构造器
/**
 * 创建具有特定属性和方法的构造函数
 * 该函数主要用于浏览器环境模拟,可以创建具有严格访问控制、继承支持和原型方法的构造函数
 * 通过外部数据存储和Symbol键实现实例数据隔离,提高安全性和防检测能力
 *
 * @param {string} constructorName - 构造函数的名称,将作为全局变量挂载到window对象上
 * @param {boolean} enableStrictMode - 是否启用严格模式验证,启用后需要特定令牌才能调用构造函数
 * @param {Array} [propertiesList=[]] - 属性定义列表,支持简单字符串属性或自定义属性描述符
 *        - 简单属性:字符串形式,如 "name"
 *        - 自定义属性:数组形式,如 ["all", {get: function() {...}}]
 * @param {Object} [prototypeMethods={}] - 要添加到原型上的方法
 * @param {string} [parentConstructorName=null] - 父构造函数的名称,用于实现继承
 * @returns {Function} 返回新创建的构造函数,同时会挂载到window对象上
 *
 * @example
 * // 创建简单的Person构造函数
 * createConstructor("Person", true, ["name", "age"], {
 *   greet: function() { return `Hello, ${this.name}`; }
 * });
 *
 * function Person(){
 *     this.name = name
 *     this.age = age
 *     greed:function(){
 *       return `Hello, ${this.name}`;
 *     }
 * }
 *
 *
 * @example
 * // 创建继承自Animal的Dog构造函数
 * createConstructor("Dog", true, ["breed"], {
 *   bark: function() { return "Woof!"; }
 * }, "Animal");
 *
 * function Dog(){    这个Dog继承Animal
 *     this.name = name
 *     this.age = age
 *     bark: function() {
 *          return "Woof!";
 *     }
 * }
 */
function createConstructor(constructorName, enableStrictMode, propertiesList = [], prototypeMethods = {}, parentConstructorName = null) {
    // 使用对象存储所有实例的数据,实现数据隔离
    // 每个实例通过Symbol键关联到其数据,提高安全性
    const instancesData = {};
    // 创建构造函数
    const Constructor = function (element, propertySetter, validationToken) {
        // 验证构造函数调用合法性
        if (enableStrictMode && !(validationToken && validationToken === "ljc")) {
            throw new Error("Illegal constructor");
        }
        // 调用父构造函数(如果存在)
        if (parentConstructorName && window[parentConstructorName]) {
            window[parentConstructorName].call(this, element, null, "ljc");
        }
        // 设置对象属性
        if (propertySetter && typeof propertySetter === "function") {
            propertySetter(this);
        }
        // 初始化元素属性
        const instanceProperties = element && typeof element === "object" ? {...element} : {};
        // 创建唯一标识符
        this._element = Symbol('_element');
        instancesData[this._element] = instanceProperties;
    };
    // 设置继承关系
    if (parentConstructorName && window[parentConstructorName]) {
        Constructor.prototype = Object.create(window[parentConstructorName].prototype);
        Constructor.prototype.constructor = Constructor;
        Constructor.__proto__ = window[parentConstructorName];
    }
    // 设置toStringTag
    Object.defineProperty(Constructor.prototype, Symbol.toStringTag, {
        value: constructorName,
        writable: false,
        enumerable: false,
        configurable: true
    });
    // 添加原型方法
    Object.keys(prototypeMethods).forEach(methodName => {
        Constructor.prototype[methodName] = prototypeMethods[methodName];
    });
    // 将构造函数挂载到全局对象
    window[constructorName] = Constructor;
    return Constructor;
}
// 二、补环境
// (1) window对象处理
global_val = globalThis   // globalThis  其实就是global  就是node中的顶级变量
// 给global加一个window属性哈  给global全局配一个window属性   给global顶级变量挂载一个全局变量window
Object.defineProperty(global_val, "window", {
    get: function () {
        return global_val
    },
    set: function set_window(val) {
        global_val = val
    },
    enumerable: true,
    configurable: true,
})
// 无论代码调取那个都是指向这个window  做一个全局处理
window.top = window.self = window.window = window
// (2)补原型链环境
/* 补大Window
* 小window和大Window是两个东西,小window类似于是大Window的实例化对象,大Window是类的概念
* */
// 创建Window空间   createConstructor 一般处理的都是大Window这样的类对象,小window一半都是我们自己构建
createConstructor("Window", true, [], {})
console.log(Window)
// 小window需要和大Window建立父子关系
window.__proto__ = Window.prototype
// 给小window补toStringTag方法  因为小window是单独处理的,所以需要单独把这些加进来
Object.defineProperty(window, Symbol.toStringTag, {
    value: "Window",   // '[object Window]'  因为是这个字符串所以需要返回Window
    writable: false,
    enumerable: false,
    configurable: true
});
window.document = {}
/* 补Element
* Element --》 Node --》 EventTarget --》 Object
* 因为Element的父类是Node,Node的父类是EventTarget,EventTarget的父类Object,而且在createConstructor方法中,每次在最后面也只能添加一层的父类
* 所以我们给Element设置完父类Node以后,还需要给Node以同样的方式设置父类EventTarget  连续三个createConstructor,这才完成了一个原型串
* */
createConstructor("EventTarget", true, [], {})   // EventTarget 后面就没爹了,没爹不用写,默认是Object
createConstructor("Node", true, ["parentNode", "childNodes"], {}, "EventTarget")
createConstructor("Element", true, ["childElementCount", "innerHTML"], {}, "Node")
// 补document的爹和爷  document --》 HTMLDocument  --》 Document --》 node  经过查看链,这些方法都在Document中,所以就补在Document类中
createConstructor("Document", true, [], {
    createElement: function (tagName) {
        // 打印一下tagName 看一下创建的是那个标签
        console.log("Document prototype  createElement ", tagName)
        if (tagName === "script") {
            // 因为script后面也有可能调用了别的,所以我们在这里也需要进行监控
            script = watch(new HTMLScriptElement({
                parentNode: watch(new HTMLHeadElement(null, null, "ljc"), "script的parentNode")
            }, null, "ljc"), "script对象")
            return script
        }
        // canvas和上面的script就一样了
        if (tagName === "canvas") {
            return {}
        }
    },
    createEvent: function () {
    },
    querySelector: function () {
    },
    getElementsByTagName: function () {
    },
}, "Node")
createConstructor("HTMLDocument", true, [], {}, "Document")
// 补document.all 的爹直接就是Object
createConstructor("HTMLAllCollection", true, [], {})
// 补document.documentElement  HTMLHtmlElement --》 HTMLElement  --》 Element --》 Node --》 EventTarget --》 Object
createConstructor("HTMLElement", true, [], {}, "Element")
createConstructor("HTMLHtmlElement", true, [], {}, "HTMLElement")
// 补document.body   HTMLBodyElement -->  HTMLElement  -->  Element  -->  Node  --> EventTarget --> Object
createConstructor("HTMLBodyElement", true, [], {}, "HTMLElement")
//补script
createConstructor("HTMLScriptElement", true, [], {}, "HTMLElement")
//补parentNode
createConstructor("HTMLHeadElement", true, [], {}, "HTMLElement")
// (3)添加监控
// 监控document
// window.document = watch({},"document")   给document对象先置空{},然后在监控起来,监控的名字是"document",在返回window.document
//如果案例不检测原型链,我们就可以使用基础补环境直接documenti = {}  按照上面的先置空,然后在监控,但是本案例对原型链进行了检测,
// 所以我们需要先通过dir(document)先找到它爹它爷,然后补HTMLDocument --》Document --》 node这样的原型链出来, 最后再用创建的爹HTMLDocument new一个document对象出来
window.document = watch(new HTMLDocument({
    // 这里是document实例化的时候传入的一些属性
    // all:new HTMLAllCollection(null,null,"ljc")  其实这样就实例化一个对象就可以了,但是还有一种可能是document.all.xxx 所以我们也需要在实例化的同时也监控一下
    // 所以我们就watch(new HTMLAllCollection(null,null,"ljc"),谁调用的呀)document.all.xxx,也就被监控了
    all: watch(new HTMLAllCollection(null, null, "ljc"), "document.all"),
    //按照上面的方式补全documentElement
    documentElement: watch(new HTMLHtmlElement(null, null, "ljc"), "document.documentElement"),
    //在补一个cookie属性
    cookie: '__jdv=143920055|direct|-|none|-|1759198339989; __jdu=1759198339988240393195; 3AB9D23F7A4B3CSS=jdd0336CXCJW76HVLG5NN3HX7RRXSOAKV7PM4O2R2CEWREEYEERJ3MGNRRZZL6AKZQDEG6JSLQTBE5CCGNYDHCKPS4VKR4EAAAAMZTBSF5TAAAAAADED2AUUGHUOS44X; _gia_d=1; xapieid=jdd0336CXCJW76HVLG5NN3HX7RRXSOAKV7PM4O2R2CEWREEYEERJ3MGNRRZZL6AKZQDEG6JSLQTBE5CCGNYDHCKPS4VKR4EAAAAMZTBSF5TAAAAAADED2AUUGHUOS44X; mba_muid=1759198339988240393195; mba_sid=17591983546191142823231.1; wlfstk_smdl=di09prnlss3k3bnvofmpwmkjo812881l; TrackID=1RVWFVwuwegh6iaq2dUaSUyxtlv2W8ZsWmxP21tBeuzA6JLP01W25RW4ZNKGWhDFKBR_xMLfaTohtTorBRBpdNlxBRXHQ87UD-YA5CjBNN54; pinId=w8Ih7LOJnxwxhZalSpUGsbV9-x-f3wj7; pin=jd_52b15ac66b44e; unick=%E5%91%86%E5%91%867115; ceshi3.com=203; _tp=oRxO98xexSXw5ZlTXC2fbDrx5URIy94AUL5RT79KEbs%3D; __jda=143920055.1759198339988240393195.1759198340.1759198340.1759198340.1; __jdb=143920055.4.1759198339988240393195|1.1759198340; __jdc=143920055; o2State=; is_avif=onAVIF; areaId=5; shshshfpa=b9a6c825-8808-1785-a164-cdcc6d85da4a-1759198380; shshshfpx=b9a6c825-8808-1785-a164-cdcc6d85da4a-1759198380; cn=160; ipLoc-djd=1-72-55653-0; shshshfpb=BApXSoJBtm_xAUGY1ek_5MROtpLvWQ2uQBhtYhK9o9xJ1MhEHxo62; sdtoken=AAbEsBpEIOVjqTAKCQtvQu17mfvlXCqDS0kDr1URwxQHljkUa_9A_ZlGxFL5FumtPOEc2qL3IJkeuNb1O6o04CUR4DMk0etLSPh823yKNu4WG_MO2ZAQTa1qerWUv7eZjfgraGRgUhS_8NI; 3AB9D23F7A4B3C9B=36CXCJW76HVLG5NN3HX7RRXSOAKV7PM4O2R2CEWREEYEERJ3MGNRRZZL6AKZQDEG6JSLQTBE5CCGNYDHCKPS4VKR4E',
    //补body
    body: watch(new HTMLBodyElement(null, null, "ljc"), "document.body"),
}, null, "ljc"), "document")
// 监控window,名字是"window" 重新用window   重新用的名字 = watch(监控对象, 监控的名字)
window = watch(window, "window")
// 三、源码
// ***************************此处补充源码**************************
// 四、测试
// 使用源码进行new,通过实例化创建window.PSign对象
/*window.PSign = new window.ParamsSign({
    appId: 'f06cc',
    preRequest: false,
    onSign: (res) => {
        // 签名可用率监控,业务方自行上报
        if (res.code != 0) {
            try {
                window.dra &&
                window.dra.sendCustomEvent &&
                window.dra.sendCustomEvent({
                    name: 'main_search',
                    metrics: {
                        error_code: '751',
                        error_type_txt: '接口加密失败onSign非0',
                    },
                    context: {
                        error_code: res.code,
                    },
                })
            } catch (error) {
                console.log(error)
            }
        }
    },
    onRequestTokenRemotely: (res) => {
        // 算法接口可用率监控,业务方自行上报
        if (res.code != 200) {
            try {
                window.dra &&
                window.dra.sendCustomEvent &&
                window.dra.sendCustomEvent({
                    name: 'main_search',
                    metrics: {
                        error_code: '751',
                        error_type_txt: '接口加密失败onRequestTokenRemotely',
                    },
                    context: {
                        error_msg: res && res.message ? res.message : '接口加密失败',
                    },
                })
            } catch (error) {
                console.log(error)
            }
        }
    },
})
const paramsH5sign = {
    appid: 'search-pc-java',
    functionId: "pc_search_adv_Search",
    client: 'pc',
    clientVersion: '1.0.0',
    t: new Date().getTime(),
}
params = {
    "ad_ids": "292:6",
    "xtest": "new_search",
    "ec": "utf-8",
    "area": "1",
    "page": "2",
    "simpleSearch": "0"
}
paramsH5sign.body = cryptoJs.SHA256(JSON.stringify(params))
// 使用实例化创建的对象,替换this
ret = window.PSign._$sdnmd(paramsH5sign);
console.log(":::::", ret)*/
function main(paramsH5sign) {
    //取源码
    // ***************************此处补充源码**************************
    // new 实例化
    encrypt = new ParamsSignMain({
        appId: "fb5df",
        debug: false,
        onSign: function (t) {
        },
        onRequestToken: function (t) {
        },
        onRequestTokenRemotely: function (t) {
        },
        bucket: "0.1.8"
    })
    // 传进来的 paramsH5sign 只在这里进行了取值操作
    result = encrypt._$sdnmd({
        "appid": paramsH5sign['appid'],//"item-v3",
        "functionId": paramsH5sign['functionId'],//"pc_stocks",
        "client": "pc",
        "clientVersion": "1.0.0",
        "t": +new Date(),
        "body": paramsH5sign['body']
    })
    console.log("H5ST:::", result["h5st"].length, result)
    document_all = 0
    return result
}
//  module.exports js的导出,导出那个函数,导出main这个函数,对外叫什么名字,对外叫get_h5st
module.exports = {get_h5st: main}
 
                    
                 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号