From 0d820c8bb3a50966f92792388f520d0f0685b624 Mon Sep 17 00:00:00 2001 From: "w1999wtw3537@sina.com" Date: Wed, 8 Jul 2020 22:13:26 +0800 Subject: [PATCH 01/27] =?UTF-8?q?=E5=BC=80=E5=8F=91=E7=8E=AF=E5=A2=83?= =?UTF-8?q?=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- blade-service/blade-desk/src/main/resources/application-dev.yml | 2 +- .../blade-system/src/main/resources/application-dev.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/blade-service/blade-desk/src/main/resources/application-dev.yml b/blade-service/blade-desk/src/main/resources/application-dev.yml index 5596f784..77fcf659 100644 --- a/blade-service/blade-desk/src/main/resources/application-dev.yml +++ b/blade-service/blade-desk/src/main/resources/application-dev.yml @@ -7,4 +7,4 @@ spring: datasource: url: ${blade.datasource.dev.url} username: ${blade.datasource.dev.username} - password: ${blade.datasource.dev.password} \ No newline at end of file + password: ${blade.datasource.dev.password} diff --git a/blade-service/blade-system/src/main/resources/application-dev.yml b/blade-service/blade-system/src/main/resources/application-dev.yml index 216bd198..e5ef8f2d 100644 --- a/blade-service/blade-system/src/main/resources/application-dev.yml +++ b/blade-service/blade-system/src/main/resources/application-dev.yml @@ -7,4 +7,4 @@ spring: datasource: url: ${blade.datasource.dev.url} username: ${blade.datasource.dev.username} - password: ${blade.datasource.dev.password} \ No newline at end of file + password: ${blade.datasource.dev.password} -- Gitee From 7cf9d72e6a9394014cbd83a5aa55ed523bc1bb67 Mon Sep 17 00:00:00 2001 From: "w1999wtw3537@sina.com" Date: Wed, 8 Jul 2020 22:42:46 +0800 Subject: [PATCH 02/27] =?UTF-8?q?=E6=95=B0=E6=8D=AE=E6=94=B6=E9=9B=86?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=E5=88=9D=E5=A7=8B=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- blade-service-api/blade-spider-api/pom.xml | 17 ++++++ blade-service-api/pom.xml | 1 + blade-service/blade-spider/pom.xml | 59 +++++++++++++++++++ .../main/java/org/springblade/spider/ss.txt | 0 .../test/java/org/springblade/AppTest.java | 20 +++++++ blade-service/pom.xml | 1 + 6 files changed, 98 insertions(+) create mode 100644 blade-service-api/blade-spider-api/pom.xml create mode 100644 blade-service/blade-spider/pom.xml create mode 100644 blade-service/blade-spider/src/main/java/org/springblade/spider/ss.txt create mode 100644 blade-service/blade-spider/src/test/java/org/springblade/AppTest.java diff --git a/blade-service-api/blade-spider-api/pom.xml b/blade-service-api/blade-spider-api/pom.xml new file mode 100644 index 00000000..6c079d40 --- /dev/null +++ b/blade-service-api/blade-spider-api/pom.xml @@ -0,0 +1,17 @@ + + + + + blade-service-api + org.springblade + 2.7.1 + + 4.0.0 + + blade-spider-api + ${project.artifactId} + ${blade.project.version} + jar + + diff --git a/blade-service-api/pom.xml b/blade-service-api/pom.xml index e4feba06..2ce8665a 100644 --- a/blade-service-api/pom.xml +++ b/blade-service-api/pom.xml @@ -21,6 +21,7 @@ blade-system-api blade-user-api blade-demo-api + blade-spider-api diff --git a/blade-service/blade-spider/pom.xml b/blade-service/blade-spider/pom.xml new file mode 100644 index 00000000..cf40f908 --- /dev/null +++ b/blade-service/blade-spider/pom.xml @@ -0,0 +1,59 @@ + + + + + blade-service + org.springblade + 2.7.1 + + 4.0.0 + + blade-spider + ${project.artifactId} + ${blade.project.version} + jar + + + + org.springblade + blade-core-boot + ${blade.tool.version} + + + org.springblade + blade-spider-api + ${blade.project.version} + + + com.yishuifengxiao.common + crawler + 2.2.0 + + + + + + + org.apache.maven.plugins + maven-antrun-plugin + + + package + + run + + + + + + + + + + + + + diff --git a/blade-service/blade-spider/src/main/java/org/springblade/spider/ss.txt b/blade-service/blade-spider/src/main/java/org/springblade/spider/ss.txt new file mode 100644 index 00000000..e69de29b diff --git a/blade-service/blade-spider/src/test/java/org/springblade/AppTest.java b/blade-service/blade-spider/src/test/java/org/springblade/AppTest.java new file mode 100644 index 00000000..14654f21 --- /dev/null +++ b/blade-service/blade-spider/src/test/java/org/springblade/AppTest.java @@ -0,0 +1,20 @@ +//package org.springblade; +// +//import static org.junit.Assert.assertTrue; +// +//import org.junit.Test; +// +///** +// * Unit test for simple App. +// */ +//public class AppTest +//{ +// /** +// * Rigorous Test :-) +// */ +// @Test +// public void shouldAnswerWithTrue() +// { +// assertTrue( true ); +// } +//} diff --git a/blade-service/pom.xml b/blade-service/pom.xml index 609d4a39..8dad52f0 100644 --- a/blade-service/pom.xml +++ b/blade-service/pom.xml @@ -22,6 +22,7 @@ blade-system blade-user blade-demo + blade-spider -- Gitee From 4df7b16d1edc50af66786a619543adbbb55d5e94 Mon Sep 17 00:00:00 2001 From: kndopensource Date: Wed, 15 Jul 2020 11:13:41 +0800 Subject: [PATCH 03/27] =?UTF-8?q?=E5=8A=A0=E5=85=A5=E7=88=AC=E8=99=AB?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- blade-service/blade-spider/pom.xml | 11 + .../springblade/spider/SpiderApplication.java | 22 ++ .../main/java/org/springblade/spider/ss.txt | 0 .../springblade/spider/vo/SpiderPipeline.java | 22 ++ .../src/main/resources/application-dev.yml | 10 + .../src/main/resources/application-prod.yml | 10 + .../src/main/resources/application-test.yml | 10 + .../test/java/org/springblade/AppTest.java | 20 -- .../springblade/spider/CssExtractTest.java | 266 ++++++++++++++++++ .../org/springblade/spider/RegexTest.java | 32 +++ .../spider/RemoveQueryParamsTest.java | 17 ++ .../springblade/spider/SpiderDemoTest.java | 91 ++++++ .../springblade/spider/SpiderDoubanTest.java | 102 +++++++ .../org/springblade/spider/XpathTest.java | 37 +++ 14 files changed, 630 insertions(+), 20 deletions(-) create mode 100644 blade-service/blade-spider/src/main/java/org/springblade/spider/SpiderApplication.java delete mode 100644 blade-service/blade-spider/src/main/java/org/springblade/spider/ss.txt create mode 100644 blade-service/blade-spider/src/main/java/org/springblade/spider/vo/SpiderPipeline.java create mode 100644 blade-service/blade-spider/src/main/resources/application-dev.yml create mode 100644 blade-service/blade-spider/src/main/resources/application-prod.yml create mode 100644 blade-service/blade-spider/src/main/resources/application-test.yml delete mode 100644 blade-service/blade-spider/src/test/java/org/springblade/AppTest.java create mode 100644 blade-service/blade-spider/src/test/java/org/springblade/spider/CssExtractTest.java create mode 100644 blade-service/blade-spider/src/test/java/org/springblade/spider/RegexTest.java create mode 100644 blade-service/blade-spider/src/test/java/org/springblade/spider/RemoveQueryParamsTest.java create mode 100644 blade-service/blade-spider/src/test/java/org/springblade/spider/SpiderDemoTest.java create mode 100644 blade-service/blade-spider/src/test/java/org/springblade/spider/SpiderDoubanTest.java create mode 100644 blade-service/blade-spider/src/test/java/org/springblade/spider/XpathTest.java diff --git a/blade-service/blade-spider/pom.xml b/blade-service/blade-spider/pom.xml index cf40f908..42931e28 100644 --- a/blade-service/blade-spider/pom.xml +++ b/blade-service/blade-spider/pom.xml @@ -25,11 +25,22 @@ blade-spider-api ${blade.project.version} + com.yishuifengxiao.common crawler 2.2.0 + + + + + + + + org.springframework.boot + spring-boot-starter-test + diff --git a/blade-service/blade-spider/src/main/java/org/springblade/spider/SpiderApplication.java b/blade-service/blade-spider/src/main/java/org/springblade/spider/SpiderApplication.java new file mode 100644 index 00000000..7951f68d --- /dev/null +++ b/blade-service/blade-spider/src/main/java/org/springblade/spider/SpiderApplication.java @@ -0,0 +1,22 @@ +package org.springblade.spider; + +import org.springblade.core.launch.BladeApplication; +import org.springblade.core.launch.constant.AppConstant; +import org.springframework.cloud.client.SpringCloudApplication; +import org.springframework.cloud.openfeign.EnableFeignClients; + +/** + * Spider启动器 + * + * @author Chill + */ +@SpringCloudApplication +//@EnableFeignClients(AppConstant.BASE_PACKAGES) +public class SpiderApplication { + + public static void main(String[] args) { + BladeApplication.run(AppConstant.APPLICATION_DESK_NAME, SpiderApplication.class, args); + } + +} + diff --git a/blade-service/blade-spider/src/main/java/org/springblade/spider/ss.txt b/blade-service/blade-spider/src/main/java/org/springblade/spider/ss.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/blade-service/blade-spider/src/main/java/org/springblade/spider/vo/SpiderPipeline.java b/blade-service/blade-spider/src/main/java/org/springblade/spider/vo/SpiderPipeline.java new file mode 100644 index 00000000..f9247a87 --- /dev/null +++ b/blade-service/blade-spider/src/main/java/org/springblade/spider/vo/SpiderPipeline.java @@ -0,0 +1,22 @@ +package org.springblade.spider.vo; + +import com.yishuifengxiao.common.crawler.domain.entity.CrawlerData; +import com.yishuifengxiao.common.crawler.pipeline.Pipeline; +import lombok.extern.slf4j.Slf4j; + +/** + * + */ +@Slf4j +public class SpiderPipeline implements Pipeline { + + @Override + public void recieve(CrawlerData crawlerData) { + + log.debug("\r\n"); + log.debug("The output is "); + log.info("request : {} , out data : {}", crawlerData.getRedirectUrl(), crawlerData.getAllData()); + log.debug("\r\n"); + } + +} diff --git a/blade-service/blade-spider/src/main/resources/application-dev.yml b/blade-service/blade-spider/src/main/resources/application-dev.yml new file mode 100644 index 00000000..ef8eb13e --- /dev/null +++ b/blade-service/blade-spider/src/main/resources/application-dev.yml @@ -0,0 +1,10 @@ +#服务器端口 +server: + port: 8109 + +#数据源配置 +spring: + datasource: + url: ${blade.datasource.dev.url} + username: ${blade.datasource.dev.username} + password: ${blade.datasource.dev.password} diff --git a/blade-service/blade-spider/src/main/resources/application-prod.yml b/blade-service/blade-spider/src/main/resources/application-prod.yml new file mode 100644 index 00000000..25635bc4 --- /dev/null +++ b/blade-service/blade-spider/src/main/resources/application-prod.yml @@ -0,0 +1,10 @@ +#服务器端口 +server: + port: 8106 + +#数据源配置 +spring: + datasource: + url: ${blade.datasource.prod.url} + username: ${blade.datasource.prod.username} + password: ${blade.datasource.prod.password} diff --git a/blade-service/blade-spider/src/main/resources/application-test.yml b/blade-service/blade-spider/src/main/resources/application-test.yml new file mode 100644 index 00000000..fb5cd8f7 --- /dev/null +++ b/blade-service/blade-spider/src/main/resources/application-test.yml @@ -0,0 +1,10 @@ +#服务器端口 +server: + port: 8106 + +#数据源配置 +spring: + datasource: + url: ${blade.datasource.test.url} + username: ${blade.datasource.test.username} + password: ${blade.datasource.test.password} diff --git a/blade-service/blade-spider/src/test/java/org/springblade/AppTest.java b/blade-service/blade-spider/src/test/java/org/springblade/AppTest.java deleted file mode 100644 index 14654f21..00000000 --- a/blade-service/blade-spider/src/test/java/org/springblade/AppTest.java +++ /dev/null @@ -1,20 +0,0 @@ -//package org.springblade; -// -//import static org.junit.Assert.assertTrue; -// -//import org.junit.Test; -// -///** -// * Unit test for simple App. -// */ -//public class AppTest -//{ -// /** -// * Rigorous Test :-) -// */ -// @Test -// public void shouldAnswerWithTrue() -// { -// assertTrue( true ); -// } -//} diff --git a/blade-service/blade-spider/src/test/java/org/springblade/spider/CssExtractTest.java b/blade-service/blade-spider/src/test/java/org/springblade/spider/CssExtractTest.java new file mode 100644 index 00000000..56fe0bc9 --- /dev/null +++ b/blade-service/blade-spider/src/test/java/org/springblade/spider/CssExtractTest.java @@ -0,0 +1,266 @@ +package org.springblade.spider; + +import com.yishuifengxiao.common.crawler.extractor.content.strategy.impl.CssStrategy; +import com.yishuifengxiao.common.crawler.extractor.content.strategy.impl.CssTextStrategy; +import org.junit.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +public class CssExtractTest { + private CssStrategy css = new CssStrategy(); + + private CssTextStrategy ct = new CssTextStrategy(); + private static String RULE1_PARAM1 = "p.post-meta"; + private static String RULE1_PARAM2 = "style"; + + private static String RULE2_PARAM1 = "h2.post-title"; + private static String RULE2_PARAM2 = ""; + + @Test + public void testExtract() { + + String out = css.extract(HTML, RULE1_PARAM1, RULE1_PARAM2); + System.out.println("=============================> \r\n"); + System.out.println(out); + } + + + @Test + public void testCssTextStrategy() { + + String out = ct.extract(HTML, RULE2_PARAM1, RULE2_PARAM2); + System.out.println("=============================> \r\n"); + System.out.println(out); + } + + private final static String HTML = "
\r\n" + " \r\n" + "\r\n" + "\r\n" + + " \r\n" + "
\r\n" + + " \r\n" + + " \r\n" + "\r\n" + "\r\n" + "\r\n" + "
\r\n" + "\r\n" + + "\r\n" + "
\r\n" + "\r\n" + + "\r\n" + "
\r\n" + "\r\n" + + "\r\n" + "
\r\n" + "\r\n" + + "\r\n" + "
\r\n" + "\r\n" + + "\r\n" + "
\r\n" + "\r\n" + + "\r\n" + "
\r\n" + "\r\n" + + "\r\n" + "
\r\n" + "\r\n" + + "\r\n" + "
\r\n" + "\r\n" + + "\r\n" + "
\r\n" + "\r\n" + + "\r\n" + "\r\n" + "\r\n" + "\r\n" + "
    \r\n" + " \r\n" + " \r\n" + + "
  • \r\n" + " Older Posts →\r\n" + + "
  • \r\n" + " \r\n" + "
\r\n" + "\r\n" + "\r\n" + "\r\n" + + " \r\n" + " \r\n" + "
\r\n" + "\r\n" + + " \r\n" + "
\r\n" + " \r\n" + " \r\n" + "\r\n" + + "
\r\n" + " \r\n" + "
FEATURED TAGS
\r\n" + + "
\r\n" + " \r\n" + " \r\n" + + " 性能测试\r\n" + " \r\n" + + " \r\n" + " \r\n" + + " linux\r\n" + " \r\n" + + " \r\n" + " \r\n" + + " node.js\r\n" + " \r\n" + + " \r\n" + " \r\n" + + " 入门教程\r\n" + " \r\n" + + " \r\n" + " \r\n" + + " 前端\r\n" + " \r\n" + + " \r\n" + " \r\n" + + " java\r\n" + " \r\n" + + " \r\n" + " \r\n" + " \r\n" + " \r\n" + + " jmeter\r\n" + " \r\n" + + " \r\n" + " \r\n" + " \r\n" + " \r\n" + " \r\n" + + " \r\n" + " \r\n" + " \r\n" + + " docker\r\n" + " \r\n" + + " \r\n" + " \r\n" + " \r\n" + " \r\n" + " \r\n" + + " \r\n" + " \r\n" + " \r\n" + + " ElasticStack\r\n" + + " \r\n" + " \r\n" + " \r\n" + " \r\n" + " \r\n" + + " \r\n" + " \r\n" + " \r\n" + " \r\n" + " \r\n" + + " \r\n" + " \r\n" + " \r\n" + + " spring security\r\n" + + " \r\n" + " \r\n" + " \r\n" + + " 易水组件\r\n" + " \r\n" + + " \r\n" + " \r\n" + " \r\n" + " \r\n" + + " spring cloud\r\n" + + " \r\n" + " \r\n" + " \r\n" + + " 微服务\r\n" + " \r\n" + + " \r\n" + " \r\n" + + " 随笔\r\n" + " \r\n" + + " \r\n" + " \r\n" + " \r\n" + "
\r\n" + "
\r\n" + "\r\n" + + "
\r\n" + " \r\n" + " \r\n" + + "
\r\n" + "
ABOUT ME
\r\n" + + "
\r\n" + "\r\n" + " \r\n" + + " \r\n" + " \r\n" + "\r\n" + + " \r\n" + "

关注我的微信,互相交流

\r\n" + " \r\n" + "\r\n" + + " \r\n" + " \r\n" + + "
\r\n" + "
\r\n" + "
\r\n" + " \r\n" + " \r\n" + + "
RECENT POSTS\r\n" + "
\r\n" + " \r\n" + "
\r\n" + "
\r\n" + "
\r\n" + " \r\n" + + " \r\n" + "\r\n" + "
FRIENDS
\r\n" + "\r\n" + "\r\n" + "
\r\n" + " \r\n" + "
\r\n" + "\r\n" + + " \r\n" + "
"; + + +} diff --git a/blade-service/blade-spider/src/test/java/org/springblade/spider/RegexTest.java b/blade-service/blade-spider/src/test/java/org/springblade/spider/RegexTest.java new file mode 100644 index 00000000..984868b4 --- /dev/null +++ b/blade-service/blade-spider/src/test/java/org/springblade/spider/RegexTest.java @@ -0,0 +1,32 @@ +package org.springblade.spider; + +import com.yishuifengxiao.common.crawler.utils.RegexFactory; +import org.junit.Test; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.List; + +/** + * 测试正则提取工具 + */ +@SpringBootTest +public class RegexTest { + @Test + public void extractAll() { + + String regex = "[0-9]+"; + String content = "阅读数 30"; + List str = RegexFactory.extractAll(regex, content); + System.out.println(str); + } + + @Test + public void extract() { + + String regex = "[0-9]+"; + String content = " 阅读数 30"; + String str = RegexFactory.extract(regex, content); + System.out.println(str); + } + +} diff --git a/blade-service/blade-spider/src/test/java/org/springblade/spider/RemoveQueryParamsTest.java b/blade-service/blade-spider/src/test/java/org/springblade/spider/RemoveQueryParamsTest.java new file mode 100644 index 00000000..1574efc3 --- /dev/null +++ b/blade-service/blade-spider/src/test/java/org/springblade/spider/RemoveQueryParamsTest.java @@ -0,0 +1,17 @@ +package org.springblade.spider; + +import org.apache.commons.lang3.StringUtils; +import org.junit.Test; + +/** + * 测试去除URL中的查询参数 + */ +public class RemoveQueryParamsTest { + + @Test + public void removeQueryParamsTest(){ + String str = "https://fanyi.baidu.com/?aldtype=16047#zh/en/%E7%A7%BB%E9%99%A4%E8%AF%B7%E6%B1%82%E5%8F%82%E6%95%B0"; + System.out.println(StringUtils.indexOf(str, "?")); + System.out.println(StringUtils.substringBefore(str, "?")); + } +} diff --git a/blade-service/blade-spider/src/test/java/org/springblade/spider/SpiderDemoTest.java b/blade-service/blade-spider/src/test/java/org/springblade/spider/SpiderDemoTest.java new file mode 100644 index 00000000..375a6dad --- /dev/null +++ b/blade-service/blade-spider/src/test/java/org/springblade/spider/SpiderDemoTest.java @@ -0,0 +1,91 @@ +package org.springblade.spider; + +import com.yishuifengxiao.common.crawler.Crawler; +import com.yishuifengxiao.common.crawler.CrawlerBuilder; +import com.yishuifengxiao.common.crawler.domain.eunm.Pattern; +import com.yishuifengxiao.common.crawler.domain.eunm.Rule; +import com.yishuifengxiao.common.crawler.domain.eunm.Statu; +import com.yishuifengxiao.common.crawler.domain.model.ExtractFieldRule; +import com.yishuifengxiao.common.crawler.domain.model.ExtractRule; +import com.yishuifengxiao.common.crawler.domain.model.MatcherRule; +import com.yishuifengxiao.common.crawler.utils.RegexFactory; +import org.apache.commons.lang3.StringUtils; +import org.junit.Test; +import org.seimicrawler.xpath.JXDocument; +import org.springblade.spider.vo.SpiderPipeline; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.Arrays; +import java.util.List; + +/** + * SpiderDemo单元测试 + * + * @author Chill + */ +//@RunWith(SpringBootTest.class) +@SpringBootTest +//@BladeBootTest(appName = "blade-spider", profile = "dev", enableLoader = true) +public class SpiderDemoTest { + +// @Autowired +// private INoticeService noticeService; + + @Test + public void spiderYahooTest() { + + // 创建一个提取属性规则 + // 该提取规则标识使用 XPATH提取器进行提取, + // 该XPATH提取器的XPATH表达式为 //h1/text() , 该提取提取器的作用顺序是0 + ExtractFieldRule extractFieldRule = new ExtractFieldRule(Rule.XPATH, "//h1/text()", "", 0); + + // 创建一个提取项 + ExtractRule extractRule = new ExtractRule(); + extractRule + // 提取项代码,不能为空,同一组提取规则之内每一个提取项的编码必须唯一 + .setCode("code") + // 提取项名字,可以不设置 + .setName("加密电子货币名字") + // 设置提取属性规则 + .setRules(Arrays.asList(extractFieldRule)); + + // 创建一个风铃虫实例 + Crawler crawler = CrawlerBuilder.create() + // 风铃虫的起始链接 + .startUrl("https://hk.finance.yahoo.com/cryptocurrencies") + // 风铃虫会将每次请求的网页的内容中的URL先全部提取出来,然后将完全匹配此规则的链接放入链接池 + // 如果不设置则表示提取链接中所有包含域名关键字(例如此例中的yahoo)的链接放入链接池 + // 链接池里的链接会作为下次抓取请求的种子链接 + // 链接提取规则的作用是风铃虫根据此规则从下载的网页里提取出符合此规则的链接,然后将链接放入链接池 + // 链接提取规则,多以添加多个链接提取规则, + .addLinkRule(new MatcherRule(Pattern.REGEX, "https://hk.finance.yahoo.com/quote/.+")) + // 可以设置多个内容页的规则 + // 只要内容页URL中完全匹配此规则就进行内容提取,如果不设置标识提取域名下所有的链接 + // 内容页规则是告诉风铃虫从哪些网页里提取出信息,因为不是所有的下载网页里都包含有需要的信息 + // 内容页的规则, + .contentPageRule(new MatcherRule(Pattern.REGEX, "https://hk.finance.yahoo.com/quote/.+")) + // 风铃虫可以设置多个提取项,这里为了演示只设置了一个提取项 + // 增加一个提取项规则 + .addExtractRule(extractRule) + // 如果不设置则使用默认时间10秒,此值是为了防止抓取频率太高被服务器封杀 + // 每次进行爬取时的平均间隔时间,单位为毫秒, + .interval(3000) + .creatCrawler(); + // 启动爬虫实例 + crawler.start(); + // 这里没有设置信息输出器,表示使用默认的信息输出器 + // 默认的信息输出器使用的logback日志输出方法,因此需要看控制台信息 + crawler.setPipeline(new SpiderPipeline()); + // 由于风铃虫是异步运行的,所以演示时这里加入循环 + while (Statu.STOP != crawler.getStatu()) { + try { + Thread.sleep(1000 * 20); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + } + + +} diff --git a/blade-service/blade-spider/src/test/java/org/springblade/spider/SpiderDoubanTest.java b/blade-service/blade-spider/src/test/java/org/springblade/spider/SpiderDoubanTest.java new file mode 100644 index 00000000..97473eb4 --- /dev/null +++ b/blade-service/blade-spider/src/test/java/org/springblade/spider/SpiderDoubanTest.java @@ -0,0 +1,102 @@ +package org.springblade.spider; + +import com.yishuifengxiao.common.crawler.Crawler; +import com.yishuifengxiao.common.crawler.CrawlerBuilder; +import com.yishuifengxiao.common.crawler.domain.entity.SimulatorData; +import com.yishuifengxiao.common.crawler.domain.eunm.Pattern; +import com.yishuifengxiao.common.crawler.domain.eunm.Rule; +import com.yishuifengxiao.common.crawler.domain.model.ExtractFieldRule; +import com.yishuifengxiao.common.crawler.domain.model.ExtractRule; +import com.yishuifengxiao.common.crawler.domain.model.MatcherRule; +import com.yishuifengxiao.common.crawler.domain.model.SiteRule; +import org.junit.Test; +import com.yishuifengxiao.common.crawler.domain.eunm.Statu; +import org.springblade.spider.vo.SpiderPipeline; + +import java.util.Arrays; + +public class SpiderDoubanTest { + + @Test + public void spiderDoubanTest() { + + // 创建一个提取属性规则 + // 该提取规则标识使用 XPATH提取器进行提取, + // 该XPATH提取器的XPATH表达式为 //h1/text() , 该提取提取器的作用顺序是0 + ExtractFieldRule moveNameExtractRule = new ExtractFieldRule(Rule.XPATH, "//span[@property='v:itemreviewed']/text()", "", 0); + + // 创建一个提取项 + ExtractRule moveNameContentItem = new ExtractRule(); + moveNameContentItem + // 提取项代码,不能为空,同一组提取规则之内每一个提取项的编码必须唯一 + .setCode("moveName") + // 提取项名字,可以不设置 + .setName("moveName") + // 设置提取属性规则 + .setRules(Arrays.asList(moveNameExtractRule)); + + ExtractFieldRule moveDirectorExtractRule = new ExtractFieldRule(Rule.XPATH, "//div[@id='info']/span/span/a[@rel='v:directedBy']/text()", "", 0); + + + //创建一个提取项rel="v:directedBy" + ExtractRule extractMoveDirectorRule = new ExtractRule(); + extractMoveDirectorRule + .setCode("moveDirector") //提取项代码,不能为空 + .setName("moveDirector") + .setRules(Arrays.asList(moveDirectorExtractRule)); //设置提取规则 + + ExtractFieldRule moveScreenwriterExtractRule = new ExtractFieldRule(Rule.XPATH, "//div[@id='info']/span[contains(@text,'编剧')]]/span/text()", "", 0); + + ExtractRule moveScreenwriterContentItem = new ExtractRule(); + + moveScreenwriterContentItem + .setCode("moveScreenwriter") + .setName("moveScreenwriter") + .setRules(Arrays.asList(moveScreenwriterExtractRule)); //设置提取规则 + + + SimulatorData data = Crawler.testContent("https://movie.douban.com/subject/27186348/", new SiteRule(), moveNameContentItem); + System.out.println(data); + SimulatorData data1 = Crawler.testContent("https://movie.douban.com/subject/27186348/", new SiteRule(), extractMoveDirectorRule); + System.out.println(data1); + SimulatorData data2 = Crawler.testContent("https://movie.douban.com/subject/27186348/", new SiteRule(), moveScreenwriterContentItem); + System.out.println(data2); + + + //创建一个风铃虫实例 + Crawler crawler = CrawlerBuilder.create() + .threadNum(1) + .connectTimeout(3000) + .startUrl("https://movie.douban.com/") //风铃虫的起始链接 + // 风铃虫会将每次请求的网页的内容中的URL先全部提取出来 + // 如果不设置则表示提取链接中所有包含域名关键字(例如此例中的ifeng)的链接放入链接池 + //链接池里的链接会作为下次抓取请求的种子链接 + //多以添加多个链接提取规则, + .addLinkRule(new MatcherRule(Pattern.REGEX, "https://movie\\.douban.*"))//链接提取规则 + //可以设置多个内容页的规则,多个内容页规则之间用半角逗号隔开 + //只要内容页URL中完全匹配此规则就进行内容提取,如果不设置标识提取域名下所有的链接 +// .extractUrl("https://movie.douban.com/subject/[0-9]+/") //内容页的规则, + //风铃虫可以设置多个提取项,这里为了演示只设置了一个提取项 + .addExtractRule(moveNameContentItem) //增加一个提取项 + .addExtractRule(extractMoveDirectorRule) //增加一个提取项 + .addExtractRule(moveScreenwriterContentItem) //增加一个提取项 + + //如果不设置则使用默认时间10秒,此值是为了防止抓取频率太高被服务器封杀 + .interval(30)//每次进行爬取时的平均间隔时间,单位为秒, + .creatCrawler(); + //启动爬虫实例 + crawler.start(); + // 这里没有设置信息输出器,表示使用默认的信息输出器 + //默认的信息输出器使用的logback日志输出方法,因此需要看控制台信息 + crawler.setPipeline(new SpiderPipeline()); + //由于风铃虫时异步运行的,所以演示时这里加入循环 + while (Statu.STOP != crawler.getStatu()) { + try { + Thread.sleep(1000 * 20); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } +} + diff --git a/blade-service/blade-spider/src/test/java/org/springblade/spider/XpathTest.java b/blade-service/blade-spider/src/test/java/org/springblade/spider/XpathTest.java new file mode 100644 index 00000000..9786957b --- /dev/null +++ b/blade-service/blade-spider/src/test/java/org/springblade/spider/XpathTest.java @@ -0,0 +1,37 @@ +package org.springblade.spider; + +import org.junit.Test; +import org.seimicrawler.xpath.JXDocument; + +import java.util.List; + +/** + * XPATH提取测试 + */ +public class XpathTest { + + @Test + public void xpathTest(){ + String html = "" + + "
ab
"; + + JXDocument jxDocument = JXDocument.create(html); + List r1s = jxDocument.sel("//a/@href"); + + for (Object obj : r1s) { + System.out.println(obj); + } + + List r2s = jxDocument.sel("//tr/td"); + + for (Object obj : r2s) { + System.out.println(obj); + } + + List r3s = jxDocument.sel("//tr/td/text()"); + + for (Object obj : r3s) { + System.out.println(obj); + } + } +} -- Gitee From 7edd4ad411fa65305fe7e616676039a78be4bdc8 Mon Sep 17 00:00:00 2001 From: kndopensource Date: Wed, 15 Jul 2020 11:42:05 +0800 Subject: [PATCH 04/27] =?UTF-8?q?=E8=82=A1=E7=A5=A8=E4=B8=9A=E5=8A=A1?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=E5=88=9D=E5=A7=8Bhaul?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- blade-service-api/blade-stock-api/pom.xml | 17 +++++ .../src/main/java/org/springblade/App.java | 13 ++++ blade-service/blade-stock/pom.xml | 63 +++++++++++++++++++ .../springblade/stock/StockApplication.java | 18 ++++++ .../src/main/resources/application-dev.yml | 10 +++ .../src/main/resources/application-prod.yml | 10 +++ .../src/main/resources/application-test.yml | 10 +++ blade-service/pom.xml | 1 + pom.xml | 1 + 9 files changed, 143 insertions(+) create mode 100644 blade-service-api/blade-stock-api/pom.xml create mode 100644 blade-service-api/blade-stock-api/src/main/java/org/springblade/App.java create mode 100644 blade-service/blade-stock/pom.xml create mode 100644 blade-service/blade-stock/src/main/java/org/springblade/stock/StockApplication.java create mode 100644 blade-service/blade-stock/src/main/resources/application-dev.yml create mode 100644 blade-service/blade-stock/src/main/resources/application-prod.yml create mode 100644 blade-service/blade-stock/src/main/resources/application-test.yml diff --git a/blade-service-api/blade-stock-api/pom.xml b/blade-service-api/blade-stock-api/pom.xml new file mode 100644 index 00000000..2f4d7432 --- /dev/null +++ b/blade-service-api/blade-stock-api/pom.xml @@ -0,0 +1,17 @@ + + + + + blade-service-api + org.springblade + 2.7.1 + + 4.0.0 + blade-stock-api + ${project.artifactId} + ${blade.project.version} + jar + + + diff --git a/blade-service-api/blade-stock-api/src/main/java/org/springblade/App.java b/blade-service-api/blade-stock-api/src/main/java/org/springblade/App.java new file mode 100644 index 00000000..86ff693a --- /dev/null +++ b/blade-service-api/blade-stock-api/src/main/java/org/springblade/App.java @@ -0,0 +1,13 @@ +package org.springblade; + +/** + * Hello world! + * + */ +public class App +{ + public static void main( String[] args ) + { + System.out.println( "Hello World!" ); + } +} diff --git a/blade-service/blade-stock/pom.xml b/blade-service/blade-stock/pom.xml new file mode 100644 index 00000000..0ccf5f22 --- /dev/null +++ b/blade-service/blade-stock/pom.xml @@ -0,0 +1,63 @@ + + + + + blade-service + org.springblade + 2.7.1 + + 4.0.0 + blade-stock + ${project.artifactId} + ${blade.project.version} + jar + + + + org.springblade + blade-core-boot + ${blade.tool.version} + + + org.springblade + blade-stock-api + ${blade.project.version} + + + + + + + + + org.springframework.boot + spring-boot-starter-test + + + + + + + org.apache.maven.plugins + maven-antrun-plugin + + + package + + run + + + + + + + + + + + + + diff --git a/blade-service/blade-stock/src/main/java/org/springblade/stock/StockApplication.java b/blade-service/blade-stock/src/main/java/org/springblade/stock/StockApplication.java new file mode 100644 index 00000000..91f89df0 --- /dev/null +++ b/blade-service/blade-stock/src/main/java/org/springblade/stock/StockApplication.java @@ -0,0 +1,18 @@ +package org.springblade.stock; + +import org.springblade.core.launch.BladeApplication; +import org.springblade.core.launch.constant.AppConstant; +import org.springframework.cloud.client.SpringCloudApplication; +import org.springframework.cloud.openfeign.EnableFeignClients; + +/** + * stock启动器 + */ +@SpringCloudApplication +@EnableFeignClients(AppConstant.BASE_PACKAGES) +public class StockApplication { + + public static void main(String[] args) { + BladeApplication.run(AppConstant.APPLICATION_DESK_NAME, StockApplication.class, args); + } +} diff --git a/blade-service/blade-stock/src/main/resources/application-dev.yml b/blade-service/blade-stock/src/main/resources/application-dev.yml new file mode 100644 index 00000000..429dac95 --- /dev/null +++ b/blade-service/blade-stock/src/main/resources/application-dev.yml @@ -0,0 +1,10 @@ +#服务器端口 +server: + port: 8201 + +#数据源配置 +spring: + datasource: + url: ${blade.datasource.dev.url} + username: ${blade.datasource.dev.username} + password: ${blade.datasource.dev.password} diff --git a/blade-service/blade-stock/src/main/resources/application-prod.yml b/blade-service/blade-stock/src/main/resources/application-prod.yml new file mode 100644 index 00000000..361c857e --- /dev/null +++ b/blade-service/blade-stock/src/main/resources/application-prod.yml @@ -0,0 +1,10 @@ +#服务器端口 +server: + port: 8201 + +#数据源配置 +spring: + datasource: + url: ${blade.datasource.prod.url} + username: ${blade.datasource.prod.username} + password: ${blade.datasource.prod.password} diff --git a/blade-service/blade-stock/src/main/resources/application-test.yml b/blade-service/blade-stock/src/main/resources/application-test.yml new file mode 100644 index 00000000..9e9b257d --- /dev/null +++ b/blade-service/blade-stock/src/main/resources/application-test.yml @@ -0,0 +1,10 @@ +#服务器端口 +server: + port: 8201 + +#数据源配置 +spring: + datasource: + url: ${blade.datasource.test.url} + username: ${blade.datasource.test.username} + password: ${blade.datasource.test.password} diff --git a/blade-service/pom.xml b/blade-service/pom.xml index 8dad52f0..ae022c3e 100644 --- a/blade-service/pom.xml +++ b/blade-service/pom.xml @@ -23,6 +23,7 @@ blade-user blade-demo blade-spider + blade-stock diff --git a/pom.xml b/pom.xml index b98d303f..8d6368a6 100644 --- a/pom.xml +++ b/pom.xml @@ -42,6 +42,7 @@ blade-service blade-service-api blade-common + blade-service-api/blade-stock-api -- Gitee From 93340969cd54381bdba64466b7e840b2fc6a7d30 Mon Sep 17 00:00:00 2001 From: kndopensource Date: Wed, 15 Jul 2020 15:35:35 +0800 Subject: [PATCH 05/27] =?UTF-8?q?stock=E6=A8=A1=E5=9D=97=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=BA=93=E8=AE=BE=E8=AE=A1=E6=96=87=E6=A1=A3=E5=88=9D=E7=89=88?= =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../blade-stock-api/doc/pic/sql/sqlDesign.jpg | Bin 0 -> 39193 bytes .../doc/pic/sql/stock\350\241\250.eddx" | Bin 0 -> 13175 bytes ...3\344\275\215\346\257\224\344\276\213.jpg" | Bin 0 -> 72945 bytes blade-service/blade-spider/README.md | 49 +++++++++++++++++ blade-service/blade-stock/README.md | 50 ++++++++++++++++++ 5 files changed, 99 insertions(+) create mode 100644 blade-service-api/blade-stock-api/doc/pic/sql/sqlDesign.jpg create mode 100644 "blade-service-api/blade-stock-api/doc/pic/sql/stock\350\241\250.eddx" create mode 100644 "blade-service-api/blade-stock-api/doc/pic/\350\241\214\346\203\205\344\273\223\344\275\215\346\257\224\344\276\213.jpg" create mode 100644 blade-service/blade-spider/README.md create mode 100644 blade-service/blade-stock/README.md diff --git a/blade-service-api/blade-stock-api/doc/pic/sql/sqlDesign.jpg b/blade-service-api/blade-stock-api/doc/pic/sql/sqlDesign.jpg new file mode 100644 index 0000000000000000000000000000000000000000..24d9afba3d10d7547d0bdaffe44d57f2ab66fde7 GIT binary patch literal 39193 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 M=D+41K70ND0O)sFr~m)} literal 0 HcmV?d00001 diff --git "a/blade-service-api/blade-stock-api/doc/pic/sql/stock\350\241\250.eddx" "b/blade-service-api/blade-stock-api/doc/pic/sql/stock\350\241\250.eddx" new file mode 100644 index 0000000000000000000000000000000000000000..5e9a009850d8f24bb176890670546ed52db83162 GIT binary patch literal 13175 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शMV55BW3dX~Dvr+DN8Xsq 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%0AXJ8MaOH#n}sq(32o=pUq z**0v|jm<1PS|TqeauIDlRzpghI>H`{pXrsZP~Rkn>jwhk2L@bxl`9Wv8`ZlfH;_#m z_WdAx!Dv5f2?2EtMr=&zmOvi?LT3 zmX$bvr1+PFXrQjwq<^}k=Pki{a|EaoO09iJNDUfCcv7MqB9@CqpEmG{AJW=EajfR{ zudkJTO}8#>eq}k%{l*E>mb&jJsT6HHpb$X-y&0?OCim2UvP7*#hZwd_>hegasO`J{ z-7BmOm6xT5!;xx11nkj)Q4FIt5NlSMmf<9w;UO^KN?`&c&vr(`WQ?j;1B*P|%aIc& zJ}?J)mb%w>)E?w$F#*@1I6z0-j@0za=+;da|w zTDd|jw7kuvZs(yuKV&N_Y))Mgd`tLOzsw$uW;2ULd`u|hKf0iTsyNVxw%0vD~ zal-$q1-ya8n=-+%D|G)D8 n-$m2E%b$||mH+o8)IUnL{y`QL?C%4E{n@ksU^m40_wN4z&PCwx literal 0 HcmV?d00001 diff --git "a/blade-service-api/blade-stock-api/doc/pic/\350\241\214\346\203\205\344\273\223\344\275\215\346\257\224\344\276\213.jpg" "b/blade-service-api/blade-stock-api/doc/pic/\350\241\214\346\203\205\344\273\223\344\275\215\346\257\224\344\276\213.jpg" new file mode 100644 index 0000000000000000000000000000000000000000..328339be8f8d7b20af8e41396f66deac9ad3642f GIT binary patch literal 72945 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

G*|#%9*lWy-hD-H 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 EU+7r3X#fBK literal 0 HcmV?d00001 diff --git a/blade-service/blade-spider/README.md b/blade-service/blade-spider/README.md new file mode 100644 index 00000000..362f2a37 --- /dev/null +++ b/blade-service/blade-spider/README.md @@ -0,0 +1,49 @@ +## balde-spider 模块 + 数据爬取整合模块 + 1. 爬取指定的站点、公众号文章 + 2. 爬取固定任务数据 + +## 架构图 + +## 工程结构 +``` +SpringBlade +├── blade-auth -- 授权服务提供 +├── blade-common -- 常用工具封装包 +├── blade-gateway -- Spring Cloud 网关 +├── blade-ops -- 运维中心 +├ ├── blade-admin -- spring-cloud后台管理 +├ ├── blade-develop -- 代码生成 +├ ├── blade-resource -- 资源管理 +├ ├── blade-seata-order -- seata分布式事务demo +├ ├── blade-seata-storage -- seata分布式事务demo +├── blade-service -- 业务模块 +├ ├── blade-desk -- 工作台模块 +├ ├── blade-log -- 日志模块 +├ ├── blade-system -- 系统模块 +├ └── blade-user -- 用户模块 +├── blade-service-api -- 业务模块api封装 +├ ├── blade-desk-api -- 工作台api +├ ├── blade-dict-api -- 字典api +├ ├── blade-system-api -- 系统api +└── └── blade-user-api -- 用户api +``` + +## 官网 + + +## 在线演示 +* Saber-基于Vue:[https://saber.bladex.vip](https://saber.bladex.vip) +* Sword-基于React:[https://sword.bladex.vip](https://sword.bladex.vip) +* Archer-全能代码生成系统:[https://archer.bladex.vip](https://archer.bladex.vip) +* Caster-数据大屏展示系统:[https://data.avuejs.com](https://data.avuejs.com) + +## 技术文档 +* [SpringBlade开发手册一览](https://gitee.com/smallc/SpringBlade/wikis/SpringBlade开发手册) +* [常见问题集锦](https://sns.bladex.vip/article-14966.html) + +## 项目地址 + +# 开源协议 +Apache Licence 2.0 ([英文原文](http://www.apache.org/licenses/LICENSE-2.0.html)) +Apache Licence是著名的非盈利开源组织Apache采用的协议。该协议和BSD类似,同样鼓励代码共享和尊重原作者的著作权,同样允许代码修改,再发布(作为开源或商业软件)。 diff --git a/blade-service/blade-stock/README.md b/blade-service/blade-stock/README.md new file mode 100644 index 00000000..4aaa273c --- /dev/null +++ b/blade-service/blade-stock/README.md @@ -0,0 +1,50 @@ +## balde-stock 模块 + 股票分析、统计模块 + 1. 行情策略筛选出自选 + 2. 自选历史统计 + 3. 操作模拟统计 + +## 架构图 + +## 工程结构 +``` +SpringBlade +├── blade-auth -- 授权服务提供 +├── blade-common -- 常用工具封装包 +├── blade-gateway -- Spring Cloud 网关 +├── blade-ops -- 运维中心 +├ ├── blade-admin -- spring-cloud后台管理 +├ ├── blade-develop -- 代码生成 +├ ├── blade-resource -- 资源管理 +├ ├── blade-seata-order -- seata分布式事务demo +├ ├── blade-seata-storage -- seata分布式事务demo +├── blade-service -- 业务模块 +├ ├── blade-desk -- 工作台模块 +├ ├── blade-log -- 日志模块 +├ ├── blade-system -- 系统模块 +├ └── blade-user -- 用户模块 +├── blade-service-api -- 业务模块api封装 +├ ├── blade-desk-api -- 工作台api +├ ├── blade-dict-api -- 字典api +├ ├── blade-system-api -- 系统api +└── └── blade-user-api -- 用户api +``` + +## 官网 + + +## 在线演示 +* Saber-基于Vue:[https://saber.bladex.vip](https://saber.bladex.vip) +* Sword-基于React:[https://sword.bladex.vip](https://sword.bladex.vip) +* Archer-全能代码生成系统:[https://archer.bladex.vip](https://archer.bladex.vip) +* Caster-数据大屏展示系统:[https://data.avuejs.com](https://data.avuejs.com) + +## 技术文档 +* [SpringBlade开发手册一览](https://gitee.com/smallc/SpringBlade/wikis/SpringBlade开发手册) +* [常见问题集锦](https://sns.bladex.vip/article-14966.html) + +## 项目地址 + +# 开源协议 +Apache Licence 2.0 ([英文原文](http://www.apache.org/licenses/LICENSE-2.0.html)) +Apache Licence是著名的非盈利开源组织Apache采用的协议。该协议和BSD类似,同样鼓励代码共享和尊重原作者的著作权,同样允许代码修改,再发布(作为开源或商业软件)。 -- Gitee From 0cfb3fabbeaa27f38a6bc8f1ce9fdbaef2fbd14d Mon Sep 17 00:00:00 2001 From: kndopensource Date: Thu, 16 Jul 2020 14:11:40 +0800 Subject: [PATCH 06/27] =?UTF-8?q?=E6=95=B0=E6=8D=AE=E6=8C=96=E6=8E=98?= =?UTF-8?q?=E9=A1=B9=E7=9B=AE=E9=9B=86=E5=90=88=E5=88=9D=E5=A7=8B=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../blade-stock-api/doc/README.md | 8 ++ blade-service-api/pom.xml | 1 + .../springblade/spider/SpiderWeiXinDemo.java | 75 ++++++++++++++++++ blade-spider-service-api/pom.xml | 72 +++++++++++++++++ ...4\346\265\201\347\250\213\345\233\276.jpg" | Bin 0 -> 61135 bytes blade-spider-service/pom.xml | 50 ++++++++++++ pom.xml | 3 +- 7 files changed, 208 insertions(+), 1 deletion(-) create mode 100644 blade-service-api/blade-stock-api/doc/README.md create mode 100644 blade-service/blade-spider/src/test/java/org/springblade/spider/SpiderWeiXinDemo.java create mode 100644 blade-spider-service-api/pom.xml create mode 100644 "blade-spider-service/doc/\346\234\215\345\212\241\345\267\245\344\275\234\346\265\201\347\250\213\345\233\276.jpg" create mode 100644 blade-spider-service/pom.xml diff --git a/blade-service-api/blade-stock-api/doc/README.md b/blade-service-api/blade-stock-api/doc/README.md new file mode 100644 index 00000000..0dbcc1e4 --- /dev/null +++ b/blade-service-api/blade-stock-api/doc/README.md @@ -0,0 +1,8 @@ +选股总结: +个股关注模型 重点看图 : + 个股分析思路 : + 一般分析股票的思路4点 依次 + 第一 看趋势 判断空间 + 第二 看资金 判断强弱 +第三 买票 看支撑 (缺口 年线半年线 辅助线 5日线 平台支撑等等) +第四 卖票 看压力 (缺口 年线半年线 平台高点 M头 等等 ) \ No newline at end of file diff --git a/blade-service-api/pom.xml b/blade-service-api/pom.xml index 2ce8665a..743aec03 100644 --- a/blade-service-api/pom.xml +++ b/blade-service-api/pom.xml @@ -22,6 +22,7 @@ blade-user-api blade-demo-api blade-spider-api + blade-stock-api diff --git a/blade-service/blade-spider/src/test/java/org/springblade/spider/SpiderWeiXinDemo.java b/blade-service/blade-spider/src/test/java/org/springblade/spider/SpiderWeiXinDemo.java new file mode 100644 index 00000000..395321bc --- /dev/null +++ b/blade-service/blade-spider/src/test/java/org/springblade/spider/SpiderWeiXinDemo.java @@ -0,0 +1,75 @@ +package org.springblade.spider; + +import com.yishuifengxiao.common.crawler.Crawler; +import com.yishuifengxiao.common.crawler.CrawlerBuilder; +import com.yishuifengxiao.common.crawler.domain.entity.SimulatorData; +import com.yishuifengxiao.common.crawler.domain.eunm.Pattern; +import com.yishuifengxiao.common.crawler.domain.eunm.Rule; +import com.yishuifengxiao.common.crawler.domain.eunm.Statu; +import com.yishuifengxiao.common.crawler.domain.model.ExtractFieldRule; +import com.yishuifengxiao.common.crawler.domain.model.ExtractRule; +import com.yishuifengxiao.common.crawler.domain.model.MatcherRule; +import com.yishuifengxiao.common.crawler.domain.model.SiteRule; +import org.junit.Test; +import org.springblade.spider.vo.SpiderPipeline; + +import java.util.Arrays; + +public class SpiderWeiXinDemo { + + + + @Test + public void spiderYahooTest() { + + ExtractFieldRule moveScreenwriterExtractRule = new ExtractFieldRule(Rule.XPATH, "//div[@id='img-content']/", "", 0); + + ExtractRule articleContent = new ExtractRule(); + + articleContent + .setCode("article") + .setName("微信文章") + .setRules(Arrays.asList(moveScreenwriterExtractRule)); //设置提取规则 + + + SimulatorData data = Crawler.testContent("https://mp.weixin.qq.com/s/QUEnXwmVxaA0AHgRuhfWbQ", new SiteRule(), articleContent); + System.out.println(data); + +// // 创建一个风铃虫实例 +// Crawler crawler = CrawlerBuilder.create() +// // 风铃虫的起始链接 +// .startUrl("https://mp.weixin.qq.com/s/QUEnXwmVxaA0AHgRuhfWbQ") +// // 风铃虫会将每次请求的网页的内容中的URL先全部提取出来,然后将完全匹配此规则的链接放入链接池 +// // 如果不设置则表示提取链接中所有包含域名关键字(例如此例中的yahoo)的链接放入链接池 +// // 链接池里的链接会作为下次抓取请求的种子链接 +// // 链接提取规则的作用是风铃虫根据此规则从下载的网页里提取出符合此规则的链接,然后将链接放入链接池 +// // 链接提取规则,多以添加多个链接提取规则, +// .addLinkRule(new MatcherRule(Pattern.REGEX, "https://hk.finance.yahoo.com/quote/.+")) +// // 可以设置多个内容页的规则 +// // 只要内容页URL中完全匹配此规则就进行内容提取,如果不设置标识提取域名下所有的链接 +// // 内容页规则是告诉风铃虫从哪些网页里提取出信息,因为不是所有的下载网页里都包含有需要的信息 +// // 内容页的规则, +// .contentPageRule(new MatcherRule(Pattern.REGEX, "https://hk.finance.yahoo.com/quote/.+")) +// // 风铃虫可以设置多个提取项,这里为了演示只设置了一个提取项 +// // 增加一个提取项规则 +// .addExtractRule(extractRule) +// // 如果不设置则使用默认时间10秒,此值是为了防止抓取频率太高被服务器封杀 +// // 每次进行爬取时的平均间隔时间,单位为毫秒, +// .interval(3000) +// .creatCrawler(); +// // 启动爬虫实例 +// crawler.start(); +// // 这里没有设置信息输出器,表示使用默认的信息输出器 +// // 默认的信息输出器使用的logback日志输出方法,因此需要看控制台信息 +// crawler.setPipeline(new SpiderPipeline()); + // 由于风铃虫是异步运行的,所以演示时这里加入循环 +// while (Statu.STOP != crawler.getStatu()) { +// try { +// Thread.sleep(1000 * 20); +// } catch (InterruptedException e) { +// e.printStackTrace(); +// } +// } + + } +} diff --git a/blade-spider-service-api/pom.xml b/blade-spider-service-api/pom.xml new file mode 100644 index 00000000..7e9e5ef5 --- /dev/null +++ b/blade-spider-service-api/pom.xml @@ -0,0 +1,72 @@ + + + + + SpringBlade + org.springblade + 2.7.1 + + 4.0.0 + + blade-spider-service-api + ${project.artifactId} + 2.7.1 + pom + 数据挖掘微服务API集合 + + + + org.springblade + blade-core-mybatis + ${blade.tool.version} + + + org.springframework.cloud + spring-cloud-starter-openfeign + + + io.springfox + springfox-swagger2 + ${swagger.version} + + + io.swagger + swagger-models + + + + + io.swagger + swagger-models + ${swagger.models.version} + + + net.dreamlu + mica-auto + ${mica.auto.version} + provided + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + true + ${project.name} + + + + com.spotify + docker-maven-plugin + ${docker.plugin.version} + + true + + + + + diff --git "a/blade-spider-service/doc/\346\234\215\345\212\241\345\267\245\344\275\234\346\265\201\347\250\213\345\233\276.jpg" "b/blade-spider-service/doc/\346\234\215\345\212\241\345\267\245\344\275\234\346\265\201\347\250\213\345\233\276.jpg" new file mode 100644 index 0000000000000000000000000000000000000000..818a135233b3015b4fe0a9c3ad332a4ac01d09f2 GIT binary patch literal 61135 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 OzwgQR`@67u+w)%{TP7L+ literal 0 HcmV?d00001 diff --git a/blade-spider-service/pom.xml b/blade-spider-service/pom.xml new file mode 100644 index 00000000..4dae4987 --- /dev/null +++ b/blade-spider-service/pom.xml @@ -0,0 +1,50 @@ + + + + + org.springblade + SpringBlade + 2.7.1 + + + blade-spider-service + ${project.artifactId} + 2.7.1 + pom + spider 微服务集合 + + + + org.springblade + blade-common + ${blade.project.version} + + + + + + + com.spotify + docker-maven-plugin + ${docker.plugin.version} + + ${docker.registry.url}/blade/${project.artifactId}:${project.version} + ${project.basedir} + ${docker.registry.host} + + + / + ${project.build.directory} + ${project.build.finalName}.jar + + + ${docker.registry.url} + ${docker.registry.url} + true + + + + + + diff --git a/pom.xml b/pom.xml index 8d6368a6..95287744 100644 --- a/pom.xml +++ b/pom.xml @@ -42,7 +42,8 @@ blade-service blade-service-api blade-common - blade-service-api/blade-stock-api + blade-spider-service + blade-spider-service-api -- Gitee From df03326dd7fa723a10a7b13c9e59d254c9b9f0bc Mon Sep 17 00:00:00 2001 From: kndopensource Date: Thu, 16 Jul 2020 15:42:04 +0800 Subject: [PATCH 07/27] =?UTF-8?q?=E6=95=B0=E6=8D=AE=E6=8C=96=E6=8E=98?= =?UTF-8?q?=E4=B8=9A=E5=8A=A1=E6=9C=8D=E5=8A=A1=E6=A8=A1=E5=9D=97=E5=88=9D?= =?UTF-8?q?=E5=A7=8B=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- blade-service-api/pom.xml | 1 - blade-service/blade-spider/README.md | 49 ---- .../springblade/spider/vo/SpiderPipeline.java | 22 -- .../springblade/spider/CssExtractTest.java | 266 ----------------- .../org/springblade/spider/RegexTest.java | 32 -- .../spider/RemoveQueryParamsTest.java | 17 -- .../springblade/spider/SpiderDemoTest.java | 91 ------ .../springblade/spider/SpiderDoubanTest.java | 102 ------- .../springblade/spider/SpiderWeiXinDemo.java | 75 ----- .../org/springblade/spider/XpathTest.java | 37 --- blade-service/pom.xml | 1 - blade-spider-common/pom.xml | 55 ++++ .../spider/common/entity/DownloadMsgInfo.java | 34 +++ .../spider/common/entity/Node.java | 69 +++++ .../spider/common/entity/Result.java | 51 ++++ .../spider/common/entity/Torrent.java | 47 +++ .../spider/common/entity/Tree.java | 134 +++++++++ .../spider/common/entity/WebConfig.java | 21 ++ .../spider/common/util/BloomFilter.java | 242 +++++++++++++++ .../spider/common/util/ByteUtil.java | 89 ++++++ .../spider/common/util/ExtensionUtil.java | 103 +++++++ .../spider/common/util/FileTypeUtil.java | 65 +++++ .../spider/common/util/JSONUtil.java | 29 ++ .../spider/common/util/NetworkUtil.java | 90 ++++++ .../spider/common/util/NodeIdUtil.java | 107 +++++++ .../spider/common/util/StringUtil.java | 208 +++++++++++++ .../spider/common/util/SystemClock.java | 55 ++++ .../util/bencode/BencodingInputStream.java | 275 ++++++++++++++++++ .../util/bencode/BencodingOutputStream.java | 178 ++++++++++++ .../common/util/bencode/BencodingUtils.java | 36 +++ .../test/java/org/springblade/AppTest.java | 20 ++ .../blade-datadig-api}/pom.xml | 12 +- .../org/springblade/feign/TorrentClient.java | 52 ++++ .../feign/TorrentClientFallback.java | 32 ++ .../springblade/request/SearchRequest.java | 28 ++ .../org/springblade/vo/TorrentPageVO.java | 20 ++ .../java/org/springblade/vo/TorrentVO.java | 18 ++ blade-spider-service-api/pom.xml | 4 + .../blade-datadig}/pom.xml | 26 +- .../datadig/DataDigApplication.java | 12 +- .../src/main/resources/application-dev.yml | 2 +- .../src/main/resources/application-prod.yml | 0 .../src/main/resources/application-test.yml | 0 .../test/java/org/springblade/AppTest.java | 20 ++ .../doc/thirdJar/sourceforge.rar | Bin 0 -> 3643871 bytes blade-spider-service/pom.xml | 4 + pom.xml | 1 + 47 files changed, 2112 insertions(+), 720 deletions(-) delete mode 100644 blade-service/blade-spider/README.md delete mode 100644 blade-service/blade-spider/src/main/java/org/springblade/spider/vo/SpiderPipeline.java delete mode 100644 blade-service/blade-spider/src/test/java/org/springblade/spider/CssExtractTest.java delete mode 100644 blade-service/blade-spider/src/test/java/org/springblade/spider/RegexTest.java delete mode 100644 blade-service/blade-spider/src/test/java/org/springblade/spider/RemoveQueryParamsTest.java delete mode 100644 blade-service/blade-spider/src/test/java/org/springblade/spider/SpiderDemoTest.java delete mode 100644 blade-service/blade-spider/src/test/java/org/springblade/spider/SpiderDoubanTest.java delete mode 100644 blade-service/blade-spider/src/test/java/org/springblade/spider/SpiderWeiXinDemo.java delete mode 100644 blade-service/blade-spider/src/test/java/org/springblade/spider/XpathTest.java create mode 100644 blade-spider-common/pom.xml create mode 100644 blade-spider-common/src/main/java/org/springblade/spider/common/entity/DownloadMsgInfo.java create mode 100644 blade-spider-common/src/main/java/org/springblade/spider/common/entity/Node.java create mode 100644 blade-spider-common/src/main/java/org/springblade/spider/common/entity/Result.java create mode 100644 blade-spider-common/src/main/java/org/springblade/spider/common/entity/Torrent.java create mode 100644 blade-spider-common/src/main/java/org/springblade/spider/common/entity/Tree.java create mode 100644 blade-spider-common/src/main/java/org/springblade/spider/common/entity/WebConfig.java create mode 100644 blade-spider-common/src/main/java/org/springblade/spider/common/util/BloomFilter.java create mode 100644 blade-spider-common/src/main/java/org/springblade/spider/common/util/ByteUtil.java create mode 100644 blade-spider-common/src/main/java/org/springblade/spider/common/util/ExtensionUtil.java create mode 100644 blade-spider-common/src/main/java/org/springblade/spider/common/util/FileTypeUtil.java create mode 100644 blade-spider-common/src/main/java/org/springblade/spider/common/util/JSONUtil.java create mode 100644 blade-spider-common/src/main/java/org/springblade/spider/common/util/NetworkUtil.java create mode 100644 blade-spider-common/src/main/java/org/springblade/spider/common/util/NodeIdUtil.java create mode 100644 blade-spider-common/src/main/java/org/springblade/spider/common/util/StringUtil.java create mode 100644 blade-spider-common/src/main/java/org/springblade/spider/common/util/SystemClock.java create mode 100644 blade-spider-common/src/main/java/org/springblade/spider/common/util/bencode/BencodingInputStream.java create mode 100644 blade-spider-common/src/main/java/org/springblade/spider/common/util/bencode/BencodingOutputStream.java create mode 100644 blade-spider-common/src/main/java/org/springblade/spider/common/util/bencode/BencodingUtils.java create mode 100644 blade-spider-common/src/test/java/org/springblade/AppTest.java rename {blade-service-api/blade-spider-api => blade-spider-service-api/blade-datadig-api}/pom.xml (54%) create mode 100644 blade-spider-service-api/blade-datadig-api/src/main/java/org/springblade/feign/TorrentClient.java create mode 100644 blade-spider-service-api/blade-datadig-api/src/main/java/org/springblade/feign/TorrentClientFallback.java create mode 100644 blade-spider-service-api/blade-datadig-api/src/main/java/org/springblade/request/SearchRequest.java create mode 100644 blade-spider-service-api/blade-datadig-api/src/main/java/org/springblade/vo/TorrentPageVO.java create mode 100644 blade-spider-service-api/blade-datadig-api/src/main/java/org/springblade/vo/TorrentVO.java rename {blade-service/blade-spider => blade-spider-service/blade-datadig}/pom.xml (72%) rename blade-service/blade-spider/src/main/java/org/springblade/spider/SpiderApplication.java => blade-spider-service/blade-datadig/src/main/java/org/springblade/datadig/DataDigApplication.java (56%) rename {blade-service/blade-spider => blade-spider-service/blade-datadig}/src/main/resources/application-dev.yml (93%) rename {blade-service/blade-spider => blade-spider-service/blade-datadig}/src/main/resources/application-prod.yml (100%) rename {blade-service/blade-spider => blade-spider-service/blade-datadig}/src/main/resources/application-test.yml (100%) create mode 100644 blade-spider-service/blade-datadig/src/test/java/org/springblade/AppTest.java create mode 100644 blade-spider-service/doc/thirdJar/sourceforge.rar diff --git a/blade-service-api/pom.xml b/blade-service-api/pom.xml index 743aec03..b0ae51e3 100644 --- a/blade-service-api/pom.xml +++ b/blade-service-api/pom.xml @@ -21,7 +21,6 @@ blade-system-api blade-user-api blade-demo-api - blade-spider-api blade-stock-api diff --git a/blade-service/blade-spider/README.md b/blade-service/blade-spider/README.md deleted file mode 100644 index 362f2a37..00000000 --- a/blade-service/blade-spider/README.md +++ /dev/null @@ -1,49 +0,0 @@ -## balde-spider 模块 - 数据爬取整合模块 - 1. 爬取指定的站点、公众号文章 - 2. 爬取固定任务数据 - -## 架构图 - -## 工程结构 -``` -SpringBlade -├── blade-auth -- 授权服务提供 -├── blade-common -- 常用工具封装包 -├── blade-gateway -- Spring Cloud 网关 -├── blade-ops -- 运维中心 -├ ├── blade-admin -- spring-cloud后台管理 -├ ├── blade-develop -- 代码生成 -├ ├── blade-resource -- 资源管理 -├ ├── blade-seata-order -- seata分布式事务demo -├ ├── blade-seata-storage -- seata分布式事务demo -├── blade-service -- 业务模块 -├ ├── blade-desk -- 工作台模块 -├ ├── blade-log -- 日志模块 -├ ├── blade-system -- 系统模块 -├ └── blade-user -- 用户模块 -├── blade-service-api -- 业务模块api封装 -├ ├── blade-desk-api -- 工作台api -├ ├── blade-dict-api -- 字典api -├ ├── blade-system-api -- 系统api -└── └── blade-user-api -- 用户api -``` - -## 官网 - - -## 在线演示 -* Saber-基于Vue:[https://saber.bladex.vip](https://saber.bladex.vip) -* Sword-基于React:[https://sword.bladex.vip](https://sword.bladex.vip) -* Archer-全能代码生成系统:[https://archer.bladex.vip](https://archer.bladex.vip) -* Caster-数据大屏展示系统:[https://data.avuejs.com](https://data.avuejs.com) - -## 技术文档 -* [SpringBlade开发手册一览](https://gitee.com/smallc/SpringBlade/wikis/SpringBlade开发手册) -* [常见问题集锦](https://sns.bladex.vip/article-14966.html) - -## 项目地址 - -# 开源协议 -Apache Licence 2.0 ([英文原文](http://www.apache.org/licenses/LICENSE-2.0.html)) -Apache Licence是著名的非盈利开源组织Apache采用的协议。该协议和BSD类似,同样鼓励代码共享和尊重原作者的著作权,同样允许代码修改,再发布(作为开源或商业软件)。 diff --git a/blade-service/blade-spider/src/main/java/org/springblade/spider/vo/SpiderPipeline.java b/blade-service/blade-spider/src/main/java/org/springblade/spider/vo/SpiderPipeline.java deleted file mode 100644 index f9247a87..00000000 --- a/blade-service/blade-spider/src/main/java/org/springblade/spider/vo/SpiderPipeline.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.springblade.spider.vo; - -import com.yishuifengxiao.common.crawler.domain.entity.CrawlerData; -import com.yishuifengxiao.common.crawler.pipeline.Pipeline; -import lombok.extern.slf4j.Slf4j; - -/** - * - */ -@Slf4j -public class SpiderPipeline implements Pipeline { - - @Override - public void recieve(CrawlerData crawlerData) { - - log.debug("\r\n"); - log.debug("The output is "); - log.info("request : {} , out data : {}", crawlerData.getRedirectUrl(), crawlerData.getAllData()); - log.debug("\r\n"); - } - -} diff --git a/blade-service/blade-spider/src/test/java/org/springblade/spider/CssExtractTest.java b/blade-service/blade-spider/src/test/java/org/springblade/spider/CssExtractTest.java deleted file mode 100644 index 56fe0bc9..00000000 --- a/blade-service/blade-spider/src/test/java/org/springblade/spider/CssExtractTest.java +++ /dev/null @@ -1,266 +0,0 @@ -package org.springblade.spider; - -import com.yishuifengxiao.common.crawler.extractor.content.strategy.impl.CssStrategy; -import com.yishuifengxiao.common.crawler.extractor.content.strategy.impl.CssTextStrategy; -import org.junit.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -public class CssExtractTest { - private CssStrategy css = new CssStrategy(); - - private CssTextStrategy ct = new CssTextStrategy(); - private static String RULE1_PARAM1 = "p.post-meta"; - private static String RULE1_PARAM2 = "style"; - - private static String RULE2_PARAM1 = "h2.post-title"; - private static String RULE2_PARAM2 = ""; - - @Test - public void testExtract() { - - String out = css.extract(HTML, RULE1_PARAM1, RULE1_PARAM2); - System.out.println("=============================> \r\n"); - System.out.println(out); - } - - - @Test - public void testCssTextStrategy() { - - String out = ct.extract(HTML, RULE2_PARAM1, RULE2_PARAM2); - System.out.println("=============================> \r\n"); - System.out.println(out); - } - - private final static String HTML = "

\r\n" + " \r\n" + "\r\n" + "\r\n" - + " \r\n" + "
\r\n" - + " \r\n" - + " \r\n" + "\r\n" + "\r\n" + "\r\n" + "
\r\n" + "\r\n" - + "\r\n" + "
\r\n" + "\r\n" - + "\r\n" + "
\r\n" + "\r\n" - + "\r\n" + "
\r\n" + "\r\n" - + "\r\n" + "
\r\n" + "\r\n" - + "\r\n" + "
\r\n" + "\r\n" - + "\r\n" + "
\r\n" + "\r\n" - + "\r\n" + "
\r\n" + "\r\n" - + "\r\n" + "
\r\n" + "\r\n" - + "\r\n" + "
\r\n" + "\r\n" - + "\r\n" + "\r\n" + "\r\n" + "\r\n" + "
    \r\n" + " \r\n" + " \r\n" - + "
  • \r\n" + " Older Posts →\r\n" - + "
  • \r\n" + " \r\n" + "
\r\n" + "\r\n" + "\r\n" + "\r\n" - + " \r\n" + " \r\n" + "
\r\n" + "\r\n" - + " \r\n" + "
\r\n" + " \r\n" + " \r\n" + "\r\n" - + "
\r\n" + " \r\n" + "
FEATURED TAGS
\r\n" - + "
\r\n" + " \r\n" + " \r\n" - + " 性能测试\r\n" + " \r\n" - + " \r\n" + " \r\n" - + " linux\r\n" + " \r\n" - + " \r\n" + " \r\n" - + " node.js\r\n" + " \r\n" - + " \r\n" + " \r\n" - + " 入门教程\r\n" + " \r\n" - + " \r\n" + " \r\n" - + " 前端\r\n" + " \r\n" - + " \r\n" + " \r\n" - + " java\r\n" + " \r\n" - + " \r\n" + " \r\n" + " \r\n" + " \r\n" - + " jmeter\r\n" + " \r\n" - + " \r\n" + " \r\n" + " \r\n" + " \r\n" + " \r\n" - + " \r\n" + " \r\n" + " \r\n" - + " docker\r\n" + " \r\n" - + " \r\n" + " \r\n" + " \r\n" + " \r\n" + " \r\n" - + " \r\n" + " \r\n" + " \r\n" - + " ElasticStack\r\n" - + " \r\n" + " \r\n" + " \r\n" + " \r\n" + " \r\n" - + " \r\n" + " \r\n" + " \r\n" + " \r\n" + " \r\n" - + " \r\n" + " \r\n" + " \r\n" - + " spring security\r\n" - + " \r\n" + " \r\n" + " \r\n" - + " 易水组件\r\n" + " \r\n" - + " \r\n" + " \r\n" + " \r\n" + " \r\n" - + " spring cloud\r\n" - + " \r\n" + " \r\n" + " \r\n" - + " 微服务\r\n" + " \r\n" - + " \r\n" + " \r\n" - + " 随笔\r\n" + " \r\n" - + " \r\n" + " \r\n" + " \r\n" + "
\r\n" + "
\r\n" + "\r\n" - + "
\r\n" + " \r\n" + " \r\n" - + "
\r\n" + "
ABOUT ME
\r\n" - + "
\r\n" + "\r\n" + " \r\n" - + " \r\n" + " \r\n" + "\r\n" - + " \r\n" + "

关注我的微信,互相交流

\r\n" + " \r\n" + "\r\n" - + " \r\n" + " \r\n" - + "
\r\n" + "
\r\n" + "
\r\n" + " \r\n" + " \r\n" - + "
RECENT POSTS\r\n" + "
\r\n" + " \r\n" + "
\r\n" + "
\r\n" + "
\r\n" + " \r\n" - + " \r\n" + "\r\n" + "
FRIENDS
\r\n" + "\r\n" + "\r\n" + "
\r\n" + " \r\n" + "
\r\n" + "\r\n" - + " \r\n" + "
"; - - -} diff --git a/blade-service/blade-spider/src/test/java/org/springblade/spider/RegexTest.java b/blade-service/blade-spider/src/test/java/org/springblade/spider/RegexTest.java deleted file mode 100644 index 984868b4..00000000 --- a/blade-service/blade-spider/src/test/java/org/springblade/spider/RegexTest.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.springblade.spider; - -import com.yishuifengxiao.common.crawler.utils.RegexFactory; -import org.junit.Test; -import org.springframework.boot.test.context.SpringBootTest; - -import java.util.List; - -/** - * 测试正则提取工具 - */ -@SpringBootTest -public class RegexTest { - @Test - public void extractAll() { - - String regex = "[0-9]+"; - String content = "阅读数 30"; - List str = RegexFactory.extractAll(regex, content); - System.out.println(str); - } - - @Test - public void extract() { - - String regex = "[0-9]+"; - String content = " 阅读数 30"; - String str = RegexFactory.extract(regex, content); - System.out.println(str); - } - -} diff --git a/blade-service/blade-spider/src/test/java/org/springblade/spider/RemoveQueryParamsTest.java b/blade-service/blade-spider/src/test/java/org/springblade/spider/RemoveQueryParamsTest.java deleted file mode 100644 index 1574efc3..00000000 --- a/blade-service/blade-spider/src/test/java/org/springblade/spider/RemoveQueryParamsTest.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.springblade.spider; - -import org.apache.commons.lang3.StringUtils; -import org.junit.Test; - -/** - * 测试去除URL中的查询参数 - */ -public class RemoveQueryParamsTest { - - @Test - public void removeQueryParamsTest(){ - String str = "https://fanyi.baidu.com/?aldtype=16047#zh/en/%E7%A7%BB%E9%99%A4%E8%AF%B7%E6%B1%82%E5%8F%82%E6%95%B0"; - System.out.println(StringUtils.indexOf(str, "?")); - System.out.println(StringUtils.substringBefore(str, "?")); - } -} diff --git a/blade-service/blade-spider/src/test/java/org/springblade/spider/SpiderDemoTest.java b/blade-service/blade-spider/src/test/java/org/springblade/spider/SpiderDemoTest.java deleted file mode 100644 index 375a6dad..00000000 --- a/blade-service/blade-spider/src/test/java/org/springblade/spider/SpiderDemoTest.java +++ /dev/null @@ -1,91 +0,0 @@ -package org.springblade.spider; - -import com.yishuifengxiao.common.crawler.Crawler; -import com.yishuifengxiao.common.crawler.CrawlerBuilder; -import com.yishuifengxiao.common.crawler.domain.eunm.Pattern; -import com.yishuifengxiao.common.crawler.domain.eunm.Rule; -import com.yishuifengxiao.common.crawler.domain.eunm.Statu; -import com.yishuifengxiao.common.crawler.domain.model.ExtractFieldRule; -import com.yishuifengxiao.common.crawler.domain.model.ExtractRule; -import com.yishuifengxiao.common.crawler.domain.model.MatcherRule; -import com.yishuifengxiao.common.crawler.utils.RegexFactory; -import org.apache.commons.lang3.StringUtils; -import org.junit.Test; -import org.seimicrawler.xpath.JXDocument; -import org.springblade.spider.vo.SpiderPipeline; -import org.springframework.boot.test.context.SpringBootTest; - -import java.util.Arrays; -import java.util.List; - -/** - * SpiderDemo单元测试 - * - * @author Chill - */ -//@RunWith(SpringBootTest.class) -@SpringBootTest -//@BladeBootTest(appName = "blade-spider", profile = "dev", enableLoader = true) -public class SpiderDemoTest { - -// @Autowired -// private INoticeService noticeService; - - @Test - public void spiderYahooTest() { - - // 创建一个提取属性规则 - // 该提取规则标识使用 XPATH提取器进行提取, - // 该XPATH提取器的XPATH表达式为 //h1/text() , 该提取提取器的作用顺序是0 - ExtractFieldRule extractFieldRule = new ExtractFieldRule(Rule.XPATH, "//h1/text()", "", 0); - - // 创建一个提取项 - ExtractRule extractRule = new ExtractRule(); - extractRule - // 提取项代码,不能为空,同一组提取规则之内每一个提取项的编码必须唯一 - .setCode("code") - // 提取项名字,可以不设置 - .setName("加密电子货币名字") - // 设置提取属性规则 - .setRules(Arrays.asList(extractFieldRule)); - - // 创建一个风铃虫实例 - Crawler crawler = CrawlerBuilder.create() - // 风铃虫的起始链接 - .startUrl("https://hk.finance.yahoo.com/cryptocurrencies") - // 风铃虫会将每次请求的网页的内容中的URL先全部提取出来,然后将完全匹配此规则的链接放入链接池 - // 如果不设置则表示提取链接中所有包含域名关键字(例如此例中的yahoo)的链接放入链接池 - // 链接池里的链接会作为下次抓取请求的种子链接 - // 链接提取规则的作用是风铃虫根据此规则从下载的网页里提取出符合此规则的链接,然后将链接放入链接池 - // 链接提取规则,多以添加多个链接提取规则, - .addLinkRule(new MatcherRule(Pattern.REGEX, "https://hk.finance.yahoo.com/quote/.+")) - // 可以设置多个内容页的规则 - // 只要内容页URL中完全匹配此规则就进行内容提取,如果不设置标识提取域名下所有的链接 - // 内容页规则是告诉风铃虫从哪些网页里提取出信息,因为不是所有的下载网页里都包含有需要的信息 - // 内容页的规则, - .contentPageRule(new MatcherRule(Pattern.REGEX, "https://hk.finance.yahoo.com/quote/.+")) - // 风铃虫可以设置多个提取项,这里为了演示只设置了一个提取项 - // 增加一个提取项规则 - .addExtractRule(extractRule) - // 如果不设置则使用默认时间10秒,此值是为了防止抓取频率太高被服务器封杀 - // 每次进行爬取时的平均间隔时间,单位为毫秒, - .interval(3000) - .creatCrawler(); - // 启动爬虫实例 - crawler.start(); - // 这里没有设置信息输出器,表示使用默认的信息输出器 - // 默认的信息输出器使用的logback日志输出方法,因此需要看控制台信息 - crawler.setPipeline(new SpiderPipeline()); - // 由于风铃虫是异步运行的,所以演示时这里加入循环 - while (Statu.STOP != crawler.getStatu()) { - try { - Thread.sleep(1000 * 20); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - - } - - -} diff --git a/blade-service/blade-spider/src/test/java/org/springblade/spider/SpiderDoubanTest.java b/blade-service/blade-spider/src/test/java/org/springblade/spider/SpiderDoubanTest.java deleted file mode 100644 index 97473eb4..00000000 --- a/blade-service/blade-spider/src/test/java/org/springblade/spider/SpiderDoubanTest.java +++ /dev/null @@ -1,102 +0,0 @@ -package org.springblade.spider; - -import com.yishuifengxiao.common.crawler.Crawler; -import com.yishuifengxiao.common.crawler.CrawlerBuilder; -import com.yishuifengxiao.common.crawler.domain.entity.SimulatorData; -import com.yishuifengxiao.common.crawler.domain.eunm.Pattern; -import com.yishuifengxiao.common.crawler.domain.eunm.Rule; -import com.yishuifengxiao.common.crawler.domain.model.ExtractFieldRule; -import com.yishuifengxiao.common.crawler.domain.model.ExtractRule; -import com.yishuifengxiao.common.crawler.domain.model.MatcherRule; -import com.yishuifengxiao.common.crawler.domain.model.SiteRule; -import org.junit.Test; -import com.yishuifengxiao.common.crawler.domain.eunm.Statu; -import org.springblade.spider.vo.SpiderPipeline; - -import java.util.Arrays; - -public class SpiderDoubanTest { - - @Test - public void spiderDoubanTest() { - - // 创建一个提取属性规则 - // 该提取规则标识使用 XPATH提取器进行提取, - // 该XPATH提取器的XPATH表达式为 //h1/text() , 该提取提取器的作用顺序是0 - ExtractFieldRule moveNameExtractRule = new ExtractFieldRule(Rule.XPATH, "//span[@property='v:itemreviewed']/text()", "", 0); - - // 创建一个提取项 - ExtractRule moveNameContentItem = new ExtractRule(); - moveNameContentItem - // 提取项代码,不能为空,同一组提取规则之内每一个提取项的编码必须唯一 - .setCode("moveName") - // 提取项名字,可以不设置 - .setName("moveName") - // 设置提取属性规则 - .setRules(Arrays.asList(moveNameExtractRule)); - - ExtractFieldRule moveDirectorExtractRule = new ExtractFieldRule(Rule.XPATH, "//div[@id='info']/span/span/a[@rel='v:directedBy']/text()", "", 0); - - - //创建一个提取项rel="v:directedBy" - ExtractRule extractMoveDirectorRule = new ExtractRule(); - extractMoveDirectorRule - .setCode("moveDirector") //提取项代码,不能为空 - .setName("moveDirector") - .setRules(Arrays.asList(moveDirectorExtractRule)); //设置提取规则 - - ExtractFieldRule moveScreenwriterExtractRule = new ExtractFieldRule(Rule.XPATH, "//div[@id='info']/span[contains(@text,'编剧')]]/span/text()", "", 0); - - ExtractRule moveScreenwriterContentItem = new ExtractRule(); - - moveScreenwriterContentItem - .setCode("moveScreenwriter") - .setName("moveScreenwriter") - .setRules(Arrays.asList(moveScreenwriterExtractRule)); //设置提取规则 - - - SimulatorData data = Crawler.testContent("https://movie.douban.com/subject/27186348/", new SiteRule(), moveNameContentItem); - System.out.println(data); - SimulatorData data1 = Crawler.testContent("https://movie.douban.com/subject/27186348/", new SiteRule(), extractMoveDirectorRule); - System.out.println(data1); - SimulatorData data2 = Crawler.testContent("https://movie.douban.com/subject/27186348/", new SiteRule(), moveScreenwriterContentItem); - System.out.println(data2); - - - //创建一个风铃虫实例 - Crawler crawler = CrawlerBuilder.create() - .threadNum(1) - .connectTimeout(3000) - .startUrl("https://movie.douban.com/") //风铃虫的起始链接 - // 风铃虫会将每次请求的网页的内容中的URL先全部提取出来 - // 如果不设置则表示提取链接中所有包含域名关键字(例如此例中的ifeng)的链接放入链接池 - //链接池里的链接会作为下次抓取请求的种子链接 - //多以添加多个链接提取规则, - .addLinkRule(new MatcherRule(Pattern.REGEX, "https://movie\\.douban.*"))//链接提取规则 - //可以设置多个内容页的规则,多个内容页规则之间用半角逗号隔开 - //只要内容页URL中完全匹配此规则就进行内容提取,如果不设置标识提取域名下所有的链接 -// .extractUrl("https://movie.douban.com/subject/[0-9]+/") //内容页的规则, - //风铃虫可以设置多个提取项,这里为了演示只设置了一个提取项 - .addExtractRule(moveNameContentItem) //增加一个提取项 - .addExtractRule(extractMoveDirectorRule) //增加一个提取项 - .addExtractRule(moveScreenwriterContentItem) //增加一个提取项 - - //如果不设置则使用默认时间10秒,此值是为了防止抓取频率太高被服务器封杀 - .interval(30)//每次进行爬取时的平均间隔时间,单位为秒, - .creatCrawler(); - //启动爬虫实例 - crawler.start(); - // 这里没有设置信息输出器,表示使用默认的信息输出器 - //默认的信息输出器使用的logback日志输出方法,因此需要看控制台信息 - crawler.setPipeline(new SpiderPipeline()); - //由于风铃虫时异步运行的,所以演示时这里加入循环 - while (Statu.STOP != crawler.getStatu()) { - try { - Thread.sleep(1000 * 20); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - } -} - diff --git a/blade-service/blade-spider/src/test/java/org/springblade/spider/SpiderWeiXinDemo.java b/blade-service/blade-spider/src/test/java/org/springblade/spider/SpiderWeiXinDemo.java deleted file mode 100644 index 395321bc..00000000 --- a/blade-service/blade-spider/src/test/java/org/springblade/spider/SpiderWeiXinDemo.java +++ /dev/null @@ -1,75 +0,0 @@ -package org.springblade.spider; - -import com.yishuifengxiao.common.crawler.Crawler; -import com.yishuifengxiao.common.crawler.CrawlerBuilder; -import com.yishuifengxiao.common.crawler.domain.entity.SimulatorData; -import com.yishuifengxiao.common.crawler.domain.eunm.Pattern; -import com.yishuifengxiao.common.crawler.domain.eunm.Rule; -import com.yishuifengxiao.common.crawler.domain.eunm.Statu; -import com.yishuifengxiao.common.crawler.domain.model.ExtractFieldRule; -import com.yishuifengxiao.common.crawler.domain.model.ExtractRule; -import com.yishuifengxiao.common.crawler.domain.model.MatcherRule; -import com.yishuifengxiao.common.crawler.domain.model.SiteRule; -import org.junit.Test; -import org.springblade.spider.vo.SpiderPipeline; - -import java.util.Arrays; - -public class SpiderWeiXinDemo { - - - - @Test - public void spiderYahooTest() { - - ExtractFieldRule moveScreenwriterExtractRule = new ExtractFieldRule(Rule.XPATH, "//div[@id='img-content']/", "", 0); - - ExtractRule articleContent = new ExtractRule(); - - articleContent - .setCode("article") - .setName("微信文章") - .setRules(Arrays.asList(moveScreenwriterExtractRule)); //设置提取规则 - - - SimulatorData data = Crawler.testContent("https://mp.weixin.qq.com/s/QUEnXwmVxaA0AHgRuhfWbQ", new SiteRule(), articleContent); - System.out.println(data); - -// // 创建一个风铃虫实例 -// Crawler crawler = CrawlerBuilder.create() -// // 风铃虫的起始链接 -// .startUrl("https://mp.weixin.qq.com/s/QUEnXwmVxaA0AHgRuhfWbQ") -// // 风铃虫会将每次请求的网页的内容中的URL先全部提取出来,然后将完全匹配此规则的链接放入链接池 -// // 如果不设置则表示提取链接中所有包含域名关键字(例如此例中的yahoo)的链接放入链接池 -// // 链接池里的链接会作为下次抓取请求的种子链接 -// // 链接提取规则的作用是风铃虫根据此规则从下载的网页里提取出符合此规则的链接,然后将链接放入链接池 -// // 链接提取规则,多以添加多个链接提取规则, -// .addLinkRule(new MatcherRule(Pattern.REGEX, "https://hk.finance.yahoo.com/quote/.+")) -// // 可以设置多个内容页的规则 -// // 只要内容页URL中完全匹配此规则就进行内容提取,如果不设置标识提取域名下所有的链接 -// // 内容页规则是告诉风铃虫从哪些网页里提取出信息,因为不是所有的下载网页里都包含有需要的信息 -// // 内容页的规则, -// .contentPageRule(new MatcherRule(Pattern.REGEX, "https://hk.finance.yahoo.com/quote/.+")) -// // 风铃虫可以设置多个提取项,这里为了演示只设置了一个提取项 -// // 增加一个提取项规则 -// .addExtractRule(extractRule) -// // 如果不设置则使用默认时间10秒,此值是为了防止抓取频率太高被服务器封杀 -// // 每次进行爬取时的平均间隔时间,单位为毫秒, -// .interval(3000) -// .creatCrawler(); -// // 启动爬虫实例 -// crawler.start(); -// // 这里没有设置信息输出器,表示使用默认的信息输出器 -// // 默认的信息输出器使用的logback日志输出方法,因此需要看控制台信息 -// crawler.setPipeline(new SpiderPipeline()); - // 由于风铃虫是异步运行的,所以演示时这里加入循环 -// while (Statu.STOP != crawler.getStatu()) { -// try { -// Thread.sleep(1000 * 20); -// } catch (InterruptedException e) { -// e.printStackTrace(); -// } -// } - - } -} diff --git a/blade-service/blade-spider/src/test/java/org/springblade/spider/XpathTest.java b/blade-service/blade-spider/src/test/java/org/springblade/spider/XpathTest.java deleted file mode 100644 index 9786957b..00000000 --- a/blade-service/blade-spider/src/test/java/org/springblade/spider/XpathTest.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.springblade.spider; - -import org.junit.Test; -import org.seimicrawler.xpath.JXDocument; - -import java.util.List; - -/** - * XPATH提取测试 - */ -public class XpathTest { - - @Test - public void xpathTest(){ - String html = "" - + "
ab
"; - - JXDocument jxDocument = JXDocument.create(html); - List r1s = jxDocument.sel("//a/@href"); - - for (Object obj : r1s) { - System.out.println(obj); - } - - List r2s = jxDocument.sel("//tr/td"); - - for (Object obj : r2s) { - System.out.println(obj); - } - - List r3s = jxDocument.sel("//tr/td/text()"); - - for (Object obj : r3s) { - System.out.println(obj); - } - } -} diff --git a/blade-service/pom.xml b/blade-service/pom.xml index ae022c3e..63d8027b 100644 --- a/blade-service/pom.xml +++ b/blade-service/pom.xml @@ -22,7 +22,6 @@ blade-system blade-user blade-demo - blade-spider blade-stock diff --git a/blade-spider-common/pom.xml b/blade-spider-common/pom.xml new file mode 100644 index 00000000..c634b286 --- /dev/null +++ b/blade-spider-common/pom.xml @@ -0,0 +1,55 @@ + + + + + SpringBlade + org.springblade + 2.7.1 + + 4.0.0 + + blade-spider-common + ${project.artifactId} + ${blade.project.version} + jar + 数据挖掘公共模块 + + + + org.springframework.boot + spring-boot-starter-data-elasticsearch + true + + + net.sourceforge.cpdetector + cpdetector + 1.0.7 + + + commons-codec + commons-codec + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + true + ${project.name} + + + + com.spotify + docker-maven-plugin + ${docker.plugin.version} + + true + + + + + diff --git a/blade-spider-common/src/main/java/org/springblade/spider/common/entity/DownloadMsgInfo.java b/blade-spider-common/src/main/java/org/springblade/spider/common/entity/DownloadMsgInfo.java new file mode 100644 index 00000000..ddedc7a7 --- /dev/null +++ b/blade-spider-common/src/main/java/org/springblade/spider/common/entity/DownloadMsgInfo.java @@ -0,0 +1,34 @@ +package org.springblade.spider.common.entity; + +import lombok.Data; +import org.springblade.spider.common.util.SystemClock; + +import java.io.Serializable; + +/*** + * Peer Wire 下载消息信息 + * + * @author Mr.Xu + * @date 2019-02-20 15:53 + **/ +@Data +public class DownloadMsgInfo implements Serializable { + + private String ip; + private int port; + private byte[] nodeId; //make sure download peer id is same to DHT server + private byte[] infoHash; + private long timestamp; + + public DownloadMsgInfo() { + + } + + public DownloadMsgInfo(String ip, int port, byte[] nodeId, byte[] infoHash) { + this.ip = ip; + this.port = port; + this.nodeId = nodeId; + this.infoHash = infoHash; + timestamp = SystemClock.now(); + } +} diff --git a/blade-spider-common/src/main/java/org/springblade/spider/common/entity/Node.java b/blade-spider-common/src/main/java/org/springblade/spider/common/entity/Node.java new file mode 100644 index 00000000..9c5a084a --- /dev/null +++ b/blade-spider-common/src/main/java/org/springblade/spider/common/entity/Node.java @@ -0,0 +1,69 @@ +package org.springblade.spider.common.entity; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class Node { + + private int nid; // 父亲id + private int pid; + private String filename = ""; + private Long filesize; + private int index; + + private List children; + + public void addChild(Node node) { + if (children == null) { + children = new ArrayList<>(); + } + children.add(node); + } + + public Node() { + + } + + public Node(int nid, int pid) { + super(); + this.nid = nid; + this.pid = pid; + } + + public Node(int nid, int pid, String filename, Long filesize, int index) { + super(); + this.nid = nid; + this.pid = pid; + this.filename = filename; + this.filesize = filesize; + this.index = index; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((filename == null) ? 0 : filename.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Node other = (Node) obj; + if (filename == null) { + if (other.filename != null) + return false; + } else if (!filename.equals(other.filename)) + return false; + return true; + } +} diff --git a/blade-spider-common/src/main/java/org/springblade/spider/common/entity/Result.java b/blade-spider-common/src/main/java/org/springblade/spider/common/entity/Result.java new file mode 100644 index 00000000..f52269e5 --- /dev/null +++ b/blade-spider-common/src/main/java/org/springblade/spider/common/entity/Result.java @@ -0,0 +1,51 @@ +package org.springblade.spider.common.entity; + +import lombok.Getter; +import lombok.Setter; +import org.apache.http.HttpStatus; + +import java.io.Serializable; + +@Getter +@Setter +public class Result implements Serializable { + + private int status; + private String msg; + private T data; + + public Result(int status) { + this.status = status; + } + + private Result(int status, String msg, T data) { + this.status = status; + this.msg = msg; + this.data = data; + } + + public static Result ok() { + return ok(null); + } + + public static Result ok(String msg) { + return ok(msg, null); + } + + public static Result ok(T data) { + return ok(null, data); + } + + public static Result ok(String msg, T data) { + return new Result<>(HttpStatus.SC_OK, msg, data); + } + + public static Result noContent() { + return new Result(HttpStatus.SC_NO_CONTENT); + } + + public static Result notFount() { + return new Result(HttpStatus.SC_NOT_FOUND); + } + +} diff --git a/blade-spider-common/src/main/java/org/springblade/spider/common/entity/Torrent.java b/blade-spider-common/src/main/java/org/springblade/spider/common/entity/Torrent.java new file mode 100644 index 00000000..1bb40f61 --- /dev/null +++ b/blade-spider-common/src/main/java/org/springblade/spider/common/entity/Torrent.java @@ -0,0 +1,47 @@ +package org.springblade.spider.common.entity; + + +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import org.springframework.data.annotation.Id; +import org.springframework.data.elasticsearch.annotations.Document; +import org.springframework.data.elasticsearch.annotations.Mapping; +import org.springframework.data.elasticsearch.annotations.Setting; + +import java.io.Serializable; + +@Getter +@Setter +@Builder +@ToString +@Document(indexName = "dodder", type = "torrent") +@Mapping(mappingPath = "torrent_search_mapping.json") +@Setting(settingPath = "elasticsearch_custom_comma_analyzer.json") +public class Torrent implements Serializable { + + @Id + private String infoHash; + private String fileType = "其他"; + private String fileName; + + private long fileSize; + + private long createDate; + + private String files; + + public Torrent() { + } + + public Torrent(String infoHash, String fileType, String fileName, long fileSize, long createDate, String files) { + this.infoHash = infoHash; + this.fileType = fileType; + this.fileName = fileName; + this.fileSize = fileSize; + this.createDate = createDate; + this.files = files; + } + +} diff --git a/blade-spider-common/src/main/java/org/springblade/spider/common/entity/Tree.java b/blade-spider-common/src/main/java/org/springblade/spider/common/entity/Tree.java new file mode 100644 index 00000000..a283f503 --- /dev/null +++ b/blade-spider-common/src/main/java/org/springblade/spider/common/entity/Tree.java @@ -0,0 +1,134 @@ +package org.springblade.spider.common.entity; + + +import org.springblade.spider.common.util.FileTypeUtil; +import org.springblade.spider.common.util.StringUtil; +import org.springframework.data.annotation.Transient; + +import java.util.ArrayList; +import java.util.List; + +public class Tree { + + private Node root; + + @Transient + private List leaves; + + public Tree() { + super(); + } + + public Tree(String text) { + root = new Node(0, -1, text, null, -1); + } + + public void createTree(List nodes) { + for (Node node : nodes) { + //父亲是根节点,直接添加到根节点下面 + if (node.getPid() == root.getNid()) { + root.addChild(node); + } else { //父亲是其他节点 + Node parent = findParent(root, node.getPid()); + if (parent != null) { + parent.addChild(node); + } + } + } + } + + private Node findParent(Node node, int pid) { + Node result = null; + for (Node n : node.getChildren()) { + if (n.getNid() == pid) { + return n; + } else { + //递归搜索 + if (n.getChildren() != null) + result = findParent(n, pid); + } + } + return result; + } + + public void middlePrint(Node tnode) { + if (tnode.getChildren() == null) { + return; + } + for (Node node : tnode.getChildren()) { + System.out.println(node.getFilename()); + middlePrint(node); + } + } + + /** + * 构建叶子节点数组,实际上就是构建子文件列表 + * @return + */ + @Transient + public List getLeafList() { + leaves = new ArrayList<>(); + deep(root); + return leaves; + } + + private void deep(Node tnode) { + if (tnode.getChildren() == null) { //叶子节点 + if (leaves.size() < 3) + leaves.add(tnode); + return; + } + if (leaves.size() >= 3) + return; + for (Node node : tnode.getChildren()) { + deep(node); + } + } + + public String getHtml(Node tnode) { + + if (tnode.getChildren() == null) { //叶子节点 + return "
  • " + tnode.getFilename() + + ((tnode.getFilesize() != null) ? "(" + StringUtil.formatSize(tnode.getFilesize()) + ")" + "" : "") + + "
  • "; + } + + String str = ""; + if (tnode.getNid() == root.getNid()) { //根节点 + str += "

      " + root.getFilename() + "

      "; + } else { //子节点 + str += "
    • " + tnode.getFilename() + "
        "; + } + + for (Node node : tnode.getChildren()) { + str += getHtml(node); + } + + if (tnode == root) { //根节点 + return str += "
      "; + } else { //子节点 + return str += "
    "; + } + } + + public boolean checkExist(Node tnode, String text) { + boolean exist = false; + if (tnode.getChildren() == null) { + return false; + } + for (Node node : tnode.getChildren()) { + exist |= checkExist(node, text); + } + return exist; + } + + public Node getRoot() { + return root; + } + + public void setRoot(Node root) { + this.root = root; + } + + +} diff --git a/blade-spider-common/src/main/java/org/springblade/spider/common/entity/WebConfig.java b/blade-spider-common/src/main/java/org/springblade/spider/common/entity/WebConfig.java new file mode 100644 index 00000000..4dad0dff --- /dev/null +++ b/blade-spider-common/src/main/java/org/springblade/spider/common/entity/WebConfig.java @@ -0,0 +1,21 @@ +package org.springblade.spider.common.entity; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.data.annotation.Id; + +import java.util.Date; + +@Getter +@Setter +public class WebConfig { + + @Id + private String id; + + private String adminUsername; + private String adminPassword; + + private Date startTime; + +} diff --git a/blade-spider-common/src/main/java/org/springblade/spider/common/util/BloomFilter.java b/blade-spider-common/src/main/java/org/springblade/spider/common/util/BloomFilter.java new file mode 100644 index 00000000..78724581 --- /dev/null +++ b/blade-spider-common/src/main/java/org/springblade/spider/common/util/BloomFilter.java @@ -0,0 +1,242 @@ +package org.springblade.spider.common.util; + +import java.io.*; +import java.util.BitSet; +import java.util.concurrent.atomic.AtomicInteger; + +public class BloomFilter implements Serializable { + private static final long serialVersionUID = -5221305273707291280L; + private final int[] seeds; + private final int size; + private final BitSet notebook; + private final MisjudgmentRate rate; + private final AtomicInteger useCount = new AtomicInteger(0); + private final Double autoClearRate; + + /** + * 默认中等程序的误判率:MisjudgmentRate.MIDDLE 以及不自动清空数据(性能会有少许提升) + * + * @param dataCount + * 预期处理的数据规模,如预期用于处理1百万数据的查重,这里则填写1000000 + */ + public BloomFilter(int dataCount) { + this(MisjudgmentRate.MIDDLE, dataCount, null); + } + + /** + * + * @param rate + * 一个枚举类型的误判率 + * @param dataCount + * 预期处理的数据规模,如预期用于处理1百万数据的查重,这里则填写1000000 + * @param autoClearRate + * 自动清空过滤器内部信息的使用比率,传null则表示不会自动清理, + * 当过滤器使用率达到100%时,则无论传入什么数据,都会认为在数据已经存在了 + * 当希望过滤器使用率达到80%时自动清空重新使用,则传入0.8 + */ + public BloomFilter(MisjudgmentRate rate, int dataCount, Double autoClearRate) { + long bitSize = rate.seeds.length * dataCount; + if (bitSize < 0 || bitSize > Integer.MAX_VALUE) { + throw new RuntimeException("位数太大溢出了,请降低误判率或者降低数据大小"); + } + this.rate = rate; + seeds = rate.seeds; + size = (int) bitSize; + notebook = new BitSet(size); + this.autoClearRate = autoClearRate; + } + + public void add(String data) { + checkNeedClear(); + + for (int i = 0; i < seeds.length; i++) { + int index = hash(data, seeds[i]); + setTrue(index); + } + } + + public boolean check(String data) { + for (int i = 0; i < seeds.length; i++) { + int index = hash(data, seeds[i]); + if (!notebook.get(index)) { + return false; + } + } + return true; + } + + /** + * 如果不存在就进行记录并返回false,如果存在了就返回true + * + * @param data + * @return + */ + public boolean addIfNotExist(String data) { + checkNeedClear(); + + int[] indexs = new int[seeds.length]; + // 先假定存在 + boolean exist = true; + int index; + + for (int i = 0; i < seeds.length; i++) { + indexs[i] = index = hash(data, seeds[i]); + + if (exist) { + if (!notebook.get(index)) { + // 只要有一个不存在,就可以认为整个字符串都是第一次出现的 + exist = false; + // 补充之前的信息 + for (int j = 0; j <= i; j++) { + setTrue(indexs[j]); + } + } + } else { + setTrue(index); + } + } + + return exist; + + } + + private void checkNeedClear() { + if (autoClearRate != null) { + if (getUseRate() >= autoClearRate) { + synchronized (this) { + if (getUseRate() >= autoClearRate) { + notebook.clear(); + useCount.set(0); + } + } + } + } + } + + public void setTrue(int index) { + useCount.incrementAndGet(); + notebook.set(index, true); + } + + private int hash(String data, int seeds) { + char[] value = data.toCharArray(); + int hash = 0; + if (value.length > 0) { + + for (int i = 0; i < value.length; i++) { + hash = i * hash + value[i]; + } + } + + hash = hash * seeds % size; + // 防止溢出变成负数 + return Math.abs(hash); + } + + public double getUseRate() { + return (double) useCount.intValue() / (double) size; + } + + public synchronized void saveFilterToFile(String path) { + try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(path))) { + oos.writeObject(this); + } catch (Exception e) { + throw new RuntimeException(e); + } + + } + + public static BloomFilter readFilterFromFile(String path) { + try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(path))) { + return (BloomFilter) ois.readObject(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * 清空过滤器中的记录信息 + */ + public void clear() { + useCount.set(0); + notebook.clear(); + } + + public MisjudgmentRate getRate() { + return rate; + } + + /** + * 分配的位数越多,误判率越低但是越占内存 + * + * 4个位误判率大概是0.14689159766308 + * + * 8个位误判率大概是0.02157714146322 + * + * 16个位误判率大概是0.00046557303372 + * + * 32个位误判率大概是0.00000021167340 + * + * @author lianghaohui + * + */ + public enum MisjudgmentRate { + // 这里要选取质数,能很好的降低错误率 + /** + * 每个字符串分配4个位 + */ + VERY_SMALL(new int[] { 2, 3, 5, 7 }), + /** + * 每个字符串分配8个位 + */ + SMALL(new int[] { 2, 3, 5, 7, 11, 13, 17, 19 }), // + /** + * 每个字符串分配16个位 + */ + MIDDLE(new int[] { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53 }), // + /** + * 每个字符串分配32个位 + */ + HIGH(new int[] { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, + 101, 103, 107, 109, 113, 127, 131 }); + + private int[] seeds; + + private MisjudgmentRate(int[] seeds) { + this.seeds = seeds; + } + + public int[] getSeeds() { + return seeds; + } + + public void setSeeds(int[] seeds) { + this.seeds = seeds; + } + + } + + public static void main(String[] args) { + BloomFilter fileter = new BloomFilter(100000000); + fileter.add("1111111111111"); + System.out.println(fileter.addIfNotExist("2222222222222222")); + System.out.println(fileter.addIfNotExist("3333333333333333")); + System.out.println(fileter.addIfNotExist("444444444444444")); + System.out.println(fileter.addIfNotExist("5555555555555")); + System.out.println(fileter.addIfNotExist("6666666666666")); + System.out.println(fileter.check("1111111111111")); + /*fileter.saveFilterToFile("C:\\Users\\john\\Desktop\\1111\\11.obj"); + fileter = readFilterFromFile("C:\\Users\\john\\Desktop\\111\\11.obj"); + System.out.println(fileter.getUseRate()); + System.out.println(fileter.addIfNotExist("1111111111111"));*/ + String path = System.getProperty("java.class.path"); + System.out.println(path); + int firstIndex = path.lastIndexOf(System.getProperty("path.separator")) + 1; + int lastIndex = path.lastIndexOf(File.separator) + 1; + path = path.substring(firstIndex, lastIndex); + System.out.println(path); + + System.out.println(ByteUtil.byteArrayToHex(NodeIdUtil.createRandomNodeId())); + System.out.println(NetworkUtil.getCurrentEnvironmentNetworkIp()); + } +} diff --git a/blade-spider-common/src/main/java/org/springblade/spider/common/util/ByteUtil.java b/blade-spider-common/src/main/java/org/springblade/spider/common/util/ByteUtil.java new file mode 100644 index 00000000..d9c85f6a --- /dev/null +++ b/blade-spider-common/src/main/java/org/springblade/spider/common/util/ByteUtil.java @@ -0,0 +1,89 @@ +package org.springblade.spider.common.util; + +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.IOException; + +public class ByteUtil { + + /** + * byte数组转16进制字符串 + * @param bytes 待转换byte数组 + * @return 16进制字符串 + */ + public static String byteArrayToHex(byte[] bytes) { + return byteArrayToHex(bytes, false); + } + + + /** + * byte数组转16进制字符串 + * @param bytes 待转换byte数组 + * @param is + * @return 16进制字符串 + */ + public static String byteArrayToHex(byte[] bytes, boolean is) { + if (bytes == null || bytes.length == 0) { + return null; + } + + StringBuffer sb = new StringBuffer(bytes.length * 2); + String hexNumber; + for (int x = 0; x < bytes.length; x++) { + hexNumber = "0" + Integer.toHexString(0xff & bytes[x]); + + if (is) + sb.append("%"); + sb.append(hexNumber.substring(hexNumber.length() - 2)); + } + return sb.toString(); + } + + public static byte[] hexStringToBytes(String hexString) { + if (hexString == null || hexString.equals("")) { + return null; + } + hexString = hexString.toUpperCase(); + int length = hexString.length() / 2; + char[] hexChars = hexString.toCharArray(); + byte[] d = new byte[length]; + for (int i = 0; i < length; i++) { + int pos = i * 2; + d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1])); + } + return d; + } + + private static byte charToByte(char c) { + return (byte) "0123456789ABCDEF".indexOf(c); + } + + /** + * int 转 byte 数组 + * @param value 待转换 int + * @return 转换后的 byte 数组 + */ + public static byte[] intToByteArray(int value) { + return new byte[]{ + (byte) (value >>> 24), + (byte) (value >>> 16), + (byte) (value >>> 8), + (byte) value}; + } + + /** + * byte[] 转 int + * @param bRefArr 待转换 byte[] + * @return 转换结果 int + */ + public static int byteArrayToInt(byte[] bRefArr) { + int r = -1; + try (ByteArrayInputStream bintput = new ByteArrayInputStream(bRefArr); + DataInputStream dintput = new DataInputStream(bintput)) { + r = dintput.readInt(); + } catch (IOException e) { + e.printStackTrace(); + } + return r; + } +} diff --git a/blade-spider-common/src/main/java/org/springblade/spider/common/util/ExtensionUtil.java b/blade-spider-common/src/main/java/org/springblade/spider/common/util/ExtensionUtil.java new file mode 100644 index 00000000..0e9c41c6 --- /dev/null +++ b/blade-spider-common/src/main/java/org/springblade/spider/common/util/ExtensionUtil.java @@ -0,0 +1,103 @@ +package org.springblade.spider.common.util; + +import java.util.HashMap; +import java.util.Map; + +/*** + * 文件扩展名工具类 + * + * @author Mr.Xu + * @date 2019-02-22 15:11 + **/ +public class ExtensionUtil { + + private static final Map EXT; + + static { + EXT = new HashMap<>(); + EXT.put(".aif", "音频"); + EXT.put(".aifc", "音频"); + EXT.put(".aiff", "音频"); + EXT.put(".mid", "音频"); + EXT.put(".mp3", "音频"); + EXT.put(".wav", "音频"); + EXT.put(".wma", "音频"); + EXT.put(".amr", "音频"); + EXT.put(".aac", "音频"); + EXT.put(".flac", "音频"); + + + EXT.put(".asf", "视频"); + EXT.put(".mpg", "视频"); + EXT.put(".rm", "视频"); + EXT.put(".avi", "视频"); + EXT.put(".rmvb", "视频"); + EXT.put(".mp4", "视频"); + EXT.put(".wmv", "视频"); + EXT.put(".mkv", "视频"); + EXT.put(".m2ts", "视频"); + EXT.put(".flv", "视频"); + EXT.put(".qmv", "视频"); + EXT.put(".mov", "视频"); + EXT.put(".vob", "视频"); + EXT.put(".3gp", "视频"); + EXT.put(".mpg", "视频"); + EXT.put(".mpeg", "视频"); + EXT.put(".m4v", "视频"); + EXT.put(".f4v", "视频"); + + EXT.put(".jpg", "图片"); + EXT.put(".bmp", "图片"); + EXT.put(".jpeg", "图片"); + EXT.put(".png", "图片"); + EXT.put(".gif", "图片"); + EXT.put(".tiff", "图片"); + + EXT.put(".pdf", "文档"); + EXT.put(".isz", "文档"); + EXT.put(".chm", "文档"); + EXT.put(".txt", "文档"); + EXT.put(".epub", "文档"); + EXT.put(".bc!", "文档"); + EXT.put(".doc", "文档"); + EXT.put(".ppt", "文档"); + EXT.put(".xls", "文档"); + + EXT.put(".rar", "压缩文件"); + EXT.put(".zip", "压缩文件"); + EXT.put(".7z", "压缩文件"); + EXT.put(".gz", "压缩文件"); + EXT.put(".war", "压缩文件"); + EXT.put(".z", "压缩文件"); + + EXT.put(".iso", "镜像文件"); + + EXT.put(".exe", "软件"); + EXT.put(".app", "软件"); + EXT.put(".msi", "软件"); + EXT.put(".apk", "软件"); + + } + + public static String getExtensionType(String name) { + + String ext = getExt(name); + if (ext == null) + return null; + + if (ext.endsWith("\\") || ext.endsWith("/")) + ext = ext.substring(0, ext.length() - 1); + + String type = EXT.get(ext); + + return type; + } + + private static String getExt(String name) { + int pos = name.lastIndexOf("."); + if (pos == -1) + return null; + String ext = name.substring(pos); + return ext; + } +} diff --git a/blade-spider-common/src/main/java/org/springblade/spider/common/util/FileTypeUtil.java b/blade-spider-common/src/main/java/org/springblade/spider/common/util/FileTypeUtil.java new file mode 100644 index 00000000..df20cd53 --- /dev/null +++ b/blade-spider-common/src/main/java/org/springblade/spider/common/util/FileTypeUtil.java @@ -0,0 +1,65 @@ +package org.springblade.spider.common.util; + +import java.util.HashMap; +import java.util.Map; + +/** + * Created by Mr.Xu on 2017/4/21. + */ +public class FileTypeUtil { + + private static Map map = new HashMap<>(); + + static { + map.put("jpg", "image"); + map.put("jpeg", "image"); + map.put("gif", "image"); + map.put("png", "image"); + map.put("bmp", "image"); + + map.put("mp4", "video"); + map.put("mkv", "video"); + map.put("ts", "video"); + map.put("rmvb", "video"); + map.put("avi", "video"); + map.put("rm", "video"); + map.put("asf", "video"); + map.put("divx", "video"); + map.put("mpeg", "video"); + map.put("mpe", "video"); + map.put("wmv", "video"); + map.put("vob", "video"); + map.put("flv", "video"); + map.put("3gp", "video"); + + map.put("srt", "subtitle"); + map.put("ass", "subtitle"); + map.put("sub", "subtitle"); + map.put("ssa", "subtitle"); + + map.put("nfo", "info"); + + map.put("xlsx", "excel"); + map.put("xls", "excel"); + map.put("doc", "word"); + map.put("docx", "word"); + } + + public static String getFileType(String fileName) { + if (fileName == null || !fileName.contains(".")) + return "file"; + String type = "file"; + try { + if (fileName.contains("")) { + fileName = fileName.substring(0, fileName.indexOf("")); + } + String sufix = fileName.substring(fileName.lastIndexOf(".") + 1); + type = map.get(sufix); + if (type == null) + type = "file"; + } catch (Exception e) { + return "file"; + } + return type; + } +} diff --git a/blade-spider-common/src/main/java/org/springblade/spider/common/util/JSONUtil.java b/blade-spider-common/src/main/java/org/springblade/spider/common/util/JSONUtil.java new file mode 100644 index 00000000..d9777205 --- /dev/null +++ b/blade-spider-common/src/main/java/org/springblade/spider/common/util/JSONUtil.java @@ -0,0 +1,29 @@ +package org.springblade.spider.common.util; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.IOException; + +public class JSONUtil { + + private static ObjectMapper objectMapper = new ObjectMapper(); + + public static String toJSONString(Object o) { + try { + return objectMapper.writeValueAsString(o); + } catch (JsonProcessingException e) { + e.printStackTrace(); + } + return ""; + } + + public static T parseObject(String json, Class clazz) { + try { + return objectMapper.readValue(json, clazz); + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } +} diff --git a/blade-spider-common/src/main/java/org/springblade/spider/common/util/NetworkUtil.java b/blade-spider-common/src/main/java/org/springblade/spider/common/util/NetworkUtil.java new file mode 100644 index 00000000..a90ed943 --- /dev/null +++ b/blade-spider-common/src/main/java/org/springblade/spider/common/util/NetworkUtil.java @@ -0,0 +1,90 @@ +package org.springblade.spider.common.util; + +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.util.Enumeration; + +/** + * Network Utility class for the network card on a gumstix + */ +public final class NetworkUtil { + /** + * The current host IP address is the IP address from the device. + */ + private static String currentHostIpAddress; + + /** + * @return the current environment's IP address, taking into account the Internet connection to any of the available + * machine's Network interfaces. Examples of the outputs can be in octats or in IPV6 format. + *
    +     *         ==> wlan0
    +     *
    +     *         fec0:0:0:9:213:e8ff:fef1:b717%4
    +     *         siteLocal: true
    +     *         isLoopback: false isIPV6: true
    +     *         130.212.150.216 <<<<<<<<<<<------------- This is the one we want to grab so that we can.
    +     *         siteLocal: false                          address the DSP on the network.
    +     *         isLoopback: false
    +     *         isIPV6: false
    +     *
    +     *         ==> lo
    +     *         0:0:0:0:0:0:0:1%1
    +     *         siteLocal: false
    +     *         isLoopback: true
    +     *         isIPV6: true
    +     *         127.0.0.1
    +     *         siteLocal: false
    +     *         isLoopback: true
    +     *         isIPV6: false
    +     *  
    + */ + public static String getCurrentEnvironmentNetworkIp() { + if (currentHostIpAddress == null) { + Enumeration netInterfaces = null; + try { + netInterfaces = NetworkInterface.getNetworkInterfaces(); + + while (netInterfaces.hasMoreElements()) { + NetworkInterface ni = netInterfaces.nextElement(); + Enumeration address = ni.getInetAddresses(); + while (address.hasMoreElements()) { + InetAddress addr = address.nextElement(); + if (!addr.isLoopbackAddress() && !addr.isSiteLocalAddress() + && !(addr.getHostAddress().indexOf(":") > -1)) { + currentHostIpAddress = addr.getHostAddress(); + } + } + } + if (currentHostIpAddress == null) { + currentHostIpAddress = "127.0.0.1"; + } + + } catch (SocketException e) { + currentHostIpAddress = "127.0.0.1"; + } + } + return currentHostIpAddress; + } + + public static long getIp() { + return ipToLong(getCurrentEnvironmentNetworkIp()); + } + + + public static long ipToLong(String ipAddress) { + + long result = 0; + + String[] ipAddressInArray = ipAddress.split("\\."); + + for (int i = 3; i >= 0; i--) { + + long ip = Long.parseLong(ipAddressInArray[3 - i]); + result |= ip << (i * 8); + + } + return result; + } + +} diff --git a/blade-spider-common/src/main/java/org/springblade/spider/common/util/NodeIdUtil.java b/blade-spider-common/src/main/java/org/springblade/spider/common/util/NodeIdUtil.java new file mode 100644 index 00000000..3d6d2aae --- /dev/null +++ b/blade-spider-common/src/main/java/org/springblade/spider/common/util/NodeIdUtil.java @@ -0,0 +1,107 @@ +package org.springblade.spider.common.util; + + +import org.apache.commons.codec.digest.PureJavaCrc32C; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Date; +import java.util.Random; + +/*** + * NodeId Util + * + * @author Mr.Xu + * @simce 2019-02-15 17:36 + **/ +public class NodeIdUtil { + + private static MessageDigest messageDigest; + private static Random random; + static + { + try { + messageDigest = MessageDigest.getInstance("SHA-1"); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + random = new Random(new Date().getTime()); + } + + /** + * See https://libtorrent.org/dht_sec.html + * + * @return byte[] node id + */ + public static byte[] randSelfNodeId() { + long ip = NetworkUtil.getIp(); + return getNodeIdByIp(ip); + } + + /** + * 根据安全扩展协议生成 node_id,规则:保持前三个与最后一个字节不变,中间的从其他节点 ID 中复制 + * 用于确保 find_node 与其他回复中的 id 保持一致,并且对外每个节点回复不同的 ID + * + * @param selfId //第一次初始时的 node_id + * @param nodeId //其他节点的 id + * @return byte[] node id + */ + public static byte[] makeSelfId(byte[] selfId, byte[] nodeId) { + byte[] bytes = new byte[20]; + bytes[0] = selfId[0]; + bytes[1] = selfId[1]; + bytes[2] = selfId[2]; + System.arraycopy(nodeId, 3, bytes, 3, 16); + bytes[19] = selfId[19]; + return bytes; + } + + /** + * generate random node_id + * + * @return byte[] node id + */ + public static byte[] createRandomNodeId() { + byte[] bytes = new byte[20]; + for (int i = 0; i < bytes.length; i++) + { + bytes[i] = (byte)random.nextInt(256); + } + messageDigest.update(bytes); + return messageDigest.digest(); + } + + /** + * distance: node1 XOR node2 + * + * @param node1 + * @param node2 + * @return node id which closeness to node2 + */ + public static byte[] getNeighbor(byte[] node1, byte[] node2) { + byte[] bytes = new byte[20]; + System.arraycopy(node2, 0, bytes, 0, 18); + System.arraycopy(node1, 18, bytes, 18, 2); + return bytes; + } + + private static byte[] getNodeIdByIp(long ip) { + int rand = new Random().nextInt(256); + int r = rand & 0x7; + + PureJavaCrc32C crc32C = new PureJavaCrc32C(); + crc32C.update(ByteUtil.intToByteArray((int)(ip & 0x030f3fff) | (r << 29)), 0, 4); + long crc = crc32C.getValue(); + + byte[] node_id = new byte[20]; + node_id[0] = (byte)((crc >> 24) & 0xff); + node_id[1] = (byte)((crc >> 16) & 0xff); + node_id[2] = (byte)(((crc >> 8) & 0xf8) | (rand & 0x7)); + for (int i = 3; i < 19; ++i) + node_id[i] = (byte)random.nextInt(256); + node_id[19] = (byte)rand; + + return node_id; + } + +} diff --git a/blade-spider-common/src/main/java/org/springblade/spider/common/util/StringUtil.java b/blade-spider-common/src/main/java/org/springblade/spider/common/util/StringUtil.java new file mode 100644 index 00000000..5433d9b1 --- /dev/null +++ b/blade-spider-common/src/main/java/org/springblade/spider/common/util/StringUtil.java @@ -0,0 +1,208 @@ +package org.springblade.spider.common.util; + +import info.monitorenter.cpdetector.io.*; +import org.springblade.spider.common.entity.Node; +import org.springblade.spider.common.entity.Torrent; +import org.springblade.spider.common.entity.Tree; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.text.DecimalFormat; +import java.util.Arrays; +import java.util.List; + +/** + * Created by Administrator on 2016/3/14. + */ +public class StringUtil { + + public static CodepageDetectorProxy detector = CodepageDetectorProxy.getInstance(); + + static { + detector.add(new ParsingDetector(false)); + detector.add(JChardetFacade.getInstance()); + detector.add(ASCIIDetector.getInstance()); + detector.add(UnicodeDetector.getInstance()); + } + + public static String getMiddleString(String text, String lStr, String rStr) { + String result = ""; + int left = -1; + int right = -1; + left = text.indexOf(lStr); + if (left >= 0) { + right = text.indexOf(rStr, left); + } + + if (left >= 0 && right > 0) { + result = text.substring(left + lStr.length(), right); + } + return result; + } + + public static String deleteCRLFOnce(String input) { + return input.replaceAll("((\r\n)|\n)[\\s\t ]*(\\1)+", "$1").replaceAll("^((\r\n)|\n)", ""); + } + + public static String str2HexStr(String str) { + char[] chars = "0123456789ABCDEF".toCharArray(); + StringBuilder sb = new StringBuilder(""); + byte[] bs = str.getBytes(); + + for (int i = 0; i < bs.length; i++) { + int bit = (bs[i] & 0xF0) >> 4; + sb.append(chars[bit]); + bit = bs[i] & 0xF; + sb.append(chars[bit]); + } + return sb.toString(); + } + + public static String hexStr2Str(String hexStr) { + String str = "0123456789ABCDEF"; + char[] hexs = hexStr.toCharArray(); + byte[] bytes = new byte[hexStr.length() / 2]; + + for (int i = 0; i < bytes.length; i++) { + int n = str.indexOf(hexs[(2 * i)]) * 16; + n += str.indexOf(hexs[(2 * i + 1)]); + bytes[i] = ((byte) (n & 0xFF)); + } + return new String(bytes); + } + + public static String byte2HexStr(byte[] b) { + String hs = ""; + String stmp = ""; + for (int n = 0; n < b.length; n++) { + stmp = Integer.toHexString(b[n] & 0xFF); + if (stmp.length() == 1) { + hs = hs + "0" + stmp; + } else { + hs = hs + stmp; + } + } + return hs.toUpperCase(); + } + + private static byte uniteBytes(String src0, String src1) { + byte b0 = Byte.decode("0x" + src0).byteValue(); + b0 = (byte) (b0 << 4); + byte b1 = Byte.decode("0x" + src1).byteValue(); + byte ret = (byte) (b0 | b1); + return ret; + } + + public static byte[] hexStr2Bytes(String src) { + int m = 0; + int n = 0; + int l = src.length() / 2; + byte[] ret = new byte[l]; + for (int i = 0; i < l; i++) { + m = i * 2 + 1; + n = m + 1; + ret[i] = uniteBytes(src.substring(i * 2, m), src.substring(m, n)); + } + return ret; + } + + public static String str2Unicode(String strText) throws Exception { + String strRet = ""; + + for (int i = 0; i < strText.length(); i++) { + char c = strText.charAt(i); + int intAsc = c; + String strHex = Integer.toHexString(intAsc); + if (intAsc > 128) { + strRet = strRet + "//u" + strHex; + } else { + strRet = strRet + "//u00" + strHex; + } + } + return strRet; + } + + public static String unicode2Str(String hex) { + int t = hex.length() / 6; + StringBuilder str = new StringBuilder(); + for (int i = 0; i < t; i++) { + String s = hex.substring(i * 6, (i + 1) * 6); + + String s1 = s.substring(2, 4) + "00"; + + String s2 = s.substring(4); + + int n = Integer.valueOf(s1, 16).intValue() + Integer.valueOf(s2, 16).intValue(); + + char[] chars = Character.toChars(n); + str.append(new String(chars)); + } + return str.toString(); + } + + ///清除html格式 + public static String delTagsFContent(String content){ + return content.replaceAll("<]*>",""); + } + + public static String formatSize(double size) { + + int rank = 0; + String rankchar = "Bytes"; + + while (size > 1024) { + size = size / 1024; + rank++; + } + switch (rank) { + case 1: + rankchar = "KB"; + break; + case 2: + rankchar = "MB"; + break; + case 3: + rankchar = "GB"; + break; + default: + rankchar = "B"; + } + return new DecimalFormat("0.##").format(size) + " " + rankchar; + } + + /** + * 获取 byte[] 编码类型 + * + * @param bytes bytes数组 + * @return 编码类型 + */ + public static String getEncoding(byte[] bytes) { + String defaultEncoding = "UTF-8"; + try(ByteArrayInputStream in = new ByteArrayInputStream(bytes)) { + java.nio.charset.Charset charset = detector.detectCodepage(in, bytes.length); + defaultEncoding = charset.name(); + } catch (IOException e) { + } + return defaultEncoding; + } + + /** + * 获取子文件列表 + * + * @param torrent + * @return java.util.List + */ + public static List getFileList(Torrent torrent) { + if (torrent.getFiles() == null) { //单文件 + int pos = torrent.getFileName().lastIndexOf("."); + String sname = torrent.getFileName(); + if (pos > 0) + torrent.setFileName(sname.substring(0, pos)); + return Arrays.asList(new Node(1, 0, sname, torrent.getFileSize(), 1)); + } + + Tree tree = JSONUtil.parseObject(torrent.getFiles(), Tree.class); + return tree.getLeafList(); + } + +} diff --git a/blade-spider-common/src/main/java/org/springblade/spider/common/util/SystemClock.java b/blade-spider-common/src/main/java/org/springblade/spider/common/util/SystemClock.java new file mode 100644 index 00000000..88d7c303 --- /dev/null +++ b/blade-spider-common/src/main/java/org/springblade/spider/common/util/SystemClock.java @@ -0,0 +1,55 @@ +package org.springblade.spider.common.util; + +import java.sql.Timestamp; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicLong; + +public class SystemClock { + + private final long period; + private final AtomicLong now; + ExecutorService executor = Executors.newSingleThreadExecutor(); + + private SystemClock(long period) { + this.period = period; + this.now = new AtomicLong(System.currentTimeMillis()); + scheduleClockUpdating(); + } + + private static class InstanceHolder { + public static final SystemClock INSTANCE = new SystemClock(1); + } + + private static SystemClock instance() { + return InstanceHolder.INSTANCE; + } + + private void scheduleClockUpdating() { + ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() { + @Override + public Thread newThread(Runnable runnable) { + Thread thread = new Thread(runnable, "System Clock"); + thread.setDaemon(true); + return thread; + } + }); + scheduler.scheduleAtFixedRate(new Runnable() { + @Override + public void run() { + now.set(System.currentTimeMillis()); + } + }, period, period, TimeUnit.MILLISECONDS); + } + + private long currentTimeMillis() { + return now.get(); + } + + public static long now() { + return instance().currentTimeMillis(); + } + + public static String nowDate() { + return new Timestamp(instance().currentTimeMillis()).toString(); + } +} diff --git a/blade-spider-common/src/main/java/org/springblade/spider/common/util/bencode/BencodingInputStream.java b/blade-spider-common/src/main/java/org/springblade/spider/common/util/bencode/BencodingInputStream.java new file mode 100644 index 00000000..812e07cd --- /dev/null +++ b/blade-spider-common/src/main/java/org/springblade/spider/common/util/bencode/BencodingInputStream.java @@ -0,0 +1,275 @@ +package org.springblade.spider.common.util.bencode; + +import java.io.*; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +public class BencodingInputStream extends FilterInputStream implements DataInput { + private final String encoding; + private final boolean decodeAsString; + + public BencodingInputStream(InputStream var1) { + this(var1, "UTF-8", false); + } + + public BencodingInputStream(InputStream var1, String var2) { + this(var1, var2, false); + } + + public BencodingInputStream(InputStream var1, boolean var2) { + this(var1, "UTF-8", var2); + } + + public BencodingInputStream(InputStream var1, String var2, boolean var3) { + super(var1); + if (var2 == null) { + throw new NullPointerException("encoding"); + } else { + this.encoding = var2; + this.decodeAsString = var3; + } + } + + public String getEncoding() { + return this.encoding; + } + + public boolean isDecodeAsString() { + return this.decodeAsString; + } + + public Object readObject() throws IOException { + int var1 = this.read(); + if (var1 == -1) { + throw new EOFException(); + } else { + return this.readObject(var1); + } + } + + protected Object readObject(int var1) throws IOException { + if (var1 == 100) { + return this.readMap0(); + } else if (var1 == 108) { + return this.readList0(); + } else if (var1 == 105) { + return this.readNumber0(); + } else if (isDigit(var1)) { + byte[] var2 = this.readBytes(var1); + return this.decodeAsString ? new String(var2, this.encoding) : var2; + } else { + return this.readCustom(var1); + } + } + + protected Object readCustom(int var1) throws IOException { + throw new IOException("Not implemented: " + var1); + } + + public byte[] readBytes() throws IOException { + int var1 = this.read(); + if (var1 == -1) { + throw new EOFException(); + } else { + return this.readBytes(var1); + } + } + + private byte[] readBytes(int var1) throws IOException { + StringBuilder var2 = new StringBuilder(); + var2.append((char)var1); + + while((var1 = this.read()) != 58) { + if (var1 == -1) { + throw new EOFException(); + } + + var2.append((char)var1); + } + + int var3 = Integer.parseInt(var2.toString()); + byte[] var4 = new byte[var3]; + this.readFully(var4); + return var4; + } + + public String readString() throws IOException { + return this.readString(this.encoding); + } + + private String readString(String var1) throws IOException { + return new String(this.readBytes(), var1); + } + + public > T readEnum(Class var1) throws IOException { + return Enum.valueOf(var1, this.readString()); + } + + public char readChar() throws IOException { + return this.readString().charAt(0); + } + + public boolean readBoolean() throws IOException { + return this.readInt() != 0; + } + + public byte readByte() throws IOException { + return this.readNumber().byteValue(); + } + + public short readShort() throws IOException { + return this.readNumber().shortValue(); + } + + public int readInt() throws IOException { + return this.readNumber().intValue(); + } + + public float readFloat() throws IOException { + return this.readNumber().floatValue(); + } + + public long readLong() throws IOException { + return this.readNumber().longValue(); + } + + public double readDouble() throws IOException { + return this.readNumber().doubleValue(); + } + + public Number readNumber() throws IOException { + int var1 = this.read(); + if (var1 == -1) { + throw new EOFException(); + } else if (var1 != 105) { + throw new IOException(); + } else { + return this.readNumber0(); + } + } + + private Number readNumber0() throws IOException { + StringBuilder var1 = new StringBuilder(); + boolean var2 = false; + + int var6; + for(boolean var3 = true; (var6 = this.read()) != 101; var1.append((char)var6)) { + if (var6 == -1) { + throw new EOFException(); + } + + if (var6 == 46) { + var2 = true; + } + } + + try { + if (var2) { + return new BigDecimal(var1.toString()); + } else { + return new BigInteger(var1.toString()); + } + } catch (NumberFormatException var5) { + throw new IOException("NumberFormatException", var5); + } + } + + public List readList() throws IOException { + int var1 = this.read(); + if (var1 == -1) { + throw new EOFException(); + } else if (var1 != 108) { + throw new IOException(); + } else { + return this.readList0(); + } + } + + private List readList0() throws IOException { + ArrayList var1 = new ArrayList(); + boolean var2 = true; + + int var3; + while((var3 = this.read()) != 101) { + if (var3 == -1) { + throw new EOFException(); + } + + var1.add(this.readObject(var3)); + } + + return var1; + } + + public Map readMap() throws IOException { + int var1 = this.read(); + if (var1 == -1) { + throw new EOFException(); + } else if (var1 != 100) { + throw new IOException(); + } else { + return this.readMap0(); + } + } + + private Map readMap0() throws IOException { + TreeMap var1 = new TreeMap(); + boolean var2 = true; + + int var5; + while((var5 = this.read()) != 101) { + if (var5 == -1) { + throw new EOFException(); + } + + String var3 = new String(this.readBytes(var5), this.encoding); + Object var4 = this.readObject(); + var1.put(var3, var4); + } + + return var1; + } + + public void readFully(byte[] var1) throws IOException { + this.readFully(var1, 0, var1.length); + } + + public void readFully(byte[] var1, int var2, int var3) throws IOException { + int var5; + for(int var4 = 0; var4 < var3; var4 += var5) { + var5 = this.read(var1, var4, var3 - var4); + if (var5 == -1) { + throw new EOFException(); + } + } + + } + + public String readLine() throws IOException { + return this.readString(); + } + + public int readUnsignedByte() throws IOException { + return this.readByte() & 255; + } + + public int readUnsignedShort() throws IOException { + return this.readShort() & '\uffff'; + } + + public String readUTF() throws IOException { + return this.readString("UTF-8"); + } + + public int skipBytes(int var1) throws IOException { + return (int)this.skip((long)var1); + } + + private static boolean isDigit(int var0) { + return 48 <= var0 && var0 <= 57; + } +} diff --git a/blade-spider-common/src/main/java/org/springblade/spider/common/util/bencode/BencodingOutputStream.java b/blade-spider-common/src/main/java/org/springblade/spider/common/util/bencode/BencodingOutputStream.java new file mode 100644 index 00000000..b587a661 --- /dev/null +++ b/blade-spider-common/src/main/java/org/springblade/spider/common/util/bencode/BencodingOutputStream.java @@ -0,0 +1,178 @@ +package org.springblade.spider.common.util.bencode; + +import java.io.DataOutput; +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.lang.reflect.Array; +import java.util.*; + +public class BencodingOutputStream extends FilterOutputStream implements DataOutput { + private final String encoding; + + public BencodingOutputStream(OutputStream var1) { + this(var1, "UTF-8"); + } + + public BencodingOutputStream(OutputStream var1, String var2) { + super(var1); + if (var2 == null) { + throw new NullPointerException("encoding"); + } else { + this.encoding = var2; + } + } + + public String getEncoding() { + return this.encoding; + } + + public void writeObject(Object var1) throws IOException { + if (var1 == null) { + this.writeNull(); + } else if (var1 instanceof byte[]) { + this.writeBytes((byte[])((byte[])var1)); + } else if (var1 instanceof Boolean) { + this.writeBoolean((Boolean)var1); + } else if (var1 instanceof Character) { + this.writeChar((Character)var1); + } else if (var1 instanceof Number) { + this.writeNumber((Number)var1); + } else if (var1 instanceof String) { + this.writeString((String)var1); + } else if (var1 instanceof Collection) { + this.writeCollection((Collection)var1); + } else if (var1 instanceof Map) { + this.writeMap((Map)var1); + } else if (var1 instanceof Enum) { + this.writeEnum((Enum)var1); + } else if (var1.getClass().isArray()) { + this.writeArray(var1); + } else { + this.writeCustom(var1); + } + + } + + public void writeNull() throws IOException { + throw new IOException("Null is not supported"); + } + + protected void writeCustom(Object var1) throws IOException { + throw new IOException("Cannot bencode " + var1); + } + + public void writeBytes(byte[] var1) throws IOException { + this.writeBytes(var1, 0, var1.length); + } + + public void writeBytes(byte[] var1, int var2, int var3) throws IOException { + this.write(Integer.toString(var3).getBytes(this.encoding)); + this.write(58); + this.write(var1, var2, var3); + } + + public void writeBoolean(boolean var1) throws IOException { + this.writeNumber(var1 ? BencodingUtils.TRUE : BencodingUtils.FALSE); + } + + public void writeChar(int var1) throws IOException { + this.writeString(Character.toString((char)var1)); + } + + public void writeByte(int var1) throws IOException { + this.writeNumber((byte)var1); + } + + public void writeShort(int var1) throws IOException { + this.writeNumber((short)var1); + } + + public void writeInt(int var1) throws IOException { + this.writeNumber(var1); + } + + public void writeLong(long var1) throws IOException { + this.writeNumber(var1); + } + + public void writeFloat(float var1) throws IOException { + this.writeNumber(var1); + } + + public void writeDouble(double var1) throws IOException { + this.writeNumber(var1); + } + + public void writeNumber(Number var1) throws IOException { + String var2 = var1.toString(); + this.write(105); + this.write(var2.getBytes(this.encoding)); + this.write(101); + } + + public void writeString(String var1) throws IOException { + this.writeBytes(var1.getBytes(this.encoding)); + } + + public void writeCollection(Collection var1) throws IOException { + this.write(108); + Iterator var2 = var1.iterator(); + + while(var2.hasNext()) { + Object var3 = var2.next(); + this.writeObject(var3); + } + + this.write(101); + } + + public void writeMap(Map var1) throws IOException { + if (!(var1 instanceof SortedMap)) { + var1 = new TreeMap((Map)var1); + } + + this.write(100); + + Object var5; + for(Iterator var2 = ((Map)var1).entrySet().iterator(); var2.hasNext(); this.writeObject(var5)) { + Map.Entry var3 = (Map.Entry)var2.next(); + Object var4 = var3.getKey(); + var5 = var3.getValue(); + if (var4 instanceof String) { + this.writeString((String)var4); + } else { + this.writeBytes((byte[])((byte[])var4)); + } + } + + this.write(101); + } + + public void writeEnum(Enum var1) throws IOException { + this.writeString(var1.name()); + } + + public void writeArray(Object var1) throws IOException { + this.write(108); + int var2 = Array.getLength(var1); + + for(int var3 = 0; var3 < var2; ++var3) { + this.writeObject(Array.get(var1, var3)); + } + + this.write(101); + } + + public void writeBytes(String var1) throws IOException { + this.writeString(var1); + } + + public void writeChars(String var1) throws IOException { + this.writeString(var1); + } + + public void writeUTF(String var1) throws IOException { + this.writeBytes(var1.getBytes("UTF-8")); + } +} diff --git a/blade-spider-common/src/main/java/org/springblade/spider/common/util/bencode/BencodingUtils.java b/blade-spider-common/src/main/java/org/springblade/spider/common/util/bencode/BencodingUtils.java new file mode 100644 index 00000000..e7173a76 --- /dev/null +++ b/blade-spider-common/src/main/java/org/springblade/spider/common/util/bencode/BencodingUtils.java @@ -0,0 +1,36 @@ +package org.springblade.spider.common.util.bencode; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.util.Map; + +public class BencodingUtils { + + public static final String UTF_8 = "UTF-8"; + public static final int LENGTH_DELIMITER = 58; + public static final int DICTIONARY = 100; + public static final int LIST = 108; + public static final int NUMBER = 105; + public static final int EOF = 101; + public static final Integer TRUE = 1; + public static final Integer FALSE = 0; + + public static byte[] encode(Map map) { + try (ByteArrayOutputStream stream = new ByteArrayOutputStream(); + BencodingOutputStream bencode = new BencodingOutputStream(stream)) { + bencode.writeMap(map); + return stream.toByteArray(); + } catch (Exception e) { + return new byte[0]; + } + } + + public static Map decode(byte[] bytes) { + try (ByteArrayInputStream stream = new ByteArrayInputStream(bytes); + BencodingInputStream bencode = new BencodingInputStream(stream)) { + return bencode.readMap(); + } catch (Exception e) { + return null; + } + } +} diff --git a/blade-spider-common/src/test/java/org/springblade/AppTest.java b/blade-spider-common/src/test/java/org/springblade/AppTest.java new file mode 100644 index 00000000..77b9542a --- /dev/null +++ b/blade-spider-common/src/test/java/org/springblade/AppTest.java @@ -0,0 +1,20 @@ +package org.springblade; + +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +/** + * Unit test for simple App. + */ +public class AppTest +{ + /** + * Rigorous Test :-) + */ + @Test + public void shouldAnswerWithTrue() + { + assertTrue( true ); + } +} diff --git a/blade-service-api/blade-spider-api/pom.xml b/blade-spider-service-api/blade-datadig-api/pom.xml similarity index 54% rename from blade-service-api/blade-spider-api/pom.xml rename to blade-spider-service-api/blade-datadig-api/pom.xml index 6c079d40..b3f76f21 100644 --- a/blade-service-api/blade-spider-api/pom.xml +++ b/blade-spider-service-api/blade-datadig-api/pom.xml @@ -1,7 +1,7 @@ + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> blade-service-api org.springblade @@ -9,9 +9,17 @@ 4.0.0 - blade-spider-api + blade-datadig-api ${project.artifactId} ${blade.project.version} jar + + + org.springblade + blade-spider-common + ${blade.tool.version} + + + diff --git a/blade-spider-service-api/blade-datadig-api/src/main/java/org/springblade/feign/TorrentClient.java b/blade-spider-service-api/blade-datadig-api/src/main/java/org/springblade/feign/TorrentClient.java new file mode 100644 index 00000000..7ee66287 --- /dev/null +++ b/blade-spider-service-api/blade-datadig-api/src/main/java/org/springblade/feign/TorrentClient.java @@ -0,0 +1,52 @@ +package org.springblade.feign; + +import org.springblade.core.launch.constant.AppConstant; +import org.springblade.request.SearchRequest; +import org.springblade.spider.common.entity.Result; +import org.springblade.vo.TorrentPageVO; +import org.springblade.vo.TorrentVO; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; + + +/** + * Feign接口类 + * + */ +@FeignClient( + value = AppConstant.APPLICATION_SYSTEM_NAME, + fallback = TorrentClientFallback.class +) +public interface TorrentClient { + + String API_PREFIX = "/dataDig"; + + /** + * 根据 info_hash 判断数据库是否已经存在 + * @param infoHash + * @return org.springframework.http.ResponseEntity + */ + @GetMapping(API_PREFIX + "/getInfoHash") + Result existHash(@RequestParam("infoHash") String infoHash); + + /** + * 根据条件搜索 Torrents + * + * @param request + * @return org.springframework.data.domain.Page + */ + @PostMapping(API_PREFIX + "/searchRequest") + Result torrents(SearchRequest request); + + /** + * 根据 infoHash 查找 Torrent + * + * @param infoHash + * @return cc.dodder.common.entity.Torrent + */ + @GetMapping(API_PREFIX+"/getTorrentVoById") + Result findById(String infoHash); + +} diff --git a/blade-spider-service-api/blade-datadig-api/src/main/java/org/springblade/feign/TorrentClientFallback.java b/blade-spider-service-api/blade-datadig-api/src/main/java/org/springblade/feign/TorrentClientFallback.java new file mode 100644 index 00000000..eae8cc9e --- /dev/null +++ b/blade-spider-service-api/blade-datadig-api/src/main/java/org/springblade/feign/TorrentClientFallback.java @@ -0,0 +1,32 @@ +package org.springblade.feign; + +import org.springblade.request.SearchRequest; +import org.springblade.spider.common.entity.Result; +import org.springblade.vo.TorrentPageVO; +import org.springblade.vo.TorrentVO; +import org.springframework.stereotype.Component; + +/** + * Feign失败配置 + * + * @author zt + */ +@Component +public class TorrentClientFallback implements TorrentClient { + + @Override + public Result existHash(String infoHash){ + return null; + } + + @Override + public Result torrents(SearchRequest request){ + return null; + } + + @Override + public Result findById(String infoHash){ + return null; + } + +} diff --git a/blade-spider-service-api/blade-datadig-api/src/main/java/org/springblade/request/SearchRequest.java b/blade-spider-service-api/blade-datadig-api/src/main/java/org/springblade/request/SearchRequest.java new file mode 100644 index 00000000..27003e21 --- /dev/null +++ b/blade-spider-service-api/blade-datadig-api/src/main/java/org/springblade/request/SearchRequest.java @@ -0,0 +1,28 @@ +package org.springblade.request; + +import lombok.Getter; +import lombok.Setter; + +import java.io.Serializable; + +/*** + * 搜索请求 + * + * @author Mr.Xu + * @date 2019-03-02 13:52 + **/ +@Getter +@Setter +public class SearchRequest implements Serializable { + + private String fileName; + private String fileType; + private String sortBy; + private String order; + + private Integer page; + private Integer limit = 20; + + public SearchRequest() { + } +} diff --git a/blade-spider-service-api/blade-datadig-api/src/main/java/org/springblade/vo/TorrentPageVO.java b/blade-spider-service-api/blade-datadig-api/src/main/java/org/springblade/vo/TorrentPageVO.java new file mode 100644 index 00000000..dad72d1a --- /dev/null +++ b/blade-spider-service-api/blade-datadig-api/src/main/java/org/springblade/vo/TorrentPageVO.java @@ -0,0 +1,20 @@ +package org.springblade.vo; + +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import org.springblade.spider.common.entity.Torrent; + +import java.io.Serializable; +import java.util.List; + +@Getter +@Setter +@Builder +public class TorrentPageVO implements Serializable { + + private List list; + private Long total; + private Integer page; + private Integer limit; +} diff --git a/blade-spider-service-api/blade-datadig-api/src/main/java/org/springblade/vo/TorrentVO.java b/blade-spider-service-api/blade-datadig-api/src/main/java/org/springblade/vo/TorrentVO.java new file mode 100644 index 00000000..d42a68ee --- /dev/null +++ b/blade-spider-service-api/blade-datadig-api/src/main/java/org/springblade/vo/TorrentVO.java @@ -0,0 +1,18 @@ +package org.springblade.vo; + +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import org.springblade.spider.common.entity.Torrent; + +import java.io.Serializable; +import java.util.List; + +@Getter +@Setter +@Builder +public class TorrentVO implements Serializable { + + private Torrent torrent; + private List similar; +} diff --git a/blade-spider-service-api/pom.xml b/blade-spider-service-api/pom.xml index 7e9e5ef5..18b86749 100644 --- a/blade-spider-service-api/pom.xml +++ b/blade-spider-service-api/pom.xml @@ -15,6 +15,10 @@ pom 数据挖掘微服务API集合 + + blade-datadig-api + + org.springblade diff --git a/blade-service/blade-spider/pom.xml b/blade-spider-service/blade-datadig/pom.xml similarity index 72% rename from blade-service/blade-spider/pom.xml rename to blade-spider-service/blade-datadig/pom.xml index 42931e28..6c58bf0e 100644 --- a/blade-service/blade-spider/pom.xml +++ b/blade-spider-service/blade-datadig/pom.xml @@ -9,7 +9,7 @@ 4.0.0 - blade-spider + blade-datadig ${project.artifactId} ${blade.project.version} jar @@ -22,25 +22,15 @@ org.springblade - blade-spider-api + blade-datadig-api ${blade.project.version} - - - com.yishuifengxiao.common - crawler - 2.2.0 - - - - - - - - - org.springframework.boot - spring-boot-starter-test - + + + diff --git a/blade-service/blade-spider/src/main/java/org/springblade/spider/SpiderApplication.java b/blade-spider-service/blade-datadig/src/main/java/org/springblade/datadig/DataDigApplication.java similarity index 56% rename from blade-service/blade-spider/src/main/java/org/springblade/spider/SpiderApplication.java rename to blade-spider-service/blade-datadig/src/main/java/org/springblade/datadig/DataDigApplication.java index 7951f68d..2ad68cd2 100644 --- a/blade-service/blade-spider/src/main/java/org/springblade/spider/SpiderApplication.java +++ b/blade-spider-service/blade-datadig/src/main/java/org/springblade/datadig/DataDigApplication.java @@ -1,4 +1,4 @@ -package org.springblade.spider; +package org.springblade.datadig; import org.springblade.core.launch.BladeApplication; import org.springblade.core.launch.constant.AppConstant; @@ -6,16 +6,16 @@ import org.springframework.cloud.client.SpringCloudApplication; import org.springframework.cloud.openfeign.EnableFeignClients; /** - * Spider启动器 - * + * 数据挖掘模块启动器 * @author Chill */ @SpringCloudApplication -//@EnableFeignClients(AppConstant.BASE_PACKAGES) -public class SpiderApplication { +@EnableFeignClients(AppConstant.BASE_PACKAGES) +public class DataDigApplication { public static void main(String[] args) { - BladeApplication.run(AppConstant.APPLICATION_DESK_NAME, SpiderApplication.class, args); + // AppConstant.APPLICATION_SYSTEM_NAME + BladeApplication.run("blade-datadig", DataDigApplication.class, args); } } diff --git a/blade-service/blade-spider/src/main/resources/application-dev.yml b/blade-spider-service/blade-datadig/src/main/resources/application-dev.yml similarity index 93% rename from blade-service/blade-spider/src/main/resources/application-dev.yml rename to blade-spider-service/blade-datadig/src/main/resources/application-dev.yml index ef8eb13e..1bdfce44 100644 --- a/blade-service/blade-spider/src/main/resources/application-dev.yml +++ b/blade-spider-service/blade-datadig/src/main/resources/application-dev.yml @@ -1,6 +1,6 @@ #服务器端口 server: - port: 8109 + port: 8301 #数据源配置 spring: diff --git a/blade-service/blade-spider/src/main/resources/application-prod.yml b/blade-spider-service/blade-datadig/src/main/resources/application-prod.yml similarity index 100% rename from blade-service/blade-spider/src/main/resources/application-prod.yml rename to blade-spider-service/blade-datadig/src/main/resources/application-prod.yml diff --git a/blade-service/blade-spider/src/main/resources/application-test.yml b/blade-spider-service/blade-datadig/src/main/resources/application-test.yml similarity index 100% rename from blade-service/blade-spider/src/main/resources/application-test.yml rename to blade-spider-service/blade-datadig/src/main/resources/application-test.yml diff --git a/blade-spider-service/blade-datadig/src/test/java/org/springblade/AppTest.java b/blade-spider-service/blade-datadig/src/test/java/org/springblade/AppTest.java new file mode 100644 index 00000000..77b9542a --- /dev/null +++ b/blade-spider-service/blade-datadig/src/test/java/org/springblade/AppTest.java @@ -0,0 +1,20 @@ +package org.springblade; + +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +/** + * Unit test for simple App. + */ +public class AppTest +{ + /** + * Rigorous Test :-) + */ + @Test + public void shouldAnswerWithTrue() + { + assertTrue( true ); + } +} diff --git a/blade-spider-service/doc/thirdJar/sourceforge.rar b/blade-spider-service/doc/thirdJar/sourceforge.rar new file mode 100644 index 0000000000000000000000000000000000000000..fc37a266d465a0701f311f16f706cface97e38bb GIT binary patch literal 3643871 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"+(i.text?''+i.value+"星":"")+"";var c=i.elem,f=c.next("."+t);f[0]&&f.remove(),e.elemTemp=a(n),i.span=e.elemTemp.next("span"),i.setText&&i.setText(i.value),c.html(e.elemTemp),c.addClass("layui-inline"),i.readonly||e.action()},v.prototype.setvalue=function(e){var a=this,i=a.config;i.value=e,a.render()},v.prototype.action=function(){var e=this,i=e.config,l=e.elemTemp,n=l.find("i").width();l.children("li").each(function(e){var t=e+1,v=a(this);v.on("click",function(e){if(i.value=t,i.half){var o=e.pageX-a(this).offset().left;o<=n/2&&(i.value=i.value-.5)}i.text&&l.next("span").text(i.value+"星"),i.choose&&i.choose(i.value),i.setText&&i.setText(i.value)}),v.on("mousemove",function(e){if(l.find("i").each(function(){a(this).addClass(o).removeClass(r)}),l.find("i:lt("+t+")").each(function(){a(this).addClass(s).removeClass(f)}),i.half){var c=e.pageX-a(this).offset().left;c<=n/2&&v.children("i").addClass(u).removeClass(s)}}),v.on("mouseleave",function(){l.find("i").each(function(){a(this).addClass(o).removeClass(r)}),l.find("i:lt("+Math.floor(i.value)+")").each(function(){a(this).addClass(s).removeClass(f)}),i.half&&parseInt(i.value)!==i.value&&l.children("li:eq("+Math.floor(i.value)+")").children("i").addClass(u).removeClass(c)})})},v.prototype.events=function(){var e=this;e.config},i.render=function(e){var a=new v(e);return l.call(a)},e(n,i)});layui.define("jquery",function(t){"use strict";var e=layui.$,i={fixbar:function(t){var i,a,n="layui-fixbar",r="layui-fixbar-top",o=e(document),l=e("body");t=e.extend({showHeight:200},t),t.bar1=t.bar1===!0?"":t.bar1,t.bar2=t.bar2===!0?"":t.bar2,t.bgcolor=t.bgcolor?"background-color:"+t.bgcolor:"";var c=[t.bar1,t.bar2,""],g=e(['
      ',t.bar1?'
    • '+c[0]+"
    • ":"",t.bar2?'
    • '+c[1]+"
    • ":"",'
    • '+c[2]+"
    • ","
    "].join("")),s=g.find("."+r),u=function(){var e=o.scrollTop();e>=t.showHeight?i||(s.show(),i=1):i&&(s.hide(),i=0)};e("."+n)[0]||("object"==typeof t.css&&g.css(t.css),l.append(g),u(),g.find("li").on("click",function(){var i=e(this),a=i.attr("lay-type");"top"===a&&e("html,body").animate({scrollTop:0},200),t.click&&t.click.call(this,a)}),o.on("scroll",function(){clearTimeout(a),a=setTimeout(function(){u()},100)}))},countdown:function(t,e,i){var a=this,n="function"==typeof e,r=new Date(t).getTime(),o=new Date(!e||n?(new Date).getTime():e).getTime(),l=r-o,c=[Math.floor(l/864e5),Math.floor(l/36e5)%24,Math.floor(l/6e4)%60,Math.floor(l/1e3)%60];n&&(i=e);var g=setTimeout(function(){a.countdown(t,o+1e3,i)},1e3);return i&&i(l>0?c:[0,0,0,0],e,g),l<=0&&clearTimeout(g),g},timeAgo:function(t,e){var i=this,a=[[],[]],n=(new Date).getTime()-new Date(t).getTime();return n>6912e5?(n=new Date(t),a[0][0]=i.digit(n.getFullYear(),4),a[0][1]=i.digit(n.getMonth()+1),a[0][2]=i.digit(n.getDate()),e||(a[1][0]=i.digit(n.getHours()),a[1][1]=i.digit(n.getMinutes()),a[1][2]=i.digit(n.getSeconds())),a[0].join("-")+" "+a[1].join(":")):n>=864e5?(n/1e3/60/60/24|0)+"天前":n>=36e5?(n/1e3/60/60|0)+"小时前":n>=12e4?(n/1e3/60|0)+"分钟前":n<0?"未来":"刚刚"},digit:function(t,e){var i="";t=String(t),e=e||2;for(var a=t.length;a/g,">").replace(/'/g,"'").replace(/"/g,""")}};!function(t,e,i){"$:nomunge";function a(){n=e[l](function(){r.each(function(){var e=t(this),i=e.width(),a=e.height(),n=t.data(this,g);(i!==n.w||a!==n.h)&&e.trigger(c,[n.w=i,n.h=a])}),a()},o[s])}var n,r=t([]),o=t.resize=t.extend(t.resize,{}),l="setTimeout",c="resize",g=c+"-special-event",s="delay",u="throttleWindow";o[s]=250,o[u]=!0,t.event.special[c]={setup:function(){if(!o[u]&&this[l])return!1;var e=t(this);r=r.add(e),t.data(this,g,{w:e.width(),h:e.height()}),1===r.length&&a()},teardown:function(){if(!o[u]&&this[l])return!1;var e=t(this);r=r.not(e),e.removeData(g),r.length||clearTimeout(n)},add:function(e){function a(e,a,r){var o=t(this),l=t.data(this,g)||{};l.w=a!==i?a:o.width(),l.h=r!==i?r:o.height(),n.apply(this,arguments)}if(!o[u]&&this[l])return!1;var n;return t.isFunction(e)?(n=e,a):(n=e.handler,void(e.handler=a))}}}(e,window),t("util",i)});layui.define("jquery",function(e){"use strict";var l=layui.$,o=function(e){},t='';o.prototype.load=function(e){var o,i,n,r,a=this,c=0;e=e||{};var f=l(e.elem);if(f[0]){var m=l(e.scrollElem||document),u=e.mb||50,s=!("isAuto"in e)||e.isAuto,v=e.end||"没有更多了",y=e.scrollElem&&e.scrollElem!==document,d="加载更多",h=l('");f.find(".layui-flow-more")[0]||f.append(h);var p=function(e,t){e=l(e),h.before(e),t=0==t||null,t?h.html(v):h.find("a").html(d),i=t,o=null,n&&n()},g=function(){o=!0,h.find("a").html(t),"function"==typeof e.done&&e.done(++c,p)};if(g(),h.find("a").on("click",function(){l(this);i||o||g()}),e.isLazyimg)var n=a.lazyimg({elem:e.elem+" img",scrollElem:e.scrollElem});return s?(m.on("scroll",function(){var e=l(this),t=e.scrollTop();r&&clearTimeout(r),i||(r=setTimeout(function(){var i=y?e.height():l(window).height(),n=y?e.prop("scrollHeight"):document.documentElement.scrollHeight;n-t-i<=u&&(o||g())},100))}),a):a}},o.prototype.lazyimg=function(e){var o,t=this,i=0;e=e||{};var n=l(e.scrollElem||document),r=e.elem||"img",a=e.scrollElem&&e.scrollElem!==document,c=function(e,l){var o=n.scrollTop(),r=o+l,c=a?function(){return e.offset().top-n.offset().top+o}():e.offset().top;if(c>=o&&c<=r&&!e.attr("src")){var m=e.attr("lay-src");layui.img(m,function(){var l=t.lazyimg.elem.eq(i);e.attr("src",m).removeAttr("lay-src"),l[0]&&f(l),i++})}},f=function(e,o){var f=a?(o||n).height():l(window).height(),m=n.scrollTop(),u=m+f;if(t.lazyimg.elem=l(r),e)c(e,f);else for(var s=0;su)break}};if(f(),!o){var m;n.on("scroll",function(){var e=l(this);m&&clearTimeout(m),m=setTimeout(function(){f(null,e)},50)}),o=!0}return f},e("flow",new o)});layui.define(["layer","form"],function(t){"use strict";var e=layui.$,i=layui.layer,a=layui.form,l=(layui.hint(),layui.device()),n="layedit",o="layui-show",r="layui-disabled",c=function(){var t=this;t.index=0,t.config={tool:["strong","italic","underline","del","|","left","center","right","|","link","unlink","face","image"],hideTool:[],height:280}};c.prototype.set=function(t){var i=this;return e.extend(!0,i.config,t),i},c.prototype.on=function(t,e){return layui.onevent(n,t,e)},c.prototype.build=function(t,i){i=i||{};var a=this,n=a.config,r="layui-layedit",c=e("string"==typeof t?"#"+t:t),u="LAY_layedit_"+ ++a.index,d=c.next("."+r),y=e.extend({},n,i),f=function(){var t=[],e={};return layui.each(y.hideTool,function(t,i){e[i]=!0}),layui.each(y.tool,function(i,a){C[a]&&!e[a]&&t.push(C[a])}),t.join("")}(),m=e(['
    ','
    '+f+"
    ",'
    ','',"
    ","
    "].join(""));return l.ie&&l.ie<8?c.removeClass("layui-hide").addClass(o):(d[0]&&d.remove(),s.call(a,m,c[0],y),c.addClass("layui-hide").after(m),a.index)},c.prototype.getContent=function(t){var e=u(t);if(e[0])return d(e[0].document.body.innerHTML)},c.prototype.getText=function(t){var i=u(t);if(i[0])return e(i[0].document.body).text()},c.prototype.setContent=function(t,i,a){var l=u(t);l[0]&&(a?e(l[0].document.body).append(i):e(l[0].document.body).html(i),layedit.sync(t))},c.prototype.sync=function(t){var i=u(t);if(i[0]){var a=e("#"+i[1].attr("textarea"));a.val(d(i[0].document.body.innerHTML))}},c.prototype.getSelection=function(t){var e=u(t);if(e[0]){var i=m(e[0].document);return document.selection?i.text:i.toString()}};var s=function(t,i,a){var l=this,n=t.find("iframe");n.css({height:a.height}).on("load",function(){var o=n.contents(),r=n.prop("contentWindow"),c=o.find("head"),s=e([""].join("")),u=o.find("body");c.append(s),u.attr("contenteditable","true").css({"min-height":a.height}).html(i.value||""),y.apply(l,[r,n,i,a]),g.call(l,r,t,a)})},u=function(t){var i=e("#LAY_layedit_"+t),a=i.prop("contentWindow");return[a,i]},d=function(t){return 8==l.ie&&(t=t.replace(/<.+>/g,function(t){return t.toLowerCase()})),t},y=function(t,a,n,o){var r=t.document,c=e(r.body);c.on("keydown",function(t){var e=t.keyCode;if(13===e){var a=m(r),l=p(a),n=l.parentNode;if("pre"===n.tagName.toLowerCase()){if(t.shiftKey)return;return i.msg("请暂时用shift+enter"),!1}r.execCommand("formatBlock",!1,"

    ")}}),e(n).parents("form").on("submit",function(){var t=c.html();8==l.ie&&(t=t.replace(/<.+>/g,function(t){return t.toLowerCase()})),n.value=t}),c.on("paste",function(e){r.execCommand("formatBlock",!1,"

    "),setTimeout(function(){f.call(t,c),n.value=c.html()},100)})},f=function(t){var i=this;i.document;t.find("*[style]").each(function(){var t=this.style.textAlign;this.removeAttribute("style"),e(this).css({"text-align":t||""})}),t.find("table").addClass("layui-table"),t.find("script,link").remove()},m=function(t){return t.selection?t.selection.createRange():t.getSelection().getRangeAt(0)},p=function(t){return t.endContainer||t.parentElement().childNodes[0]},v=function(t,i,a){var l=this.document,n=document.createElement(t);for(var o in i)n.setAttribute(o,i[o]);if(n.removeAttribute("text"),l.selection){var r=a.text||i.text;if("a"===t&&!r)return;r&&(n.innerHTML=r),a.pasteHTML(e(n).prop("outerHTML")),a.select()}else{var r=a.toString()||i.text;if("a"===t&&!r)return;r&&(n.innerHTML=r),a.deleteContents(),a.insertNode(n)}},h=function(t,i){var a=this.document,l="layedit-tool-active",n=p(m(a)),o=function(e){return t.find(".layedit-tool-"+e)};i&&i[i.hasClass(l)?"removeClass":"addClass"](l),t.find(">i").removeClass(l),o("unlink").addClass(r),e(n).parents().each(function(){var t=this.tagName.toLowerCase(),e=this.style.textAlign;"b"!==t&&"strong"!==t||o("b").addClass(l),"i"!==t&&"em"!==t||o("i").addClass(l),"u"===t&&o("u").addClass(l),"strike"===t&&o("d").addClass(l),"p"===t&&("center"===e?o("center").addClass(l):"right"===e?o("right").addClass(l):o("left").addClass(l)),"a"===t&&(o("link").addClass(l),o("unlink").removeClass(r))})},g=function(t,a,l){var n=t.document,o=e(n.body),c={link:function(i){var a=p(i),l=e(a).parent();b.call(o,{href:l.attr("href"),target:l.attr("target")},function(e){var a=l[0];"A"===a.tagName?a.href=e.url:v.call(t,"a",{target:e.target,href:e.url,text:e.url},i)})},unlink:function(t){n.execCommand("unlink")},face:function(e){x.call(this,function(i){v.call(t,"img",{src:i.src,alt:i.alt},e)})},image:function(a){var n=this;layui.use("upload",function(o){var r=l.uploadImage||{};o.render({url:r.url,method:r.type,elem:e(n).find("input")[0],done:function(e){0==e.code?(e.data=e.data||{},v.call(t,"img",{src:e.data.src,alt:e.data.title},a)):i.msg(e.msg||"上传失败")}})})},code:function(e){k.call(o,function(i){v.call(t,"pre",{text:i.code,"lay-lang":i.lang},e)})},help:function(){i.open({type:2,title:"帮助",area:["600px","380px"],shadeClose:!0,shade:.1,skin:"layui-layer-msg",content:["http://www.layui.com/about/layedit/help.html","no"]})}},s=a.find(".layui-layedit-tool"),u=function(){var i=e(this),a=i.attr("layedit-event"),l=i.attr("lay-command");if(!i.hasClass(r)){o.focus();var u=m(n);u.commonAncestorContainer;l?(n.execCommand(l),/justifyLeft|justifyCenter|justifyRight/.test(l)&&n.execCommand("formatBlock",!1,"

    "),setTimeout(function(){o.focus()},10)):c[a]&&c[a].call(this,u),h.call(t,s,i)}},d=/image/;s.find(">i").on("mousedown",function(){var t=e(this),i=t.attr("layedit-event");d.test(i)||u.call(this)}).on("click",function(){var t=e(this),i=t.attr("layedit-event");d.test(i)&&u.call(this)}),o.on("click",function(){h.call(t,s),i.close(x.index)})},b=function(t,e){var l=this,n=i.open({type:1,id:"LAY_layedit_link",area:"350px",shade:.05,shadeClose:!0,moveType:1,title:"超链接",skin:"layui-layer-msg",content:['

      ','
    • ','','
      ','',"
      ","
    • ",'
    • ','','
      ','",'","
      ","
    • ",'
    • ','','',"
    • ","
    "].join(""),success:function(t,n){var o="submit(layedit-link-yes)";a.render("radio"),t.find(".layui-btn-primary").on("click",function(){i.close(n),l.focus()}),a.on(o,function(t){i.close(b.index),e&&e(t.field)})}});b.index=n},x=function(t){var a=function(){var t=["[微笑]","[嘻嘻]","[哈哈]","[可爱]","[可怜]","[挖鼻]","[吃惊]","[害羞]","[挤眼]","[闭嘴]","[鄙视]","[爱你]","[泪]","[偷笑]","[亲亲]","[生病]","[太开心]","[白眼]","[右哼哼]","[左哼哼]","[嘘]","[衰]","[委屈]","[吐]","[哈欠]","[抱抱]","[怒]","[疑问]","[馋嘴]","[拜拜]","[思考]","[汗]","[困]","[睡]","[钱]","[失望]","[酷]","[色]","[哼]","[鼓掌]","[晕]","[悲伤]","[抓狂]","[黑线]","[阴险]","[怒骂]","[互粉]","[心]","[伤心]","[猪头]","[熊猫]","[兔子]","[ok]","[耶]","[good]","[NO]","[赞]","[来]","[弱]","[草泥马]","[神马]","[囧]","[浮云]","[给力]","[围观]","[威武]","[奥特曼]","[礼物]","[钟]","[话筒]","[蜡烛]","[蛋糕]"],e={};return layui.each(t,function(t,i){e[i]=layui.cache.dir+"images/face/"+t+".gif"}),e}();return x.hide=x.hide||function(t){"face"!==e(t.target).attr("layedit-event")&&i.close(x.index)},x.index=i.tips(function(){var t=[];return layui.each(a,function(e,i){t.push('
  • '+e+'
  • ')}),'
      '+t.join("")+"
    "}(),this,{tips:1,time:0,skin:"layui-box layui-util-face",maxWidth:500,success:function(l,n){l.css({marginTop:-4,marginLeft:-10}).find(".layui-clear>li").on("click",function(){t&&t({src:a[this.title],alt:this.title}),i.close(n)}),e(document).off("click",x.hide).on("click",x.hide)}})},k=function(t){var e=this,l=i.open({type:1,id:"LAY_layedit_code",area:"550px",shade:.05,shadeClose:!0,moveType:1,title:"插入代码",skin:"layui-layer-msg",content:['
      ','
    • ','','
      ','","
      ","
    • ",'
    • ','','
      ','',"
      ","
    • ",'
    • ','','',"
    • ","
    "].join(""),success:function(l,n){var o="submit(layedit-code-yes)";a.render("select"),l.find(".layui-btn-primary").on("click",function(){i.close(n),e.focus()}),a.on(o,function(e){i.close(k.index),t&&t(e.field)})}});k.index=l},C={html:'',strong:'',italic:'',underline:'',del:'',"|":'',left:'',center:'',right:'',link:'',unlink:'',face:'',image:'',code:'',help:''},w=new c;t(n,w)});layui.define("jquery",function(e){"use strict";var a=layui.$,l="http://www.layui.com/doc/modules/code.html";e("code",function(e){var t=[];e=e||{},e.elem=a(e.elem||".layui-code"),e.about=!("about"in e)||e.about,e.elem.each(function(){t.push(this)}),layui.each(t.reverse(),function(t,i){var c=a(i),o=c.html();(c.attr("lay-encode")||e.encode)&&(o=o.replace(/&(?!#?[a-zA-Z0-9]+;)/g,"&").replace(//g,">").replace(/'/g,"'").replace(/"/g,""")),c.html('
    1. '+o.replace(/[\r\t\n]+/g,"
    2. ")+"
    "),c.find(">.layui-code-h3")[0]||c.prepend('

    '+(c.attr("lay-title")||e.title||"code")+(e.about?'layui.code':"")+"

    ");var d=c.find(">.layui-code-ol");c.addClass("layui-box layui-code-view"),(c.attr("lay-skin")||e.skin)&&c.addClass("layui-code-"+(c.attr("lay-skin")||e.skin)),(d.find("li").length/100|0)>0&&d.css("margin-left",(d.find("li").length/100|0)+"px"),(c.attr("lay-height")||e.height)&&d.css("max-height",c.attr("lay-height")||e.height)})})}).addcss("modules/code.css","skincodecss"); \ No newline at end of file diff --git a/blade-spider-service/blade-datadig/src/main/resources/static/layui/layui.js b/blade-spider-service/blade-datadig/src/main/resources/static/layui/layui.js deleted file mode 100644 index 3cd51c21..00000000 --- a/blade-spider-service/blade-datadig/src/main/resources/static/layui/layui.js +++ /dev/null @@ -1,2 +0,0 @@ -/** layui-v2.4.5 MIT License By https://www.layui.com */ - ;!function(e){"use strict";var t=document,o={modules:{},status:{},timeout:10,event:{}},n=function(){this.v="2.4.5"},r=function(){var e=t.currentScript?t.currentScript.src:function(){for(var e,o=t.scripts,n=o.length-1,r=n;r>0;r--)if("interactive"===o[r].readyState){e=o[r].src;break}return e||o[n].src}();return e.substring(0,e.lastIndexOf("/")+1)}(),i=function(t){e.console&&console.error&&console.error("Layui hint: "+t)},a="undefined"!=typeof opera&&"[object Opera]"===opera.toString(),u={layer:"modules/layer",laydate:"modules/laydate",laypage:"modules/laypage",laytpl:"modules/laytpl",layim:"modules/layim",layedit:"modules/layedit",form:"modules/form",upload:"modules/upload",tree:"modules/tree",table:"modules/table",element:"modules/element",rate:"modules/rate",colorpicker:"modules/colorpicker",slider:"modules/slider",carousel:"modules/carousel",flow:"modules/flow",util:"modules/util",code:"modules/code",jquery:"modules/jquery",mobile:"modules/mobile","layui.all":"../layui.all"};n.prototype.cache=o,n.prototype.define=function(e,t){var n=this,r="function"==typeof e,i=function(){var e=function(e,t){layui[e]=t,o.status[e]=!0};return"function"==typeof t&&t(function(n,r){e(n,r),o.callback[n]=function(){t(e)}}),this};return r&&(t=e,e=[]),layui["layui.all"]||!layui["layui.all"]&&layui["layui.mobile"]?i.call(n):(n.use(e,i),n)},n.prototype.use=function(e,n,l){function s(e,t){var n="PLaySTATION 3"===navigator.platform?/^complete$/:/^(complete|loaded)$/;("load"===e.type||n.test((e.currentTarget||e.srcElement).readyState))&&(o.modules[f]=t,d.removeChild(v),function r(){return++m>1e3*o.timeout/4?i(f+" is not a valid module"):void(o.status[f]?c():setTimeout(r,4))}())}function c(){l.push(layui[f]),e.length>1?y.use(e.slice(1),n,l):"function"==typeof n&&n.apply(layui,l)}var y=this,p=o.dir=o.dir?o.dir:r,d=t.getElementsByTagName("head")[0];e="string"==typeof e?[e]:e,window.jQuery&&jQuery.fn.on&&(y.each(e,function(t,o){"jquery"===o&&e.splice(t,1)}),layui.jquery=layui.$=jQuery);var f=e[0],m=0;if(l=l||[],o.host=o.host||(p.match(/\/\/([\s\S]+?)\//)||["//"+location.host+"/"])[0],0===e.length||layui["layui.all"]&&u[f]||!layui["layui.all"]&&layui["layui.mobile"]&&u[f])return c(),y;if(o.modules[f])!function g(){return++m>1e3*o.timeout/4?i(f+" is not a valid module"):void("string"==typeof o.modules[f]&&o.status[f]?c():setTimeout(g,4))}();else{var v=t.createElement("script"),h=(u[f]?p+"lay/":/^\{\/\}/.test(y.modules[f])?"":o.base||"")+(y.modules[f]||f)+".js";h=h.replace(/^\{\/\}/,""),v.async=!0,v.charset="utf-8",v.src=h+function(){var e=o.version===!0?o.v||(new Date).getTime():o.version||"";return e?"?v="+e:""}(),d.appendChild(v),!v.attachEvent||v.attachEvent.toString&&v.attachEvent.toString().indexOf("[native code")<0||a?v.addEventListener("load",function(e){s(e,h)},!1):v.attachEvent("onreadystatechange",function(e){s(e,h)}),o.modules[f]=h}return y},n.prototype.getStyle=function(t,o){var n=t.currentStyle?t.currentStyle:e.getComputedStyle(t,null);return n[n.getPropertyValue?"getPropertyValue":"getAttribute"](o)},n.prototype.link=function(e,n,r){var a=this,u=t.createElement("link"),l=t.getElementsByTagName("head")[0];"string"==typeof n&&(r=n);var s=(r||e).replace(/\.|\//g,""),c=u.id="layuicss-"+s,y=0;return u.rel="stylesheet",u.href=e+(o.debug?"?v="+(new Date).getTime():""),u.media="all",t.getElementById(c)||l.appendChild(u),"function"!=typeof n?a:(function p(){return++y>1e3*o.timeout/100?i(e+" timeout"):void(1989===parseInt(a.getStyle(t.getElementById(c),"width"))?function(){n()}():setTimeout(p,100))}(),a)},o.callback={},n.prototype.factory=function(e){if(layui[e])return"function"==typeof o.callback[e]?o.callback[e]:null},n.prototype.addcss=function(e,t,n){return layui.link(o.dir+"css/"+e,t,n)},n.prototype.img=function(e,t,o){var n=new Image;return n.src=e,n.complete?t(n):(n.onload=function(){n.onload=null,"function"==typeof t&&t(n)},void(n.onerror=function(e){n.onerror=null,"function"==typeof o&&o(e)}))},n.prototype.config=function(e){e=e||{};for(var t in e)o[t]=e[t];return this},n.prototype.modules=function(){var e={};for(var t in u)e[t]=u[t];return e}(),n.prototype.extend=function(e){var t=this;e=e||{};for(var o in e)t[o]||t.modules[o]?i("模块名 "+o+" 已被占用"):t.modules[o]=e[o];return t},n.prototype.router=function(e){var t=this,e=e||location.hash,o={path:[],search:{},hash:(e.match(/[^#](#.*$)/)||[])[1]||""};return/^#\//.test(e)?(e=e.replace(/^#\//,""),o.href="/"+e,e=e.replace(/([^#])(#.*$)/,"$1").split("/")||[],t.each(e,function(e,t){/^\w+=/.test(t)?function(){t=t.split("="),o.search[t[0]]=t[1]}():o.path.push(t)}),o):o},n.prototype.data=function(t,o,n){if(t=t||"layui",n=n||localStorage,e.JSON&&e.JSON.parse){if(null===o)return delete n[t];o="object"==typeof o?o:{key:o};try{var r=JSON.parse(n[t])}catch(i){var r={}}return"value"in o&&(r[o.key]=o.value),o.remove&&delete r[o.key],n[t]=JSON.stringify(r),o.key?r[o.key]:r}},n.prototype.sessionData=function(e,t){return this.data(e,t,sessionStorage)},n.prototype.device=function(t){var o=navigator.userAgent.toLowerCase(),n=function(e){var t=new RegExp(e+"/([^\\s\\_\\-]+)");return e=(o.match(t)||[])[1],e||!1},r={os:function(){return/windows/.test(o)?"windows":/linux/.test(o)?"linux":/iphone|ipod|ipad|ios/.test(o)?"ios":/mac/.test(o)?"mac":void 0}(),ie:function(){return!!(e.ActiveXObject||"ActiveXObject"in e)&&((o.match(/msie\s(\d+)/)||[])[1]||"11")}(),weixin:n("micromessenger")};return t&&!r[t]&&(r[t]=n(t)),r.android=/android/.test(o),r.ios="ios"===r.os,r},n.prototype.hint=function(){return{error:i}},n.prototype.each=function(e,t){var o,n=this;if("function"!=typeof t)return n;if(e=e||[],e.constructor===Object){for(o in e)if(t.call(e[o],o,e[o]))break}else for(o=0;oi?1:r - - - - - \ No newline at end of file diff --git a/blade-spider-service/blade-datadig/src/main/resources/templates/common/footer.html b/blade-spider-service/blade-datadig/src/main/resources/templates/common/footer.html deleted file mode 100644 index 74197b6c..00000000 --- a/blade-spider-service/blade-datadig/src/main/resources/templates/common/footer.html +++ /dev/null @@ -1,8 +0,0 @@ - - -
    -
    -

    版权声明:本站为非盈利社区,一切出发点均出自于兴趣爱好,所有资源均来自 DHT 网络,仅供技术交流使用,如用作其他商业用途,一切后果由使用者后自行承担。

    -
    -
    - \ No newline at end of file diff --git a/blade-spider-service/blade-datadig/src/main/resources/templates/common/header.html b/blade-spider-service/blade-datadig/src/main/resources/templates/common/header.html deleted file mode 100644 index c7ab80ff..00000000 --- a/blade-spider-service/blade-datadig/src/main/resources/templates/common/header.html +++ /dev/null @@ -1,16 +0,0 @@ - - -
    -

    菟丝子资源社区

    -
    - -
    -
    - \ No newline at end of file diff --git a/blade-spider-service/blade-datadig/src/main/resources/templates/common/profile.html b/blade-spider-service/blade-datadig/src/main/resources/templates/common/profile.html deleted file mode 100644 index c8c36e51..00000000 --- a/blade-spider-service/blade-datadig/src/main/resources/templates/common/profile.html +++ /dev/null @@ -1,13 +0,0 @@ - - -
    -
    -
    社区简介
    -
    - 爱技术,爱分享。
    - 开源地址:https://github.com/xwlcn/Dodder
    - 关于作者:https://0o0.me -
    -
    -
    - \ No newline at end of file diff --git a/blade-spider-service/blade-datadig/src/main/resources/templates/common/status.html b/blade-spider-service/blade-datadig/src/main/resources/templates/common/status.html deleted file mode 100644 index 1d90e7fe..00000000 --- a/blade-spider-service/blade-datadig/src/main/resources/templates/common/status.html +++ /dev/null @@ -1,13 +0,0 @@ - - -
    -
    -
    资源抓取状态
    -
    -

    文件类型:

    -

    -

    稳定运行:1 天

    -
    -
    -
    - \ No newline at end of file diff --git a/blade-spider-service/blade-datadig/src/main/resources/templates/error/404.html b/blade-spider-service/blade-datadig/src/main/resources/templates/error/404.html deleted file mode 100644 index ac9d78ad..00000000 --- a/blade-spider-service/blade-datadig/src/main/resources/templates/error/404.html +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - - - - - 菟丝子资源社区 - - -
    -
    -
    
    -    
    -
    -
    资源列表
    - -
    -_  _      ___    _  _
    -| || |    / _ \  | || |
    -| || |_  | | | | | || |_
    -|__   _| | |_| | |__   _|
    -|_|    \___/     |_|
    -
    -  页面消失在了异次元空间!
    -            
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - - - - \ No newline at end of file diff --git a/blade-spider-service/blade-datadig/src/main/resources/templates/index.html b/blade-spider-service/blade-datadig/src/main/resources/templates/index.html deleted file mode 100644 index 7e140876..00000000 --- a/blade-spider-service/blade-datadig/src/main/resources/templates/index.html +++ /dev/null @@ -1,121 +0,0 @@ - - - - - - - - - - 菟丝子资源社区 - - -
    -
    -
    
    -    
    -
    -
    -
    资源列表
    -
    - 分类: - - 全部 - 音频 - 视频 - 图片 - 文档 - 压缩文件 - 镜像文件 - 软件 - -
    - - - - - - - - - - - -
    - - -
    - - - -
      -
    • -
    - -
    -
    -
    - -
    -
    -
    -_  _      ___    _  _
    -| || |    / _ \  | || |
    -| || |_  | | | | | || |_
    -|__   _| | |_| | |__   _|
    -|_|    \___/     |_|
    -
    -    没有找到相关记录!
    -            
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - - - - - \ No newline at end of file diff --git a/blade-spider-service/blade-datadig/src/main/resources/templates/info.html b/blade-spider-service/blade-datadig/src/main/resources/templates/info.html deleted file mode 100644 index 8d86c6a6..00000000 --- a/blade-spider-service/blade-datadig/src/main/resources/templates/info.html +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - - - - - - - - -
    -
    -
    
    -    
    -
    -
    -
    - -   -
    -
    -
      -
    • 发现时间:[[${#calendars.format(torrent.createDate,'yyyy年MM月dd日 HH时mm分ss秒')}]]
    • -
    • 文件大小:[[${#dodderUtil.formatSize(torrent.fileSize)}]]
    • -
    • 文件类型: -
        - -
      -
    • -
    - -
    -
    -
    相关推荐
    -
      -
    • - -
      - - -
      -
    • -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - - - - - - \ No newline at end of file diff --git a/blade-spider-service/blade-datadig/src/test/java/org/springblade/AppTest.java b/blade-spider-service/blade-datadig/src/test/java/org/springblade/AppTest.java deleted file mode 100644 index 77b9542a..00000000 --- a/blade-spider-service/blade-datadig/src/test/java/org/springblade/AppTest.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.springblade; - -import static org.junit.Assert.assertTrue; - -import org.junit.Test; - -/** - * Unit test for simple App. - */ -public class AppTest -{ - /** - * Rigorous Test :-) - */ - @Test - public void shouldAnswerWithTrue() - { - assertTrue( true ); - } -} diff --git a/blade-spider-service/blade-datadownload/pom.xml b/blade-spider-service/blade-datadownload/pom.xml deleted file mode 100644 index 28497d4d..00000000 --- a/blade-spider-service/blade-datadownload/pom.xml +++ /dev/null @@ -1,82 +0,0 @@ - - - - - blade-service - org.springblade - 2.7.1 - - 4.0.0 - - blade-datadownload - ${project.artifactId} - ${blade.project.version} - jar - - - - org.springblade - blade-core-boot - ${blade.tool.version} - - - org.springblade - blade-spider-common - ${blade.project.version} - - - - - - org.springframework.kafka - spring-kafka - - - org.springframework.boot - spring-boot-starter-test - test - - - org.springframework.kafka - spring-kafka-test - test - - - org.springframework.cloud - spring-cloud-stream - - - org.springframework.cloud - spring-cloud-starter-stream-kafka - - - - - - - org.apache.maven.plugins - maven-antrun-plugin - - - package - - run - - - - - - - - - - - - - diff --git a/blade-spider-service/blade-datadownload/src/main/java/org/springblade/datadownload/DataDownLoadApplication.java b/blade-spider-service/blade-datadownload/src/main/java/org/springblade/datadownload/DataDownLoadApplication.java deleted file mode 100644 index f4179092..00000000 --- a/blade-spider-service/blade-datadownload/src/main/java/org/springblade/datadownload/DataDownLoadApplication.java +++ /dev/null @@ -1,69 +0,0 @@ -package org.springblade.datadownload; - -import org.springblade.core.launch.BladeApplication; -import org.springblade.core.launch.constant.AppConstant; -import org.springblade.datadownload.stream.MessageStreams; -import org.springblade.datadownload.task.BlockingExecutor; -import org.springblade.datadownload.task.DownloadTask; -import org.springblade.spider.common.entity.DownloadMsgInfo; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.cloud.client.SpringCloudApplication; -import org.springframework.cloud.openfeign.EnableFeignClients; -import org.springframework.cloud.stream.annotation.EnableBinding; -import org.springframework.cloud.stream.annotation.StreamListener; -import org.springframework.scheduling.annotation.EnableScheduling; -import org.springframework.scheduling.annotation.Scheduled; - -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -/** - * 数据挖掘--下载服务模块启动器 - * @author zt - */ -@EnableScheduling -@EnableBinding(MessageStreams.class) -@SpringCloudApplication -@EnableFeignClients(AppConstant.BASE_PACKAGES) -public class DataDownLoadApplication { - - @Value("${download.num.thread}") - private int nThreads; - - private BlockingExecutor blockingExecutor; - - public static void main(String[] args) { - // AppConstant.APPLICATION_SYSTEM_NAME - BladeApplication.run("blade-datadownload", DataDownLoadApplication.class, args); - } - @StreamListener("download-message-in") - public void handleMessage(DownloadMsgInfo msgInfo) { - //submit to blocking executor - try { - blockingExecutor.execute(new DownloadTask(msgInfo)); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - - @Scheduled(fixedDelay = 5 * 60 * 1000) - public void autoFinalize() { - //定时强制回收 Finalizer 队列里的 Socket 对象(有个抽象父类重写了 finalize 方法, - //频繁创建 Socket 会导致 Socket 得不到及时回收频繁发生 FGC) - System.runFinalization(); - } - - @PostConstruct - public void init() { - //max task bound 5000 TODO Executors 创建方式需要优化 - blockingExecutor = new BlockingExecutor(Executors.newFixedThreadPool(nThreads), 5000); - } - - @PreDestroy - public void destroy() { - blockingExecutor.shutdownNow(); - } -} - diff --git a/blade-spider-service/blade-datadownload/src/main/java/org/springblade/datadownload/client/Constants.java b/blade-spider-service/blade-datadownload/src/main/java/org/springblade/datadownload/client/Constants.java deleted file mode 100644 index b690c795..00000000 --- a/blade-spider-service/blade-datadownload/src/main/java/org/springblade/datadownload/client/Constants.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.springblade.datadownload.client; - - -import org.springblade.spider.common.util.NodeIdUtil; - -public class Constants { - - public static final String BT_PROTOCOL = "BitTorrent protocol"; - public static final byte[] BT_RESERVED = new byte[] { - (byte) (0x00 & 0xff), (byte) (0x00 & 0xff), (byte) (0x00 & 0xff), (byte) (0x00 & 0xff), - (byte) (0x00 & 0xff), (byte) (0x10 & 0xff), (byte) (0x00 & 0xff), (byte) (0x01 & 0xff), - }; - - public static final byte[] EXT_HANDSHAKE_DATA = "d1:md11:ut_metadatai1eee".getBytes(); - - public static final byte BT_MSG_ID = 20 & 0xff; - public static final int EXT_HANDSHAKE_ID = 0; - public static final int CONNECT_TIMEOUT = 5000; - public static final int READ_WRITE_TIMEOUT = 5000; - public static final int MAX_METADATA_SIZE = 1024 * 1024 * 50; //最大 1M - - public static final byte[] PEER_ID = NodeIdUtil.createRandomNodeId(); - - public static final long MAX_LOSS_TIME = 30 * 60 * 1000; - -} diff --git a/blade-spider-service/blade-datadownload/src/main/java/org/springblade/datadownload/client/PeerWireClient.java b/blade-spider-service/blade-datadownload/src/main/java/org/springblade/datadownload/client/PeerWireClient.java deleted file mode 100644 index 10ea5c54..00000000 --- a/blade-spider-service/blade-datadownload/src/main/java/org/springblade/datadownload/client/PeerWireClient.java +++ /dev/null @@ -1,445 +0,0 @@ -package org.springblade.datadownload.client; - -import lombok.extern.slf4j.Slf4j; -import org.springblade.spider.common.entity.Node; -import org.springblade.spider.common.entity.Torrent; -import org.springblade.spider.common.entity.Tree; -import org.springblade.spider.common.util.ByteUtil; -import org.springblade.spider.common.util.ExtensionUtil; -import org.springblade.spider.common.util.JSONUtil; -import org.springblade.spider.common.util.StringUtil; -import org.springblade.spider.common.util.bencode.BencodingUtils; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.math.BigInteger; -import java.net.InetSocketAddress; -import java.net.Socket; -import java.nio.charset.StandardCharsets; -import java.util.*; -import java.util.function.Consumer; - -/*** - * Peer Wire 协议客户端,参见协议: - * http://www.bittorrent.org/beps/bep_0009.html - * - * @author: Mr.Xu - * @create: 2019-02-19 09:21 - **/ -@Slf4j -public class PeerWireClient { - - private Map map; - - private Socket socket; - private InputStream in; - private OutputStream out; - private int protocolLen; - private int ut_metadata; //extended message ID - private int metadata_size; - private int pieces; - private String hexHash; - - private byte[] readBuff; - - private int nextSize; - private NextFunction next; - - private Torrent torrent; - - private static ThreadLocal cachedBuff = new ThreadLocal<>(); - - private byte[] metadata; - - private byte[] peerId; - - /** - * 下载完成监听器 - */ - private Consumer onFinishedListener; - - public void setOnFinishedListener(Consumer listener) { - this.onFinishedListener = listener; - } - - - public void downloadMetadata(InetSocketAddress address, byte[] peerId, byte[] infoHash) { - this.peerId = peerId; - hexHash = ByteUtil.byteArrayToHex(infoHash); - socket = new Socket(); - try { - socket.connect(address, Constants.CONNECT_TIMEOUT); - socket.setSoTimeout(Constants.READ_WRITE_TIMEOUT); - socket.setSoLinger(true, 0); - socket.setTcpNoDelay(true); - socket.setReuseAddress(true); - - in = socket.getInputStream(); - out = socket.getOutputStream(); - - setNext(1, onProtocolLen); - sendHandShake(infoHash); - - map = new HashMap<>(); - readBuff = new byte[256]; - if (cachedBuff.get() == null) { - cachedBuff.set(new PipedStream()); - } - - int len = -1; - while (!socket.isClosed() && (len = in.read(readBuff)) != -1) { - cachedBuff.get().write(readBuff, 0, len); - handleMessage(); - } - } catch (Exception e) { - } finally { - destroy(); - if (onFinishedListener != null) - onFinishedListener.accept(torrent); - } - } - - private void sendHandShake(byte[] infoHash) throws Exception { - - /*proctocol*/ - out.write(Constants.BT_PROTOCOL.length() & 0xff); - out.write(Constants.BT_PROTOCOL.getBytes()); - - /*reserved bytes*/ - out.write(Constants.BT_RESERVED); - - /*info_hash*/ - out.write(infoHash); - - /*peer_id*/ - out.write(peerId); - - out.flush(); - - } - - private void handleMessage() throws Exception { - while (cachedBuff.get().available() >= nextSize) { - byte[] buff = new byte[nextSize]; - cachedBuff.get().read(buff, 0, nextSize); - next.doNext(buff); - } - } - - private NextFunction onMessage = new NextFunction() { - @Override - public void doNext(byte[] buff) throws Exception { - setNext(4, onMessageLength); - if (buff[0] == Constants.BT_MSG_ID) { - resolveExtendMessage(buff[1], Arrays.copyOfRange(buff, 2, buff.length)); - } - } - }; - - private void resolveExtendMessage(byte b, byte[] buf) throws Exception { - if (b == 0) - resolveExtendHandShake(BencodingUtils.decode(buf)); - else - resolvePiece(buf); - } - - private void resolvePiece(byte[] buff) { - String str = new String(buff, StandardCharsets.ISO_8859_1); - int pos = str.indexOf("ee") + 2; - byte[] piece_metadata = Arrays.copyOfRange(buff, pos, buff.length); - - Map map = BencodingUtils.decode(str.substring(0, pos).getBytes(StandardCharsets.ISO_8859_1)); - - int piece = ((BigInteger) map.get("piece")).intValue(); - System.arraycopy(piece_metadata, 0, this.metadata, piece * 16 * 1024, piece_metadata.length); - - pieces--; - - checkFinished(); - } - - private void checkFinished() { - if (pieces <= 0) { - try { - if (metadata[0] == 'd') { - Map map = BencodingUtils.decode(metadata); - if (map != null) - torrent = parseTorrent(map); - } - } catch (Exception e) { - } - destroy(); - } - } - - /** - * 解析 Torrent 文件信息,封装成对象 - * - * 多文件Torrent的结构的树形图为: - * - * Multi-file Torrent - * ├─announce - * ├─announce-list - * ├─comment - * ├─comment.utf-8 - * ├─creation date - * ├─encoding - * ├─info - * │ ├─files - * │ │ ├─length - * │ │ ├─path - * │ │ └─path.utf-8 - * │ ├─name - * │ ├─name.utf-8 - * │ ├─piece length - * │ ├─pieces - * │ ├─publisher - * │ ├─publisher-url - * │ ├─publisher-url.utf-8 - * │ └─publisher.utf-8 - * └─nodes - * - * 单文件Torrent的结构的树形图为: - * - * Single-File Torrent - * ├─announce - * ├─announce-list - * ├─comment - * ├─comment.utf-8 - * ├─creation date - * ├─encoding - * ├─info - * │ ├─length - * │ ├─name - * │ ├─name.utf-8 - * │ ├─piece length - * │ ├─pieces - * │ ├─publisher - * │ ├─publisher-url - * │ ├─publisher-url.utf-8 - * │ └─publisher.utf-8 - * └─nodes - * - * @param map - * @return java.util.Optional - */ - private Torrent parseTorrent(Map map) throws Exception { - String encoding = null; - Map info; - if (map.containsKey("info")) - info = (Map) map.get("info"); - else - info = map; - - if (!info.containsKey("name")) - return null; - if (map.containsKey("encoding")) - encoding = (String) map.get("encoding"); - - Torrent torrent = new Torrent(); - - if (map.containsKey("creation date")) - torrent.setCreateDate(((BigInteger) map.get("creation date")).longValue()); - else - torrent.setCreateDate(System.currentTimeMillis()); - - byte[] temp; - if (info.containsKey("name.utf-8")) { - temp = (byte[]) info.get("name.utf-8"); - encoding = "UTF-8"; - } - else { - temp = (byte[]) info.get("name"); - if (encoding == null) { - encoding = StringUtil.getEncoding(temp); - } - } - - torrent.setFileName(new String(temp, encoding)); - - //多文件 - if (info.containsKey("files")) { - Set types = new HashSet<>(); - - List> list = (List>) info.get("files"); - - long total = 0; - int i = 0; - List nodes = new ArrayList<>(); - int cur = 1, parent = 0; - for (Map f : list) { - long length = ((BigInteger) f.get("length")).longValue(); - total += length; - - Long filesize = null; //null 表示为文件夹 - boolean uft8 = f.containsKey("path.utf-8"); - List aList = f.containsKey("path.utf-8") ? (List) f.get("path.utf-8") : (List) f.get("path"); - int j = 0; - for (byte[] bytes : aList) { - String sname = new String(bytes, uft8 ? "UTF-8" : StringUtil.getEncoding(bytes)); - if (sname.contains("_____padding_file_")) - continue; - if (j == aList.size() - 1) { - filesize = length; - String type = ExtensionUtil.getExtensionType(sname); - if (type != null) { - types.add(type); - } - } - Node node = new Node(cur, j == 0 ? 0 : parent, sname, filesize, i); - if (!nodes.contains(node)) { - nodes.add(node); - parent = cur; - } else { - parent = nodes.get(nodes.indexOf(node)).getNid(); - } - cur++; - j++; - } - i++; - } - Tree tree = new Tree(null); - tree.createTree(nodes); - torrent.setFileSize(total); - torrent.setFiles(JSONUtil.toJSONString(tree)); - if (types.size() <= 0) - types.add("其他"); - String sType = String.join(",", types); - if (sType != null && !"".equals(sType)) { - torrent.setFileType(sType); - } - } else { - torrent.setFileSize(((BigInteger) info.get("length")).longValue()); - - String type = ExtensionUtil.getExtensionType(torrent.getFileName()); - if (type != null) { - torrent.setFileType(type); - } - } - torrent.setInfoHash(hexHash); - return torrent; - } - - private void resolveExtendHandShake(Map map) throws Exception { - - Map m = (Map) map.get("m"); - - if (m == null || !m.containsKey("ut_metadata") || !map.containsKey("metadata_size")) { - destroy(); - return; - } - this.ut_metadata = ((BigInteger) m.get("ut_metadata")).intValue(); - this.metadata_size = ((BigInteger) map.get("metadata_size")).intValue(); - - if (this.metadata_size > Constants.MAX_METADATA_SIZE) { - destroy(); - return; - } - - requestPieces(); - } - - private void requestPieces() throws Exception { - - metadata = new byte[this.metadata_size]; - - pieces = (int) Math.ceil(this.metadata_size / (16.0 * 1024)); - - int temp = pieces; - for (int piece = 0; piece < temp; piece++) { - requestPiece(piece); - } - } - - private void requestPiece(int piece) throws Exception { - map.clear(); - map.put("msg_type", 0); - map.put("piece", piece); - - byte[] data = BencodingUtils.encode(map); - - sendMessage(this.ut_metadata, data); - - } - - private NextFunction onMessageLength = (byte[] buff) -> { - int length = ByteUtil.byteArrayToInt(buff); - if (length > 0) - setNext(length, onMessage); - }; - - private NextFunction onHandshake = (byte[] buff) -> { - byte[] handshake = Arrays.copyOfRange(buff, protocolLen, buff.length); - if (handshake[5] == 0x10) { - setNext(4, onMessageLength); - sendExtHandShake(); - } - }; - - private NextFunction onProtocolLen = (byte[] buff) -> { - protocolLen = (int) buff[0]; - //接下来是协议名称(长度:protocolLen)和BT_RESERVED(长度:8)、info_hash(长度:20)、peer_id(长度:20) - setNext(protocolLen + 48, onHandshake); - }; - - private void sendExtHandShake() throws Exception { - sendMessage(Constants.EXT_HANDSHAKE_ID, Constants.EXT_HANDSHAKE_DATA); - } - - private void sendMessage(int id, byte[] data) throws Exception { - - //length prefix bytes - byte[] length_prefix = ByteUtil.intToByteArray(data.length + 2); - for(int i=0; i<4; i++) - length_prefix[i] = (byte)(length_prefix[i] & 0xff); - out.write(length_prefix); - - //bittorrent message ID, = 20 - out.write(Constants.BT_MSG_ID); - - //extended message ID. 0 = handshake, >0 = extended message as specified by the handshake. - out.write((byte)(id & 0xff)); - - //data - out.write(data); - out.flush(); - } - - private interface NextFunction { - void doNext(byte[] buff) throws Exception; - } - - private void setNext(int nextSize, NextFunction next) { - this.nextSize = nextSize; - this.next = next; - } - - private void destroy() { - if (socket.isConnected()) { - try { - socket.close(); - } catch (Exception e) { - } - } - try { - in.close(); - } catch (Exception e) { - } - try { - out.close(); - } catch (Exception e) { - } - readBuff = null; - if (cachedBuff.get() != null) { - try { - cachedBuff.get().clear(); - } catch (IOException e) { - e.printStackTrace(); - } - } - metadata = null; - } - - -} diff --git a/blade-spider-service/blade-datadownload/src/main/java/org/springblade/datadownload/client/PipedStream.java b/blade-spider-service/blade-datadownload/src/main/java/org/springblade/datadownload/client/PipedStream.java deleted file mode 100644 index 71765127..00000000 --- a/blade-spider-service/blade-datadownload/src/main/java/org/springblade/datadownload/client/PipedStream.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.springblade.datadownload.client; - -import java.io.IOException; -import java.io.PipedInputStream; -import java.io.PipedOutputStream; - -/*** - * Piped Stream 封装类 - * - * @author Mr.Xu - * @date 2019-03-01 19:08 - **/ -public class PipedStream { - - private PipedOutputStream writeStream; - private PipedInputStream readStream; - - public PipedStream() throws IOException { - writeStream = new PipedOutputStream(); - readStream = new PipedInputStream(22 * 1024); - readStream.connect(writeStream); - } - - public int available() throws IOException { - return readStream.available(); - } - - public void read(byte[] b, int off, int len) throws IOException { - readStream.read(b, off, len); - } - - public void write(byte[] b, int off, int len) throws IOException { - writeStream.write(b, off, len); - writeStream.flush(); - } - - public void clear() throws IOException { - writeStream.flush(); - readStream.skip(readStream.available()); - } -} diff --git a/blade-spider-service/blade-datadownload/src/main/java/org/springblade/datadownload/stream/MessageStreams.java b/blade-spider-service/blade-datadownload/src/main/java/org/springblade/datadownload/stream/MessageStreams.java deleted file mode 100644 index de89fc59..00000000 --- a/blade-spider-service/blade-datadownload/src/main/java/org/springblade/datadownload/stream/MessageStreams.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.springblade.datadownload.stream; - -import org.springframework.cloud.stream.annotation.Input; -import org.springframework.cloud.stream.annotation.Output; -import org.springframework.messaging.MessageChannel; - -public interface MessageStreams { - - @Input("download-message-in") - MessageChannel downloadMessageInput(); - - @Output("torrent-message-out") - MessageChannel torrentMessageOutput(); - -} diff --git a/blade-spider-service/blade-datadownload/src/main/java/org/springblade/datadownload/task/BlockingExecutor.java b/blade-spider-service/blade-datadownload/src/main/java/org/springblade/datadownload/task/BlockingExecutor.java deleted file mode 100644 index b9b3816b..00000000 --- a/blade-spider-service/blade-datadownload/src/main/java/org/springblade/datadownload/task/BlockingExecutor.java +++ /dev/null @@ -1,43 +0,0 @@ -package org.springblade.datadownload.task; - -import java.util.concurrent.ExecutorService; -import java.util.concurrent.RejectedExecutionException; -import java.util.concurrent.Semaphore; - -/*** - * Bloking executor - * - * @author Mr.Xu - * @simce 2019-10-18 17:36 - **/ -public class BlockingExecutor { - - private final ExecutorService exec; - private final Semaphore semaphore; - - public BlockingExecutor(ExecutorService exec, int bound) { - this.exec = exec; - this.semaphore = new Semaphore(bound); - } - - public void execute(final Runnable command) - throws InterruptedException, RejectedExecutionException { - semaphore.acquire(); - try { - exec.execute(() -> { - try { - command.run(); - } finally { - semaphore.release(); - } - }); - } catch (RejectedExecutionException e) { - semaphore.release(); - throw e; - } - } - - public void shutdownNow() { - exec.shutdownNow(); - } -} diff --git a/blade-spider-service/blade-datadownload/src/main/java/org/springblade/datadownload/task/DownloadTask.java b/blade-spider-service/blade-datadownload/src/main/java/org/springblade/datadownload/task/DownloadTask.java deleted file mode 100644 index 497b99a8..00000000 --- a/blade-spider-service/blade-datadownload/src/main/java/org/springblade/datadownload/task/DownloadTask.java +++ /dev/null @@ -1,62 +0,0 @@ -package org.springblade.datadownload.task; - - -import lombok.extern.slf4j.Slf4j; -import org.springblade.datadownload.client.Constants; -import org.springblade.datadownload.client.PeerWireClient; -import org.springblade.datadownload.stream.MessageStreams; -import org.springblade.spider.common.entity.DownloadMsgInfo; -import org.springblade.spider.common.util.SpringContextUtil; -import org.springblade.spider.common.util.SystemClock; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.messaging.MessageHeaders; -import org.springframework.util.MimeTypeUtils; - -import java.net.InetSocketAddress; - -/*** - * Torrent 下载线程 - * - * @author Mr.Xu - * @date 2019-02-22 10:43 - **/ -@Slf4j -public class DownloadTask implements Runnable { - - private DownloadMsgInfo msgInfo; - private MessageStreams messageStreams; - - public DownloadTask(DownloadMsgInfo msgInfo) { - this.msgInfo = msgInfo; - } - - @Override - public void run() { - //由于下载线程消费的速度总是比 dht server 生产的速度慢,所以要做一下时间限制,否则程序越跑越慢 - if (SystemClock.now() - msgInfo.getTimestamp() >= Constants.MAX_LOSS_TIME) { - return; - } - PeerWireClient wireClient = new PeerWireClient(); - //设置下载完成监听器 - wireClient.setOnFinishedListener((torrent) -> { - if (torrent == null) { //下载失败 - return; - } - RedisTemplate redisTemplate = (RedisTemplate) SpringContextUtil.getBean("redisTemplate"); - torrent.setCreateDate(msgInfo.getTimestamp()); - redisTemplate.opsForValue().set(msgInfo.getInfoHash(), new byte[0]); - messageStreams = (MessageStreams) SpringContextUtil.getBean(MessageStreams.class); - //丢进 kafka 消息队列进行入库及索引操作 - messageStreams.torrentMessageOutput() - .send(MessageBuilder.withPayload(torrent) - .setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON) - .build()); - log.info("[{}:{}] Download torrent success, info hash is {}", - msgInfo.getIp(), - msgInfo.getPort(), - torrent.getInfoHash()); - }); - wireClient.downloadMetadata(new InetSocketAddress(msgInfo.getIp(), msgInfo.getPort()), msgInfo.getNodeId(), msgInfo.getInfoHash()); - } -} diff --git a/blade-spider-service/blade-datadownload/src/main/resources/application.yml b/blade-spider-service/blade-datadownload/src/main/resources/application.yml deleted file mode 100644 index 17f8e8bf..00000000 --- a/blade-spider-service/blade-datadownload/src/main/resources/application.yml +++ /dev/null @@ -1,50 +0,0 @@ -server: - port: 8765 -spring: - # 数据源配置 - datasource: - url: ${blade.datasource.dev.url} - username: ${blade.datasource.dev.username} - password: ${blade.datasource.dev.password} - redis: - host: 47.110.236.215 - port: 6379 - password: redis_nozdormu - jedis: - pool: - max-idle: 8 - min-idle: 0 - max-active: 500 - max-wait: -1ms - cloud: - stream: - kafka: - binder: - brokers: 192.168.80.168 - min-partition-count: 1 - bindings: - download-message-in: - group: torrent-download-group - destination: downloadMessages - contentType: application/json - consumer: - compressionType: gzip - torrent-message-out: - destination: torrentMessages - contentType: application/json - producer: - compressionType: gzip - -feign: - httpclient: - enabled: true - -management: - endpoints: - web: - exposure: - include: ["*"] - -download: - num: - thread: 2500 diff --git a/blade-spider-service/blade-datastore/pom.xml b/blade-spider-service/blade-datastore/pom.xml deleted file mode 100644 index 2b4b555d..00000000 --- a/blade-spider-service/blade-datastore/pom.xml +++ /dev/null @@ -1,100 +0,0 @@ - - - - - blade-service - org.springblade - 2.7.1 - - 4.0.0 - - blade-datastore - ${project.artifactId} - ${blade.project.version} - jar - - - - org.springblade - blade-core-boot - ${blade.tool.version} - - - org.springblade - blade-spider-common - ${blade.project.version} - - - - - - org.springframework.kafka - spring-kafka - - - org.springframework.kafka - spring-kafka-test - test - - - org.springframework.cloud - spring-cloud-stream - - - org.springframework.cloud - spring-cloud-starter-stream-kafka - - - org.springframework.boot - spring-boot-starter-actuator - - - org.springframework.data - spring-data-mongodb - - - org.springframework.boot - spring-boot-starter-data-elasticsearch - - - org.springframework.data - spring-data-elasticsearch - - - - - org.springframework.data - spring-data-elasticsearch - 3.2.0.RELEASE - - - - - - - org.apache.maven.plugins - maven-antrun-plugin - - - package - - run - - - - - - - - - - - - - diff --git a/blade-spider-service/blade-datastore/src/main/java/org/springblade/datastore/DataStoreApplication.java b/blade-spider-service/blade-datastore/src/main/java/org/springblade/datastore/DataStoreApplication.java deleted file mode 100644 index 7fe5854d..00000000 --- a/blade-spider-service/blade-datastore/src/main/java/org/springblade/datastore/DataStoreApplication.java +++ /dev/null @@ -1,71 +0,0 @@ -package org.springblade.datastore; - -import lombok.extern.slf4j.Slf4j; -import org.springblade.core.launch.BladeApplication; -import org.springblade.core.launch.constant.AppConstant; -import org.springblade.datastore.service.TorrentService; -import org.springblade.datastore.stream.MessageStreams; -import org.springblade.spider.common.entity.Torrent; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.cloud.client.SpringCloudApplication; -import org.springframework.cloud.openfeign.EnableFeignClients; -import org.springframework.cloud.stream.annotation.EnableBinding; -import org.springframework.cloud.stream.annotation.StreamListener; -import org.springframework.kafka.support.Acknowledgment; -import org.springframework.kafka.support.KafkaHeaders; -import org.springframework.messaging.Message; -import org.springframework.scheduling.annotation.EnableScheduling; - -/** - * 数据挖掘--存储服务模块启动器 - * @author zt - */ -@Slf4j -@EnableScheduling -@EnableBinding(MessageStreams.class) -@SpringCloudApplication -@EnableFeignClients(AppConstant.BASE_PACKAGES) -public class DataStoreApplication { - - @Autowired - private TorrentService torrentService; - - public static void main(String[] args) { - System.setProperty("es.set.netty.runtime.available.processors", "false"); - // AppConstant.APPLICATION_SYSTEM_NAME - BladeApplication.run("blade-datastore", DataStoreApplication.class, args); - } - - @StreamListener("torrent-message-in") - public void handleTorrent(Message message) { - try { - Acknowledgment acknowledgment = message.getHeaders().get(KafkaHeaders.ACKNOWLEDGMENT, Acknowledgment.class); - Torrent torrent = message.getPayload(); - log.debug("Save torrent to MongoDB, info hash is {}", torrent.getInfoHash()); - torrentService.upsert(torrent); - //no error, execute acknowledge - if (acknowledgment != null) { - acknowledgment.acknowledge(); - } - } catch (Exception e) { - log.error("Insert or update torrent error: {}", e); - } - } - - @StreamListener("index-message-in") - public void indexTorrent(Message message) { - try { - Acknowledgment acknowledgment = message.getHeaders().get(KafkaHeaders.ACKNOWLEDGMENT, Acknowledgment.class); - Torrent torrent = message.getPayload(); - log.debug("Index torrent to elasticsearch, info hash is {}", torrent.getInfoHash()); - torrentService.index(torrent); - //no error, execute acknowledge - if (acknowledgment != null) { - acknowledgment.acknowledge(); - } - } catch (Exception e) { - log.error("Index torrent error: {}", e); - } - } -} - diff --git a/blade-spider-service/blade-datastore/src/main/java/org/springblade/datastore/config/ElasticsearchConfiguration.java b/blade-spider-service/blade-datastore/src/main/java/org/springblade/datastore/config/ElasticsearchConfiguration.java deleted file mode 100644 index 91c96484..00000000 --- a/blade-spider-service/blade-datastore/src/main/java/org/springblade/datastore/config/ElasticsearchConfiguration.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.springblade.datastore.config; - -import org.elasticsearch.client.Client; -import org.springblade.spider.common.entity.Torrent; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.elasticsearch.core.ElasticsearchTemplate; -import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter; - -@Configuration -public class ElasticsearchConfiguration { - - /** - * 由于并未使用 ElasticsearchRepository, 并且使用 Client 的方式插入更新索引 - * 导致 Entity 上的 @Mapping 和 @Setting 不会生效,所以自己手动创建索引 - * - * @param client - * @param converter - * @return - */ - @Bean - public ElasticsearchTemplate elasticsearchTemplate(Client client, ElasticsearchConverter converter) { - try { - ElasticsearchTemplate elasticsearchTemplate = new ElasticsearchTemplate(client, converter); - //手动创建索引 - elasticsearchTemplate.createIndex(Torrent.class); - elasticsearchTemplate.putMapping(Torrent.class); - return elasticsearchTemplate; - } - catch (Exception ex) { - throw new IllegalStateException(ex); - } - } -} diff --git a/blade-spider-service/blade-datastore/src/main/java/org/springblade/datastore/config/MongoConfig.java b/blade-spider-service/blade-datastore/src/main/java/org/springblade/datastore/config/MongoConfig.java deleted file mode 100644 index 21aef19b..00000000 --- a/blade-spider-service/blade-datastore/src/main/java/org/springblade/datastore/config/MongoConfig.java +++ /dev/null @@ -1,72 +0,0 @@ -package org.springblade.datastore.config; - -import com.mongodb.MongoClient; -import com.mongodb.MongoClientOptions; -import com.mongodb.MongoCredential; -import com.mongodb.ServerAddress; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.mongodb.MongoDbFactory; -import org.springframework.data.mongodb.core.SimpleMongoDbFactory; - -import java.util.ArrayList; -import java.util.List; - -@Configuration -public class MongoConfig { - // 覆盖默认的MongoDbFactory - @Bean - MongoDbFactory mongoDbFactory(MongoSettingsProperties mongoSettingsProperties) { - // 客户端配置(连接数、副本集群验证) - MongoClientOptions.Builder builder = new MongoClientOptions.Builder(); - builder.connectionsPerHost(mongoSettingsProperties.getMaxConnectionsPerHost()); - builder.minConnectionsPerHost(mongoSettingsProperties.getMinConnectionsPerHost()); - if (mongoSettingsProperties.getReplicaSet() != null) { - builder.requiredReplicaSetName(mongoSettingsProperties.getReplicaSet()); - } - builder.threadsAllowedToBlockForConnectionMultiplier( - mongoSettingsProperties.getThreadsAllowedToBlockForConnectionMultiplier()); - builder.serverSelectionTimeout(mongoSettingsProperties.getServerSelectionTimeout()); - builder.maxWaitTime(mongoSettingsProperties.getMaxWaitTime()); - builder.maxConnectionIdleTime(mongoSettingsProperties.getMaxConnectionIdleTime()); - builder.maxConnectionLifeTime(mongoSettingsProperties.getMaxConnectionLifeTime()); - builder.connectTimeout(mongoSettingsProperties.getConnectTimeout()); - builder.socketTimeout(mongoSettingsProperties.getSocketTimeout()); - builder.sslEnabled(mongoSettingsProperties.getSslEnabled()); - builder.sslInvalidHostNameAllowed(mongoSettingsProperties.getSslInvalidHostNameAllowed()); - builder.alwaysUseMBeans(mongoSettingsProperties.getAlwaysUseMBeans()); - builder.heartbeatFrequency(mongoSettingsProperties.getHeartbeatFrequency()); - builder.minHeartbeatFrequency(mongoSettingsProperties.getMinHeartbeatFrequency()); - builder.heartbeatConnectTimeout(mongoSettingsProperties.getHeartbeatConnectTimeout()); - builder.heartbeatSocketTimeout(mongoSettingsProperties.getHeartbeatSocketTimeout()); - builder.localThreshold(mongoSettingsProperties.getLocalThreshold()); - - MongoClientOptions mongoClientOptions = builder.build(); - - // MongoDB地址列表 - List serverAddresses = new ArrayList<>(); - for (String address : mongoSettingsProperties.getAddress()) { - String[] hostAndPort = address.split(":"); - String host = hostAndPort[0]; - Integer port = Integer.parseInt(hostAndPort[1]); - ServerAddress serverAddress = new ServerAddress(host, port); - serverAddresses.add(serverAddress); - } - - // 连接认证 - if (mongoSettingsProperties.getUsername() == null) { - throw new RuntimeException("mongodb username can not be null"); - } - - MongoCredential mongoCredential = MongoCredential.createScramSha1Credential( - mongoSettingsProperties.getUsername(), - mongoSettingsProperties.getAuthenticationDatabase() != null ? mongoSettingsProperties - .getAuthenticationDatabase() : mongoSettingsProperties.getDatabase(), - mongoSettingsProperties.getPassword().toCharArray()); - // 创建客户端和Factory - MongoClient mongoClient = new MongoClient(serverAddresses, mongoCredential, mongoClientOptions); - MongoDbFactory mongoDbFactory = new SimpleMongoDbFactory(mongoClient,mongoSettingsProperties.getDatabase()); - - return mongoDbFactory; - } -} diff --git a/blade-spider-service/blade-datastore/src/main/java/org/springblade/datastore/config/MongoSettingsProperties.java b/blade-spider-service/blade-datastore/src/main/java/org/springblade/datastore/config/MongoSettingsProperties.java deleted file mode 100644 index c4002663..00000000 --- a/blade-spider-service/blade-datastore/src/main/java/org/springblade/datastore/config/MongoSettingsProperties.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.springblade.datastore.config; - -import lombok.Data; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.annotation.PropertySource; -import org.springframework.stereotype.Component; - -import java.util.List; - -@Data -@Component -@PropertySource(value = "classpath:mongo-pool.properties") -@ConfigurationProperties(prefix = "spring.data.mongodb") -public class MongoSettingsProperties { - - private List address; - private String replicaSet; - private String database; - private String username; - private String password; - private Integer minConnectionsPerHost = 0; - private Integer maxConnectionsPerHost = 100; - private Integer threadsAllowedToBlockForConnectionMultiplier = 5; - private Integer serverSelectionTimeout = 30000; - private Integer maxWaitTime = 120000; - private Integer maxConnectionIdleTime = 0; - private Integer maxConnectionLifeTime = 0; - private Integer connectTimeout = 10000; - private Integer socketTimeout = 0; - private Boolean socketKeepAlive = false; - private Boolean sslEnabled = false; - private Boolean sslInvalidHostNameAllowed = false; - private Boolean alwaysUseMBeans = false; - private Integer heartbeatConnectTimeout = 20000; - private Integer heartbeatSocketTimeout = 20000; - private Integer minHeartbeatFrequency = 500; - private Integer heartbeatFrequency = 10000; - private Integer localThreshold = 15; - private String authenticationDatabase; - - -} diff --git a/blade-spider-service/blade-datastore/src/main/java/org/springblade/datastore/repository/TorrentRepository.java b/blade-spider-service/blade-datastore/src/main/java/org/springblade/datastore/repository/TorrentRepository.java deleted file mode 100644 index 59f81ddb..00000000 --- a/blade-spider-service/blade-datastore/src/main/java/org/springblade/datastore/repository/TorrentRepository.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.springblade.datastore.repository; - -import org.springblade.datastore.repository.customer.TorrentDao; -import org.springblade.spider.common.entity.Torrent; -import org.springframework.data.mongodb.repository.MongoRepository; - -public interface TorrentRepository extends MongoRepository, TorrentDao { - -} diff --git a/blade-spider-service/blade-datastore/src/main/java/org/springblade/datastore/repository/customer/TorrentDao.java b/blade-spider-service/blade-datastore/src/main/java/org/springblade/datastore/repository/customer/TorrentDao.java deleted file mode 100644 index 4e2e1f17..00000000 --- a/blade-spider-service/blade-datastore/src/main/java/org/springblade/datastore/repository/customer/TorrentDao.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.springblade.datastore.repository.customer; - - -import org.springblade.spider.common.entity.Torrent; -import org.springblade.spider.common.request.SearchRequest; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; - -import java.io.IOException; - -/*** - * 自定义扩展 Torrent Dao - * - * @author Mr.Xu - * @date 2019-02-25 11:07 - **/ -public interface TorrentDao { - - /** - * Elasticsearch 创建索引 - * - * @param torrent - * @return void - */ - void index(Torrent torrent) throws IOException; - - /** - * 存在则更新,不存在则插入 - * - * @param torrent - * @return void - */ - void upsert(Torrent torrent); - - /** - * 分页搜索 - * - * @param request, pageable - * @return org.springframework.data.domain.Page - */ - Page query(SearchRequest request, Pageable pageable); - - /** - * 相关推荐搜索 - * - * @param torrent - * @param fields - * @param pageable - * @return org.springframework.data.domain.Page - */ - Page searchSimilar(Torrent torrent, String[] fields, Pageable pageable); -} diff --git a/blade-spider-service/blade-datastore/src/main/java/org/springblade/datastore/repository/customer/TorrentDaoImpl.java b/blade-spider-service/blade-datastore/src/main/java/org/springblade/datastore/repository/customer/TorrentDaoImpl.java deleted file mode 100644 index 6b90acce..00000000 --- a/blade-spider-service/blade-datastore/src/main/java/org/springblade/datastore/repository/customer/TorrentDaoImpl.java +++ /dev/null @@ -1,158 +0,0 @@ -package org.springblade.datastore.repository.customer; - -import org.elasticsearch.action.index.IndexRequest; -import org.elasticsearch.action.search.SearchResponse; -import org.elasticsearch.action.update.UpdateRequest; -import org.elasticsearch.client.Client; -import org.elasticsearch.common.xcontent.XContentBuilder; -import org.elasticsearch.index.query.BoolQueryBuilder; -import org.elasticsearch.index.query.QueryBuilders; -import org.elasticsearch.search.SearchHit; -import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder; -import org.springblade.spider.common.entity.Torrent; -import org.springblade.spider.common.request.SearchRequest; -import org.springblade.spider.common.util.JSONUtil; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.elasticsearch.core.DefaultResultMapper; -import org.springframework.data.elasticsearch.core.ElasticsearchTemplate; -import org.springframework.data.elasticsearch.core.ResultsMapper; -import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage; -import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl; -import org.springframework.data.elasticsearch.core.query.MoreLikeThisQuery; -import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; -import org.springframework.data.mongodb.core.MongoTemplate; -import org.springframework.data.mongodb.core.query.Criteria; -import org.springframework.data.mongodb.core.query.Query; -import org.springframework.data.mongodb.core.query.Update; -import org.springframework.stereotype.Service; -import org.springframework.util.StringUtils; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; -import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery; -import static org.elasticsearch.index.query.QueryBuilders.matchQuery; -import static org.elasticsearch.search.sort.SortBuilders.fieldSort; -import static org.elasticsearch.search.sort.SortOrder.ASC; -import static org.elasticsearch.search.sort.SortOrder.DESC; - -/*** - * 自定义扩展 Torrent Dao 实现类 - * - * @author Mr.Xu - * @date 2019-02-25 11:09 - **/ -@Service -public class TorrentDaoImpl implements TorrentDao { - - @Autowired - private ElasticsearchTemplate elasticsearchTemplate; - @Autowired - private MongoTemplate mongoTemplate; - - @Override - public void index(Torrent torrent) throws IOException { - Client client = elasticsearchTemplate.getClient(); - XContentBuilder source = jsonBuilder() - .startObject() - .field("fileName", torrent.getFileName()) - .field("fileType", torrent.getFileType()) - .field("fileSize", torrent.getFileSize()) - .field("createDate", torrent.getCreateDate()) - .endObject(); - - IndexRequest indexRequest = new IndexRequest("dodder", "torrent", torrent.getInfoHash()) - .source(source); - UpdateRequest updateRequest = new UpdateRequest("dodder", "torrent", torrent.getInfoHash()) - .doc(indexRequest) - .docAsUpsert(true); - client.update(updateRequest); - } - - @Override - public void upsert(Torrent torrent) { - Query query = Query.query(Criteria.where("infoHash").is(torrent.getInfoHash())); - Update update = new Update().addToSet("createDate", torrent.getCreateDate()) - .addToSet("fileName", torrent.getFileName()) - .addToSet("files", torrent.getFiles()) - .addToSet("fileSize", torrent.getFileSize()) - .addToSet("fileType", torrent.getFileType()); - mongoTemplate.upsert(query, update, Torrent.class); - } - - - - @Override - public Page query(SearchRequest request, Pageable pageable) { - NativeSearchQueryBuilder query = new NativeSearchQueryBuilder(); - BoolQueryBuilder boolQuery = QueryBuilders.boolQuery(); - - if (request.getFileName() != null) { - boolQuery.must(matchQuery("fileName", request.getFileName())); - } else { - boolQuery.must(matchAllQuery()); - } - if (request.getFileType() != null) { - boolQuery.must(matchQuery("fileType", request.getFileType())); - } - if (request.getSortBy() != null) { - if (ASC.toString().equals(request.getOrder())) - query.withSort(fieldSort("createDate").order(ASC)); - else - query.withSort(fieldSort("createDate").order(DESC)); - } else if (StringUtils.isEmpty(request.getFileName())) { - query.withSort(fieldSort("createDate").order(DESC)); - } - - query.withPageable(pageable) - .withQuery(boolQuery) - .withHighlightFields(new HighlightBuilder.Field("fileName")); - Page page = elasticsearchTemplate.queryForPage(query.build(), Torrent.class, highlightResultMapper); - return page; - } - - @Override - public Page searchSimilar(Torrent torrent, String[] fields, Pageable pageable) { - MoreLikeThisQuery query = new MoreLikeThisQuery(); - query.setId(torrent.getInfoHash()); - query.setPageable(pageable); - query.setMinTermFreq(1); - if (fields != null) { - query.addFields(fields); - } - return elasticsearchTemplate.moreLikeThis(query, Torrent.class); - } - - private ResultsMapper highlightResultMapper = new DefaultResultMapper() { - - @Override - public AggregatedPage mapResults(SearchResponse response, Class clazz, Pageable pageable) { - - long totalHits = response.getHits().getTotalHits(); - float maxScore = response.getHits().getMaxScore(); - - List results = new ArrayList<>(); - for (SearchHit hit : response.getHits().getHits()) { - if (hit == null) - continue; - Torrent result; - result = JSONUtil.parseObject(hit.getSourceAsString(), Torrent.class); - result.setInfoHash(hit.getId()); - if (hit.getHighlightFields().containsKey("fileName")) - result.setFileName(hit.getHighlightFields().get("fileName").fragments()[0].toString()); - else - result.setFileName((String) hit.getSourceAsMap().get("fileName")); - results.add(result); - } - return new AggregatedPageImpl<>((List) results, pageable, totalHits, response.getAggregations(), response.getScrollId(), - maxScore); - } - - }; - - -} diff --git a/blade-spider-service/blade-datastore/src/main/java/org/springblade/datastore/service/TorrentService.java b/blade-spider-service/blade-datastore/src/main/java/org/springblade/datastore/service/TorrentService.java deleted file mode 100644 index 5bfabd4f..00000000 --- a/blade-spider-service/blade-datastore/src/main/java/org/springblade/datastore/service/TorrentService.java +++ /dev/null @@ -1,43 +0,0 @@ -package org.springblade.datastore.service; - -import org.springblade.datastore.repository.TorrentRepository; -import org.springblade.spider.common.entity.Torrent; -import org.springblade.spider.common.request.SearchRequest; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Service; - -import java.io.IOException; -import java.util.Optional; - -@Service -public class TorrentService { - - @Autowired - private TorrentRepository torrentRepository; - - public boolean existsById(String infoHash) { - return torrentRepository.existsById(infoHash); - } - - public void index(Torrent torrent) throws IOException { - torrentRepository.index(torrent); - } - - public void upsert(Torrent torrent) { - torrentRepository.upsert(torrent); - } - - public Page query(SearchRequest request, Pageable pageable) { - return torrentRepository.query(request, pageable); - } - - public Optional findById(String infoHash) { - return torrentRepository.findById(infoHash); - } - - public Page findSimilar(Torrent torrent, Pageable pageable) { - return torrentRepository.searchSimilar(torrent, new String[] {"fileName"}, pageable); - } -} diff --git a/blade-spider-service/blade-datastore/src/main/java/org/springblade/datastore/stream/MessageStreams.java b/blade-spider-service/blade-datastore/src/main/java/org/springblade/datastore/stream/MessageStreams.java deleted file mode 100644 index 15e33f66..00000000 --- a/blade-spider-service/blade-datastore/src/main/java/org/springblade/datastore/stream/MessageStreams.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.springblade.datastore.stream; - -import org.springframework.cloud.stream.annotation.Input; -import org.springframework.messaging.MessageChannel; - -public interface MessageStreams { - @Input("torrent-message-in") - MessageChannel torrentMessageInput(); - - @Input("index-message-in") - MessageChannel indexMessageInput(); -} diff --git a/blade-spider-service/blade-datastore/src/main/resources/application.yml b/blade-spider-service/blade-datastore/src/main/resources/application.yml deleted file mode 100644 index 04e9e1e1..00000000 --- a/blade-spider-service/blade-datastore/src/main/resources/application.yml +++ /dev/null @@ -1,40 +0,0 @@ -server: - port: 8764 -spring: - # 数据源配置 - datasource: - url: ${blade.datasource.dev.url} - username: ${blade.datasource.dev.username} - password: ${blade.datasource.dev.password} - cloud: - stream: - kafka: - binder: - brokers: 192.168.80.168 - bindings: - torrent-message-in: - group: torrent-store-group - destination: torrentMessages - contentType: application/json - consumer: - compressionType: gzip - autoCommitOffset: false #设置手动提交偏移 - index-message-in: - group: torrent-index-group - destination: torrentMessages - contentType: application/json - consumer: - compressionType: gzip - autoCommitOffset: false #设置手动提交偏移 - data: - elasticsearch: - cluster-nodes: 192.168.80.168:9300 - repositories: - enabled: true - properties: - path: - logs: ./elasticsearch/log #elasticsearch日志存储目录 - data: ./elasticsearch/data #elasticsearch数据存储目录 -logging: - level: - root: info diff --git a/blade-spider-service/blade-datastore/src/main/resources/elasticsearch_custom_comma_analyzer.json b/blade-spider-service/blade-datastore/src/main/resources/elasticsearch_custom_comma_analyzer.json deleted file mode 100644 index 2e7f8b0c..00000000 --- a/blade-spider-service/blade-datastore/src/main/resources/elasticsearch_custom_comma_analyzer.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "index": { - "number_of_shards": "1", - "number_of_replicas": "1" - }, - "analysis": { - "analyzer": { - "comma": { - "type": "pattern", - "pattern":"," - } - } - } -} \ No newline at end of file diff --git a/blade-spider-service/blade-datastore/src/main/resources/mongo-pool.properties b/blade-spider-service/blade-datastore/src/main/resources/mongo-pool.properties deleted file mode 100644 index 45081a4b..00000000 --- a/blade-spider-service/blade-datastore/src/main/resources/mongo-pool.properties +++ /dev/null @@ -1,25 +0,0 @@ -spring.data.mongodb.address=192.168.80.168:27017 -spring.data.mongodb.database=admin -spring.data.mongodb.username=admin -spring.data.mongodb.password=123456 - -# Configure spring.data.mongodbDB Pool -spring.data.mongodb.min-connections-per-host=10 -spring.data.mongodb.max-connections-per-host=200 -spring.data.mongodb.threads-allowed-to-block-for-connection-multiplier=5 -spring.data.mongodb.server-selection-timeout=30000 -spring.data.mongodb.max-wait-time=120000 -spring.data.mongodb.max-connection-idel-time=0 -spring.data.mongodb.max-connection-life-time=0 -spring.data.mongodb.connect-timeout=10000 -spring.data.mongodb.socket-timeout=0 -spring.data.mongodb.socket-keep-alive=false -spring.data.mongodb.ssl-enabled=false -spring.data.mongodb.ssl-invalid-host-name-allowed=false -spring.data.mongodb.always-use-m-beans=false -spring.data.mongodb.heartbeat-socket-timeout=20000 -spring.data.mongodb.heartbeat-connect-timeout=20000 -spring.data.mongodb.min-heartbeat-frequency=500 -spring.data.mongodb.heartbeat-frequency=10000 -spring.data.mongodb.local-threshold=15 -spring.data.mongodb.authentication-database=admin diff --git a/blade-spider-service/blade-datastore/src/main/resources/torrent_search_mapping.json b/blade-spider-service/blade-datastore/src/main/resources/torrent_search_mapping.json deleted file mode 100644 index 3d3f831d..00000000 --- a/blade-spider-service/blade-datastore/src/main/resources/torrent_search_mapping.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "torrent": { - "properties": { - "createDate": { - "type": "long" - }, - "fileName": { - "type": "text", - "analyzer": "ik_max_word", - "search_analyzer": "ik_smart" - }, - "fileSize": { - "type": "long" - }, - "fileType": { - "type": "text", - "analyzer": "comma", - "search_analyzer": "comma" - } - } - } -} \ No newline at end of file diff --git "a/blade-spider-service/doc/DHT\350\265\204\346\226\231.md" "b/blade-spider-service/doc/DHT\350\265\204\346\226\231.md" deleted file mode 100644 index 421520c9..00000000 --- "a/blade-spider-service/doc/DHT\350\265\204\346\226\231.md" +++ /dev/null @@ -1,4 +0,0 @@ -### DHT介绍资料网址 -https://blog.csdn.net/liweisnake/article/details/9207919 -参考项目:https://github.com/BrightStarry/zx-bt -DHT网络资料文档:https://www.aneasystone.com/archives/2015/05/analyze-magnet-protocol-using-wireshark.html \ No newline at end of file diff --git a/blade-spider-service/doc/thirdJar/sourceforge.rar b/blade-spider-service/doc/thirdJar/sourceforge.rar deleted file mode 100644 index fc37a266d465a0701f311f16f706cface97e38bb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3643871 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