├── .gitignore ├── .mvn └── wrapper │ ├── MavenWrapperDownloader.java │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── LICENSE ├── README.md ├── config.json ├── data ├── template │ ├── articleItem.html │ ├── blog.html │ └── index.html └── webroot │ ├── 404.html │ ├── home.css │ ├── images │ └── Yc.jpg │ └── main.css ├── mvnw ├── mvnw.cmd ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── mocyx │ │ └── yinwangblog │ │ ├── BlogException.java │ │ ├── ConfigDto.java │ │ ├── Global.java │ │ ├── Runner.java │ │ ├── Util.java │ │ ├── YinwangblogApplication.java │ │ ├── blog │ │ ├── BlogGenerateService.java │ │ ├── GithubService.java │ │ └── entity │ │ │ ├── LinkDto.java │ │ │ ├── gql │ │ │ └── GqlQuery.java │ │ │ └── issue │ │ │ ├── AuthorDto.java │ │ │ ├── CommentDto.java │ │ │ ├── CommentsDto.java │ │ │ ├── IssueDto.java │ │ │ └── IssuesDto.java │ │ └── http │ │ ├── ActionHandler.java │ │ ├── HttpException.java │ │ └── HttpServer.java └── resources │ └── application.properties └── test └── java └── com └── mocyx └── yinwangblog └── YinwangblogApplicationTests.java /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | config-*.json 3 | webroot/blog-cn 4 | webroot/index.html 5 | # Compiled class file 6 | *.class 7 | data/webroot0 8 | # Log file 9 | *.log 10 | 11 | # BlueJ files 12 | *.ctxt 13 | 14 | # Mobile Tools for Java (J2ME) 15 | .mtj.tmp/ 16 | 17 | # Package Files # 18 | *.jar 19 | *.war 20 | *.nar 21 | *.ear 22 | *.zip 23 | *.tar.gz 24 | *.rar 25 | 26 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 27 | hs_err_pid* 28 | 29 | 30 | HELP.md 31 | target/ 32 | !.mvn/wrapper/maven-wrapper.jar 33 | !**/src/main/** 34 | !**/src/test/** 35 | 36 | ### STS ### 37 | .apt_generated 38 | .classpath 39 | .factorypath 40 | .project 41 | .settings 42 | .springBeans 43 | .sts4-cache 44 | 45 | ### IntelliJ IDEA ### 46 | .idea 47 | *.iws 48 | *.iml 49 | *.ipr 50 | 51 | ### NetBeans ### 52 | /nbproject/private/ 53 | /nbbuild/ 54 | /dist/ 55 | /nbdist/ 56 | /.nb-gradle/ 57 | build/ 58 | 59 | ### VS Code ### 60 | .vscode/ 61 | -------------------------------------------------------------------------------- /.mvn/wrapper/MavenWrapperDownloader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2007-present the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | import java.net.*; 17 | import java.io.*; 18 | import java.nio.channels.*; 19 | import java.util.Properties; 20 | 21 | public class MavenWrapperDownloader { 22 | 23 | private static final String WRAPPER_VERSION = "0.5.6"; 24 | /** 25 | * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. 26 | */ 27 | private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" 28 | + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; 29 | 30 | /** 31 | * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to 32 | * use instead of the default one. 33 | */ 34 | private static final String MAVEN_WRAPPER_PROPERTIES_PATH = 35 | ".mvn/wrapper/maven-wrapper.properties"; 36 | 37 | /** 38 | * Path where the maven-wrapper.jar will be saved to. 39 | */ 40 | private static final String MAVEN_WRAPPER_JAR_PATH = 41 | ".mvn/wrapper/maven-wrapper.jar"; 42 | 43 | /** 44 | * Name of the property which should be used to override the default download url for the wrapper. 45 | */ 46 | private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; 47 | 48 | public static void main(String args[]) { 49 | System.out.println("- Downloader started"); 50 | File baseDirectory = new File(args[0]); 51 | System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); 52 | 53 | // If the maven-wrapper.properties exists, read it and check if it contains a custom 54 | // wrapperUrl parameter. 55 | File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); 56 | String url = DEFAULT_DOWNLOAD_URL; 57 | if(mavenWrapperPropertyFile.exists()) { 58 | FileInputStream mavenWrapperPropertyFileInputStream = null; 59 | try { 60 | mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); 61 | Properties mavenWrapperProperties = new Properties(); 62 | mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); 63 | url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); 64 | } catch (IOException e) { 65 | System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); 66 | } finally { 67 | try { 68 | if(mavenWrapperPropertyFileInputStream != null) { 69 | mavenWrapperPropertyFileInputStream.close(); 70 | } 71 | } catch (IOException e) { 72 | // Ignore ... 73 | } 74 | } 75 | } 76 | System.out.println("- Downloading from: " + url); 77 | 78 | File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); 79 | if(!outputFile.getParentFile().exists()) { 80 | if(!outputFile.getParentFile().mkdirs()) { 81 | System.out.println( 82 | "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); 83 | } 84 | } 85 | System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); 86 | try { 87 | downloadFileFromURL(url, outputFile); 88 | System.out.println("Done"); 89 | System.exit(0); 90 | } catch (Throwable e) { 91 | System.out.println("- Error downloading"); 92 | e.printStackTrace(); 93 | System.exit(1); 94 | } 95 | } 96 | 97 | private static void downloadFileFromURL(String urlString, File destination) throws Exception { 98 | if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { 99 | String username = System.getenv("MVNW_USERNAME"); 100 | char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); 101 | Authenticator.setDefault(new Authenticator() { 102 | @Override 103 | protected PasswordAuthentication getPasswordAuthentication() { 104 | return new PasswordAuthentication(username, password); 105 | } 106 | }); 107 | } 108 | URL website = new URL(urlString); 109 | ReadableByteChannel rbc; 110 | rbc = Channels.newChannel(website.openStream()); 111 | FileOutputStream fos = new FileOutputStream(destination); 112 | fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); 113 | fos.close(); 114 | rbc.close(); 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mightofcode/netty-issue-blog/0299d3d180f3bf4c27c3459b82f874e4aed1ea05/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # yinwangblog 2 | yinwangblog让你拥有一个跟王垠一样的博客 3 | 4 | 基于GitHub issue博文、评论 5 | 使用GitHub api V4读取issue,每分钟进行一次全量更新 6 | 基于netty的静态文件服务器,不存在seo问题,单机QPS1000 7 | 8 | ## 1, 下载release中的发布版本 9 | 解压后目录结构如下: 10 | ├── data 静态文件目录 11 | ├── config.json 配置文件 12 | ├── start.sh 启动脚本 13 | ├── start.bat 启动脚本 14 | ├── yinwangblog-0.jar jar包 15 | 16 | ## 2,配置config.json 17 | 18 | ``` 19 | { 20 | "githubToken": "", //github token,用与访问github api,获取方式:https://github.com/settings/tokens 21 | "serverIp": "0.0.0.0", //服务器ip 22 | "serverPort": 9701, //服务器端口 23 | "siteRoot": "http://127.0.0.1:9701", //http服务器网址 24 | "githubName": "mightofcode", //github name 25 | "githubRepo": "blog", //github 仓库 会从这个仓库的issues读取blog 26 | "blogName": "我没有在扯淡", //博客名 27 | "links": [ //首页右上角的链接 28 | { 29 | "title": "微博", 30 | "href": "https://www.baidu.com/" 31 | }, 32 | { 33 | "title": "付费", 34 | "href": "https://www.baidu.com/" 35 | }, 36 | { 37 | "title": "联系", 38 | "href": "https://www.baidu.com/" 39 | } 40 | ] 41 | } 42 | 43 | ``` 44 | ## 3,启动服务器 45 | ``` 46 | java -jar yinwangblog-0.jar config.json 47 | ``` 48 | 或者直接 ./start.sh 49 | 50 | ## 4,通过浏览器访问博客 51 | 52 | 你可以修改data下面的静态文件来调整网站样式 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "githubToken": "", 3 | "serverIp": "0.0.0.0", 4 | "serverPort": 9701, 5 | "siteRoot": "http://127.0.0.1:9701", 6 | "githubName": "mightofcode", 7 | "githubRepo": "blog", 8 | "blogName": "我没有在扯淡", 9 | "links": [ 10 | { 11 | "title": "微博", 12 | "href": "https://www.baidu.com/" 13 | }, 14 | { 15 | "title": "付费", 16 | "href": "https://www.baidu.com/" 17 | }, 18 | { 19 | "title": "联系", 20 | "href": "https://www.baidu.com/" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /data/template/articleItem.html: -------------------------------------------------------------------------------- 1 |
  • 2 |
    #{date}

    3 | #{title} 4 |
  • -------------------------------------------------------------------------------- /data/template/blog.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | #{title} 9 | 10 | 11 | 12 | 13 | 18 | 19 |
    20 |

    #{title}

    21 | #{article} 22 |
    23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /data/template/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | #{blogTitle} 14 | 15 |    16 | 17 | 23 | 24 | 45 | 46 |
    47 | 50 |
    51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /data/webroot/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Page not found · GitHub Pages 8 | 51 | 52 | 53 | 54 |
    55 | 56 |

    404

    57 |

    File not found

    58 | 59 |

    60 | The site configured at this address does not 61 | contain the requested file. 62 |

    63 | 64 | 65 |
    66 | 67 | 68 | -------------------------------------------------------------------------------- /data/webroot/home.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | /* reset CSS */ 6 | table { 7 | font-size: 100%; 8 | } 9 | /* end reset CSS */ 10 | 11 | body { 12 | font-family: STFangSong, Helvetica, Arial, Vernada, Tahoma, STXihei, "Microsoft YaHei", "Songti SC", SimSun, Heiti, sans-serif; 13 | font-size: 21px; 14 | } 15 | 16 | body.mobile { 17 | font-size: 36px; 18 | } 19 | 20 | .navbar-brand { 21 | font-family: "Palatino Linotype", "Book Antiqua", Palatino, Helvetica, STKaiti, SimSun, serif; 22 | font-size: 100%; 23 | } 24 | 25 | .title a { 26 | display: block; 27 | } 28 | 29 | .title { 30 | cursor: pointer; 31 | padding: 15px 20px; 32 | } 33 | 34 | li.title:hover { 35 | background-color: #eee; 36 | font-size: 100%; 37 | } 38 | 39 | .panel { 40 | border-radius: 0; 41 | font-size: 100%; 42 | } 43 | 44 | .panel-default > .panel-heading { 45 | background-image: none; 46 | background-color: #fff; 47 | font-size: 100%; 48 | } 49 | 50 | .navbar-default { 51 | background-image: none; 52 | background-color: #fff; 53 | box-shadow: none; 54 | font-size: 100%; 55 | } 56 | 57 | .navbar-brand, .navbar-nav { 58 | font-size: 24px; 59 | } 60 | 61 | body.mobile .navbar-brand { 62 | font-size: 36px; 63 | } 64 | 65 | body.mobile .navbar-nav { 66 | font-size: 36px; 67 | } 68 | 69 | div.outer { 70 | margin: 5% 10% 2% 10%; 71 | } 72 | 73 | div.date { 74 | display: inline-block; 75 | font-size: 80%; 76 | color: darkgray; 77 | } 78 | 79 | body.mobile div.outer { 80 | margin: 4% 2% 2% 2%; 81 | } 82 | -------------------------------------------------------------------------------- /data/webroot/images/Yc.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mightofcode/netty-issue-blog/0299d3d180f3bf4c27c3459b82f874e4aed1ea05/data/webroot/images/Yc.jpg -------------------------------------------------------------------------------- /data/webroot/main.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | html { 6 | -webkit-text-size-adjust: none; 7 | } 8 | 9 | /* reset CSS */ 10 | table { 11 | font-size: 100%; 12 | } 13 | /* end reset CSS */ 14 | 15 | body { 16 | font-family:"lucida grande", "lucida sans unicode", lucida, helvetica, "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif; 17 | font-size: 18px; 18 | } 19 | 20 | body.mobile { 21 | font-size: 32px; 22 | } 23 | 24 | P, li { 25 | line-height: 1.8; 26 | } 27 | 28 | H1 { 29 | font-family: "Palatino Linotype", "Book Antiqua", Palatino, Helvetica, STKaiti, SimSun, serif; 30 | } 31 | 32 | H2 { 33 | font-family: "Palatino Linotype", "Book Antiqua", Palatino, Helvetica, STKaiti, SimSun, serif; 34 | margin-bottom: 60px; 35 | margin-bottom: 40px; 36 | padding: 5px; 37 | border-bottom: 2px LightGrey solid; 38 | width: 98%; 39 | line-height: 150%; 40 | color: #666666; 41 | } 42 | 43 | 44 | H3 { 45 | font-family: "Palatino Linotype", "Book Antiqua", Palatino, Helvetica, STKaiti, SimSun, serif; 46 | margin-top: 40px; 47 | margin-bottom: 30px; 48 | border-bottom: 1px LightGrey solid; 49 | width: 98%; 50 | line-height: 150%; 51 | color: #666666; 52 | } 53 | 54 | 55 | H4 { 56 | font-family: "Palatino Linotype", "Book Antiqua", Palatino, Helvetica, STKaiti, SimSun, serif; 57 | margin-top: 40px; 58 | margin-bottom: 30px; 59 | border-bottom: 1px LightGrey solid; 60 | width: 98%; 61 | line-height: 150%; 62 | color: #666666; 63 | } 64 | 65 | .box { 66 | padding: 2% 8% 5% 8%; 67 | border: 1px solid LightGrey; 68 | } 69 | 70 | 71 | li { 72 | margin-left: 10px; 73 | } 74 | 75 | 76 | blockquote { 77 | border-left: 5px lightgrey solid; 78 | padding-left: 15px; 79 | margin-left: 20px; 80 | background: #f4f4f4; 81 | } 82 | 83 | 84 | pre { 85 | font-family: Inconsolata, Consolas, "DEJA VU SANS MONO", "DROID SANS MONO", Proggy, monospace; 86 | font-size: 80%; 87 | background-color: #FAFAFA; 88 | border: 1px solid #E0E0E0; 89 | border-radius: 4px; 90 | padding: 12px; 91 | line-height: 1.5; 92 | display: block; 93 | width: 100%; 94 | overflow: auto; 95 | } 96 | 97 | 98 | code { 99 | font-family: Inconsolata, Consolas, "DEJA VU SANS MONO", "DROID SANS MONO", Proggy, monospace; 100 | } 101 | 102 | 103 | a { 104 | text-decoration: none; 105 | cursor: crosshair; 106 | border-bottom: 1px dashed orange; 107 | color: #930ee4; 108 | } 109 | 110 | 111 | a:hover { 112 | background-color: LightGrey; 113 | } 114 | 115 | 116 | img { 117 | display: block; 118 | box-shadow: 0 0 10px #555; 119 | border-radius: 6px; 120 | margin-left: auto; 121 | margin-right: auto; 122 | margin-top: 10px; 123 | margin-bottom: 10px; 124 | -webkit-box-shadow: 0 0 10px #555; 125 | } 126 | 127 | img.displayed { 128 | text-align: center; 129 | display: block; 130 | } 131 | 132 | hr { 133 | color: LightGrey; 134 | } 135 | 136 | p.notice { 137 | color: #AA4433; 138 | font-size: 14px; 139 | } 140 | 141 | div.tweet p { 142 | font-size: 16px; 143 | border: 1px solid #aaa; 144 | border-left: 10px solid #f28500; 145 | padding: 2px 0.5em 2px 0.5em; 146 | margin-bottom: 20px; 147 | background-color: #fbfbfb; 148 | } 149 | 150 | div.tweet i { 151 | color: brown; 152 | font-size: 14px; 153 | font-style: normal; 154 | border: 1px solid #aaa; 155 | margin-right: 0.5em; 156 | padding: 0px 2px; 157 | } 158 | 159 | div.outer { 160 | margin: 2% 5% 2% 5%; 161 | } 162 | 163 | body.mobile div.outer { 164 | margin: 2% 0% 2% 0%; 165 | } 166 | 167 | div.inner { 168 | margin: 0% 14%; 169 | padding: 2% 8% 4% 8%; 170 | border: 1px solid LightGrey; 171 | } 172 | 173 | body.mobile div.inner { 174 | margin: 0; 175 | padding: 2% 4% 4% 4%; 176 | } 177 | 178 | div.ad-banner { 179 | margin: 0% 14%; 180 | } 181 | 182 | body.mobile div.ad-banner { 183 | display: none; 184 | } 185 | 186 | .side-ad.mobile { 187 | display: none; 188 | } 189 | 190 | .left { 191 | float: left; 192 | clear: both; 193 | width: 50%; 194 | padding: 25 24 25 0; 195 | border-top: 1px solid lightblue; 196 | } 197 | 198 | .right { 199 | float: left; 200 | clear: none; 201 | width: 50%; 202 | padding: 25 0 25 24; 203 | border-top: 1px solid lightblue; 204 | } 205 | 206 | .row:after { 207 | content: ""; 208 | display: table; 209 | clear: both; 210 | } 211 | 212 | .row { 213 | clear: both; 214 | } 215 | 216 | .highlight .hll { background-color: #ffc; } 217 | .highlight .c { color: #888; font-weight: bold; } /* Comment */ 218 | .highlight .err { color: #a00; background-color: #fff } /* Error */ 219 | .highlight .k { color: #cc7832; } /* Keyword */ 220 | .highlight .o { color: #555 } /* Operator */ 221 | .highlight .p { color: #333 } /* Punctuation */ 222 | .highlight .po { color: #333 } /* Parenthesis Outer */ 223 | .highlight .pi { color: #553 } /* Parenthesis Inner */ 224 | .highlight .cm { color: #09f; font-style: italic } /* Comment.Multiline */ 225 | .highlight .cp { color: #099 } /* Comment.Preproc */ 226 | .highlight .c1 { color: #999; } /* Comment.Single */ 227 | .highlight .cs { color: #999; } /* Comment.Special */ 228 | .highlight .gd { background-color: #fcc; border: 1px solid #c00 } /* Generic.Deleted */ 229 | .highlight .ge { font-style: italic } /* Generic.Emph */ 230 | .highlight .gr { color: #f00 } /* Generic.Error */ 231 | .highlight .gh { color: #030; } /* Generic.Heading */ 232 | .highlight .gi { background-color: #cfc; border: 1px solid #0c0 } /* Generic.Inserted */ 233 | .highlight .go { color: #aaa } /* Generic.Output */ 234 | .highlight .gp { color: #009; } /* Generic.Prompt */ 235 | .highlight .gs { } /* Generic.Strong */ 236 | .highlight .gu { color: #030; } /* Generic.Subheading */ 237 | .highlight .gt { color: #9c6 } /* Generic.Traceback */ 238 | .highlight .kc { color: #cc7832; } /* Keyword.Constant */ 239 | .highlight .kd { color: #cc7832; } /* Keyword.Declaration */ 240 | .highlight .kn { color: #cc7832; } /* Keyword.Namespace */ 241 | .highlight .kp { color: #cc7832 } /* Keyword.Pseudo */ 242 | .highlight .kr { color: #cc7832; } /* Keyword.Reserved */ 243 | .highlight .kt { color: #cc7832; } /* Keyword.Type */ 244 | .highlight .m { color: #6897BB } /* Literal.Number */ 245 | .highlight .s { color: #d44950 } /* Literal.String */ 246 | .highlight .s2 { color: #d44950 } /* Literal.String */ 247 | .highlight .s2 { color: #d44950 } /* Literal.String */ 248 | .highlight .na { color: #9876aa } /* Name.Attribute */ 249 | .highlight .nb { color: #366 } /* Name.Builtin */ 250 | .highlight .nc { color: #000; } /* Name.Class */ 251 | .highlight .no { color: #360 } /* Name.Constant */ 252 | .highlight .nd { color: #77c } /* Name.Decorator */ 253 | .highlight .ni { color: #999; } /* Name.Entity */ 254 | .highlight .ne { color: #c00; } /* Name.Exception */ 255 | .highlight .nf { color: #182669 } /* Name.Function */ 256 | .highlight .nl { color: #648 } /* Name.Label */ 257 | .highlight .nn { color: #0cf; } /* Name.Namespace */ 258 | .highlight .nt { color: #2f6f9f; } /* Name.Tag */ 259 | .highlight .nv { color: #033 } /* Name.Variable */ 260 | .highlight .ow { color: #000; } /* Operator.Word */ 261 | .highlight .w { color: #bbb } /* Text.Whitespace */ 262 | .highlight .mf { color: #6897BB } /* Literal.Number.Float */ 263 | .highlight .mh { color: #6897BB } /* Literal.Number.Hex */ 264 | .highlight .mi { color: #6897BB } /* Literal.Number.Integer */ 265 | .highlight .mo { color: #6897BB } /* Literal.Number.Oct */ 266 | .highlight .sb { color: #c30 } /* Literal.String.Backtick */ 267 | .highlight .sc { color: #c30 } /* Literal.String.Char */ 268 | .highlight .sd { color: #c30; font-style: italic } /* Literal.String.Doc */ 269 | .highlight .s2 { color: #c30 } /* Literal.String.Double */ 270 | .highlight .se { color: #c30; } /* Literal.String.Escape */ 271 | .highlight .sh { color: #c30 } /* Literal.String.Heredoc */ 272 | .highlight .si { color: #a00 } /* Literal.String.Interpol */ 273 | .highlight .sx { color: #c30 } /* Literal.String.Other */ 274 | .highlight .sr { color: #3aa } /* Literal.String.Regex */ 275 | .highlight .s1 { color: #c30 } /* Literal.String.Single */ 276 | .highlight .ss { color: #fc3 } /* Literal.String.Symbol */ 277 | .highlight .bp { color: #366 } /* Name.Builtin.Pseudo */ 278 | .highlight .vc { color: #033 } /* Name.Variable.Class */ 279 | .highlight .vg { color: #033 } /* Name.Variable.Global */ 280 | .highlight .vi { color: #033 } /* Name.Variable.Instance */ 281 | .highlight .il { color: #f60 } /* Literal.Number.Integer.Long */ -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # https://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 59 | if [ -z "$JAVA_HOME" ]; then 60 | if [ -x "/usr/libexec/java_home" ]; then 61 | export JAVA_HOME="`/usr/libexec/java_home`" 62 | else 63 | export JAVA_HOME="/Library/Java/Home" 64 | fi 65 | fi 66 | ;; 67 | esac 68 | 69 | if [ -z "$JAVA_HOME" ] ; then 70 | if [ -r /etc/gentoo-release ] ; then 71 | JAVA_HOME=`java-config --jre-home` 72 | fi 73 | fi 74 | 75 | if [ -z "$M2_HOME" ] ; then 76 | ## resolve links - $0 may be a link to maven's home 77 | PRG="$0" 78 | 79 | # need this for relative symlinks 80 | while [ -h "$PRG" ] ; do 81 | ls=`ls -ld "$PRG"` 82 | link=`expr "$ls" : '.*-> \(.*\)$'` 83 | if expr "$link" : '/.*' > /dev/null; then 84 | PRG="$link" 85 | else 86 | PRG="`dirname "$PRG"`/$link" 87 | fi 88 | done 89 | 90 | saveddir=`pwd` 91 | 92 | M2_HOME=`dirname "$PRG"`/.. 93 | 94 | # make it fully qualified 95 | M2_HOME=`cd "$M2_HOME" && pwd` 96 | 97 | cd "$saveddir" 98 | # echo Using m2 at $M2_HOME 99 | fi 100 | 101 | # For Cygwin, ensure paths are in UNIX format before anything is touched 102 | if $cygwin ; then 103 | [ -n "$M2_HOME" ] && 104 | M2_HOME=`cygpath --unix "$M2_HOME"` 105 | [ -n "$JAVA_HOME" ] && 106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 107 | [ -n "$CLASSPATH" ] && 108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 109 | fi 110 | 111 | # For Mingw, ensure paths are in UNIX format before anything is touched 112 | if $mingw ; then 113 | [ -n "$M2_HOME" ] && 114 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 115 | [ -n "$JAVA_HOME" ] && 116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 117 | fi 118 | 119 | if [ -z "$JAVA_HOME" ]; then 120 | javaExecutable="`which javac`" 121 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 122 | # readlink(1) is not available as standard on Solaris 10. 123 | readLink=`which readlink` 124 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 125 | if $darwin ; then 126 | javaHome="`dirname \"$javaExecutable\"`" 127 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 128 | else 129 | javaExecutable="`readlink -f \"$javaExecutable\"`" 130 | fi 131 | javaHome="`dirname \"$javaExecutable\"`" 132 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 133 | JAVA_HOME="$javaHome" 134 | export JAVA_HOME 135 | fi 136 | fi 137 | fi 138 | 139 | if [ -z "$JAVACMD" ] ; then 140 | if [ -n "$JAVA_HOME" ] ; then 141 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 142 | # IBM's JDK on AIX uses strange locations for the executables 143 | JAVACMD="$JAVA_HOME/jre/sh/java" 144 | else 145 | JAVACMD="$JAVA_HOME/bin/java" 146 | fi 147 | else 148 | JAVACMD="`which java`" 149 | fi 150 | fi 151 | 152 | if [ ! -x "$JAVACMD" ] ; then 153 | echo "Error: JAVA_HOME is not defined correctly." >&2 154 | echo " We cannot execute $JAVACMD" >&2 155 | exit 1 156 | fi 157 | 158 | if [ -z "$JAVA_HOME" ] ; then 159 | echo "Warning: JAVA_HOME environment variable is not set." 160 | fi 161 | 162 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 163 | 164 | # traverses directory structure from process work directory to filesystem root 165 | # first directory with .mvn subdirectory is considered project base directory 166 | find_maven_basedir() { 167 | 168 | if [ -z "$1" ] 169 | then 170 | echo "Path not specified to find_maven_basedir" 171 | return 1 172 | fi 173 | 174 | basedir="$1" 175 | wdir="$1" 176 | while [ "$wdir" != '/' ] ; do 177 | if [ -d "$wdir"/.mvn ] ; then 178 | basedir=$wdir 179 | break 180 | fi 181 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 182 | if [ -d "${wdir}" ]; then 183 | wdir=`cd "$wdir/.."; pwd` 184 | fi 185 | # end of workaround 186 | done 187 | echo "${basedir}" 188 | } 189 | 190 | # concatenates all lines of a file 191 | concat_lines() { 192 | if [ -f "$1" ]; then 193 | echo "$(tr -s '\n' ' ' < "$1")" 194 | fi 195 | } 196 | 197 | BASE_DIR=`find_maven_basedir "$(pwd)"` 198 | if [ -z "$BASE_DIR" ]; then 199 | exit 1; 200 | fi 201 | 202 | ########################################################################################## 203 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 204 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 205 | ########################################################################################## 206 | if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then 207 | if [ "$MVNW_VERBOSE" = true ]; then 208 | echo "Found .mvn/wrapper/maven-wrapper.jar" 209 | fi 210 | else 211 | if [ "$MVNW_VERBOSE" = true ]; then 212 | echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." 213 | fi 214 | if [ -n "$MVNW_REPOURL" ]; then 215 | jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 216 | else 217 | jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 218 | fi 219 | while IFS="=" read key value; do 220 | case "$key" in (wrapperUrl) jarUrl="$value"; break ;; 221 | esac 222 | done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" 223 | if [ "$MVNW_VERBOSE" = true ]; then 224 | echo "Downloading from: $jarUrl" 225 | fi 226 | wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" 227 | if $cygwin; then 228 | wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` 229 | fi 230 | 231 | if command -v wget > /dev/null; then 232 | if [ "$MVNW_VERBOSE" = true ]; then 233 | echo "Found wget ... using wget" 234 | fi 235 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 236 | wget "$jarUrl" -O "$wrapperJarPath" 237 | else 238 | wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" 239 | fi 240 | elif command -v curl > /dev/null; then 241 | if [ "$MVNW_VERBOSE" = true ]; then 242 | echo "Found curl ... using curl" 243 | fi 244 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 245 | curl -o "$wrapperJarPath" "$jarUrl" -f 246 | else 247 | curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f 248 | fi 249 | 250 | else 251 | if [ "$MVNW_VERBOSE" = true ]; then 252 | echo "Falling back to using Java to download" 253 | fi 254 | javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" 255 | # For Cygwin, switch paths to Windows format before running javac 256 | if $cygwin; then 257 | javaClass=`cygpath --path --windows "$javaClass"` 258 | fi 259 | if [ -e "$javaClass" ]; then 260 | if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 261 | if [ "$MVNW_VERBOSE" = true ]; then 262 | echo " - Compiling MavenWrapperDownloader.java ..." 263 | fi 264 | # Compiling the Java class 265 | ("$JAVA_HOME/bin/javac" "$javaClass") 266 | fi 267 | if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 268 | # Running the downloader 269 | if [ "$MVNW_VERBOSE" = true ]; then 270 | echo " - Running MavenWrapperDownloader.java ..." 271 | fi 272 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") 273 | fi 274 | fi 275 | fi 276 | fi 277 | ########################################################################################## 278 | # End of extension 279 | ########################################################################################## 280 | 281 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 282 | if [ "$MVNW_VERBOSE" = true ]; then 283 | echo $MAVEN_PROJECTBASEDIR 284 | fi 285 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 286 | 287 | # For Cygwin, switch paths to Windows format before running java 288 | if $cygwin; then 289 | [ -n "$M2_HOME" ] && 290 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 291 | [ -n "$JAVA_HOME" ] && 292 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 293 | [ -n "$CLASSPATH" ] && 294 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 295 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 296 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 297 | fi 298 | 299 | # Provide a "standardized" way to retrieve the CLI args that will 300 | # work with both Windows and non-Windows executions. 301 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" 302 | export MAVEN_CMD_LINE_ARGS 303 | 304 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 305 | 306 | exec "$JAVACMD" \ 307 | $MAVEN_OPTS \ 308 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 309 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 310 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 311 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM https://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 50 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 124 | 125 | FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 126 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 127 | ) 128 | 129 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 130 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 131 | if exist %WRAPPER_JAR% ( 132 | if "%MVNW_VERBOSE%" == "true" ( 133 | echo Found %WRAPPER_JAR% 134 | ) 135 | ) else ( 136 | if not "%MVNW_REPOURL%" == "" ( 137 | SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 138 | ) 139 | if "%MVNW_VERBOSE%" == "true" ( 140 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 141 | echo Downloading from: %DOWNLOAD_URL% 142 | ) 143 | 144 | powershell -Command "&{"^ 145 | "$webclient = new-object System.Net.WebClient;"^ 146 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 147 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 148 | "}"^ 149 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ 150 | "}" 151 | if "%MVNW_VERBOSE%" == "true" ( 152 | echo Finished downloading %WRAPPER_JAR% 153 | ) 154 | ) 155 | @REM End of extension 156 | 157 | @REM Provide a "standardized" way to retrieve the CLI args that will 158 | @REM work with both Windows and non-Windows executions. 159 | set MAVEN_CMD_LINE_ARGS=%* 160 | 161 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 162 | if ERRORLEVEL 1 goto error 163 | goto end 164 | 165 | :error 166 | set ERROR_CODE=1 167 | 168 | :end 169 | @endlocal & set ERROR_CODE=%ERROR_CODE% 170 | 171 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 172 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 173 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 174 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 175 | :skipRcPost 176 | 177 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 178 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 179 | 180 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 181 | 182 | exit /B %ERROR_CODE% 183 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.2.4.RELEASE 9 | 10 | 11 | com.mocyx 12 | yinwangblog 13 | 0 14 | yinwangblog 15 | a blog looks like http://www.yinwang.org/, based on netty 16 | 17 | 18 | 1.8 19 | 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter 25 | 26 | 27 | 28 | org.projectlombok 29 | lombok 30 | true 31 | 32 | 33 | org.springframework.boot 34 | spring-boot-starter-test 35 | test 36 | 37 | 38 | org.junit.vintage 39 | junit-vintage-engine 40 | 41 | 42 | 43 | 44 | 45 | io.netty 46 | netty-all 47 | 4.1.45.Final 48 | 49 | 50 | 51 | 52 | com.alibaba 53 | fastjson 54 | 1.2.62 55 | 56 | 57 | 58 | commons-io 59 | commons-io 60 | 2.6 61 | 62 | 63 | 64 | com.squareup.okhttp3 65 | okhttp 66 | 4.3.1 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | org.springframework.boot 75 | spring-boot-maven-plugin 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /src/main/java/com/mocyx/yinwangblog/BlogException.java: -------------------------------------------------------------------------------- 1 | package com.mocyx.yinwangblog; 2 | 3 | 4 | /** 5 | * @author Administrator 6 | */ 7 | public class BlogException extends RuntimeException { 8 | 9 | public BlogException(String msg) { 10 | super(msg); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/mocyx/yinwangblog/ConfigDto.java: -------------------------------------------------------------------------------- 1 | package com.mocyx.yinwangblog; 2 | 3 | import com.mocyx.yinwangblog.blog.entity.LinkDto; 4 | import lombok.Data; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * @author Administrator 10 | */ 11 | 12 | 13 | @Data 14 | public class ConfigDto { 15 | 16 | 17 | private String serverIp = "0.0.0.0"; 18 | private int serverPort = 9701; 19 | private String siteRoot = "http://www.yinwang.org/"; 20 | 21 | private String blogName; 22 | 23 | private String githubToken; 24 | private String githubName; 25 | private String githubRepo; 26 | 27 | private List links; 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/mocyx/yinwangblog/Global.java: -------------------------------------------------------------------------------- 1 | package com.mocyx.yinwangblog; 2 | 3 | public class Global { 4 | public static ConfigDto config; 5 | public static String gqlEndpoint = "https://api.github.com/graphql"; 6 | 7 | public static String webRoot = "./data/webroot/"; 8 | public static String webRoot0 = "./data/webroot0/"; 9 | public static String blogRoot = "./data/webroot0/blog-cn/"; 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/mocyx/yinwangblog/Runner.java: -------------------------------------------------------------------------------- 1 | package com.mocyx.yinwangblog; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.mocyx.yinwangblog.blog.BlogGenerateService; 5 | import com.mocyx.yinwangblog.blog.GithubService; 6 | import com.mocyx.yinwangblog.blog.entity.issue.IssuesDto; 7 | import com.mocyx.yinwangblog.http.HttpServer; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.apache.commons.io.FileUtils; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.boot.CommandLineRunner; 12 | import org.springframework.stereotype.Component; 13 | 14 | import java.io.File; 15 | 16 | /** 17 | * @author Administrator 18 | */ 19 | @Component 20 | @Slf4j 21 | public class Runner implements CommandLineRunner { 22 | 23 | @Autowired 24 | GithubService githubService; 25 | 26 | @Autowired 27 | BlogGenerateService blogGenerateService; 28 | 29 | 30 | private void startGithubDump() { 31 | Thread t = new Thread(new Runnable() { 32 | @Override 33 | public void run() { 34 | while (true) { 35 | try { 36 | IssuesDto issuesDto = githubService.getIssues(); 37 | blogGenerateService.generateBlog(issuesDto); 38 | log.info("github issues copied"); 39 | } catch (Exception e) { 40 | log.error(e.getMessage(), e); 41 | } 42 | try { 43 | //每分钟刷新一次 44 | Thread.sleep(60 * 1000); 45 | } catch (InterruptedException e) { 46 | log.error(e.getMessage(), e); 47 | } 48 | 49 | } 50 | 51 | } 52 | }); 53 | t.start(); 54 | } 55 | 56 | @Override 57 | public void run(String... args) throws Exception { 58 | 59 | 60 | String configFile = "./config.json"; 61 | if (args.length > 0) { 62 | configFile = args[0]; 63 | } 64 | 65 | String str = FileUtils.readFileToString(new File(configFile), "utf-8"); 66 | ConfigDto configDto = JSON.parseObject(str, ConfigDto.class); 67 | Global.config = configDto; 68 | //启动github更新线程 69 | startGithubDump(); 70 | //启动http 71 | Thread t = new Thread(new HttpServer()); 72 | t.start(); 73 | log.info("blog started"); 74 | t.join(); 75 | } 76 | 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/com/mocyx/yinwangblog/Util.java: -------------------------------------------------------------------------------- 1 | package com.mocyx.yinwangblog; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.apache.commons.io.FileUtils; 5 | import org.apache.commons.io.IOUtils; 6 | 7 | import java.io.*; 8 | import java.nio.charset.StandardCharsets; 9 | import java.nio.file.Files; 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | import org.springframework.util.ResourceUtils; 14 | 15 | 16 | @Slf4j 17 | public class Util { 18 | 19 | public static String readFile(String path) { 20 | try { 21 | return FileUtils.readFileToString(new File(path), StandardCharsets.UTF_8); 22 | } catch (IOException e) { 23 | return null; 24 | } 25 | } 26 | 27 | public static void deleteFile(String path) { 28 | try { 29 | FileUtils.forceDelete(new File(path)); 30 | } catch (IOException e) { 31 | //log.error(e.getMessage(), e); 32 | } 33 | } 34 | // 35 | // public static byte[] readResouceAsBytes(String path) { 36 | // try { 37 | // File file = ResourceUtils.getFile("classpath:" + path); 38 | // return Files.readAllBytes(file.toPath()); 39 | // } catch (IOException e) { 40 | // return null; 41 | // } 42 | // } 43 | // 44 | // public static String readResouce(String path) { 45 | // try { 46 | // 47 | // File file = ResourceUtils.getFile("classpath:" + path); 48 | // List lines = Files.readAllLines(file.toPath(), StandardCharsets.UTF_8); 49 | // StringBuilder sb = new StringBuilder(); 50 | // for (String line : lines) { 51 | // sb.append(line); 52 | // sb.append("\n"); 53 | // } 54 | // return sb.toString(); 55 | // } catch (IOException e) { 56 | // return null; 57 | // } 58 | // } 59 | 60 | public static String templateReplace(String template, Map reps) { 61 | String res = template; 62 | for (String k : reps.keySet()) { 63 | res = res.replace(k, reps.get(k)); 64 | 65 | } 66 | return res; 67 | } 68 | 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/mocyx/yinwangblog/YinwangblogApplication.java: -------------------------------------------------------------------------------- 1 | package com.mocyx.yinwangblog; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | /** 7 | * @author Administrator 8 | */ 9 | @SpringBootApplication 10 | public class YinwangblogApplication { 11 | public static void main(String[] args) { 12 | SpringApplication.run(YinwangblogApplication.class, args); 13 | } 14 | } 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/main/java/com/mocyx/yinwangblog/blog/BlogGenerateService.java: -------------------------------------------------------------------------------- 1 | package com.mocyx.yinwangblog.blog; 2 | 3 | import com.mocyx.yinwangblog.Util; 4 | import com.mocyx.yinwangblog.Global; 5 | import com.mocyx.yinwangblog.blog.entity.LinkDto; 6 | import com.mocyx.yinwangblog.blog.entity.issue.IssueDto; 7 | import com.mocyx.yinwangblog.blog.entity.issue.IssuesDto; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.apache.commons.io.FileUtils; 10 | import org.springframework.stereotype.Component; 11 | 12 | import java.io.File; 13 | import java.io.IOException; 14 | import java.nio.charset.StandardCharsets; 15 | import java.nio.file.Path; 16 | import java.nio.file.Paths; 17 | import java.text.SimpleDateFormat; 18 | import java.util.Date; 19 | import java.util.HashMap; 20 | import java.util.Map; 21 | import java.util.TimeZone; 22 | 23 | /** 24 | * @author Administrator 25 | */ 26 | @Component 27 | @Slf4j 28 | public class BlogGenerateService { 29 | 30 | private static String indexTpl = "data/template/index.html"; 31 | private static String blogTpl = "data/template/blog.html"; 32 | private static String articleItemTpl = "data/template/articleItem.html"; 33 | 34 | 35 | private String buildItems(IssuesDto issuesDto) throws Exception { 36 | String articleItemTemplate = Util.readFile(articleItemTpl); 37 | 38 | StringBuilder sb = new StringBuilder(); 39 | for (IssueDto issueDto : issuesDto.getNodes()) { 40 | 41 | Map reps = new HashMap<>(); 42 | 43 | Date date = getDate(issueDto); 44 | String dateString = String.format("%d年%02d月%02d日", date.getYear() + 1900, date.getMonth() + 1, date.getDate()); 45 | reps.put("#{date}", dateString); 46 | String href = generateHref(issueDto); 47 | reps.put("#{href}", href); 48 | reps.put("#{title}", issueDto.getTitle()); 49 | reps.put("#{siteRoot}", Global.config.getSiteRoot()); 50 | 51 | String s = Util.templateReplace(articleItemTemplate, reps); 52 | sb.append(s); 53 | } 54 | return sb.toString(); 55 | } 56 | 57 | private Date getDate(IssueDto issueDto) throws Exception { 58 | String tm = issueDto.getCreatedAt(); 59 | SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); 60 | dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); 61 | Date date = dateFormat.parse(tm); 62 | return date; 63 | } 64 | 65 | private String generateHref(IssueDto issueDto) throws Exception { 66 | Date date = getDate(issueDto); 67 | String href = String.format("/blog-cn/%d/%02d/%02d/%s", date.getYear() + 1900, date.getMonth() + 1, date.getDate(), issueDto.getId()); 68 | return href; 69 | } 70 | 71 | private String generateLinks() { 72 | String res = ""; 73 | for (LinkDto linkDto : Global.config.getLinks()) { 74 | res += String.format("
  • %s
  • ", linkDto.getHref(), linkDto.getTitle()); 75 | } 76 | return res; 77 | } 78 | 79 | private void generateIndex(IssuesDto issuesDto) throws Exception { 80 | String articles = buildItems(issuesDto); 81 | String indexTemplate = Util.readFile(indexTpl); 82 | Map reps = new HashMap<>(); 83 | reps.put("#{blogTitle}", Global.config.getBlogName()); 84 | reps.put("#{articles}", articles); 85 | reps.put("#{siteRoot}", Global.config.getSiteRoot()); 86 | 87 | reps.put("#{links}", generateLinks()); 88 | 89 | String indexHtml = Util.templateReplace(indexTemplate, reps); 90 | 91 | String filePath = Global.webRoot0 + "/index.html"; 92 | Util.deleteFile(filePath); 93 | writeStringToFile(indexHtml, filePath); 94 | } 95 | 96 | private void writeStringToFile(String txt, String path) throws IOException { 97 | Path p = Paths.get(path).toAbsolutePath(); 98 | String s = p.getParent().toString(); 99 | FileUtils.forceMkdir(new File(s)); 100 | FileUtils.writeStringToFile(new File(path), txt, StandardCharsets.UTF_8); 101 | log.info("write file {}", path); 102 | } 103 | 104 | private void generateArticle(IssuesDto issuesDto) throws Exception { 105 | 106 | for (IssueDto issueDto : issuesDto.getNodes()) { 107 | String indexTemplate = Util.readFile(blogTpl); 108 | Map reps = new HashMap<>(); 109 | reps.put("#{title}", issueDto.getTitle()); 110 | reps.put("#{article}", issueDto.getBodyHTML()); 111 | String html = Util.templateReplace(indexTemplate, reps); 112 | 113 | String filePath = Global.webRoot0 + generateHref(issueDto); 114 | Util.deleteFile(filePath); 115 | writeStringToFile(html, filePath); 116 | } 117 | 118 | } 119 | 120 | public void generateBlog(IssuesDto issuesDto) { 121 | try { 122 | // 123 | File dir = new File(Global.blogRoot); 124 | if (dir.exists()) { 125 | FileUtils.deleteDirectory(dir); 126 | } 127 | FileUtils.forceMkdir(dir); 128 | // 129 | generateIndex(issuesDto); 130 | // 131 | generateArticle(issuesDto); 132 | 133 | 134 | } catch (Exception e) { 135 | log.error(e.getMessage(), e); 136 | System.exit(0); 137 | } 138 | 139 | 140 | } 141 | 142 | 143 | } 144 | -------------------------------------------------------------------------------- /src/main/java/com/mocyx/yinwangblog/blog/GithubService.java: -------------------------------------------------------------------------------- 1 | package com.mocyx.yinwangblog.blog; 2 | 3 | 4 | import com.alibaba.fastjson.JSON; 5 | import com.alibaba.fastjson.JSONObject; 6 | import com.mocyx.yinwangblog.BlogException; 7 | import com.mocyx.yinwangblog.Global; 8 | import com.mocyx.yinwangblog.blog.entity.gql.GqlQuery; 9 | import com.mocyx.yinwangblog.blog.entity.issue.IssuesDto; 10 | import lombok.extern.slf4j.Slf4j; 11 | import okhttp3.*; 12 | import org.apache.commons.io.IOUtils; 13 | import org.springframework.stereotype.Component; 14 | 15 | import java.io.IOException; 16 | import java.nio.charset.Charset; 17 | import java.nio.charset.StandardCharsets; 18 | import java.util.concurrent.TimeUnit; 19 | 20 | /** 21 | * @author Administrator 22 | */ 23 | @Component 24 | @Slf4j 25 | public class GithubService { 26 | private final String queryStr = "{\n" + 27 | " repository(owner: \"#{owner}\", name: \"#{name}\") {\n" + 28 | " issues(first: 100, orderBy: {field: CREATED_AT, direction: DESC},states: OPEN) {\n" + 29 | " nodes {\n" + 30 | " author {\n" + 31 | " login\n" + 32 | " }\n" + 33 | " bodyHTML\n" + 34 | " title\n" + 35 | " comments(first: 100) {\n" + 36 | " nodes {\n" + 37 | " bodyHTML\n" + 38 | " author {\n" + 39 | " login\n" + 40 | " }\n" + 41 | " createdAt\n" + 42 | " id\n" + 43 | " databaseId\n" + 44 | " }\n" + 45 | " }\n" + 46 | " createdAt\n" + 47 | " id\n" + 48 | " databaseId\n" + 49 | " }\n" + 50 | " }\n" + 51 | " }\n" + 52 | "}"; 53 | 54 | private IssuesDto parseJson(String s) { 55 | JSONObject jsonObject = JSON.parseObject(s); 56 | JSONObject data = (JSONObject) jsonObject.get("data"); 57 | JSONObject repository = (JSONObject) data.get("repository"); 58 | JSONObject issues = (JSONObject) repository.get("issues"); 59 | String str = JSON.toJSONString(issues); 60 | IssuesDto issuesDto = JSON.parseObject(str, IssuesDto.class); 61 | return issuesDto; 62 | } 63 | 64 | public IssuesDto getIssues() throws IOException { 65 | OkHttpClient client = new OkHttpClient.Builder() 66 | .connectTimeout(3000, TimeUnit.MILLISECONDS) 67 | .writeTimeout(3000, TimeUnit.MILLISECONDS) 68 | .readTimeout(60000, TimeUnit.MILLISECONDS) 69 | .build(); 70 | 71 | MediaType jsonMedia = MediaType.parse("application/json; charset=utf-8"); 72 | GqlQuery query = new GqlQuery(); 73 | 74 | String gql = queryStr.replace("#{owner}", Global.config.getGithubName()) 75 | .replace("#{name}", Global.config.getGithubRepo()); 76 | 77 | query.setQuery(gql); 78 | String jsonStr = JSON.toJSONString(query); 79 | RequestBody formBody = RequestBody.create(jsonMedia, jsonStr); 80 | 81 | Request request = new Request.Builder() 82 | .addHeader("Authorization", "bearer " + Global.config.getGithubToken()) 83 | .url(Global.gqlEndpoint) 84 | .post(formBody) 85 | .build(); 86 | Response response = client.newCall(request).execute(); 87 | 88 | String bodyString = IOUtils.toString(response.body().byteStream(), StandardCharsets.UTF_8); 89 | 90 | 91 | if (!response.isSuccessful()) { 92 | log.error("http fail {} {}", response.code(), bodyString); 93 | throw new BlogException("http error"); 94 | } else { 95 | log.debug("http success {} {}", response.code(), bodyString); 96 | } 97 | 98 | IssuesDto issuesDto = parseJson(bodyString); 99 | 100 | return issuesDto; 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/com/mocyx/yinwangblog/blog/entity/LinkDto.java: -------------------------------------------------------------------------------- 1 | package com.mocyx.yinwangblog.blog.entity; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Data 9 | @Builder 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | public class LinkDto { 13 | private String href; 14 | private String title; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/mocyx/yinwangblog/blog/entity/gql/GqlQuery.java: -------------------------------------------------------------------------------- 1 | package com.mocyx.yinwangblog.blog.entity.gql; 2 | 3 | 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | @Data 10 | @Builder 11 | @NoArgsConstructor 12 | @AllArgsConstructor 13 | public class GqlQuery { 14 | private String query; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/mocyx/yinwangblog/blog/entity/issue/AuthorDto.java: -------------------------------------------------------------------------------- 1 | package com.mocyx.yinwangblog.blog.entity.issue; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | /** 9 | * @author Administrator 10 | */ 11 | @Data 12 | @Builder 13 | @NoArgsConstructor 14 | @AllArgsConstructor 15 | public class AuthorDto { 16 | private String login; 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/mocyx/yinwangblog/blog/entity/issue/CommentDto.java: -------------------------------------------------------------------------------- 1 | package com.mocyx.yinwangblog.blog.entity.issue; 2 | 3 | 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | @Data 10 | @Builder 11 | @NoArgsConstructor 12 | @AllArgsConstructor 13 | public class CommentDto { 14 | private String bodyHTML; 15 | private AuthorDto author; 16 | private String createdAt; 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/mocyx/yinwangblog/blog/entity/issue/CommentsDto.java: -------------------------------------------------------------------------------- 1 | package com.mocyx.yinwangblog.blog.entity.issue; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.util.List; 9 | 10 | 11 | @Data 12 | @Builder 13 | @NoArgsConstructor 14 | @AllArgsConstructor 15 | public class CommentsDto { 16 | private List nodes; 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/mocyx/yinwangblog/blog/entity/issue/IssueDto.java: -------------------------------------------------------------------------------- 1 | package com.mocyx.yinwangblog.blog.entity.issue; 2 | 3 | 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | /** 10 | * @author Administrator 11 | */ 12 | @Data 13 | @Builder 14 | @NoArgsConstructor 15 | @AllArgsConstructor 16 | public class IssueDto { 17 | private String bodyHTML; 18 | private AuthorDto author; 19 | private String title; 20 | private CommentsDto comments; 21 | private String createdAt; 22 | private String id; 23 | private String databaseId; 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/mocyx/yinwangblog/blog/entity/issue/IssuesDto.java: -------------------------------------------------------------------------------- 1 | package com.mocyx.yinwangblog.blog.entity.issue; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.util.List; 9 | 10 | @Data 11 | @Builder 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | public class IssuesDto { 15 | private List nodes; 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/mocyx/yinwangblog/http/ActionHandler.java: -------------------------------------------------------------------------------- 1 | package com.mocyx.yinwangblog.http; 2 | 3 | import com.mocyx.yinwangblog.Global; 4 | import com.mocyx.yinwangblog.Util; 5 | import com.mocyx.yinwangblog.http.HttpException; 6 | import io.netty.buffer.ByteBuf; 7 | import io.netty.channel.ChannelHandlerContext; 8 | import io.netty.channel.SimpleChannelInboundHandler; 9 | import io.netty.handler.codec.http.*; 10 | import lombok.extern.slf4j.Slf4j; 11 | import org.apache.commons.io.FileUtils; 12 | 13 | import java.io.File; 14 | import java.io.IOException; 15 | import java.nio.file.Path; 16 | import java.nio.file.Paths; 17 | 18 | /** 19 | * @author Administrator 20 | */ 21 | @Slf4j 22 | public class ActionHandler extends SimpleChannelInboundHandler { 23 | 24 | 25 | private String fileToContentType(String path) { 26 | 27 | if (path.endsWith(".txt")) { 28 | return "text/plain; charset=utf-8"; 29 | } else if (path.endsWith(".html")) { 30 | return "text/html; charset=utf-8"; 31 | } else if (path.endsWith(".png")) { 32 | return "image/png"; 33 | } else if (path.endsWith(".jpg")) { 34 | return "image/jpeg"; 35 | } else if (path.endsWith(".css")) { 36 | return "text/css; charset=utf-8"; 37 | } else { 38 | return "text/html; charset=utf-8"; 39 | } 40 | } 41 | 42 | // 43 | // private byte[] tryReadFromResource(String path) { 44 | // try { 45 | // String filePath = Global.resourceWebRoot + path; 46 | // Path p = Paths.get(filePath).normalize(); 47 | // Path pRoot = Paths.get(Global.resourceWebRoot).normalize(); 48 | // if (!p.startsWith(pRoot)) { 49 | // return null; 50 | // } 51 | // byte[] data = Util.readResouceAsBytes(filePath); 52 | // return data; 53 | // } catch (Exception e) { 54 | // log.error(e.getMessage(), e); 55 | // return null; 56 | // } 57 | // 58 | // } 59 | 60 | private byte[] readFile(String root, String path) { 61 | String filePath = root + path; 62 | Path p = Paths.get(filePath).normalize(); 63 | Path pRoot = Paths.get(root).normalize(); 64 | if (!p.startsWith(pRoot)) { 65 | return null; 66 | } 67 | File f = p.toFile(); 68 | if (!f.exists()) { 69 | return null; 70 | } 71 | if (f.isDirectory()) { 72 | return null; 73 | } 74 | try { 75 | byte[] bytes = FileUtils.readFileToByteArray(f); 76 | return bytes; 77 | } catch (IOException e) { 78 | log.error(e.getMessage(), e); 79 | return null; 80 | } 81 | } 82 | 83 | @Override 84 | protected void channelRead0(ChannelHandlerContext channelHandlerContext, FullHttpRequest req) throws Exception { 85 | QueryStringDecoder queryString = new QueryStringDecoder(req.uri()); 86 | 87 | String path = queryString.path(); 88 | 89 | if (path.endsWith("/")) { 90 | path = "/index.html"; 91 | } 92 | 93 | try { 94 | log.info("ActionHandler {}", queryString); 95 | 96 | byte[] data = null; 97 | data = readFile(Global.webRoot, path); 98 | if (data == null) { 99 | data = readFile(Global.webRoot0, path); 100 | } 101 | if (data == null) { 102 | throw new HttpException(HttpResponseStatus.NOT_FOUND, path); 103 | } 104 | 105 | ByteBuf buf = channelHandlerContext.alloc().buffer(); 106 | buf.writeBytes(data); 107 | 108 | FullHttpResponse fullHttpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, 109 | (buf)); 110 | fullHttpResponse.headers().add(HttpHeaderNames.CONTENT_TYPE, fileToContentType(path)); 111 | fullHttpResponse.headers().add(HttpHeaderNames.CONTENT_LENGTH, data.length); 112 | 113 | channelHandlerContext.writeAndFlush(fullHttpResponse); 114 | } catch (HttpException e) { 115 | HttpResponseStatus status = e.code; 116 | 117 | byte[] data = null; 118 | if (status == HttpResponseStatus.NOT_FOUND) { 119 | data = readFile(Global.webRoot, "404.html"); 120 | } else { 121 | String html = String.format("

    %d

    %s

    ", status.code(), e.getMessage()); 122 | data = html.getBytes(); 123 | } 124 | 125 | ByteBuf buf = channelHandlerContext.alloc().buffer(); 126 | buf.writeBytes(data); 127 | FullHttpResponse fullHttpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, 128 | buf); 129 | fullHttpResponse.headers().add(HttpHeaderNames.CONTENT_TYPE, "text/html"); 130 | fullHttpResponse.headers().add(HttpHeaderNames.CONTENT_LENGTH, data.length); 131 | channelHandlerContext.writeAndFlush(fullHttpResponse); 132 | } 133 | } 134 | 135 | @Override 136 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 137 | if (cause instanceof IOException) { 138 | log.warn("{}", cause.getMessage()); 139 | } else { 140 | super.exceptionCaught(ctx, cause); 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/main/java/com/mocyx/yinwangblog/http/HttpException.java: -------------------------------------------------------------------------------- 1 | package com.mocyx.yinwangblog.http; 2 | 3 | import io.netty.handler.codec.http.HttpResponseStatus; 4 | 5 | /** 6 | * @author Administrator 7 | */ 8 | public class HttpException extends RuntimeException { 9 | public HttpException(HttpResponseStatus code, String message) { 10 | super(message); 11 | this.code = code; 12 | } 13 | public final HttpResponseStatus code; 14 | } 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/main/java/com/mocyx/yinwangblog/http/HttpServer.java: -------------------------------------------------------------------------------- 1 | package com.mocyx.yinwangblog.http; 2 | 3 | 4 | import com.mocyx.yinwangblog.Global; 5 | import io.netty.bootstrap.ServerBootstrap; 6 | import io.netty.channel.Channel; 7 | import io.netty.channel.ChannelInitializer; 8 | import io.netty.channel.ChannelOption; 9 | import io.netty.channel.EventLoopGroup; 10 | import io.netty.channel.nio.NioEventLoopGroup; 11 | import io.netty.channel.socket.SocketChannel; 12 | import io.netty.channel.socket.nio.NioServerSocketChannel; 13 | import io.netty.handler.codec.http.HttpObjectAggregator; 14 | import io.netty.handler.codec.http.HttpServerCodec; 15 | import io.netty.handler.stream.ChunkedWriteHandler; 16 | import lombok.extern.slf4j.Slf4j; 17 | import org.springframework.stereotype.Component; 18 | 19 | /** 20 | * @author Administrator 21 | */ 22 | @Component 23 | @Slf4j 24 | public class HttpServer implements Runnable { 25 | 26 | private EventLoopGroup bossGroup; 27 | private EventLoopGroup workerGroup; 28 | 29 | 30 | @Override 31 | public void run() { 32 | bossGroup = new NioEventLoopGroup(3); 33 | workerGroup = new NioEventLoopGroup(1); 34 | 35 | ServerBootstrap b = new ServerBootstrap(); 36 | try { 37 | b.group(bossGroup) 38 | .option(ChannelOption.SO_BACKLOG, 1024) 39 | .channel(NioServerSocketChannel.class) 40 | // .handler(new LoggingHandler(LogLevel.INFO)) 41 | .childHandler(new ChannelInitializer() { 42 | @Override 43 | protected void initChannel(SocketChannel ch) throws Exception { 44 | ch.pipeline() 45 | .addLast(new HttpServerCodec()) 46 | //把多个消息转换为一个单一的FullHttpRequest或是FullHttpResponse 47 | .addLast(new HttpObjectAggregator(65536)) 48 | //压缩Http消息 49 | // .addLast(new HttpChunkContentCompressor()) 50 | //大文件支持 51 | .addLast(new ChunkedWriteHandler()) 52 | .addLast(new ActionHandler()); 53 | ; 54 | 55 | 56 | } 57 | }); 58 | Channel ch = b.bind(Global.config.getServerIp(), Global.config.getServerPort()).sync().channel(); 59 | log.info("http server started {} {}", Global.config.getServerIp(), Global.config.getServerPort()); 60 | ch.closeFuture().sync(); 61 | } catch (Exception e) { 62 | log.error(e.getMessage(), e); 63 | } 64 | 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | logging.level.com.mocyx=INFO 2 | -------------------------------------------------------------------------------- /src/test/java/com/mocyx/yinwangblog/YinwangblogApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.mocyx.yinwangblog; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | // 6 | //@SpringBootTest 7 | //class YinwangblogApplicationTests { 8 | // 9 | // @Test 10 | // void contextLoads() { 11 | // } 12 | // 13 | //} 14 | --------------------------------------------------------------------------------