diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..e27d0e6 --- /dev/null +++ b/README.md @@ -0,0 +1,32 @@ +![Indexing - 搜索引擎推送工具 - SEO 工具箱](https://cdn.renfei.net/upload/image/2021/indexing_tools.gif) +# Indexing - 百度-必应-谷歌 搜索引擎推送工具 +此工具利用「百度-必应-谷歌」站长工具或开放平台接口即时推送网站更新给搜索引擎,加快蜘蛛程序爬取与更新。 +此工具代码作者已经使用多年,同时作者正在探索 Swing/AWT 编程,此工具作为作者 Swing/AWT 编程处女作,顺便将技术能力通过可视化界面分享出来,让不懂编程的站长也可以使用 API 接口的便利。 + +## 安全声明 +作为软件开发工程师的我,深知安全的重要性,因为此工具运行时需要站长提供 API 的 Token 令牌,这就相当于密码授权。 +为了证明此工具不会抓取上传站长的 Token 令牌,所以开源公布出来,欢迎监督。(PS:本来想闭源使用混淆编译发布,防止被仿冒) +因为开源以后任何人可以利用源码修改制作小版本,站长们请认准 renfei.net 官网。如果发现仿冒请向我举报。 + +## 使用帮助 +本工具基于 Java8 制作,如果您拥有 Java8 或更高的 JDK/JRE 环境,可以直接下载 Jar 包文件,使用如下命令即可启动: +```bash +java -jar Indexing.jar +``` +如果您不确定自己的环境是否拥有 JDK/JRE,我还提供了环境打包版本,由于操作系统不同请下载对应的版本,执行其中的 start 脚本。 + +## 其他说明 +- 百度Token获取地址: https://ziyuan.baidu.com/linksubmit/index +- 必应Token获取地址:https://docs.microsoft.com/en-us/bingwebmaster/getting-access#using-api-key +- 谷歌JSON私钥获取:https://www.renfei.net/posts/1003342 +- 谷歌上报需要本地是翻墙状态,否则网络不通 +- 各个平台的接口提交配额与本工具无关,是各个平台分配给你的;例如百度快速收录是百度站长工具给予的权限,与是否使用本工具无关 +- 本工具不会收集上报用户的Token,本工具代码已开源,欢迎监督,如遇仿制程序上报Token请联系 i@renfei.net +### 代码仓库 +- [Main] Github:[https://github.com/renfei/Indexing](https://github.com/renfei/Indexing) +- [Mirror] Gitee:[https://gitee.com/rnf/Indexing](https://gitee.com/rnf/Indexing) +- [Mirror] Gitlab:[https://gitlab.com/renfei/Indexing](https://gitlab.com/renfei/Indexing) + +### 求鼓励 + +如果这个项目帮助到了你,是否能给我点个免费的星星 (Star) 给个鼓励呢。高星项目我将持续关注努力更新的。 \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..4c90303 --- /dev/null +++ b/pom.xml @@ -0,0 +1,117 @@ + + + 4.0.0 + + net.renfei + Indexing + 1.0.0 + Indexing + https://www.renfei.net/kitbox/indexing + Indexing - 搜索引擎推送工具 - SEO 工具箱 + + + + The Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + + + + + GitHub Issues + https://github.com/renfei/Indexing/issues + + + 2021 + + + scm:git:git://github.com/renfei/Indexing.git + scm:git:git@github.com:renfei/Indexing.git + https://github.com/renfei/Indexing + + + + + renfei + RenFei + i@renfei.net + + owner + developer + + RENFEI.NET + https://www.renfei.net + +8 + + + + + 8 + 8 + + + + + net.renfei + sdk + 1.0.14 + + + com.google.apis + google-api-services-indexing + v3-rev71-1.25.0 + + + com.intellij + forms_rt + 7.0.3 + + + + + Indexing + + + org.codehaus.mojo + ideauidesigner-maven-plugin + 1.0-beta-1 + + + + javac2 + + + + + true + true + true + + + + maven-assembly-plugin + 2.6 + + + + net.renfei.indexing.Application + + + + jar-with-dependencies + + + + + make-assembly + package + + single + + + + + + + \ No newline at end of file diff --git a/script/start.bat b/script/start.bat new file mode 100644 index 0000000..c08ff21 --- /dev/null +++ b/script/start.bat @@ -0,0 +1,7 @@ +@echo ################################### +@echo # --=== Indexing ===-- # +@echo # author: renfei (i@renfei.net) # +@echo ################################### + +@echo 请勿关闭此窗口,关闭此窗口程序立即退出 +@bin\java -jar Indexing.jar \ No newline at end of file diff --git a/script/start.sh b/script/start.sh new file mode 100644 index 0000000..913975f --- /dev/null +++ b/script/start.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +echo "###################################" +echo "# --=== Indexing ===-- #" +echo "# author: renfei (i@renfei.net) #" +echo "###################################" +DIR_NAME=$0 +if [ "${DIR_NAME:0:1}" = "/" ];then + # shellcheck disable=SC2006 + DIR_PATH=`dirname "$DIR_NAME"` +else + # shellcheck disable=SC2006 + # shellcheck disable=SC2123 + DIR_PATH="`pwd`"/"`dirname "$DIR_NAME"`" +fi +nohup "$DIR_PATH"/bin/java -jar "$DIR_PATH"/Indexing.jar > "$DIR_PATH"/Indexing.log 2>&1 & echo $! > "$DIR_PATH"/Indexing.pid \ No newline at end of file diff --git a/src/main/java/net/renfei/indexing/Application.java b/src/main/java/net/renfei/indexing/Application.java new file mode 100644 index 0000000..f79bb4f --- /dev/null +++ b/src/main/java/net/renfei/indexing/Application.java @@ -0,0 +1,28 @@ +package net.renfei.indexing; + +import net.renfei.indexing.ui.MainWindow; +import net.renfei.indexing.ui.MenuBar; + +import javax.swing.*; + + +/** + * 程序入口 + * + * @author renfei + */ +public class Application { + public static final MainWindow MAIN_WINDOW = new MainWindow(); + public static final String VERSION = "1.0.0"; + public static void main(String[] args) { + javax.swing.JFrame frame = new javax.swing.JFrame("Indexing - 搜索引擎推送工具 - SEO 工具箱"); + frame.setContentPane(MAIN_WINDOW.mainPanel); + frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); + frame.pack(); + frame.setSize(1000, 618); + frame.setLocationRelativeTo(null); + frame.setJMenuBar(new MenuBar()); + MAIN_WINDOW.init(); + frame.setVisible(true); + } +} diff --git a/src/main/java/net/renfei/indexing/entity/GoogleIndexingEntity.java b/src/main/java/net/renfei/indexing/entity/GoogleIndexingEntity.java new file mode 100644 index 0000000..eff9497 --- /dev/null +++ b/src/main/java/net/renfei/indexing/entity/GoogleIndexingEntity.java @@ -0,0 +1,22 @@ +package net.renfei.indexing.entity; + +public class GoogleIndexingEntity { + private String url; + private String type; + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } +} diff --git a/src/main/java/net/renfei/indexing/service/BaiduService.java b/src/main/java/net/renfei/indexing/service/BaiduService.java new file mode 100644 index 0000000..d792d00 --- /dev/null +++ b/src/main/java/net/renfei/indexing/service/BaiduService.java @@ -0,0 +1,50 @@ +package net.renfei.indexing.service; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpStatus; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.util.EntityUtils; + +import java.io.IOException; + +public class BaiduService { + private final String siteUrl; + private final String baiduToken; + + public BaiduService(String siteUrl, String baiduToken) { + this.siteUrl = siteUrl; + this.baiduToken = baiduToken; + } + + public String push(String url) throws IOException { + return push(url, false); + } + + public String push(String url, boolean isMobile) throws IOException { + String pushUrl = "http://data.zz.baidu.com/urls?site=" + siteUrl + "&token=" + baiduToken; + if (isMobile) { + pushUrl = "http://data.zz.baidu.com/urls?site=" + siteUrl + "&token=" + baiduToken + "&type=daily"; + } + CloseableHttpClient closeableHttpClient = HttpClients.createDefault(); + CloseableHttpResponse closeableHttpResponse; + HttpPost httpPost = new HttpPost(pushUrl); + httpPost.addHeader("Content-Type", "text/plain"); + StringEntity entity = new StringEntity(url, ContentType.create("text/plain", "UTF-8")); + httpPost.setEntity(entity); + RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(5000).setConnectTimeout(5000).build(); + httpPost.setConfig(requestConfig); + closeableHttpResponse = closeableHttpClient.execute(httpPost); + int statusCode = closeableHttpResponse.getStatusLine().getStatusCode(); + if (statusCode != HttpStatus.SC_OK) { + throw new RuntimeException("\r\n#### 请求错误 ####\r\n响应状态码:" + statusCode + "\r\n请求地址:" + pushUrl); + } + HttpEntity httpEntity = closeableHttpResponse.getEntity(); + return EntityUtils.toString(httpEntity, "UTF-8"); + } +} diff --git a/src/main/java/net/renfei/indexing/service/BingService.java b/src/main/java/net/renfei/indexing/service/BingService.java new file mode 100644 index 0000000..01302cc --- /dev/null +++ b/src/main/java/net/renfei/indexing/service/BingService.java @@ -0,0 +1,47 @@ +package net.renfei.indexing.service; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpStatus; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.util.EntityUtils; + +import java.io.IOException; + +public class BingService { + private final String siteUrl; + private final String bingToken; + + public BingService(String siteUrl, String bingToken) { + this.siteUrl = siteUrl; + this.bingToken = bingToken; + } + + public String push(String url) throws IOException { + String pushUrl = "https://ssl.bing.com/webmaster/api.svc/json/SubmitUrlbatch?apikey=" + bingToken; + String json = "{\"siteUrl\":\"" + siteUrl + "\",\"urlList\":[\"" + url + "\"]}"; + CloseableHttpClient closeableHttpClient = HttpClients.createDefault(); + CloseableHttpResponse closeableHttpResponse; + HttpPost httpPost = new HttpPost(pushUrl); + httpPost.addHeader("Content-Type", "application/json"); + StringEntity entity = new StringEntity(json, ContentType.create("application/json", "UTF-8")); + httpPost.setEntity(entity); + RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(5000).setConnectTimeout(5000).build(); + httpPost.setConfig(requestConfig); + closeableHttpResponse = closeableHttpClient.execute(httpPost); + int statusCode = closeableHttpResponse.getStatusLine().getStatusCode(); + if (statusCode != HttpStatus.SC_OK) { + throw new RuntimeException("\r\n#### 请求错误 ####\r\n响应状态码:" + + statusCode + + "\r\n请求地址:" + + pushUrl); + } + HttpEntity httpEntity = closeableHttpResponse.getEntity(); + return EntityUtils.toString(httpEntity, "UTF-8"); + } +} diff --git a/src/main/java/net/renfei/indexing/service/ExecService.java b/src/main/java/net/renfei/indexing/service/ExecService.java new file mode 100644 index 0000000..8ef80cb --- /dev/null +++ b/src/main/java/net/renfei/indexing/service/ExecService.java @@ -0,0 +1,66 @@ +package net.renfei.indexing.service; + +import com.google.api.client.http.HttpResponse; +import net.renfei.sdk.utils.BeanUtils; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.security.GeneralSecurityException; + +/** + * @author renfei + */ +public class ExecService { + private BaiduService baiduService; + private BingService bingService; + private GoogleService googleService; + + public String execBaidu(String siteUrl, String baiduToken, String url) throws IOException { + if (baiduService == null) { + baiduService = new BaiduService(siteUrl, baiduToken); + } + return baiduService.push(url); + } + + public String execBaiduKuaiSu(String siteUrl, String baiduToken, String url) throws IOException { + if (baiduService == null) { + baiduService = new BaiduService(siteUrl, baiduToken); + } + return baiduService.push(url, true); + } + + public String execBing(String siteUrl, String bingToken, String url) throws IOException { + if (bingService == null) { + bingService = new BingService(siteUrl, bingToken); + } + return bingService.push(url); + } + + public String execGoogle(String googleJsonPath, String url) throws IOException, GeneralSecurityException { + if (BeanUtils.isEmpty(googleJsonPath)) { + throw new RuntimeException("请先选择谷歌私钥JSON文件"); + } + if (googleJsonPath.endsWith(".json")) { + File file = new File(googleJsonPath); + if (file.exists()) { + if (googleService == null) { + googleService = new GoogleService(googleJsonPath); + } + HttpResponse httpResponse = googleService.update(url); + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(httpResponse.getContent())); + StringBuilder sb = new StringBuilder(); + String sTempOneLine; + while ((sTempOneLine = bufferedReader.readLine()) != null) { + sb.append(sTempOneLine); + } + return sb.toString(); + } else { + throw new RuntimeException(".json文件不存在"); + } + } else { + throw new RuntimeException("请选择.json的文件"); + } + } +} diff --git a/src/main/java/net/renfei/indexing/service/GoogleService.java b/src/main/java/net/renfei/indexing/service/GoogleService.java new file mode 100644 index 0000000..d44066b --- /dev/null +++ b/src/main/java/net/renfei/indexing/service/GoogleService.java @@ -0,0 +1,68 @@ +package net.renfei.indexing.service; + +import com.alibaba.fastjson.JSON; +import com.google.api.client.googleapis.auth.oauth2.GoogleCredential; +import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport; +import com.google.api.client.http.*; +import com.google.api.client.json.JsonFactory; +import com.google.api.client.json.jackson2.JacksonFactory; +import net.renfei.indexing.entity.GoogleIndexingEntity; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.security.GeneralSecurityException; +import java.util.Collections; + +/** + * Google提供的服务 + * + * @author RenFei + */ +public class GoogleService { + private final String googleJsonPath; + private HttpTransport httpTransport; + + public GoogleService(String googleJsonPath) { + this.googleJsonPath = googleJsonPath; + } + + public HttpResponse update(String url) throws IOException, GeneralSecurityException { + GoogleCredential googleCredential = getGoogleCredential("https://www.googleapis.com/auth/indexing"); + GoogleIndexingEntity googleIndexingEntity = new GoogleIndexingEntity(); + googleIndexingEntity.setUrl(url); + googleIndexingEntity.setType("URL_UPDATED"); + return this.publish(googleCredential, googleIndexingEntity); + } + + public HttpResponse publish(GoogleCredential googleCredential, GoogleIndexingEntity googleIndexingEntity) throws IOException { + String endPoint = "https://indexing.googleapis.com/v3/urlNotifications:publish"; + GenericUrl genericUrl = new GenericUrl(endPoint); + String content = JSON.toJSONString(googleIndexingEntity); + HttpRequestFactory requestFactory = this.httpTransport.createRequestFactory(); + HttpRequest request = + requestFactory.buildPostRequest(genericUrl, ByteArrayContent.fromString("application/json", content)); + googleCredential.initialize(request); + return request.execute(); + } + + public GoogleCredential getGoogleCredential(String scopes) throws IOException, GeneralSecurityException { + return getGoogleCredential(googleJsonPath, scopes); + } + + public GoogleCredential getGoogleCredential(String path, String scopes) throws IOException, GeneralSecurityException { + return getGoogleCredential(new FileInputStream(path), scopes); + } + + public GoogleCredential getGoogleCredential(InputStream in, String scopes) throws IOException, GeneralSecurityException { + JsonFactory jsonFactory = new JacksonFactory(); + initHttpTransport(); + return GoogleCredential.fromStream(in, this.httpTransport, jsonFactory).createScoped(Collections.singleton(scopes)); + } + + private void initHttpTransport() throws GeneralSecurityException, IOException { + if (this.httpTransport == null) { + this.httpTransport = GoogleNetHttpTransport.newTrustedTransport(); + } + } +} diff --git a/src/main/java/net/renfei/indexing/ui/MainWindow.form b/src/main/java/net/renfei/indexing/ui/MainWindow.form new file mode 100644 index 0000000..7c964c4 --- /dev/null +++ b/src/main/java/net/renfei/indexing/ui/MainWindow.form @@ -0,0 +1,284 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/src/main/java/net/renfei/indexing/ui/MainWindow.java b/src/main/java/net/renfei/indexing/ui/MainWindow.java new file mode 100644 index 0000000..9e2b18b --- /dev/null +++ b/src/main/java/net/renfei/indexing/ui/MainWindow.java @@ -0,0 +1,298 @@ +package net.renfei.indexing.ui; + +import com.intellij.uiDesigner.core.GridConstraints; +import com.intellij.uiDesigner.core.GridLayoutManager; +import com.intellij.uiDesigner.core.Spacer; +import net.renfei.indexing.service.ExecService; +import net.renfei.sdk.utils.BeanUtils; +import net.renfei.sdk.utils.DateUtils; + +import javax.swing.*; +import javax.swing.text.BadLocationException; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.util.ArrayList; +import java.util.List; + +/** + * 主窗体 + * + * @author renfei + */ +public class MainWindow { + private final ExecService execService = new ExecService(); + public JPanel mainPanel; + public JTextArea urls; + public JTextArea logText; + public JCheckBox chkBaiduPuTong; + public JCheckBox chkGoogle; + public JCheckBox chkBaiDuKuiSu; + public JButton execButton; + public JButton googleJson; + public JCheckBox chkBing; + public JTextField siteUrl; + public JTextField baiduToken; + public JTextField bingToken; + public JTextArea explain; + public JSplitPane rightSplitPane; + public JScrollPane urlsScroPane; + public JScrollPane logsScroPane; + + public void init() { + urlsScroPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED); + logsScroPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED); + execButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + setLog("开始执行"); + exec(); + setLog("执行结束"); + } + }); + googleJson.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + JFileChooser fc = new JFileChooser("/"); + int val = fc.showOpenDialog(null); + if (val == JFileChooser.APPROVE_OPTION) { + googleJson.setText(fc.getSelectedFile().getPath()); + } else { + googleJson.setText("点击选择JSON文件"); + } + } + }); + } + + private void setLog(String log) { + logText.append("\r\n" + DateUtils.getDate("yyyy-MM-dd HH:mm:ss") + " : " + log); + } + + private void exec() { + String site = siteUrl.getText(); + if (BeanUtils.isEmpty(site)) { + setLog("【站点URL】不能为空。"); + return; + } + setLog("获取到【站点URL】:" + site); + try { + if (chkBaiduPuTong.isSelected()) { + execBaidu(site); + } + if (chkBaiDuKuiSu.isSelected()) { + execBaiduKuaiSu(site); + } + if (chkBing.isSelected()) { + execBing(site); + } + if (chkGoogle.isSelected()) { + execGoogle(site); + } + } catch (Exception e) { + setLog("\n[!] 发生错误:\r\n" + e.getMessage() + "\r\n如果您认为不是您的错误,请联系开发者:i@renfei.net。\r\n"); + } + } + + private void execBaidu(String siteUrl) throws BadLocationException, IOException { + setLog("开始执行百度普通收录推送"); + String token = baiduToken.getText(); + if (BeanUtils.isEmpty(token)) { + setLog("【百度Token】为空,跳过执行。"); + } else { + List urlList = getUrlTexts(); + for (String url : urlList + ) { + setLog("推送:" + url); + setLog("结果:" + execService.execBaidu(siteUrl, token, url)); + } + } + } + + private void execBaiduKuaiSu(String siteUrl) throws BadLocationException, IOException { + setLog("开始执行百度快速收录推送"); + String token = baiduToken.getText(); + if (BeanUtils.isEmpty(token)) { + setLog("【百度Token】为空,跳过执行。"); + } else { + List urlList = getUrlTexts(); + for (String url : urlList + ) { + setLog("推送:" + url); + setLog("结果:" + execService.execBaiduKuaiSu(siteUrl, token, url)); + } + } + } + + private void execBing(String siteUrl) throws BadLocationException, IOException { + setLog("开始执行必应推送"); + String token = bingToken.getText(); + if (BeanUtils.isEmpty(token)) { + setLog("【必应Token】为空,跳过执行。"); + } else { + List urlList = getUrlTexts(); + for (String url : urlList + ) { + setLog("推送:" + url); + setLog("结果:" + execService.execBing(siteUrl, token, url)); + } + } + } + + private void execGoogle(String siteUrl) throws BadLocationException, IOException, GeneralSecurityException { + setLog("开始执行谷歌推送"); + String token = googleJson.getText(); + if (BeanUtils.isEmpty(token) || "点击选择JSON文件".equals(token)) { + setLog("【谷歌私钥】为空,跳过执行。"); + } else { + List urlList = getUrlTexts(); + for (String url : urlList + ) { + setLog("推送:" + url); + setLog("结果:" + execService.execGoogle(googleJson.getText(), url)); + } + } + } + + private List getUrlTexts() throws BadLocationException { + List stringList = new ArrayList<>(urls.getRows()); + for (int i = 0; i < urls.getRows() + 1; i++) { + int start = urls.getLineStartOffset(i); + int end = urls.getLineEndOffset(i); + String str = urls.getText(start, end - start).replace("\r\n", "").replace("\n", ""); + if (!BeanUtils.isEmpty(str)) { + if (str.startsWith(siteUrl.getText())) { + stringList.add(str); + } else { + setLog("检测到【" + str + "】与站点URL不符,跳过处理。"); + } + } + } + return stringList; + } + + { +// GUI initializer generated by IntelliJ IDEA GUI Designer +// >>> IMPORTANT!! <<< +// DO NOT EDIT OR ADD ANY CODE HERE! + $$$setupUI$$$(); + } + + /** + * Method generated by IntelliJ IDEA GUI Designer + * >>> IMPORTANT!! <<< + * DO NOT edit this method OR call it in your code! + * + * @noinspection ALL + */ + private void $$$setupUI$$$() { + mainPanel = new JPanel(); + mainPanel.setLayout(new BorderLayout(0, 0)); + final JSplitPane splitPane1 = new JSplitPane(); + splitPane1.setContinuousLayout(true); + splitPane1.setDividerLocation(400); + splitPane1.setDividerSize(5); + mainPanel.add(splitPane1, BorderLayout.CENTER); + rightSplitPane = new JSplitPane(); + rightSplitPane.setDividerLocation(300); + rightSplitPane.setDividerSize(5); + rightSplitPane.setOrientation(0); + splitPane1.setRightComponent(rightSplitPane); + urlsScroPane = new JScrollPane(); + rightSplitPane.setLeftComponent(urlsScroPane); + urls = new JTextArea(); + urls.setEditable(true); + urls.setText("https://www.renfei.net/demo/1"); + urlsScroPane.setViewportView(urls); + logsScroPane = new JScrollPane(); + rightSplitPane.setRightComponent(logsScroPane); + logText = new JTextArea(); + logText.setBackground(new Color(-1118482)); + logText.setEditable(false); + logText.setText("--== 运行日志 ==--"); + logsScroPane.setViewportView(logText); + final JPanel panel1 = new JPanel(); + panel1.setLayout(new GridLayoutManager(21, 2, new Insets(0, 0, 0, 0), 2, 4)); + splitPane1.setLeftComponent(panel1); + final JLabel label1 = new JLabel(); + label1.setText("百度Token:"); + panel1.add(label1, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(63, 16), null, 0, false)); + final JLabel label2 = new JLabel(); + label2.setText("必应Token:"); + panel1.add(label2, new GridConstraints(2, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(63, 16), null, 0, false)); + final Spacer spacer1 = new Spacer(); + panel1.add(spacer1, new GridConstraints(10, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, 1, null, new Dimension(63, 11), null, 0, false)); + final JLabel label3 = new JLabel(); + label3.setText("上报项:"); + panel1.add(label3, new GridConstraints(7, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(63, 16), null, 0, false)); + chkBaiduPuTong = new JCheckBox(); + chkBaiduPuTong.setSelected(true); + chkBaiduPuTong.setText("百度普通收录"); + panel1.add(chkBaiduPuTong, new GridConstraints(7, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + chkBaiDuKuiSu = new JCheckBox(); + chkBaiDuKuiSu.setText("百度快速收录"); + panel1.add(chkBaiDuKuiSu, new GridConstraints(8, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + chkGoogle = new JCheckBox(); + chkGoogle.setText("谷歌搜索"); + panel1.add(chkGoogle, new GridConstraints(10, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + final Spacer spacer2 = new Spacer(); + panel1.add(spacer2, new GridConstraints(11, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, 1, null, new Dimension(63, 11), null, 0, false)); + execButton = new JButton(); + execButton.setText("执行"); + panel1.add(execButton, new GridConstraints(11, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + final JLabel label4 = new JLabel(); + label4.setText("谷歌私钥:"); + panel1.add(label4, new GridConstraints(3, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(63, 16), null, 0, false)); + final Spacer spacer3 = new Spacer(); + panel1.add(spacer3, new GridConstraints(19, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, 1, null, null, null, 0, false)); + final Spacer spacer4 = new Spacer(); + panel1.add(spacer4, new GridConstraints(12, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, 1, null, null, null, 0, false)); + final Spacer spacer5 = new Spacer(); + panel1.add(spacer5, new GridConstraints(13, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, 1, null, null, null, 0, false)); + final JLabel label5 = new JLabel(); + label5.setText("使用说明:"); + panel1.add(label5, new GridConstraints(14, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(63, 16), null, 0, false)); + chkBing = new JCheckBox(); + chkBing.setText("必应搜索"); + panel1.add(chkBing, new GridConstraints(9, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + siteUrl = new JTextField(); + siteUrl.setText("https://www.renfei.net"); + panel1.add(siteUrl, new GridConstraints(0, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(150, -1), null, 0, false)); + final JLabel label6 = new JLabel(); + label6.setText("站点URL:"); + panel1.add(label6, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(63, 16), null, 0, false)); + final Spacer spacer6 = new Spacer(); + panel1.add(spacer6, new GridConstraints(20, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_VERTICAL, 1, GridConstraints.SIZEPOLICY_CAN_GROW, null, new Dimension(11, 4052), null, 0, false)); + baiduToken = new JTextField(); + panel1.add(baiduToken, new GridConstraints(1, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(150, -1), null, 0, false)); + bingToken = new JTextField(); + panel1.add(bingToken, new GridConstraints(2, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(150, -1), null, 0, false)); + final Spacer spacer7 = new Spacer(); + panel1.add(spacer7, new GridConstraints(18, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, 1, null, null, null, 0, false)); + final Spacer spacer8 = new Spacer(); + panel1.add(spacer8, new GridConstraints(17, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, 1, null, null, null, 0, false)); + final Spacer spacer9 = new Spacer(); + panel1.add(spacer9, new GridConstraints(16, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, 1, null, null, null, 0, false)); + final Spacer spacer10 = new Spacer(); + panel1.add(spacer10, new GridConstraints(14, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, 1, null, null, null, 0, false)); + explain = new JTextArea(); + explain.setBackground(new Color(-1118482)); + explain.setEditable(false); + explain.setLineWrap(true); + explain.setText("1.百度Token获取地址: https://ziyuan.baidu.com/linksubmit/index\n2.必应Token获取地址:https://docs.microsoft.com/en-us/bingwebmaster/getting-access\n3.谷歌JSON私钥获取:https://www.renfei.net/posts/1003342\n4.谷歌上报需要本地是翻墙状态,否则网络不通\n5.各个平台的接口提交配额与本工具无关,是各个平台分配给你的;例如百度快速收录是百度站长工具给予的权限,与是否使用本工具无关\n6.本工具不会收集上报用户的Token,本工具代码已开源,欢迎监督,如遇仿制程序上报Token请联系 i@renfei.net"); + explain.setWrapStyleWord(true); + panel1.add(explain, new GridConstraints(15, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_WANT_GROW, null, new Dimension(150, 50), null, 0, false)); + googleJson = new JButton(); + googleJson.setText("点击选择JSON文件"); + panel1.add(googleJson, new GridConstraints(3, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + } + + /** + * @noinspection ALL + */ + public JComponent $$$getRootComponent$$$() { + return mainPanel; + } + +} diff --git a/src/main/java/net/renfei/indexing/ui/MenuBar.java b/src/main/java/net/renfei/indexing/ui/MenuBar.java new file mode 100644 index 0000000..023aa6c --- /dev/null +++ b/src/main/java/net/renfei/indexing/ui/MenuBar.java @@ -0,0 +1,133 @@ +package net.renfei.indexing.ui; + +import net.renfei.indexing.Application; +import net.renfei.sdk.utils.DateUtils; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; + +/** + * 菜单条 + * + * @author renfei + */ +public class MenuBar extends JMenuBar { + public MenuBar() { + add(createFileMenu()); + add(createEditMenu()); + setVisible(true); + } + + private JMenu createFileMenu() { + JMenu menu = new JMenu("系统(S)"); + JMenuItem item; + item = new JMenuItem("工具箱(T)"); + item.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + try { + openURL(new URI("https://www.renfei.net/kitbox?spm=application.indexing.st")); + } catch (URISyntaxException uriSyntaxException) { + JOptionPane.showMessageDialog(null, "打开失败,请手动打开: https://www.renfei.net/kitbox"); + } + } + }); + menu.add(item); + menu.addSeparator(); + item = new JMenuItem("退出(E)"); + item.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + System.exit(0); + } + }); + menu.add(item); + return menu; + } + + private JMenu createEditMenu() { + JMenu menu = new JMenu("帮助(H)"); + JMenuItem item; + item = new JMenuItem("主页(P)"); + item.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + try { + openURL(new URI("https://www.renfei.net/kitbox/indexing?spm=application.indexing.hp")); + } catch (URISyntaxException uriSyntaxException) { + JOptionPane.showMessageDialog(null, "打开失败,请手动打开: https://www.renfei.net/kitbox/indexing"); + } + } + }); + menu.add(item); + JMenu menuOpen = new JMenu("开源(O)"); + item = new JMenuItem("Github"); + item.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + try { + openURL(new URI("https://github.com/renfei/Indexing")); + } catch (URISyntaxException uriSyntaxException) { + JOptionPane.showMessageDialog(null, "打开失败,请手动打开: https://github.com/renfei/Indexing"); + } + } + }); + menuOpen.add(item); + item = new JMenuItem("Gitee"); + item.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + try { + openURL(new URI("https://gitee.com/rnf/Indexing")); + } catch (URISyntaxException uriSyntaxException) { + JOptionPane.showMessageDialog(null, "打开失败,请手动打开: https://gitee.com/rnf/Indexing"); + } + } + }); + menuOpen.add(item); + menu.add(menuOpen); + menu.addSeparator(); + item = new JMenuItem("关于(A)"); + item.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + StringBuilder sb = new StringBuilder("\t\t--== 关于==--\t\t").append("\r\n"); + sb.append("\r\n"); + sb.append("版本号:" + Application.VERSION).append("\r\n"); + sb.append("「Indexing」是一个集合了百度、必应、谷歌搜索引擎网址推送的工具。").append("\r\n"); + sb.append("主要用于即时向搜索引擎蜘蛛推送本站的更新动态,加快蜘蛛程序爬取和更新。").append("\r\n"); + sb.append("\r\n"); + sb.append("Copyright © ").append(DateUtils.getDate("yyyy")).append(" RENFEI.NET All rights reserved.").append("\r\n"); + JOptionPane.showMessageDialog(Application.MAIN_WINDOW.mainPanel, + sb.toString(), "关于", JOptionPane.PLAIN_MESSAGE); + } + }); + menu.add(item); + return menu; + } + + public static void openURL(URI uri) { + try { + Desktop desktop = Desktop.isDesktopSupported() ? Desktop.getDesktop() : null; + if (desktop != null && desktop.isSupported(Desktop.Action.BROWSE)) { + try { + desktop.browse(uri); + } catch (Exception e) { + e.printStackTrace(); + } + } else { + JOptionPane.showMessageDialog(null, "打开失败,请手动打开:" + uri); + } + } catch (Exception e) { + e.printStackTrace(); + JOptionPane.showMessageDialog(null, "打开失败,请手动打开:" + uri); + } + } +}