码迷,mamicode.com
首页 > 其他好文 > 详细

电商秒杀

时间:2014-09-16 16:14:31      阅读:261      评论:0      收藏:0      [点我收藏+]

标签:des   style   blog   http   color   io   os   使用   java   

秒杀思路:后台根据商品加入秒杀发布秒杀详细信息,生成静态页面,发布信息添加到库,前台查看秒杀信息,

(我前台的概念是买家使用的系统为前台,公司内部使用的管理系统为后台),秒杀中库存和事件通过ajax

取服务器时间(客户端的时间不准),秒杀库存的统计已下单为主,库存数量的更改以下单为准,秒杀结束和下面《》里内容

就直接抛秒杀结束的静态页面.

阻止秒杀请求,比如有一个商品,已经有100人在秒杀,前100人至少有一个人来买

正在秒杀的信息可以放入缓存(可以用开源的memcached等)【key:商品id,value:action请求统计数】

(只允许让产品数量的N倍以内的人请求秒杀可以通过配置文件判断)

要参与秒杀的商品都放入缓存中,价格,库存,id,数量,加上一个字段 加上sell_num 卖出数量,缓存中存储是否

已经达到卖出数量,达到的话就直接抛出秒杀结束静态页面。支付的时候根据是否已卖出判断,商品的秒杀数量已经达到的话操作秒杀用户信息的操作事务回滚。先下单成功的去库中减1,保存用户支付信息

( action请求统计数可以单独弄服务器来做 这个action只做一件事情就是统计请求数量,用servlet就可以了,一台不行,弄多台)

秒杀:准点开抢,先抢先得。
页面上的操作流程大致是这样的:登录->填写用户资料->等待倒计时->弹出随机问题->填写答案->提交。
前两步人工完成,程序完成从等待倒计时到提交结束的步骤。主要使用httpclient模拟http请求。前期工作主要研究下原站的相关js代码,获得各步骤必要的URL和相关参数。其中还用到了坛子里的fastjson感谢一下bubuko.com,布布扣

主要代码如下,就不做什么解释了。

Java代码 bubuko.com,布布扣 bubuko.com,布布扣bubuko.com,布布扣
  1. Protocol easyhttps = new Protocol("https", new EasySSLProtocolSocketFactory(), 443);
  2. Protocol.registerProtocol("https", easyhttps);
  3. HttpClient httpClient = new HttpClient();
  4. //字符编码
  5. httpClient.getParams().setContentCharset("gbk");
  6. HostConfiguration hc = new HostConfiguration();
  7. hc.setHost("www.uqbook.cn", 80, easyhttps);
  8. String configId = "200011111111444444";
  9. String agent = "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.2.13) Gecko/20101203 Firefox/3.6.13";
  10. String refer = "https://www.uqbook.cn/sec_initListInfo.action";
  11. String contentType = "application/x-www-form-urlencoded; charset=UTF-8";
  12. //Cookie
  13. String cookie = "JSESSIONID1=pkfXNQDY0DTKRpch4ngp62hD1LNmcp5Jpn8G2R2LQVylxHcH1hpr!-595124091";
  14. String inputsubje = "";//答案一
  15. String inputArith = "68";//答案二
  16. int i = 1;
  17. boolean isFirstIn = true;
  18. while (true) {
  19. if(isFirstIn){
  20. //进入页面
  21. GetMethod inMethod = new GetMethod("https://www.uqbook.cn/initInfo.action");
  22. // 设置参数
  23. inMethod.setRequestHeader("User-Agent", agent);
  24. inMethod.setRequestHeader("Referer", refer);
  25. inMethod.setRequestHeader("Cookie", cookie);
  26. try {
  27. int codeCode = httpClient.executeMethod(hc, inMethod);
  28. InputStream inResp = inMethod.getResponseBodyAsStream();
  29. //循环读取每一行
  30. BufferedReader reader = new BufferedReader(new InputStreamReader(inResp));
  31. String line = null;
  32. String sysCurrentTime = "";
  33. String secondTt = "";
  34. while ((line = reader.readLine())!=null){
  35. //对读取的数据进行编码转换
  36. line = new String(line.getBytes(),"gb2312");
  37. line = line.trim();
  38. if(line.startsWith("var sysCurrentTime")){
  39. sysCurrentTime = line.substring(line.indexOf("\"")+1, line.lastIndexOf("\""));
  40. }else if(line.startsWith("var secondTt")){
  41. secondTt = line.substring(line.indexOf("‘")+1, line.lastIndexOf("‘"));
  42. }
  43. //logger.info(line);
  44. if(sysCurrentTime!="" && secondTt!=""){
  45. break;
  46. }
  47. }
  48. reader.close();
  49. logger.info("服务器时间:sysCurrentTime="+sysCurrentTime);
  50. logger.info("本机时间:"+DateUtil.getCurrentDateStr(DateUtil.C_TIME_PATTON_DEFAULT));
  51. logger.info("还没到点...等待"+secondTt+"秒...");
  52. Thread.sleep(Integer.parseInt(secondTt));
  53. } catch (Exception e) {
  54. e.printStackTrace();
  55. logger.error("进入页面得到倒计时间异常:" + e);
  56. continue;//继续来一次
  57. }
  58. }
  59. isFirstIn = false;
  60. // 1、取问题及答案
  61. PostMethod subInfoMethod = new PostMethod("https://www.uqbook.cn/getSubjectInfo.action");
  62. // 设置参数
  63. subInfoMethod.setRequestHeader("User-Agent", agent);
  64. subInfoMethod.setRequestHeader("Referer", refer);
  65. subInfoMethod.setRequestHeader("Cookie", cookie);
  66. subInfoMethod.addParameter("timeStamp", ""+System.currentTimeMillis());
  67. subInfoMethod.addParameter("configId", configId);
  68. try {
  69. int subInfoCode = httpClient.executeMethod(hc, subInfoMethod);
  70. String subInfoResp = subInfoMethod.getResponseBodyAsString();
  71. if (subInfoResp == null || "".equals(subInfoResp)) {
  72. logger.info("还没有发布安全问题及答案,本次退出,继续执行下一次!");
  73. continue;
  74. }
  75. logger.info("安全问题及答案解码:" + EscapeUnescape.unescape(subInfoResp));
  76. // 解析 JSON
  77. JSONArray array = JSON.parseArray(subInfoResp);
  78. JSONObject object = (JSONObject) array.get(0);
  79. String content = object.getString("subjectContent");
  80. inputArith = object.getString("arithmeticAnswer");
  81. content = EscapeUnescape.unescape(content).replace("@", " ");
  82. Parser parser = new Parser();
  83. parser.setInputHTML(content);
  84. parser.elements();
  85. for (NodeIterator it = parser.elements(); it.hasMoreNodes();) {
  86. Node node = it.nextNode();
  87. if (node instanceof TagNode) {
  88. TagNode tag = (TagNode) node;
  89. if ("ttwrsnspysk".equalsIgnoreCase(tag
  90. .getAttribute("id"))) {
  91. inputsubje = tag.toPlainTextString().trim();
  92. }
  93. }
  94. ;
  95. }
  96. } catch (Exception e) {
  97. e.printStackTrace();
  98. }
  99. inputArith = "27";
  100. if (inputsubje == null || "".equals(inputsubje)
  101. || inputArith == null || "".equals(inputArith)) {
  102. logger.info("获安全问题及答案时可能发生异常了,本次退出,继续执行下一次!");
  103. continue;
  104. }
  105. //2、判断 是否参加过
  106. PostMethod judgementSecSalMethod = new PostMethod("https://www.uqbook.cn/judgement.action");
  107. // 设置参数
  108. judgementSecSalMethod.setRequestHeader("User-Agent", agent);
  109. judgementSecSalMethod.setRequestHeader("Referer", refer);
  110. judgementSecSalMethod.setRequestHeader("Cookie", cookie);
  111. judgementSecSalMethod.addParameter("timeStamp", ""+System.currentTimeMillis());
  112. judgementSecSalMethod.addParameter("configId", configId);
  113. try {
  114. int judgementSecSalCode = httpClient.executeMethod(hc, judgementSecSalMethod);
  115. String judgementSecSalResp = judgementSecSalMethod.getResponseBodyAsString();
  116. } catch (Exception e) {
  117. e.printStackTrace();
  118. }
  119. //3、验证问题和答案是否正确
  120. PostMethod checkMethod = new PostMethod("https://www.uqbook.cn/judgementAnswer.action");
  121. // 设置参数
  122. checkMethod.setRequestHeader("User-Agent", agent);
  123. checkMethod.setRequestHeader("Content-Type", contentType);
  124. checkMethod.setRequestHeader("Referer", refer);
  125. checkMethod.setRequestHeader("Cookie", cookie);
  126. checkMethod.addParameter("timeStamp", ""+System.currentTimeMillis());
  127. String tempInputsubje = inputsubje;
  128. try {
  129. tempInputsubje = URLEncoder.encode(inputsubje, "utf-8");
  130. } catch (UnsupportedEncodingException e1) {
  131. // TODO Auto-generated catch block
  132. e1.printStackTrace();
  133. }
  134. checkMethod.addParameter("inputsubje", tempInputsubje);
  135. checkMethod.addParameter("inputArith", inputArith);
  136. try {
  137. int checkCode = httpClient.executeMethod(hc, checkMethod);
  138. String checkResp = checkMethod.getResponseBodyAsString();
  139. } catch (Exception e) {
  140. e.printStackTrace();
  141. }
  142. // 3、提交抢购!
  143. PostMethod submitMethod = new PostMethod("https://www.uqbook.cn/submit.action");
  144. // 设置参数
  145. submitMethod.setRequestHeader("User-Agent", agent);
  146. submitMethod.setRequestHeader("Referer", refer);
  147. submitMethod.setRequestHeader("Cookie", cookie);
  148. submitMethod.addParameter("timeStamp", ""+System.currentTimeMillis());
  149. submitMethod.addParameter("inputsubje", EscapeUnescape.escape(inputsubje));// 编一下码
  150. submitMethod.addParameter("inputArith", inputArith);
  151. submitMethod.addParameter("configId", configId);
  152. try {
  153. int submitCode = httpClient.executeMethod(hc, submitMethod);
  154. String submitResp = submitMethod.getResponseBodyAsString();
  155. JSONArray array = JSON.parseArray(submitResp);
  156. JSONObject object = (JSONObject) array.get(0);
  157. String sign = object.getString("sign");
  158. if ("yes".equals(sign)) {
  159. logger.info("抢购登记成功, 退出程序!");
  160. break;
  161. }
  162. logger.info("抢购登记失败,继续...");
  163. } catch (Exception e) {
  164. logger.error("提交抢购异常:" + e);
  165. }
  166. subInfoMethod.releaseConnection();
  167. submitMethod.releaseConnection();
  168. break;
  169. }
  170.  

    一、题目

    这是一个秒杀系统,即大量用户抢有限的商品,先到先得
    用户并发访问流量非常大,需要分布式的机器集群处理请求
    系统实现使用Java

    二、模块设计

    1、用户请求分发模块:使用Nginx或Apache将用户的请求分发到不同的机器上。

    2、用户请求预处理模块:判断商品是不是还有剩余来决定是不是要处理该请求。

    3、用户请求处理模块:把通过预处理的请求封装成事务提交给数据库,并返回是否成功。

    4、数据库接口模块:该模块是数据库的唯一接口,负责与数据库交互,提供RPC接口供查询是否秒杀结束、剩余数量等信息。

    第一部分就不多说了,配置HTTP服务器即可,这里主要谈谈后面的模块。

    用户请求预处理模块:

    经过HTTP服务器的分发后,单个服务器的负载相对低了一些,但总量依然可能很大,如果后台商品已经被秒杀完毕,那么直接给后来的请求返回秒杀失败即可,不必再进一步发送事务了,示例代码可以如下所示:

    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
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    package seckill; import org.apache.http.HttpRequest; /**
     * 预处理阶段,把不必要的请求直接驳回,必要的请求添加到队列中进入下一阶段.
     */ public class PreProcessor { // 商品是否还有剩余 private static boolean reminds = true; private static void forbidden() { // Do something. } public static boolean checkReminds() { if (reminds) { // 远程检测是否还有剩余,该RPC接口应由数据库服务器提供,不必完全严格检查. if (!RPC.checkReminds()) { reminds = false; } } return reminds; } /**
         * 每一个HTTP请求都要经过该预处理.
         */ public static void preProcess(HttpRequest request) { if (checkReminds()) { // 一个并发的队列 RequestQueue.queue.add(request); } else { // 如果已经没有商品了,则直接驳回请求即可. forbidden(); } } }

    并发队列的选择

    Java的并发包提供了三个常用的并发队列实现,分别是:ConcurrentLinkedQueue 、 LinkedBlockingQueue 和 ArrayBlockingQueue。

    ArrayBlockingQueue是初始容量固定的阻塞队列,我们可以用来作为数据库模块成功竞拍的队列,比如有10个商品,那么我们就设定一个10大小的数组队列。

    ConcurrentLinkedQueue使用的是CAS原语无锁队列实现,是一个异步队列,入队的速度很快,出队进行了加锁,性能稍慢。

    LinkedBlockingQueue也是阻塞的队列,入队和出队都用了加锁,当队空的时候线程会暂时阻塞。

    由于我们的系统入队需求要远大于出队需求,一般不会出现队空的情况,所以我们可以选择ConcurrentLinkedQueue来作为我们的请求队列实现:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    package seckill; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ConcurrentLinkedQueue; import org.apache.http.HttpRequest; public class RequestQueue { public static ConcurrentLinkedQueue<HttpRequest> queue = new ConcurrentLinkedQueue<HttpRequest>(); }

    用户请求模块

    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
    package seckill; import org.apache.http.HttpRequest; public class Processor { /**
         * 发送秒杀事务到数据库队列.
         */ public static void kill(BidInfo info) { DB.bids.add(info); } public static void process() { BidInfo info = new BidInfo(RequestQueue.queue.poll()); if (info != null) { kill(info); } } } class BidInfo { BidInfo(HttpRequest request) { // Do something. } }

    数据库模块:

    数据库主要是使用一个ArrayBlockingQueue来暂存有可能成功的用户请求。

    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
    package seckill; import java.util.concurrent.ArrayBlockingQueue; /**
     * DB应该是数据库的唯一接口.
     */ public class DB { public static int count = 10; public static ArrayBlockingQueue<BidInfo> bids = new ArrayBlockingQueue<BidInfo>(10); public static boolean checkReminds() { // TODO return true; } // 单线程操作 public static void bid() { BidInfo info = bids.poll(); while (count-- > 0) { // insert into table Bids values(item_id, user_id, bid_date, other) // select count(id) from Bids where item_id = ? // 如果数据库商品数量大约总数,则标志秒杀已完成,设置标志位reminds = false. info = bids.poll(); } } }

    三、结语

    看起来大体这样应该就可以了,当然还有细节可以优化,比如数据库请求可以都做成异步的等等。

昨天晚上睡觉的时候还在想这个秒杀,还是发现了一些问题,一个在秒杀商品共享一个action,

1:每个商品的秒杀用户都可能不同,都会变化,一起秒杀过来,怎么让秒杀同一个商品的共享同一个action

2:什么时候开始对USER_NUM开始递增,根据客户端时间显然不行,调度?还是每次请求都判断下时间?

解释下为什么要同一个商品要共享一个action:用户数不是确定的 秒杀这个商品的人肯定得共享这个商品的action,为了知道USER_NUM是否已经超过允许的,不允许的话直接抛出秒杀结束静态页面。有多少的秒杀商品就应该有多少的action实例,请求的这个商品的秒杀就是请求这个商品对应的action,但是参与秒杀的商品数不是固定的。该如何设计action,这样的需求怎么配置???秒杀这玩意儿还真的很麻烦,你妹的,看来这样的action无法配置,那就所有用户共同用一个action,放一个map, key:商品ID,value:请求数。这个商品有请求的话就放入map,重新put(商品id,请求+1)每次请求判断下该商品的时间,到了时间就对该商品的请求数进行+1操作

网络误差:请求action的到操作库的时间 可能会导致超过了秒杀结束时

主要的问题

1共同问题:宽带问题

2 服务器端的问题:1:web服务器所在的PC硬件配置 2 web服务器的处理能力 3 数据库服务器的处理能力

用户的问题:1 用户PC配置问题 2 用户所使用的浏览器(用IE就是等死) 3 用户对秒杀点击的及不及时问题

前台对于秒杀时间问题显示问题:加载静态页面用ajax 请求服务器,获取服务器时间当前时间和开始时间,结束时间,

然后页面秒杀的时间就根据第一次请求的时间用js处理

一个文章不错,贴上来:javascript实现秒杀倒计时

被秒杀过的商品数据直接转入历史库,原表不存该数据,减少查询时间,秒杀关注的是未来或者正在秒杀的商品,历史已成为过去,只是查看。

秒杀这东西我感觉都是虚头,刚好那一秒 0 微妙 如此精确的时间,这个时间点可能真的没人在0微妙 秒杀到,那肯定还有要让一个人秒杀到的,估计很多是选这一秒内的随机数,或者这一秒的第一个人。如果以提交订单为主那就谁先提交谁先得到。

所有问题都已解决,网络延迟,这个问题我们这个真心没办法

秒杀seckill大致接口:

前台就是请求静态页面查看,ajax查询动态信息,比如库存,秒杀时间

1:查询秒杀分类信息接口(包括全部秒杀,aa秒杀,bb秒杀,价格段秒杀)

2:搜索秒杀接口

3:秒杀详细内页接口

4: 秒杀剩余时间接口

5: 跳转秒杀结束静态页面接口

6:查询秒杀开始结束和当前服务器时间接口

7:查询已卖出数接口

8: 查询秒杀缓存接口

9: 修改秒杀缓存接口

10:删除秒杀缓存接口

后台:

1:商品加入秒杀接口(同时添加到缓存中)

2:秒杀分类管理接口

3:修改详细秒杀信息接口

4:删除详细秒杀信息接口(转入历史库)

5:静态页面生成接口

6:历史秒杀管理接口

7:秒杀搜索接口

8:请求配置(配置商品的N倍才可请求秒杀)

9:审核接口

欢迎讨论秒杀合理性,缺点等,谢谢

高并发,cache,锁机制
看似简单的秒杀,其实真的不简单。
如果有大牛知道,欢迎分享~

函数用synchronized修饰,数据库先插入在判断是否到达秒杀最大人数,cache记录本次秒杀是否完成.

没深入研究过,不过参与过淘宝的双11活动。很明显的一点是商品没有提供一个非常即时的锁,即便商品实际没有了也是可以下单成功的。但是在支付的时候会提示你商品没有了。猜测只是在支付的时候才有即时的锁。

如果都使用锁的话,效率可能会有问题。秒杀的PV一般都会相当高

 

简单介绍下功能

1.每隔一段时间(比如1分钟)在京东手机每日一秒杀页面提取产品(手机)链接。
http://sale.360buy.com/act/8VTHFGr10CjMDyZ.html#01
2.根据提取到得产品链接给后台发送数据,以便获取产品价格,描述,折扣,库存(是否有货)等信息。
3.根据得到的信息,进行判断。

若符合条件自动调用浏览器(前提是chrome加入环境变量,或者改代码将浏览器.exe路径加入代码,修改程序)打开产品订购页面。
4.其实也就解决了一个问题:不用自己频繁的刷新网页了,不用自己查看;
登陆,提交订单还得浏览器解决(貌似这几个功能点比较复杂,没做处理)

程序做的不太完善:
运行前需要修改几个地方:
1.环境变量:chrome 加入浏览器变量,以便调用。。或者自己修改源代码用其它方式打开。
2.活动中每个产品的价格信息需要设置。这个比较不好,必须修改源代码。
修改的地方在filter()这个函数里。
3.另外一个需要修改的地方是
hasStore(String skuidkey)

address="http://price.360buy.com/stocksoa/StockHandler.ashx?callback=getProvinceStockCallback&type=pcastock&skuid="+skuidkey+"&provinceid=1&cityid=2800&areaid=2850";
这个地方的cityid=2800&areaid=...地里位置信息。这个没做处理。需要从手机产品页自己搞出来。
其实也比较简单。chrome+F12 ,修改“城市”,区域等信息后,会看到一个get请求发送到后台,这个链接里面包含了需要的信息。(http://price.360buy.com/stocksoa/StockHandler.ashx?callback=getProvinceStockCallback&type=pcastock&skuid=64EBD0F20F593D95C72C6EED59B64658&provinceid=1&cityid=2805&areaid=2854)适当修改。

Util.java

复制代码 代码如下:

package view.Util;

import java.util.ArrayList;

public class Util {
public static void print(Object o){
System.out.print(o);
}
public static void println(Object o){
if(null==o)
System.out.println();
else
System.out.println(o);
}
public static ArrayList<Integer> toArrayList(int[] ints){
if(ints.length==0)
return null;
ArrayList<Integer> al=new ArrayList<Integer>();
for(int i=0;i<ints.length;i++){
al.add(ints[i]);
}
return al;
}
}

Miaosha360buy.java

复制代码 代码如下:

package jingdong;

public class Miaosha360buy {
java.util.concurrent.CountDownLatch t= new java.util.concurrent.CountDownLatch(1);

/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println(Thread.currentThread().getName() + "开始");
Miaosha360buy ms360=new Miaosha360buy();
new ThreadOne360buy(ms360.t).start();
while(true){
try {
ms360.t.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
Thread.sleep(1000*60);//间隔1分钟调用一次?
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
ms360.t=new java.util.concurrent.CountDownLatch(1);
new ThreadOne360buy(ms360.t).start();
System.out.println("New Tread in while..");
}
}

}

Miaosha360buy.java

复制代码 代码如下:

package jingdong;

public class Miaosha360buy {
java.util.concurrent.CountDownLatch t= new java.util.concurrent.CountDownLatch(1);

/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println(Thread.currentThread().getName() + "开始");
Miaosha360buy ms360=new Miaosha360buy();
new ThreadOne360buy(ms360.t).start();
while(true){
try {
ms360.t.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
Thread.sleep(1000*60);//间隔1分钟调用一次?
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
ms360.t=new java.util.concurrent.CountDownLatch(1);
new ThreadOne360buy(ms360.t).start();
System.out.println("New Tread in while..");
}
}

}

ThreadOne360buy.java

复制代码 代码如下:

package jingdong;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import view.Util.Util;

public class ThreadOne360buy extends Thread{
java.util.concurrent.CountDownLatch c;
ArrayList al;//记录秒杀产品页面
float price=0.0f;//商品价格
float discount=0.0f;//商品折扣

//用于保存线程信息,在这个项目里用处不大
private static List<Thread> runningThreads = new ArrayList<Thread>();

//这个是一个计数器(不太会用,线程这方面一直感觉是比较复杂的)
public ThreadOne360buy(java.util.concurrent.CountDownLatch c) {
this.c=c;
}

@Override
public void run() {
regist(this);// 线程开始时注册
// 打印开始标记
System.out.println(Thread.currentThread().getName() + "开始...");
try {
//抓取京东手机秒杀页面
this.getMessage("http://sale.360buy.com/act/8VTHFGr10CjMDyZ.html#01");

} catch (ClientProtocolException e) {
// TODO Auto-generated catch block
e.printStackTrace();
c.countDown();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
c.countDown();
}
c.countDown();
unRegist(this);// 线程结束时取消注册
// 打印结束标记
System.out.println(Thread.currentThread().getName() + "结束.");
}

public void regist(Thread t) {
synchronized (runningThreads) {
runningThreads.add(t);
}
}

public void unRegist(Thread t) {
synchronized (runningThreads) {
runningThreads.remove(t);
}
}

public static boolean hasThreadRunning() {
// 通过判断runningThreads是否为空就能知道是否还有线程未执行完
return (runningThreads.size() > 0);
}
/**
* 从手机秒杀页面获取 prodcut链接,product skuid,skuidkey,price,store信息
* @param url:手机秒杀页面
* @throws ClientProtocolException
* @throws IOException
*/
public void getMessage(String url) throws ClientProtocolException, IOException{
al=getMainUrl(down(url));

Util.println(al);
if(al.size()==0){
c.countDown();
System.exit(0);
return;
}

for(int i=0;i<al.size();i++){
StringBuffer sb=new StringBuffer();
StringBuffer openUrl = new StringBuffer();
openUrl.append("http://www.360buy.com/product/");
openUrl.append(al.get(i).toString().subSequence(al.get(i).toString().lastIndexOf(‘/‘)+1, al.get(i).toString().lastIndexOf(‘.‘)));
openUrl.append(".html");
// 557673
sb.append("http://d.360buy.com/fittingInfo/get?skuId=");
sb.append(al.get(i).toString().subSequence(al.get(i).toString().lastIndexOf(‘/‘)+1, al.get(i).toString().lastIndexOf(‘.‘)));
sb.append("&callback=Recommend.cbRecoFittings");
Util.println(sb.toString());
//map中保存的是产品name,price,折扣信息
Util.println("Al("+i+") down:"+sb.toString());
HashMap<String, String> hm=parseProduct(down(sb.toString()));
//用来匹配价格信息。匹配库存信息
filter(hm,openUrl.toString());//过滤价格,如果条件符合就打开浏览器
}
}
/**
* 一个验证方法
* @param hm 保存着价格信息
* @param url 产品页面
*/
public void filter(HashMap<String, String> hm,String url){//url既是产品页面
// view.Util.oenCMD.openWinExe(null,url);
// 是不是应该先查看库存?
String skuidkey=parseSkuidkey(url);
if(!hasStore(skuidkey)){
Util.println("-------------------------------------");
Util.println("没有库存了!");
Util.println("-------------------------------------");
//减掉计数,以便主线程判断
c.countDown();
//应该结束子线程哦?
return;
}

if(hm.get("skuid").equals("201602")){//判断//摩托罗拉skuid=201602
//这里的价格是写死了,运行前得改过来才行。
this.setPrice(499.0f);
//是不是应该打开控制台?
if(Float.parseFloat(hm.get("price"))<=this.getPrice()){
view.Util.oenCMD.openWinExe(null,url);
}
}else if(hm.get("skuid").equals("675647")){//天语skuid=675647
// //这里的价格是写死了,运行前得改过来才行。
// this.setPrice(699.0f);
// //是不是应该打开控制台?
// if(Float.parseFloat(hm.get("price"))<=this.getPrice()){
// view.Util.oenCMD.openWinExe(null,url);
// }
}

}
/**
* 解析了产品页面中name,skuid,price信息
* @param doc
* @return
*/
public static HashMap<String, String> parseProduct(Document doc){
String text=doc.text();
String docc=text.substring(text.indexOf("master")+9,text.indexOf("fittings")-3).replaceAll("[\\s]", "");
String[] ss=docc.split(",");
HashMap<String, String> hm=new HashMap<String, String>();
for(String it: ss){
String string=it.replaceAll("\"", "");
if(string.contains("\\u"))
string=unicodeDecode(string);

String[] str=string.split(":");
hm.put(str[0], str[1]);
}
Util.println(hm);
return hm;
}
/**
* 处理unicode字符,转换成显示字符(汉字),不太通用
* @param it: \u6a5d
* @return
*/
public static String unicodeDecode(String it){//有个缺点,就是前面的字符无法去掉
Util.println(it);
String regex="(\\\\u[0-9a-f]{4})";
Pattern pt= Pattern.compile(regex);
Matcher mc;
StringBuffer sb;
StringBuffer sba=new StringBuffer();
mc=pt.matcher(it);
while(mc.find()){
sb=new StringBuffer();
mc.appendReplacement(sba,sb.append((char )Integer.parseInt((mc.group(1).substring(2)), 16)).toString());
}
return sba.toString();
}
/**
* 返回文档对象(下载内容)
* @param url 下载页面
* @return
* @throws ClientProtocolException
* @throws IOException
*/
public static Document down(String url) throws ClientProtocolException, IOException{
Document doc = null;
DefaultHttpClient httpClient=new DefaultHttpClient();
Util.println("DownLoad:"+url);
HttpGet get=new HttpGet(url);
HttpResponse response;
response = httpClient.execute(get);
HttpEntity entity = response.getEntity();
doc = Jsoup.parse(entity.getContent(), "utf-8","");
//释放资源
EntityUtils.consume(entity);
//关闭连接
httpClient.getConnectionManager().shutdown();
return doc;
}
/**
* 加入了编码控制信息
* @param url 待下载页面
* @param code 编码
* @return
* @throws ClientProtocolException
* @throws IOException
*/
public static Document down(String url,String code) throws ClientProtocolException, IOException{
Document doc = null;
DefaultHttpClient httpClient=new DefaultHttpClient();
Util.println("DownLoad:"+url);
HttpGet get=new HttpGet(url);
HttpResponse response;
response = httpClient.execute(get);
HttpEntity entity = response.getEntity();
doc = Jsoup.parse(entity.getContent(), code,"");
//释放资源
EntityUtils.consume(entity);
//关闭连接
httpClient.getConnectionManager().shutdown();
return doc;
}
/**
* 用来解析 秒杀页面中的产品(收集)链接
* @param doc
* @return
*/
public static ArrayList<String> getMainUrl(Document doc){
if(doc.equals("")||doc==null)
return null;
try {
Thread.sleep(50);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
ArrayList<String> urls=new ArrayList<String>();
String rule="map[name=Map] >area[href~=product]";
/**
* 开始解析
*/
Elements elements=doc.select(rule);
for (Element e : elements) {
// Util.println(e.absUrl("abs:href"));
urls.add(e.absUrl("abs:href"));
}
return urls;
}
/**
* 获取skuidkey,用于查询商品库存信息
* @param url
* @return
*/
public static String parseSkuidkey(String url){
Document doc=null;
try {
doc=down(url,"gb2312");
} catch (ClientProtocolException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// Util.println(doc.select("script"));
String text = null;
for(Element e : doc.select("script")){
if(e.data().contains("skuidkey:")){
text=e.data();
break;
}
}
//skuidkey:‘7D45919EA8242511DAA5CC7C6D7B351C‘
text=text.substring(text.indexOf("skuidkey:")+10, text.indexOf("skuidkey:")+42);
Util.println("---------------------------------");
Util.println(text);
return text;
}
/**
* 查看库存信息
* @param skuidkey
* @return
*/
public static boolean hasStore(String skuidkey){//这个地方没有处理,直接提取浏览器中的信息
String address = null;
boolean hasStore=false;
if(skuidkey!=null && !"".equals(skuidkey))
address="http://price.360buy.com/stocksoa/StockHandler.ashx?callback=getProvinceStockCallback&type=pcastock&skuid="+skuidkey+"&provinceid=1&cityid=2800&areaid=2850";
else{
Util.println("解析skuidkey错误");
}
try {
if(parseStore(down(address))){
hasStore=true;
}
} catch (ClientProtocolException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return hasStore;
}
/* if(array[1]=="34"||array[1]=="18"){
changeCart(false);
djdarea.stockInfoDom.html("<strong class=‘store-over‘>无货</strong>");
}
else if(array[1]=="0"){
changeCart(false);
djdarea.stockInfoDom.html("<strong class=‘store-over‘>无货</strong>");
}
else if(array[2]=="0"&&array[4]!="2"){
changeCart(false);
djdarea.stockInfoDom.html("很抱歉,该商品无法送至您选择的区域");
}
else if(array[1]=="33"||array[1]=="5"){
changeCart(true);
djdarea.stockInfoDom.html("<strong>现货</strong>"+(array[4]=="1"?",该区域"+(array[3]=="0"?"不":"")+"支持货到付款":"")+cashdesc);
}
else if(array[1]=="36"){
changeCart(true);
djdarea.stockInfoDom.html("<strong>预订</strong>"+(array[4]=="1"?",该区域"+(array[3]=="0"?"不":"")+"支持货到付款":"")+cashdesc);
}
else if(array[1]=="39"){
changeCart(true);
djdarea.stockInfoDom.html("<strong>在途</strong>"+(array[4]=="1"?",该区域"+(array[3]=="0"?"不":"")+"支持货到付款":"")+cashdesc);
}
else if(array[1]=="40"){
changeCart(true);
djdarea.stockInfoDom.html("<strong>可配货</strong>"+(array[4]=="1"?",该区域"+(array[3]=="0"?"不":"")+"支持货到付款":"")+cashdesc);
}
*/
/**
* 解析库存信息
* @param doc
* @return
*/
public static boolean parseStore(Document doc){
String text=doc.text();
String docc=text.substring(text.indexOf("-")-1,text.lastIndexOf(",")-1);
Util.println(docc);
String[] store=docc.split("-");
if(store[1].equals("34") || store[1].equals("18")){
//无货
Util.println("此地无货");
return false;
}else if(store[1].equals("33") || store[1].equals("5")){
//现货
Util.println("此地现货");
return true;
}
Util.println(store[1]);
return false;
}
//几个bean方法
public float getPrice() {
return price;
}
public void setPrice(float price) {
this.price = price;
}
public float getDiscount() {
return discount;
}
public void setDiscount(float discount) {
this.discount = discount;
}

}

oenCMD.java

复制代码 代码如下:

package view.Util;

public class oenCMD {
// public static void main(String[] args) {
//// openWinExe(null);
// openExe(null,"http://www.baidu.com");
// }
//用 Java 调用windows系统的exe文件,比如notepad,calc之类
public static void openWinExe(String command,String url) {
if(command==null ||command.equals("")){
command = "chrome "+url;
}
Runtime rn = Runtime.getRuntime();
Process p = null;
try {

p = rn.exec(command);
} catch (Exception e) {
System.out.println("Error win exec!");
}
}
//调用其他的可执行文件,例如:自己制作的exe,或是 下载 安装的软件.
public static void openExe(String pathAndName,String url) {
if(pathAndName==null || pathAndName.equals("")){
pathAndName="C:\\Users\\Administrator\\AppData\\Local\\Google\\Chrome\\Application\\chrome.exe";
}
if(url!=null && !url.equals("")){
pathAndName+=" ";
pathAndName+=url;
}
Runtime rn = Runtime.getRuntime();
Process p = null;
try {
p = rn.exec(pathAndName);
} catch (Exception e) {
System.out.println("Error exec!");
}
}
}

电商秒杀

标签:des   style   blog   http   color   io   os   使用   java   

原文地址:http://my.oschina.net/u/1398304/blog/314101

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!