【深入浅出 Node + React 的微服务项目】7. 响应一致化策略

这篇博客详细介绍了如何在Express.js应用中实现错误处理,包括创建路由、引入验证、处理认证错误和统一错误信息。通过自定义错误类和错误处理中间件,确保了无论何种错误都能返回结构化的响应。同时,文章还展示了如何处理404错误和异步错误,以及如何通过子类化Error来增强错误信息。
摘要由CSDN通过智能技术生成

响应一致化策略

创建路由处理

// current-user.ts
import express from "express";
// 使用 express.Router
const router = express.Router();
router.get("/api/users/currentuser", () => {});

export { router as currentUserRouter };
// index.ts
import express from "express";
import { json } from "body-parser";
import { currentUserRouter } from "./routes/current-user";

const app = express();
app.use(json());
app.use(currentUserRouter);

app.listen(3000, () => {
  console.log("Listening on port 3000!!!!!!!!");
});

⬆ back to top

引入更多的路由

  • 这里注意的是,express.Router 负责包装路由
  • express 负责 通过 use 整合包装后的路由
// index.ts
import express from "express";
import { json } from "body-parser";

import { currentUserRouter } from "./routes/current-user";
import { signinRouter } from "./routes/signin";
import { signoutRouter } from "./routes/signout";
import { signupRouter } from "./routes/signup";

const app = express();
app.use(json());

app.use(currentUserRouter);
app.use(signinRouter);
app.use(signoutRouter);
app.use(signupRouter);

app.listen(3000, () => {
  console.log("Listening on port 3000!");
});

⬆ back to top

增加验证

  • 使用express-validator
  • 一句话概括:express-validator 主要提供 express 的验证(到目前只用到过 body)
  • 具体用法如下
    • 在 router.post / [xxx method] 中,route 和 callback 前,用数组 或者 「,」的分割来传入 body 的 isXXX 验证,最后.withMessage 返回 msg
// signup.ts
import express, { Request, Response } from "express";
import { body } from "express-validator";

const router = express.Router();

router.post(
  "/api/users/signup",
  [
    body("email").isEmail().withMessage("Email must be valid"),
    body("password")
      .trim()
      .isLength({ min: 4, max: 20 })
      .withMessage("Password must be between 4 and 20 characters"),
  ],
  (req: Request, res: Response) => {
    const { email, password } = req.body;

    if (!email || typeof email !== "string") {
      res.status(400).send("Provide a valid email");
    }

    // new User({ email, password })
  }
);

export { router as signupRouter };

⬆ back to top

认证错误的处理

  • 用 express-validator 的 validationResult 接收 Request 的错误信息
  • 通过 .array() 就会返回如下格式的错误信息
{
  "msg": "The error message",
  "param": "param.name.with.index[0]",
  "value": "param value",
  // Location of the param that generated this error.
  // It's either body, query, params, cookies or headers.
  "location": "body",

  // nestedErrors only exist when using the oneOf function
  "nestedErrors": [{ ... }]
}

import express, { Request, Response } from "express";
import { body, validationResult } from "express-validator";

const router = express.Router();

router.post(
  "/api/users/signup",
  [
    body("email").isEmail().withMessage("Email must be valid"),
    body("password")
      .trim()
      .isLength({ min: 4, max: 20 })
      .withMessage("Password must be between 4 and 20 characters"),
  ],
  (req: Request, res: Response) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).send(errors.array());
    }

    const { email, password } = req.body;
    console.log("Creating a user...");
    res.send({});
  }
);

export { router as signupRouter };

⬆ back to top

统一不同类型微服务的错误信息


在这里插入图片描述

⬆ back to top

其他的错误来源

在这里插入图片描述

⬆ back to top

Error 处理的解决方案

处理 Error 的困难解决方式
无论出现什么问题,我们都必须从所有服务器获得一致的结构化响应编写一个错误处理中间件来处理错误,给它们一个一致的结构,然后返回给浏览器
不仅仅是验证请求处理程序的输入。还有其他奇奇怪怪的错误要处理确保我们使用 Express 的错误处理机制捕获所有可能的错误(调用 ‘next’ 函数!)

expressjs 官网的 Error Handling

⬆ back to top

创建一个 Error 处理的 middleware

// error-handler.ts
import { Request, Response, NextFunction } from "express";

export const errorHandler = (
  err: Error,
  req: Request,
  res: Response,
  next: NextFunction
) => {
  console.log("Something went wrong", err);

  res.status(400).send({
    message: "Something went wrong",
  });
};

⬆ back to top

向错误处理程序传达更多信息

在这里插入图片描述

⬆ back to top

定义并处理更多 Error 信息

  • 我们想要一个像“ Error ”这样的对象,但我们想向它添加更多自定义属性
  • 通常这类信息需要进行子类化继承
  • Custom errors, extending Error

在这里插入图片描述
在这里插入图片描述

⬆ back to top

自定义错误 Custom Errors 的子类化及子类类型

// request-validation-error.ts
import { ValidationError } from "express-validator";

export class RequestValidationError extends Error {
  constructor(public errors: ValidationError[]) {
    super();

    // Only because we are extending a built in class
    Object.setPrototypeOf(this, RequestValidationError.prototype);
  }
}
// database-connection-error copy.ts
export class DatabaseConnectionError extends Error {
  reason = "Error connecting to database";

  constructor() {
    super();

    // Only because we are extending a built in class
    Object.setPrototypeOf(this, DatabaseConnectionError.prototype);
  }
}

⬆ back to top

确定错误类型 Error Type

  • instanceof判断继承关系,因为我们之前Object.setPrototypeOf(this, [CurrentTypeError].prototype)
  • 设置一个指定的对象的原型 ( 即, 内部[[Prototype]]属性)到另一个对象或 null。
if (!Object.setPrototypeOf) {
  // 仅适用于Chrome和FireFox,在IE中不工作:
  Object.prototype.setPrototypeOf = function (obj, proto) {
    if (obj.__proto__) {
      obj.__proto__ = proto;
      return obj;
    } else {
      // 如果你想返回 prototype of Object.create(null):
      var Fn = function () {
        for (var key in obj) {
          Object.defineProperty(this, key, {
            value: obj[key],
          });
        }
      };
      Fn.prototype = proto;
      return new Fn();
    }
  };
}
import { Request, Response, NextFunction } from "express";
import { RequestValidationError } from "../errors/request-validation-error";
import { DatabaseConnectionError } from "../errors/database-connection-error copy";

export const errorHandler = (
  err: Error,
  req: Request,
  res: Response,
  next: NextFunction
) => {
  if (err instanceof RequestValidationError) {
    console.log("handling this error as a request validation error");
  }

  if (err instanceof DatabaseConnectionError) {
    console.log("handling this error as a database connection error");
  }

  res.status(400).send({
    message: err.message,
  });
};

⬆ back to top

将 Error 转换为 Response

f

⬆ back to top

将序列化逻辑植入进 Errors middleware

在这里插入图片描述

// database-connection-error.ts
export class DatabaseConnectionError extends Error {
  statusCode = 500;
  reason = "Error connecting to database";

  constructor() {
    super();

    // Only because we are extending a built in class
    Object.setPrototypeOf(this, DatabaseConnectionError.prototype);
  }

  serializeErrors() {
    return [{ message: this.reason }];
  }
}
// request-validation-error.ts
import { ValidationError } from "express-validator";

export class RequestValidationError extends Error {
  statusCode = 400;

  constructor(public errors: ValidationError[]) {
    super();

    // Only because we are extending a built in class
    Object.setPrototypeOf(this, RequestValidationError.prototype);
  }

  serializeErrors() {
    return this.errors.map((error) => {
      return { message: error.msg, field: error.param };
    });
  }
}
// error-handler.ts
import { Request, Response, NextFunction } from "express";
import { RequestValidationError } from "../errors/request-validation-error";
import { DatabaseConnectionError } from "../errors/database-connection-error copy";

export const errorHandler = (
  err: Error,
  req: Request,
  res: Response,
  next: NextFunction
) => {
  if (err instanceof RequestValidationError) {
    return res.status(err.statusCode).send({ errors: err.serializeErrors() });
  }

  if (err instanceof DatabaseConnectionError) {
    return res.status(err.statusCode).send({ errors: err.serializeErrors() });
  }

  res.status(400).send({
    errors: [{ message: "Something went wrong" }],
  });
};

⬆ back to top

验证自定义的 Error

在这里插入图片描述
在这里插入图片描述

  • 于是就有了 CommonError 对于继承的子类的 abstract 约束
    在这里插入图片描述
import { ValidationError } from "express-validator";

interface CustomError {
  statusCode: number;
  serializeErrors(): {
    message: string;
    field?: string;
  }[]; // 对象数组的声明
}

export class RequestValidationError extends Error implements CustomError {
  statusCode = 400;

  constructor(public errors: ValidationError[]) {
    super();

    // Only because we are extending a built in class
    Object.setPrototypeOf(this, RequestValidationError.prototype);
  }

  serializeErrors() {
    return this.errors.map((error) => {
      return { message: error.msg, field: error.param };
    });
  }
}

在这里插入图片描述

⬆ back to top

最终 Error 相关的 Code

custom-error.ts
  • 抽象类无法实例化
  • 主要对子类进行继承属性的约束(序列归一化的体现)
// custom-error.ts
export abstract class CustomError extends Error {
  abstract statusCode: number;

  constructor(message: string) {
    super(message);

    Object.setPrototypeOf(this, CustomError.prototype);
  }

  abstract serializeErrors(): { message: string; field?: string }[];
}
database-connection-error.ts
  • 报 500 的 DatabaseConnectionError
  • 无 field 验证错误字段返回
// database-connection-error.ts
import { CustomError } from "./custom-error";

export class DatabaseConnectionError extends CustomError {
  statusCode = 500;
  reason = "Error connecting to database";

  constructor() {
    super("Error connecting to db");

    // Only because we are extending a built in class
    Object.setPrototypeOf(this, DatabaseConnectionError.prototype);
  }

  serializeErrors() {
    return [{ message: this.reason }];
  }
}
request-validation-error.ts
  • 报 400 的 RequestValidationError
  • 有错误字段返回
// request-validation-error.ts
import { ValidationError } from "express-validator";
import { CustomError } from "./custom-error";

export class RequestValidationError extends CustomError {
  statusCode = 400;

  constructor(public errors: ValidationError[]) {
    super("Invalid request parameters");

    // Only because we are extending a built in class
    Object.setPrototypeOf(this, RequestValidationError.prototype);
  }

  serializeErrors() {
    return this.errors.map((error) => {
      return { message: error.msg, field: error.param };
    });
  }
}
error-handler.ts
// error-handler.ts
import { Request, Response, NextFunction } from "express";
import { CustomError } from "../errors/custom-error";

export const errorHandler = (
  err: Error,
  req: Request,
  res: Response,
  next: NextFunction
) => {
  if (err instanceof CustomError) {
    return res.status(err.statusCode).send({ errors: err.serializeErrors() });
  }

  res.status(400).send({
    errors: [{ message: "Something went wrong" }],
  });
};

⬆ back to top
在这里插入图片描述

创建 404 的路由错误

import { CustomError } from "./custom-error";

export class NotFoundError extends CustomError {
  statusCode = 404;

  constructor() {
    super("Route not found");

    Object.setPrototypeOf(this, NotFoundError.prototype);
  }

  serializeErrors() {
    return [{ message: "Not Found" }];
  }
}
import express from "express";
import { json } from "body-parser";

import { currentUserRouter } from "./routes/current-user";
import { signinRouter } from "./routes/signin";
import { signoutRouter } from "./routes/signout";
import { signupRouter } from "./routes/signup";
import { errorHandler } from "./middleware/error-handler";
import { NotFoundError } from "./errors/not-found-error";

const app = express();
app.use(json());

app.use(currentUserRouter);
app.use(signinRouter);
app.use(signoutRouter);
app.use(signupRouter);

app.all("*", () => {
  throw new NotFoundError();
});

app.use(errorHandler);

app.listen(3000, () => {
  console.log("Listening on port 3000!");
});

⬆ back to top

异步抛出错误

  • express.all 使用案例
var express = require("express");
var app = express();

app.all("*", function(request, response, next) {
    response.writeHead(200, { "Content-Type": "text/html;charset=utf-8" });      //设置响应头属性值
    next();
});

app.get("/", function(request, response) {
    response.end("欢迎来到首页!");
});

app.get("/about", function(request, response) {
    response.end("欢迎来到about页面!");
});

app.get("*", function(request, response) {
    response.end("404 - 未找到!");
});

app.listen(80);
app.all("*", async (req, res, next) => {
  next(new NotFoundError());
});
app.all("*", async (req, res) => {
  throw new NotFoundError();
});

⬆ back to top

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值