最近又研究研究了vercel,看到了database,发现vercel可以直接整合各大数据库,其中MongoDB有免费够用的方案,于是我试了试,用数据库存储博客浏览量。
初始化
首先注册一个免费的MongoDB账号(无需绑卡,他真的,我哭死),免费版512 MB容量,普通使用绰绰有余,虽然是共享内存,但实测速度并不慢。
然后在vercel上一键整合到nuxt3-blog项目,控制界面就可以看到MONGODB_URI
环境变量了:
MONGODB_URI
实在太方便啦,关键还免费。OK,现在非代码部分已经over了,下面是喜闻乐见的Talk is cheap,show me the code环节。分两部分:
- 功能代码:数据库增删改查
- 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调用功能代码。
我发现这种方式可以封装为通用代码,目前写了一部分,挖个坑,放到后面写吧。下面贴一个示例:
// 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!");
}
}
// dev-server/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免费了,顺便集成到了图片上传,现在图片上传支持自动压缩 随着功能越做越多,感觉偏离了当初追求简单的核心。最近在学双拼,写完这篇文章可真艰难!