背景

由于博客的碎碎念功能采用读取短链接网站下的 JSON 文件形式,为了方便维护 JSON 文件,编写了一个 Java 程序来更新 API 下的 JSON 内容。

JSON 数据格式

示例 JSON 数据结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[
{
"date": "2024-12-10 21:33:26",
"key": "key-1",
"content": "测试1 <br><img src=\"https://xxx.jpg\" alt=\"测试1图片\">",
"tags": [
"tags1",
"tags2"
]
},
{
"date": "2024-12-11 22:33:26",
"key": "key-2",
"content": "GitHub 的 Codespaces 好慢啊,还是转本地开发好了,但又因为 .gitignore 搞得得 ZIP 下整个项目而不是 git clone,烦死。",
"tags": [
"codespaces",
"github"
]
}
]
  • date:使用 new Date() 生成实时时间。
  • key:按 - 分割,左侧不变,右侧 ID 递增。
  • content:用户实时输入,支持换行,输入 exit 结束输入。
  • tags:支持多标签输入,exit 结束输入。
  • content 是否插入图片会有额外询问,alt 标签默认值为 文章图片

Java 代码实现

以下是具体实现代码,涉及网站的部分做了模糊处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.JSONArray;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.jetbrains.annotations.NotNull;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.URL;
import java.net.URLConnection;
import java.text.SimpleDateFormat;
import java.util.*;

/**
* @Description: TODO
* @author: scott
* @date: 2021年12月13日 22:26
*/
class SuisuinianUpdate {

public static String URL = "https://shortlink.xxx.us.kg/short_api";
public static String SUISUINIAN_URL = "https://shortlink.xxx.us.kg/suisuinian.json";
public static SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static Scanner scanner = new Scanner(System.in);

public static String updateSuisuinian() {

OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(SUISUINIAN_URL)
.build();

OutputStreamWriter out = null;
BufferedReader in = null;
StringBuilder result = new StringBuilder("");
try {
List<Map<String, Object>> articles = new ArrayList<>();
String key = "";
// 获取碎碎念内容
Response suisuinian = client.newCall(request).execute();
if (suisuinian.isSuccessful() && suisuinian.body() != null) {
String suisuinianBody = suisuinian.body().string(); // 只读取一次响应体
// 使用 fastjson 解析 JSON 字符串
JSONArray jsonArray = JSON.parseArray(suisuinianBody);

// 遍历 JSON 数组,将每篇文章封装成一个 Map
for (int i = 0; i < jsonArray.size(); i++) {
JSONObject articleNode = jsonArray.getJSONObject(i);
Map<String, Object> article = new HashMap<>();
article.put("date", articleNode.getString("date"));
article.put("key", articleNode.getString("key"));
key = articleNode.getString("key");
article.put("content", articleNode.getString("content"));

// 处理 tags,如果存在的话
if (articleNode.containsKey("tags")) {
List<String> tags = articleNode.getJSONArray("tags").toJavaList(String.class);
article.put("tags", tags); // 将 tags 添加到 Map 中
}
articles.add(article); // 将每篇文章加入列表
}

if (articles.isEmpty()) {
return "碎碎念内容获取为空";
}
} else {
return "GET request failed. Code: " + suisuinian.code();
}

// 输入文章内容
String finalContent = "";
while (finalContent.isEmpty()) {
System.out.print("请输入文章内容(输入 'exit' 结束): \n");
StringBuilder contentBuilder = new StringBuilder(); // 用于拼接多行输入内容
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
if ("exit".equalsIgnoreCase(line.trim())) {
break; // 如果输入 "exit" 则退出
}
contentBuilder.append(line).append("\n");
}
finalContent = contentBuilder.toString().trim();
if (finalContent.isEmpty()) {
System.out.println("输入的文章内容不能为空,请重新输入。\n");
}
}
System.out.println("您输入的文章内容是:\n" + finalContent);

// 询问是否添加图片
System.out.print("是否需要为文章添加图片?(输入 'y' 或 'n'): ");
String addImage = scanner.nextLine().trim();
if ("y".equalsIgnoreCase(addImage)) {
System.out.println("请输入图片地址(输入 'exit' 结束):");
StringBuilder imageHtmlBuilder = new StringBuilder(); // 用于拼接多张图片的 HTML
boolean hasImage = false; // 标记是否输入了至少一张图片

while (scanner.hasNextLine()) {
String imageUrl = scanner.nextLine().trim();
if ("exit".equalsIgnoreCase(imageUrl)) {
break; // 输入 "exit" 停止输入图片地址
}
if (!imageUrl.isEmpty()) {
// 生成 HTML 图片标签
imageHtmlBuilder.append("<br><img src=\"")
.append(imageUrl)
.append("\" alt=\"文章图片\">");
hasImage = true; // 标记至少有一张图片
} else {
System.out.println("图片地址为空,请重新输入或输入 'exit' 结束。\n");
}
}

if (hasImage) {
// 将所有图片 HTML 代码追加到文章内容
finalContent += imageHtmlBuilder.toString();
} else {
System.out.println("未添加任何图片。\n");
}
} else {
System.out.println("跳过添加图片。\n");
}

// 输出最终内容
System.out.println("您输入的文章内容是:\n" + finalContent);

// 获取最后一条数据的 key
String newKey = "";
// 解析当前 key 中的数字部分
String[] parts = key.split("-");
if (parts.length == 2) {
int currentKeyNumber = Integer.parseInt(parts[1]);
int newKeyNumber = currentKeyNumber + 1;
// 构建新的 key
newKey = "key-" + newKeyNumber; // 更新 key
}

// tag输入
List<String> tagList = new ArrayList<>(); // 存储与 tags 相关的 key
System.out.println("请输入分类(tag),输入 'exit' 退出:");
while (scanner.hasNextLine()) {
String line = scanner.nextLine().trim();
if ("exit".equalsIgnoreCase(line)) {
break;
}
if (!line.isEmpty()) {
tagList.add(line); // 添加非空行
}
}

//组装新数据
String param = initNewJson(newKey, finalContent, tagList, articles);

//初始化conn数据
URLConnection conn = initConn();

// 获取URLConnection对象对应的输出流
out = new OutputStreamWriter(conn.getOutputStream(), "UTF-8");
// 发送请求参数
out.write(param);
// flush输出流的缓冲
out.flush();
// 定义BufferedReader输入流来读取URL的响应
in = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
String line1;
while ((line1 = in.readLine()) != null) {
result.append(line1);
}
} catch (Exception e) {
System.out.println("发送 POST 请求出现异常!" + e);
e.printStackTrace();
} finally {
if (out != null) {
try {
out.close();
} catch (Exception ex) {
}
}
if (in != null) {
try {
in.close();
} catch (Exception ex) {
}
}
}

return result.toString();
}

private static String initNewJson(String newKey, String finalContent, List<String> tagList, List<Map<String, Object>> articles) {
// 创建新的文章 Map
Map<String, Object> newArticle = new HashMap<>();
newArticle.put("date", SDF.format(new Date()));
newArticle.put("key", newKey);
newArticle.put("content", finalContent);
if (!tagList.isEmpty()) {
newArticle.put("tags", tagList);
}
// 增加到原 list
articles.add(newArticle);

// 转换为 JSON 字符串并返回
String newOrgiUrl = JSON.toJSONString(articles);

//组装json
Map<String, String> map = new HashMap<>();
map.put("orgi_url", newOrgiUrl);
map.put("short_code", "suisuinian.json");
map.put("type", "text");
String param = JSON.toJSONString(map);
return param;
}

@NotNull
private static URLConnection initConn() throws IOException {
URL realUrl = new URL(URL);
// 打开和URL之间的连接
URLConnection conn = realUrl.openConnection();
// 设置通用的请求属性
conn.setRequestProperty("Content-Type", "application/json");
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("Connection", "keep-alive");
conn.setRequestProperty("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36");
conn.setRequestProperty("accept-language", "zh-CN,zh;q=0.9");
conn.setRequestProperty("origin", "https://shortlink.xxx.us.kg");//我的短链接网站
conn.setRequestProperty("cache-control", "max-age=0");
conn.setRequestProperty("sec-ch-ua", "\"Google Chrome\";v=\"131\", \"Chromium\";v=\"131\", \"Not_A Brand\";v=\"24\"");
conn.setRequestProperty("sec-ch-ua-mobile", "?0");
conn.setRequestProperty("sec-ch-ua-platform", "\"Windows\"");
conn.setRequestProperty("sec-fetch-dest", "empty");
conn.setRequestProperty("sec-fetch-mode", "cors");
conn.setRequestProperty("sec-fetch-site", "same-origin");

// 发送 POST 请求
conn.setDoOutput(true);
conn.setDoInput(true);
return conn;
}

public static void main(String[] args) {
String response = updateSuisuinian();
Map resultMap = JSON.parseObject(response, HashMap.class);
String shortUrl = (String) resultMap.get("short_url");
System.out.println(shortUrl + "\n");
System.out.println("按P键退出程序...");
// 等待用户输入
while (!scanner.nextLine().equalsIgnoreCase("p")) {
// 退出程序
System.out.println("输入错误,请按 P 键退出程序...");
}
scanner.close();
}
}

代码优化点:

  • 采用 try-with-resources 处理资源,减少资源泄露风险。
  • 逻辑拆分为多个方法,提高可读性。
  • 代码风格统一,提高可维护性。

封装打包为 EXE

使用 exe4j 进行打包。

1. 构建 JAR 文件

  • 在 IntelliJ IDEA 中构建 JAR 文件。

1742277979778

image-20250318140815097

image-20250318140842885

  • build项目

image-20250318141642397

image-20250318141655936

  • 然后在项目目录下out\artifacts\xxx_jar\下找到jar文件。

image-20250318141752561

2. 使用 exe4j 进行打包

  1. 选择 JAR 文件并配置 exe4j。

  2. 设置入口类和 JVM 选项。

  3. 生成 EXE 文件。

    下面跟随图片操作:

在这里插入图片描述

image-20250318142039930

image-20250318142026877

image-20250318142147421

image-20250318142156485

image-20250318142210776

image-20250318142245519

image-20250318142325634

image-20250318142346302

image-20250318142419017

然后一路next,就完成了。