【优雅写博客】hexo图片相对路径与开头模板处理

在hexo静态博客部署中,图片的显示一直困扰我,之前几次搞博客也常常遇到图片显示不了的问题,今天来解决一下。还有对于butterfly主题的博文开头的自动生成,因为我写博客一般习惯是手动创建markdown文件,写那个开头觉得挺烦的,索性就一起解决了。

图片展示

前提环境

  • hexo+butterfly
  • typora
  • jdk8
  • idea

前置了解

首先,markdown的图片可以通过以下两种方式展示:

1
![图片描述](图片路径)
1
<img src="图片路劲" alt="图片描述" />

但是很遗憾,hexo并不支持。所以,比较痛苦的,我们在推博文上去时候必须把图片路径转化成hexo识别的了的。

下面这种就是hexo能够识别的语法

1
{% asset_img 8248398428fb42d78c3313cf4578e10f.png 图片描述 %}

准备工作

  • hexo默认不支持文章资源文件夹,需要先去开下配置。

在hexo中使用文章资源文件夹需要在config.yaml文件中更改一下配置:

1
post_asset_folder: true

当该配置被应用后,使用hexo new命令创建新文章时,会生成相同名字的文件夹,也就是文章资源文件夹。

  • 在typora中修改设置,改变图片的复制存储路径

然后这样我们每次粘贴图片就可以自动放入与md文件同名的文件夹了。

  • 了解主题的博客开头格式,如我在用的butterfly主题就是下面这个开头
1
2
3
4
5
6
---
title:
date:
tags: [未分类]
categories: [未分类]
---

这里是个易错点,我配了好久。就是冒号要是英文的,然后冒号后面需要有一个空格。分类和标签要加[],如果有多个分类用","隔开。当然,具体主题应该有样例可以参照。

实现markdown适应hexo

简单来说就是用java进行正则的匹配替换,然后加入模板开头。

下面就是java代码的实现。

  • 这个类运行第一次会把模板写入没写模板的markdown中,注意:输入文件输出文件都会被写入,同时替换两种markdown的图片显示格式成Hexo能够识别的格式。然后运行第二次会把模板中的空值写入默认值
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
import java.io.*;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.regex.*;

public class MarkdownImageConverter {
public static void main(String[] args) {
String inputFolder = "A:\\usr\\MyBlog\\RawBlog"; // 输入文件夹路径
String outputFolder = "A:\\usr\\MyBlog\\CrazylycheeBlog\\source\\_posts"; // 输出文件夹路径

addTheHexoBegin(inputFolder);

File inputDir = new File(inputFolder);
File outputDir = new File(outputFolder);

if (!outputDir.exists()) {
outputDir.mkdirs();
}

File[] files = inputDir.listFiles((dir, name) -> name.endsWith(".md"));

if (files != null) {
for (File file : files) {
try {
convertFile(file, outputDir);
copyRelatedFolder(file, inputDir, outputDir);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

private static void convertFile(File inputFile, File outputDir) throws IOException {
String content = new String(Files.readAllBytes(inputFile.toPath()));

String name = inputFile.getName().replaceAll("\\.md$", "");

String redix1 = "<img src=\""+name+"/([^/][^\"]+)\" alt=\"([^\"]+)\" />";
String redix2 ="!\\[([^\\]]+)\\]\\("+name+"/([^/][^\\)]+)\\)";

// 处理 HTML 格式的 <img src="Logback/..."> 标签
Pattern htmlPattern = Pattern.compile(redix1);
Matcher htmlMatcher = htmlPattern.matcher(content);
StringBuffer sb = new StringBuffer();

while (htmlMatcher.find()) {
String replacement = String.format("{%% asset_img %s %s %%}", htmlMatcher.group(1), htmlMatcher.group(2));
htmlMatcher.appendReplacement(sb, replacement);
}
htmlMatcher.appendTail(sb);

// 处理 Markdown 格式的 ![在这里插入图片描述](Logback/...)
String updatedContent = sb.toString();
sb.setLength(0); // 清空 StringBuffer

Pattern markdownPattern = Pattern.compile(redix2);
Matcher markdownMatcher = markdownPattern.matcher(updatedContent);

while (markdownMatcher.find()) {
String replacement = String.format("{%% asset_img %s %s %%}", markdownMatcher.group(2), markdownMatcher.group(1));
markdownMatcher.appendReplacement(sb, replacement);
}
markdownMatcher.appendTail(sb);

File outputFile = new File(outputDir, inputFile.getName());
try (BufferedWriter writer = new BufferedWriter(new FileWriter(outputFile))) {
writer.write(sb.toString());
}

System.out.println("Processed " + inputFile.getName());
}

private static void copyRelatedFolder(File inputFile, File inputDir, File outputDir) throws IOException {
String baseName = inputFile.getName().replaceAll("\\.md$", "");
File sourceFolder = new File(inputDir, baseName);
File destinationFolder = new File(outputDir, baseName);

if (sourceFolder.exists() && sourceFolder.isDirectory()) {
copyDirectory(sourceFolder.toPath(), destinationFolder.toPath());
System.out.println("Copied folder " + baseName);
}
}

private static void copyDirectory(Path source, Path target) throws IOException {
Files.walkFileTree(source, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
Path targetDir = target.resolve(source.relativize(dir));
try {
Files.createDirectories(targetDir);
} catch (FileAlreadyExistsException e) {
if (!Files.isDirectory(targetDir)) throw e;
}
return FileVisitResult.CONTINUE;
}

@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.copy(file, target.resolve(source.relativize(file)), StandardCopyOption.REPLACE_EXISTING);
return FileVisitResult.CONTINUE;
}
});
}

public static void addTheHexoBegin(String directoryPath) {

String beginFilePath = "src/begin.txt"; // begin.txt 文件路径

try {
List<String> beginLines = Files.readAllLines(Paths.get(beginFilePath));
String beginContent = String.join(System.lineSeparator(), beginLines) + System.lineSeparator();

Files.list(Paths.get(directoryPath))
.filter(path -> path.toString().endsWith(".md"))
.forEach(path -> {
try {
List<String> lines = Files.readAllLines(path);
if (lines.isEmpty() || !lines.get(0).trim().equals("---")) {
System.out.println("Updating file: " + path);
addHeaderToFile(path, beginContent);
}else {
checkAndAddTitle(path, lines);
checkAndAddDate(path, lines);
}
} catch (IOException e) {
e.printStackTrace();
}
});
} catch (IOException e) {
e.printStackTrace();
}
}

private static void addHeaderToFile(Path filePath, String headerContent) throws IOException {
List<String> originalLines = Files.readAllLines(filePath);
String originalContent = String.join(System.lineSeparator(), originalLines);

try (BufferedWriter writer = Files.newBufferedWriter(filePath)) {
writer.write(headerContent);
writer.write(originalContent);
}
}

private static void checkAndAddTitle(Path filePath, List<String> lines) throws IOException {
boolean titleFound = false;
boolean titleEmpty = false;
int titleIndex = -1;

for (int i = 0; i < lines.size(); i++) {
String line = lines.get(i).trim();
if (line.startsWith("title:")) {
titleFound = true;
if (line.equals("title:") || line.equals("title: ")) {
titleEmpty = true;
titleIndex = i;
}
break;
}
}

if (titleFound && titleEmpty) {
String fileName = filePath.getFileName().toString();
String baseName = fileName.substring(0, fileName.lastIndexOf('.'));
lines.set(titleIndex, "title: " + baseName);

try (BufferedWriter writer = Files.newBufferedWriter(filePath)) {
for (String line : lines) {
writer.write(line);
writer.newLine();
}
}
System.out.println("Added file name to title in file: " + filePath);
}
}

private static void checkAndAddDate(Path filePath, List<String> lines) throws IOException {
boolean dateFound = false;
boolean dateEmpty = false;
int dateIndex = -1;

for (int i = 0; i < lines.size(); i++) {
String line = lines.get(i).trim();
if (line.startsWith("date:")) {
dateFound = true;
if (line.equals("date:") || line.equals("date: ")) {
dateEmpty = true;
dateIndex = i;
}
break;
}
}

if (dateFound && dateEmpty) {
//设置为今天
String date = new SimpleDateFormat("yyyy-MM-dd").format(new Date());

lines.set(dateIndex, "date: " + date);

try (BufferedWriter writer = Files.newBufferedWriter(filePath)) {
for (String line : lines) {
writer.write(line);
writer.newLine();
}
}
System.out.println("Added file name to title in file: " + filePath);
}
}

}

下面就是begin.txt的内容了

1
2
3
4
5
6
---
title:
date:
tags: [未分类]
categories: [未分类]
---

下面的代码是用来删除加错的开头的,别问为啥有,问就是错了很多次。。。X﹏X

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
import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;

public class MarkdownHeaderRemover {
public static void main(String[] args) {
String directoryPath = "A:\\usr\\MyBlog\\RawBlog"; // Markdown 文件所在目录
int linesToDelete = 6; // 需要删除的行数,包括 YAML 的分隔符

try {
Files.list(Paths.get(directoryPath))
.filter(path -> path.toString().endsWith(".md"))
.forEach(path -> {
try {
List<String> lines = Files.readAllLines(path);
removeHeader(path, lines, linesToDelete);
} catch (IOException e) {
e.printStackTrace();
}
});
} catch (IOException e) {
e.printStackTrace();
}
}

private static void removeHeader(Path filePath, List<String> lines, int linesToDelete) throws IOException {
int headerStartIndex = -1;

// 找到 YAML 前端元数据的开始
for (int i = 0; i < lines.size(); i++) {
if (lines.get(i).trim().equals("---")) {
headerStartIndex = i;
break;
}
}

// 如果找到了 YAML 前端元数据的开始,并且可以删除包括 --- 在内的指定行数
if (headerStartIndex != -1 && lines.size() > headerStartIndex + linesToDelete - 1) {
lines.subList(headerStartIndex, headerStartIndex + linesToDelete).clear();
try (BufferedWriter writer = Files.newBufferedWriter(filePath)) {
for (String line : lines) {
writer.write(line);
writer.newLine();
}
}
System.out.println("Removed header from file: " + filePath);
}
}
}

然后记得备份!记得备份!记得备份!重要的说三次。里面的要删除行数变量记得改

最后

处理过的就可以直接hexo g和hexo s看到图片了。