Fastify+TS实现基础IM服务(三)搭建Fastify+TS基础项目

这篇文章涵盖了项目的组织、代码风格、日志记录、配置管理、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.jsonscripts部分添加一个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日志库,它提供了多种日志级别(如infoerrordebug等),以及可配置的日志输出格式。Fastify选择Pino作为内置日志库的原因之一是其性能优异,对应用程序的性能影响较小。

使用Fastify时,可以在创建Fastify实例时配置日志选项,例如:

javascript 复制代码
const fastify = require('fastify')({
  logger: true
})

这将启用内置的Pino日志记录,也可以通过传递更详细的配置来自定义日志行为,如指定日志级别、自定义序列化函数等。

为什么不使用console作为日志

在Node.js应用程序中,虽然使用console.log()console.error()等方法进行日志记录是最简单直接的方式,但这种做法在生产环境中通常不推荐,原因包括:

  1. 性能问题console.log()会导致同步写入操作,尤其是在高负载情况下,可能会显著影响应用程序的性能。
  2. 日志管理不便 :使用console进行日志记录,缺乏灵活的日志级别控制,且不易于集中管理和查询。
  3. 格式化和序列化console方法输出的日志难以自定义格式,不利于将日志输出到文件或远程日志系统,并且在处理复杂对象时可能不如专业日志库灵活。

其他Node.js日志库

除了Pino之外,Node.js生态系统中还有许多其他优秀的日志库,可以根据项目需求选择使用,例如:

  • Winston:一个功能丰富的日志库,支持多种传输方式(如文件、控制台和远程服务),并允许自定义日志格式。
  • Bunyan:另一个流行的JSON日志库,提供了简单的API和丰富的日志级别,易于集成到各种系统中。

为什么还要单独创建一个日志实例

已经内置了pino日志了,为什么还要自定义一个pino日志呢?除了方便移植之外还为了不破坏现在fastify内置的日志。总结下来说,就是以下几点:

  1. 日志分离:自定义日志实例允许将应用日志与框架日志分开,便于管理和分析。
  2. 细致控制:提供更多自定义选项,如日志级别、格式化和旋转策略,满足特定需求。
  3. 第三方集成:方便与第三方日志服务集成,如Loggly或Datadog。
  4. 模块化重用:在多模块应用或微服务中,方便日志配置和行为的重用。
  5. 场景灵活性 :适用于不仅限于HTTP请求处理的日志记录场景,如后台任务、心跳

首先,需要安装 pinopino-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 日志实例,它配置了以下几点:

  1. transport: 使用 pino-pretty 来美化日志输出,使其在开发过程中更易于阅读。
  2. base: 默认情况下,Pino 会记录进程 ID (pid) 和主机名 (hostname)。在这里,我们设置 pid: false 来禁用进程 ID 的记录。
  3. 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

主要逻辑:

  1. 定义了 ConfigExtendedConfig 接口,其中 ExtendedConfig 允许包含任意额外的属性。
  2. 使用 getEnv 函数来确定当前的运行环境。
  3. getConfig 函数用于加载和解析指定环境对应的配置文件。如果已经加载过配置,它会从缓存中返回配置,否则会从文件系统中读取配置文件,解析它,并将其存储在缓存中以备后续使用。
  4. 如果在读取或解析配置文件过程中发生错误,程序将输出错误信息并终止执行。

类型注解和代码

类型注解

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 来启动服务器,以及其他全局初始化逻辑。

相关推荐
LUCIAZZZ2 小时前
Https解决了Http的哪些问题
java·网络·网络协议·spring·http·rpc·https
一天八小时6 小时前
计算机网络————(一)HTTP讲解
网络协议·计算机网络·http
哑巴语天雨18 小时前
前端面试-网络协议篇
websocket·网络协议·http·面试·https
垣宇19 小时前
Vite 和 Webpack 的区别和选择
前端·webpack·node.js
爱吃南瓜的北瓜19 小时前
npm install 卡在“sill idealTree buildDeps“
前端·npm·node.js
翻滚吧键盘19 小时前
npm使用了代理,但是代理软件已经关闭导致创建失败
前端·npm·node.js
浪九天20 小时前
node.js的版本管理
node.js
小梁不秃捏21 小时前
HTTP 常见状态码技术解析(应用层)
网络·网络协议·计算机网络·http
浪九天1 天前
node.js的常用指令
node.js
卑微的小鬼1 天前
rpc和http的区别,为啥golang使用grpc 不使用http?
http·rpc·golang