注意:本篇文章部份内容引用了2890和kasuganosoras的内容
是什么?
Cloudflare workers + Github 实现的动态博客系统,使用边缘计算,无需服务器,Workers是CloudFlare提供的边缘计算,每天有10万次的访问次数,而且速度十分快。
源代码
伸手党可以直接复制下面这个我修改了CDN节点的版本,因为官方的CDN不稳定。
// 定义 Github 项目,文章会从这里读取
const github_base = "kasuganosoras/frp-blog";
// 设置站点信息
var default_title = "SakuraFrp Blog - 樱花内网穿透官方博客"; // 站点标题(显示在浏览器标题栏)
var default_intitle = "SakuraFrp Blog"; // 站点名称(显示在首页)
var default_description = "欢迎访问 Sakura Frp 官方博客,本博客分享与 Frp 相关的技术以及记录一些日常。"; // 站点简介,有利于 SEO
var site_domain = "blog.natfrp.org"; // 站点域名
var site_subtitle = "樱花内网穿透官方博客"; // 站点副标题
var site_favicon = "https://cn.tql.ink:4443/gitea/img/favicon.png"; // 站点 Logo
// 博主信息
var owner_name = "Akkariin"; // 博主名字
var owner_logo = "https://secure.gravatar.com/avatar/80962ca1ced98d0e679b2bc315d049f2?s=256" // 博主头像
var owner_desc = "鸽子王/咸鱼/phper,日常水贴摸鱼,佛系出租服务器"; // 博主简介
// 设置站点资源文件地址
var css_bootstrap = "https://cdn.lo-li.icu/wwf/bootstrap.min.css"; // Boostrap css 文件地址
var css_hljs_github = "https://cdn.lo-li.icu/wwf/github.css"; // Highlight js css 地址
var js_jquery = "https://cdn.lo-li.icu/wwf/jquery.min.js"; // JQuery 地址
var js_bootstrap = "https://cdn.lo-li.icu/wwf/bootstrap.min.js"; // Bootstrap 地址
var js_instantclick = "https://cdn.lo-li.icu/wwf/instantclick.min.js"; // InstantClick 地址
var js_showdown = "https://cdn.lo-li.icu/wwf/showdown.min.js"; // Showdown 地址
var js_showdown_table = "https://cdn.lo-li.icu/wwf/showdown-table.min.js"; // Showdown table 地址
var js_highlight = "https://cdn.lo-li.icu/wwf/highlight.min.js"; // Highlight 地址
var js_highlight_pack = "https://cdn.lo-li.icu/wwf/highlight.pack.js"; // Highlight pack 地址
// 这是一些临时变量,无需修改
var title = "";
var intitle = "";
var title2 = "";
var description = "";
var ctime = "unknown";
var isunknown = "";
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
});
var header = `<!DOCTYPE HTML>
<!-- 由 CloudFlare Workers Blog 强力驱动 -->
<!-- SakuraFrp Blog 模板 1.0 by Akkariin -->
<html lang="zh_CN">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=11">
<meta name="application-name" content="SakuraFrp Blog">
<meta name="msapplication-TileColor" content="#F1F1F1">
<link rel="shortcut icon" href="${site_favicon}" />
<meta name="description" content="{description}">
<link rel="stylesheet" href="${css_bootstrap}" crossorigin="anonymous">
<link rel="stylesheet" href="${css_hljs_github}">
<title>{title}{title_2}</title>
<style type="text/css">.pageid{margin-bottom:-26px}code{color:#484848;background-color:#f5f5f5;border-radius:0px;border:1px solid #dadada;}pre>code{color:unset;background-color:unset;border-radius:unset;border:0px;}.post-a {color: #000;text-decoration: none ! important;}.post-box {padding: 12px 20px 12px 20px;border-bottom: 1px solid rgba(0,0,0,0.07);cursor: pointer;border-left: 0px solid rgba(66, 66, 66, 0);transition-duration: 0.3s;}.post-box:hover {transition-duration: 0.3s;border-left: 5px solid rgba(66, 66, 66, 0.15);}.thread h2 {border-bottom: 1px solid rgb(238,238,238);padding-bottom: 10px;}.editor-preview pre, .editor-preview-side pre{padding: 0.5em;}.hljs{background: unset ! important;padding: 0px;}.CodeMirror{height: calc(100% - 320px);min-height: 360px;}.msgid{font-family:Consolas;}.tooltip {word-break: break-all;}h2 a{font-weight: 400;}body{/*background:url(https://i.natfrp.org/cbf5973ce9da283bc9abe307cdea7f30.jpg);*/font-family:'-apple-system','BlinkMacSystemFont','Segoe UI','Helvetica','Arial','sans-serif','Apple Color Emoji','Segoe UI Emoji','Segoe UI Symbol' ! important;font-weight:400;background-attachment:fixed;background-size:cover;background-repeat:no-repeat;background-position:center;}h2 a{color: #000;} h2 a:hover{color: #000; text-decoration: none;}.full-width{width: 100%;}.thread img{vertical-align:text-bottom ! important;max-width:100% ! important;margin-top:8px;margin-bottom:8px;}.thread table{display:block;width:100%;overflow:auto;margin-bottom:8px;}.thread table tr{background-color:#fff;border-top:1px solid #c6cbd1;}.thread table tr:nth-child(2n){background-color:#f7f7f7;}.thread table th,.thread table td{padding:10px 12px 0px 12px;border:1px solid #dfe2e5;font-size:14px;}.thread table th {padding-bottom: 10px;background: #f7f7f7;}.thread pre{margin-bottom:16px;}pre{border:none ! important;}blockquote{font-size:15px ! important;}@media screen and(max-width:768px){.copyright{text-align:center;}}</style>
<script>
var _hmt = _hmt || [];
(function() {
var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?b1f3cc985ea87c4141634fa0572a1612";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
</script>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-sm-12">
<h2><a href="/" class="post-a">{intitle}</a></h2>
<p>${site_subtitle}</p>
<hr>
</div>
<div class="col-sm-9">
<div class="thread">
`;
var modifyHeader = {};
var cookieText = "";
function getRequestParams(str) {
var index = str.indexOf("?");
str = str.substring(index + 1, str.length);
if(typeof(str) == "string"){
u = str.split("&");
var get = {};
for(var i in u){
var j = u[i].split("=");
get[j[0]] = j[1];
}
return get;
} else {
return {};
}
}
async function bloghandle(request) {
var cookie = {};
var clist = undefined;
try {
cookieText.split(';').forEach(l => {
var parts = l.split('=');
cookie[parts[0].trim()] = unescape((parts[1] || '').trim());
});
} catch(e) {
// 无可奉告
}
var $_GET = getRequestParams(request.url);
var urls = new URL(request.url);
var data = header;
if(urls.pathname == "/") {
var url = "https://raw.githubusercontent.com/" + github_base + "/master/list.json";
const init = {
method: "GET"
};
const response = await fetch(url, init);
var resptxt = await response.text();
if(cookie['list'] == undefined) {
var Days = 30;
var exp = new Date();
exp.setTime(exp.getTime() + Days*24*60*60*1000);
modifyHeader = {
"Set-Cookie" : "list="+ escape (resptxt) + ";expires=" + exp.toGMTString()
};
}
var json = JSON.parse(resptxt);
// console.log(json);
data += `<p>所有文章</p>
`;
var before_page = 0;
var current_page = 1;
var next_page = 2;
var pagenow = json.length;
var pageval = json.length - 5;
if($_GET['p'] != undefined && $_GET['p'] != "") {
pageval = json.length - (parseInt($_GET['p']) * 5);
pagenow = json.length - ((parseInt($_GET['p']) - 1) * 5) - 1;
next_page = parseInt($_GET['p']) + 1;
current_page = parseInt($_GET['p']);
before_page = parseInt($_GET['p']) - 1;
}
console.log(pageval);
var update_i = 0;
for(var i = pagenow;i >= pageval;i--) {
try {
var tmpfilename = encodeURIComponent(json[i].file
.replace(/"/g, "").replace(/posts\//ig, "").replace(/\.md/ig, ""));
var tmptime = json[i].time;
var tmptitle = json[i].title;
data += `<a href="/${tmpfilename}" class="post-a">
<div class="post-box">
<h4>${tmptitle}</h4>
<p>发表于 ${tmptime}</p>
</div>
</a>
`;
update_i++;
} catch(e) {
// 收声
}
}
console.log(update_i);
if(update_i == 0) {
data += `<p><blockquote>暂时没有文章!</blockquote></p>
`
}
data += `<br>
<p class="text-left pageid">当前在第 ${current_page} 页</p>
<p class="text-right">
`;
if(current_page > 1) {
data += `<a href="/?p=${before_page}"><button class="btn btn-default">上一页</button></a> `;
}
if(update_i >= 5) {
data += `<a href="/?p=${next_page}"><button class="btn btn-default">下一页</button></a>`;
}
data += `
</p>
</div>
`;
title = default_title;
intitle = default_intitle;
title2 = "";
} else {
var uname = unescape("posts" + urls.pathname + ".md");
try {
clist = cookie['list'];
} catch(e) {
var url = "https://raw.githubusercontent.com/" + github_base + "/master/list.json";
const init = {
method: "GET"
};
const response = await fetch(url, init);
clist = await response.text();
}
if(clist != undefined) {
try {
var json = JSON.parse(clist);
var found = false;
for(var i in json) {
tmpfilename = json[i].file.replace(/"/g, "");
tmptime = json[i].time;
tmptitle = json[i].title;
if(tmpfilename == uname) {
title = tmptitle;
intitle = tmptitle;
ctime = tmptime;
found = true;
}
}
if(!found) {
var url = "https://raw.githubusercontent.com/" + github_base + "/master/list.json";
const init = {
method: "GET"
};
const response = await fetch(url, init);
clist = await response.text();
var json = JSON.parse(clist);
for(var i in json) {
tmpfilename = json[i].file.replace(/"/g, "");
tmptime = json[i].time;
tmptitle = json[i].title;
if(tmpfilename == uname) {
title = tmptitle;
intitle = tmptitle;
ctime = tmptime;
}
}
var Days = 30;
var exp = new Date();
exp.setTime(exp.getTime() + Days*24*60*60*1000);
modifyHeader = {
"Set-Cookie" : "list="+ escape (clist) + ";expires=" + exp.toGMTString()
};
}
} catch(e) {
// 收声
}
} else {
var url = "https://raw.githubusercontent.com/" + github_base + "/master/list.json";
const init = {
method: "GET"
};
const response = await fetch(url, init);
var clist = await response.text();
var json = JSON.parse(clist);
for(var i in json) {
tmpfilename = json[i].file.replace(/"/g, "");
tmptime = json[i].time;
tmptitle = json[i].title;
if(tmpfilename == uname) {
title = tmptitle;
intitle = tmptitle;
ctime = tmptime;
}
}
var Days = 30;
var exp = new Date();
exp.setTime(exp.getTime() + Days*24*60*60*1000);
modifyHeader = {
"Set-Cookie" : "list="+ escape (clist) + ";expires=" + exp.toGMTString()
};
}
data += `</div>
<p class="text-center{isunknown}"><small>发表于 ${ctime}</small></p>
<textarea id="textdata" style="display: none;">`;
var url = "https://raw.githubusercontent.com/" + github_base + "/master/posts" + urls.pathname + ".md";
const init = {
method: "GET"
};
const response = await fetch(url, init);
if(response.status == 200) {
var resptxt = await response.text();
data += resptxt.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
description = resptxt.substring(0, 128).replace(/"/ig, "").replace(/\n/g, " ");
data += `</textarea>
<hr>
<div id="comments">评论区加载中 qwq</div>
`;
} else {
data += `### 404 Not Found
未找到您访问的页面,原因可能是:
- 该文章已被删除
- 该文章已经更改名称
- 您输入的链接不正确
<a href="/">返回 ${default_intitle} 首页</a>
</textarea>
`;
title = `404 Not Found`;
title2 = ` - ${default_title}`;
intitle = `未找到指定的页面`;
description = ``;
isunknown = " hidden";
}
title2 = ` - ${default_title}`;
}
data += `</div>
<div class="col-sm-3">
<div style="padding: 16px;text-align: center;">
<img src="${owner_logo}" style="max-width: 220px;width: 100%;border-radius: 50%;">
<h3>${owner_name}</h3>
<p class="text-left">${owner_desc}</p>
<hr>
<div class="text-left">
<h4>友情链接</h4>
<p><a href="https://www.natfrp.org/" target="_blank">Sakura Frp</a></p>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<p>Powered by CloudFlare Workers | <a href="https://github.com/kasuganosoras/cloudflare-worker-blog" target="_blank">Github</a></p>
<p>© 2019 ${default_intitle}</p>
<br><br>
</div>
</div>
</div>
<script src="${js_jquery}"></script>
<script src="${js_bootstrap}" crossorigin="anonymous"></script>
<script src="${js_instantclick}" data-no-instant></script>
<script src="${js_showdown}" type="text/javascript"></script>
<script src="${js_showdown_table}" type="text/javascript"></script>
<script src="${js_highlight}"></script>
<script src="${js_highlight_pack}"></script>
<script src="https://comments.natfrp.org/comments.js?s=2"></script>
<script type="text/javascript">
var init = {
site: "${site_domain}",
cid: "posts${urls.pathname}.md"
};
hljs.initHighlightingOnLoad();
var md = new showdown.Converter({extensions: ['table']});
md.setOption('simplifiedAutoLink', true);
md.setOption('simpleLineBreaks', true);
md.setOption('openLinksInNewWindow', true);
md.setOption('noHeaderId', true);
window.onload = function() {
try {
$(".thread").html(md.makeHtml($("#textdata").val()));
document.querySelectorAll('pre code').forEach(function(e) {
hljs.highlightBlock(e);
});
CommentsInit(comments, init);
} catch(e) {}
}
</script>
<script data-no-instant>
InstantClick.init();
InstantClick.on('change', function() {
try {
$(".thread").html(md.makeHtml($("#textdata").val()));
document.querySelectorAll('pre code').forEach(function(e) {
hljs.highlightBlock(e);
});
CommentsInit(comments, init);
} catch(e) {}
});
</script>
</body>
</html>
`;
data = data.replace(/\{title\}/ig, title)
.replace(/\{intitle\}/ig, intitle)
.replace(/\{title\_2\}/ig, title2)
.replace(/\{isunknown\}/ig, isunknown)
.replace(/\{description\}/ig, description);
return data;
}
/**
* Respond to the request
* @param {Request} request
*/
async function handleRequest(request) {
if(new URL(request.url).protocol != "https:") {
var rhttps = new Response("Location to https", {status: 301});
rhttps.headers.set("Location", request.url.replace("http://", "https://"));
return rhttps;
}
cookieText = request.headers.get("cookie");
var resp = new Response(await bloghandle(request), {status: 200});
resp.headers.set("Content-Type", "text/html");
if(modifyHeader != undefined) {
for(var index in modifyHeader) {
resp.headers.set(index, modifyHeader[index]);
}
}
return resp;
}
如何写作?
首先创建一个 Github 项目,名字随意,然后将这个项目 clone 到本地。
# 示例
git clone https://github.com/kasuganosoras/cloudflare-worker-blog
cd cloudflare-worker-blog/
进入项目文件夹,新建一个 posts
文件夹
mkdir posts/
在里面编写文章,内容一般用 .md 后缀即可,例如 helloworld.md
写完之后回到项目根目录(就是上级目录),然后新建一个 list.json
touch list.json
编辑 list.json
,在里面写入以下内容
[
{
"title":"文章名称",
"time":"发布时间",
"file":"posts/helloworld.md(或者其他名字)"
}
]
如果你有多篇文章就这样写:
[
{
"title":"文章1",
"time":"2019-06-01",
"file":"posts/1.md"
},
{
"title":"文章2",
"time":"2019-06-03",
"file":"posts/2.md"
},
{
"title":"文章3",
"time":"2019-06-07",
"file":"posts/3.md"
} 注意json格式,最后一篇文章的这里不需要逗号
]
一切就绪后,使用 git push
命令将代码推送到仓库上。
然后修改你的 workers,设置 github_base 为你的仓库名称,例如 kasuganosoras/cloudflare-worker-blog
现在访问你的 Workers 即可看到文章。
自定义域名
获取到自己的**.workers.dev
域名 Cname 到(**.workers.dev)
然后去 Workers 点击 ADD route
输入自定义域名,例如 iloli.icu/*
后面要加上 /*
,下面的 workers 选择刚才的项目