Files
fatsify-schema/app.js
2025-09-22 16:00:32 +08:00

201 lines
6.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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