简介这套Java自动下单工具面向有一定Java基础的电商开发爱好者解决在京东商城大促时手动下单慢、易错过时机的问题工具已在真实平台验证。压缩包共33个文件容量仅73KB内部以maven工程组织含有12个class字节码、10个java源文件、7个xml配置文件、2个properties属性文件、1个jsp页面和1个iml工程标识且src/main与src/test目录分离导入IDE即可快速定位核心类既可阅读源码也可直接运行。目前已有3427人学习下载。项目的核心在于模拟用户购物链路从商品选择、收货地址、支付方式到最终提交订单全过程涉及HTTP请求构造、Html页面解析、JSON数据交互等典型电商自动化技术代码中针对京东改版造成的接口变动做过适配调整并保留了并发、异常处理与安全通信相关逻辑。若想学习真实B2C平台下单交互可重点阅读源码中如何维持登录会话、解析动态参数以及处理订单提交前后的校验字段也可借助它搭建自己的抢购脚本或补全测试用例适合作为课程设计或电商自动化入门参考。1. 自动下单工具Java在解决什么问题把“点击下单”变成一个可调度的请求链自动下单工具Java已在京东商城验证——这套工具要解决的事情一句话就能说清把在京东商城网页上手动完成的搜索、加购、提交订单三个动作变成一串由 Java 程序自动执行的 HTTP 请求。真正做过的人都知道难点从来不在 HTTP 请求本身而在登录态能不能持续保有、风控会不会在中途把你拦下来、重试会不会造成重复下单。我是被这三座山挨个绊倒之后才把这套流程跑稳的后面每一章都对应一段血泪经验。这套工具适合的人群很明确需要定时补货的个人买家、做价格监控联动下单的技术爱好者、以及想研究电商接口请求链路的开发者。它解决的核心痛点是“人盯页面太累”把商品价格、库存、下单动作交给程序盯着。但要注意边界我只建议用于自购、自用场景不要做批量囤货转售也不要长时间高频压测接口账号被标记之后恢复成本远高于省下的那点时间。2. 下单链路拆解请求顺序、会话保持与 Java 技术选型2.1 自动下单的本质把页面操作映射成四个请求阶段我重建这套工具时第一件事不是写代码而是把京东商城网页端的关键动作全部抓一遍包。别急着打开 IDE先打开浏览器开发者工具切到 Network 面板过滤 XHR/Fetch然后手动走一遍搜索、加购、发起订单的完整流程。这一步拿到的接口顺序和参数依赖比任何代码骨架都值钱。把这一套依赖关系总结成一张请求链路的表后面所有代码都是围着它转阶段页面动作依赖数据说明登录二维码登录/扫码确认无兑换会话票据后续请求都要带上 Cookie商品查询搜索关键词/打开详情页keyword、skuId拿到商品 ID、价格、库存状态加购点击“加入购物车”skuId、数量、收货区域写入购物车不生成订单提交订单结算页点击“提交订单”skuId、数量、地址、支付方式真正生成订单整套链路的高风险点这四步里登录和提交订单是两个独立关口商品查询和加购属于常规操作。依赖方向是单向的必须先拿到 skuId 才能加购必须先维持有效登录态才能提交订单。抓包时最需要注意的是请求头里的 Referer、Cookie 和自定义参数。京东商城的网页端部分请求带签名参数常见做法是把这些参数按固定模板拼接后保存为配置不要每次都重新生成因为接口校验的是“同一会话内参数的一致性”而不是参数明文本身。2.2 为什么是 Java 而不是脚本语言OkHttp 的连接池和超时控制常见选择是 Python 加 requests但我用 Java 重写这套工具时理由有三连接池、线程模型、类型约束。OkHttp 对 HTTP 连接复用、超时处理、Cookie 管理的默认行为比 requests 粗放的 Session 模型更适合高频轮询场景。先看一个最小客户端构建OkHttpClient client new OkHttpClient.Builder() .connectTimeout(3, TimeUnit.SECONDS) // 连接建立超时 .readTimeout(8, TimeUnit.SECONDS) // 响应超时卡住时及时止损 .writeTimeout(8, TimeUnit.SECONDS) // 请求体写出超时 .cookieJar(new LocalCookieJar()) // 自定义会话管理见 2.3 .retryOnConnectionFailure(false) // 连接失败不自动重试避免重复下单 .build();逻辑说明connectTimeout 是建连阶段最长等待时间登录和加购接口正常情况下响应很快设太短会误伤readTimeout 要留足下单接口的响应余量设太长会让请求挂死后更难排查retryOnConnectionFailure 必须设成 false这是下单工具和普通爬虫最大的区别——连接失败后自动重试对查询接口没问题但下单接口不是幂等的一个 TCP 中断后的自动重试可能让订单重复提交。参数上我建议 connectTimeout 保持 3 秒、readTimeout 8 秒、writeTimeout 8 秒这个组合在京东商城的网络环境下足够稳。还有一个隐藏优化OkHttp 客户端必须是全局单例。我第一版图省事每个请求 new 一个 client在同一个网络出口下产生大量短连接跑了二十分钟后接口连续报错改成单例后问题消失这正是连接池复用的效果。2.3 Cookie 会话管理登录态决定 80% 的成败自动下单的第一座山是会话。网页端登录成功后服务端通过下发 Set-Cookie 标记登录态浏览器自动保存并在后续请求附带而 Java 程序里没有人替你保存必须自己实现。OkHttp 提供了 CookieJar 接口实现它就能把登录得到的 Cookie 存住并在每次请求前自动附加public class LocalCookieJar implements CookieJar { private final MapString, ListCookie store new ConcurrentHashMap(); Override public void saveFromResponse(HttpUrl url, ListCookie cookies) { store.put(url.host(), cookies); // 按域名保存避免跨域串号 } Override public ListCookie loadForRequest(HttpUrl url) { ListCookie cookies store.get(url.host()); return cookies ! null ? cookies : Collections.emptyList(); } }逻辑说明saveFromResponse 在每次响应带回 Set-Cookie 时被调用loadForRequest 在每次请求发送前被调用。按 host 分桶存储可以保证京东商城域名的 Cookie 不会附加到其他域名请求上。有个细节会话票据下发时经常带多个 CookieOkHttp 不管 HttpOnly它只按 host 保存所以请求头里的 Cookie 会带全。要注意线程安全。多线程共用一个 client 时这个 jar 必须用 ConcurrentHashMap不能用 HashMap。我见过有人用 HashMap 导致高峰期偶发未登录原因就是并发 put 时丢 Cookie排查了很久才定位到是数据结构问题。另外进程重启后 Cookie 会丢需要重新扫码。常见做法是每次拿到新 Cookie 就序列化到本地文件启动时优先加载旧会话这样至少能省一次手动登录。扫码登录本身的逻辑是请求二维码接口拿到图形展示给用户然后轮询扫码结果接口成功后把响应里的 Cookie 写入 store轮询间隔不要低于 2 秒一次太频繁容易触发滑块校验。3. 核心下单流程实现搜索、加购、提交订单的三段式3.1 搜索与加购拿到 skuId 之后动作要连贯下单链路从拿到正确的 skuId 开始。京东商城的搜索接口返回 HTML 或 JSON取决于你请求的是网页搜索页还是移动端接口。我一般稳定跑的是移动端接口——响应体小、字段结构固定、风控阈值相对网页端宽松。具体接口地址从抓包复制到配置文件里不写死在代码中接口换版时改配置比改代码快。// 步骤1搜索商品拿到 skuId、名称、当前价 String searchUrl SEARCH_URL.replace({keyword}, URLEncoder.encode(keyword, UTF-8)); Request searchReq new Request.Builder().url(searchUrl) .header(User-Agent, USER_AGENT) // 固定 UA和扫码登录时保持一致 .build(); try (Response resp client.newCall(searchReq).execute()) { SearchResult result JSON.parseObject(resp.body().string(), SearchResult.class); String skuId result.getFirstSkuId(); int price result.getPrice(); }逻辑说明URLEncoder.encode 处理中文关键词防错解析用 JSON 库只映射需要的字段不要用正则去抓 HTML 里的价格——正则一旦碰到促销价和划线价两种 DOM 结构结果天差地别。User-Agent 必须固定不能每轮随机同一个会话里 UA 跳变是风控的高频触发特征。参数说明价格字段要选“实际到手价”而不是“页面价”促销场景下两者悬殊搜索之后紧接着要校验库存用一个只读库存接口重点看库存状态和可售数量。加购动作紧随其后。// 步骤2加购返回购物车变更后的校验信息 FormBody formBody new FormBody.Builder() .add(skuId, skuId) .add(count, 1) // 加购数量脚本里写固定值 .add(area, AREA_CODE) // 收货区域编码抓包时原样复制 .build(); Request addCartReq new Request.Builder().url(ADD_CART_URL) .post(formBody) .build();逻辑说明加购接口用表单方式提交而不是 JSON。我在这踩过一次坑照抄成 application/json 后接口返回参数错误改成表单格式一切正常。area 参数对应收货区域编码必须从自己常用收货地址的抓包里抠出来写死填错会在下单阶段暴露为无货。加购成功后不要立即查购物车列表直接提交订单即可查列表是多余请求多一次请求就多一分风控风险。提示加购和提交订单之间不要插入任何无关网络请求。这段间隔越短库存被他人占用的窗口越小行为特征也越接近真人。3.2 提交订单幂等键是重复下单的后悔药整个工具最怕的不是下单失败而是重复下单。我第一次小批量验证时翻过车提交订单接口超时我以为失败了做了自动重试结果扣了两次款。后来我定下铁规矩下单接口只允许串行调用任何重试执行前必须查本地订单记录确认没有“已提交但未收到响应”的记录才能放行。// 提交订单调用前先检查本地幂等表 if (submittedOrders.contains(orderKey)) { log.warn(订单已提交过跳过本次提交: {}, orderKey); return; } Request orderReq new Request.Builder().url(ORDER_SUBMIT_URL) .post(requestBody) .build(); String respBody; try (Response resp client.newCall(orderReq).execute()) { respBody resp.body().string(); } catch (IOException e) { // 网络异常不确定是否已落单宁可人工确认不自动重试 throw new OrderSubmitUncertainException(e); } submittedOrders.put(orderKey, respBody);逻辑说明orderKey 由 skuId 加数量加日期加会话标识拼成submittedOrders 用 ConcurrentHashMap支持并发读写。网络异常时抛出特定异常类型上层捕获后标记为“待人工确认”而不是自动重试。这个策略比任何重试算法都重要——下单接口不确定是否成功时最安全的动作是停下等人。参数说明请求体里通常包含 skuId、数量、收货地址编码、支付方式、订单备注。支付方式固定用在线支付不选货到付款因为自动工具没法替你完成货到付款的确认动作。订单备注留空不要填促销编码多余参数增加风控识别概率。要注意的是这个幂等表是客户端幂等服务端并不保证同一标识只创建一单所以它只能做本地提醒不能当作唯一约束。3.3 把“抢”改成“守”价格与库存校验再下单直接把下单动作绑在定时器上是最粗糙的用法。更稳的用法是先跑一个轻量轮询循环把价格和库存变化当作触发条件条件满足才调用 3.2 的提交逻辑。这样既降低无效请求数量也避免价格没变时反复提交。// 价格/库存监听达到阈值才触发下单 while (maxPolls-- 0) { PriceStock status fetchPriceAndStock(skuId); if (status.getStock() 0) { continue; } // 无货继续等下一轮 if (status.getPrice() targetPrice) { continue; } // 价格未到继续等 submitOrder(skuId, defaultCount); // 条件满足立刻下单 return; }逻辑说明注释已经把三分支写清楚。这里有一个很容易踩的竞态如果价格和库存分别在两轮循环里各自满足条件但从未在同一轮同时满足就会一直不触发。解决办法是每轮都重新取一遍价格和库存用同一个快照对象判断两个条件——这就是上面代码传一个 status 对象的原因分开获取就会存在时间差。参数说明targetPrice 不要设成历史最低价要设成你真正愿意成交的价格maxPolls 必须设上限防止程序挂在死循环里空转。每轮之间建议 sleep 800 毫秒到 1 秒低于 500 毫秒就属于压测频率了。fetch 接口本身是只读请求但高频持续调用同样会被限流轮询间隔加一点随机抖动比固定间隔更稳。4. 定时触发、重试与限速让自动下单在线上不翻车的三个开关4.1 定时触发Java 自带调度器就够用大多数场景里下单工具是“定点启动”而不是“持续运行”——商品在某个时刻开售程序需要在该时刻前几秒启动流程。Java 自带 ScheduledExecutorService 就够用不必为单机脚本引入 Quartz。ScheduledExecutorService scheduler Executors.newScheduledThreadPool(2); scheduler.scheduleAtFixedRate(() - { try { BuyFlow.builder() .skuId(skuId) .targetPrice(targetPrice) .maxPolls(maxPolls) .build() .run(); // 内部按 3.3 的循环执行轮询下单 } catch (Exception e) { log.error(下单流程执行异常, e); // 异常只记录不影响下一次调度 } }, initialDelay, period, TimeUnit.MILLISECONDS);逻辑说明scheduleAtFixedRate 表示按固定速率启动period 是相邻两次启动的间隔不是“执行完再等”的周期。下单流程单次执行如果超过 period下一次会被顺延堆积所以 period 必须大于单次流程最坏耗时。catch 必须放在 Runnable 最外层否则任务抛异常后scheduleAtFixedRate 会取消后续所有执行这是新手最容易忽略的调度器行为。参数说明initialDelay 按开售时间倒推设置比如开售前 50 秒period 我习惯设为 5 分钟流程正常跑完不到 10 秒留出足够余量。线程池大小 2 就够一个跑轮询一个跑下单心跳。如果同一进程里要管多个商品把每个商品放进独立任务而不是一个任务里循环遍历所有 skuId——后者会让单次执行时间拉长打乱 period 节奏。4.2 重试策略先判断能不能重试再退避前面说过下单接口不允许盲目重试这里把可重试和不可重试画一条明确分界线。我整理了一张决策表贴在代码注释上方场景是否重试说明连接超时否无法确认服务端是否已落单人工确认响应体为验证码/滑块否重试只会加重风控返回无货/价格变化否条件已失效应回到监听轮询本地参数错误否改配置后重新部署返回系统繁忙是最多2次退避 3 秒后再试这个表只适用于查询类接口。下单接口的重试逻辑不在这张表里它走的是 3.2 的“抛异常等人工”分支。两者必须分开混在一起就是重复下单事故的起点。private void retryableCall(Call call, int maxRetries) throws IOException { int attempt 0; while (attempt maxRetries) { try (Response resp call.execute()) { if (!isTransientError(resp)) { return; } // 只有“系统繁忙”类错误才走重试 } attempt; Thread.sleep(3000L * attempt); // 退避3秒、6秒递增 } }逻辑说明isTransientError 判断的是业务错误码不同网关含义不一样不要靠 HTTP 状态码判断。退避用线性递增而非固定间隔可以避免多个客户端在同一时间点同时重试形成请求毛刺。参数上maxRetries 固定为 2超过后直接放弃本轮回到监听循环等下一轮。4.3 单会话串行、多会话隔离限速是自我保护限速不是为了对抗平台而是保护账号不被异常识别。下单链路天然是串行的一个会话同一时间最好只有一个流在做加购和提交。多线程共用一个会话时Cookie 本身没问题但请求频率特征会被放大最终表现为验证码弹窗或账号被标记。Semaphore orderPermit new Semaphore(1); // 同一会话只允许一个下单流程 boolean acquired orderPermit.tryAcquire(); if (!acquired) { log.warn(已有下单流程在执行本次调度跳过); return; } try { buyFlow.run(); } finally { orderPermit.release(); }逻辑说明tryAcquire 防止同一会话里两个下单流程重叠执行拿不到信号量就直接跳过而不是排队。排队会造成两个流程几乎连在一起执行短时间内的请求频率特征会翻倍这正是风控最喜欢抓的模式。finally 里 release 保证异常时信号量不泄漏。参数说明单账号场景 Semaphore(1) 就够。多账号场景要每个账号一个独立 client、一个独立信号量绝对不要共用——共用连接池会让多个账号的请求从同一出口 IP 发出手机号关联风险极高。我给自己定的线是单账号每秒不超过 2 个请求一个完整下单流程约 15 秒两个账号轮换跑已经接近边界。5. 京东商城自动下单避坑指南验证码、Cookie 过期与账号标记排查5.1 验证码弹窗立即停止重试人工介入一次现象下单接口返回的不是订单数据而是一个验证码弹窗或滑块校验地址响应体里带一个 token 字段。原因短时间高频请求、同一设备多账号切换登录、UA 或设备指纹变化都会让风控要求额外确认。解决把验证码地址完整打到日志里程序立即停止本轮所有自动操作等 5 到 10 分钟窗口恢复期间人工过一遍验证码让会话续上。不要尝试自动过验证码这类方案的稳定性和合规性都不可控一次失败就会把账号拖进更深的限制。5.2 Cookie 过期HTTP 200 不代表登录态有效现象接口返回 HTTP 200但业务状态码是未登录或会话失效页面表现就是“请重新登录”。原因会话票据有效期到了、服务端主动刷新会话、多端登录互踢。解决把业务状态码判断放在所有响应解析的第一位单独识别未登录状态并触发重新扫码不要只看 HTTP 状态码。我有一次排查了很久因为所有请求都是 200服务器没报错最后还是从响应体里的 code 字段发现会话早断了。另外注意会话有“滑动续期”机制把 Cookie 序列化到本地后重启会自动续期但如果存的是过期前最后一次快照续期失败会反复出现未登录这时删掉本地 Cookie 文件重新扫码就是后悔药。注意存储 Cookie 前先校验会话有效期字段不要无条件覆盖旧文件否则可能把一套有效会话覆盖成过期会话。5.3 订单提交成功但支付受限账号被风控标记现象提交订单接口返回成功去支付时提示账户风险要求人工提交材料申诉。原因行为轨迹异常积累比如全天候轮询、收货地址频繁变化、同一设备跑多个账号。解决降频运行。自用场景建议只开售前 30 分钟启动监听结束后彻底退出一个设备只跑一个账号不要用多个账号共享同一个 UA 和出口 IP。账号一旦被标记恢复周期以天为单位远超省下的那点抢购时间。5.4 库存竞态购物车有货并不代表提交时有货现象加购接口返回有货提交订单时返回无货。原因库存是变动的加购到提交之间隔了校验和网络延迟这个窗口里库存被清空。解决把“提交前最后一道库存校验”和“提交订单”放在同一段代码里紧挨着执行校验通过后立即提交中间不插入日志、不写文件、不做任何其他网络请求。如果提交仍然失败不要再自动重试回到监听轮询等下一轮有货窗口。这个竞态无法彻底消除只能缩短窗口时间。5.5 本地配置与抓包不一致最隐蔽的玄学问题现象本地跑一切正常换一台机器后各种参数错误或者同一个脚本上午正常下午报错。原因很多参数在抓包里是动态的例如区域编码、站内价格缓存的附加参数、促销标识。它们会随时间或地区变化复制时看着没问题运行时就是不对。解决配置里所有动态参数都标注来源写明是从哪一次抓包、哪个环境里抠出来的启动时做一次参数自检用只读接口验证当前配置仍有效无效则启动失败而不是带病下单。这个自检动作看起来多花几秒实际能省掉数小时的排查时间。6. 进阶把下单工具改造成“到达阈值才动手”的监控助手6.1 监测器与执行器解耦工具成熟后的形态不是一个“到点下单”的定时器而是一个“先观察、再决定”的监控助手。我把整个流程拆成三个对象PriceMonitor 负责轮询价格库存ThresholdDecider 负责判断条件OrderExecutor 负责执行提交。三者之间用接口连接每个对象都可以单独替换换平台时只动 Monitor 和 Executor判断逻辑原样保留。public class PriceMonitor { public void start() { while (!Thread.currentThread().isInterrupted()) { PriceStock now fetchPriceAndStock(skuId); if (decider.shouldBuy(now)) { // 阈值判断放在监听侧 orderExecutor.submit(skuId); break; // 下单成功后退出监听 } Thread.sleep(800); } } }逻辑说明这个结构的关键是阈值判断放在监听侧不在执行器侧。提交订单前只认一份快照执行器不重新查价、不重新判断减少竞态窗口。参数上轮询间隔、目标价、库存下限都从配置读取换商品只需改配置。6.2 一份值得长期维护的运行日志日志字段比代码重要。我习惯每轮询打一行 CSV字段包含时间、价格、库存、状态、触发与否。这样出了问题可以完整回放而不是对着空控制台猜。验证方法也很直接先在本地跑两天“只监控不下单”模式把所有日志存下来回放后把阈值参数调准再放开下单开关。这是最稳的上手路径也是我建议任何人改这个工具时的第一步。第一版工具我直接对着空控制台调参数后来发现阈值设错、频率太高全靠日志回放才定位。现在所有参数默认先以日志方式输出一轮确认行为符合预期后才接执行器。每个自动下单工具都是“先学会停再学会跑”——把监听、阈值、执行三件事分开你才真正握住了开关。希望帮到你。本文还有配套的精品资源点击获取