201 lines
6.8 KiB
JavaScript
201 lines
6.8 KiB
JavaScript
// ====== 1. 引入和设置 ======
|
||
import Fastify from 'fastify';
|
||
import mongoPlugin from '@fastify/mongodb';
|
||
import { ObjectId } from 'mongodb';
|
||
import { request as undiciRequest } from 'undici';
|
||
|
||
const fastify = Fastify({
|
||
logger: true, // 启用日志记录
|
||
});
|
||
|
||
|
||
// 你的 MongoDB 连接字符串
|
||
const mongoConnectionString = "mongodb://mongo_c6bNmG:mongo_tyNkXh@83.229.121.44:27017/todo_app_fastify?authSource=admin";
|
||
|
||
// ====== 2. 注册 MongoDB 插件 ======
|
||
// fastify.register() 是 Fastify 插件系统的核心
|
||
// 我们在这里告诉 Fastify:“请加载 @fastify/mongodb 插件,
|
||
// 并使用这个连接字符串去连接数据库。”
|
||
fastify.register(mongoPlugin, {
|
||
forceClose: true, // 在应用关闭时强制关闭连接
|
||
url: mongoConnectionString,
|
||
});
|
||
// 插件注册后,Fastify 的实例上会多出一个 `mongo` 对象 (`fastify.mongo`)
|
||
// 我们可以通过它来访问数据库和 ObjectId 等工具。
|
||
|
||
// ====== 3. 创建 CRUD 路由 ======
|
||
// 我们将创建一个简单的 "items" 集合来存放商品
|
||
|
||
fastify.get('/', async (request, reply) => {
|
||
return { message: '欢迎来到 Fastify 核心功能示例!'};
|
||
})
|
||
|
||
const createUserSchema = {
|
||
// body 部分定义了对 POST 请求体的验证规则
|
||
body: {
|
||
type: 'object',
|
||
required: ['name', 'email'], // name 和 email 字段是必需的
|
||
properties: {
|
||
name: { type: 'string' },
|
||
email: { type: 'string', format: 'email' }, // 甚至可以验证 email 格式
|
||
},
|
||
},
|
||
//response 部分定义了响应的格式,Fastify 会用它来极速序列化 JSON
|
||
response: {
|
||
201: { // 201 状态码对应的响应格式
|
||
type: 'object',
|
||
properties: {
|
||
_id: { type: 'string' },
|
||
name: { type: 'string' },
|
||
email: { type: 'string' },
|
||
},
|
||
// 建议明确哪些字段是必须返回的
|
||
required: ['_id', 'name', 'email'],
|
||
},
|
||
},
|
||
};
|
||
|
||
// --- CREATE (增) ---
|
||
// 创建一个新商品
|
||
fastify.post('/users',{ schema : createUserSchema}, async (request, reply) => {
|
||
// `fastify.mongo.db` 指向你连接的那个数据库实例 (my-beginner-db)
|
||
const itemsCollection = fastify.mongo.db.collection('items');
|
||
|
||
// request.body 包含了 POST 请求发来的 JSON 数据
|
||
const newUsers = request.body;
|
||
|
||
// 调用 MongoDB 的 insertOne 方法插入数据
|
||
const result = await itemsCollection.insertOne(newUsers);
|
||
fastify.log.info({ msg: '用户创建成功', result: result });
|
||
|
||
if (!result.acknowledged) {
|
||
return reply.status(500).send({ message: 'Failed to insert user into database' });
|
||
}
|
||
|
||
// 关键修改:我们需要构建一个对象,使其结构精确匹配 response.201 schema
|
||
const responsePayload = {
|
||
_id: result.insertedId.toString(), // 将 ObjectId 转换为字符串
|
||
name: newUsers.name, // 从原始请求体中获取 name
|
||
email: newUsers.email, // 从原始请求体中获取 email
|
||
};
|
||
|
||
// 现在 responsePayload 的结构完全符合 response.201 schema
|
||
return reply.status(201).send(responsePayload); // 发送这个符合 schema 的对象
|
||
});
|
||
|
||
|
||
|
||
// --- 正确实现的 Hook ---
|
||
fastify.addHook('onRequest', async (request, reply) => {
|
||
// 这个钩子会在每个请求匹配到路由之前执行
|
||
fastify.log.info(`收到一个 ${request.method} 请求,访问路径: ${request.url}`);
|
||
|
||
// --- 转发到 Gotify ---
|
||
const gotifyPayload = {
|
||
title: "新请求通知",
|
||
message: `收到 ${request.method} 请求,ip ${request.ip}路径: ${request.url}`,
|
||
priority: 5,
|
||
};
|
||
|
||
fastify.log.info("准备向 Gotify 发送通知...");
|
||
|
||
// 2.【关键】采用“即发即忘”模式,不要使用 await 来阻塞主流程
|
||
undiciRequest('https://gotify.zotv.ru/message?token=A1wFaeaj-VskqyF', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(gotifyPayload),
|
||
})
|
||
.then(response => {
|
||
// 可选:如果成功,可以在后台记录一下
|
||
// 注意:这里的 .statusCode 是 undici v5 及更早版本的用法,新版可能不同
|
||
if (response.statusCode >= 200 && response.statusCode < 300) {
|
||
fastify.log.info("Gotify 通知发送成功!");
|
||
} else {
|
||
fastify.log.warn(`Gotify 通知发送异常,状态码: ${response.statusCode}`);
|
||
}
|
||
// 消耗掉响应体,防止内存泄漏
|
||
return response.body.dump();
|
||
})
|
||
.catch(error => {
|
||
// 3.【关键】必须捕获错误,否则一个失败的通知会让你的整个服务器进程崩溃!
|
||
fastify.log.error({ msg: 'Gotify 通知发送失败', err: error });
|
||
});
|
||
|
||
// 4.【关键】注意,这里没有 await,钩子函数会立即执行完毕,不会等待通知结果
|
||
});
|
||
|
||
// --- READ (查) ---
|
||
// 获取所有商品
|
||
fastify.get('/users', async (request, reply) => {
|
||
const itemsCollection = fastify.mongo.db.collection('items');
|
||
const items = await itemsCollection.find({}).toArray();
|
||
return items;
|
||
});
|
||
|
||
// 获取单个商品 (通过 ID)
|
||
fastify.get('/items/:id', async (request, reply) => {
|
||
const itemsCollection = fastify.mongo.db.collection('items');
|
||
|
||
// **关键点**: MongoDB 的 `_id` 是一个特殊的 ObjectId 类型, 不是普通字符串。
|
||
// 我们需要从 fastify.mongo 获取 ObjectId 构造函数来转换 URL 中的 id 参数。
|
||
const { ObjectId } = fastify.mongo;
|
||
const idToFind = new ObjectId(request.params.id);
|
||
|
||
const item = await itemsCollection.findOne({ _id: idToFind });
|
||
|
||
if (!item) {
|
||
return reply.status(404).send({ error: '商品未找到' });
|
||
}
|
||
return item;
|
||
});
|
||
|
||
// --- UPDATE (改) ---
|
||
// 更新一个商品
|
||
fastify.put('/items/:id', async (request, reply) => {
|
||
const itemsCollection = fastify.mongo.db.collection('items');
|
||
const { ObjectId } = fastify.mongo;
|
||
const idToUpdate = new ObjectId(request.params.id);
|
||
|
||
const updateData = request.body;
|
||
|
||
const result = await itemsCollection.updateOne(
|
||
{ _id: idToUpdate }, // 筛选条件
|
||
{ $set: updateData } // 更新操作
|
||
);
|
||
|
||
if (result.modifiedCount === 0) {
|
||
return reply.status(404).send({ error: '未找到商品或无需更新' });
|
||
}
|
||
|
||
return { message: '商品更新成功' };
|
||
});
|
||
|
||
// --- DELETE (删) ---
|
||
// 删除一个商品
|
||
fastify.delete('/items/:id', async (request, reply) => {
|
||
const itemsCollection = fastify.mongo.db.collection('items');
|
||
const { ObjectId } = fastify.mongo;
|
||
const idToDelete = new ObjectId(request.params.id);
|
||
|
||
const result = await itemsCollection.deleteOne({ _id: idToDelete });
|
||
|
||
if (result.deletedCount === 0) {
|
||
return reply.status(404).send({ error: '商品未找到' });
|
||
}
|
||
|
||
// 204 No Content 是一个常见的成功删除的响应
|
||
return reply.status(204).send(idToDelete);
|
||
});
|
||
|
||
|
||
// ====== 4. 启动服务器 ======
|
||
const start = async () => {
|
||
try {
|
||
await fastify.listen({ port: 3000, host: '0.0.0.0' });
|
||
} catch (err) {
|
||
fastify.log.error(err);
|
||
process.exit(1);
|
||
}
|
||
};
|
||
|
||
start(); |