这篇文章涵盖了项目的组织、代码风格、日志记录、配置管理、HTTP服务初始化和全局错误处理六大核心要素。
项目结构
在node项目中,下面是一个常见的组织结构。这种清晰、易扩展、易维护,是非常推荐的代码组织方式:
当然,某交友网站上还有形形色色的各种结构。有老哥觉得哪种不错可以给个项目地址,我也去学习一下
bash
project-root/
│
├── src/ # 项目的源代码
│ ├── plugins/ # Fastify 插件(用于共享功能,如数据库连接)
│ ├── routes/ # 路由定义目录
│ │ ├── __tests__/ # 路由的单元测试
│ │ └── index.ts # 路由入口文件,用于汇总所有路由
│ │
│ ├── services/ # 业务逻辑层,可能还包含服务的模型(MVC中的Model)
│ ├── utils/ # 工具方法和通用函数
│ ├── config.ts # 配置文件(环境变量等)
│ ├── webserver.ts # Fastify 实例创建和配置
│ └── index.ts # 应用的入口点
│
├── test/ # 集成测试
│
├── public/ # 静态文件,如图片、样式表、JavaScript 文件等
│
├── views/ # 模板文件,如果使用了模板引擎
│
├── dist/ # TypeScript 编译后生成的 JavaScript 文件
│
├── node_modules/ # Node.js 创建的项目依赖目录
│
├── .env # 环境变量文件
├── .gitignore # Git 忽略的文件和目录
├── package.json # 项目的元数据和依赖关系列表
├── tsconfig.json # TypeScript 编译器的配置文件
└── README.md # 项目说明文件
-
Plugins(插件) : 在
plugins
目录中,可以放置 Fastify 插件,这些插件可以用来封装功能并在整个应用中复用,比如数据库连接、认证插件等。 -
Routes(路由) :
routes
盔里包含应用的路由定义。每个路由可以有自己的处理函数、钩子(hooks)、选项等。通常每个文件定义一组相关的路由,例如users.js
可以包含所有用户相关的路由。 -
Services(服务) :
services
目录包含业务逻辑,它们可以被路由处理程序调用。这些服务可以处理数据验证、数据库交互等任务。 -
Utilities(工具) :
utils
目录用于存放可在多个地方使用的通用代码,比如日期时间处理函数、自定义数据验证器等。 -
Configuration(配置) :
config.ts
文件用于集中管理应用配置,如数据库连接信息、第三方服务的 API 密钥等。 -
Server(服务器) :
server.ts
文件用于创建和配置 Fastify 实例,包括插件的注册、中间件、钩子等。 -
Application Entry(应用入口) :
index.ts
是应用程序的主入口文件,用于启动服务器。
代码风格检查
在TypeScript项目中加入ESLint需要几个步骤,包括安装必要的包、初始化ESLint配置文件以及根据需要调整配置。下面是一个详细的指南:
1. 安装必要的包
首先,需要安装eslint
本身,以及一些支持TypeScript的插件和解析器。打开终端,切换到的项目目录,然后运行以下命令:
bash
npm install eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin --save-dev
这里的包说明如下:
eslint
: ESLint的核心包。@typescript-eslint/parser
: 一个解析器,允许ESLint理解TypeScript代码。@typescript-eslint/eslint-plugin
: 包含了一系列特定于TypeScript的linting规则。
2. 初始化ESLint配置文件
接下来,需要创建一个ESLint的配置文件。这可以通过运行ESLint的初始化命令来完成:
bash
npx eslint --init
You can also run this command directly using 'npm init @eslint/config'.
✔ How would you like to use ESLint? · style
✔ What type of modules does your project use? · esm
✔ Which framework does your project use? · none
✔ Does your project use TypeScript? · No / Yes
✔ Where does your code run? · node
✔ How would you like to define a style for your project? · guide
✔ Which style guide do you want to follow? · standard-with-typescript
✔ What format do you want your config file to be in? · JavaScript
Checking peerDependencies of eslint-config-standard-with-typescript@latest
The config that you've selected requires the following dependencies:
eslint-config-standard-with-typescript@latest @typescript-eslint/eslint-plugin@^6.4.0 eslint@^8.0.1 eslint-plugin-import@^2.25.2 eslint-plugin-n@^15.0.0 || ^16.0.0 eslint-plugin-promise@^6.0.0 typescript@*
✔ Would you like to install them now? · No / Yes
A config file was generated, but the config file itself may not follow your linting rules.
Successfully created .eslintrc.js file in
这个命令会引导通过一系列问题来创建一个基本的配置文件。对于TypeScript项目,确保选择:
- "To check syntax, find problems, and enforce code style"(检查语法,发现问题,并强制代码风格)。
- "TypeScript"作为的项目使用的语言。
- 选择的项目是否运行在浏览器或Node.js环境中。
- 选择喜欢的代码风格指南(如Airbnb、Standard或其他)。
这将生成一个.eslintrc.*
文件(格式可能是JSON、YAML或JS),其中包含了的基本配置。
3. 调整ESLint配置(可选)
根据的项目需求,可能需要调整生成的ESLint配置文件。例如,可以在.eslintrc.*
文件中添加@typescript-eslint
插件和规则。这里是一个简单的配置示例:
json
{
// 指定解析器,这里使用的是"@typescript-eslint/parser",它允许ESLint解析TypeScript代码
"parser": "@typescript-eslint/parser",
// "extends"字段列出了一系列的配置,这些配置定义了一组规则,你的项目将继承和遵循这些规则
"extends": [
"eslint:recommended", // 继承ESLint官方推荐的规则集,这为JavaScript代码提供了一套核心的静态检查规则
"plugin:@typescript-eslint/eslint-recommended", // 调整一些来自eslint:recommended的规则,以更好地适应TypeScript
"plugin:@typescript-eslint/recommended" // 应用来自@typescript-eslint插件的推荐规则,这些规则专门针对TypeScript代码的静态检查
],
// "plugins"字段列出了项目中使用的插件,"@typescript-eslint"插件提供了针对TypeScript的规则和扩展
"plugins": [
"@typescript-eslint"
],
// "env"定义了代码的运行环境,每种环境都预定义了一组全局变量
"env": {
"node": true, // 启用Node.js全局变量和Node.js作用域
"es6": true // 启用ES6的新特性以及相应的全局变量(例如Set和Map)
},
// "parserOptions"提供给解析器的选项
"parserOptions": {
"ecmaVersion": 2020, // 指定ECMAScript版本,这里是2020,允许解析最新的ES2020语法
"sourceType": "module" // 设置代码模块化方式,默认是"script",这里使用"module"表示使用ES6模块
},
// "rules"字段允许定义自己的规则或覆盖extends中的规则
"rules": {
// 自定义规则
// 例如:"no-unused-vars": "warn",表示未使用的变量会被警告
}
}
4. 添加Lint脚本到package.json
为了方便地运行ESLint,可以在package.json
的scripts
部分添加一个lint脚本:
"lint": "eslint 'src/**/*.{js,ts}' --fix",
json
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"prettier": "npx prettier --write **/*.ts",
"lint": "eslint 'src/**/*.{js,ts}' --fix",
"start": "node dist/index.js",
"dev": "nodemon --watch src -e ts --exec ts-node --esm src/index.ts",
"build": "tsc && tsc-alias"
},
这样,就可以通过运行npm run lint
来检查的项目中的TypeScript文件了。
5. 运行ESLint
最后,运行以下命令来检查的代码:
bash
npm run lint
如果有任何问题,ESLint会列出它们。也可以使用--fix
选项来自动修复一些问题:
bash
npm run lint -- --fix
这些步骤应该能帮助在TypeScript项目中成功地集成ESLint。
自定义日志
Fastify内置日志
Fastify框架内置了pino作为其日志库。Pino是一个非常快速的Node.js日志库,它提供了多种日志级别(如info
、error
、debug
等),以及可配置的日志输出格式。Fastify选择Pino作为内置日志库的原因之一是其性能优异,对应用程序的性能影响较小。
使用Fastify时,可以在创建Fastify实例时配置日志选项,例如:
javascript
const fastify = require('fastify')({
logger: true
})
这将启用内置的Pino日志记录,也可以通过传递更详细的配置来自定义日志行为,如指定日志级别、自定义序列化函数等。
为什么不使用console
作为日志
在Node.js应用程序中,虽然使用console.log()
、console.error()
等方法进行日志记录是最简单直接的方式,但这种做法在生产环境中通常不推荐,原因包括:
- 性能问题 :
console.log()
会导致同步写入操作,尤其是在高负载情况下,可能会显著影响应用程序的性能。 - 日志管理不便 :使用
console
进行日志记录,缺乏灵活的日志级别控制,且不易于集中管理和查询。 - 格式化和序列化 :
console
方法输出的日志难以自定义格式,不利于将日志输出到文件或远程日志系统,并且在处理复杂对象时可能不如专业日志库灵活。
其他Node.js日志库
除了Pino之外,Node.js生态系统中还有许多其他优秀的日志库,可以根据项目需求选择使用,例如:
- Winston:一个功能丰富的日志库,支持多种传输方式(如文件、控制台和远程服务),并允许自定义日志格式。
- Bunyan:另一个流行的JSON日志库,提供了简单的API和丰富的日志级别,易于集成到各种系统中。
为什么还要单独创建一个日志实例
已经内置了pino日志了,为什么还要自定义一个pino日志呢?除了方便移植之外还为了不破坏现在fastify内置的日志。总结下来说,就是以下几点:
- 日志分离:自定义日志实例允许将应用日志与框架日志分开,便于管理和分析。
- 细致控制:提供更多自定义选项,如日志级别、格式化和旋转策略,满足特定需求。
- 第三方集成:方便与第三方日志服务集成,如Loggly或Datadog。
- 模块化重用:在多模块应用或微服务中,方便日志配置和行为的重用。
- 场景灵活性 :适用于不仅限于HTTP请求处理的日志记录场景,如后台任务、心跳等
首先,需要安装 pino
和 pino-pretty
,这两个库分别用于创建日志记录器和美化日志输出。
bash
npm install pino pino-pretty
# 应该是不用装pino,fastify依赖中有这个。但是我没试过
然后,可以创建一个 logger.ts
文件来封装日志处理逻辑:
typescript
// src/logger.ts
import pino from 'pino';
// 确定当前的运行环境
const isProduction = process.env.NODE_ENV === 'production';
// 创建Pino日志实例
const logger = pino({
// 基本配置
base: {
pid: false,
},
// 时间戳配置
timestamp: pino.stdTimeFunctions.isoTime,
// 生产中异步生成日志
transport: isProduction
? undefined
: {
target: 'pino-pretty',
options: { colorize: true },
},
});
export default logger;
这段代码创建了一个 pino
日志实例,它配置了以下几点:
transport
: 使用pino-pretty
来美化日志输出,使其在开发过程中更易于阅读。base
: 默认情况下,Pino 会记录进程 ID (pid
) 和主机名 (hostname
)。在这里,我们设置pid: false
来禁用进程 ID 的记录。timestamp
: Pino 允许自定义时间戳的格式。在这里,我们使用了一个函数来返回当前时间的 ISO 字符串。
现在,可以在的应用程序中导入 logger
并使用它来记录日志:
typescript
import logger from './logger/logger';
logger.info('信息日志');
logger.error('错误日志');
这样,就有了一个简单的日志处理器,可以根据需要进一步扩展和自定义。例如,可以添加更多的配置项,如日志级别或者根据不同环境配置不同的日志处理策略。
配置管理
这里我们先试用yaml文档的方式来做默认配置,如果喜欢其他的方式可以自行修改一下。总体的逻辑是不变的:先获取当前环境,然后返回相应的配置
bash
npm i yaml
# 这个库本身是ts写的,不用安装额外的类型库
mkdir src/config
touch src/config/index.ts
touch src/config/config.d.ts
主要逻辑:
- 定义了
Config
和ExtendedConfig
接口,其中ExtendedConfig
允许包含任意额外的属性。 - 使用
getEnv
函数来确定当前的运行环境。 getConfig
函数用于加载和解析指定环境对应的配置文件。如果已经加载过配置,它会从缓存中返回配置,否则会从文件系统中读取配置文件,解析它,并将其存储在缓存中以备后续使用。- 如果在读取或解析配置文件过程中发生错误,程序将输出错误信息并终止执行。
类型注解和代码
类型注解
typescript
// src/config/config.d.ts
// 定义 Config 接口,用于描述配置文件的结构
interface Config {
database: {
host: string
port: number
}
}
// 定义 ExtendedConfig 接口,它继承自 Config 接口,并允许有任意的额外属性
export interface ExtendedConfig extends Config {
// @ts-expect-error Use "@ts-expect-error" to ignore the next line error, and it will report an error itself if there's no error on the next line.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[key: string]: any
}
config代码
typescript
// src/config/index.ts
import { readFileSync } from "fs";
import { join } from "path";
import { parse } from "yaml";
import { ExtendedConfig } from "./config";
// 定义一个变量来缓存配置对象,初始值为 null
let configCache: ExtendedConfig | null = null;
// 定义 getEnv 函数,用于获取当前的运行环境,默认为 "dev"
export const getEnv = (): string => {
return process.env.RUNNING_ENV || "dev";
};
// 定义 getConfig 函数,用于读取并解析配置文件,然后返回配置对象
export const getConfig = (type?: keyof ExtendedConfig) => {
// 获取当前运行环境
const environment = getEnv();
// 如果配置已经被缓存,则直接返回缓存的配置
if (configCache) {
return type ? configCache[type] : configCache;
}
try {
// 根据运行环境构造配置文件的路径
const yamlPath = join(process.cwd(), `./src/config/.${environment}.yaml`);
// 读取配置文件内容
const file = readFileSync(yamlPath, "utf8");
// 解析 YAML 文件内容为 JavaScript 对象
const parsedConfig = parse(file) as ExtendedConfig;
// 将解析后的配置对象缓存起来
configCache = parsedConfig;
// 返回请求的配置部分,如果没有指定则返回整个配置对象
return type ? parsedConfig[type] : parsedConfig;
} catch (error) {
// 如果读取或解析配置文件失败,则打印错误信息并退出程序
console.error(`Failed to read or parse the config file: ${error}`);
process.exit(1); // 或者抛出异常,或者返回一个默认配置
}
};
这段代码主要用于从 YAML 配置文件中读取配置信息。它首先尝试从缓存中获取配置,如果缓存不存在,则读取和解析 YAML 文件,将解析后的配置对象缓存起来,并根据需要返回整个配置对象或其特定部分。此外,它还包含了获取当前运行环境的功能,允许根据不同的环境加载不同的配置文件。
写之前还考虑过是不是要远程注册中心或者是配置热更新之类的,现在想想就好了
PS:默认异常退出这里可能不是一个好的方案,后面有机会再升级一下
http服务初始化
前面项目结构的时候提到过两个文件,index.ts和webserver.ts。
index.ts
作为启动和引导整个应用的入口,而webserver.ts
则专注于具体的业务逻辑处理。这种分离的模式有助于保持代码的组织性和可维护性,同时也便于测试和重用代码。
webserver.ts
这个文件专注于与HTTP服务相关的所有配置和初始化。它的主要职责包括:
- 创建HTTP服务实例 :通过
fastify
框架创建一个新的HTTP服务器实例,并启用内置的日志记录功能。 - 定义路由 :在这个例子中,它定义了一个简单的根路由
"/"
,当访问这个路由时,返回{ hello: "world" }
。 - 读取配置:从配置文件中获取应用程序相关的配置,如端口和主机地址。
- 启动服务器:使用从配置文件中读取的端口和主机地址来启动HTTP服务器,并记录启动所需的时间。
- 错误处理:在启动过程中遇到错误时,记录错误信息并退出进程。
这个文件的主要目的是封装与HTTP服务启动相关的所有细节,使得这个过程从应用程序的其它部分中解耦。
index.ts
这个文件作为应用程序的主入口点,负责协调整个应用程序的初始化过程。它的职责包括:
- 初始化数据库连接:虽然在代码示例中没有具体实现,但通常这里会包含数据库连接的初始化代码。
- 初始化其他服务:除了数据库之外,如果应用程序中还有其他需要在启动时初始化的服务,它们也会在这里初始化。
- 启动HTTP服务器 :调用
webserver.ts
中定义的startServer
函数来启动HTTP服务。 - 错误处理:如果在初始化过程中遇到任何错误,记录错误信息并退出进程。
index.ts
文件的作用是作为应用程序启动的起点,它不仅仅限于启动HTTP服务器,还包括了应用程序所需的所有初始化步骤。
新增开发环境配置
yaml
# src/config/.dev.yaml
APP:
name: "base-im"
port: 3588 # 自己换一个也行,我3000被占了。以后会见到各种奇葩端口
host: "0.0.0.0"
ts
// 新的Config注解
// 定义 Config 接口,用于描述配置文件的结构
interface Config {
database: {
host: string
port: number
}
app: {
name: string
port: number
host: string
}
}
http服务(webserver.ts)
typescript
// src/webserver.ts
import fastify from "fastify";
import { getConfig } from "./config";
import logger from "./logger";
// 创建 Fastify 应用实例,启用内置的日志记录功能
const app = fastify({
logger: true,
});
// 定义一个异步函数来启动服务器
const startServer = async () => {
const startTime = Date.now(); // 记录开始启动服务器的时间
try {
// 定义根路由,当访问 '/' 时返回 { hello: 'world' }
app.get("/", async () => {
return { hello: "world" };
});
// 从配置中获取应用程序信息
const APP_INFO = getConfig("APP");
// 启动服务器,监听配置中指定的端口和主机
await app.listen({ port: APP_INFO.port, host: APP_INFO.host });
const endTime = Date.now(); // 记录服务器启动完成的时间
const startupTime = (endTime - startTime) / 1000; // 计算服务器启动耗时(秒)
// 记录启动信息到日志
logger.info(
`Starting ${APP_INFO.name} server on ${APP_INFO.host}:${APP_INFO.port}`
);
// 记录启动耗时到日志
logger.info(`Server started in ${startupTime} seconds.`);
} catch (err) {
// 如果启动过程中发生错误,则记录错误信息并退出进程
app.log.error(err);
process.exit(1);
}
};
export { startServer };
入口文件(index.ts)
typescript
// src/index.ts
import logger from './infrastructure/logger';
import { startServer } from './infrastructure/webserver';
async function initializeApplication() {
try {
// 初始化数据库连接
// 初始化其他服务
// 启动Web服务器
await startServer();
logger.info('Server started successfully.');
} catch (error) {
logger.error('Failed to start the application:', error);
process.exit(1);
}
}
// 执行应用程序初始化
initializeApplication();
启动,提示成功后访问http://localhost:3588/
bash
# 启动
npm run dev
> service@1.0.0 dev
> nodemon --watch src -e ts --exec ts-node --esm src/index.ts
[nodemon] 3.0.3
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): src/**/*
[nodemon] watching extensions: ts
[nodemon] starting `ts-node --esm src/index.ts`
{"level":30,"time":1706522637232,"pid":81804,"hostname":"ply***M","msg":"Server listening at http://0.0.0.0:3588"}
[18:03:57.233] INFO: Starting user-hub server on 0.0.0.0:3588
[18:03:57.233] INFO: Server started in 0.011 seconds.
[18:03:57.233] INFO: Server started successfully.
全局错误处理
全局错误处理在Web应用程序中非常重要,因为它帮助保持错误响应的一致性,减少代码重复,简化错误管理过程,提高应用的安全性,以及便于错误的调试和记录。在Fastify框架中,通过定义自定义错误类、创建和注册Fastify插件,可以实现高效且一致的全局错误处理机制。这不仅提升了开发效率,还能改善用户体验和应用的稳定性。
封装自定义错误类
这里的主要逻辑是,定义一个自定义错误类,然后封装好fastify插件函数,最后注册这个插件进行全局异常捕获。如果没有在领域中定义错误就会使用预先定义的错误类处理异常。
首先,需要安装 fastify-plugin
,这是一个官方提供的辅助库,用于创建可重用的插件模块。它的作用是简化了Fastify插件的开发流程,并确保插件可以正确地与Fastify的封装系统(encapsulation system)兼容。
bash
npm install fastify-plugin
ts
// src/errors/custom-error.ts
export class CustomError extends Error {
// 状态码,用于HTTP响应
statusCode: number;
// 内部错误编号,可用于错误跟踪
internalErrorNumber?: number;
/**
* 创建一个CustomError实例。
* @param {string} message - 错误信息。
* @param {number} statusCode - HTTP状态码。
* @param {number} [internalErrorNumber] - 可选的内部错误编号。
*/
constructor(message: string, statusCode: number, internalErrorNumber?: number) {
// 调用基类的构造函数
super(message);
// 设置HTTP状态码
this.statusCode = statusCode;
// 设置内部错误编号(如果提供)
this.internalErrorNumber = internalErrorNumber;
// 恢复原型链
Object.setPrototypeOf(this, new.target.prototype);
}
/**
* 将错误信息转换为JSON对象,用于HTTP响应。
* @returns {Object} 表示错误的JSON对象。
*/
toJSON() {
return {
message: this.message, // 错误信息
statusCode: this.statusCode, // HTTP状态码
// 如果存在内部错误编号,则添加到JSON对象中
...(this.internalErrorNumber && { internalErrorNumber: this.internalErrorNumber }),
};
}
}
使用自定义错误类封装Fastify插件(中间件)
typescript
// src/plugins/error-handler-plugin.ts
import fp from 'fastify-plugin';
import { FastifyInstance, FastifyError, FastifyReply, FastifyRequest } from 'fastify';
import logger from '../../logger';
import { CustomError } from '../../error/error-handler';
async function errorHandlerPlugin(fastify: FastifyInstance) {
fastify.setErrorHandler((error: FastifyError, request: FastifyRequest, reply: FastifyReply) => {
// 如果错误是我们定义的CustomError,我们使用定义的状态码和消息
if (error instanceof CustomError) {
logger.error(error.toJSON());
reply.status(error.statusCode).send(error.toJSON());
} else {
// 对于其他类型的错误,我们使用500状态码和通用消息
logger.error(error);
reply.status(500).send({ message: 'Something went wrong' });
}
});
}
// 使用 'fp' 创建插件,以支持封装和异步注册
export default fp(errorHandlerPlugin);
对于常见错误的封装
ts
// src/errors/index.ts
import { CustomError } from "./custom-error";
// 定义一个 BadRequestError 类,表示 HTTP 400 错误。通常用于表示客户端请求错误。
export class BadRequestError extends CustomError {
constructor(message = "Bad Request", internalErrorNumber?: number) {
super(message, 400, internalErrorNumber);
}
}
// 定义一个 UnauthorizedError 类,表示 HTTP 401 错误。用于需要用户认证的情况。
export class UnauthorizedError extends CustomError {
constructor(message = "Unauthorized", internalErrorNumber?: number) {
super(message, 401, internalErrorNumber);
}
}
// 定义一个 ForbiddenError 类,表示 HTTP 403 错误。用于表示服务器拒绝执行此请求。
export class ForbiddenError extends CustomError {
constructor(message = "Forbidden", internalErrorNumber?: number) {
super(message, 403, internalErrorNumber);
}
}
// 定义一个 NotFoundError 类,表示 HTTP 404 错误。用于请求的资源不存在。
export class NotFoundError extends CustomError {
constructor(message = "Not Found", internalErrorNumber?: number) {
super(message, 404, internalErrorNumber);
}
}
// 定义一个 InternalServerError 类,表示 HTTP 500 错误。用于服务器内部错误。
export class InternalServerError extends CustomError {
constructor(message = "Internal Server Error", internalErrorNumber?: number) {
super(message, 500, internalErrorNumber);
}
}
// 定义一个 BadGatewayError 类,表示 HTTP 502 错误。用于作为网关或代理工作的服务器收到无效响应。
export class BadGatewayError extends CustomError {
constructor(message = "Bad Gateway", internalErrorNumber?: number) {
super(message, 502, internalErrorNumber);
}
}
// 定义一个 ServiceUnavailableError 类,表示 HTTP 503 错误。用于服务器暂不可用。
export class ServiceUnavailableError extends CustomError {
constructor(message = "Service Unavailable", internalErrorNumber?: number) {
super(message, 503, internalErrorNumber);
}
}
// 定义一个 GatewayTimeoutError 类,表示 HTTP 504 错误。用于网关超时。
export class GatewayTimeoutError extends CustomError {
constructor(message = "Gateway Timeout", internalErrorNumber?: number) {
super(message, 504, internalErrorNumber);
}
}
// 定义一个 NotImplementedError 类,表示 HTTP 501 错误。用于服务器不支持请求的功能。
export class NotImplementedError extends CustomError {
constructor(message = "Not Implemented", internalErrorNumber?: number) {
super(message, 501, internalErrorNumber);
}
}
// 定义一个 TooManyRequestsError 类,表示 HTTP 429 错误。用于客户端发送的请求过多。
export class TooManyRequestsError extends CustomError {
constructor(message = "Too Many Requests", internalErrorNumber?: number) {
super(message, 429, internalErrorNumber);
}
}
更新webserver
日志和配置完成之后,就可以启动一个hello word程序来体验一下
http服务
typescript
import fastify from "fastify";
import { getConfig } from "./config";
import logger from "./logger";
import errorHandlerPlugin from "./plugins/error-handler-plugin"; // 新增引入
// 创建 Fastify 应用实例,启用内置的日志记录功能
const app = fastify({
logger: true,
});
// 定义一个异步函数来启动服务器
const startServer = async () => {
const startTime = Date.now(); // 记录开始启动服务器的时间
try {
// 定义根路由,当访问 '/' 时返回 { hello: 'world' }
app.get("/", async () => {
return { hello: "world" };
});
// 注册自定义的错误处理插件
await errorHandlerPlugin(app); // 新增注册插件
// 从配置中获取应用程序信息
const APP_INFO = getConfig("APP");
// 启动服务器,监听配置中指定的端口和主机
await app.listen({ port: APP_INFO.port, host: APP_INFO.host });
const endTime = Date.now(); // 记录服务器启动完成的时间
const startupTime = (endTime - startTime) / 1000; // 计算服务器启动耗时(秒)
// 记录启动信息到日志
logger.info(
`Starting ${APP_INFO.name} server on ${APP_INFO.host}:${APP_INFO.port}`
);
// 记录启动耗时到日志
logger.info(`Server started in ${startupTime} seconds.`);
} catch (err) {
// 如果启动过程中发生错误,则记录错误信息并退出进程
app.log.error(err);
process.exit(1);
}
};
export { startServer };
总结一下
虽然内容质量和数量都比较一般,但是目前这篇文章涵盖了项目的组织、代码风格、日志记录、配置管理、HTTP服务初始化和全局错误处理六大核心要素。后续章节应该是讨论websocket或者路由、参数验证、数据库了。
当前目录结构
csharp
project-root/
│
├── src/ # 项目的源代码
│ ├── config/ # 配置文件目录
│ │ ├── config.d.ts # 配置的TypeScript声明文件,用于类型安全
│ │ └── index.ts # 配置文件的实现,负责加载和导出配置
│ │
│ ├── errors/ # 错误处理相关的目录
│ │ ├── custom-error.ts # 自定义错误类,用于创建统一的错误响应
│ │ └── index.ts # 错误处理的入口文件,可能用于汇总和导出错误类
│ │
│ ├── plugins/ # Fastify插件目录
│ │ └── error-handler-plugin.ts # 错误处理插件,用于全局错误处理
│ │
│ ├── logger.ts # 日志配置文件,定义日志记录方式和配置
│ ├── webserver.ts # Fastify服务器设置和启动逻辑
│ └── index.ts # 应用入口文件,用于启动服务器和其他初始化设置
│
├── package.json # 定义项目依赖和脚本的npm配置文件
├── package-lock.json # 锁定安装时的包的版本,确保一致性
├── tsconfig.json # TypeScript的编译配置文件
项目结构详细说明:
-
config/: 包含应用的配置逻辑和类型声明。这里的配置可能包括数据库连接信息、应用密钥等,利用TypeScript以确保类型安全。
-
errors/ : 提供一个中心化的错误处理机制。
custom-error.ts
定义了一个或多个自定义错误类,而index.ts
可能用于导出所有错误类,以便在其他文件中使用。 -
plugins/ : 存放Fastify插件。
error-handler-plugin.ts
是一个专门用于错误处理的插件,它可能会捕获应用中的异步错误,并返回统一的错误响应。 -
logger.ts: 定义应用的日志记录策略。这可能包括配置不同的日志级别、日志格式以及日志的存储位置等。
-
webserver.ts: 包含Fastify服务器的配置和启动逻辑。这里设置了服务器的各种选项,如端口号、插件注册、路由以及启动服务器等。
-
index.ts : 作为应用的主入口点,负责初始化和启动整个应用。这里可能包括调用
webserver.ts
来启动服务器,以及其他全局初始化逻辑。