终极白嫖:使用免费MongoDB显示浏览量

  最近又研究研究了vercel,看到了database,发现vercel可以直接整合各大数据库,其中MongoDB有免费够用的方案,于是我试了试,用数据库存储博客浏览量。

初始化

  首先注册一个免费的MongoDB账号(无需绑卡,他真的,我哭死),免费版512 MB容量,普通使用绰绰有余,虽然是共享内存,但实测速度并不慢。   然后在vercel上一键整合到nuxt3-blog项目,控制界面就可以看到MONGODB_URI环境变量了: MONGODB_URIMONGODB_URI   实在太方便啦,关键还免费sticker。OK,现在非代码部分已经over了,下面是喜闻乐见的Talk is cheap,show me the code环节。分两部分:

  1. 功能代码:数据库增删改查
  2. API代码:前端调用

功能代码

  这里省略创建并缓存数据库连接的代码,我参考的是官方示例。另外有两个函数:

import { HeaderTabUrl } from "./../../../utils/types"; import { getCollection } from "./mongodb"; type VisitorsDb = { nid: number, ntype: HeaderTabUrl, nvisitors?: number, } // 只取 nid 和 nvisitors const sqlOptions = { projection: { _id: 0, nid: 1, nvisitors: 1 } }; // 获取整个列表的浏览量 export async function getVisitors (type: HeaderTabUrl) { const collection = await getCollection<VisitorsDb>(); const query: Partial<VisitorsDb> = { ntype: type }; const results = await collection.find(query, sqlOptions); return await results.toArray(); } // 浏览量 +1 export async function increaseVisitors ({ id, type }: {id: number, type: HeaderTabUrl}) { const collection = await getCollection<VisitorsDb>(); const preset: VisitorsDb = { nid: id, ntype: type }; // 若已有,则 +1 const result = await collection.findOneAndUpdate(preset, { $inc: { nvisitors: 1 } }, sqlOptions); if (result) { return result.value.nvisitors + 1; } else { // 没有则新增 await collection.insertOne({ ...preset, nvisitors: 1 }); return 1; } }

API代码

  API代码分两部分,开发模式和部署模式。区别是:

  • 开发模式下,使用vite的HMR API调用功能代码。
  • 部署模式下,使用vercel的serverless function调用功能代码。

  我发现这种方式可以封装为通用代码,目前写了一部分,挖个坑,放到后面写吧sticker。下面贴一个示例:

// api/db/inc-visitors.ts 部署模式走这里,serverless服务端 // 另外还有 api/db/get-visitors.ts 这里不写了 import type { VercelRequest, VercelResponse } from "@vercel/node"; import { increaseVisitors } from "../../lib/api/db/visitors"; export default async function (req: VercelRequest, res: VercelResponse) { if (req.method.toUpperCase() === "POST") { try { res.status(200).send(await increaseVisitors({ id: req.body.id, type: req.body.type })); } catch (e) { res.status(503).send(e.toString()); } } else { res.status(405).send("Post only!"); } }
// vite-plugins/visitors.ts 开发模式走这里,devServer import type { Plugin } from "vite"; import { getVisitors, increaseVisitors } from "../lib/api/db/visitors"; export default { name: "nb-visitors-plugin", configureServer (server) { server.ws.on("get-visitors", async (data, client) => { try { const result = await getVisitors(data.type); client.send("nb:get-visitors:result", result); } catch (e) { client.send("nb:get-visitors:result", e.toString()); } }); server.ws.on("increase-visitors", async (data, client) => { try { client.send("nb:inc-visitors:result", await increaseVisitors(data)); } catch (e) { client.send("nb:inc-visitors:result", e.toString()); } }); } } as Plugin;
  • 在详情界面调用:
// utils/public/detail.ts // ...... if (item.id) { // 开发模式和部署模式用一样的callback const setVisitors = (data) => { item.visitors = data; }; const query = { id: item.id, type: targetTab.url }; if (isDev) { // 开发模式,HMR API 调用 import.meta.hot.send("increase-visitors", query); devHotListen("nb:inc-visitors:result", setVisitors); // 封装的import.meta.hot.on,这里省略 } else { // 部署模式,serverless 调用 axios.post("/api/db/inc-visitors", query).then(res => setVisitors(res.data)); } } // ......

其他

  发现tinypng的api免费了,顺便集成到了图片上传,现在图片上传支持自动压缩sticker   随着功能越做越多,感觉偏离了当初追求简单的核心。最近在学双拼,写完这篇文章可真艰难!

1年前
0