构建博客系统

Posted by 谌中钱 on 2025-01-18


使用 Hugo 框架

Hugo 是最流行的开源静态站点生成器之一。凭借其惊人的速度和灵活性,Hugo 让搭建网站再次变得有趣。

安装 Hugo

配置 Hugo 主题

  • Hugo 主题列表: https://themes.gohugo.io
  • 配置方法:
    • 下载 主题源代码 放到 站点 的 ./themes 文件夹下
    • 然后修改 根目录 下的 站点配置 hugo.toml
    • 启动站点
  • 注意:
    • 如果没有配置主题:
      • 新建的站点会找不到页面文件,导致报错。
      • blog 容器无法成功启动,nginx 容器会报 服务器内部错误。

使用 hugo-theme-cleanwhite 主题

站点配置 (参考主题演示站点)
  • 站点配置 是参考 主题演示站点的站点配置 进行修改或复制。
  • 注意:
    • 根目录下的 content 文件夹是用来存放 文章 或 页面 文件:可参考 主题演示站点 的 写法
    • 根目录下的 static 文件夹是用来存放 静态资源
主题默认版本
  • 默认版本存在错误:
    • 120行:add 后面的 空格 需要删除,否则启动会报错: unmarshal failed: toml: expected character ]
    • 130行:add 后面的 空格 需要删除,否则启动会报错: unmarshal failed: toml: expected character ]

hugo.toml

  1baseurl = "https://zhaohuabing.com"
  2title = "Huabing Blog"
  3theme = "hugo-theme-cleanwhite"
  4languageCode = "en-us"
  5preserveTaxonomyNames = true
  6hasCJKLanguage = true
  7
  8[services]
  9  # Enable comments by entering your Disqus shortname
 10  [services.disqus]
 11    shortname = ""
 12  [services.googleAnalytics]
 13    id = ""
 14
 15[pagination]
 16  pagerSize = 5 # frontpage pagination
 17
 18[outputs]
 19home = ["HTML", "RSS", "Algolia"]
 20
 21[params]
 22  header_image = "img/home-bg-jeep.jpg"
 23  SEOTitle = "赵化冰的博客 | Zhaohuabing Blog"
 24  description = "赵化冰,程序员, 开源爱好者,生活探险家 | 这里是 赵化冰 的博客,与你一起发现更大的世界。"
 25  keyword = "赵化冰, zhaohuabing, Zhaohuabing, , 赵化冰的网络日志, 赵化冰的博客, Zhaohuabing Blog, 博客, 个人网站, 互联网, Web, 云原生, PaaS, Istio, Kubernetes, 微服务, Microservice"
 26  slogan = "路在脚下,心向远方"
 27  upstreamAttribution = true
 28
 29  image_404 = "img/404-bg.jpg"
 30  title_404 = "你来到了没有知识的荒原 :("
 31  omit_categories = false
 32
 33  # leancloud storage for page view counter
 34  page_view_counter = false
 35  leancloud_app_id = ""
 36  leancloud_app_key = ""
 37
 38  # algolia site search
 39  algolia_search = true
 40  algolia_appId = ""
 41  algolia_indexName = ""
 42  algolia_apiKey = ""
 43
 44  # Sidebar settings
 45  sidebar_about_description = "Software Developer, Open Source Enthusiast and Life Adventurer"
 46  #sidebar_avatar = "img/avatar-zhaohuabing.jpg"      # use absolute URL, seeing it's used in both `/` and `/about/`
 47  sidebar_avatar = "img/zhaohuabing.png"      # use absolute URL, seeing it's used in both `/` and `/about/`
 48
 49  featured_tags = true 
 50  featured_condition_size = 1 
 51
 52  # Baidu Analytics
 53  ba_track_id = ""
 54  
 55  # We need a proxy to access Disqus api in China
 56  # Follow https://github.com/zhaohuabing/disqus-php-api to set up your own disqus proxy
 57  disqus_proxy = ""
 58  disqus_site = ""
 59
 60  # Twikoo comments
 61  # Follow https://twikoo.js.org/ to set up your own env_id
 62  twikoo_env_id = ""
 63
 64  # Artalk comments
 65  # Follow https://artalk.js.org/ to set up your own server
 66  artalk_enable = true
 67  artalk_server = "https://xxx.xxx.com"
 68  artalk_site = "xxx blog"
 69
 70  #Enable wechat pay & alipay to allow readers send reward money if they like the articles 
 71  reward = true 
 72  # reward_guide = "如果这篇文章对你有用,请随意打赏" 
 73
 74  friends = true
 75  bookmarks = false
 76  about_me = true
 77
 78  # Include any custom CSS and/or JS files, url or relative to /static folder
 79  #custom_css = ["css/lightbox.css", "https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.7.2/animate.min.css", "css/main.css"]
 80  #custom_js = ["js/lightbox.js", "https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js", "js/main.js"]
 81
 82  [params.social]
 83  rss            = true 
 84  email          = "youremail@gmail.com"
 85  #facebook      = "full profile url in facebook"
 86  #googleplus    = "full profile url in googleplus"
 87  #twitter       = "full profile url in twitter"
 88  linkedin       = "https://www.linkedin.com/in/yourlinkedinid"
 89  stackoverflow  = "https://stackoverflow.com/users/yourstackoverflowid"
 90  #instagram     = "full profile url in instagram"
 91  github         = "https://github.com/yourgithub"
 92  wechat         = "your wechat qr code image"
 93  #medium        = "full profile url in medium"
 94  #slack         = "full workspace url in slack"
 95  #pinterest     = "full profile url in pinterest"
 96  #reddit        = "full profile url in reddit"
 97  #gitlab        = "full profile url in gitlab"
 98  #mastodon      = "full profile url in mastodon"
 99  #keybase       = "full profile url in keybase"
100  #xing          = "https://www.xing.com/profile/yourxingid"
101  #git           = "full profile url for git user"
102  
103  [[params.friend_link]]
104  title = "Linda的博客"
105  href =  "https://zhaozhihan.com"
106
107  [[params.bookmark_link]]
108  title =  "Martin Fowler"
109  href =  "https://martinfowler.com"
110  [[params.bookmark_link]]
111  title = "ServiceMesher"
112  href =  "http://www.servicemesher.com"
113  [[params.bookmark_link]]
114  title =  "Pxhere"
115  href = "https://pxhere.com"
116  [[params.bookmark_link]]
117  title = "unsplash"
118  href = "https://unsplash.com"
119  
120  [[params.add itional_menus]]
121  title =  "ARCHIVE"
122  href =  "/archive/"
123  [[params.add itional_menus]]
124  title =  "NOTES"
125  href =  "/notes/"
126  [[params.additional_menus]]
127  title =  "ABOUT"
128  href =  "/about/"
129
130[outputFormats.Algolia]
131baseName = "algolia"
132isPlainText = true
133mediaType = "application/json"
134notAlternative = true
135
136[params.algolia]
137vars = ["title", "summary", "date", "publishdate", "expirydate", "permalink"]
138params = ["categories", "tags"] 
139
140[markup]
141  [markup.tableOfContents]
142    endLevel = 2
143    startLevel = 1
144  [markup.highlight]
145    style = "dracula"
146  [markup.goldmark]
147    [markup.goldmark.renderer]
148      unsafe = true
初始化版本备份

hugo.toml

1baseURL = 'https://example.org/'
2languageCode = 'en-us'
3title = 'My New Hugo Site'
主题bug修复
  • .\themes\hugo-theme-cleanwhite\layouts\partials\sidebar.html:
    • 289 行: /tags/ 前面的 空格 要删除,否则生成的 tag 链接会出错,无法正确跳转。
  • .\themes\hugo-theme-cleanwhite\layouts\partials\footer.html:
    • fastClick.js cdn 大陆无法访问,它的作用是 消除移动设备上的300毫秒点击延迟,这里我们直接 下载,本地导入
 1<!--fastClick.js -->
 2<script>
 3    // loadAsync("https://cdn.jsdelivr.net/npm/fastclick@1.0.6/lib/fastclick.min.js", function () {
 4    //     var $nav = document.querySelector("nav");
 5    //     if ($nav) FastClick.attach($nav);
 6    // })
 7    loadAsync('{{ "js/fastclick.min.js" | relURL }}', function () {
 8        var $nav = document.querySelector("nav");
 9        if ($nav) FastClick.attach($nav);
10    })
11</script>

Hugo 的 Front matter 模板

文章 Front matter

 1---
 2layout: single
 3title: "Visual Studio Code 使用手册"
 4description: "VS Code 下载安装,常用插件 等。"
 5author: "谌中钱"
 6date: "2025-01-14"
 7image: "/img/temple_404_bg.jpg" # banner 背景图
 8categories: [ "tech" ]
 9tags:
10  - "tools"
11weight: 1 # 置顶,数值越小,权重越大
12---

页面 Front matter

1---
2layout: page
3title: "关于我"
4description: "Hi,我是谌中钱,本科主修计算机科学,全栈高级开发工程师,曾在 阿里云,腾讯,Coupang 从事开发工作。常用技术栈以 React,Node 等技术为主。"
5author: "谌中钱"
6date: "2025-01-14"
7---

Window 11 下

包管理器 方式安装

  1. Win + R 打开 运行 窗口,输入 cmd 打开 命令终端
  2. 操作命令: 参考 《Git 使用手册》
 1# 查看本地是否安装 Hugo
 2winget list | findstr Hugo
 3# 更新包索引,确保包都是最新的
 4winget update
 5# 查询一下 Hugo 包列表
 6winget search Hugo
 7# 名称                  ID                    版本     匹配          源
 8# --------------------------------------------------------------------------
 9# HuGO - Hygiene und GO 9NCDTRC8TX5J          Unknown                msstore
10# Remember Passwords    9NXJM8NGHDG6          Unknown                msstore
11# Hugo                  Hugo.Hugo             0.146.7                winget
12# Hugo (Deploy)         Hugo.Hugo.Deploy      0.145.0  Command: hugo winget
13# Hugo (Extended)       Hugo.Hugo.Extended    0.146.7                winget
14# 希沃集控              Seewo.SeewoHugo       1.4.5.57               winget
15# 掌上看班              Seewo.SeewoHugoKanban 1.4.5.68               winget
16
17# 安装 Hugo 标准版
18winget install Hugo.Hugo
19# 更新 Hugo 版本,有时候版本会有 bug,需要更新
20# winget upgrade --name Hugo --version 0.146.7
21# 卸载 Hugo
22# winget uninstall Hugo
23
24# 查看 Hugo 版本
25hugo version
26
27# 创建新站点,指定路径,命名为 blog
28hugo new site D:\workspace\blog && cd /d D:\workspace\blog
29
30# 下载主题 hugo-theme-cleanwhite
31# 记得删除 ./themes/hugo-theme-cleanwhite 下的 .git 文件夹,不然后续提交会报错
32git clone https://github.com/zhaohuabing/hugo-theme-cleanwhite.git ./themes/hugo-theme-cleanwhite
33
34# 复制 主题演示站点 的 站点配置文件 hugo.toml,content 文件夹,static 文件夹 到 新站点 下
35# 注意修改 hugo.toml 的错误,上面有描述
36copy /Y ".\themes\hugo-theme-cleanwhite\exampleSite\hugo.toml" "."
37xcopy ".\themes\hugo-theme-cleanwhite\exampleSite\content" ".\content" /E /Y
38xcopy ".\themes\hugo-theme-cleanwhite\exampleSite\static" ".\static" /E /Y
39
40# 启动测试服务器,访问站点 http://localhost:1313
41# --bind 0.0.0.0 是为了监听 IPv4 所有网络地址,让 手机 等设备可以通过 局域网 (同一 WiFi 下) 访问 电脑 的站点,进行移动端测试
42hugo server --bind 0.0.0.0

CentOS 9 下

图片批量动态压缩

  • 采用 ImageMagick、bc、parallel 包 批量动态压缩图片资源,提高站点访问速度,会在 Docker 方式安装 时用到,操作命令如下:
 1# 手动压缩图片资源(会覆盖源文件,注意保留源文件)
 2# 压缩规则: 
 3# 1. 超过 500KB 的图片才会压缩
 4# 2. 根据 图片大小 动态控制压缩比例,最后都控制在 300KB 左右
 5# 3. 可压缩 PNG,JPG,JPEG,WEBP 的图片
 6
 7# 安装工具包
 8dnf install -y ImageMagick-7.1.1.26-2.oc9 bc parallel
 9# 配置ImageMagick策略文件
10vim /etc/ImageMagick-7/policy.xml
11
12# 按 i 键进入 插入模式
13# 复制下面的 配置内容 到里面:
14
15# <!-- 允许读写图片格式 -->
16# <policy domain="coder" rights="read|write" pattern="PNG,JPG,JPEG,WEBP" />
17# <!-- 提升资源限制 -->
18# <policy domain="resource" name="memory" value="1GiB"/>
19# <policy domain="resource" name="disk" value="4GiB"/>
20# <policy domain="resource" name="width" value="32KP"/>
21# <policy domain="resource" name="height" value="32KP"/>
22
23# 按 ESC 键退出 插入模式,输入 :wq 并按 Enter 来保存(write)并退出(quit)
24# 查看当前生效策略
25convert -list policy
26
27# 将脚本合成为一条命令
28find ./static/img/ \( -name "*.png" -o -name "*.jpg" -o -name "*.jpeg" -o -name "*.webp" \) -type f -print0 | parallel -0 -j 4 --bar 'f="{}";s=$(stat -c%s "$f");if [ $s -gt 512000 ];then q=$(echo "scale=0;60-30*l($s/512000)/l(10)" | bc -l | awk "{print int(\$1+0.5)}");q=$((q<10?10:q>75?75:q));case "${f##*.}" in png) p="-quality $((q-25)) -define png:compression-level=9 -colors 128" ;; jpg|jpeg) p="-quality $((q-10)) -sampling-factor 4:2:0" ;; webp) p="-quality $((q-20)) -define webp:method=6" ;; esac;mogrify $p "$f";fi'
  • 下面是命令展开的脚本,用于分析:
 1find ./static/img/ \( -name "*.png" -o ... \) -type f -print0 | 
 2parallel -0 -j4 --bar '
 3f="{}";
 4s=$(stat -c%s "$f");
 5if [ $s -gt 512000 ];then
 6  # 质量计算
 7  q=$(echo "scale=0;60-30*l($s/512000)/l(10)" | bc -l | awk "{print int(\$1+0.5)}");
 8  q=$((q<10?10:q>75?75:q));
 9  
10  # 格式处理分支
11  case "${f##*.}" in
12    png)  p="-quality $((q-25)) -define png:compression-level=9 -colors 128";;
13    jpg|jpeg) p="-quality $((q-10)) -sampling-factor 4:2:0";;
14    webp) p="-quality $((q-20)) -define webp:method=6";;
15  esac;
16  
17  # 执行压缩
18  mogrify $p "$f";
19fi'

Docker 方式安装

  1. 安装 Docker
  2. 进入云服务终端
  3. 安装 blog 容器:
 1# 创建文件夹
 2mkdir /usr/local/src/blog && cd /usr/local/src/blog
 3# 新建 Dockerfile 文件
 4vim Dockerfile
 5
 6# 按 i 键进入 插入模式
 7# 复制下面的 Dockfile 文件 内容到里面
 8# 按 ESC 键退出 插入模式,输入 :wq 并按 Enter 来保存(write)并退出(quit)
 9
10# 构建镜像
11docker build -t blog .
12
13# 把需要 外部挂载 的文件,放到指定的 目录里: 
14# ./themes : 存放 主题源代码
15# ./hugo.toml : 存放 主题站点配置
16# ./content : 存放 文章 和 页面 的 md 文件
17# ./static : 存放 静态资源
18# ./public : 存放 站点构建结果
19
20# 下载主题 hugo-theme-cleanwhite
21# 记得删除 ./themes/hugo-theme-cleanwhite 下的 .git 文件夹,不然后续提交会报错
22git clone https://github.com/zhaohuabing/hugo-theme-cleanwhite.git ./themes/hugo-theme-cleanwhite
23# 复制 主题演示站点 的 站点配置文件 hugo.toml,content 文件夹,static 文件夹 到 新站点 下
24# 注意用 vim 修改 hugo.toml 的错误,上面有描述
25cp -f ./themes/hugo-theme-cleanwhite/exampleSite/hugo.toml .
26cp -rf ./themes/hugo-theme-cleanwhite/exampleSite/content/* ./content/
27cp -rf ./themes/hugo-theme-cleanwhite/exampleSite/static/* ./static/
28
29
30# 手动压缩图片资源(会覆盖源文件,注意保留源文件)
31# 压缩规则: 
32# 1. 超过 500KB 的图片才会压缩
33# 2. 根据 图片大小 动态控制压缩比例,最后都控制在 300KB 左右
34# 3. 可压缩 PNG,JPG,JPEG,WEBP 的图片
35
36# 安装工具包
37dnf install -y ImageMagick-7.1.1.26-2.oc9 bc parallel
38# 配置ImageMagick策略文件
39sed -i '/<policy domain="coder" rights="read|write"/!b;n;c\ \ <policy domain="coder" rights="read|write" pattern="PNG,JPG,JPEG,WEBP" />' /etc/ImageMagick-7/policy.xml
40sed -i '/<policy domain="resource" name="memory"/s/value=".*"/value="1GiB"/' /etc/ImageMagick-7/policy.xml
41sed -i '/<policy domain="resource" name="disk"/s/value=".*"/value="4GiB"/' /etc/ImageMagick-7/policy.xml
42sed -i '/<policy domain="resource" name="width"/s/value=".*"/value="32KP"/' /etc/ImageMagick-7/policy.xml
43sed -i '/<policy domain="resource" name="height"/s/value=".*"/value="32KP"/' /etc/ImageMagick-7/policy.xml
44# 查看当前生效策略
45convert -list policy
46
47# 将脚本合成为一条命令
48find ./static/img/ \( -name "*.png" -o -name "*.jpg" -o -name "*.jpeg" -o -name "*.webp" \) -type f -print0 | parallel -0 -j 4 --bar 'f="{}";s=$(stat -c%s "$f");if [ $s -gt 512000 ];then q=$(echo "scale=0;60-30*l($s/512000)/l(10)" | bc -l | awk "{print int(\$1+0.5)}");q=$((q<10?10:q>75?75:q));case "${f##*.}" in png) p="-quality $((q-25)) -define png:compression-level=9 -colors 128" ;; jpg|jpeg) p="-quality $((q-10)) -sampling-factor 4:2:0" ;; webp) p="-quality $((q-20)) -define webp:method=6" ;; esac;mogrify $p "$f";fi'
49
50
51# 创建并运行容器
52docker run -d --restart=always -p 81:80 \
53-v ./themes:/blog/themes \
54-v ./hugo.toml:/blog/hugo.toml \
55-v ./content:/blog/content \
56-v ./static:/blog/static \
57-v ./public:/blog/public \
58--name blog blog
59
60# 等待 5 秒,确保容器已启动
61sleep 5
62# 修复 RSS
63sed -i 's#http://212.64.16.86:80#https://blog.climbtw.com#g' ./public/index.xml
64sed -i 's#http://212.64.16.86:80#https://blog.climbtw.com#g' ./public/sitemap.xml
65# 修复站点的动态链接
66sed -i 's#http://212\.64\.16\.86:80#https://blog.climbtw.com#g' \
67./public/index.html \
68./public/categories/solutions/index.html \
69./public/categories/tech/index.html
70
71# 推送索引到 Algolia,下面的 使用 Algolia 会详细介绍(atomic-algolia 是基于 Node.js 的工具,需先安装 Node.js)
72# npm install atomic-algolia
73# npx atomic-algolia
  • Dockfile 文件:
 1# 注意更新 Hugo 版本,有时候版本会有 bug,需要更新
 2FROM hugomods/hugo:std-base-0.146.7
 3RUN hugo new site blog
 4WORKDIR /blog
 5
 6# 创建索引文件
 7RUN hugo
 8
 9# 212.64.16.86 为 云服务器 的 公网IP
10# 这里不能使用域名,否则 Hugo 生成的 动态链接 会变成 https://域名:80,导致 端口 重复,文章跳转会出错
11# --disableLiveReload:在生产环境中部署网站,不希望浏览器频繁刷新
12CMD ["hugo", "server", "--disableLiveReload", "--baseURL", "http://212.64.16.86", "-p", "80"]
  1. 搭建 Nginx 服务器 容器

  2. 修改 nginx.conf 配置:

1# 修改 nginx.conf 配置,让 二级域名 blog.climbtw.com 反向代理到 blog 容器映射的 宿主机端口 81:
2# 即 复制下面的 nginx.conf 文件 到 nginx 容器 配置文件的 挂载目录 /usr/local/src/nginx/conf/nginx.conf 里
3
4# 重启 nginx 容器
5docker restart nginx
6
7# 可以通过 二级域名 blog.climbtw.com 访问站点
  • nginx.conf 文件:
  1worker_processes  1;
  2
  3events {
  4    worker_connections  1024;
  5}
  6
  7http {
  8    include       mime.types;
  9    default_type  application/octet-stream;
 10
 11    sendfile        on;
 12
 13    keepalive_timeout  65;
 14    
 15    proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:100m inactive=60m max_size=1g use_temp_path=off;
 16
 17    # 开启gzip
 18    gzip  on;
 19    gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
 20    gzip_min_length 1024; # 设置最小压缩数据大小,小于该值的数据将不进行压缩
 21    gzip_comp_level 5; # 设置压缩级别,1 为最快压缩,9 为最高压缩率(建议设置为 4~6)
 22
 23    gzip_buffers 16 8k; # 设置用于存储压缩数据的缓冲区数量和大小
 24    gzip_http_version 1.1; # 仅对支持 HTTP/1.1 或更高版本的客户端启用 Gzip 压缩
 25    gzip_vary on; # 启用 Vary 响应头,告知缓存代理服务器对不同编码方式进行缓存分离处理
 26    gzip_static on; # 启用对预生成 .gz 文件的直接传输支持,减少服务器实时压缩负担
 27    gzip_disable "msie6"; # 禁用对特定客户端(如 IE6)的 Gzip 支持,避免兼容性问题
 28    gzip_proxied any; # 指定在代理场景下是否启用压缩(如 any 表示对所有请求启用压缩)
 29    
 30
 31    upstream blog_server {
 32        ip_hash;
 33        server 172.17.0.1:81 max_fails=3 fail_timeout=30s;
 34        
 35        keepalive 32; # 保持连接数,减少每次请求的连接开销
 36
 37        # max_fails 服务器失败的最大次数
 38        # fail_timeout 每台服务器失败的超时时间
 39    }
 40    
 41    upstream twikoo_server {
 42        ip_hash;
 43        server 172.17.0.1:82 max_fails=3 fail_timeout=30s;
 44        
 45        keepalive 32;
 46    }
 47
 48    server {
 49        listen 80;
 50        server_name blog.climbtw.com climbtw.com www.climbtw.com;
 51        # rewrite ^(.*)$ https://$server_name$1 permanent; # permanent,301 永久重定向,更新 url
 52        return 301 https://$server_name$request_uri; # 重定向使用 return 效率更高
 53    }
 54    
 55    # 通过 ip 访问的话,优先匹配 显式标记为 default_server 的 server,如果没有则 使用第一个 server
 56    # 这里设置下,通过 ip 访问的话,跳到博客容器
 57    server {
 58        listen 80 default_server;
 59        server_name blog.climbtw.com;
 60        # rewrite ^(.*)$ https://$server_name$1 permanent; # permanent,301 永久重定向,更新 url
 61        return 301 https://$server_name$request_uri; # 重定向使用 return 效率更高
 62    }
 63
 64    server {
 65        listen       443 ssl;
 66        server_name  blog.climbtw.com;
 67
 68        ssl_certificate      /etc/nginx/certs/blog.climbtw.com_bundle.pem;
 69        ssl_certificate_key  /etc/nginx/certs/blog.climbtw.com.key;
 70
 71        ssl_session_cache    shared:SSL:1m;
 72        
 73        ssl_session_timeout  5m;
 74        # 请按照以下协议配置
 75        ssl_protocols TLSv1.2 TLSv1.3; 
 76        # 请按照以下套件配置,配置加密套件,写法遵循 openssl 标准。
 77        ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE; 
 78        ssl_prefer_server_ciphers on;
 79
 80        # 系统临时维护
 81        # rewrite ^(.*)$ /maintainace.html break; # break,地址栏 url 不变
 82        # location = /maintainace.html {
 83        #     root /etc/nginx/html/nginx/html;
 84        # }
 85
 86        # location / {
 87        #     root   /etc/nginx/html/blog/public;
 88        #     # try_files $uri $uri/ /index.html; # 解决单页应用 history 路由 404 的问题
 89        #     index  index.html index.htm;
 90        # }
 91
 92        # 反向代理
 93        location / {
 94            proxy_pass http://blog_server;
 95            
 96            proxy_cache my_cache;
 97            proxy_set_header Host $host;
 98            proxy_set_header X-Real-IP $remote_addr;
 99            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
100            proxy_set_header X-Forwarded-Proto $scheme;
101        }
102
103        error_page 500 502 503 504 /50x.html;
104        location = /50x.html {
105            root /etc/nginx/html/nginx/html;
106        }
107
108        # error_page 404 /404.html;
109        # location = /404.html {
110        #     root /etc/nginx/html/blog/public;
111        # }
112    }
113
114    server {
115        listen       443 ssl;
116        server_name  twikoo.climbtw.com;
117
118        ssl_certificate      /etc/nginx/certs/twikoo.climbtw.com_bundle.pem;
119        ssl_certificate_key  /etc/nginx/certs/twikoo.climbtw.com.key;
120
121        ssl_session_cache    shared:SSL:1m;
122        
123        ssl_session_timeout  5m;
124        # 请按照以下协议配置
125        ssl_protocols TLSv1.2 TLSv1.3; 
126        # 请按照以下套件配置,配置加密套件,写法遵循 openssl 标准。
127        ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE; 
128        ssl_prefer_server_ciphers on;
129
130        # 系统临时维护
131        # rewrite ^(.*)$ /maintainace.html break; # break,地址栏 url 不变
132        # location = /maintainace.html {
133        #     root /etc/nginx/html/nginx/html;
134        # }
135
136        # 反向代理
137        location / {
138            proxy_pass http://twikoo_server;
139            
140            proxy_cache my_cache;
141            proxy_set_header Host $host;
142            proxy_set_header X-Real-IP $remote_addr;
143            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
144            proxy_set_header X-Forwarded-Proto $scheme;
145        }
146
147        error_page 500 502 503 504 /50x.html;
148        location = /50x.html {
149            root /etc/nginx/html/nginx/html;
150        }
151    }
152
153    server {
154        listen       443 ssl;
155        server_name  climbtw.com www.climbtw.com;
156
157        ssl_certificate      /etc/nginx/certs/climbtw.com_bundle.pem;
158        ssl_certificate_key  /etc/nginx/certs/climbtw.com.key;
159
160        ssl_session_cache    shared:SSL:1m;
161        
162        ssl_session_timeout  5m;
163        #请按照以下协议配置
164        ssl_protocols TLSv1.2 TLSv1.3; 
165        #请按照以下套件配置,配置加密套件,写法遵循 openssl 标准。
166        ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE; 
167        ssl_prefer_server_ciphers on;
168
169        # 系统临时维护
170        # rewrite ^(.*)$ /maintainace.html break; # break,地址栏 url 不变
171        # location = /maintainace.html {
172        #     root /etc/nginx/html/nginx/html;
173        # }
174
175        location / {
176            root   /etc/nginx/html/climbtw;
177            index  index.html index.htm;
178        }
179
180        error_page   500 502 503 504  /50x.html;
181        location = /50x.html {
182            root   /etc/nginx/html/nginx/html;
183        }
184    }
185}

使用 Twikoo 评论系统

一个简洁、安全、免费的静态网站评论系统。

云函数部署

Docker 方式

  1. 进入云服务终端
  2. 操作命令:
 1# 创建文件夹,用来存放 数据文件
 2mkdir /usr/local/src/twikoo && cd /usr/local/src/twikoo
 3
 4# 拉取 twikoo 镜像
 5docker pull imaegoo/twikoo:1.6.42
 6
 7# 创建并运行容器
 8docker run -d --restart=always -p 82:8080 -e TWIKOO_THROTTLE=1000  -v ./data:/app/data --name twikoo imaegoo/twikoo:1.6.42
 9
10# 修改 nginx.conf 配置,让 二级域名 twikoo.climbtw.com 反向代理到 twikoo 容器映射的 宿主机端口 82,重启 nginx 容器

在 hugo-theme-cleanwhite 主题中使用

修改站点配置启用

  1. 修改 站点配置文件 hugo.toml
1# Twikoo comments
2# Follow https://twikoo.js.org/ to set up your own env_id
3twikoo_env_id = "https://twikoo.climbtw.com/" # 云函数部署的地址
  1. 修改 主题 源码
  • 修改 前端引用的 twikoo.js,与 云函数版本 保持一致,这里用的 1.6.42:
    • 静态引入的话,可以去上面 GitHub 地址 里下载 对应版本(注意 twikoo.all.min.js 中存在很多敏感信息,无法提交到 git)

.\themes\hugo-theme-cleanwhite\layouts\partials\comments.html

1<script src="https://registry.npmmirror.com/twikoo/1.6.42/files/dist/twikoo.all.min.js"></script>
2
3<!-- <script src='{{ "js/twikoo.all.min.js" | relURL }}'></script> -->
  • 主题 的 bootstrap.min.css 对 上传图像 样式有覆盖,修改一下:

.\themes\hugo-theme-cleanwhite\static\css\bootstrap.min.css

1<style>
2.twikoo .tk-input-image {
3    display: none;
4}
5</style>
6<div id="twikoo-tcomment"></div>

Twikoo 设置

在 站点文章 下方会有 Twikoo 评论组件,组件右下侧有 设置 按钮,第一次打开 设置 需要 配置密码。

使用自定义头像
使用 Weavatar 服务

Weavatar,多端多元化的统一头像服务。

  • Weavatar 官网: https://weavatar.com/
  • 配置方法:
    • 去 Weavatar 官网 用 邮箱 注册 和 上传 自定义头像。
    • 在 Twikoo 设置 -> 配置管理 -> 通用,按如下配置:
GRAVATAR_CDN: weavatar.com
  • 主题 hugo-theme-cleanwhite 的样式对 头像 div 有覆盖,需要修改:

.\themes\hugo-theme-cleanwhite\layouts\partials\comments.html

1<style>
2.twikoo img {
3    margin: 0;
4}
5</style>
6<div id="twikoo-tcomment"></div>
使用 Server酱 (ServerChan) 评论推送

Server酱,英文名 ServerChan,是一款 手机 和 服务器、智能设备 之间的通信软件。

  • Server酱 官网: https://sct.ftqq.com
  • 配置方法:
    • 去 Server酱 官网 注册,获取 SendKey,然后在 通道配置 里面设置 通知方式,我使用的 方糖的微信服务号。
    • 在 Twikoo 设置 -> 配置管理 -> 即时通知,按如下配置:
PUSHOO_CHANNEL: serverchan
PUSHOO_TOKEN: 填写获取的 SendKey
SC_MAIL_NOTIFY: false
  • 注意:
    • ServerChan 目前由于用户量比较大,免费的通知额度只有 5 条。
    • 自己发布的评论 (按邮箱判断),不会通知。

使用 Algolia 站点搜索

Algolia 是为你的 APP 或者网站添加搜索的最佳方式。

在 hugo-theme-cleanwhite 主题中使用

开启 Algolia

  • 修改 站点配置文件 hugo.toml
 1# algolia site search
 2algolia_search = true
 3algolia_appId = "***"
 4algolia_apiKey = "***"
 5algolia_indexName = "hugo-blog"
 6
 7[outputs]
 8home = ["HTML", "RSS", "Algolia"]
 9
10[outputFormats.Algolia]
11baseName = "algolia"
12isPlainText = true
13mediaType = "application/json"
14notAlternative = true
15
16[params.algolia]
17vars = ["title", "summary", "date", "publishdate", "expirydate", "permalink"]
18params = ["categories", "tags"]

本地生成索引文件

  • 在站点项目下执行构建命令:hugo,会在 public 目录下新建一个名为 algolia.json 的索引文件,它的地址即 ALGOLIA_INDEX_FILE

推送搜索引到 Algolia

  1. 去 Algolia 官网 注册一个账号,获取 Application IDWrite API Key
  2. 然后点击左侧的 Search 按钮,创建一个新的索引,自定义取名,比如 “hugo-blog”,即 ALGOLIA_INDEX_NAME
  3. 在站点根目录下创建一个 .env 文件,写入上面获取到的 ALGOLIA_INDEX_FILEApplication IDWrite API KeyALGOLIA_INDEX_NAME

.env

1ALGOLIA_INDEX_FILE=/public/algolia.json
2ALGOLIA_APP_ID=***
3ALGOLIA_ADMIN_KEY=***
4ALGOLIA_INDEX_NAME=hugo-blog
  1. 安装推送工具包 atomic-algolia,推送索引到 Algolia
1npm install atomic-algolia
2npx atomic-algolia

访问索引的配置

  • 即在上面的 站点配置 hugo.toml 中,填入 Algolia 官网中的 Application IDSearch API Key, 以及 ALGOLIA_INDEX_NAME
  • 创建 搜索页面文件,即在 content 创建一个文件夹 search,构建一个空文件,命名 placeholder.md,即可。

placeholder.md:

1---
2layout: page
3title: "搜索页面"
4description: "使用 Algolia 为 站点 提供 搜索功能。 "
5author: "谌中钱"
6date: "2025-01-14"
7ogurl: "https://blog.climbtw.com/search"
8---

嵌入视频

哔哩哔哩

使用方式:{ {< bilibili BV1kZ4y137gv >}}

  • 优点:不需要梯子。
  • 缺点:站内只能观看 360P 内容。

Vimeo (需要梯子)

使用方式:{ {< vimeo 146022717 >}}

YouTube (需要梯子)

使用方式:{ {< youtube cllc1ZGlhsQ >}}

  • 优点:
    • 可在 站内 直接观看 1080p 内容。
    • 月活跃用户 26亿+(全球第一)
  • 缺点:
    • 需要梯子。
    • 站内 不能全屏观看。
  • 参考: 《梯子使用手册》

自动化部署

实现推送代码到 github 上的 main 分支时,会自动部署到 部署服务器。

  • 自动化部署流程:
    • 在 Github 站点仓库上,通过 Github Action,新建一个 Workflow 工作流,创建 .github/workflows/deploy.yml,在里面监听 代码上传,合并 等操作,触发 连接部署服务器 的动作,并在 部署服务器 上执行 站点仓库里的 部署脚本 ./deploy.sh
      • 要连接部署服务器,需要先把 部署服务器 的 SSH公钥 id_rsa.pub 添加到其 ~/.ssh/authorized_keys 里,然后把 部署服务器 的 SSH私钥 设置到 GitHub Action 上,取个名字。
        • 在 .github/workflows/deploy.yml 中使用 SSH私钥 去连接 部署服务器。
      • 参考:
    • 部署脚本 ./deploy.sh 里面,会拉取最新的代码,完成网站更新。

在部署服务器上生成SSH密钥

 1# 在云服务器上生成 公钥(id_rsa.pub) 和 私钥(id_rsa)
 2ssh-keygen -t rsa -b 4096 -C "templechan@126.com"
 3# 将 公钥 添加到云服务器的 ~/.ssh/authorized_keys 文件中
 4cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
 5
 6# 查看 私钥
 7cat ~/.ssh/id_rsa
 8# 登录 GitHub -> 站点下的 Settings -> Secrets and variables -> Actions -> New repository secret 中添加 3 个常量:
 9# Name: CTW_SSH_PRIVATE_KEY
10# Value: 你的私钥内容(注意不要添加换行符,可以直接从文件复制)
11
12# Name: CTW_SERVER_IP
13# Value: 部署服务器的 IP
14
15# Name: CTW_USER
16# Value: 部署服务器的 user
17
18
19# 私钥 CTW_SSH_PRIVATE_KEY 会在 .github/workflows/deploy.yml 的 Secrets 中被用来连接 部署服务器

编写 GitHub Action 工作流

.github/workflows/deploy.yml

 1name: Deploy to Server
 2
 3on:
 4  push:
 5    branches:
 6      - main
 7 
 8jobs:
 9  deploy:
10    runs-on: ubuntu-latest
11    steps:
12    - name: Checkout code
13      uses: actions/checkout@v2
14 
15    - name: Install SSH key
16      uses: webfactory/ssh-agent@v0.5.3
17      with:
18        ssh-private-key: ${{ secrets.CTW_SSH_PRIVATE_KEY }}
19 
20    - name: Adding Known Hosts
21      run: ssh-keyscan ${{ secrets.CTW_SERVER_IP }} >> ~/.ssh/known_hosts
22 
23    - name: Deploy to Server
24      run: ssh ${{ secrets.CTW_USER }}@${{ secrets.CTW_SERVER_IP }} 'bash -s' < ./deploy.sh

站点仓库的部署脚本

./deploy.sh:

  1cd /usr/local/src
  2rm -rf /usr/local/src/blog
  3
  4if ! command -v git &> /dev/null; then
  5    dnf install -y git
  6    git config --global user.email "templechan@126.com"
  7    git config --global user.name "templechan"
  8    # 设置 GitHub 国内镜像源
  9    # git config --global url."https://bgithub.xyz/".insteadOf https://github.com/
 10fi
 11
 12# 如果失效,则删除旧的,设置的新的
 13# git config --global --unset url."https://bgithub.xyz/".insteadOf https://github.com/
 14# git config --global url."https://kkgithub.com/".insteadOf https://github.com/
 15git clone -b main https://github.com/templechan/blog.git
 16
 17if [ -d /usr/local/src/blog ]; then
 18    cd /usr/local/src/blog
 19    if ! command -v mogrify &> /dev/null; then
 20        # 安装图片压缩包 ImageMagick
 21        dnf install -y ImageMagick-7.1.1.26-2.oc9 bc parallel
 22        # 配置ImageMagick策略文件
 23        sed -i '/<policy domain="coder" rights="read|write"/!b;n;c\ \ <policy domain="coder" rights="read|write" pattern="PNG,JPG,JPEG,WEBP" />' /etc/ImageMagick-7/policy.xml
 24        sed -i '/<policy domain="resource" name="memory"/s/value=".*"/value="1GiB"/' /etc/ImageMagick-7/policy.xml
 25        sed -i '/<policy domain="resource" name="disk"/s/value=".*"/value="4GiB"/' /etc/ImageMagick-7/policy.xml
 26        sed -i '/<policy domain="resource" name="width"/s/value=".*"/value="32KP"/' /etc/ImageMagick-7/policy.xml
 27        sed -i '/<policy domain="resource" name="height"/s/value=".*"/value="32KP"/' /etc/ImageMagick-7/policy.xml
 28    fi
 29
 30    # 手动压缩图片资源(会覆盖源文件,注意保留源文件)
 31    # 压缩规则: 
 32    # 1. 超过 500KB 的图片才会压缩
 33    # 2. 根据 图片大小 动态控制压缩比例,最后都控制在 300KB 左右
 34    # 3. 可压缩 PNG,JPG,JPEG,WEBP 的图片
 35    find ./static/img/ \( -name "*.png" -o -name "*.jpg" -o -name "*.jpeg" -o -name "*.webp" \) -type f -print0 | parallel -0 -j 4 --bar 'f="{}";s=$(stat -c%s "$f");if [ $s -gt 512000 ];then q=$(echo "scale=0;60-30*l($s/512000)/l(10)" | bc -l | awk "{print int(\$1+0.5)}");q=$((q<10?10:q>75?75:q));case "${f##*.}" in png) p="-quality $((q-25)) -define png:compression-level=9 -colors 128" ;; jpg|jpeg) p="-quality $((q-10)) -sampling-factor 4:2:0" ;; webp) p="-quality $((q-20)) -define webp:method=6" ;; esac;mogrify $p "$f";fi'
 36    
 37
 38    if [ ! "$(docker ps -a -f "name=blog" --quiet)" ]; then
 39        if [ ! "$(docker images -q blog)" ]; then
 40            # 构建镜像
 41            docker build -t blog .
 42        fi
 43        # 创建并运行容器
 44        docker run -d --restart=always -p 81:80 -v ./themes:/blog/themes -v ./hugo.toml:/blog/hugo.toml -v ./content:/blog/content -v ./static:/blog/static -v ./public:/blog/public -v ./static/sitemap/:/blog/public/*.xml --name blog blog
 45    else
 46        docker restart blog
 47    fi
 48
 49    # 等待 5 秒,确保容器已启动
 50    sleep 5
 51    # 修复 RSS
 52    sed -i 's#http://212.64.16.86:80#https://blog.climbtw.com#g' ./public/index.xml
 53    sed -i 's#http://212.64.16.86:80#https://blog.climbtw.com#g' ./public/sitemap.xml
 54    # 修复站点的动态链接
 55    sed -i 's#http://212\.64\.16\.86:80#https://blog.climbtw.com#g' \
 56    ./public/index.html \
 57    ./public/categories/solutions/index.html \
 58    ./public/categories/tech/index.html
 59
 60    # 推送索引到 Algolia
 61    # 检查 npm 是否存在 (注意 非交互式 Shell 不会自动加载 ~/.bashrc 或 ~/.zshrc,导致:nvm 命令找不到, npm/node 路径未正确设置)
 62    # 所以尝试先加载一遍 nvm 环境
 63    export NVM_DIR="$HOME/.nvm"
 64    [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
 65    [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
 66
 67    if ! command -v npm &> /dev/null; then
 68        echo "npm 未安装,开始安装 nvm 并安装 npm..."
 69
 70        if ! command -v nvm &> /dev/null; then
 71            echo "nvm 未安装,开始安装 nvm..."
 72            # 下载并安装 nvm
 73            curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash
 74            # 加载 nvm 环境
 75            export NVM_DIR="$HOME/.nvm"
 76            [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
 77            [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
 78        
 79        else
 80            echo "nvm 已存在。"
 81        fi
 82
 83        echo "使用 nvm 安装 node 和 npm"
 84        nvm install 20.19.1
 85        nvm use 20.19.1
 86        nvm alias default 20.19.1
 87    fi
 88
 89    # 检查 atomic-algolia 是否全局已安装
 90    if ! npm list -g atomic-algolia --depth=0 &> /dev/null; then
 91        echo "atomic-algolia 未安装,安装中..."
 92        npm install -g atomic-algolia
 93    else
 94        echo "atomic-algolia 全局已安装。"
 95    fi
 96
 97    # 执行 atomic-algolia 命令
 98    echo "执行 atomic-algolia 命令..."
 99    npx atomic-algolia
100
101    # Nginx 如果配置好了,可直接访问网站查看部署更新
102fi

SEO

搜索引擎收录

修复站点地图

  • 本文使用 hugo server --disableLiveReload --baseURL http://212.64.16.86 -p 80 的方式 部署服务器站点, 这样 Hugo 生成的 动态链接 的头部会变成 https://212.64.16.86:80,导致在生成 站点地图 index.xmlsitmap.xml 时,谷歌会判定 非域名 的链接 无效。
    • 解决方案:
      • 用 VSCode 手动批量替换 ip 为 域名 的 站点地图文件 index.xmlsitmap.xml
        • 比如:http://212.64.16.86:80 替换为 https://blog.climbtw.com
      • 可以在 启动站点容器 的命令结束后,通过 命令 替换 站点地图文件中的 https://212.64.16.86:80https://blog.climbtw.com
        • 同时也修复下站点的动态链接
        • 修复命令:
 1# 等待 5 秒,确保容器已启动
 2sleep 5
 3# 修复 RSS
 4sed -i 's#http://212.64.16.86:80#https://blog.climbtw.com#g' ./public/index.xml
 5sed -i 's#http://212.64.16.86:80#https://blog.climbtw.com#g' ./public/sitemap.xml
 6# 修复站点的动态链接
 7sed -i 's#http://212\.64\.16\.86:80#https://blog.climbtw.com#g' \
 8./public/index.html \
 9./public/categories/solutions/index.html \
10./public/categories/tech/index.html

「 您的咖啡能让我写出少 Bug 的代码 ☕️ ~ 」
「 会出现在赞赏名单中哦 ~ 」

谌中钱 Temple
山高自有客行路,水深自有渡船人

您的咖啡能让我写出少 Bug 的代码 ☕️ ~

使用 微信 扫描二维码完成支付