// ====== 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();