Updated @ 2025-8-20: 修改了标题和一些文本,未雨绸缪地移除了具体网址、商品名和教程
在军训期间,***洗衣机尤其抢手,由于严重的供需不平衡关系,很难抢到一个时间正好(23:00)的洗衣机。甚至于,***洗衣机可以预约3天的洗衣机,而在当天的凌晨零点,新放出来的后天的洗衣机已经抢完。一旦抢不到洗衣机,就必须手洗衣服,对军训一天、腰酸背疼的学生造成了严重负担。
对此,治本的方式就是增加洗衣机数量,最大程度缓解供需问题。但这并不明显可行,所以,以下是帮助你在抢洗衣机时如有神助的方法。
原理
POST https://api.******/orders/store
参数为:
| query |
可选值 |
说明 |
count |
1 |
推测为预定洗衣机的数量,未测试 |
deviceServerId |
一段24位hex |
未知 |
predetermineTime |
Timestamp |
预定的时间 |
storeId |
一段24位hex |
店面编号,例如,玉湖7栋1楼为5f375802c6e30100014256b6 |
physiologicalGender |
M or F |
字面意思 |
exclusive |
0 |
未知 |
sessionId |
一段字符串 |
字面意思,删了不影响 |
我们主要关心的是predetermineTime.
有这几种response.
| code |
说明 |
| 200 |
创建未成功,返回data为一个数组,包含距离所选时间最近的两个可用时段。 如果predetermineTime是一个过于超前的时间,那么数组可能只含有一个项 |
| 201 |
创建成功,返回data为一个Object,其中包含订单信息 |
| 4xx |
未登录/登录失效/Authorization错误 |
如何构建 Authorization
核心鉴权为请求Headers中的Authorization项目。
这是一个Base64值。
解码后,其值形如以下:
1
| Bearer {"userId":"aaaaaaaaaaaaaaaaaaaaaaaa","time":1724507391097,"sign":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}
|
time是时间戳,userId经确认不会变化,问题是如何获得sign.
最终,在源码中找到以下片段:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| var t = this, n = localStorage.getItem("token") || localStorage.getItem("timeToken"); if (n) { var r = localStorage.getItem("userId"), o = new Date().getTime(), a = V()(o + n), i = { userId: r, time: o, sign: a }; (i = "Bearer " + d()(i)), (e.headers.common.Authorization = $.encode(i)); } else { var u = (function () { var e = window.navigator.userAgent.toLowerCase(), t = d()(e); /micromessenger/.test(e) && (t = "wechat"); /alipayclient/.test(e) && (t = "alipay"); /yunmaapp/.test(e) && (t = "yunma"); /cloudpay/.test(e) && (t = "cloudpay"); /dingtalk/.test(e) && (t = "dingtalk"); return t; })();
|
我们可以确定:d()为JSON.stringify,sign仅与timestamp和token有关,且userId和token都可以从localStorage获取
所以V()是什么?
通过上下文,V()是webpackJsonp中一个编号为NC6I的包,通过遍历Chunks,最终找到这个包。

所以sign就是md5(timestamp + token),经测试成功。
1 2 3 4 5 6 7 8
| const NOWTime=(new Date()).getTime() const authorization=Buffer.from("Bearer "+JSON.stringify( { "userId":UserId, "time":NOWTime, "sign":md5(""+NOWTime+TOKEN) } )).toString("base64"),
|
如何获取localStorage
首先,我们需要devTools,所以我们启动浏览器,访问wap.wash.ltd,尝试登陆。
发现根本无法登录。该网页鉴权只能通过微信/支付宝/钉钉进行,换言之,只能在相关应用程序内置的Webview中访问应用。 (也许有办法将这个过程放在外界容器中进行,但是我不会)
为了实现获取前文所需的userId和token,我们尝试重写页面,将相关代码送入webview中。
p.s. 这个过程还是不够亲民,希望有高手帮忙改进。
我们在index.html的body末尾中加入如下片段,然后用Fiddler的Auto Responder功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| <div id="pwned" style="display: block;position: fixed;top: 20px;bottom: 20pxwidth:600px;height:100px;overflow: scroll;" > <h1>Your page has been pwned.</h1> <pre>UserId: <span id="pwn__userId"></span></pre> <pre>Token: <span id="pwn__token"></span></pre> <input id="exec000" /><button onclick="execCode___()">EXEC</button> <pre>Outputs:</pre> <div id="pwned_reqs"></div> </div> <script> function execCode___() { document .getElementById("pwned_reqs") .insertAdjacentHTML( "beforeend", "<pre>" + eval(document.getElementById("exec000").value) + "</pre>" ); }
(() => { document.getElementById("pwn__userId").innerText = localStorage.getItem("userId"); document.getElementById("pwn__token").innerText = localStorage.getItem("token") || localStorage.getItem("timeToken"); })(); </script>
|
随后用电脑端微信打开wap.wash.ltd,发现成功获取userId和token
Fun Facts
- 在玉湖7栋1楼,一天的开始是2:19。换言之,你在一天最晚能订到大后天的2:19的洗衣机。
- 从后天往后所有的时间,在选择时间界面上都是“后天”