正式开始

请全程在Windows上操作

我们首先需要安装Scoop,这是一个适用于Windows的包管理器,个人认为非常好用

Scoop默认会安装到C盘,如果你想要换盘请按需更改

1
2
3
4
$env:SCOOP='D:\Scoop'
$env:SCOOP_GLOBAL='D:\ScoopApps'
[Environment]::SetEnvironmentVariable('SCOOP', $env:SCOOP, 'User')
[Environment]::SetEnvironmentVariable('SCOOP_GLOBAL', $env:SCOOP_GLOBAL, 'Machine')

安装Scoop:

1
2
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
Invoke-RestMethod -Uri https://get.scoop.sh | Invoke-Expression

如果你以管理员的身份会安装失败,请切换为普通用户。若想强制以管理员身份安装Scoop请使用

github原帖

出于安全考虑,默认情况下已禁用管理员控制台下的安装。如果您知道自己在做什么并希望以管理员身份安装Scoop,请下载安装程序并在提升的控制台中手动执行它,使用 -RunAsAdmin 参数。以下是示例:

1
2
3
4
irm get.scoop.sh -outfile 'install.ps1'
.\install.ps1 -RunAsAdmin [-OtherParameters ...]
# 如果你想要一行解决:
iex "& {$(irm get.scoop.sh)} -RunAsAdmin"

安装Hugo框架:

1
scoop install hugo

然后选择一个你喜欢的文件夹创建你的站点。 myblog 即你的站点文件夹名称

1
2
hugo new site myblog
cd myblog

安装PaperMod主题:

1
git clone https://github.com/adityatelange/hugo-PaperMod.git themes/PaperMod

站点根目录会有一个 hugo.toml。我推荐使用YAML。将文件重命名为 hugo.yaml。粘贴并更改以下内容

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
baseURL: "https://站点url"
title: "网站标题"
LanguageCode: "zh-CN"
theme: "PaperMod"

# 启用首页个人简介展示
params:
# 是否启用评论。你需要自己配置,或者直接引入Giscus等评论系统
comments: false
# 是否显示代码复制按钮
ShowCodeCopyButtons: true
# 是否显示面包屑导航
ShowBreadCrumbs: false
# 是否显示阅读时间
ShowReadingTime: true
# 是否显示分享按钮
ShowShareButtons: true
# 分享按钮配置
# ShareButtons: ["linkedin", "twitter"]
# 是否禁用主题切换按钮
disableThemeToggle: false
assets:
favicon: "/你的/网站图标.jpg" # 需要在static文件夹放置对应的图片
iconHeight: 35
# 首页信息配置
homeInfoParams:
Title: "首页展示的标题"
Content: >
首页展示的文本

# 设置网站头像和首页头像
profileMode:
enabled: false # 设为 true 将完全替换 homeInfoParams

# 网站头像设置 (显示在导航栏)
label:
text: "左上角显示的文本"
icon: "/你的/左上角显示的图片.jpg" # 这将显示在导航栏标题旁边。需要在static文件夹放置对应的图片
iconHeight: 35

# 社交图标 (显示在简介下方)
socialIcons:
- name: bilibili
url: ""
- name: github
url: ""
- name: telegram
url: ""
# 可以添加更多社交图标 https://github.com/adityatelange/hugo-PaperMod/wiki/Icons

# 顶部导航栏的快捷链接
menu:
main:
- identifier: categories
name: 分类
url: /categories/
weight: 10
- identifier: tags
name: 标签
url: /tags/
weight: 20
- identifier: archives
name: 归档
url: /archives/
weight: 30
- identifier: search
name: 搜索
url: /search/
weight: 40
# 可以添加更多导航链接。weight的值越高排序越靠后

# 如果要启用搜索功能,需要添加这个
outputs:
home:
- HTML
- RSS
- JSON # 必须,用于搜索功能

然后我们需要分别配置分类、标签、归档和搜索页

创建 content\categories\_index.md 写入:

1
2
3
4
---
title: 分类
layout: categories
---

创建 content\tags\_index.md 写入:

1
2
3
4
---
title: 标签
layout: tags
---

创建 content\archives.md 写入:

1
2
3
4
---
title: 归档
layout: archives
---

创建 content\search.md 写入:

1
2
3
4
---
title: "搜索"
layout: "search"
---

然后我们要更改默认的文章创建模板

archetypes\default.md 写入:

1
2
3
4
5
6
7
8
9
10
11
---
title: {{ replace .File.ContentBaseName "-" " " | title }}
published: {{ .Date }}
summary: "文章简介"
cover:
image: "文章封面图。也支持HTTPS"
tags: [标签1, 标签2]
categories: '文章所处的分类'
draft: false
lang: ''
---

接下来我们就可以通过命令来创建文章,并开始写作了。注意,最终构建的文章URL是你的文章的文件名。比如:https://你的网站.com/posts/first 所以文章文件名尽量简短,这并不会影响你的文章标题

1
hugo new posts/first.md

当我们写完一篇文章想要预览网站,可以使用

1
hugo server

当我们想要将站点发布到Vercel、Cloudflare Pages等静态网站托管平台可以将我们的 myblog 作为一个Git存储库提交到Github

根目录:./

输出目录:public

构建命令:hugo --gc

环境变量(适用于Vercel): Key:HUGO_VERSION Value:0.145.0


对象存储存图中间件代码:

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
import keyboard
import pyperclip
from PIL import ImageGrab, Image
import io
import boto3
from botocore.config import Config
import time
import uuid
import pyautogui
import os
from io import BytesIO
# 示例配置
# # R2 配置
# R2_CONFIG = {
# 'account_id': '11111111111111111',
# 'access_key_id': '11111111111111111',
# 'secret_access_key': '11111111111111111',
# 'bucket_name': '11111111111111111'
# }

# # OSS 配置
# OSS_CONFIG = {
# 'url': 'oss.onani.cn',
# 'prefix': '/fuwari-blog/img'
# }
#########################################################
# R2 配置
R2_CONFIG = {
'account_id': '',
'access_key_id': '',
'secret_access_key': '',
'bucket_name': ''
}

# OSS 配置
OSS_CONFIG = {
'url': '',
'prefix': ''
}
#########################################################
def init_r2_client():
"""初始化 R2 客户端"""
return boto3.client(
's3',
endpoint_url=f'https://{R2_CONFIG["account_id"]}.r2.cloudflarestorage.com',
aws_access_key_id=R2_CONFIG['access_key_id'],
aws_secret_access_key=R2_CONFIG['secret_access_key'],
config=Config(signature_version='s3v4'),
region_name='auto'
)

def get_image_from_clipboard():
"""从剪贴板获取图片"""
try:
image = ImageGrab.grabclipboard()
if image is None:
return None

# 如果是列表(多个文件),取第一个
if isinstance(image, list):
if len(image) > 0:
# 如果是图片文件路径,打开它
try:
return Image.open(image[0])
except Exception as e:
print(f"打开图片文件失败: {e}")
return None
return None

# 如果直接是 Image 对象
if isinstance(image, Image.Image):
return image

return None
except Exception as e:
print(f"获取剪贴板图片失败: {e}")
return None

def convert_to_webp(image):
"""将图片转换为 webp 格式"""
if not image:
return None

try:
buffer = BytesIO()
# 确保图片是 RGB 模式
if image.mode in ('RGBA', 'LA'):
background = Image.new('RGB', image.size, (255, 255, 255))
background.paste(image, mask=image.split()[-1])
image = background
elif image.mode != 'RGB':
image = image.convert('RGB')

image.save(buffer, format="WEBP", quality=80)
return buffer.getvalue()
except Exception as e:
print(f"转换图片失败: {e}")
return None

def upload_to_r2(image_data):
"""上传图片到 R2"""
if not image_data:
return None

client = init_r2_client()

# 生成基础文件名
base_filename = f"{uuid.uuid4()}.webp"
filename = base_filename

try:
# 检查文件是否已存在
attempt = 1
while True:
try:
# 尝试获取文件信息,如果文件存在会返回数据,不存在会抛出异常
client.head_object(
Bucket=R2_CONFIG['bucket_name'],
Key=f"{OSS_CONFIG['prefix'].strip('/')}/{filename}"
)
# 如果文件存在,修改文件名
name_without_ext = base_filename.rsplit('.', 1)[0]
filename = f"{name_without_ext}_{attempt}.webp"
attempt += 1
print(f"文件名已存在,尝试重命名为: {filename}")
except client.exceptions.ClientError as e:
# 如果是 404 错误,说明文件不存在,可以使用这个文件名
if e.response['Error']['Code'] == '404':
break
raise e # 其他错误则抛出

# 上传文件
client.put_object(
Bucket=R2_CONFIG['bucket_name'],
Key=f"{OSS_CONFIG['prefix'].strip('/')}/{filename}",
Body=image_data,
ContentType='image/webp'
)
return filename
except Exception as e:
print(f"上传失败: {e}")
return None

def generate_markdown_link(filename):
"""生成 Markdown 图片链接"""
if not filename:
return None

url = f"https://{OSS_CONFIG['url']}{OSS_CONFIG['prefix']}/{filename}"
return f"![]({url})"

def type_markdown_link(markdown_link):
"""模拟键盘输入 Markdown 链接"""
if not markdown_link:
return

pyperclip.copy(markdown_link)
pyautogui.hotkey('ctrl', 'v')

def handle_upload():
"""处理图片上传的主函数"""
print(f"\n[{time.strftime('%Y-%m-%d %H:%M:%S')}] 收到粘贴请求")

print("正在检查剪贴板...")
# 获取剪贴板图片
image = get_image_from_clipboard()
if not image:
print("❌ 剪贴板中没有图片")
return
print("✅ 获取到剪贴板图片")

# 转换为 webp
print("正在转换为 WebP 格式...")
image_data = convert_to_webp(image)
if not image_data:
print("❌ 图片转换失败")
return
print(f"✅ 转换完成,大小: {len(image_data)/1024:.2f}KB")

# 上传到 R2
print("正在上传到 R2...")
filename = upload_to_r2(image_data)
if not filename:
print("❌ 上传失败")
return
print(f"✅ 上传成功,文件名: {filename}")

# 生成并输入 Markdown 链接
markdown_link = generate_markdown_link(filename)
if markdown_link:
print(f"生成的 URL: https://{OSS_CONFIG['url']}{OSS_CONFIG['prefix']}/{filename}")
print(f"模拟键入: {markdown_link}")
type_markdown_link(markdown_link)
print("✅ 操作完成")

def main():
"""主函数"""
print("=" * 50)
print("R2 图片上传插件已启动")
print(f"当前配置:")
print(f"- OSS 域名: {OSS_CONFIG['url']}")
print(f"- 存储路径: {OSS_CONFIG['prefix']}")
print(f"- R2 存储桶: {R2_CONFIG['bucket_name']}")
print("使用 Ctrl+Alt+V 上传剪贴板中的图片")
print("=" * 50)

# 注册快捷键
keyboard.add_hotkey('ctrl+alt+v', handle_upload)

# 保持程序运行
keyboard.wait()

if __name__ == "__main__":
main()