├── 20190305.jpg ├── dodder-web ├── src │ └── main │ │ ├── resources │ │ ├── static │ │ │ ├── img │ │ │ │ ├── excel.gif │ │ │ │ ├── image.gif │ │ │ │ ├── info.gif │ │ │ │ ├── minsb.gif │ │ │ │ ├── video.gif │ │ │ │ ├── word.gif │ │ │ │ ├── favicon.ico │ │ │ │ ├── torrent.gif │ │ │ │ ├── subtitle.gif │ │ │ │ └── treeview │ │ │ │ │ ├── file.gif │ │ │ │ │ ├── folder.gif │ │ │ │ │ ├── minus.gif │ │ │ │ │ ├── plus.gif │ │ │ │ │ ├── ajax-loader.gif │ │ │ │ │ ├── folder-closed.gif │ │ │ │ │ ├── treeview-gray.gif │ │ │ │ │ ├── treeview-red.gif │ │ │ │ │ ├── treeview-black.gif │ │ │ │ │ ├── treeview-default.gif │ │ │ │ │ ├── treeview-famfamfam.gif │ │ │ │ │ ├── treeview-gray-line.gif │ │ │ │ │ ├── treeview-red-line.gif │ │ │ │ │ ├── treeview-black-line.gif │ │ │ │ │ ├── treeview-default-line.gif │ │ │ │ │ └── treeview-famfamfam-line.gif │ │ │ ├── layui │ │ │ │ ├── font │ │ │ │ │ ├── iconfont.eot │ │ │ │ │ ├── iconfont.ttf │ │ │ │ │ └── iconfont.woff │ │ │ │ ├── images │ │ │ │ │ └── face │ │ │ │ │ │ ├── 0.gif │ │ │ │ │ │ ├── 1.gif │ │ │ │ │ │ ├── 2.gif │ │ │ │ │ │ ├── 3.gif │ │ │ │ │ │ ├── 4.gif │ │ │ │ │ │ ├── 5.gif │ │ │ │ │ │ ├── 6.gif │ │ │ │ │ │ ├── 7.gif │ │ │ │ │ │ ├── 8.gif │ │ │ │ │ │ ├── 9.gif │ │ │ │ │ │ ├── 10.gif │ │ │ │ │ │ ├── 11.gif │ │ │ │ │ │ ├── 12.gif │ │ │ │ │ │ ├── 13.gif │ │ │ │ │ │ ├── 14.gif │ │ │ │ │ │ ├── 15.gif │ │ │ │ │ │ ├── 16.gif │ │ │ │ │ │ ├── 17.gif │ │ │ │ │ │ ├── 18.gif │ │ │ │ │ │ ├── 19.gif │ │ │ │ │ │ ├── 20.gif │ │ │ │ │ │ ├── 21.gif │ │ │ │ │ │ ├── 22.gif │ │ │ │ │ │ ├── 23.gif │ │ │ │ │ │ ├── 24.gif │ │ │ │ │ │ ├── 25.gif │ │ │ │ │ │ ├── 26.gif │ │ │ │ │ │ ├── 27.gif │ │ │ │ │ │ ├── 28.gif │ │ │ │ │ │ ├── 29.gif │ │ │ │ │ │ ├── 30.gif │ │ │ │ │ │ ├── 31.gif │ │ │ │ │ │ ├── 32.gif │ │ │ │ │ │ ├── 33.gif │ │ │ │ │ │ ├── 34.gif │ │ │ │ │ │ ├── 35.gif │ │ │ │ │ │ ├── 36.gif │ │ │ │ │ │ ├── 37.gif │ │ │ │ │ │ ├── 38.gif │ │ │ │ │ │ ├── 39.gif │ │ │ │ │ │ ├── 40.gif │ │ │ │ │ │ ├── 41.gif │ │ │ │ │ │ ├── 42.gif │ │ │ │ │ │ ├── 43.gif │ │ │ │ │ │ ├── 44.gif │ │ │ │ │ │ ├── 45.gif │ │ │ │ │ │ ├── 46.gif │ │ │ │ │ │ ├── 47.gif │ │ │ │ │ │ ├── 48.gif │ │ │ │ │ │ ├── 49.gif │ │ │ │ │ │ ├── 50.gif │ │ │ │ │ │ ├── 51.gif │ │ │ │ │ │ ├── 52.gif │ │ │ │ │ │ ├── 53.gif │ │ │ │ │ │ ├── 54.gif │ │ │ │ │ │ ├── 55.gif │ │ │ │ │ │ ├── 56.gif │ │ │ │ │ │ ├── 57.gif │ │ │ │ │ │ ├── 58.gif │ │ │ │ │ │ ├── 59.gif │ │ │ │ │ │ ├── 60.gif │ │ │ │ │ │ ├── 61.gif │ │ │ │ │ │ ├── 62.gif │ │ │ │ │ │ ├── 63.gif │ │ │ │ │ │ ├── 64.gif │ │ │ │ │ │ ├── 65.gif │ │ │ │ │ │ ├── 66.gif │ │ │ │ │ │ ├── 67.gif │ │ │ │ │ │ ├── 68.gif │ │ │ │ │ │ ├── 69.gif │ │ │ │ │ │ ├── 70.gif │ │ │ │ │ │ └── 71.gif │ │ │ │ ├── css │ │ │ │ │ └── modules │ │ │ │ │ │ ├── layer │ │ │ │ │ │ └── default │ │ │ │ │ │ │ ├── icon.png │ │ │ │ │ │ │ ├── icon-ext.png │ │ │ │ │ │ │ ├── loading-0.gif │ │ │ │ │ │ │ ├── loading-1.gif │ │ │ │ │ │ │ └── loading-2.gif │ │ │ │ │ │ └── code.css │ │ │ │ └── lay │ │ │ │ │ └── modules │ │ │ │ │ ├── code.js │ │ │ │ │ ├── laytpl.js │ │ │ │ │ ├── flow.js │ │ │ │ │ ├── rate.js │ │ │ │ │ ├── tree.js │ │ │ │ │ ├── util.js │ │ │ │ │ ├── carousel.js │ │ │ │ │ └── laypage.js │ │ │ ├── js │ │ │ │ └── search.js │ │ │ └── css │ │ │ │ └── jquery.treeview.css │ │ ├── application.yml │ │ └── templates │ │ │ ├── common │ │ │ ├── footer.html │ │ │ ├── banner.html │ │ │ ├── profile.html │ │ │ ├── status.html │ │ │ └── header.html │ │ │ ├── info.html │ │ │ └── error │ │ │ └── 404.html │ │ └── java │ │ └── cc │ │ └── dodder │ │ └── web │ │ ├── DodderWebApplication.java │ │ ├── dialect │ │ ├── GlobalUtilDialect.java │ │ └── GlobalUtilExpressionObjectFactory.java │ │ └── controller │ │ └── IndexController.java ├── .gitignore └── pom.xml ├── dodder-torrent-store-service ├── src │ ├── main │ │ ├── resources │ │ │ ├── mongodb.properties │ │ │ ├── torrent_search_mapping.json │ │ │ ├── application.yml │ │ │ ├── mongodb.xml │ │ │ └── elasticsearch_custom_comma_analyzer.json │ │ └── java │ │ │ └── cc │ │ │ └── dodder │ │ │ └── torrent │ │ │ └── store │ │ │ ├── config │ │ │ ├── MongoConfig.java │ │ │ └── ElasticsearchConfiguration.java │ │ │ ├── repository │ │ │ └── TorrentDao.java │ │ │ ├── service │ │ │ └── TorrentService.java │ │ │ ├── TorrentStoreServiceApplication.java │ │ │ └── api │ │ │ └── impl │ │ │ └── TorrentApiImpl.java │ └── test │ │ └── java │ │ └── cc │ │ └── dodder │ │ └── torrent │ │ └── store │ │ └── service │ │ └── TorrentStoreServiceApplicationTests.java ├── .gitignore └── pom.xml ├── words ├── src │ └── main │ │ ├── java │ │ └── toolgood │ │ │ └── words │ │ │ ├── internals │ │ │ ├── TwoTuple.java │ │ │ ├── TrieNode2.java │ │ │ ├── TrieNode2Ex.java │ │ │ ├── TrieNode3.java │ │ │ ├── TrieNode.java │ │ │ ├── TrieNode3Ex.java │ │ │ ├── IntDictionary.java │ │ │ ├── BaseSearch.java │ │ │ └── TrieNodeEx.java │ │ │ ├── IllegalWordsSearchResult.java │ │ │ ├── WordsSearchResult.java │ │ │ ├── StringSearch.java │ │ │ ├── WordsSearch.java │ │ │ └── StringSearchEx2.java │ │ └── resources │ │ ├── t2hk.dat │ │ └── pyName.txt └── pom.xml ├── dodder-dht-server ├── src │ ├── main │ │ ├── resources │ │ │ ├── banner.txt │ │ │ └── application.yml │ │ └── java │ │ │ └── cc │ │ │ └── dodder │ │ │ └── dhtserver │ │ │ ├── netty │ │ │ ├── entity │ │ │ │ ├── Node.java │ │ │ │ └── UniqueBlockingQueue.java │ │ │ ├── config │ │ │ │ ├── DHTChannelInitializer.java │ │ │ │ └── NettyConfig.java │ │ │ ├── schedule │ │ │ │ └── AutoJoinDHT.java │ │ │ └── DHTServer.java │ │ │ └── DhtServerApplication.java │ └── test │ │ └── java │ │ └── cc │ │ └── dodder │ │ └── dhtserver │ │ └── DhtServerApplicationTests.java ├── .gitignore └── pom.xml ├── dodder-torrent-download-service ├── .gitignore ├── src │ ├── test │ │ └── java │ │ │ └── cc │ │ │ └── dodder │ │ │ └── torrent │ │ │ └── download │ │ │ └── stream │ │ │ └── TorrentDownloadServiceApplicationTests.java │ └── main │ │ ├── java │ │ └── cc │ │ │ └── dodder │ │ │ └── torrent │ │ │ └── download │ │ │ ├── task │ │ │ ├── DownloadTask.java │ │ │ └── BlockingExecutor.java │ │ │ ├── client │ │ │ ├── Constants.java │ │ │ └── PipedStream.java │ │ │ ├── util │ │ │ └── SpringContextUtil.java │ │ │ └── TorrentDownloadServiceApplication.java │ │ └── resources │ │ └── application.yml └── pom.xml ├── dodder-common ├── src │ └── main │ │ └── java │ │ └── cc │ │ └── dodder │ │ └── common │ │ ├── vo │ │ ├── TorrentVO.java │ │ └── TorrentPageVO.java │ │ ├── entity │ │ ├── WebConfig.java │ │ ├── DownloadMsgInfo.java │ │ ├── Result.java │ │ ├── Node.java │ │ ├── Torrent.java │ │ └── Tree.java │ │ ├── request │ │ └── SearchRequest.java │ │ └── util │ │ ├── JSONUtil.java │ │ ├── LanguageUtil.java │ │ ├── SystemClock.java │ │ ├── ByteUtil.java │ │ ├── FileTypeUtil.java │ │ ├── SensitiveWordsUtil.java │ │ ├── ExtensionUtil.java │ │ ├── bencode │ │ └── BencodingUtils.java │ │ ├── NodeIdUtil.java │ │ └── NetworkUtil.java └── pom.xml ├── dodder-api ├── src │ └── main │ │ └── java │ │ └── cc │ │ └── dodder │ │ └── api │ │ └── TorrentApi.java └── pom.xml ├── LICENSE ├── pom.xml └── README.md /20190305.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/20190305.jpg -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/img/excel.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/img/excel.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/img/image.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/img/image.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/img/info.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/img/info.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/img/minsb.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/img/minsb.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/img/video.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/img/video.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/img/word.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/img/word.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/img/favicon.ico -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/img/torrent.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/img/torrent.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/img/subtitle.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/img/subtitle.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/img/treeview/file.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/img/treeview/file.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/img/treeview/folder.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/img/treeview/folder.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/img/treeview/minus.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/img/treeview/minus.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/img/treeview/plus.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/img/treeview/plus.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/font/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/font/iconfont.eot -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/font/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/font/iconfont.ttf -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/0.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/0.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/1.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/2.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/3.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/4.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/4.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/5.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/5.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/6.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/6.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/7.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/7.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/8.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/8.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/9.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/9.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/font/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/font/iconfont.woff -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/10.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/10.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/11.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/11.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/12.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/12.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/13.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/13.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/14.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/14.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/15.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/15.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/16.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/17.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/17.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/18.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/18.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/19.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/19.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/20.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/20.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/21.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/21.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/22.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/22.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/23.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/23.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/24.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/24.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/25.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/25.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/26.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/26.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/27.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/27.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/28.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/28.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/29.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/29.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/30.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/30.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/31.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/31.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/32.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/32.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/33.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/33.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/34.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/34.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/35.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/35.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/36.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/36.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/37.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/37.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/38.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/38.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/39.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/39.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/40.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/40.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/41.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/41.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/42.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/42.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/43.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/43.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/44.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/44.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/45.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/45.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/46.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/46.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/47.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/47.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/48.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/48.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/49.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/49.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/50.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/50.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/51.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/51.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/52.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/52.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/53.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/53.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/54.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/54.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/55.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/55.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/56.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/56.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/57.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/57.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/58.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/58.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/59.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/59.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/60.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/60.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/61.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/61.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/62.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/62.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/63.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/63.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/64.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/64.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/65.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/65.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/66.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/66.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/67.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/67.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/68.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/68.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/69.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/69.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/70.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/70.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/images/face/71.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/images/face/71.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/img/treeview/ajax-loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/img/treeview/ajax-loader.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/img/treeview/folder-closed.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/img/treeview/folder-closed.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/img/treeview/treeview-gray.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/img/treeview/treeview-gray.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/img/treeview/treeview-red.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/img/treeview/treeview-red.gif -------------------------------------------------------------------------------- /dodder-torrent-store-service/src/main/resources/mongodb.properties: -------------------------------------------------------------------------------- 1 | mongo.host=127.0.0.1 2 | mongo.port=27017 3 | mongo.dbname=dodder 4 | mongo.pool-min-size=50 5 | mongo.pool-max-size=50 -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/img/treeview/treeview-black.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/img/treeview/treeview-black.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/img/treeview/treeview-default.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/img/treeview/treeview-default.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/img/treeview/treeview-famfamfam.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/img/treeview/treeview-famfamfam.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/img/treeview/treeview-gray-line.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/img/treeview/treeview-gray-line.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/img/treeview/treeview-red-line.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/img/treeview/treeview-red-line.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/img/treeview/treeview-black-line.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/img/treeview/treeview-black-line.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/img/treeview/treeview-default-line.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/img/treeview/treeview-default-line.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/img/treeview/treeview-famfamfam-line.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/img/treeview/treeview-famfamfam-line.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/css/modules/layer/default/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/css/modules/layer/default/icon.png -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/css/modules/layer/default/icon-ext.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/css/modules/layer/default/icon-ext.png -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/css/modules/layer/default/loading-0.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/css/modules/layer/default/loading-0.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/css/modules/layer/default/loading-1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/css/modules/layer/default/loading-1.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/css/modules/layer/default/loading-2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwlcn/Dodder/HEAD/dodder-web/src/main/resources/static/layui/css/modules/layer/default/loading-2.gif -------------------------------------------------------------------------------- /dodder-web/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 666 3 | spring: 4 | application: 5 | name: web 6 | 7 | thymeleaf: 8 | cache: false 9 | mode: LEGACYHTML5 10 | store: 11 | service: 12 | version: 1.0.0 13 | url: dubbo://127.0.0.1:23456 -------------------------------------------------------------------------------- /words/src/main/java/toolgood/words/internals/TwoTuple.java: -------------------------------------------------------------------------------- 1 | package toolgood.words.internals; 2 | 3 | public class TwoTuple { 4 | public A Item1; 5 | public B Item2; 6 | 7 | public TwoTuple(A a, B b) { 8 | this.Item1 = a; 9 | this.Item2 = b; 10 | } 11 | } -------------------------------------------------------------------------------- /dodder-web/src/main/resources/templates/common/footer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | -------------------------------------------------------------------------------- /dodder-dht-server/src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | ${AnsiColor.BRIGHT_YELLOW} 2 | ________ _________________ 3 | ___ __ \___________ /_____ /____________ 4 | __ / / / __ \ __ /_ __ /_ _ \_ ___/ 5 | _ /_/ // /_/ / /_/ / / /_/ / / __/ / 6 | /_____/ \____/\__,_/ \__,_/ \___//_/ 7 | ${AnsiColor.BRIGHT_BLUE} 8 | :: Dodder :: (${application.version}) 9 | -------------------------------------------------------------------------------- /dodder-web/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | .sts4-cache 12 | 13 | ### IntelliJ IDEA ### 14 | .idea 15 | *.iws 16 | *.iml 17 | *.ipr 18 | 19 | ### NetBeans ### 20 | /nbproject/private/ 21 | /nbbuild/ 22 | /dist/ 23 | /nbdist/ 24 | /.nb-gradle/ 25 | /build/ 26 | -------------------------------------------------------------------------------- /dodder-dht-server/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | .sts4-cache 12 | 13 | ### IntelliJ IDEA ### 14 | .idea 15 | *.iws 16 | *.iml 17 | *.ipr 18 | 19 | ### NetBeans ### 20 | /nbproject/private/ 21 | /nbbuild/ 22 | /dist/ 23 | /nbdist/ 24 | /.nb-gradle/ 25 | /build/ 26 | -------------------------------------------------------------------------------- /dodder-torrent-download-service/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | .sts4-cache 12 | 13 | ### IntelliJ IDEA ### 14 | .idea 15 | *.iws 16 | *.iml 17 | *.ipr 18 | 19 | ### NetBeans ### 20 | /nbproject/private/ 21 | /nbbuild/ 22 | /dist/ 23 | /nbdist/ 24 | /.nb-gradle/ 25 | /build/ 26 | -------------------------------------------------------------------------------- /dodder-torrent-store-service/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | .sts4-cache 12 | 13 | ### IntelliJ IDEA ### 14 | .idea 15 | *.iws 16 | *.iml 17 | *.ipr 18 | 19 | ### NetBeans ### 20 | /nbproject/private/ 21 | /nbbuild/ 22 | /dist/ 23 | /nbdist/ 24 | /.nb-gradle/ 25 | /build/ 26 | -------------------------------------------------------------------------------- /dodder-web/src/main/java/cc/dodder/web/DodderWebApplication.java: -------------------------------------------------------------------------------- 1 | package cc.dodder.web; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class DodderWebApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(DodderWebApplication.class, args); 11 | } 12 | 13 | } 14 | 15 | -------------------------------------------------------------------------------- /dodder-common/src/main/java/cc/dodder/common/vo/TorrentVO.java: -------------------------------------------------------------------------------- 1 | package cc.dodder.common.vo; 2 | 3 | import cc.dodder.common.entity.Torrent; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | 8 | import java.io.Serializable; 9 | import java.util.List; 10 | 11 | @Getter @Setter @Builder 12 | public class TorrentVO implements Serializable { 13 | 14 | private Torrent torrent; 15 | private List similar; 16 | } 17 | -------------------------------------------------------------------------------- /dodder-common/src/main/java/cc/dodder/common/entity/WebConfig.java: -------------------------------------------------------------------------------- 1 | package cc.dodder.common.entity; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | import org.springframework.data.annotation.Id; 6 | 7 | import java.util.Date; 8 | 9 | @Getter @Setter 10 | public class WebConfig { 11 | 12 | @Id 13 | private String id; 14 | 15 | private String adminUsername; 16 | private String adminPassword; 17 | 18 | private Date startTime; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /dodder-web/src/main/resources/templates/common/banner.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /dodder-dht-server/src/test/java/cc/dodder/dhtserver/DhtServerApplicationTests.java: -------------------------------------------------------------------------------- 1 | package cc.dodder.dhtserver; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class DhtServerApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | 18 | -------------------------------------------------------------------------------- /dodder-dht-server/src/main/java/cc/dodder/dhtserver/netty/entity/Node.java: -------------------------------------------------------------------------------- 1 | package cc.dodder.dhtserver.netty.entity; 2 | 3 | import lombok.Data; 4 | 5 | import java.net.InetSocketAddress; 6 | 7 | /*** 8 | * DHT 节点 9 | * 10 | * @author: Mr.Xu 11 | * @create: 2019-02-17 14:26 12 | **/ 13 | @Data 14 | public class Node { 15 | 16 | private byte[] nodeId; 17 | private InetSocketAddress addr; 18 | 19 | public Node(byte[] nodeId, InetSocketAddress addr) { 20 | this.nodeId = nodeId; 21 | this.addr = addr; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /dodder-common/src/main/java/cc/dodder/common/vo/TorrentPageVO.java: -------------------------------------------------------------------------------- 1 | package cc.dodder.common.vo; 2 | 3 | import cc.dodder.common.entity.Torrent; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | 8 | import java.io.Serializable; 9 | import java.util.List; 10 | 11 | @Getter @Setter @Builder 12 | public class TorrentPageVO implements Serializable { 13 | 14 | private List list; 15 | private Long total; 16 | private Integer page; 17 | private Integer limit; 18 | 19 | private Long dbTotal; 20 | } 21 | -------------------------------------------------------------------------------- /dodder-dht-server/src/main/java/cc/dodder/dhtserver/DhtServerApplication.java: -------------------------------------------------------------------------------- 1 | package cc.dodder.dhtserver; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.scheduling.annotation.EnableScheduling; 6 | 7 | @EnableScheduling 8 | @SpringBootApplication 9 | public class DhtServerApplication { 10 | 11 | 12 | public static void main(String[] args) { 13 | SpringApplication.run(DhtServerApplication.class, args); 14 | } 15 | 16 | } 17 | 18 | -------------------------------------------------------------------------------- /dodder-torrent-download-service/src/test/java/cc/dodder/torrent/download/stream/TorrentDownloadServiceApplicationTests.java: -------------------------------------------------------------------------------- 1 | package cc.dodder.torrent.download.stream; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | @RunWith(SpringRunner.class) 8 | @SpringBootTest 9 | public class TorrentDownloadServiceApplicationTests { 10 | 11 | @Test 12 | public void contextLoads() { 13 | } 14 | 15 | } 16 | 17 | -------------------------------------------------------------------------------- /dodder-torrent-store-service/src/test/java/cc/dodder/torrent/store/service/TorrentStoreServiceApplicationTests.java: -------------------------------------------------------------------------------- 1 | package cc.dodder.torrent.store.service; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class TorrentStoreServiceApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | 18 | -------------------------------------------------------------------------------- /dodder-web/src/main/resources/templates/common/profile.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 |
社区简介
6 |
7 | 爱技术,爱分享。
8 | 开源地址:https://github.com/xwlcn/Dodder
9 | 关于作者:https://0o0.me 10 |
11 |
12 |
13 | -------------------------------------------------------------------------------- /words/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | dodder 7 | cc.dodder 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | toolgood.words 13 | words 14 | 15 | 16 | -------------------------------------------------------------------------------- /dodder-common/src/main/java/cc/dodder/common/request/SearchRequest.java: -------------------------------------------------------------------------------- 1 | package cc.dodder.common.request; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | import java.io.Serializable; 7 | 8 | /*** 9 | * 搜索请求 10 | * 11 | * @author Mr.Xu 12 | * @date 2019-03-02 13:52 13 | **/ 14 | @Getter @Setter 15 | public class SearchRequest implements Serializable { 16 | 17 | private String fileName; 18 | private String fileType; 19 | private String sortBy; 20 | private String order; 21 | 22 | private Integer page; 23 | private Integer limit = 20; 24 | 25 | public SearchRequest() { 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/js/search.js: -------------------------------------------------------------------------------- 1 | layui.use('layer', function(){ 2 | var layer = layui.layer; 3 | document.getElementById('search-btn').addEventListener('click', function(){ 4 | search(); 5 | }); 6 | document.getElementById('keyword').onkeydown = function(e) { 7 | if (e.keyCode == 13) 8 | search(); 9 | } 10 | }); 11 | 12 | function search() { 13 | var keyword = document.getElementById('keyword').value; 14 | if (keyword.trim() == '') { 15 | layer.msg("搜索内容不能为空!"); 16 | return; 17 | } 18 | window.location.href = '/?fileName=' + keyword; 19 | } -------------------------------------------------------------------------------- /dodder-web/src/main/resources/templates/common/status.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 |
资源抓取状态
6 |
7 |

文件类型:

8 |

资源总数:

9 |
10 |
11 |
12 | -------------------------------------------------------------------------------- /dodder-torrent-store-service/src/main/resources/torrent_search_mapping.json: -------------------------------------------------------------------------------- 1 | { 2 | "properties": { 3 | "fileName": { 4 | "type": "text", 5 | "analyzer": "my_ik_max_word", 6 | "search_analyzer": "my_ik_smart" 7 | }, 8 | "fileNameRu": { 9 | "type": "text", 10 | "analyzer": "rebuilt_russian", 11 | "search_analyzer": "rebuilt_russian" 12 | }, 13 | "createDate": { 14 | "type": "keyword" 15 | }, 16 | "fileSize": { 17 | "type": "long", 18 | "index": false 19 | }, 20 | "fileType": { 21 | "type": "text", 22 | "analyzer": "comma", 23 | "search_analyzer": "comma" 24 | }, 25 | "isXxx": { 26 | "type": "keyword" 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /dodder-common/src/main/java/cc/dodder/common/entity/DownloadMsgInfo.java: -------------------------------------------------------------------------------- 1 | package cc.dodder.common.entity; 2 | 3 | import cc.dodder.common.util.SystemClock; 4 | import lombok.Data; 5 | 6 | import java.io.Serializable; 7 | 8 | /*** 9 | * Peer Wire 下载消息信息 10 | * 11 | * @author Mr.Xu 12 | * @date 2019-02-20 15:53 13 | **/ 14 | @Data 15 | public class DownloadMsgInfo implements Serializable { 16 | 17 | private String ip; 18 | private int port; 19 | private byte[] infoHash; 20 | private long timestamp; 21 | private String crc64; 22 | 23 | public DownloadMsgInfo() { 24 | 25 | } 26 | 27 | public DownloadMsgInfo(String ip, int port, byte[] infoHash, String crc64) { 28 | this.ip = ip; 29 | this.port = port; 30 | this.infoHash = infoHash; 31 | timestamp = SystemClock.now(); 32 | this.crc64 = crc64; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /words/src/main/java/toolgood/words/IllegalWordsSearchResult.java: -------------------------------------------------------------------------------- 1 | package toolgood.words; 2 | 3 | 4 | public class IllegalWordsSearchResult 5 | { 6 | public IllegalWordsSearchResult(final String keyword, final int start, final int end, final int index, 7 | final String matchKeyword, final int type) 8 | { 9 | MatchKeyword = matchKeyword; 10 | End = end; 11 | Start = start; 12 | Index = index; 13 | Keyword = keyword; 14 | BlacklistType = type; 15 | } 16 | 17 | /**开始位置 */ 18 | public int Start; 19 | /**结束位置 */ 20 | public int End ; 21 | /**原始文本 */ 22 | public String Keyword ; 23 | /**关键字 */ 24 | public String MatchKeyword; 25 | /**黑名单类型 */ 26 | public int BlacklistType ; 27 | /**索引 */ 28 | public int Index; 29 | 30 | 31 | } -------------------------------------------------------------------------------- /dodder-web/src/main/resources/templates/common/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

菟丝子资源社区

8 |
9 | 14 |
15 |
16 | -------------------------------------------------------------------------------- /dodder-web/src/main/java/cc/dodder/web/dialect/GlobalUtilDialect.java: -------------------------------------------------------------------------------- 1 | package cc.dodder.web.dialect; 2 | 3 | import org.springframework.stereotype.Component; 4 | import org.thymeleaf.dialect.AbstractDialect; 5 | import org.thymeleaf.dialect.IExpressionObjectDialect; 6 | import org.thymeleaf.expression.IExpressionObjectFactory; 7 | 8 | /*** 9 | * 自定义 Thymeleaf 工具 Dialect 10 | * 11 | * @author Mr.Xu 12 | * @date 2019-03-02 22:35 13 | **/ 14 | @Component 15 | public class GlobalUtilDialect extends AbstractDialect implements IExpressionObjectDialect { 16 | 17 | private final IExpressionObjectFactory GLOBAL_UTIL_EXPRESSION_OBJECTS_FACTORY = new GlobalUtilExpressionObjectFactory(); 18 | 19 | protected GlobalUtilDialect() { 20 | super("dodderUtil"); 21 | } 22 | 23 | @Override 24 | public IExpressionObjectFactory getExpressionObjectFactory() { 25 | return GLOBAL_UTIL_EXPRESSION_OBJECTS_FACTORY; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /dodder-common/src/main/java/cc/dodder/common/util/JSONUtil.java: -------------------------------------------------------------------------------- 1 | package cc.dodder.common.util; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | 6 | import java.io.IOException; 7 | 8 | public class JSONUtil { 9 | 10 | private static ObjectMapper objectMapper = new ObjectMapper(); 11 | 12 | public static String toJSONString(Object o) { 13 | try { 14 | return objectMapper.writeValueAsString(o); 15 | } catch (JsonProcessingException e) { 16 | e.printStackTrace(); 17 | } 18 | return ""; 19 | } 20 | 21 | public static T parseObject(String json, Class clazz) { 22 | try { 23 | return objectMapper.readValue(json, clazz); 24 | } catch (IOException e) { 25 | e.printStackTrace(); 26 | } 27 | return null; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /dodder-dht-server/src/main/java/cc/dodder/dhtserver/netty/config/DHTChannelInitializer.java: -------------------------------------------------------------------------------- 1 | package cc.dodder.dhtserver.netty.config; 2 | 3 | import cc.dodder.dhtserver.netty.handler.DHTServerHandler; 4 | import io.netty.channel.ChannelInitializer; 5 | import io.netty.channel.ChannelPipeline; 6 | import io.netty.channel.socket.DatagramChannel; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.beans.factory.annotation.Qualifier; 9 | import org.springframework.stereotype.Component; 10 | 11 | @Component 12 | @Qualifier("channelInitializer") 13 | public class DHTChannelInitializer extends ChannelInitializer { 14 | 15 | @Autowired 16 | private DHTServerHandler dhtServerHandler; 17 | 18 | @Override 19 | protected void initChannel(DatagramChannel ch) throws Exception { 20 | ChannelPipeline pipeline = ch.pipeline(); 21 | pipeline.addLast("handler", dhtServerHandler); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /dodder-api/src/main/java/cc/dodder/api/TorrentApi.java: -------------------------------------------------------------------------------- 1 | package cc.dodder.api; 2 | 3 | import cc.dodder.common.entity.Result; 4 | import cc.dodder.common.request.SearchRequest; 5 | import cc.dodder.common.vo.TorrentPageVO; 6 | import cc.dodder.common.vo.TorrentVO; 7 | 8 | public interface TorrentApi { 9 | 10 | /** 11 | * 根据 info_hash 判断数据库是否已经存在 12 | * 13 | * @param infoHash 14 | * @return org.springframework.http.ResponseEntity 15 | */ 16 | Result existHash(String infoHash); 17 | 18 | /** 19 | * 根据条件搜索 Torrents 20 | * 21 | * @param request 22 | * @return org.springframework.data.domain.Page 23 | */ 24 | Result torrents(SearchRequest request); 25 | 26 | /** 27 | * 根据 infoHash 查找 Torrent 28 | * 29 | * @param infoHash 30 | * @return cc.dodder.common.entity.Torrent 31 | */ 32 | Result findById(String infoHash); 33 | } 34 | -------------------------------------------------------------------------------- /words/src/main/java/toolgood/words/WordsSearchResult.java: -------------------------------------------------------------------------------- 1 | package toolgood.words; 2 | 3 | public class WordsSearchResult { 4 | 5 | public WordsSearchResult(final String keyword, final int start, final int end, final int index) { 6 | Keyword = keyword; 7 | End = end; 8 | Start = start; 9 | Index = index; 10 | MatchKeyword = keyword; 11 | } 12 | 13 | public WordsSearchResult(final String keyword, final int start, final int end, final int index, 14 | final String matchKeyword) { 15 | Keyword = keyword; 16 | End = end; 17 | Start = start; 18 | Index = index; 19 | MatchKeyword = matchKeyword; 20 | } 21 | 22 | /** 开始位置 */ 23 | public int Start; 24 | /** 结束位置 */ 25 | public int End; 26 | /** 关键字 */ 27 | public String Keyword; 28 | /** 索引 */ 29 | public int Index; 30 | /** 匹配关键字 */ 31 | public String MatchKeyword; 32 | 33 | } -------------------------------------------------------------------------------- /dodder-dht-server/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8766 3 | ## netty服务器端口 4 | netty: 5 | udp: 6 | port: 6688 7 | so: 8 | rcvbuf: 268435456 9 | sndbuf: 65536 10 | 11 | spring: 12 | redis: 13 | host: 127.0.0.1 14 | port: 6379 15 | jedis: 16 | pool: 17 | max-idle: 8 18 | min-idle: 0 19 | max-active: 8 20 | max-wait: -1ms 21 | cloud: 22 | stream: 23 | kafka: 24 | binder: 25 | brokers: 127.0.0.1 26 | auto-add-partitions: true 27 | min-partition-count: 1 28 | configuration: 29 | max.request.size: 12048576 30 | bindings: 31 | download-out: 32 | destination: download-topic 33 | producer: 34 | partitionCount: 1 # 大于等于 download service 集群数量 35 | useNativeEncoding: true 36 | compressionType: gzip 37 | logging: 38 | level: 39 | root: info -------------------------------------------------------------------------------- /dodder-dht-server/src/main/java/cc/dodder/dhtserver/netty/entity/UniqueBlockingQueue.java: -------------------------------------------------------------------------------- 1 | package cc.dodder.dhtserver.netty.entity; 2 | 3 | import java.util.HashSet; 4 | import java.util.Set; 5 | import java.util.concurrent.BlockingQueue; 6 | import java.util.concurrent.LinkedBlockingQueue; 7 | 8 | /*** 9 | * 去重的节点阻塞队列 10 | * 11 | * @author Mr.Xu 12 | * @date 2019-02-17 18:44 13 | **/ 14 | public class UniqueBlockingQueue { 15 | 16 | private Set ips = new HashSet<>(); 17 | private BlockingQueue nodes = new LinkedBlockingQueue<>(); 18 | 19 | public int size() { 20 | return ips.size(); 21 | } 22 | 23 | public boolean isEmpty() { 24 | return nodes.isEmpty(); 25 | } 26 | 27 | public boolean offer(Node node) { 28 | if (ips.add(node.getAddr().getHostString())) 29 | return nodes.offer(node); 30 | return false; 31 | } 32 | 33 | public Node poll() { 34 | Node node = nodes.poll(); 35 | if (node != null) 36 | ips.remove(node.getAddr().getHostString()); 37 | return node; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /dodder-dht-server/src/main/java/cc/dodder/dhtserver/netty/schedule/AutoJoinDHT.java: -------------------------------------------------------------------------------- 1 | package cc.dodder.dhtserver.netty.schedule; 2 | 3 | import cc.dodder.dhtserver.netty.DHTServer; 4 | import cc.dodder.dhtserver.netty.handler.DHTServerHandler; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.scheduling.annotation.Scheduled; 8 | import org.springframework.stereotype.Component; 9 | 10 | /*** 11 | * 定时检测本地节点数并自动加入 DHT 网络 12 | * 13 | * @author Mr.Xu 14 | * @date 2019-02-16 22:04 15 | **/ 16 | @Slf4j 17 | @Component 18 | public class AutoJoinDHT { 19 | 20 | @Autowired 21 | private DHTServer dhtServer; 22 | @Autowired 23 | private DHTServerHandler handler; 24 | 25 | @Scheduled(fixedDelay = 60 * 1000, initialDelay = 10 * 1000) 26 | public void doJob() { 27 | if (handler.NODES_QUEUE.isEmpty()) { 28 | log.info("本地 DHT 节点数为0,自动重新加入 DHT 网络中..."); 29 | handler.joinDHT(); 30 | } 31 | //dhtServer.saveBloomFilter(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /dodder-torrent-store-service/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8764 3 | spring: 4 | application: 5 | name: torrent-store-service 6 | cloud: 7 | stream: 8 | kafka: 9 | binder: 10 | brokers: 127.0.0.1 11 | bindings: 12 | store-in-0: 13 | group: store-group 14 | destination: store-topic 15 | contentType: application/json 16 | consumer: 17 | batch-mode: true 18 | max-poll-records: 500 19 | compressionType: gzip 20 | autoCommitOffset: false #设置手动提交偏移 21 | elasticsearch: 22 | rest: 23 | uris: 127.0.0.1:9200 24 | # main: 25 | # allow-bean-definition-overriding: true 26 | dubbo: 27 | protocol: 28 | name: dubbo 29 | port: 23456 30 | registry: 31 | address: N/A 32 | scan: 33 | base-packages: cc.dodder.torrent.store.api.impl 34 | store: 35 | service: 36 | version: 1.0.0 37 | dodder: 38 | filter-sensitive-torrent: true #开启过滤敏感资源 39 | logging: 40 | level: 41 | root: info -------------------------------------------------------------------------------- /words/src/main/resources/t2hk.dat: -------------------------------------------------------------------------------- 1 | 僞 偽 2 | 兌 兑 3 | 冑 胄 4 | 冗 宂 5 | 勳 勛 6 | 叄 叁 7 | 告 吿 8 | 啓 啟 9 | 喫 吃 10 | 嘆 歎 11 | 囪 囱 12 | 妝 粧 13 | 媼 媪 14 | 嫋 裊 15 | 嫺 嫻 16 | 嬀 媯 17 | 岩 巖 18 | 悅 悦 19 | 慍 愠 20 | 戶 户 21 | 挩 捝 22 | 搵 揾 23 | 擡 抬 24 | 敓 敚 25 | 敘 敍 26 | 柺 枴 27 | 梲 棁 28 | 棱 稜 29 | 榲 榅 30 | 檯 枱 31 | 氳 氲 32 | 涗 涚 33 | 溫 温 34 | 溼 濕 35 | 潙 溈 36 | 潨 潀 37 | 熅 煴 38 | 爲 為 39 | 痹 痺 40 | 癡 痴 41 | 皁 皂 42 | 稅 税 43 | 竈 灶 44 | 糉 粽 45 | 縕 緼 46 | 繮 韁 47 | 纔 才 48 | 脫 脱 49 | 膃 腽 50 | 臥 卧 51 | 臺 台 52 | 菸 煙 53 | 蒕 蒀 54 | 蔥 葱 55 | 蔿 蒍 56 | 蘊 藴 57 | 蛻 蜕 58 | 衆 眾 59 | 衕 同 60 | 衚 胡 61 | 衛 衞 62 | 覈 核 63 | 說 説 64 | 贗 贋 65 | 踊 踴 66 | 蹟 跡 67 | 轀 輼 68 | 醞 醖 69 | 鉢 缽 70 | 鉤 鈎 71 | 銳 鋭 72 | 閱 閲 73 | 鰮 鰛 74 | 鱉 鼈 75 | 貝胄 貝冑 76 | 大欖涌 大欖涌 77 | 大涌 大涌 78 | 東涌 東涌 79 | 冠胄 冠冑 80 | 韓侂胄 韓侂冑 81 | 蠔涌 蠔涌 82 | 河涌 河涌 83 | 甲胄 甲冑 84 | 介胄 介冑 85 | 鎧胄 鎧冑 86 | 葵涌 葵涌 87 | 黎涌 黎涌 88 | 麻涌 麻涌 89 | 免胄 免冑 90 | 南涌 南涌 91 | 泥涌 泥涌 92 | 沙魚涌 沙魚涌 93 | 深涌 深涌 94 | 西涌 西涌 95 | 溪涌 溪涌 96 | 懸胄 懸冑 97 | 壓胄子 壓冑子 98 | 飮胄 飮冑 99 | 涌尾 涌尾 100 | 魚胄 魚冑 101 | 鰂魚涌 鰂魚涌 102 | 胄甲 冑甲 103 | 胄科 冑科 -------------------------------------------------------------------------------- /dodder-torrent-download-service/src/main/java/cc/dodder/torrent/download/task/DownloadTask.java: -------------------------------------------------------------------------------- 1 | package cc.dodder.torrent.download.task; 2 | 3 | import cc.dodder.common.entity.DownloadMsgInfo; 4 | import cc.dodder.torrent.download.client.PeerWireClient; 5 | 6 | import java.net.InetSocketAddress; 7 | 8 | /*** 9 | * Torrent 下载线程 10 | * 11 | * @author Mr.Xu 12 | * @date 2019-02-22 10:43 13 | **/ 14 | public class DownloadTask implements Runnable { 15 | 16 | private DownloadMsgInfo msgInfo; 17 | private static ThreadLocal wireClient = new ThreadLocal<>(); 18 | 19 | public DownloadTask(DownloadMsgInfo msgInfo) { 20 | this.msgInfo = msgInfo; 21 | } 22 | 23 | @Override 24 | public void run() { 25 | try { 26 | if (wireClient.get() == null) { 27 | wireClient.set(new PeerWireClient()); 28 | } 29 | wireClient.get().downloadMetadata(new InetSocketAddress(msgInfo.getIp(), msgInfo.getPort()), msgInfo.getInfoHash(), msgInfo.getCrc64()); 30 | } catch (Exception e) { 31 | e.printStackTrace(); 32 | } finally { 33 | msgInfo = null; 34 | } 35 | } 36 | 37 | 38 | } -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/css/modules/code.css: -------------------------------------------------------------------------------- 1 | /** layui-v2.4.5 MIT License By https://www.layui.com */ 2 | html #layuicss-skincodecss{display:none;position:absolute;width:1989px}.layui-code-h3,.layui-code-view{position:relative;font-size:12px}.layui-code-view{display:block;margin:10px 0;padding:0;border:1px solid #e2e2e2;border-left-width:6px;background-color:#F2F2F2;color:#333;font-family:Courier New}.layui-code-h3{padding:0 10px;height:32px;line-height:32px;border-bottom:1px solid #e2e2e2}.layui-code-h3 a{position:absolute;right:10px;top:0;color:#999}.layui-code-view .layui-code-ol{position:relative;overflow:auto}.layui-code-view .layui-code-ol li{position:relative;margin-left:45px;line-height:20px;padding:0 5px;border-left:1px solid #e2e2e2;list-style-type:decimal-leading-zero;*list-style-type:decimal;background-color:#fff}.layui-code-view pre{margin:0}.layui-code-notepad{border:1px solid #0C0C0C;border-left-color:#3F3F3F;background-color:#0C0C0C;color:#C2BE9E}.layui-code-notepad .layui-code-h3{border-bottom:none}.layui-code-notepad .layui-code-ol li{background-color:#3F3F3F;border-left:none} -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 xwlcn 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /dodder-torrent-download-service/src/main/java/cc/dodder/torrent/download/client/Constants.java: -------------------------------------------------------------------------------- 1 | package cc.dodder.torrent.download.client; 2 | 3 | import cc.dodder.common.util.NodeIdUtil; 4 | 5 | public class Constants { 6 | 7 | public static final String BT_PROTOCOL = "BitTorrent protocol"; 8 | public static final byte[] BT_RESERVED = new byte[] { 9 | (byte) (0x00 & 0xff), (byte) (0x00 & 0xff), (byte) (0x00 & 0xff), (byte) (0x00 & 0xff), 10 | (byte) (0x00 & 0xff), (byte) (0x10 & 0xff), (byte) (0x00 & 0xff), (byte) (0x01 & 0xff), 11 | }; 12 | 13 | public static final byte[] EXT_HANDSHAKE_DATA = "d1:md11:ut_metadatai1eee".getBytes(); 14 | public static String EXT_REQUEST_PIECE_MSG = "d8:msg_typei0e5:piecei#ee"; 15 | 16 | public static final byte BT_MSG_ID = 20 & 0xff; 17 | public static final int EXT_HANDSHAKE_ID = 0; 18 | public static final int CONNECT_TIMEOUT = 1500; 19 | public static final int READ_WRITE_TIMEOUT = 5000; 20 | public static final int MAX_METADATA_SIZE = 1024 * 1024 * 10; //最大 10M 21 | 22 | public static final byte[] PEER_ID = NodeIdUtil.createRandomNodeId(); 23 | 24 | public static final long MAX_LOSS_TIME = 30 * 60 * 1000; 25 | 26 | } 27 | -------------------------------------------------------------------------------- /dodder-common/src/main/java/cc/dodder/common/entity/Result.java: -------------------------------------------------------------------------------- 1 | package cc.dodder.common.entity; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | import org.apache.http.HttpStatus; 6 | 7 | import java.io.Serializable; 8 | 9 | @Getter @Setter 10 | public class Result implements Serializable { 11 | 12 | private int status; 13 | private String msg; 14 | private T data; 15 | 16 | public Result(int status) { 17 | this.status = status; 18 | } 19 | 20 | private Result(int status, String msg, T data) { 21 | this.status = status; 22 | this.msg = msg; 23 | this.data = data; 24 | } 25 | 26 | public static Result ok() { 27 | return ok(null); 28 | } 29 | 30 | public static Result ok(String msg) { 31 | return ok(msg, null); 32 | } 33 | 34 | public static Result ok(T data) { 35 | return ok(null, data); 36 | } 37 | 38 | public static Result ok(String msg, T data) { 39 | return new Result<>(HttpStatus.SC_OK, msg, data); 40 | } 41 | 42 | public static Result noContent() { 43 | return new Result(HttpStatus.SC_NO_CONTENT); 44 | } 45 | 46 | public static Result notFount() { 47 | return new Result(HttpStatus.SC_NOT_FOUND); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /dodder-torrent-store-service/src/main/java/cc/dodder/torrent/store/config/MongoConfig.java: -------------------------------------------------------------------------------- 1 | package cc.dodder.torrent.store.config; 2 | 3 | import cc.dodder.common.entity.Torrent; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.context.annotation.ImportResource; 7 | import org.springframework.data.domain.Sort; 8 | import org.springframework.data.mongodb.MongoDatabaseFactory; 9 | import org.springframework.data.mongodb.core.MongoTemplate; 10 | import org.springframework.data.mongodb.core.index.Index; 11 | 12 | @Configuration 13 | @ImportResource("classpath:mongodb.xml") 14 | public class MongoConfig { 15 | 16 | @Bean 17 | public MongoTemplate mongoTemplate(MongoDatabaseFactory mongoDatabaseFactory) { 18 | MongoTemplate mongoTemplate = new MongoTemplate(mongoDatabaseFactory); 19 | if (!mongoTemplate.collectionExists(Torrent.class)) { 20 | mongoTemplate.createCollection(Torrent.class); 21 | mongoTemplate.indexOps(Torrent.class).ensureIndex(new Index().on("isXxx", Sort.Direction.ASC)); 22 | } 23 | return mongoTemplate; 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/lay/modules/code.js: -------------------------------------------------------------------------------- 1 | /** layui-v2.4.5 MIT License By https://www.layui.com */ 2 | ;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"); -------------------------------------------------------------------------------- /dodder-torrent-download-service/src/main/java/cc/dodder/torrent/download/util/SpringContextUtil.java: -------------------------------------------------------------------------------- 1 | package cc.dodder.torrent.download.util; 2 | 3 | import org.springframework.beans.BeansException; 4 | import org.springframework.context.ApplicationContext; 5 | import org.springframework.context.ApplicationContextAware; 6 | import org.springframework.stereotype.Component; 7 | 8 | /*** 9 | * Spring 容器上下文工具类,用于在线程中获取 Bean 10 | * 11 | * @author Mr.Xu 12 | * @date 2019-02-22 10:47 13 | **/ 14 | @Component 15 | public class SpringContextUtil implements ApplicationContextAware { 16 | 17 | private static ApplicationContext applicationContext; 18 | 19 | @Override 20 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 21 | SpringContextUtil.applicationContext = applicationContext; 22 | } 23 | 24 | /** 25 | * 根据 Bean name 获取 Bean 26 | * 27 | * @param name 28 | * @return java.lang.Object 29 | */ 30 | public static Object getBean(String name) { 31 | return applicationContext.getBean(name); 32 | } 33 | 34 | /** 35 | * 根据 Class 获取 Bean 36 | * 37 | * @param clazz 38 | * @return java.lang.Object 39 | */ 40 | public static Object getBean(Class clazz) { 41 | return applicationContext.getBean(clazz); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /dodder-torrent-download-service/src/main/java/cc/dodder/torrent/download/task/BlockingExecutor.java: -------------------------------------------------------------------------------- 1 | package cc.dodder.torrent.download.task; 2 | 3 | import java.util.concurrent.ExecutorService; 4 | import java.util.concurrent.RejectedExecutionException; 5 | import java.util.concurrent.Semaphore; 6 | 7 | /*** 8 | * Bloking executor 9 | * 10 | * @author Mr.Xu 11 | * @simce 2019-10-18 17:36 12 | **/ 13 | public class BlockingExecutor { 14 | 15 | private final ExecutorService exec; 16 | private final Semaphore semaphore; 17 | 18 | public BlockingExecutor(ExecutorService exec, int bound) { 19 | this.exec = exec; 20 | this.semaphore = new Semaphore(bound); 21 | } 22 | 23 | public void execute(final Runnable command) 24 | throws InterruptedException, RejectedExecutionException { 25 | semaphore.acquire(); 26 | try { 27 | exec.execute(() -> { 28 | try { 29 | command.run(); 30 | } finally { 31 | semaphore.release(); 32 | } 33 | }); 34 | } catch (RejectedExecutionException e) { 35 | semaphore.release(); 36 | throw e; 37 | } 38 | } 39 | 40 | public void shutdownNow() { 41 | exec.shutdownNow(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /dodder-web/src/main/java/cc/dodder/web/dialect/GlobalUtilExpressionObjectFactory.java: -------------------------------------------------------------------------------- 1 | package cc.dodder.web.dialect; 2 | 3 | import cc.dodder.common.util.StringUtil; 4 | import org.thymeleaf.context.IExpressionContext; 5 | import org.thymeleaf.expression.IExpressionObjectFactory; 6 | 7 | import java.util.Arrays; 8 | import java.util.Collections; 9 | import java.util.HashSet; 10 | import java.util.Set; 11 | 12 | public class GlobalUtilExpressionObjectFactory implements IExpressionObjectFactory { 13 | 14 | private static final String GLOBAL_UTIL__EVALUATION_VARIABLE_NAME = "dodderUtil"; 15 | 16 | private static final Set ALL_EXPRESSION_OBJECT_NAMES = Collections.unmodifiableSet( 17 | new HashSet<>(Arrays.asList(GLOBAL_UTIL__EVALUATION_VARIABLE_NAME))); 18 | 19 | 20 | @Override 21 | public Set getAllExpressionObjectNames() { 22 | return ALL_EXPRESSION_OBJECT_NAMES; 23 | } 24 | 25 | @Override 26 | public Object buildObject(IExpressionContext context, String expressionObjectName) { 27 | if (expressionObjectName.equals(GLOBAL_UTIL__EVALUATION_VARIABLE_NAME)) { 28 | return new StringUtil(); 29 | } 30 | return null; 31 | } 32 | 33 | @Override 34 | public boolean isCacheable(String expressionObjectName) { 35 | return GLOBAL_UTIL__EVALUATION_VARIABLE_NAME != null && GLOBAL_UTIL__EVALUATION_VARIABLE_NAME.equals(expressionObjectName); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /words/src/main/java/toolgood/words/internals/TrieNode2.java: -------------------------------------------------------------------------------- 1 | package toolgood.words.internals; 2 | 3 | import java.util.List; 4 | import java.util.ArrayList; 5 | import java.util.HashMap; 6 | 7 | 8 | public class TrieNode2{ 9 | public boolean End; 10 | public List Results; 11 | public HashMap m_values; 12 | private int minflag = Integer.MAX_VALUE; 13 | private int maxflag = 0; 14 | 15 | public TrieNode2() 16 | { 17 | Results = new ArrayList(); 18 | m_values = new HashMap(); 19 | } 20 | 21 | public void Add(final char c, final TrieNode2 node3) { 22 | if (minflag > c) { 23 | minflag = c; 24 | } 25 | if (maxflag < c) { 26 | maxflag = c; 27 | } 28 | m_values.put(c, node3); 29 | } 30 | 31 | public void SetResults(final Integer index) { 32 | if (End == false) { 33 | End = true; 34 | } 35 | if (Results.contains(index) == false) { 36 | Results.add(index); 37 | } 38 | } 39 | 40 | public boolean HasKey(final char c) { 41 | if (minflag <= c && maxflag >= c) { 42 | return m_values.containsKey(c); 43 | } 44 | return false; 45 | } 46 | 47 | public TrieNode2 GetValue(final char c) { 48 | return m_values.get(c); 49 | } 50 | } -------------------------------------------------------------------------------- /dodder-common/src/main/java/cc/dodder/common/util/LanguageUtil.java: -------------------------------------------------------------------------------- 1 | package cc.dodder.common.util; 2 | 3 | import com.google.common.base.Optional; 4 | import com.optimaize.langdetect.LanguageDetector; 5 | import com.optimaize.langdetect.LanguageDetectorBuilder; 6 | import com.optimaize.langdetect.i18n.LdLocale; 7 | import com.optimaize.langdetect.ngram.NgramExtractors; 8 | import com.optimaize.langdetect.profiles.LanguageProfile; 9 | import com.optimaize.langdetect.profiles.LanguageProfileReader; 10 | 11 | import java.io.IOException; 12 | import java.util.Arrays; 13 | import java.util.List; 14 | 15 | public class LanguageUtil { 16 | 17 | private static List languageProfiles; 18 | private static LanguageDetector detector; 19 | 20 | static { 21 | try { 22 | languageProfiles = new LanguageProfileReader().readBuiltIn(Arrays.asList(LdLocale.fromString("ru"), LdLocale.fromString("en"))); 23 | detector = LanguageDetectorBuilder.create(NgramExtractors.standard()).withProfiles(languageProfiles).build(); 24 | } catch (IOException e) { 25 | e.printStackTrace(); 26 | } 27 | } 28 | 29 | public static String getLanguage(String text) { 30 | Optional locale = detector.detect(text); 31 | if (locale.isPresent()) 32 | return locale.get().getLanguage(); 33 | return null; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /words/src/main/java/toolgood/words/internals/TrieNode2Ex.java: -------------------------------------------------------------------------------- 1 | package toolgood.words.internals; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | 7 | public class TrieNode2Ex { 8 | public int Index; 9 | public boolean End; 10 | public List Results; 11 | public HashMap m_values; 12 | public int minflag = Integer.MAX_VALUE; 13 | public int maxflag = 0; 14 | 15 | public TrieNode2Ex() 16 | { 17 | Results = new ArrayList(); 18 | m_values = new HashMap(); 19 | } 20 | 21 | public void Add(final int c, final TrieNode2Ex node3) { 22 | if (minflag > c) { 23 | minflag = c; 24 | } 25 | if (maxflag < c) { 26 | maxflag = c; 27 | } 28 | m_values.put(c, node3); 29 | } 30 | 31 | public void SetResults(final int index) { 32 | if (End == false) { 33 | End = true; 34 | } 35 | if (Results.contains(index) == false) { 36 | Results.add(index); 37 | } 38 | } 39 | 40 | public boolean HasKey(final int c) { 41 | if (minflag <= c && maxflag >= c) { 42 | return m_values.containsKey(c); 43 | } 44 | return false; 45 | } 46 | 47 | public TrieNode2Ex GetValue(final int c) { 48 | return m_values.get(c); 49 | } 50 | 51 | } -------------------------------------------------------------------------------- /dodder-torrent-store-service/src/main/java/cc/dodder/torrent/store/config/ElasticsearchConfiguration.java: -------------------------------------------------------------------------------- 1 | package cc.dodder.torrent.store.config; 2 | 3 | import cc.dodder.common.entity.Torrent; 4 | import org.elasticsearch.client.RestHighLevelClient; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate; 8 | import org.springframework.data.elasticsearch.core.IndexOperations; 9 | import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter; 10 | 11 | @Configuration 12 | public class ElasticsearchConfiguration { 13 | 14 | /** 15 | * 由于并未使用 ElasticsearchRepository, 并且使用 Client 的方式插入更新索引 16 | * 导致 Entity 上的 @Mapping 和 @Setting 不会生效,所以自己手动创建索引 17 | * 18 | * @param client 19 | * @param converter 20 | * @return 21 | */ 22 | @Bean 23 | public ElasticsearchRestTemplate elasticsearchRestTemplate(RestHighLevelClient client, ElasticsearchConverter converter) { 24 | ElasticsearchRestTemplate elasticsearchRestTemplate = new ElasticsearchRestTemplate(client, converter); 25 | IndexOperations ops = elasticsearchRestTemplate.indexOps(Torrent.class); 26 | if (!ops.exists()) { 27 | ops.create(); 28 | ops.putMapping(Torrent.class); 29 | } 30 | return elasticsearchRestTemplate; 31 | } 32 | } -------------------------------------------------------------------------------- /dodder-torrent-download-service/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8762 3 | spring: 4 | application: 5 | name: torrent-download-service 6 | redis: 7 | host: 127.0.0.1 8 | port: 6379 9 | jedis: 10 | pool: 11 | max-idle: 1000 12 | min-idle: 0 13 | max-active: 1000 14 | max-wait: -1ms 15 | cloud: 16 | stream: 17 | kafka: 18 | binder: 19 | brokers: 127.0.0.1 20 | auto-add-partitions: true 21 | min-partition-count: 1 22 | configuration: 23 | max.request.size: 12048576 24 | bindings: 25 | download-in-0: 26 | group: download-group 27 | destination: download-topic 28 | contentType: application/json 29 | consumer: 30 | max-poll-records: 3000 31 | batch-mode: true 32 | compressionType: gzip 33 | download-out-0: 34 | destination: store-topic 35 | contentType: application/json 36 | producer: 37 | partitionCount: 1 # 大于等于 store service 集群数量 38 | useNativeEncoding: true 39 | compressionType: gzip 40 | 41 | management: 42 | endpoints: 43 | web: 44 | exposure: 45 | include: ["*"] 46 | 47 | download: 48 | num: 49 | thread: 3000 50 | enable-filter-sensitive-words: true #是否开启过滤敏感词 51 | 52 | logging: 53 | level: 54 | root: info -------------------------------------------------------------------------------- /words/src/main/java/toolgood/words/internals/TrieNode3.java: -------------------------------------------------------------------------------- 1 | package toolgood.words.internals; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | 7 | public class TrieNode3 { 8 | public boolean End; 9 | public boolean HasWildcard; 10 | public List Results; 11 | public HashMap m_values; 12 | private int minflag = Integer.MAX_VALUE; 13 | private int maxflag = 0; 14 | public TrieNode3 WildcardNode; 15 | 16 | 17 | public TrieNode3() 18 | { 19 | Results = new ArrayList(); 20 | m_values = new HashMap(); 21 | } 22 | 23 | public void Add(final char c, final TrieNode3 node3) { 24 | if (minflag > c) { 25 | minflag = c; 26 | } 27 | if (maxflag < c) { 28 | maxflag = c; 29 | } 30 | m_values.put(c, node3); 31 | } 32 | 33 | public void SetResults(final int index) { 34 | if (End == false) { 35 | End = true; 36 | } 37 | if (Results.contains(index) == false) { 38 | Results.add(index); 39 | } 40 | } 41 | 42 | public boolean HasKey(final char c) { 43 | if (minflag <= c && maxflag >= c) { 44 | return m_values.containsKey(c); 45 | } 46 | return false; 47 | } 48 | 49 | public TrieNode3 GetValue(final char c) { 50 | return m_values.get(c); 51 | } 52 | } -------------------------------------------------------------------------------- /words/src/main/java/toolgood/words/internals/TrieNode.java: -------------------------------------------------------------------------------- 1 | package toolgood.words.internals; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | 7 | public class TrieNode implements Comparable { 8 | 9 | public int Index; 10 | public int Layer; 11 | public boolean End; 12 | public char Char; 13 | public List Results; 14 | public HashMap m_values; 15 | public TrieNode Failure; 16 | public TrieNode Parent; 17 | public boolean IsWildcard; 18 | public int WildcardLayer; 19 | public boolean HasWildcard; 20 | 21 | 22 | public TrieNode() { 23 | m_values = new HashMap(); 24 | Results = new ArrayList(); 25 | } 26 | 27 | public TrieNode Add(final Character c) { 28 | if (m_values.containsKey(c)) { 29 | return m_values.get(c); 30 | } 31 | final TrieNode node = new TrieNode(); 32 | node.Parent = this; 33 | node.Char = c; 34 | m_values.put(c, node); 35 | return node; 36 | } 37 | 38 | public void SetResults(final Integer index) { 39 | if (End == false) { 40 | End = true; 41 | } 42 | if (Results.contains(index) == false) { 43 | Results.add(index); 44 | } 45 | } 46 | 47 | @Override 48 | public int compareTo(final TrieNode o) { 49 | return this.Layer - o.Layer ; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /dodder-torrent-store-service/src/main/java/cc/dodder/torrent/store/repository/TorrentDao.java: -------------------------------------------------------------------------------- 1 | package cc.dodder.torrent.store.repository; 2 | 3 | import cc.dodder.common.entity.Torrent; 4 | import cc.dodder.common.request.SearchRequest; 5 | import org.springframework.data.domain.Page; 6 | import org.springframework.data.domain.Pageable; 7 | import java.util.List; 8 | import java.util.Optional; 9 | 10 | /*** 11 | * 自定义扩展 Torrent Dao 12 | * 13 | * @author Mr.Xu 14 | * @date 2019-02-25 11:07 15 | **/ 16 | public interface TorrentDao { 17 | 18 | /** 19 | * Elasticsearch 创建索引 20 | * 21 | * @param torrents 22 | * @return void 23 | */ 24 | void index(List torrents); 25 | 26 | /** 27 | * 存在则更新,不存在则插入 28 | * 29 | * @param torrents 30 | * @return void 31 | */ 32 | void upsert(List torrents); 33 | 34 | /** 35 | * 根据 id 查找 36 | * @param id 37 | * @return 38 | */ 39 | Optional findById(String id); 40 | 41 | /** 42 | * 分页搜索 43 | * 44 | * @param request, pageable 45 | * @return org.springframework.data.domain.Page 46 | */ 47 | Page query(SearchRequest request, Pageable pageable); 48 | 49 | /** 50 | * 相关推荐搜索 51 | * 52 | * @param torrent 53 | * @param pageable 54 | * @return org.springframework.data.domain.Page 55 | */ 56 | Page searchSimilar(Torrent torrent, Pageable pageable); 57 | 58 | boolean existsById(String infoHash); 59 | 60 | Long countAll(); 61 | } 62 | -------------------------------------------------------------------------------- /words/src/main/java/toolgood/words/internals/TrieNode3Ex.java: -------------------------------------------------------------------------------- 1 | package toolgood.words.internals; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | 7 | public class TrieNode3Ex { 8 | public int Index; 9 | public boolean End; 10 | public boolean HasWildcard; 11 | public List Results; 12 | public HashMap m_values; 13 | public int minflag = Integer.MAX_VALUE; 14 | public int maxflag = 0; 15 | public TrieNode3Ex WildcardNode; 16 | 17 | 18 | public TrieNode3Ex() 19 | { 20 | Results = new ArrayList(); 21 | m_values = new HashMap(); 22 | } 23 | 24 | public void Add(final char c, final TrieNode3Ex node3) { 25 | if (minflag > c) { 26 | minflag = c; 27 | } 28 | if (maxflag < c) { 29 | maxflag = c; 30 | } 31 | m_values.put(c, node3); 32 | } 33 | 34 | public void SetResults(final int index) { 35 | if (End == false) { 36 | End = true; 37 | } 38 | if (Results.contains(index) == false) { 39 | Results.add(index); 40 | } 41 | } 42 | 43 | public boolean HasKey(final char c) { 44 | if (minflag <= c && maxflag >= c) { 45 | return m_values.containsKey(c); 46 | } 47 | return false; 48 | } 49 | 50 | public TrieNode3Ex GetValue(final char c) { 51 | return m_values.get(c); 52 | } 53 | } -------------------------------------------------------------------------------- /dodder-torrent-download-service/src/main/java/cc/dodder/torrent/download/client/PipedStream.java: -------------------------------------------------------------------------------- 1 | package cc.dodder.torrent.download.client; 2 | 3 | import java.io.IOException; 4 | import java.io.PipedInputStream; 5 | import java.io.PipedOutputStream; 6 | 7 | /*** 8 | * Piped Stream 封装类 9 | * 10 | * @author Mr.Xu 11 | * @date 2019-03-01 19:08 12 | **/ 13 | public class PipedStream { 14 | 15 | private PipedOutputStream writeStream; 16 | private PipedInputStream readStream; 17 | 18 | public PipedStream() throws IOException { 19 | writeStream = new PipedOutputStream(); 20 | readStream = new PipedInputStream(22 * 1024); 21 | readStream.connect(writeStream); 22 | } 23 | 24 | public int available() throws IOException { 25 | return readStream.available(); 26 | } 27 | 28 | public void read(byte[] b, int off, int len) throws IOException { 29 | readStream.read(b, off, len); 30 | } 31 | 32 | public void write(byte[] b, int off, int len) throws IOException { 33 | writeStream.write(b, off, len); 34 | writeStream.flush(); 35 | } 36 | 37 | public void clear() throws IOException { 38 | writeStream.flush(); 39 | readStream.skip(readStream.available()); 40 | } 41 | 42 | public void close() { 43 | if (writeStream != null) { 44 | try { 45 | writeStream.close(); 46 | } catch (IOException e) { 47 | e.printStackTrace(); 48 | } 49 | } 50 | if (readStream != null) { 51 | try { 52 | readStream.close(); 53 | } catch (IOException e) { 54 | e.printStackTrace(); 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /dodder-torrent-store-service/src/main/resources/mongodb.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | 14 | 15 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /dodder-common/src/main/java/cc/dodder/common/util/SystemClock.java: -------------------------------------------------------------------------------- 1 | package cc.dodder.common.util; 2 | 3 | import java.sql.Timestamp; 4 | import java.util.concurrent.*; 5 | import java.util.concurrent.atomic.AtomicLong; 6 | 7 | public class SystemClock { 8 | 9 | private final long period; 10 | private final AtomicLong now; 11 | ExecutorService executor = Executors.newSingleThreadExecutor(); 12 | 13 | private SystemClock(long period) { 14 | this.period = period; 15 | this.now = new AtomicLong(System.currentTimeMillis()); 16 | scheduleClockUpdating(); 17 | } 18 | 19 | private static class InstanceHolder { 20 | public static final SystemClock INSTANCE = new SystemClock(1); 21 | } 22 | 23 | private static SystemClock instance() { 24 | return InstanceHolder.INSTANCE; 25 | } 26 | 27 | private void scheduleClockUpdating() { 28 | ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() { 29 | @Override 30 | public Thread newThread(Runnable runnable) { 31 | Thread thread = new Thread(runnable, "System Clock"); 32 | thread.setDaemon(true); 33 | return thread; 34 | } 35 | }); 36 | scheduler.scheduleAtFixedRate(new Runnable() { 37 | @Override 38 | public void run() { 39 | now.set(System.currentTimeMillis()); 40 | } 41 | }, period, period, TimeUnit.MILLISECONDS); 42 | } 43 | 44 | private long currentTimeMillis() { 45 | return now.get(); 46 | } 47 | 48 | public static long now() { 49 | return instance().currentTimeMillis(); 50 | } 51 | 52 | public static String nowDate() { 53 | return new Timestamp(instance().currentTimeMillis()).toString(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /dodder-torrent-store-service/src/main/resources/elasticsearch_custom_comma_analyzer.json: -------------------------------------------------------------------------------- 1 | { 2 | "index": { 3 | "number_of_shards": "2", 4 | "number_of_replicas": "0", 5 | "refresh_interval": "1m" 6 | }, 7 | "analysis": { 8 | "analyzer": { 9 | "comma": { 10 | "type": "pattern", 11 | "pattern":"," 12 | }, 13 | "rebuilt_russian": { 14 | "tokenizer": "standard", 15 | "filter": [ 16 | "lowercase", 17 | "russian_stop", 18 | "russian_keywords", 19 | "russian_stemmer" 20 | ] 21 | }, 22 | "my_ik_smart": { 23 | "type": "custom", 24 | "tokenizer": "ik_smart", 25 | "filter": [ 26 | "stemmer" 27 | ], 28 | "char_filter": [ 29 | "dot_char_filter" 30 | ] 31 | }, 32 | "my_ik_max_word": { 33 | "type": "custom", 34 | "tokenizer": "ik_max_word", 35 | "filter": [ 36 | "stemmer" 37 | ], 38 | "char_filter": [ 39 | "dot_char_filter" 40 | ] 41 | } 42 | }, 43 | "char_filter": { 44 | "dot_char_filter": { 45 | "type": "pattern_replace", 46 | "pattern": "\\.|-", 47 | "replacement": " " 48 | } 49 | }, 50 | "filter": { 51 | "russian_stop": { 52 | "type": "stop", 53 | "stopwords": "_russian_" 54 | }, 55 | "russian_keywords": { 56 | "type": "keyword_marker", 57 | "keywords": ["пример"] 58 | }, 59 | "russian_stemmer": { 60 | "type": "stemmer", 61 | "language": "russian" 62 | } 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /dodder-torrent-store-service/src/main/java/cc/dodder/torrent/store/service/TorrentService.java: -------------------------------------------------------------------------------- 1 | package cc.dodder.torrent.store.service; 2 | 3 | import cc.dodder.common.entity.Torrent; 4 | import cc.dodder.common.request.SearchRequest; 5 | import cc.dodder.torrent.store.repository.TorrentDao; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.data.domain.Page; 8 | import org.springframework.data.domain.Pageable; 9 | import org.springframework.stereotype.Service; 10 | import org.springframework.transaction.annotation.Transactional; 11 | 12 | import java.util.List; 13 | import java.util.Optional; 14 | 15 | @Service 16 | public class TorrentService { 17 | 18 | @Autowired 19 | private TorrentDao torrentDao; 20 | 21 | public long countAll() { 22 | return torrentDao.countAll(); 23 | } 24 | 25 | public boolean existsById(String infoHash) { 26 | return torrentDao.existsById(infoHash); 27 | } 28 | 29 | public void index(List torrents) { 30 | torrentDao.index(torrents); 31 | } 32 | 33 | public void upsert(List torrents) { 34 | torrentDao.upsert(torrents); 35 | } 36 | 37 | public Page query(SearchRequest request, Pageable pageable) { 38 | return torrentDao.query(request, pageable); 39 | } 40 | 41 | public Optional findById(String infoHash) { 42 | return torrentDao.findById(infoHash); 43 | } 44 | 45 | public Page findSimilar(Torrent torrent, Pageable pageable) { 46 | return torrentDao.searchSimilar(torrent, pageable); 47 | } 48 | @Transactional 49 | public void upsertAndIndex(List torrents) { 50 | torrentDao.upsert(torrents); 51 | torrentDao.index(torrents); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /dodder-common/src/main/java/cc/dodder/common/entity/Node.java: -------------------------------------------------------------------------------- 1 | package cc.dodder.common.entity; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import lombok.Data; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | @Data 10 | @JsonInclude(JsonInclude.Include.NON_NULL) 11 | public class Node { 12 | 13 | private Integer nid; // 父亲id 14 | private Integer pid; 15 | private String filename = ""; 16 | private Long filesize; 17 | private Integer index; 18 | 19 | private List children; 20 | 21 | public void addChild(Node node) { 22 | if (children == null) { 23 | children = new ArrayList<>(); 24 | } 25 | children.add(node); 26 | } 27 | 28 | public Node() { 29 | 30 | } 31 | 32 | public Node(Integer nid, Integer pid) { 33 | super(); 34 | this.nid = nid; 35 | this.pid = pid; 36 | } 37 | 38 | public Node(Integer nid, Integer pid, String filename, Long filesize, Integer index) { 39 | super(); 40 | this.nid = nid; 41 | this.pid = pid; 42 | this.filename = filename; 43 | this.filesize = filesize; 44 | this.index = index; 45 | } 46 | 47 | @Override 48 | public int hashCode() { 49 | final Integer prime = 31; 50 | Integer result = 1; 51 | result = prime * result + ((filename == null) ? 0 : filename.hashCode()); 52 | return result; 53 | } 54 | 55 | @Override 56 | public boolean equals(Object obj) { 57 | if (this == obj) 58 | return true; 59 | if (obj == null) 60 | return false; 61 | if (getClass() != obj.getClass()) 62 | return false; 63 | Node other = (Node) obj; 64 | if (filename == null) { 65 | if (other.filename != null) 66 | return false; 67 | } else if (!filename.equals(other.filename)) 68 | return false; 69 | return true; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /dodder-torrent-store-service/src/main/java/cc/dodder/torrent/store/TorrentStoreServiceApplication.java: -------------------------------------------------------------------------------- 1 | package cc.dodder.torrent.store; 2 | 3 | import cc.dodder.common.entity.Torrent; 4 | import cc.dodder.torrent.store.service.TorrentService; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.boot.SpringApplication; 9 | import org.springframework.boot.autoconfigure.SpringBootApplication; 10 | import org.springframework.context.annotation.Bean; 11 | import org.springframework.kafka.support.Acknowledgment; 12 | import org.springframework.kafka.support.KafkaHeaders; 13 | import org.springframework.messaging.Message; 14 | 15 | import java.util.List; 16 | import java.util.function.Consumer; 17 | 18 | @Slf4j 19 | @SpringBootApplication 20 | public class TorrentStoreServiceApplication { 21 | 22 | @Autowired 23 | private TorrentService torrentService; 24 | public static Boolean filterXxx; 25 | @Value("${dodder.filter-sensitive-torrent}") 26 | public void setFilterXxx(Boolean filterXxx) { 27 | TorrentStoreServiceApplication.filterXxx = filterXxx; 28 | } 29 | public static void main(String[] args) { 30 | SpringApplication.run(TorrentStoreServiceApplication.class, args); 31 | } 32 | 33 | 34 | @Bean 35 | public Consumer>> store() { 36 | return message -> { 37 | Acknowledgment acknowledgment = message.getHeaders().get(KafkaHeaders.ACKNOWLEDGMENT, Acknowledgment.class); 38 | List torrents = message.getPayload(); 39 | //save to mongodb and index to es 40 | torrentService.upsertAndIndex(torrents); 41 | //no error, execute acknowledge 42 | if (acknowledgment != null) { 43 | acknowledgment.acknowledge(); 44 | } 45 | }; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /dodder-common/src/main/java/cc/dodder/common/entity/Torrent.java: -------------------------------------------------------------------------------- 1 | package cc.dodder.common.entity; 2 | 3 | 4 | import com.fasterxml.jackson.annotation.JsonInclude; 5 | import lombok.Builder; 6 | import lombok.Getter; 7 | import lombok.Setter; 8 | import lombok.ToString; 9 | import org.springframework.data.annotation.Id; 10 | import org.springframework.data.elasticsearch.annotations.Document; 11 | import org.springframework.data.elasticsearch.annotations.Mapping; 12 | import org.springframework.data.elasticsearch.annotations.Setting; 13 | 14 | import java.io.Serializable; 15 | 16 | @Getter @Setter @Builder @ToString 17 | @Document(indexName = "torrent") 18 | @Mapping(mappingPath = "torrent_search_mapping.json") 19 | @Setting(settingPath = "elasticsearch_custom_comma_analyzer.json") 20 | @JsonInclude(JsonInclude.Include.NON_NULL) 21 | public class Torrent implements Serializable { 22 | 23 | @Id 24 | private String infoHash; 25 | private String fileType = "其他"; 26 | private String fileName; 27 | //IK分词器对于俄语根本解析不了,单独分离出来 28 | //其他语言虽然没有专门的分词器准,但至少能够分词,将就着用,如有需要,自行增加额外字段,并且对种子文件名进行语言检测 29 | private String fileNameRu; 30 | private long fileSize; 31 | 32 | private long createDate; 33 | 34 | private String files; 35 | 36 | private Integer isXxx = 0; //is sensitive torrent? 37 | 38 | public Torrent() { 39 | } 40 | 41 | public Torrent(String infoHash, String fileType, String fileName, String fileNameRu, long fileSize, long createDate, String files, Integer isXXX) { 42 | this.infoHash = infoHash; 43 | this.fileType = fileType; 44 | this.fileName = fileName; 45 | this.fileNameRu = fileNameRu; 46 | this.fileSize = fileSize; 47 | this.createDate = createDate; 48 | this.files = files; 49 | this.isXxx = isXXX; 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/lay/modules/laytpl.js: -------------------------------------------------------------------------------- 1 | /** layui-v2.4.5 MIT License By https://www.layui.com */ 2 | ;layui.define(function(e){"use strict";var r={open:"{{",close:"}}"},c={exp:function(e){return new RegExp(e,"g")},query:function(e,c,t){var o=["#([\\s\\S])+?","([^{#}])*?"][e||0];return n((c||"")+r.open+o+r.close+(t||""))},escape:function(e){return String(e||"").replace(/&(?!#?[a-zA-Z0-9]+;)/g,"&").replace(//g,">").replace(/'/g,"'").replace(/"/g,""")},error:function(e,r){var c="Laytpl Error:";return"object"==typeof console&&console.error(c+e+"\n"+(r||"")),c+e}},n=c.exp,t=function(e){this.tpl=e};t.pt=t.prototype,window.errors=0,t.pt.parse=function(e,t){var o=this,p=e,a=n("^"+r.open+"#",""),l=n(r.close+"$","");e=e.replace(/\s+|\r|\t|\n/g," ").replace(n(r.open+"#"),r.open+"# ").replace(n(r.close+"}"),"} "+r.close).replace(/\\/g,"\\\\").replace(n(r.open+"!(.+?)!"+r.close),function(e){return e=e.replace(n("^"+r.open+"!"),"").replace(n("!"+r.close),"").replace(n(r.open+"|"+r.close),function(e){return e.replace(/(.)/g,"\\$1")})}).replace(/(?="|')/g,"\\").replace(c.query(),function(e){return e=e.replace(a,"").replace(l,""),'";'+e.replace(/\\/g,"")+';view+="'}).replace(c.query(1),function(e){var c='"+(';return e.replace(/\s/g,"")===r.open+r.close?"":(e=e.replace(n(r.open+"|"+r.close),""),/^=/.test(e)&&(e=e.replace(/^=/,""),c='"+_escape_('),c+e.replace(/\\/g,"")+')+"')}),e='"use strict";var view = "'+e+'";return view;';try{return o.cache=e=new Function("d, _escape_",e),e(t,c.escape)}catch(u){return delete o.cache,c.error(u,p)}},t.pt.render=function(e,r){var n,t=this;return e?(n=t.cache?t.cache(e,c.escape):t.parse(t.tpl,e),r?void r(n):n):c.error("no data")};var o=function(e){return"string"!=typeof e?c.error("Template not found"):new t(e)};o.config=function(e){e=e||{};for(var c in e)r[c]=e[c]},o.v="1.2.0",e("laytpl",o)}); -------------------------------------------------------------------------------- /dodder-common/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | dodder 7 | cc.dodder 8 | 1.0-SNAPSHOT 9 | ../pom.xml 10 | 11 | 4.0.0 12 | 13 | dodder-common 14 | 15 | 16 | 17 | toolgood.words 18 | words 19 | 1.0-SNAPSHOT 20 | compile 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-data-elasticsearch 25 | true 26 | 27 | 28 | commons-codec 29 | commons-codec 30 | compile 31 | 32 | 33 | com.optimaize.languagedetector 34 | language-detector 35 | 0.6 36 | 37 | 38 | ch.qos.logback 39 | logback-classic 40 | 41 | 42 | 43 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/lay/modules/flow.js: -------------------------------------------------------------------------------- 1 | /** layui-v2.4.5 MIT License By https://www.layui.com */ 2 | ;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)}); -------------------------------------------------------------------------------- /dodder-common/src/main/java/cc/dodder/common/util/ByteUtil.java: -------------------------------------------------------------------------------- 1 | package cc.dodder.common.util; 2 | 3 | import java.io.ByteArrayInputStream; 4 | import java.io.DataInputStream; 5 | import java.io.IOException; 6 | 7 | public class ByteUtil { 8 | 9 | /** 10 | * byte数组转16进制字符串 11 | * @param ba 待转换byte数组 12 | * @return 16进制字符串 13 | */ 14 | public static String byteArrayToHex(byte[] ba) { 15 | StringBuilder sbuf = new StringBuilder(); 16 | for (byte b : ba) { 17 | String s = Integer.toHexString((int) (b & 0xff)); 18 | if (s.length() == 1) { 19 | sbuf.append('0'); 20 | } 21 | sbuf.append(s); 22 | } 23 | return sbuf.toString(); 24 | } 25 | 26 | public static byte[] hexStringToBytes(String hexString) { 27 | if (hexString == null || hexString.equals("")) { 28 | return null; 29 | } 30 | hexString = hexString.toUpperCase(); 31 | int length = hexString.length() / 2; 32 | char[] hexChars = hexString.toCharArray(); 33 | byte[] d = new byte[length]; 34 | for (int i = 0; i < length; i++) { 35 | int pos = i * 2; 36 | d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1])); 37 | } 38 | return d; 39 | } 40 | 41 | private static byte charToByte(char c) { 42 | return (byte) "0123456789ABCDEF".indexOf(c); 43 | } 44 | 45 | /** 46 | * int 转 byte 数组 47 | * @param value 待转换 int 48 | * @return 转换后的 byte 数组 49 | */ 50 | public static byte[] intToByteArray(int value) { 51 | return new byte[]{ 52 | (byte) (value >>> 24), 53 | (byte) (value >>> 16), 54 | (byte) (value >>> 8), 55 | (byte) value}; 56 | } 57 | 58 | /** 59 | * byte[] 转 int 60 | * @param bRefArr 待转换 byte[] 61 | * @return 转换结果 int 62 | */ 63 | public static int byteArrayToInt(byte[] bRefArr) { 64 | int r = -1; 65 | try (ByteArrayInputStream bintput = new ByteArrayInputStream(bRefArr); 66 | DataInputStream dintput = new DataInputStream(bintput)) { 67 | r = dintput.readInt(); 68 | } catch (IOException e) { 69 | e.printStackTrace(); 70 | } 71 | return r; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /words/src/main/java/toolgood/words/internals/IntDictionary.java: -------------------------------------------------------------------------------- 1 | package toolgood.words.internals; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.Map; 6 | 7 | public class IntDictionary { 8 | private int[] _keys; 9 | private int[] _values; 10 | private int last; 11 | 12 | public IntDictionary() { 13 | last = -1; 14 | } 15 | 16 | public int[] getKeys() { 17 | return _keys; 18 | } 19 | 20 | public int[] getValues() { 21 | return _values; 22 | } 23 | 24 | public void SetDictionary(Map dict) { 25 | 26 | List keys = new ArrayList(); 27 | dict.forEach((k, v) -> { 28 | keys.add((int) k); 29 | }); 30 | 31 | _keys = new int[dict.size()]; 32 | _values = new int[dict.size()]; 33 | for (int i = 0; i < keys.size(); i++) { 34 | _keys[i] = keys.get(i); 35 | _values[i] = dict.get(_keys[i]); 36 | } 37 | last = _keys.length - 1; 38 | } 39 | 40 | public void SetDictionary(int[] keys, int[] values) { 41 | _keys = keys; 42 | _values = values; 43 | last = _keys.length - 1; 44 | } 45 | 46 | public int IndexOf(int key) { 47 | if (last == -1) { 48 | return -1; 49 | } 50 | if (_keys[0] == key) { 51 | return 0; 52 | } 53 | if (_keys[last] == key) { 54 | return last; 55 | } 56 | 57 | int left = 0; 58 | int right = last; 59 | while (left + 1 < right) { 60 | int mid = (left + right) >> 1; 61 | int d = _keys[mid] - key; 62 | 63 | if (d == 0) { 64 | return mid; 65 | } else if (d > 0) { 66 | right = mid; 67 | } else { 68 | left = mid; 69 | } 70 | } 71 | return -1; 72 | } 73 | 74 | public int GetValue(int index){ 75 | return _values[index]; 76 | } 77 | 78 | } -------------------------------------------------------------------------------- /dodder-api/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | dodder 7 | cc.dodder 8 | 1.0-SNAPSHOT 9 | ../pom.xml 10 | 11 | 4.0.0 12 | jar 13 | 14 | dodder-api 15 | 16 | 17 | 18 | cc.dodder 19 | dodder-common 20 | 1.0-SNAPSHOT 21 | 22 | 23 | org.apache.dubbo 24 | dubbo-spring-boot-starter 25 | 2.7.8 26 | compile 27 | 28 | 29 | org.apache.dubbo 30 | dubbo-dependencies-zookeeper 31 | 2.7.8 32 | pom 33 | 34 | 35 | zookeeper 36 | org.apache.zookeeper 37 | 38 | 39 | 40 | 41 | zookeeper 42 | org.apache.zookeeper 43 | 3.5.8 44 | 45 | 46 | org.slf4j 47 | slf4j-log4j12 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /dodder-common/src/main/java/cc/dodder/common/util/FileTypeUtil.java: -------------------------------------------------------------------------------- 1 | package cc.dodder.common.util; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | /** 7 | * Created by Mr.Xu on 2017/4/21. 8 | */ 9 | public class FileTypeUtil { 10 | 11 | private static Map map = new HashMap<>(); 12 | 13 | static { 14 | map.put("jpg", "image"); 15 | map.put("jpeg", "image"); 16 | map.put("gif", "image"); 17 | map.put("png", "image"); 18 | map.put("bmp", "image"); 19 | 20 | map.put("mp4", "video"); 21 | map.put("mkv", "video"); 22 | map.put("ts", "video"); 23 | map.put("rmvb", "video"); 24 | map.put("avi", "video"); 25 | map.put("rm", "video"); 26 | map.put("asf", "video"); 27 | map.put("divx", "video"); 28 | map.put("mpeg", "video"); 29 | map.put("mpe", "video"); 30 | map.put("wmv", "video"); 31 | map.put("vob", "video"); 32 | map.put("flv", "video"); 33 | map.put("3gp", "video"); 34 | 35 | map.put("srt", "subtitle"); 36 | map.put("ass", "subtitle"); 37 | map.put("sub", "subtitle"); 38 | map.put("ssa", "subtitle"); 39 | 40 | map.put("nfo", "info"); 41 | 42 | map.put("xlsx", "excel"); 43 | map.put("xls", "excel"); 44 | map.put("doc", "word"); 45 | map.put("docx", "word"); 46 | } 47 | 48 | public static String getFileType(String fileName) { 49 | if (fileName == null || !fileName.contains(".")) 50 | return "file"; 51 | String type = "file"; 52 | try { 53 | if (fileName.contains("")) { 54 | fileName = fileName.substring(0, fileName.indexOf("")); 55 | } 56 | String sufix = fileName.substring(fileName.lastIndexOf(".") + 1); 57 | type = map.get(sufix); 58 | if (type == null) 59 | type = "file"; 60 | } catch (Exception e) { 61 | return "file"; 62 | } 63 | return type; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /dodder-common/src/main/java/cc/dodder/common/util/SensitiveWordsUtil.java: -------------------------------------------------------------------------------- 1 | package cc.dodder.common.util; 2 | 3 | import toolgood.words.WordsSearch; 4 | 5 | import java.io.BufferedReader; 6 | import java.io.InputStream; 7 | import java.io.InputStreamReader; 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | /*** 12 | * 敏感词检测工具 13 | */ 14 | public class SensitiveWordsUtil { 15 | 16 | private static volatile SensitiveWordsUtil instance; 17 | 18 | private WordsSearch ws = new WordsSearch(); 19 | 20 | public static SensitiveWordsUtil getInstance() { 21 | if (instance == null) { 22 | synchronized (SensitiveWordsUtil.class) { 23 | if (instance == null) { 24 | instance = new SensitiveWordsUtil(); 25 | List list = instance.loadKeywords("sensi_words.txt"); 26 | instance.ws.SetKeywords(list); 27 | } 28 | } 29 | } 30 | return instance; 31 | } 32 | 33 | /** 34 | * 检测是否包含敏感词 35 | * @param text 36 | * @return 37 | */ 38 | public boolean containsAny(String text) { 39 | return instance.ws.ContainsAny(text); 40 | } 41 | 42 | /** 43 | * 替换敏感词为 * 44 | * @param text 45 | * @return 46 | */ 47 | public String replace(String text) { 48 | return instance.ws.Replace(text); 49 | } 50 | 51 | private List loadKeywords(String resourceName){ 52 | List keyArray=new ArrayList(); 53 | try{ 54 | InputStream u1 = WordsSearch.class.getClassLoader().getResourceAsStream(resourceName); 55 | BufferedReader br = new BufferedReader(new InputStreamReader(u1)); 56 | String s = null; 57 | while((s = br.readLine())!=null){//使用readLine方法,一次读一行 58 | keyArray.add(s); 59 | } 60 | br.close(); 61 | }catch(Exception e){ 62 | e.printStackTrace(); 63 | } 64 | return keyArray; 65 | } 66 | 67 | public static void main(String[] args) { 68 | System.out.println(SensitiveWordsUtil.getInstance().replace("操逼 强奸多斯拉克 class所担负的顺丰速递所担AV电影负的顺丰速递")); 69 | } 70 | 71 | } -------------------------------------------------------------------------------- /dodder-torrent-store-service/src/main/java/cc/dodder/torrent/store/api/impl/TorrentApiImpl.java: -------------------------------------------------------------------------------- 1 | package cc.dodder.torrent.store.api.impl; 2 | 3 | import cc.dodder.api.TorrentApi; 4 | import cc.dodder.common.entity.Result; 5 | import cc.dodder.common.entity.Torrent; 6 | import cc.dodder.common.request.SearchRequest; 7 | import cc.dodder.common.vo.TorrentPageVO; 8 | import cc.dodder.common.vo.TorrentVO; 9 | import cc.dodder.torrent.store.service.TorrentService; 10 | import lombok.extern.slf4j.Slf4j; 11 | import org.apache.dubbo.config.annotation.DubboService; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.data.domain.Page; 14 | import org.springframework.data.domain.PageRequest; 15 | import org.springframework.data.domain.Pageable; 16 | 17 | import java.util.Optional; 18 | 19 | @Slf4j 20 | @DubboService(version = "${store.service.version}") 21 | public class TorrentApiImpl implements TorrentApi { 22 | 23 | @Autowired 24 | private TorrentService torrentService; 25 | 26 | @Override 27 | public Result existHash(String infoHash) { 28 | if (torrentService.existsById(infoHash)) 29 | return Result.noContent(); 30 | return Result.notFount(); 31 | } 32 | 33 | @Override 34 | public Result torrents(SearchRequest request) { 35 | if (request.getPage() == null || request.getPage() <= 0) 36 | request.setPage(1); 37 | Pageable pageable = PageRequest.of(request.getPage() - 1, request.getLimit()); 38 | Page torrents = torrentService.query(request, pageable); 39 | Result result = Result.ok(TorrentPageVO.builder() 40 | .list(torrents.getContent()) 41 | .total(torrents.getTotalElements()) 42 | .page(request.getPage()) 43 | .limit(request.getLimit()) 44 | .dbTotal(torrentService.countAll()) 45 | .build()); 46 | return result; 47 | } 48 | 49 | @Override 50 | public Result findById(String infoHash) { 51 | Optional torrent = torrentService.findById(infoHash); 52 | 53 | Result result = Result.ok(TorrentVO.builder() 54 | .torrent(torrent.orElse(null)).build()); 55 | torrent.ifPresent(t -> { 56 | Pageable pageable = PageRequest.of(0, 10); 57 | Page similar = torrentService.findSimilar(t, pageable); 58 | result.getData().setSimilar(similar.getContent()); 59 | }); 60 | return result; 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /dodder-torrent-download-service/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | cc.dodder 7 | dodder 8 | 1.0-SNAPSHOT 9 | ../pom.xml 10 | 11 | dodder-torrent-download-service 12 | jar 13 | 14 | 15 | 16 | cc.dodder 17 | dodder-common 18 | 1.0-SNAPSHOT 19 | 20 | 21 | org.springframework.boot 22 | spring-boot-starter-test 23 | test 24 | 25 | 26 | org.springframework.cloud 27 | spring-cloud-starter-stream-kafka 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-starter-actuator 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-data-redis 36 | 37 | 38 | redis.clients 39 | jedis 40 | 2.9.0 41 | 42 | 43 | junit 44 | junit 45 | test 46 | 47 | 48 | 49 | 50 | 51 | 52 | org.springframework.boot 53 | spring-boot-maven-plugin 54 | 2.4.5 55 | 56 | ${basedir}/../output/ 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /dodder-web/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | cc.dodder 7 | dodder 8 | 1.0-SNAPSHOT 9 | 10 | dodder-web 11 | jar 12 | 13 | 14 | 15 | cc.dodder 16 | dodder-api 17 | 1.0-SNAPSHOT 18 | 19 | 20 | org.springframework.boot 21 | spring-boot-starter-web 22 | 23 | 24 | org.springframework.boot 25 | spring-boot-starter-thymeleaf 26 | 27 | 28 | org.apache.httpcomponents 29 | httpclient 30 | 31 | 32 | org.springframework.boot 33 | spring-boot-devtools 34 | true 35 | 36 | 37 | junit 38 | junit 39 | test 40 | 41 | 42 | cc.dodder 43 | dodder-common 44 | 1.0-SNAPSHOT 45 | compile 46 | 47 | 48 | com.github.pemistahl 49 | lingua 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | org.springframework.boot 59 | spring-boot-maven-plugin 60 | 2.4.5 61 | 62 | ${basedir}/../output/ 63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/lay/modules/rate.js: -------------------------------------------------------------------------------- 1 | /** layui-v2.4.5 MIT License By https://www.layui.com */ 2 | ;layui.define("jquery",function(e){"use strict";var a=layui.jquery,i={config:{},index:layui.rate?layui.rate.index+1e4:0,set:function(e){var i=this;return i.config=a.extend({},i.config,e),i},on:function(e,a){return layui.onevent.call(this,n,e,a)}},l=function(){var e=this,a=e.config;return{setvalue:function(a){e.setvalue.call(e,a)},config:a}},n="rate",t="layui-rate",o="layui-icon-rate",s="layui-icon-rate-solid",u="layui-icon-rate-half",r="layui-icon-rate-solid layui-icon-rate-half",c="layui-icon-rate-solid layui-icon-rate",f="layui-icon-rate layui-icon-rate-half",v=function(e){var l=this;l.index=++i.index,l.config=a.extend({},l.config,i.config,e),l.render()};v.prototype.config={length:5,text:!1,readonly:!1,half:!1,value:0,theme:""},v.prototype.render=function(){var e=this,i=e.config,l=i.theme?'style="color: '+i.theme+';"':"";i.elem=a(i.elem),parseInt(i.value)!==i.value&&(i.half||(i.value=Math.ceil(i.value)-i.value<.5?Math.ceil(i.value):Math.floor(i.value)));for(var n='
    ",u=1;u<=i.length;u++){var r='
  • ";i.half&&parseInt(i.value)!==i.value&&u==Math.ceil(i.value)?n=n+'
  • ":n+=r}n+="
"+(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)}); -------------------------------------------------------------------------------- /dodder-common/src/main/java/cc/dodder/common/util/ExtensionUtil.java: -------------------------------------------------------------------------------- 1 | package cc.dodder.common.util; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | /*** 7 | * 文件扩展名工具类 8 | * 9 | * @author Mr.Xu 10 | * @date 2019-02-22 15:11 11 | **/ 12 | public class ExtensionUtil { 13 | 14 | private static final Map EXT; 15 | 16 | static { 17 | EXT = new HashMap<>(); 18 | EXT.put(".aif", "音频"); 19 | EXT.put(".aifc", "音频"); 20 | EXT.put(".aiff", "音频"); 21 | EXT.put(".mid", "音频"); 22 | EXT.put(".mp3", "音频"); 23 | EXT.put(".wav", "音频"); 24 | EXT.put(".wma", "音频"); 25 | EXT.put(".amr", "音频"); 26 | EXT.put(".aac", "音频"); 27 | EXT.put(".flac", "音频"); 28 | 29 | 30 | EXT.put(".asf", "视频"); 31 | EXT.put(".mpg", "视频"); 32 | EXT.put(".rm", "视频"); 33 | EXT.put(".avi", "视频"); 34 | EXT.put(".rmvb", "视频"); 35 | EXT.put(".mp4", "视频"); 36 | EXT.put(".wmv", "视频"); 37 | EXT.put(".mkv", "视频"); 38 | EXT.put(".m2ts", "视频"); 39 | EXT.put(".flv", "视频"); 40 | EXT.put(".qmv", "视频"); 41 | EXT.put(".mov", "视频"); 42 | EXT.put(".vob", "视频"); 43 | EXT.put(".3gp", "视频"); 44 | EXT.put(".mpg", "视频"); 45 | EXT.put(".mpeg", "视频"); 46 | EXT.put(".m4v", "视频"); 47 | EXT.put(".f4v", "视频"); 48 | 49 | EXT.put(".jpg", "图片"); 50 | EXT.put(".bmp", "图片"); 51 | EXT.put(".jpeg", "图片"); 52 | EXT.put(".png", "图片"); 53 | EXT.put(".gif", "图片"); 54 | EXT.put(".tiff", "图片"); 55 | 56 | EXT.put(".pdf", "文档"); 57 | EXT.put(".isz", "文档"); 58 | EXT.put(".chm", "文档"); 59 | EXT.put(".txt", "文档"); 60 | EXT.put(".epub", "文档"); 61 | EXT.put(".bc!", "文档"); 62 | EXT.put(".doc", "文档"); 63 | EXT.put(".docx", "文档"); 64 | EXT.put(".ppt", "文档"); 65 | EXT.put(".xls", "文档"); 66 | EXT.put(".xlsx", "文档"); 67 | 68 | EXT.put(".rar", "压缩文件"); 69 | EXT.put(".zip", "压缩文件"); 70 | EXT.put(".7z", "压缩文件"); 71 | EXT.put(".gz", "压缩文件"); 72 | EXT.put(".war", "压缩文件"); 73 | EXT.put(".z", "压缩文件"); 74 | 75 | EXT.put(".iso", "镜像文件"); 76 | 77 | EXT.put(".exe", "软件"); 78 | EXT.put(".app", "软件"); 79 | EXT.put(".msi", "软件"); 80 | EXT.put(".apk", "软件"); 81 | EXT.put(".dmg", "软件"); 82 | } 83 | 84 | public static String getExtensionType(String name) { 85 | 86 | String ext = getExt(name); 87 | if (ext == null) 88 | return null; 89 | 90 | if (ext.endsWith("\\") || ext.endsWith("/")) 91 | ext = ext.substring(0, ext.length() - 1); 92 | 93 | String type = EXT.get(ext); 94 | 95 | return type; 96 | } 97 | 98 | private static String getExt(String name) { 99 | int pos = name.lastIndexOf("."); 100 | if (pos == -1) 101 | return null; 102 | String ext = name.substring(pos); 103 | return ext; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /dodder-dht-server/src/main/java/cc/dodder/dhtserver/netty/DHTServer.java: -------------------------------------------------------------------------------- 1 | package cc.dodder.dhtserver.netty; 2 | 3 | 4 | import cc.dodder.common.util.BloomFilter; 5 | import cc.dodder.common.util.NodeIdUtil; 6 | import io.netty.bootstrap.Bootstrap; 7 | import io.netty.channel.ChannelFuture; 8 | import io.netty.channel.socket.DatagramPacket; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.beans.factory.annotation.Qualifier; 12 | import org.springframework.stereotype.Component; 13 | 14 | import javax.annotation.PostConstruct; 15 | import java.net.InetSocketAddress; 16 | import java.util.ArrayList; 17 | import java.util.Arrays; 18 | import java.util.List; 19 | 20 | /*** 21 | * 模拟 DHT 节点服务器 22 | * 23 | * @author Mr.Xu 24 | * @date 2019-02-15 14:44 25 | **/ 26 | @Slf4j 27 | @Component 28 | public class DHTServer { 29 | 30 | @Autowired 31 | @Qualifier("serverBootstrap") 32 | private Bootstrap b; 33 | 34 | @Autowired 35 | @Qualifier("udpSocketAddress") 36 | private InetSocketAddress udpPort; 37 | 38 | private ChannelFuture serverChannelFuture; 39 | 40 | public BloomFilter bloomFilter; 41 | private String filterSavePath; 42 | 43 | /** 44 | * 本机 DHT 节点 ID (根据 IP 生成) 45 | */ 46 | public static final byte[] SELF_NODE_ID = NodeIdUtil.randSelfNodeId(); 47 | 48 | public static final int SECRET = 888; 49 | 50 | /** 51 | * 启动节点列表 52 | */ 53 | public static final List BOOTSTRAP_NODES = new ArrayList<>(Arrays.asList( 54 | new InetSocketAddress("router.bittorrent.com", 6881), 55 | new InetSocketAddress("dht.transmissionbt.com", 6881), 56 | new InetSocketAddress("router.utorrent.com", 6881), 57 | new InetSocketAddress("dht.aelitis.com", 6881))); 58 | 59 | /** 60 | * 随 SpringBoot 启动 DHT 服务器 61 | * 62 | * @throws Exception 63 | */ 64 | @PostConstruct 65 | public void start() throws Exception { 66 | log.info("Starting dht server at " + udpPort); 67 | serverChannelFuture = b.bind(udpPort).sync(); 68 | serverChannelFuture.channel().closeFuture(); 69 | 70 | //init bloom filter 71 | /*bloomFilter = new BloomFilter(10000000); 72 | String path = System.getProperty("java.class.path"); 73 | int firstIndex = path.lastIndexOf(System.getProperty("path.separator")) + 1; 74 | int lastIndex = path.lastIndexOf(File.separator) + 1; 75 | filterSavePath = path.substring(firstIndex, lastIndex) + "filter.data"; 76 | File file = new File(filterSavePath); 77 | if (file.exists()) 78 | BloomFilter.readFilterFromFile(filterSavePath);*/ 79 | } 80 | 81 | /*@PreDestroy 82 | public void saveBloomFilter() { 83 | bloomFilter.saveFilterToFile(filterSavePath); 84 | }*/ 85 | 86 | /** 87 | * 发送 KRPC 协议数据报文 88 | * 89 | * @param packet 90 | */ 91 | public void sendKRPC(DatagramPacket packet) { 92 | serverChannelFuture.channel().writeAndFlush(packet); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /dodder-web/src/main/java/cc/dodder/web/controller/IndexController.java: -------------------------------------------------------------------------------- 1 | package cc.dodder.web.controller; 2 | 3 | import cc.dodder.api.TorrentApi; 4 | import cc.dodder.common.entity.Result; 5 | import cc.dodder.common.entity.Torrent; 6 | import cc.dodder.common.entity.Tree; 7 | import cc.dodder.common.request.SearchRequest; 8 | import cc.dodder.common.util.JSONUtil; 9 | import cc.dodder.common.util.StringUtil; 10 | import cc.dodder.common.vo.TorrentPageVO; 11 | import cc.dodder.common.vo.TorrentVO; 12 | import org.apache.dubbo.config.annotation.DubboReference; 13 | import org.springframework.stereotype.Controller; 14 | import org.springframework.ui.Model; 15 | import org.springframework.web.bind.annotation.PathVariable; 16 | import org.springframework.web.bind.annotation.RequestMapping; 17 | 18 | @Controller 19 | public class IndexController { 20 | 21 | @DubboReference(timeout = 30000, version = "${store.service.version}", url = "${store.service.url}") 22 | private TorrentApi torrentApi; 23 | 24 | private static final String TREE_HTML = "

    #1

  • #2#3
"; 25 | 26 | @RequestMapping("/") 27 | public String index(SearchRequest searchRequest, Model model) { 28 | if (searchRequest == null) 29 | searchRequest = new SearchRequest(); 30 | if (searchRequest.getPage() != null && searchRequest.getPage() > 500) 31 | searchRequest.setPage(500); 32 | Result result = torrentApi.torrents(searchRequest); 33 | model.addAttribute("result", result); 34 | model.addAttribute("searchRequest", searchRequest); 35 | return "index"; 36 | } 37 | 38 | @RequestMapping("/info/{infoHash}") 39 | public String info(@PathVariable("infoHash") String infoHash, Model model) { 40 | if (!infoHash.matches("^[a-zA-Z0-9]{40}$")) 41 | return "error/404"; 42 | Result result = torrentApi.findById(infoHash); 43 | if (result.getData().getTorrent() == null) 44 | return "error/404"; 45 | Torrent torrent = result.getData().getTorrent(); 46 | if ("".equals(torrent.getFiles()) || torrent.getFiles() == null || "null".equals(torrent.getFiles())) { //单文件 47 | int pos = torrent.getFileName().lastIndexOf("."); 48 | String name = pos > 0 ? torrent.getFileName().substring(0, pos) : torrent.getFileName(); 49 | model.addAttribute("treeFiles", TREE_HTML.replace("#1", name) 50 | .replace("#2", torrent.getFileName()) 51 | .replace("#3", StringUtil.formatSize(torrent.getFileSize()))); 52 | } else { 53 | Tree tree = JSONUtil.parseObject(torrent.getFiles(), Tree.class); 54 | tree.getRoot().setFilename(torrent.getFileName()); 55 | model.addAttribute("treeFiles", tree.getHtml(tree.getRoot())); 56 | } 57 | 58 | model.addAttribute("torrent", torrent); 59 | model.addAttribute("similar", result.getData().getSimilar()); 60 | return "info"; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /dodder-common/src/main/java/cc/dodder/common/util/bencode/BencodingUtils.java: -------------------------------------------------------------------------------- 1 | package cc.dodder.common.util.bencode; 2 | 3 | import cc.dodder.common.util.JSONUtil; 4 | 5 | import java.io.ByteArrayInputStream; 6 | import java.io.ByteArrayOutputStream; 7 | import java.nio.charset.StandardCharsets; 8 | import java.util.Map; 9 | 10 | public class BencodingUtils { 11 | 12 | public static final String UTF_8 = "UTF-8"; 13 | public static final int LENGTH_DELIMITER = 58; 14 | public static final int DICTIONARY = 100; 15 | public static final int LIST = 108; 16 | public static final int NUMBER = 105; 17 | public static final int EOF = 101; 18 | public static final Integer TRUE = 1; 19 | public static final Integer FALSE = 0; 20 | 21 | public static byte[] encode(Map map) { 22 | try (ByteArrayOutputStream stream = new ByteArrayOutputStream(); 23 | BencodingOutputStream bencode = new BencodingOutputStream(stream)) { 24 | bencode.writeMap(map); 25 | return stream.toByteArray(); 26 | } catch (Exception e) { 27 | return new byte[0]; 28 | } 29 | } 30 | 31 | public static Map decode1(byte[] bytes) { 32 | try (ByteArrayInputStream stream = new ByteArrayInputStream(bytes); 33 | BencodingInputStream bencode = new BencodingInputStream(stream)) { 34 | return bencode.readMap(); 35 | } catch (Exception e) { 36 | return null; 37 | } 38 | } 39 | 40 | public static Map decode(byte[] bytes) { 41 | try (ByteArrayInputStream stream = new ByteArrayInputStream(bytes); 42 | BencodeReader bencode = new BencodeReader(stream)) { 43 | return bencode.readDict(); 44 | } catch (Exception e) { 45 | return null; 46 | } 47 | } 48 | 49 | public static Map decode(byte[] bytes, int offset, int length) { 50 | try (ByteArrayInputStream stream = new ByteArrayInputStream(bytes, offset, length); 51 | BencodeReader bencode = new BencodeReader(stream)) { 52 | return bencode.readDict(); 53 | } catch (Exception e) { 54 | return null; 55 | } 56 | } 57 | 58 | 59 | public static void main(String[] args) { 60 | 61 | String s = "d1:ei0e4:ipv44:aaaa12:complete_agoi1729e1:md11:upload_onlyi3e11:lt_donthavei7e12:ut_holepunchi4e11:ut_metadatai2e6:ut_pexi1e10:ut_commenti6e6:ut_bidi9e15:ut_bid_responsei10e17:ut_channel_state2i11e18:ut_payment_addressi12ee13:metadata_sizei14987e1:pi44454e4:reqqi255e1:v17:BitTorrent 7.10.52:ypi52166e6:yourip4:Y���e"; 62 | 63 | Map map = decode1(s.getBytes(StandardCharsets.ISO_8859_1)); 64 | System.out.println(JSONUtil.toJSONString(map)); 65 | map = decode(s.getBytes()); 66 | System.out.println(JSONUtil.toJSONString(map)); 67 | /*try (FileInputStream stream = new FileInputStream(new File("E:\\web\\img1\\53.torrent")); 68 | BencodingInputStream bencode = new BencodingInputStream(stream, "UTF-8", true)) { 69 | Map map = bencode.readMap(); 70 | System.out.println(); 71 | } catch (Exception e) { 72 | }*/ 73 | } 74 | } -------------------------------------------------------------------------------- /dodder-dht-server/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | cc.dodder 7 | dodder 8 | 1.0-SNAPSHOT 9 | ../pom.xml 10 | 11 | dodder-dht-server 12 | jar 13 | 14 | 15 | 16 | cc.dodder 17 | dodder-common 18 | 1.0-SNAPSHOT 19 | 20 | 21 | com.github.pemistahl 22 | lingua 23 | 24 | 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-starter-test 33 | test 34 | 35 | 36 | org.springframework.cloud 37 | spring-cloud-starter-stream-kafka 38 | 39 | 40 | io.netty 41 | netty-all 42 | 4.1.42.Final 43 | 44 | 45 | org.springframework.boot 46 | spring-boot-starter-data-redis 47 | 48 | 49 | redis.clients 50 | jedis 51 | 3.1.0 52 | 53 | 54 | junit 55 | junit 56 | test 57 | 58 | 59 | 60 | 61 | 62 | 63 | org.springframework.boot 64 | spring-boot-maven-plugin 65 | 2.4.5 66 | 67 | ${basedir}/../output/ 68 | 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/lay/modules/tree.js: -------------------------------------------------------------------------------- 1 | /** layui-v2.4.5 MIT License By https://www.layui.com */ 2 | ;layui.define("jquery",function(e){"use strict";var o=layui.$,a=layui.hint(),i="layui-tree-enter",r=function(e){this.options=e},t={arrow:["",""],checkbox:["",""],radio:["",""],branch:["",""],leaf:""};r.prototype.init=function(e){var o=this;e.addClass("layui-box layui-tree"),o.options.skin&&e.addClass("layui-tree-skin-"+o.options.skin),o.tree(e),o.on(e)},r.prototype.tree=function(e,a){var i=this,r=i.options,n=a||r.nodes;layui.each(n,function(a,n){var l=n.children&&n.children.length>0,c=o('
    '),s=o(["
  • ",function(){return l?''+(n.spread?t.arrow[1]:t.arrow[0])+"":""}(),function(){return r.check?''+("checkbox"===r.check?t.checkbox[0]:"radio"===r.check?t.radio[0]:"")+"":""}(),function(){return'"+(''+(l?n.spread?t.branch[1]:t.branch[0]:t.leaf)+"")+(""+(n.name||"未命名")+"")}(),"
  • "].join(""));l&&(s.append(c),i.tree(c,n.children)),e.append(s),"function"==typeof r.click&&i.click(s,n),i.spread(s,n),r.drag&&i.drag(s,n)})},r.prototype.click=function(e,o){var a=this,i=a.options;e.children("a").on("click",function(e){layui.stope(e),i.click(o)})},r.prototype.spread=function(e,o){var a=this,i=(a.options,e.children(".layui-tree-spread")),r=e.children("ul"),n=e.children("a"),l=function(){e.data("spread")?(e.data("spread",null),r.removeClass("layui-show"),i.html(t.arrow[0]),n.find(".layui-icon").html(t.branch[0])):(e.data("spread",!0),r.addClass("layui-show"),i.html(t.arrow[1]),n.find(".layui-icon").html(t.branch[1]))};r[0]&&(i.on("click",l),n.on("dblclick",l))},r.prototype.on=function(e){var a=this,r=a.options,t="layui-tree-drag";e.find("i").on("selectstart",function(e){return!1}),r.drag&&o(document).on("mousemove",function(e){var i=a.move;if(i.from){var r=(i.to,o('
    '));e.preventDefault(),o("."+t)[0]||o("body").append(r);var n=o("."+t)[0]?o("."+t):r;n.addClass("layui-show").html(i.from.elem.children("a").html()),n.css({left:e.pageX+10,top:e.pageY+10})}}).on("mouseup",function(){var e=a.move;e.from&&(e.from.elem.children("a").removeClass(i),e.to&&e.to.elem.children("a").removeClass(i),a.move={},o("."+t).remove())})},r.prototype.move={},r.prototype.drag=function(e,a){var r=this,t=(r.options,e.children("a")),n=function(){var t=o(this),n=r.move;n.from&&(n.to={item:a,elem:e},t.addClass(i))};t.on("mousedown",function(){var o=r.move;o.from={item:a,elem:e}}),t.on("mouseenter",n).on("mousemove",n).on("mouseleave",function(){var e=o(this),a=r.move;a.from&&(delete a.to,e.removeClass(i))})},e("tree",function(e){var i=new r(e=e||{}),t=o(e.elem);return t[0]?void i.init(t):a.error("layui.tree 没有找到"+e.elem+"元素")})}); -------------------------------------------------------------------------------- /dodder-torrent-download-service/src/main/java/cc/dodder/torrent/download/TorrentDownloadServiceApplication.java: -------------------------------------------------------------------------------- 1 | package cc.dodder.torrent.download; 2 | 3 | import cc.dodder.common.entity.DownloadMsgInfo; 4 | import cc.dodder.common.util.SensitiveWordsUtil; 5 | import cc.dodder.common.util.SystemClock; 6 | import cc.dodder.torrent.download.client.Constants; 7 | import cc.dodder.torrent.download.task.BlockingExecutor; 8 | import cc.dodder.torrent.download.task.DownloadTask; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.beans.factory.annotation.Value; 12 | import org.springframework.boot.SpringApplication; 13 | import org.springframework.boot.autoconfigure.SpringBootApplication; 14 | import org.springframework.context.annotation.Bean; 15 | import org.springframework.data.redis.core.RedisTemplate; 16 | import org.springframework.scheduling.annotation.EnableScheduling; 17 | 18 | import javax.annotation.PostConstruct; 19 | import javax.annotation.PreDestroy; 20 | import java.util.List; 21 | import java.util.concurrent.Executors; 22 | import java.util.function.Consumer; 23 | 24 | @Slf4j 25 | @EnableScheduling 26 | @SpringBootApplication 27 | public class TorrentDownloadServiceApplication { 28 | 29 | @Value("${download.num.thread}") 30 | private int nThreads; 31 | @Autowired 32 | private RedisTemplate redisTemplate; 33 | 34 | private BlockingExecutor blockingExecutor; 35 | public static Boolean filterSensitiveWords; 36 | 37 | @Value("${download.enable-filter-sensitive-words}") 38 | public void setFilterSensitiveWords(Boolean filterSensitiveWords) { 39 | TorrentDownloadServiceApplication.filterSensitiveWords = filterSensitiveWords; 40 | } 41 | 42 | public static void main(String[] args) { 43 | SensitiveWordsUtil.getInstance(); //init it 44 | SpringApplication.run(TorrentDownloadServiceApplication.class, args); 45 | } 46 | 47 | @Bean 48 | public Consumer> download() { 49 | return list -> { 50 | //submit to blocking executor 51 | try { 52 | for(DownloadMsgInfo msgInfo: list) { 53 | if (redisTemplate.hasKey(msgInfo.getCrc64())) 54 | continue; 55 | //由于下载线程消费的速度总是比 dht server 生产的速度慢,所以要做一下时间限制,否则程序越跑越慢 56 | if (SystemClock.now() - msgInfo.getTimestamp() >= Constants.MAX_LOSS_TIME) { 57 | continue; 58 | } 59 | blockingExecutor.execute(new DownloadTask(msgInfo)); 60 | } 61 | } catch (Exception e) { 62 | e.printStackTrace(); 63 | } 64 | }; 65 | } 66 | 67 | /*@Scheduled(fixedDelay = 5 * 60 * 1000) 68 | public void autoFinalize() { 69 | //定时强制回收 Finalizer 队列里的 Socket 对象(有个抽象父类重写了 finalize 方法, 70 | //频繁创建 Socket 会导致 Socket 得不到及时回收频繁发生 FGC) 71 | System.runFinalization(); 72 | }*/ 73 | 74 | @PostConstruct 75 | public void init() { 76 | //max task bound 5000 77 | blockingExecutor = new BlockingExecutor(Executors.newFixedThreadPool(nThreads), 5000); 78 | } 79 | 80 | @PreDestroy 81 | public void destroy() { 82 | blockingExecutor.shutdownNow(); 83 | } 84 | 85 | } -------------------------------------------------------------------------------- /dodder-common/src/main/java/cc/dodder/common/util/NodeIdUtil.java: -------------------------------------------------------------------------------- 1 | package cc.dodder.common.util; 2 | 3 | 4 | import org.apache.commons.codec.digest.PureJavaCrc32C; 5 | 6 | import java.security.MessageDigest; 7 | import java.security.NoSuchAlgorithmException; 8 | import java.util.Date; 9 | import java.util.Random; 10 | 11 | /*** 12 | * NodeId Util 13 | * 14 | * @author Mr.Xu 15 | * @simce 2019-02-15 17:36 16 | **/ 17 | public class NodeIdUtil { 18 | 19 | private static MessageDigest messageDigest; 20 | private static Random random; 21 | static 22 | { 23 | try { 24 | messageDigest = MessageDigest.getInstance("SHA-1"); 25 | } catch (NoSuchAlgorithmException e) { 26 | e.printStackTrace(); 27 | } 28 | random = new Random(new Date().getTime()); 29 | } 30 | 31 | /** 32 | * See https://libtorrent.org/dht_sec.html 33 | * 34 | * @return byte[] node id 35 | */ 36 | public static byte[] randSelfNodeId() { 37 | long ip = NetworkUtil.getIp(); 38 | return getNodeIdByIp(ip); 39 | } 40 | 41 | /** 42 | * 根据安全扩展协议生成 node_id,规则:保持前三个与最后一个字节不变,中间的从其他节点 ID 中复制 43 | * 用于确保 find_node 与其他回复中的 id 保持一致,并且对外每个节点回复不同的 ID 44 | * 45 | * @param selfId //第一次初始时的 node_id 46 | * @param nodeId //其他节点的 id 47 | * @return byte[] node id 48 | */ 49 | public static byte[] makeSelfId(byte[] selfId, byte[] nodeId) { 50 | byte[] bytes = new byte[20]; 51 | bytes[0] = selfId[0]; 52 | bytes[1] = selfId[1]; 53 | bytes[2] = selfId[2]; 54 | System.arraycopy(nodeId, 3, bytes, 3, 16); 55 | bytes[19] = selfId[19]; 56 | return bytes; 57 | } 58 | 59 | /** 60 | * generate random node_id 61 | * 62 | * @return byte[] node id 63 | */ 64 | public static byte[] createRandomNodeId() { 65 | byte[] bytes = new byte[20]; 66 | for (int i = 0; i < bytes.length; i++) 67 | { 68 | bytes[i] = (byte)random.nextInt(256); 69 | } 70 | messageDigest.update(bytes); 71 | return messageDigest.digest(); 72 | } 73 | 74 | /** 75 | * distance: node1 XOR node2 76 | * 77 | * @param node1 78 | * @param node2 79 | * @return node id which closeness to node2 80 | */ 81 | public static byte[] getNeighbor(byte[] node1, byte[] node2) { 82 | byte[] bytes = new byte[20]; 83 | System.arraycopy(node2, 0, bytes, 0, 18); 84 | System.arraycopy(node1, 18, bytes, 18, 2); 85 | return bytes; 86 | } 87 | 88 | private static byte[] getNodeIdByIp(long ip) { 89 | int rand = new Random().nextInt(256); 90 | int r = rand & 0x7; 91 | 92 | PureJavaCrc32C crc32C = new PureJavaCrc32C(); 93 | crc32C.update(ByteUtil.intToByteArray((int)(ip & 0x030f3fff) | (r << 29)), 0, 4); 94 | long crc = crc32C.getValue(); 95 | 96 | byte[] node_id = new byte[20]; 97 | node_id[0] = (byte)((crc >> 24) & 0xff); 98 | node_id[1] = (byte)((crc >> 16) & 0xff); 99 | node_id[2] = (byte)(((crc >> 8) & 0xf8) | (rand & 0x7)); 100 | for (int i = 3; i < 19; ++i) 101 | node_id[i] = (byte)random.nextInt(256); 102 | node_id[19] = (byte)rand; 103 | 104 | return node_id; 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /dodder-torrent-store-service/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | cc.dodder 7 | dodder 8 | 1.0-SNAPSHOT 9 | ../pom.xml 10 | 11 | dodder-torrent-store-service 12 | jar 13 | 14 | 15 | 16 | cc.dodder 17 | dodder-common 18 | 1.0-SNAPSHOT 19 | 20 | 21 | com.github.pemistahl 22 | lingua 23 | 24 | 25 | 26 | 27 | cc.dodder 28 | dodder-api 29 | 1.0-SNAPSHOT 30 | 31 | 32 | org.springframework.boot 33 | spring-boot-starter 34 | 35 | 36 | org.springframework.boot 37 | spring-boot-starter-test 38 | test 39 | 40 | 41 | org.springframework.cloud 42 | spring-cloud-starter-stream-kafka 43 | 44 | 45 | org.springframework.boot 46 | spring-boot-starter-actuator 47 | 48 | 49 | org.springframework.boot 50 | spring-boot-starter-data-mongodb 51 | 52 | 53 | org.springframework.boot 54 | spring-boot-starter-data-elasticsearch 55 | 56 | 57 | junit 58 | junit 59 | test 60 | 61 | 62 | 63 | 64 | 65 | 66 | org.springframework.boot 67 | spring-boot-maven-plugin 68 | 2.4.5 69 | 70 | ${basedir}/../output/ 71 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/css/jquery.treeview.css: -------------------------------------------------------------------------------- 1 | .treeview, .treeview ul { 2 | padding: 0; 3 | margin: 0; 4 | list-style: none; 5 | } 6 | 7 | .treeview ul { 8 | background-color: white; 9 | margin-top: 4px; 10 | } 11 | 12 | .treeview .hitarea { 13 | background: url(../img/treeview/treeview-default.gif) -64px -25px no-repeat; 14 | height: 16px; 15 | width: 16px; 16 | margin-left: -16px; 17 | float: left; 18 | cursor: pointer; 19 | } 20 | /* fix for IE6 */ 21 | * html .hitarea { 22 | display: inline; 23 | float:none; 24 | } 25 | 26 | .treeview li { 27 | margin: 0; 28 | padding: 3px 0pt 3px 16px; 29 | } 30 | 31 | .treeview a.selected { 32 | background-color: #eee; 33 | } 34 | 35 | #treecontrol { margin: 1em 0; display: none; } 36 | 37 | .treeview .hover { color: red; cursor: pointer; } 38 | 39 | .treeview li { background: url(../img/treeview/treeview-default-line.gif) 0 0 no-repeat; } 40 | .treeview li.collapsable, .treeview li.expandable { background-position: 0 -176px; } 41 | 42 | .treeview .expandable-hitarea { background-position: -80px -3px; } 43 | 44 | .treeview li.last { background-position: 0 -1766px } 45 | .treeview li.lastCollapsable, .treeview li.lastExpandable { background-image: url(../img/treeview/treeview-default.gif); } 46 | .treeview li.lastCollapsable { background-position: 0 -111px } 47 | .treeview li.lastExpandable { background-position: -32px -67px } 48 | 49 | .treeview div.lastCollapsable-hitarea, .treeview div.lastExpandable-hitarea { background-position: 0; } 50 | 51 | .treeview-red li { background-image: url(../img/treeview/treeview-red-line.gif); } 52 | .treeview-red .hitarea, .treeview-red li.lastCollapsable, .treeview-red li.lastExpandable { background-image: url(../img/treeview/treeview-red.gif); } 53 | 54 | .treeview-black li { background-image: url(../img/treeview/treeview-black-line.gif); } 55 | .treeview-black .hitarea, .treeview-black li.lastCollapsable, .treeview-black li.lastExpandable { background-image: url(../img/treeview/treeview-black.gif); } 56 | 57 | .treeview-gray li { background-image: url(../img/treeview/treeview-gray-line.gif); } 58 | .treeview-gray .hitarea, .treeview-gray li.lastCollapsable, .treeview-gray li.lastExpandable { background-image: url(../img/treeview/treeview-gray.gif); } 59 | 60 | .treeview-famfamfam li { background-image: url(../img/treeview/treeview-famfamfam-line.gif); } 61 | .treeview-famfamfam .hitarea, .treeview-famfamfam li.lastCollapsable, .treeview-famfamfam li.lastExpandable { background-image: url(../img/treeview/treeview-famfamfam.gif); } 62 | 63 | .treeview .placeholder { 64 | background: url(../img/treeview/ajax-loader.gif) 0 0 no-repeat; 65 | height: 16px; 66 | width: 16px; 67 | display: block; 68 | } 69 | 70 | .filetree li { padding: 3px 0 2px 16px; } 71 | .filetree span.folder, .filetree span.file { padding: 1px 0 1px 16px; display: block; } 72 | .filetree span.folder { background: url(../img/treeview/folder.gif) 0 0 no-repeat; } 73 | .filetree li.expandable span.folder { background: url(../img/treeview/folder-closed.gif) 0 0 no-repeat; } 74 | .filetree span.file { background: url(../img/treeview/file.gif) 0 0 no-repeat; } 75 | -------------------------------------------------------------------------------- /dodder-dht-server/src/main/java/cc/dodder/dhtserver/netty/config/NettyConfig.java: -------------------------------------------------------------------------------- 1 | package cc.dodder.dhtserver.netty.config; 2 | 3 | import io.netty.bootstrap.Bootstrap; 4 | import io.netty.buffer.PooledByteBufAllocator; 5 | import io.netty.channel.ChannelInitializer; 6 | import io.netty.channel.ChannelOption; 7 | import io.netty.channel.EventLoopGroup; 8 | import io.netty.channel.nio.NioEventLoopGroup; 9 | import io.netty.channel.socket.nio.NioDatagramChannel; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.beans.factory.annotation.Qualifier; 12 | import org.springframework.beans.factory.annotation.Value; 13 | import org.springframework.boot.context.properties.ConfigurationProperties; 14 | import org.springframework.context.ApplicationListener; 15 | import org.springframework.context.annotation.Bean; 16 | import org.springframework.context.annotation.Configuration; 17 | import org.springframework.context.event.ContextClosedEvent; 18 | 19 | import java.net.InetSocketAddress; 20 | import java.util.HashMap; 21 | import java.util.Map; 22 | import java.util.Set; 23 | 24 | /*** 25 | * Netty 服务器配置 26 | * 27 | * @author: Mr.Xu 28 | * @create: 2019-02-15 14:50 29 | **/ 30 | @Configuration 31 | @ConfigurationProperties(prefix = "netty") 32 | public class NettyConfig implements ApplicationListener { 33 | 34 | @Value("${netty.udp.port}") 35 | private int udpPort; 36 | @Value("${netty.so.rcvbuf}") 37 | private int rcvbuf; 38 | @Value("${netty.so.sndbuf}") 39 | private int sndbuf; 40 | 41 | private EventLoopGroup group; 42 | 43 | 44 | @Autowired 45 | @Qualifier("channelInitializer") 46 | private ChannelInitializer channelInitializer; 47 | 48 | @Bean(name = "serverBootstrap") 49 | public Bootstrap bootstrap() { 50 | group = group(); 51 | Bootstrap b = new Bootstrap(); 52 | b.group(group) 53 | .channel(NioDatagramChannel.class) 54 | .handler(channelInitializer); 55 | Map, Object> udpChannelOptions = udpChannelOptions(); 56 | Set> keySet = udpChannelOptions.keySet(); 57 | for (@SuppressWarnings("rawtypes") 58 | ChannelOption option : keySet) { 59 | b.option(option, udpChannelOptions.get(option)); 60 | } 61 | return b; 62 | } 63 | 64 | @Bean(name = "group") 65 | public EventLoopGroup group() { 66 | return new NioEventLoopGroup(); 67 | } 68 | 69 | @Bean(name = "udpSocketAddress") 70 | public InetSocketAddress udpPort() { 71 | return new InetSocketAddress(udpPort); 72 | } 73 | 74 | @Bean(name = "udpChannelOptions") 75 | public Map, Object> udpChannelOptions() { 76 | Map, Object> options = new HashMap, Object>(); 77 | options.put(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT); 78 | options.put(ChannelOption.SO_BROADCAST, true); 79 | options.put(ChannelOption.SO_RCVBUF, rcvbuf); 80 | options.put(ChannelOption.SO_SNDBUF, sndbuf); 81 | return options; 82 | } 83 | 84 | @Override 85 | public void onApplicationEvent(ContextClosedEvent contextClosedEvent) { 86 | if(contextClosedEvent.getApplicationContext().getParent() == null) { 87 | group.shutdownGracefully(); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | cc.dodder 8 | dodder 9 | 1.0-SNAPSHOT 10 | pom 11 | 12 | 13 | org.springframework.boot 14 | spring-boot-starter-parent 15 | 2.4.3 16 | 17 | 18 | 19 | 20 | dodder-common 21 | dodder-dht-server 22 | dodder-torrent-download-service 23 | dodder-torrent-store-service 24 | dodder-web 25 | dodder-api 26 | words 27 | 28 | 29 | 30 | UTF-8 31 | UTF-8 32 | 1.8 33 | 2020.0.2 34 | true 35 | 36 | 37 | 38 | 39 | org.projectlombok 40 | lombok 41 | true 42 | 43 | 44 | 45 | 46 | 47 | 48 | org.springframework.cloud 49 | spring-cloud-dependencies 50 | ${spring-cloud.version} 51 | pom 52 | import 53 | 54 | 55 | org.springframework.cloud 56 | spring-cloud-alibaba-dependencies 57 | 0.2.0.RELEASE 58 | pom 59 | import 60 | 61 | 62 | 63 | 64 | 65 | 66 | nexus-aliyun 67 | Nexus aliyun 68 | http://maven.aliyun.com/nexus/content/groups/public 69 | 70 | 71 | spring-milestones 72 | Spring Milestones 73 | https://repo.spring.io/milestone 74 | 75 | 76 | nexus-ebi 77 | Nexus ebi 78 | https://www.ebi.ac.uk/intact/maven/nexus/content/repositories/public/ 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
     2 |         ________      _________________
     3 | ___  __ \___________  /_____  /____________
     4 | __  / / /  __ \  __  /_  __  /_  _ \_  ___/
     5 | _  /_/ // /_/ / /_/ / / /_/ / /  __/  /
     6 | /_____/ \____/\__,_/  \__,_/  \___//_/     一个支持集群部署的分布式 DHT 网络爬虫。
     7 | 
    8 | ------- 9 | # 快速开始 10 | #### 环境依赖 11 | - Zookeeper-3.7.0 ([http://zookeeper.apache.org/](http://zookeeper.apache.org/)) 12 | - Kafka-2.13-2.8.0 ([http://kafka.apache.org/](http://kafka.apache.org/)) 13 | - Redis-2.6 ([https://redis.io/](https://redis.io/)) 14 | - MongoDB-4.4.5 ([https://www.mongodb.com/](https://www.mongodb.com/)) 15 | - Elasticsearch-7.12.0 ([https://www.elastic.co/](https://www.elastic.co/)) 16 | - elasticsearch-analysis-ik-7.12.0 ([https://github.com/medcl/elasticsearch-analysis-ik](https://github.com/medcl/elasticsearch-analysis-ik)) 17 | #### 演示地址 18 | [https://dodder.icu](https://dodder.icu) 19 | 20 | announce_peer messages: 21 | ![announce_peer](https://github.com/xwlcn/img/raw/master/announce_peer.gif) 22 | 23 | 单机运行环境: 24 | * CPU: Intel Xeon E3-1230 v3 - 3.3 GHz - 4 core(s) 25 | * RAM: 32GB - DDR3 26 | * Hard Drive(s): 2x 1TB (HDD SATA) 27 | * Bandwidth: Unmetered @ 1Gbps 28 | 29 | #### 更新日志 30 | * 2021-06-11 31 | - 优化搜索、相关推荐查询速度 32 | - 解决 dodder-torrent-download-service 内存泄露问题 33 | * 2021-05-06 34 | - 优化种子下载服务内存:丢弃协议 pieces 内容,bencode 不存储 pieces 内容 35 | - web 改用 tomcat 容器,目前版本对应的 undertow 搭配 Dubbo 使用存在内存泄露 36 | - 修复某些文件名乱码问题 37 | - 使用 CRC64 作为 redis key 去重 38 | - 新增或删除某些中文敏感词汇 39 | - 修复俄文资源无相关推荐问题 40 | * 2021-04-30 41 | - 优化分词搜索功能 42 | * 2021-04-29 43 | - 降低 Spring Dubbo 版本(之前的可能导致内存泄露) 44 | - 移除 KafkaTemplate 的使用,统一使用 Spring Cloud Stream 提供的 StreamBridge 45 | * 2021-04-27 46 | - 升级 Spring Boot 以及 Spring Cloud 版本 47 | - 修改 kafka 消费端为批量消费 48 | - 新增敏感词过滤功能 49 | - 优化种子下载内存占用一直升高问题 50 | - 优化 MongoDB 数据存储内容(内存占用是个大问题,以后考虑换 HBase) 51 | - 合并 MongoDB 入库与 Elasticsearch 索引为同步方法,之前两个不同分组进行入库与索引可能造成先索引数据库中还没有数据的情况,以至于前端网页404问题 52 | * 2019-10-25 53 | - 升级目前部署在服务器上的 MongoDB,之前 3.6.8 版本频繁挂掉无错误日志 54 | - 使用 MongoDB 连接池 55 | - 去掉 indexMessages 主题消息,索引与入库使用 torrentMessages 同一个主题消息(使用不同分组),减少网络传输以及磁盘占用 56 | - 去除种子信息下载之前的 MongoDB 去重查询(几千个下载线程就是几千个并发查询,过于耗费资源) 57 | * 2019-10-20 58 | - 优化爬虫速度,新增阻塞线程池用于下载种子信息(降低内存使用) 59 | - 40万数据时(日爬取35w+新数据) 60 | * 2019-10-13 61 | - 数据存储到 MongoDB,Elasticsearch 只做索引 62 | - 升级各个环境依赖的版本 63 | - 优化数据存储服务,将 Kafka Torrent 的入库和索引消息改为手动提交模式,防止数据丢失 64 | - 进行两台服务器分布式部署测试,目前成功部署运行中: 65 | 1. A 服务器部署微服务:dht-server、store-service (服务器有限,所以所有环境依赖比如 Elasticsearch 全在这台机器上) 66 | 2. B 服务器部署微服务:dht-server、download-service、dodder-web 67 | * 2019-04-17 68 | - 初始版本 69 | 70 | #### 整体架构 71 | ![架构图](https://github.com/xwlcn/Dodder/raw/master/20190305.jpg) 72 | 73 | 说明:项目中的`dht-server`、`download-service`、`store-service`都是可以集群部署的, 74 | `dht-server`负责爬取 DHT 网络中的 info_hash,然后写入到 Kafka 消息队列中去,`download-service` 75 | 负责读取 info_hash 信息到指定 ip 去下载种子文件的 metadata(集群部署时,注意设置好 kafka 主题的分区数量, 76 | 分区数量 >= 服务部署个数)。下载好的 metadata 解析出文件信息封装成 Torrent 对象写入 Kafka 的 77 | `torrentMessages`主题中去,`store-service`负责读取 Torrent 存储到 Elasticsearch 中去。 78 | 79 | 去重:Redis 第一次去重,MongDB 与 Elasticsearch 采用 upsert 插入数据防止重复插入。 80 | 81 | #### 部署 82 | 前面的环境全部搭好之后,clone 整个项目到本地,如果是集群部署请修改各个服务模块里面的一些 ip 地址参数, 83 | 我这里服务器有限,只拿了一台服务器单机部署,集群部署有问题的欢迎提 issue。 84 | 85 | ### 注意 86 | **dht-server 需要公网 IP 才能爬取到 info_hash** 87 | -------------------------------------------------------------------------------- /dodder-common/src/main/java/cc/dodder/common/util/NetworkUtil.java: -------------------------------------------------------------------------------- 1 | package cc.dodder.common.util; 2 | 3 | import java.net.InetAddress; 4 | import java.net.NetworkInterface; 5 | import java.net.SocketException; 6 | import java.util.Enumeration; 7 | 8 | /** 9 | * Network Utility class for the network card on a gumstix 10 | */ 11 | public final class NetworkUtil { 12 | /** 13 | * The current host IP address is the IP address from the device. 14 | */ 15 | private static String currentHostIpAddress; 16 | 17 | /** 18 | * @return the current environment's IP address, taking into account the Internet connection to any of the available 19 | * machine's Network interfaces. Examples of the outputs can be in octats or in IPV6 format. 20 | *
    21 |      *         ==> wlan0
    22 |      *
    23 |      *         fec0:0:0:9:213:e8ff:fef1:b717%4
    24 |      *         siteLocal: true
    25 |      *         isLoopback: false isIPV6: true
    26 |      *         130.212.150.216 <<<<<<<<<<<------------- This is the one we want to grab so that we can.
    27 |      *         siteLocal: false                          address the DSP on the network.
    28 |      *         isLoopback: false
    29 |      *         isIPV6: false
    30 |      *
    31 |      *         ==> lo
    32 |      *         0:0:0:0:0:0:0:1%1
    33 |      *         siteLocal: false
    34 |      *         isLoopback: true
    35 |      *         isIPV6: true
    36 |      *         127.0.0.1
    37 |      *         siteLocal: false
    38 |      *         isLoopback: true
    39 |      *         isIPV6: false
    40 |      *  
    41 | */ 42 | public static String getCurrentEnvironmentNetworkIp() { 43 | if (currentHostIpAddress == null) { 44 | Enumeration netInterfaces = null; 45 | try { 46 | netInterfaces = NetworkInterface.getNetworkInterfaces(); 47 | 48 | while (netInterfaces.hasMoreElements()) { 49 | NetworkInterface ni = netInterfaces.nextElement(); 50 | Enumeration address = ni.getInetAddresses(); 51 | while (address.hasMoreElements()) { 52 | InetAddress addr = address.nextElement(); 53 | if (!addr.isLoopbackAddress() && !addr.isSiteLocalAddress() 54 | && !(addr.getHostAddress().indexOf(":") > -1)) { 55 | currentHostIpAddress = addr.getHostAddress(); 56 | } 57 | } 58 | } 59 | if (currentHostIpAddress == null) { 60 | currentHostIpAddress = "127.0.0.1"; 61 | } 62 | 63 | } catch (SocketException e) { 64 | currentHostIpAddress = "127.0.0.1"; 65 | } 66 | } 67 | return currentHostIpAddress; 68 | } 69 | 70 | public static long getIp() { 71 | return ipToLong(getCurrentEnvironmentNetworkIp()); 72 | } 73 | 74 | 75 | public static long ipToLong(String ipAddress) { 76 | 77 | long result = 0; 78 | 79 | String[] ipAddressInArray = ipAddress.split("\\."); 80 | 81 | for (int i = 3; i >= 0; i--) { 82 | 83 | long ip = Long.parseLong(ipAddressInArray[3 - i]); 84 | result |= ip << (i * 8); 85 | 86 | } 87 | return result; 88 | } 89 | 90 | } -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/lay/modules/util.js: -------------------------------------------------------------------------------- 1 | /** layui-v2.4.5 MIT License By https://www.layui.com */ 2 | ;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)}); -------------------------------------------------------------------------------- /dodder-common/src/main/java/cc/dodder/common/entity/Tree.java: -------------------------------------------------------------------------------- 1 | package cc.dodder.common.entity; 2 | 3 | 4 | import cc.dodder.common.util.FileTypeUtil; 5 | import cc.dodder.common.util.JSONUtil; 6 | import cc.dodder.common.util.StringUtil; 7 | import com.fasterxml.jackson.annotation.JsonIgnore; 8 | import com.fasterxml.jackson.annotation.JsonInclude; 9 | import org.springframework.data.annotation.Transient; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | @JsonInclude(JsonInclude.Include.NON_NULL) 15 | public class Tree { 16 | 17 | private Node root; 18 | 19 | @JsonIgnore 20 | private List leaves; 21 | 22 | public Tree() { 23 | super(); 24 | } 25 | 26 | public Tree(String text) { 27 | root = new Node(null, null, text, null, null); 28 | } 29 | 30 | public void createTree(List nodes) { 31 | for (Node node : nodes) { 32 | //父亲是根节点,直接添加到根节点下面 33 | if (node.getPid() == root.getNid()) { 34 | root.addChild(node); 35 | } else { //父亲是其他节点 36 | Node parent = findParent(root, node.getPid()); 37 | if (parent != null) { 38 | parent.addChild(node); 39 | } 40 | } 41 | } 42 | } 43 | 44 | private Node findParent(Node node, int pid) { 45 | Node result = null; 46 | for (Node n : node.getChildren()) { 47 | if (n.getNid() == null) return root; 48 | if (n.getNid() == pid) { 49 | return n; 50 | } else { 51 | //递归搜索 52 | if (n.getChildren() != null) 53 | result = findParent(n, pid); 54 | } 55 | } 56 | return result; 57 | } 58 | 59 | public void middlePrint(Node tnode) { 60 | if (tnode.getChildren() == null) { 61 | return; 62 | } 63 | for (Node node : tnode.getChildren()) { 64 | System.out.println(node.getFilename()); 65 | middlePrint(node); 66 | } 67 | } 68 | 69 | /** 70 | * 构建叶子节点数组,实际上就是构建子文件列表 71 | * @return 72 | */ 73 | @JsonIgnore 74 | public List getLeafList() { 75 | leaves = new ArrayList<>(); 76 | deep(root); 77 | return leaves; 78 | } 79 | 80 | private void deep(Node tnode) { 81 | if (tnode.getChildren() == null) { //叶子节点 82 | if (leaves.size() < 3) 83 | leaves.add(tnode); 84 | return; 85 | } 86 | if (leaves.size() >= 3) 87 | return; 88 | for (Node node : tnode.getChildren()) { 89 | deep(node); 90 | } 91 | } 92 | 93 | public String getHtml(Node tnode) { 94 | 95 | if (tnode.getChildren() == null) { //叶子节点 96 | return "
  • " + tnode.getFilename() 97 | + ((tnode.getFilesize() != null) ? "(" + StringUtil.formatSize(tnode.getFilesize()) + ")" + "" : "") 98 | + "
  • "; 99 | } 100 | 101 | String str = ""; 102 | if (tnode.getNid() == root.getNid()) { //根节点 103 | str += "

      " + root.getFilename() + "

      "; 104 | } else { //子节点 105 | str += "
    • " + tnode.getFilename() + "
        "; 106 | } 107 | 108 | for (Node node : tnode.getChildren()) { 109 | str += getHtml(node); 110 | } 111 | 112 | if (tnode == root) { //根节点 113 | return str += "
      "; 114 | } else { //子节点 115 | return str += "
    "; 116 | } 117 | } 118 | 119 | public boolean checkExist(Node tnode, String text) { 120 | boolean exist = false; 121 | if (tnode.getChildren() == null) { 122 | return false; 123 | } 124 | for (Node node : tnode.getChildren()) { 125 | exist |= checkExist(node, text); 126 | } 127 | return exist; 128 | } 129 | 130 | public Node getRoot() { 131 | return root; 132 | } 133 | 134 | public void setRoot(Node root) { 135 | this.root = root; 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/lay/modules/carousel.js: -------------------------------------------------------------------------------- 1 | /** layui-v2.4.5 MIT License By https://www.layui.com */ 2 | ;layui.define("jquery",function(e){"use strict";var i=layui.$,n=(layui.hint(),layui.device(),{config:{},set:function(e){var n=this;return n.config=i.extend({},n.config,e),n},on:function(e,i){return layui.onevent.call(this,t,e,i)}}),t="carousel",a="layui-this",l=">*[carousel-item]>*",o="layui-carousel-left",r="layui-carousel-right",d="layui-carousel-prev",s="layui-carousel-next",u="layui-carousel-arrow",c="layui-carousel-ind",m=function(e){var t=this;t.config=i.extend({},t.config,n.config,e),t.render()};m.prototype.config={width:"600px",height:"280px",full:!1,arrow:"hover",indicator:"inside",autoplay:!0,interval:3e3,anim:"",trigger:"click",index:0},m.prototype.render=function(){var e=this,n=e.config;n.elem=i(n.elem),n.elem[0]&&(e.elemItem=n.elem.find(l),n.index<0&&(n.index=0),n.index>=e.elemItem.length&&(n.index=e.elemItem.length-1),n.interval<800&&(n.interval=800),n.full?n.elem.css({position:"fixed",width:"100%",height:"100%",zIndex:9999}):n.elem.css({width:n.width,height:n.height}),n.elem.attr("lay-anim",n.anim),e.elemItem.eq(n.index).addClass(a),e.elemItem.length<=1||(e.indicator(),e.arrow(),e.autoplay(),e.events()))},m.prototype.reload=function(e){var n=this;clearInterval(n.timer),n.config=i.extend({},n.config,e),n.render()},m.prototype.prevIndex=function(){var e=this,i=e.config,n=i.index-1;return n<0&&(n=e.elemItem.length-1),n},m.prototype.nextIndex=function(){var e=this,i=e.config,n=i.index+1;return n>=e.elemItem.length&&(n=0),n},m.prototype.addIndex=function(e){var i=this,n=i.config;e=e||1,n.index=n.index+e,n.index>=i.elemItem.length&&(n.index=0)},m.prototype.subIndex=function(e){var i=this,n=i.config;e=e||1,n.index=n.index-e,n.index<0&&(n.index=i.elemItem.length-1)},m.prototype.autoplay=function(){var e=this,i=e.config;i.autoplay&&(e.timer=setInterval(function(){e.slide()},i.interval))},m.prototype.arrow=function(){var e=this,n=e.config,t=i(['",'"].join(""));n.elem.attr("lay-arrow",n.arrow),n.elem.find("."+u)[0]&&n.elem.find("."+u).remove(),n.elem.append(t),t.on("click",function(){var n=i(this),t=n.attr("lay-type");e.slide(t)})},m.prototype.indicator=function(){var e=this,n=e.config,t=e.elemInd=i(['
      ',function(){var i=[];return layui.each(e.elemItem,function(e){i.push("")}),i.join("")}(),"
    "].join(""));n.elem.attr("lay-indicator",n.indicator),n.elem.find("."+c)[0]&&n.elem.find("."+c).remove(),n.elem.append(t),"updown"===n.anim&&t.css("margin-top",-(t.height()/2)),t.find("li").on("hover"===n.trigger?"mouseover":n.trigger,function(){var t=i(this),a=t.index();a>n.index?e.slide("add",a-n.index):a 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
    16 |
    17 |
    
    18 |     
    19 |
    20 |
    21 |
    22 | 23 |   24 |
    25 |
    26 |
      27 |
    • 发现时间:[[${#calendars.format(torrent.createDate,'yyyy年MM月dd日 HH时mm分ss秒')}]]
    • 28 |
    • 文件大小:[[${#dodderUtil.formatSize(torrent.fileSize)}]]
    • 29 |
    • 文件类型: 30 |
        31 | 32 |
      33 |
    • 34 |
    35 | 38 |
    39 | 敏感词汇已用“*”代替 40 |
    41 |
    相关推荐
    42 |
      43 |
    • 44 | 45 |
      46 | 48 | 54 |
      55 |
    • 56 |
    57 |
    58 |
    59 |
    60 |
    61 |
    62 |
    63 |
    64 |
    65 |
    66 | 67 | 68 | 69 | 72 | 73 | -------------------------------------------------------------------------------- /dodder-web/src/main/resources/static/layui/lay/modules/laypage.js: -------------------------------------------------------------------------------- 1 | /** layui-v2.4.5 MIT License By https://www.layui.com */ 2 | ;layui.define(function(e){"use strict";var a=document,t="getElementById",n="getElementsByTagName",i="laypage",r="layui-disabled",u=function(e){var a=this;a.config=e||{},a.config.index=++s.index,a.render(!0)};u.prototype.type=function(){var e=this.config;if("object"==typeof e.elem)return void 0===e.elem.length?2:3},u.prototype.view=function(){var e=this,a=e.config,t=a.groups="groups"in a?0|a.groups:5;a.layout="object"==typeof a.layout?a.layout:["prev","page","next"],a.count=0|a.count,a.curr=0|a.curr||1,a.limits="object"==typeof a.limits?a.limits:[10,20,30,40,50],a.limit=0|a.limit||10,a.pages=Math.ceil(a.count/a.limit)||1,a.curr>a.pages&&(a.curr=a.pages),t<0?t=1:t>a.pages&&(t=a.pages),a.prev="prev"in a?a.prev:"上一页",a.next="next"in a?a.next:"下一页";var n=a.pages>t?Math.ceil((a.curr+(t>1?1:0))/(t>0?t:1)):1,i={prev:function(){return a.prev?''+a.prev+"":""}(),page:function(){var e=[];if(a.count<1)return"";n>1&&a.first!==!1&&0!==t&&e.push(''+(a.first||1)+"");var i=Math.floor((t-1)/2),r=n>1?a.curr-i:1,u=n>1?function(){var e=a.curr+(t-i-1);return e>a.pages?a.pages:e}():t;for(u-r2&&e.push('');r<=u;r++)r===a.curr?e.push('"+r+""):e.push(''+r+"");return a.pages>t&&a.pages>u&&a.last!==!1&&(u+1…'),0!==t&&e.push(''+(a.last||a.pages)+"")),e.join("")}(),next:function(){return a.next?''+a.next+"":""}(),count:'共 '+a.count+" 条",limit:function(){var e=['"}(),refresh:['','',""].join(""),skip:function(){return['到第','','页',""].join("")}()};return['
    ',function(){var e=[];return layui.each(a.layout,function(a,t){i[t]&&e.push(i[t])}),e.join("")}(),"
    "].join("")},u.prototype.jump=function(e,a){if(e){var t=this,i=t.config,r=e.children,u=e[n]("button")[0],l=e[n]("input")[0],p=e[n]("select")[0],c=function(){var e=0|l.value.replace(/\s|\D/g,"");e&&(i.curr=e,t.render())};if(a)return c();for(var o=0,y=r.length;oi.pages||(i.curr=e,t.render())});p&&s.on(p,"change",function(){var e=this.value;i.curr*e>i.count&&(i.curr=Math.ceil(i.count/e)),i.limit=e,t.render()}),u&&s.on(u,"click",function(){c()})}},u.prototype.skip=function(e){if(e){var a=this,t=e[n]("input")[0];t&&s.on(t,"keyup",function(t){var n=this.value,i=t.keyCode;/^(37|38|39|40)$/.test(i)||(/\D/.test(n)&&(this.value=n.replace(/\D/,"")),13===i&&a.jump(e,!0))})}},u.prototype.render=function(e){var n=this,i=n.config,r=n.type(),u=n.view();2===r?i.elem&&(i.elem.innerHTML=u):3===r?i.elem.html(u):a[t](i.elem)&&(a[t](i.elem).innerHTML=u),i.jump&&i.jump(i,e);var s=a[t]("layui-laypage-"+i.index);n.jump(s),i.hash&&!e&&(location.hash="!"+i.hash+"="+i.curr),n.skip(s)};var s={render:function(e){var a=new u(e);return a.index},index:layui.laypage?layui.laypage.index+1e4:0,on:function(e,a,t){return e.attachEvent?e.attachEvent("on"+a,function(a){a.target=a.srcElement,t.call(e,a)}):e.addEventListener(a,t,!1),this}};e(i,s)}); -------------------------------------------------------------------------------- /dodder-web/src/main/resources/templates/error/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 菟丝子资源社区 11 | 12 | 13 |
    14 |
    15 |
    
    16 |     
    17 |
    18 |
    资源列表
    19 |
    20 | 分类: 21 | 22 | 全部 24 | 音频 26 | 视频 28 | 图片 30 | 文档 32 | 压缩文件 34 | 镜像文件 36 | 软件 38 | 39 | 40 | 全部 41 | 音频 42 | 视频 43 | 图片 44 | 文档 45 | 压缩文件 46 | 镜像文件 47 | 软件 48 | 49 |
    50 |
    51 | _  _      ___    _  _
    52 | | || |    / _ \  | || |
    53 | | || |_  | | | | | || |_
    54 | |__   _| | |_| | |__   _|
    55 | |_|    \___/     |_|
    56 | 
    57 |   页面消失在了异次元空间!
    58 |             
    59 |
    60 |
    61 |
    62 |
    63 |
    64 |
    65 |
    66 |
    67 |
    68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /words/src/main/java/toolgood/words/internals/BaseSearch.java: -------------------------------------------------------------------------------- 1 | package toolgood.words.internals; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Hashtable; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | public class BaseSearch { 9 | protected TrieNode2[] _first = new TrieNode2[Character.MAX_VALUE + 1]; 10 | protected String[] _keywords; 11 | 12 | 13 | /** 14 | * 设置关键字 15 | * 16 | * @param keywords 关键字列表 17 | */ 18 | public void SetKeywords(List keywords) { 19 | _keywords = new String[keywords.size()]; 20 | _keywords = keywords.toArray(_keywords); 21 | SetKeywords(); 22 | } 23 | 24 | protected void SetKeywords() { 25 | TrieNode root = new TrieNode(); 26 | Map> allNodeLayers = new Hashtable>(); 27 | for (int i = 0; i < _keywords.length; i++) { 28 | String p = _keywords[i]; 29 | TrieNode nd = root; 30 | for (int j = 0; j < p.length(); j++) { 31 | nd = nd.Add(p.charAt(j)); 32 | if (nd.Layer == 0) { 33 | nd.Layer = j + 1; 34 | if (allNodeLayers.containsKey(nd.Layer) == false) { 35 | List nodes = new ArrayList(); 36 | nodes.add(nd); 37 | allNodeLayers.put(nd.Layer, nodes); 38 | } else { 39 | allNodeLayers.get(nd.Layer).add(nd); 40 | } 41 | } 42 | } 43 | nd.SetResults(i); 44 | } 45 | 46 | List allNode = new ArrayList(); 47 | allNode.add(root); 48 | for (int i = 0; i < allNodeLayers.size(); i++) { // 注意 这里不能用 keySet() 49 | List nodes = allNodeLayers.get(i + 1); 50 | for (int j = 0; j < nodes.size(); j++) { 51 | allNode.add(nodes.get(j)); 52 | } 53 | } 54 | allNodeLayers.clear(); 55 | allNodeLayers = null; 56 | 57 | for (int i = 1; i < allNode.size(); i++) { 58 | TrieNode nd = allNode.get(i); 59 | nd.Index = i; 60 | TrieNode r = nd.Parent.Failure; 61 | Character c = nd.Char; 62 | while (r != null && !r.m_values.containsKey(c)) 63 | r = r.Failure; 64 | if (r == null) 65 | nd.Failure = root; 66 | else { 67 | nd.Failure = r.m_values.get(c); 68 | for (Integer result : nd.Failure.Results) { 69 | nd.SetResults(result); 70 | } 71 | } 72 | } 73 | root.Failure = root; 74 | 75 | List allNode2 = new ArrayList(); 76 | for (int i = 0; i < allNode.size(); i++) { 77 | allNode2.add(new TrieNode2()); 78 | } 79 | for (int i = 0; i < allNode2.size(); i++) { 80 | TrieNode oldNode = allNode.get(i); 81 | TrieNode2 newNode = allNode2.get(i); 82 | 83 | for (Character key : oldNode.m_values.keySet()) { 84 | TrieNode nd = oldNode.m_values.get(key); 85 | newNode.Add(key, allNode2.get(nd.Index)); 86 | } 87 | oldNode.Results.forEach(item -> { 88 | newNode.SetResults(item); 89 | }); 90 | 91 | oldNode = oldNode.Failure; 92 | while (oldNode != root) { 93 | for (Character key : oldNode.m_values.keySet()) { 94 | TrieNode nd = oldNode.m_values.get(key); 95 | if (newNode.HasKey(key) == false) { 96 | newNode.Add(key, allNode2.get(nd.Index)); 97 | } 98 | } 99 | oldNode.Results.forEach(item -> { 100 | newNode.SetResults(item); 101 | }); 102 | oldNode = oldNode.Failure; 103 | } 104 | } 105 | allNode.clear(); 106 | allNode = null; 107 | root = null; 108 | 109 | TrieNode2[] first = new TrieNode2[Character.MAX_VALUE + 1]; 110 | TrieNode2 root2 = allNode2.get(0); 111 | for (Character key : root2.m_values.keySet()) { 112 | TrieNode2 nd = root2.m_values.get(key); 113 | first[(int) key] = nd; 114 | } 115 | _first = first; 116 | } 117 | 118 | 119 | } -------------------------------------------------------------------------------- /words/src/main/java/toolgood/words/internals/TrieNodeEx.java: -------------------------------------------------------------------------------- 1 | package toolgood.words.internals; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Hashtable; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | public class TrieNodeEx { 9 | public Integer Char; 10 | public boolean End; 11 | public Integer Index; 12 | public List Results; 13 | public Map m_values; 14 | private Integer minflag = Integer.MAX_VALUE; 15 | private Integer maxflag = 0; 16 | public int Next; 17 | 18 | public TrieNodeEx() { 19 | m_values = new Hashtable(); 20 | Results = new ArrayList(); 21 | } 22 | 23 | public void Add(int c, TrieNodeEx node3) { 24 | if (minflag > c) { 25 | minflag = c; 26 | } 27 | if (maxflag < c) { 28 | maxflag = c; 29 | } 30 | m_values.put(c, node3); 31 | } 32 | 33 | public void SetResults(Integer text) { 34 | if (End == false) { 35 | End = true; 36 | } 37 | if (Results.contains(text) == false) { 38 | Results.add(text); 39 | } 40 | } 41 | 42 | public boolean HasKey(Integer c) { 43 | if (minflag <= c && maxflag >= c) { 44 | return m_values.containsKey((int) c); 45 | } 46 | return false; 47 | } 48 | 49 | public void Rank(Integer oneStart, Integer start, boolean[] seats, boolean[] seats2, Integer[] has) { 50 | if (maxflag == 0) 51 | return; 52 | if (minflag == maxflag) { 53 | RankOne(oneStart, seats, has); 54 | return; 55 | } 56 | List keys = new ArrayList(); 57 | m_values.forEach((k, v) -> { 58 | keys.add((int) k); 59 | }); 60 | 61 | Integer length = keys.size() - 1; 62 | int[] moves = new int[keys.size() - 1]; 63 | for (int i = 1; i < keys.size(); i++) { 64 | moves[i - 1] = maxflag - keys.get(i); 65 | } 66 | 67 | while (has[start] != null) { 68 | start++; 69 | } 70 | Integer s = start < minflag ? minflag : start; 71 | 72 | for (int i = s; i < s + (maxflag - minflag); i++) { 73 | if (has[i] != null) { 74 | for (int j = 0; j < length; j++) { 75 | Integer p = i + moves[j]; 76 | if (seats2[p] == false) { 77 | seats2[p] = true; 78 | } 79 | } 80 | } 81 | } 82 | Integer max = 0; 83 | for (int i = s + (maxflag - minflag); i < has.length; i++) { 84 | if (has[i] == null) { 85 | if (seats2[i]) { 86 | continue; 87 | } 88 | Integer next = i - (Integer) maxflag; 89 | if (seats[next]) 90 | continue; 91 | SetSeats(next, seats, has); 92 | max = i; 93 | break; 94 | } else { 95 | for (int j = 0; j < length; j++) { 96 | Integer p = i + moves[j]; 97 | if (seats2[p] == false) { 98 | seats2[p] = true; 99 | } 100 | } 101 | } 102 | } 103 | start += keys.size() / 2; 104 | for (int p = start; p < max + maxflag - start + 1; p++) { 105 | if (seats2[p] == true) { 106 | seats2[p] = false; 107 | } 108 | } 109 | } 110 | 111 | private void RankOne(Integer start, boolean[] seats, Integer[] has) { 112 | while (has[start] != null) { 113 | start++; 114 | } 115 | Integer s = start < minflag ? minflag : start; 116 | 117 | for (Integer i = s; i < has.length; i++) { 118 | if (has[i] == null) { 119 | Integer next = i - (Integer) minflag; 120 | if (seats[next]) 121 | continue; 122 | SetSeats(next, seats, has); 123 | break; 124 | } 125 | } 126 | start++; 127 | } 128 | 129 | 130 | private void SetSeats(Integer next, boolean[] seats, Integer[] has) { 131 | Next = next; 132 | seats[next] = true; 133 | 134 | m_values.forEach((key, value) -> { 135 | int position = next + key; 136 | has[position] = value.Index; 137 | }); 138 | } 139 | 140 | } -------------------------------------------------------------------------------- /words/src/main/java/toolgood/words/StringSearch.java: -------------------------------------------------------------------------------- 1 | package toolgood.words; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import toolgood.words.internals.TrieNode2; 7 | import toolgood.words.internals.BaseSearch; 8 | 9 | public class StringSearch extends BaseSearch { 10 | /** 11 | * 在文本中查找第一个关键字 12 | * 13 | * @param text 文本 14 | * @return 15 | */ 16 | public String FindFirst(final String text) { 17 | TrieNode2 ptr = null; 18 | for (int i = 0; i < text.length(); i++) { 19 | final char t = text.charAt(i); 20 | TrieNode2 tn = null; 21 | if (ptr == null) { 22 | tn = _first[t]; 23 | } else { 24 | if (ptr.HasKey(t) == false) { 25 | tn = _first[t]; 26 | } else { 27 | tn = ptr.GetValue(t); 28 | } 29 | } 30 | if (tn != null) { 31 | if (tn.End) { 32 | return _keywords[tn.Results.get(0)]; 33 | } 34 | } 35 | ptr = tn; 36 | } 37 | return null; 38 | } 39 | 40 | /** 41 | * 在文本中查找所有的关键字 42 | * 43 | * @param text 文本 44 | * @return 45 | */ 46 | public List FindAll(final String text) { 47 | TrieNode2 ptr = null; 48 | final List list = new ArrayList(); 49 | 50 | for (int i = 0; i < text.length(); i++) { 51 | final char t = text.charAt(i); 52 | TrieNode2 tn = null; 53 | if (ptr == null) { 54 | tn = _first[t]; 55 | } else { 56 | if (ptr.HasKey(t) == false) { 57 | tn = _first[t]; 58 | } else { 59 | tn = ptr.GetValue(t); 60 | } 61 | } 62 | if (tn != null) { 63 | if (tn.End) { 64 | tn.Results.forEach(item -> { 65 | list.add(_keywords[item]); 66 | }); 67 | } 68 | } 69 | ptr = tn; 70 | } 71 | return list; 72 | } 73 | 74 | /** 75 | * 判断文本是否包含关键字 76 | * 77 | * @param text 文本 78 | * @return 79 | */ 80 | public boolean ContainsAny(final String text) { 81 | TrieNode2 ptr = null; 82 | for (int i = 0; i < text.length(); i++) { 83 | final char t = text.charAt(i); 84 | TrieNode2 tn = null; 85 | if (ptr == null) { 86 | tn = _first[t]; 87 | } else { 88 | if (ptr.HasKey(t) == false) { 89 | tn = _first[t]; 90 | } else { 91 | tn = ptr.GetValue(t); 92 | } 93 | } 94 | if (tn != null) { 95 | if (tn.End) { 96 | return true; 97 | } 98 | } 99 | ptr = tn; 100 | } 101 | return false; 102 | } 103 | 104 | /** 105 | * 在文本中替换所有的关键字, 替换符默认为 * 106 | * 107 | * @param text 文本 108 | * @return 109 | */ 110 | public String Replace(final String text) { 111 | return Replace(text, '*'); 112 | } 113 | 114 | /** 115 | * 在文本中替换所有的关键字 116 | * 117 | * @param text 文本 118 | * @param replaceChar 替换符 119 | * @return 120 | */ 121 | public String Replace(final String text, final char replaceChar) { 122 | final StringBuilder result = new StringBuilder(text); 123 | 124 | TrieNode2 ptr = null; 125 | for (int i = 0; i < text.length(); i++) { 126 | final char t = text.charAt(i); 127 | TrieNode2 tn = null; 128 | if (ptr == null) { 129 | tn = _first[t]; 130 | } else { 131 | if (ptr.HasKey(t) == false) { 132 | tn = _first[t]; 133 | } else { 134 | tn = ptr.GetValue(t); 135 | } 136 | } 137 | if (tn != null) { 138 | if (tn.End) { 139 | final int maxLength = _keywords[tn.Results.get(0)].length(); 140 | final int start = i + 1 - maxLength; 141 | for (int j = start; j <= i; j++) { 142 | result.setCharAt(j, replaceChar); 143 | } 144 | } 145 | } 146 | ptr = tn; 147 | } 148 | return result.toString(); 149 | } 150 | 151 | } 152 | -------------------------------------------------------------------------------- /words/src/main/java/toolgood/words/WordsSearch.java: -------------------------------------------------------------------------------- 1 | package toolgood.words; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import toolgood.words.internals.BaseSearch; 7 | import toolgood.words.internals.TrieNode2; 8 | 9 | public class WordsSearch extends BaseSearch { 10 | public String[] _others; 11 | 12 | /** 13 | * 在文本中查找第一个关键字 14 | * 15 | * @param text 文本 16 | * @return 17 | */ 18 | public WordsSearchResult FindFirst(final String text) { 19 | TrieNode2 ptr = null; 20 | for (int i = 0; i < text.length(); i++) { 21 | final char t = text.charAt(i); 22 | TrieNode2 tn = null; 23 | if (ptr == null) { 24 | tn = _first[t]; 25 | } else { 26 | if (ptr.HasKey(t) == false) { 27 | tn = _first[t]; 28 | } else { 29 | tn = ptr.GetValue(t); 30 | } 31 | } 32 | if (tn != null) { 33 | if (tn.End) { 34 | for (final Integer index : tn.Results) { 35 | final String key = _keywords[index]; 36 | return new WordsSearchResult(key, i + 1 - key.length(), i, index); 37 | } 38 | } 39 | } 40 | ptr = tn; 41 | } 42 | return null; 43 | } 44 | 45 | /** 46 | * 在文本中查找所有的关键字 47 | * 48 | * @param text 文本 49 | * @return 50 | */ 51 | public List FindAll(final String text) { 52 | TrieNode2 ptr = null; 53 | final List list = new ArrayList(); 54 | 55 | for (int i = 0; i < text.length(); i++) { 56 | final char t = text.charAt(i); 57 | TrieNode2 tn = null; 58 | if (ptr == null) { 59 | tn = _first[t]; 60 | } else { 61 | if (ptr.HasKey(t) == false) { 62 | tn = _first[t]; 63 | } else { 64 | tn = ptr.GetValue(t); 65 | } 66 | } 67 | if (tn != null) { 68 | if (tn.End) { 69 | for (final Integer index : tn.Results) { 70 | final String key = _keywords[index]; 71 | final WordsSearchResult item = new WordsSearchResult(key, i + 1 - key.length(), i, index); 72 | list.add(item); 73 | } 74 | } 75 | } 76 | ptr = tn; 77 | } 78 | return list; 79 | } 80 | 81 | /** 82 | * 判断文本是否包含关键字 83 | * 84 | * @param text 文本 85 | * @return 86 | */ 87 | public boolean ContainsAny(final String text) { 88 | TrieNode2 ptr = null; 89 | for (int i = 0; i < text.length(); i++) { 90 | final char t = text.charAt(i); 91 | TrieNode2 tn = null; 92 | if (ptr == null) { 93 | tn = _first[t]; 94 | } else { 95 | if (ptr.HasKey(t) == false) { 96 | tn = _first[t]; 97 | } else { 98 | tn = ptr.GetValue(t); 99 | } 100 | } 101 | if (tn != null) { 102 | if (tn.End) { 103 | return true; 104 | } 105 | } 106 | ptr = tn; 107 | } 108 | return false; 109 | } 110 | 111 | /** 112 | * 在文本中替换所有的关键字, 替换符默认为 * 113 | * 114 | * @param text 文本 115 | * @return 116 | */ 117 | public String Replace(final String text) { 118 | return Replace(text, '*'); 119 | } 120 | 121 | /** 122 | * 在文本中替换所有的关键字 123 | * 124 | * @param text 文本 125 | * @param replaceChar 替换符 126 | * @return 127 | */ 128 | public String Replace(final String text, final char replaceChar) { 129 | final StringBuilder result = new StringBuilder(text); 130 | 131 | TrieNode2 ptr = null; 132 | for (int i = 0; i < text.length(); i++) { 133 | final char t = text.charAt(i); 134 | TrieNode2 tn = null; 135 | if (ptr == null) { 136 | tn = _first[t]; 137 | } else { 138 | if (ptr.HasKey(t) == false) { 139 | tn = _first[t]; 140 | } else { 141 | tn = ptr.GetValue(t); 142 | } 143 | } 144 | if (tn != null) { 145 | if (tn.End) { 146 | final int maxLength = _keywords[tn.Results.get(0)].length(); 147 | final int start = i + 1 - maxLength; 148 | for (int j = start; j <= i; j++) { 149 | result.setCharAt(j, replaceChar); 150 | } 151 | } 152 | } 153 | ptr = tn; 154 | } 155 | return result.toString(); 156 | } 157 | 158 | } -------------------------------------------------------------------------------- /words/src/main/java/toolgood/words/StringSearchEx2.java: -------------------------------------------------------------------------------- 1 | package toolgood.words; 2 | 3 | import toolgood.words.internals.BaseSearchEx2; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | 9 | public class StringSearchEx2 extends BaseSearchEx2 { 10 | 11 | /** 12 | * 在文本中查找所有的关键字 13 | * @param text 文本 14 | * @return 15 | */ 16 | public List FindAll(final String text) { 17 | final List root = new ArrayList(); 18 | int p = 0; 19 | 20 | for (int i = 0; i < text.length(); i++) { 21 | final int t = _dict[text.charAt(i)]; 22 | if (t == 0) { 23 | p = 0; 24 | continue; 25 | } 26 | int next = _next[p] + t; 27 | boolean find = _key[next] == t; 28 | if (find == false && p != 0) { 29 | p = 0; 30 | next = _next[0] + t; 31 | find = _key[next] == t; 32 | } 33 | if (find) { 34 | final int index = _check[next]; 35 | if (index > 0) { 36 | for (final int item : _guides[index]) { 37 | root.add(_keywords[item]); 38 | } 39 | } 40 | p = next; 41 | } 42 | } 43 | return root; 44 | } 45 | 46 | /** 47 | * 在文本中查找第一个关键字 48 | * 49 | * @param text 文本 50 | * @return 51 | */ 52 | public String FindFirst(final String text) { 53 | int p = 0; 54 | for (int i = 0; i < text.length(); i++) { 55 | final int t = _dict[text.charAt(i)]; 56 | if (t == 0) { 57 | p = 0; 58 | continue; 59 | } 60 | int next = _next[p] + t; 61 | if (_key[next] == t) { 62 | final int index = _check[next]; 63 | if (index > 0) { 64 | return _keywords[_guides[index][0]]; 65 | } 66 | p = next; 67 | } else { 68 | p = 0; 69 | next = _next[p] + t; 70 | if (_key[next] == t) { 71 | final int index = _check[next]; 72 | if (index > 0) { 73 | return _keywords[_guides[index][0]]; 74 | } 75 | p = next; 76 | } 77 | } 78 | } 79 | return null; 80 | } 81 | 82 | /** 83 | * 判断文本是否包含关键字 84 | * 85 | * @param text 文本 86 | */ 87 | public boolean ContainsAny(final String text) { 88 | int p = 0; 89 | for (int i = 0; i < text.length(); i++) { 90 | final int t = _dict[text.charAt(i)]; 91 | if (t == 0) { 92 | p = 0; 93 | continue; 94 | } 95 | int next = _next[p] + t; 96 | if (_key[next] == t) { 97 | if (_check[next] > 0) { 98 | return true; 99 | } 100 | p = next; 101 | } else { 102 | p = 0; 103 | next = _next[p] + t; 104 | if (_key[next] == t) { 105 | if (_check[next] > 0) { 106 | return true; 107 | } 108 | p = next; 109 | } 110 | } 111 | } 112 | return false; 113 | } 114 | 115 | /** 116 | * 在文本中替换所有的关键字, 替换符默认为 * 117 | * 118 | * @param text 文本 119 | * @return 120 | */ 121 | public String Replace(final String text) { 122 | return Replace(text, '*'); 123 | } 124 | 125 | /** 126 | * 在文本中替换所有的关键字 127 | * 128 | * @param text 文本 129 | * @param replaceChar 替换符 130 | * @return 131 | */ 132 | public String Replace(final String text, final char replaceChar) { 133 | final StringBuilder result = new StringBuilder(text); 134 | 135 | int p = 0; 136 | 137 | for (int i = 0; i < text.length(); i++) { 138 | final int t = _dict[text.charAt(i)]; 139 | if (t == 0) { 140 | p = 0; 141 | continue; 142 | } 143 | int next = _next[p] + t; 144 | boolean find = _key[next] == t; 145 | if (find == false && p != 0) { 146 | p = 0; 147 | next = _next[p] + t; 148 | find = _key[next] == t; 149 | } 150 | if (find) { 151 | final int index = _check[next]; 152 | if (index > 0) { 153 | final int maxLength = _keywords[_guides[index][0]].length(); 154 | final int start = i + 1 - maxLength; 155 | for (int j = start; j <= i; j++) { 156 | result.setCharAt(j, replaceChar); 157 | } 158 | } 159 | p = next; 160 | } 161 | } 162 | return result.toString(); 163 | } 164 | } -------------------------------------------------------------------------------- /words/src/main/resources/pyName.txt: -------------------------------------------------------------------------------- 1 | 艾,F 2 | 安,1D 3 | 敖,2B 4 | 巴,3B 5 | 白,3F 6 | 柏,43 7 | 班,4F 8 | 包,63 9 | 暴,5F 10 | 鲍,5F 11 | 贝,67 12 | 贲,73 13 | 毕,83 14 | 边,8F 15 | 卞,8B 16 | 别,9D 17 | 邴,AF 18 | 薄,B5 19 | 卜,C3 20 | 步,C1 21 | 蔡,D3 22 | 苍,E9 23 | 曹,ED 24 | 岑,FB 25 | 柴,113 26 | 单于,11D,C7F 27 | 昌,12D 28 | 常,127 29 | 巢,131 30 | 晁,131 31 | 车,13F 32 | 陈,143 33 | 成,14D 34 | 程,14D 35 | 池,157 36 | 充,167 37 | 储,179 38 | 褚,179 39 | 淳于,1AB,C7F 40 | 从,1C3 41 | 崔,1E5 42 | 戴,207 43 | 党,219 44 | 邓,237 45 | 狄,23F 46 | 第五,241,B7D 47 | 刁,259 48 | 丁,269 49 | 东方,275,2ED 50 | 东,275 51 | 董,273 52 | 窦,279 53 | 都,287 54 | 堵,285 55 | 杜,283 56 | 段,28B 57 | 鄂,2AF 58 | 樊,2DD 59 | 范,2DF 60 | 方,2ED 61 | 房,2E7 62 | 费,2F3 63 | 丰,30B 64 | 封,30B 65 | 酆,30B 66 | 冯,305 67 | 凤,307 68 | 伏,31D 69 | 扶,31D 70 | 福,31D 71 | 符,31D 72 | 傅,31F 73 | 富,31F 74 | 干,33D 75 | 甘,33D 76 | 高,34D 77 | 郜,349 78 | 戈,357 79 | 盖,355 80 | 葛,355 81 | 耿,36B 82 | 公孙,375,A6F 83 | 公羊,375,C2B 84 | 公冶,375,C43 85 | 宗政,375,C43 86 | 公,375 87 | 宫,375 88 | 弓,375 89 | 龚,375 90 | 巩,373 91 | 贡,371 92 | 勾,381 93 | 古,389 94 | 谷,389 95 | 顾,387 96 | 关,3A5 97 | 管,3A3 98 | 广,3AB 99 | 桂,3B1 100 | 郭,3C5 101 | 国,3BF 102 | 韩,3DD 103 | 杭,3E7 104 | 郝,3F3 105 | 何,3F9 106 | 和,3F9 107 | 赫连,3FB,5CF 108 | 贺,3FB 109 | 衡,40D 110 | 弘,415 111 | 洪,415 112 | 红,415 113 | 侯,41F 114 | 後,421 115 | 胡,429 116 | 扈,42B 117 | 花,437 118 | 滑,433 119 | 华,435 120 | 怀,43B 121 | 桓,441 122 | 宦,443 123 | 皇甫,44B,321 124 | 黄,44B 125 | 惠,457 126 | 霍,469 127 | 姬,477 128 | 嵇,477 129 | 吉,471 130 | 汲,471 131 | 纪,475 132 | 冀,473 133 | 季,473 134 | 暨,473 135 | 蓟,473 136 | 计,473 137 | 家,481 138 | 郏,47B 139 | 贾,47F 140 | 简,489 141 | 姜,493 142 | 江,493 143 | 蒋,491 144 | 焦,49D 145 | 金,4AF 146 | 靳,4AB 147 | 经,4B7 148 | 荆,4B7 149 | 井,4B5 150 | 景,4B5 151 | 居,4D1 152 | 鞠,4D1 153 | 阚,4FB 154 | 康,509 155 | 柯,51B 156 | 空,531 157 | 孔,52F 158 | 寇,535 159 | 蒯,54F 160 | 匡,55F 161 | 夔,563 162 | 隗,563 163 | 赖,585 164 | 蓝,58B 165 | 郎,593 166 | 劳,59D 167 | 雷,5AD 168 | 冷,5BB 169 | 黎,5C1 170 | 李,5C5 171 | 利,5C3 172 | 厉,5C3 173 | 郦,5C3 174 | 廉,5CF 175 | 连,5CF 176 | 梁,5D7 177 | 廖,5E1 178 | 林,5F3 179 | 蔺,5F5 180 | 令狐,5FD,429 181 | 凌,5FD 182 | 刘,607 183 | 柳,60B 184 | 隆,617 185 | 龙,617 186 | 娄,621 187 | 闾丘,62B,8CD 188 | 卢,62B 189 | 吕,631 190 | 鲁,631 191 | 禄,62D 192 | 路,62D 193 | 逯,62D 194 | 陆,62D 195 | 栾,63D 196 | 罗,651 197 | 骆,653 198 | 麻,661 199 | 马,665 200 | 满,677 201 | 毛,685 202 | 茅,685 203 | 梅,697 204 | 蒙,6A7 205 | 孟,6A9 206 | 糜,6B1 207 | 米,6B5 208 | 宓,6B3 209 | 苗,6C3 210 | 缪,6C5 211 | 闵,6D5 212 | 明,6D9 213 | 万俟,6E7,86F 214 | 莫,6E7 215 | 慕容,6F9,927 216 | 慕,6F9 217 | 牧,6F9 218 | 穆,6F9 219 | 那,705 220 | 能,70B 221 | 倪,747 222 | 乜,769 223 | 聂,769 224 | 宁,777 225 | 牛,77D 226 | 钮,781 227 | 农,787 228 | 欧阳,7C7,C2B 229 | 欧,7C7 230 | 潘,7E3 231 | 庞,7E7 232 | 逄,7E7 233 | 裴,7FB 234 | 彭,80F 235 | 蓬,80F 236 | 皮,819 237 | 平,849 238 | 濮阳,865,C2B 239 | 濮,865 240 | 蒲,865 241 | 浦,869 242 | 戚,875 243 | 祁,86F 244 | 齐,86F 245 | 钱,883 246 | 强,88D 247 | 乔,89B 248 | 秦,8AF 249 | 秋,8CD 250 | 邱,8CD 251 | 仇,8C7 252 | 裘,8C7 253 | 屈,8D7 254 | 麴,8D7 255 | 璩,8D1 256 | 瞿,8D1 257 | 全,8DB 258 | 权,8DB 259 | 阙,8E9 260 | 冉,8F5 261 | 饶,903 262 | 任,913 263 | 容,927 264 | 戎,927 265 | 荣,927 266 | 融,927 267 | 茹,939 268 | 阮,947 269 | 芮,94D 270 | 桑,97B 271 | 沙,99B 272 | 山,9AB 273 | 单,9A7 274 | 上官,9AF,3A5 275 | 尚,9AF 276 | 韶,9B7 277 | 邵,9B9 278 | 厍,9C3 279 | 申屠,9D5,AFD 280 | 申,9D5 281 | 莘,9D5 282 | 沈,9D3 283 | 慎,9D1 284 | 盛,9D7 285 | 师,9E9 286 | 施,9E9 287 | 时,9E3 288 | 石,9E3 289 | 史,9E7 290 | 寿,9F3 291 | 殳,A01 292 | 舒,A01 293 | 束,9FD 294 | 双,A1F 295 | 水,A27 296 | 司空,A3B,531 297 | 司马,A3B,665 298 | 司徒,A3B,AFD 299 | 司,A3B 300 | 松,A45 301 | 宋,A41 302 | 苏,A55 303 | 宿,A53 304 | 孙,A6F 305 | 索,A75 306 | 邰,A83 307 | 太叔,A85,A01 308 | 澹台,A8D,A83 309 | 谈,A8D 310 | 谭,A8D 311 | 汤,A9D 312 | 唐,A97 313 | 陶,AA1 314 | 滕,AAF 315 | 田,AC1 316 | 通,AEF 317 | 佟,AE9 318 | 童,AE9 319 | 钭,AF7 320 | 屠,AFD 321 | 万,B43 322 | 汪,B51 323 | 王,B4B 324 | 危,B5B 325 | 韦,B55 326 | 卫,B57 327 | 蔚,B57 328 | 魏,B57 329 | 温,B65 330 | 闻人,B5F,913 331 | 文,B5F 332 | 闻,B5F 333 | 翁,B6D 334 | 沃,B71 335 | 乌,B7F 336 | 巫,B7F 337 | 邬,B7F 338 | 吴,B79 339 | 毋,B79 340 | 伍,B7D 341 | 武,B7D 342 | 奚,B89 343 | 郗,B89 344 | 习,B83 345 | 席,B83 346 | 郤,B85 347 | 夏侯,B8F,41F 348 | 夏,B8F 349 | 鲜于,B9D,C7F 350 | 咸,B97 351 | 向,BA3 352 | 相,BA3 353 | 项,BA3 354 | 萧,BB1 355 | 解,BB7 356 | 谢,BB7 357 | 辛,BC5 358 | 邢,BC9 359 | 幸,BCB 360 | 熊,BD3 361 | 胥,BED 362 | 须,BED 363 | 徐,BE7 364 | 许,BEB 365 | 轩辕,BF7,C89 366 | 宣,BF7 367 | 薛,C01 368 | 荀,C05 369 | 燕,C27 370 | 严,C21 371 | 言,C21 372 | 阎,C21 373 | 颜,C21 374 | 晏,C23 375 | 杨,C2B 376 | 羊,C2B 377 | 仰,C2F 378 | 养,C2F 379 | 姚,C35 380 | 叶,C41 381 | 伊,C4F 382 | 易,C4B 383 | 益,C4B 384 | 羿,C4B 385 | 殷,C59 386 | 阴,C59 387 | 尹,C57 388 | 印,C55 389 | 应,C63 390 | 雍,C71 391 | 尤,C75 392 | 游,C75 393 | 於,C85 394 | 于,C7F 395 | 余,C7F 396 | 俞,C7F 397 | 虞,C7F 398 | 鱼,C7F 399 | 宇文,C83,B5F 400 | 庾,C83 401 | 禹,C83 402 | 尉迟,C81,157 403 | 喻,C81 404 | 郁,C81 405 | 元,C89 406 | 袁,C89 407 | 乐,C93 408 | 越,C93 409 | 云,C9B 410 | 宰,CAF 411 | 昝,CB9 412 | 臧,CC3 413 | 曾,CE7 414 | 查,CF1 415 | 翟,CF5 416 | 詹,D05 417 | 湛,D01 418 | 张,D0D 419 | 章,D0D 420 | 长孙,D0B,A6F 421 | 赵,D13 422 | 甄,D2D 423 | 郑,D31 424 | 支,D3F 425 | 钟离,D47,5C1 426 | 仲孙,D47,A6F 427 | 终,D47 428 | 钟,D47 429 | 仲,D43 430 | 周,D51 431 | 诸葛,D5B,355 432 | 朱,D5B 433 | 诸,D5B 434 | 竺,D55 435 | 祝,D57 436 | 庄,D79 437 | 卓,D8D 438 | 訾,D99 439 | 宗,DA3 440 | 邹,DAB 441 | 祖,DB3 442 | 左,DD5 --------------------------------------------------------------------------------