前段时间大发哥TG频道说要发一个用CFWorker实现文章点赞和浏览数统计的功能,很快啊,星期二教程就出来了: Hugo Cloudflare Worker 。
整体方案是使用CfWorker 实现api请求,再用D1做数据持久化,效果还是很赞的。
本来我是想照着教程完全一步步来的,但是想到我前文 将博主 PC 上使用的应用信息实时显示到博客 在折腾时搭了一个API服务,就不想再去用CFWorker了,打算直接在自己服务器上实现这个功能,顺便再实践一下用Express搭配数据库工具。
所以打算稍改一下,结构如下:
前端我这里用的Alpinejs做驱动,前文 用 Alpinejs 完成主题切换功能 介绍过这个库,在不使用Vue等工具下普通HTML的一个替代方案,好用。
ID
(这里我因为用的Ghost做数据源,所以自带一个MD5的ID,用一个固定唯一值就好)x-data
为后面js中初始化的 post_action
对象x-show
调用 initViews
、 initLike
实现组件数据初始化@click
绑定 like
函数实现点赞之前做那个PC上APP状态显示的时候使用了一下CSS动画,效果还不错,这里再次借鉴一下那个思路,点赞后实现了一个心脏跳动的感觉。
.post_action {
.post_like {
align-items: center;
cursor: pointer;
display: flex;
height: 6rem;
position: relative;
text-align: center;
flex-direction: column;
justify-content: center;
svg {
fill: none;
stroke: var(--body-font-color);
height: 1.5em;
margin-inline-end: 00;
width: 1.5em;
}
/** 点赞按钮的激活效果 **/
&.active svg{
fill: var(--hint-color-danger);
stroke: var(--hint-color-danger);
animation: growAndFade 1s 1;
transform-origin: center;
}
}
.post_like_desc{
font-style: oblique;
padding: 8px 0px;
color: var(--gray-500);
font-size: 14px;
}
}
/** 点赞动画 **/
@keyframes growAndFade {
0% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(2);
opacity: 0.3; /* 这里调整透明度,以符合动画结束立即恢复的需求 */
}
100% {
transform: scale(1);
opacity: 1;
}
}
var apiUrl = "https://yourapidomain.com";
Alpine.data("post_action", () => ({
apiUrl: apiUrl,
// 点赞函数
like: (post_id) => {
// 初始化一些元素变量
var likelist = localStorage.getItem("lieklist") || "";
var likeButton = document.querySelector(".post_like");
var likeText = document.querySelector(".post_likes");
// 每次点赞都去本地数据中匹配一下
// 我这里将用户浏览的数据拼成字符串处理的,以后应该会有性能问题
if (likelist.indexOf(post_id + ",") != -1) {
console.log("你已经点过赞了");
likeButton.classList.add("active");
} else {
// 如果没有就发起POST请求更新数据
fetch(`${apiUrl}/post/${post_id}/like`, { method: "post" })
.then((res) => {
return res.json();
})
.then((data) => {
// 成功了就做一些页面数据同步的工作
console.log("点赞成功" + JSON.stringify(data));
localStorage.setItem("lieklist", likelist + post_id + ",");
likeButton.classList.add("active");
likeText.innerText = data.likes
})
.catch((error) => {
console.error(
"There was a problem with the fetch operation:",
error
);
});
}
},
// 后续的代码逻辑上都差不多,我就不一一写备注了。
initLike: (post_id) => {
var likelist = localStorage.getItem("lieklist") || "";
var likeButton = document.querySelector(".post_like");
var likeText = document.querySelector(".post_likes");
if (likelist.indexOf(post_id + ",") != -1) {
likeButton.classList.add("active");
}
fetch(`${apiUrl}/post/${post_id}/like`)
.then((res) => {
return res.json();
})
.then((data) => {
if (data.likes) {
likeButton.dataset.like = data.likes;
likeText.innerText = data.likes;
}else{
likeButton.dataset.like = 0;
likeText.innerText = 0;
}
}).catch((error) => {
likeButton.dataset.like = 0;
likeText.innerText = 0;
console.error(
"There was a problem with the fetch operation:",
error
);
});
return true;
},
initViews: (post_id) => {
var viewlist = localStorage.getItem("viewlist") || "";
var viewText = document.querySelector(".post_views");
if (viewlist.indexOf(post_id + ",") != -1) {
fetch(`${apiUrl}/post/${post_id}/views`)
.then((res) => {
return res.json();
})
.then((data) => {
if (data.Views) {
viewText.innerText = data.Views;
}else{
viewText.innerText = 0;
}
});
} else {
fetch(`${apiUrl}/post/${post_id}/views`, { method: "post" })
.then((res) => {
return res.json();
})
.then((data) => {
console.log("浏览量" + JSON.stringify(data));
localStorage.setItem("viewlist", viewlist + post_id + ",");
viewText.innerText = data.Views;
})
.catch((error) => {
viewText.innerText = 0;
console.error(
"There was a problem with the fetch operation:",
error
);
});
}
return true;
},
}));
后端的实现其实还是和之前一样,只不过这次多了一个数据库的持久化操作。
因为是个轻量型的服务,我也不想搞什么MySQL之类的服务,甚至连SqLite也不想用,所以想到之前在使用 Twikoo 的时候,发现它用的一个数据服务是一个以 JSON 为结构的数据库:LokiJS,特性如下,应付这个功能应该绰绰有余了。
Loki的数据操作很简单、方便,会用JavaScript就可以,对于我来说上手难度更低。
// LojiJS基础操作
// 新建数据库
var db = new loki("blog.db");
// 添加一个「表」(集合)
db.addCollection("posts");
//通过id查找
var post = posts.findOne({ post_id: post_id });
// 插入数据
posts.insert({
post_id: post_id,
likes: 1,
Views: 0,
});
// 操作更新到数据库中
posts.update(post);
另外,
Loki中 addCollection
这个操作是会覆盖你原有的数据的,我之前用的时候以为打开自动加载、自动保存就好了,没有判断表存不存在,导致了每次服务器重启都会新添加一个表进去,导致原表数据被覆写。
另外Express的接口代码和前端的JS代码一样没有做结构整理,图方便全部写了一遍,后期是有很大优化空间的。
// 创建数据库
// 开启自动加载,自动保存
var db = new loki("blog.db", {
autoload: true,
// 数据库加载完毕后回调
autoloadCallback: loadHandler,
autosave: true,
});
let posts;
function loadHandler() {
posts = db.getCollection("posts");
// 如果posts不存在才创建
if (posts === null) {
db.addCollection("posts");
}
}
app.get("/post/:key/like", (req, res) => {
const post_id = req.params.key;
try {
let post = posts.findOne({post_id:post_id})
if(post){
res.status(200).json(post)
}
} catch (e) {
res.status(500).json({ error: e });
}
});
app.post("/post/:key/like", (req, res) => {
const post_id = req.params.key;
try {
let post = posts.findOne({ post_id: post_id });
if (!post) {
posts.insert({
post_id: post_id,
likes: 1,
Views: 0,
});
post = posts.findOne({ post_id: post_id });
} else {
post.likes += 1;
}
posts.update(post);
res.status(200).json(post);
} catch (e) {
res.status(500).json({ error: e });
}
});
app.post("/post/:key/views", (req, res) => {
const post_id = req.params.key;
try {
let post = posts.findOne({ post_id: post_id });
if (!post) {
posts.insert({
post_id: post_id,
likes: 0,
Views: 1,
});
post = posts.findOne({ post_id: post_id });
} else {
post.Views += 1;
}
posts.update(post);
res.status(200).json( post );
} catch (e) {
res.status(500).json({ error: e });
}
});
app.get("/post/:key/views", (req, res) => {
const post_id = req.params.key;
try {
let post = posts.findOne({post_id:post_id})
if(post){
res.status(200).json(post)
}
} catch (e) {
res.status(500).json({ error: e });
}
});
弄完这些也算是对NodeJS发布一个拥有完整功能的网站有了些基础了解,不过这种了解暂时还很片面,在架构、性能、安全性方面肯定还有很多需要学习的地方,之后如果碰到了以上问题了再做学习、优化。
在折腾中学习、进步,这种感觉真棒按。
加入评论