怎么做cc网站,学院网站建设规划,小规模企业所得税5%,域名与网站利用github pages建立Serverless个人博客
概述
使用github pages#xff0c;可以在github上部署静态网站。利用这个功能#xff0c;可以很方便地实现个人博客的发布托管。
比如我的个人博客#xff1a;Buttering’s Blog
对应代码仓库#xff1a;buttering/EasyBlog: 自…利用github pages建立Serverless个人博客
概述
使用github pages可以在github上部署静态网站。利用这个功能可以很方便地实现个人博客的发布托管。
比如我的个人博客Buttering’s Blog
对应代码仓库buttering/EasyBlog: 自动将本地博客部署到github中展示
相关技术
1. 博客处理
采用go语言对md格式的博客进行处理主要目的是处理文档中的图片将其保存在图床并将文档中的图片链接替换为图床链接。
2. 图床
采用github仓库直接作为图床。图片和博客存放在同一个仓库中。
3. Hexo博客框架
Hexo是一个快速、简洁且高效的博客框架能很方便地生成静态的博客网站。采用Fluid主题。
4. CI/CD流程
采用github actions自动实现博客上传后网站的生成和部署。
5. 更多功能 使用hexo-renderer-markdown-it-plus插件实现对latex的支持。 使用giscus评论插件利用github discussions实现博客的评论功能。 使用LeanCloud实现访问统计。 展示网站运行时长
实现
0. 项目框架
项目所有文章和图片都位于同一个github仓库中 .github存放github action的配置文件asset存放处理后的博客和图片pictures起到了图床的作用publishHexo部署在此子目录resource存放githubPub公钥tools一些自己写的工具函数UploadBlogs实现博客处理和自动push的脚本
1. 博客预处理
对md格式的博客进行预处理使用正则表达式提取其中的图片路径
对于本地图片直接将其拷贝到asset/pictures目录中对于网络图片将其下载在asset/pictures目录中。
在pictures目录中对文章名和图片进行了哈希处理如图 博客则直接按照原文件名放在blogs目录下。
博客中图片的链接会被替换为形如 https://raw.githubusercontent.com/buttering/EasyBlogs/master/asset/pictures/bdf03a9b9bdacaaf60d1f899c0222865/74d51bfa109b4065ccb3ba37b6922bb3.png的url。 raw.githubusercontent.com 是github用来存储用户上传文件的服务地址。 package mainimport (EasyBlogs/toolsfmtgithub.com/go-git/go-git/v5github.com/go-git/go-git/v5/plumbing/objectgithub.com/go-git/go-git/v5/plumbing/transport/sshiolognet/httpnet/urlospathpath/filepathregexpstringstime
)type markdownPicture struct {isUrl boolsourcePicturePath stringstart int // md中源图片路径的起始偏移量end inthashName string // 均将包含后缀的文件名进行hash且后拼接上原有后缀名targetUrl string // 修改后在github仓库中的url
}type Blog struct {name stringhashName stringpictures []markdownPicturedirectoryPath string // 源文件文件夹路径legal bool // 成功通过解析
}var (BLOG_PATH stringPICTURE_PATH stringREPOSITORY_URL string
)func getBlogList(path string) (blogsList []Blog) {blogsList make([]Blog, 0, 10)fileList, err : os.ReadDir(path)if err ! nil {panic(err)}for _, file : range fileList {if !file.IsDir() filepath.Ext(file.Name()) .md {fileName : file.Name()blogsList append(blogsList, Blog{fileName, tools.Hash(fileName), nil, path, false})}}return
}func extractPicture(blog *Blog) {isUrl : func(path string) bool {return strings.HasPrefix(path, http://) || strings.HasPrefix(path, https://)}content, err : os.ReadFile(filepath.Join(blog.directoryPath, blog.name))if err ! nil {println(err)return}re, _ : regexp.Compile(!\[.*?]\((.*?)\))matches : re.FindAllSubmatchIndex(content, -1)for _, match : range matches {start : match[2]end : match[3]picturePath : string(content[start:end])var pictureName stringif isUrl(picturePath) {u, err : url.Parse(picturePath)if err ! nil {println(解析图片url, picturePath, 失败)continue}pictureName path.Base(u.Path)} else if filepath.IsAbs(picturePath) {pictureName filepath.Base(picturePath)} else { // 相对路径的本地文件picturePath filepath.Join(blog.directoryPath, picturePath)pictureName filepath.Base(picturePath)}hashName : tools.Hash(pictureName) path.Ext(pictureName)blog.pictures append(blog.pictures,markdownPicture{isUrl(picturePath),picturePath,start,end,hashName,REPOSITORY_URL / blog.hashName / hashName,},)}blog.legal true
}func copyBlog(blog *Blog) {fmt.Println(拷贝博客“ blog.name ”)blogTargetPath : filepath.Join(BLOG_PATH, blog.name)pictureTargetPath : filepath.Join(PICTURE_PATH, blog.hashName)if _, err : os.Stat(blogTargetPath); !os.IsNotExist(err) {println(文章“ blog.name ”已经存在)blog.legal falsereturn}if err : os.Mkdir(pictureTargetPath, 0777); err ! nil {println(为博客“ blog.name ”创建对应picture文件夹失败)blog.legal falsereturn}content, _ : os.ReadFile(filepath.Join(blog.directoryPath, blog.name))offset : 0for _, picture : range blog.pictures {start : picture.start offsetend : picture.end offsetcontent append(content[:start], append([]byte(picture.targetUrl), content[end:]...)...)offset len(picture.targetUrl) - (end - start)}err : os.WriteFile(blogTargetPath, content, 0644)if err ! nil {println(复制文件“ blog.name ”错误)blog.legal false}}func copyPicture(blog Blog) {pictureTargetPath : filepath.Join(PICTURE_PATH, blog.hashName)for _, picture : range blog.pictures {fmt.Println(导入图片“ picture.sourcePicturePath ”)var sourceFile interface{}if picture.isUrl {for i : 0; i 5; i {response, err : http.Get(picture.sourcePicturePath)if err nil response.StatusCode http.StatusOK {sourceFile response.Bodybreak}time.Sleep(50 * time.Millisecond)}if sourceFile nil {println(下载图片“ picture.sourcePicturePath ”失败)continue}} else {file, err : os.Open(picture.sourcePicturePath)if err ! nil {println(打开图片“ picture.sourcePicturePath ”失败)continue}sourceFile file}destinationFile, _ : os.Create(filepath.Join(pictureTargetPath, picture.hashName))_, err : io.Copy(destinationFile, sourceFile.(io.Reader))if err ! nil {println(复制图片“ picture.sourcePicturePath ”失败)}}
}func gitOperate(blogList []Blog) {if len(blogList) 0 {return}repositoryPath, _ : filepath.Abs(.)r, err : git.PlainOpen(repositoryPath)if err ! nil {println(打开仓库失败)return}w, err : r.Worktree()if err ! nil {println(打开仓库失败)println(err.Error())return}_, err w.Add(./asset)if err ! nil {println(向仓库添加文件失败)println(err.Error())return}status, _ : w.Status()println(git 状态)println(status.String())nameList : tools.Map(blogList, func(blog Blog) string {return blog.name})var summary stringif len(nameList) 1 {summary fmt.Sprintf(提交文件 [%s], blogList[0].name)} else {summary fmt.Sprintf(提交 %d 个博客\n\n文件列表: [%s],len(blogList),strings.Join(nameList, , ),)}commit, err : w.Commit(summary, git.CommitOptions{Author: object.Signature{Name: Wang,When: time.Now(),},})obj, _ : r.CommitObject(commit)fmt.Println(提交文件)fmt.Println(obj.String())// user必须是git。。。困扰了半天最后查issue发现的。真够郁闷的。privateKey, err : ssh.NewPublicKeysFromFile(git, ./resource/githubPublicKey, )if err ! nil {println(err.Error())}for i : 0; i 3; i {err r.Push(git.PushOptions{RemoteName: origin,RemoteURL: gitgithub.com:buttering/EasyBlogs.git,Auth: privateKey,Progress: os.Stdout,})if err nil {break}println(第 %d 次上传失败)}if err ! nil {println(重试次数已达上限上传失败)return}fmt.Println(提交成功)
}func init() {path, _ : filepath.Abs(.)BLOG_PATH filepath.Join(path, asset, blogs)PICTURE_PATH filepath.Join(path, asset, pictures)REPOSITORY_URL https://raw.githubusercontent.com/buttering/EasyBlogs/master/asset/pictures
}func main() {filePath : E:/desktop/blog//yamlPath : ./asset/blogs-list.yamlblogList : getBlogList(filePath)for i : range blogList {extractPicture(blogList[i])copyBlog(blogList[i])copyPicture(blogList[i])}if len(blogList) 0 {return}// 改用github page进行博客部署不需要额外记录博客信息//yamlOperate(yamlPath, blogList)//dbOperate(blogList)gitOperate(blogList)}
2. 引入Hexo框架
Hexo框架的使用方式很简单只需使用Hexo CLI建立基本框架而后将博客和相关资源移入指定文件夹然后运行Hexo服务器即可。这里为实现Serverless后两部交由github action实现。
安装Hexo并初始化站点
保证电脑中安装了Node.js。Hexo的部署目录为publish。
npm install -g hexo-cli
nexo init publish
cd publish
npm install执行后Hexo会自动在publish目录建立一个空白站点目录如下
.
├── _config.yml
├── package.json
├── scaffolds
├── source
| └── _posts
└── themes_config.yml站点的配置文件_postsHexo会自动将其中的文档转化为静态资源themes主题文件夹
安装fluid主题
初始的Hexo站点已经很完善了但是为了更加个性化安装了fluid主题配置指南 | Hexo Fluid 用户手册 (fluid-dev.com)
# 仍是在publish目录
npm install --save hexo-theme-fluid然后在博客目录下创建 _config.fluid.yml将主题的 _config.yml(位于publish\themes\fluid_config.yml内容复制进去。这个_config.fluid.yml是主题的覆盖配置之后要自定义主题只需修改其中的内容即可。
修改publish下的_config.yml如下
theme: fluid # 指定主题
language: zh-CN # 指定语言会影响主题显示的语言按需修改最后创建主题的关于页
hexo new page about测试博客
自此一个本地的博客网站即搭建成功可以尝试手动将文档移入_post文件夹而后在命令行内的 publish 文件夹下运行 hexo g 和 hexo s 就可以在浏览器看到博客。
最后记得使用hexo clean 清空生成的文件避免影响到后面的部署。
3. github actions配置
在预处理函数的最后一步实现了git的自动提交和推送。在成功向github推送后就轮到github action实现博客网站的自动部署了。
配置github仓库
首先将本地的git仓库上传至github且设置为公开。
进入github 项目的设置页面开启page功能。设置Source为GitHub Actions。 编写actions脚本文件
回到本地在项目根目录创建 .github/workflows/xxx.yml 文件。
这里需要一些github actions的知识简单地说 Actions 就是在设定的时机触发创建一个虚拟云环境然后执行一连串动作从而实现自动部署的功能。
可以直接复制以下内容
name: Deploy Github# 在master分支发生push事件时触发。
on:push:branches:- masterworkflow_dispatch:jobs:build:runs-on: ubuntu-lateststeps:- name: 检出仓库uses: actions/checkoutv4- name: Install pandocrun: |cd /tmpwget -c https://github.com/jgm/pandoc/releases/download/2.14.0.3/pandoc-2.14.0.3-1-amd64.debsudo dpkg -i pandoc-2.14.0.3-1-amd64.deb- name: 安装Node.js 18uses: actions/setup-nodev4with:node-version: 18- name: Cache NPM dependenciesuses: actions/cachev4with:path: node_moduleskey: ${{ runner.OS }}-npm-cacherestore-keys: |${{ runner.OS }}-npm-cache- name: 安装相关依赖working-directory: publishrun: |npm install --silent --no-fundnpm un hexo-renderer-marked --savenpm i hexo-renderer-markdown-it-plus --save- name: 复制博客文件run: |cd publishrm -rf source/_postsmkdir source/_postscp -rf ../asset/blogs/* ./source/_posts/- name: 构建网站working-directory: publishrun: npm run build- name: Upload Pages artifactuses: actions/upload-pages-artifactv3with:path: ./publish/publicdeploy:needs: buildpermissions:pages: writeid-token: writeenvironment:name: github-pagesurl: ${{ steps.deployment.outputs.page_url }}runs-on: ubuntu-lateststeps:- name: Deploy to GitHub Pagesid: deploymentuses: actions/deploy-pagesv4github 工作流在完成了环境的初始化后会自动将位于 asset/blog 中的博客文件复制到 publish/source/_post 目录内。而后的构建步骤工作流就会利用 _post 目录中的博客文件生成网站。
修改配置
编辑 _config.yml将 url: 更改为 你的 GitHub 用户名.github.io/repository 的名字。
提交并自动部署
在提交前记得编辑.gitignore文件
/resource/githubPublicKey
/publish/node_modules/
/publish/public/在之后的每次提交都会触发actions流程生成站点。部署完成后前往 https://你的 GitHub 用户名.github.io/repository 的名字 查看站点。
4. 开启latex功能
hexo默认的渲染器不支持复杂的数学公式。
Latex的渲染分为前端渲染和后端渲染两种方法这里选择后端渲染需要更换渲染器已经在actions脚本中完成了。
npm un hexo-renderer-marked --save
npm i hexo-renderer-markdown-it-plus --save修改 _config.fluid.yml
post:math:enable: truespecific: falseengine: katex使用hexo-renderer-markdown-it-plus插件进行latex渲染时如果使用mathjax一些不完全符合md语法的标题会渲染失败如一级标题前没有空行因此要将引擎改为katex。
可以参考配置指南 | Hexo Fluid 用户手册 (fluid-dev.com) 和 CHENXCHEN/hexo-renderer-markdown-it-plus
5. 实现评论功能
使用 Giscus插件且基于 GitHub Discussion实现评论功能。
开启 GitHub Discussion
进入github仓库-Settings-General-Features勾选 Discussions。 安装 Github Apps
点击链接GitHub Apps - giscus 为本仓库安装应用。 可以到 giscus仓库 检查自己的仓库是否满足giscus要求的条件。 配置插件
由于使用了fluid主题因此只需修改配置文件即可开启评论。
修改 _config.fluid.yml comments:enable: true# 指定的插件需要同时设置对应插件的必要参数# The specified plugin needs to set the necessary parameters at the same time# Options: utterances | disqus | gitalk | valine | waline | changyan | livere | remark42 | twikoo | cusdis | giscus | discusstype: giscus# Giscus
# 基于 GitHub Discussions类似于 Utterances
# Based on GitHub Discussions, similar to Utterances
# See: https://giscus.app/
giscus:repo: buttering/EasyBlogrepo-id: category: Announcementscategory-id: theme-light: lighttheme-dark: darkmapping: titlereactions-enabled: 1emit-metadata: 0input-position: bottomlang: zh-CN在配置基于 Github Discussions 的博客评论系统时例如 giscus往往需要获取 repo 的 repo-id, category-id 等属性因此这里介绍一种获取这些信息的方法
首先打开以下 Github Docs 地址Github Docs Explorer然后授权 Github 账户并输入以下内容
{repository(owner: userName, name: repoName) {iddiscussionCategories (first: 5) {nodes {nameid}}}
}将 userName 换成具体的 Github 用户名将 repoName 换成保存评论数据的 Repo 名称。
点击运行按钮即可得到形如 id: R_kgDOKjFfn1 和 DIC_kwdOJPFfnc4CU9... 就是我们需要的 repoId 和 categoryId。
因为网站中文章的url是直接使用文章名进行定位的如 https://buttering.github.io/EasyBlog/2022/10/15/使用plotly离线模式报错UnicodeEncodeErrorgbk codec cant encode character in position的解决方法/如果 mapping: 的值设为 pathname 或者 url其在 github discussions中出现的标题中文会被base64字符代替严重影响观感设为 title 可以解决这个问题。
6. 实现访问统计
参考Hexo-fluid主题设置统计博客阅读量与评论 - 贾明晖的博客 (minghuijia.cn)
到LeanCloud注册一个免费账户创建一个开发板应用后再创建一个Class依次点击数据存储-结构化存储-创建Class权限给到最大。
然后在设置-应用凭证里找到AppID与AppKey。
修改 _config.fluid.yml
# 网页访问统计
# Analysis of website visitors
web_analytics: # 网页访问统计enable: true# LeanCloud 计数统计可用于 PV UV 展示如果 web_analytics: enable 没有开启PV UV 展示只会查询不会增加leancloud:app_id: xxxapp_key: xxx# REST API 服务器地址国际版不填server_url:# 统计页面时获取路径的属性path: window.location.pathname# 开启后不统计本地路径( localhost 与 127.0.0.1 )ignore_local: falsefooter:# 展示网站的 PV、UV 统计数statistics:enable: true7. 网站运行时长
参考Fluid 页脚增加网站运行时长 - Hexo Theme Fluid (fluid-dev.com)
修改 _config.fluid.yml
footer:content: a hrefhttps://hexo.io target_blank relnofollow noopenerspanHexo/span/ai classiconfont icon-love/ia hrefhttps://github.com/fluid-dev/hexo-theme-fluid target_blank relnofollow noopenerspanFluid/span/adiv stylefont-size: 0.85remspan idtimeDate载入天数.../spanspan idtimes载入时分秒.../spanscript src/js/duration.js/script/divcontent 前三行是 Fluid 原有的页脚内容建议不要删除可稍作修改保留 Fluid 的超链接用于向更多人推广主题。
新增的div标签用于实现功能。duration.js 包含的是功能代码我们在博客目录的 source/js 下创建 duration.js 如果没有js目录创建即可
如果直接创建source/js/duration.js 是无效的
针对fluid 1.9.7 需要将 content 的 script src/js/duration.js 这行改为 script src项目根目录名/js/duration.js如 script src/EasyBlog/js/duration.js/script
js文件内容如下
!(function() {/** 计时起始时间自行修改 **/var start new Date(2020/01/01 00:00:00);function update() {var now new Date();now.setTime(now.getTime()250);days (now - start) / 1000 / 60 / 60 / 24;dnum Math.floor(days);hours (now - start) / 1000 / 60 / 60 - (24 * dnum);hnum Math.floor(hours);if(String(hnum).length 1 ){hnum 0 hnum;}minutes (now - start) / 1000 /60 - (24 * 60 * dnum) - (60 * hnum);mnum Math.floor(minutes);if(String(mnum).length 1 ){mnum 0 mnum;}seconds (now - start) / 1000 - (24 * 60 * 60 * dnum) - (60 * 60 * hnum) - (60 * mnum);snum Math.round(seconds);if(String(snum).length 1 ){snum 0 snum;}document.getElementById(timeDate).innerHTML 本站安全运行nbspdnumnbsp天;document.getElementById(times).innerHTML hnum nbsp小时nbsp mnum nbsp分nbsp snum nbsp秒;}update();setInterval(update, 1000);
})();把 start 的时间改为自己的时间即可。