当前位置: 首页 > news >正文

WEB3全栈开发——面试专业技能点P6后端框架 / 微服务设计

一、Express

Express是国内大部分公司重点问的。我在本文最后,单独讲解了Express框架。

概念介绍

Express 是基于 Node.js 平台的极简、灵活且广泛使用的 Web 应用框架。它提供了一系列强大的功能,用于构建单页、多页及混合型的 Web 应用程序和 API 服务。

Express 的核心特点包括:

  • 简洁易用的路由系统

  • 中间件机制,方便请求处理和功能扩展

  • 灵活的模板引擎支持

  • 支持多种 HTTP 请求方法和路径匹配

  • 兼容性强,易于与各种数据库和前端框架集成

Express 通常被用作后端 Web 服务的骨架,尤其在构建 RESTful API 和微服务架构时非常流行。

示例代码

下面是一个简单的 Express 服务器示例,实现一个基础的 GET 请求接口:

const express = require('express');
const app = express();
const port = 3000;// 定义路由,处理 GET 请求
app.get('/', (req, res) => {res.send('Hello, Express!');
});// 启动服务器监听端口
app.listen(port, () => {console.log(`Express server listening at http://localhost:${port}`);
});

讲解总结

  • 路由管理:Express 通过 app.get, app.post 等方法定义 URL 路径对应的请求处理函数,支持参数、查询字符串等。

  • 中间件机制:Express 支持中间件函数,可以拦截请求,实现功能如身份验证、日志记录、请求体解析等。

  • 简洁高效:相比原生 Node.js HTTP 模块,Express 大幅简化了代码量和复杂度,提升开发效率。

  • 灵活扩展:拥有庞大的生态系统,可集成多种第三方中间件与插件,满足各种业务需求。

  • 广泛应用:常用于构建 RESTful API、前后端分离架构的后端服务,及微服务组件。

Express 是 Node.js Web 开发的基础框架,掌握它对后端开发非常关键。

二、Koa

概念介绍

Koa 是由 Express 原班人马开发的下一代 Node.js Web 框架,设计目标是打造一个更小、更富表现力、更健壮的基础框架。它利用现代 JavaScript 的 async/await 特性,简化异步流程控制,摒弃了传统中间件的回调嵌套问题,提升代码的可读性和维护性。

Koa 本身非常轻量,不内置中间件,开发者可以根据需要自由组合,具有极高的灵活性。


示例代码

下面是一个使用 Koa 创建的简单服务器,响应 GET 请求:

const Koa = require('koa');
const app = new Koa();
const port = 3000;// 定义中间件,处理请求
app.use(async (ctx) => {if (ctx.path === '/') {ctx.body = 'Hello, Koa!';} else {ctx.status = 404;ctx.body = 'Not Found';}
});// 启动服务器监听端口
app.listen(port, () => {console.log(`Koa server running at http://localhost:${port}`);
});

讲解总结

  • 现代异步处理:Koa 使用 async/await 处理异步代码,避免回调地狱,使代码更简洁易懂。

  • 洋葱模型中间件:中间件执行遵循洋葱模型(洋葱圈层),支持在请求进入和响应返回时进行处理,便于实现日志、错误捕获、响应压缩等功能。

  • 极简核心:Koa 只提供核心功能,不包含路由、中间件等,开发者可根据业务需求灵活引入,打造定制化架构。

  • 更好错误处理:通过 async 函数的错误捕获机制,Koa 能优雅地处理异步错误,提升程序稳定性。

  • 适合微服务:Koa 的灵活性和简洁性非常适合用来构建轻量级的微服务或 API 服务。

Koa 是 Node.js 生态中注重现代语法与灵活设计的 Web 框架,适合对代码质量和扩展性有较高要求的项目。

三、NestJS 的模块化架构

概念介绍

NestJS 是一个基于 TypeScript 构建的进阶 Node.js 框架,借鉴了 Angular 的设计理念,采用模块化架构来组织应用。模块(Module)是 NestJS 应用的基本组成单元,每个模块封装了一组相关的功能,包括控制器(Controllers)、服务(Providers)、导入的其他模块等。

模块化架构有助于分离关注点,提升代码的复用性和可维护性,使大型应用易于管理和扩展。


示例代码

下面是一个简单的模块定义示例,展示如何创建和使用模块:

import { Module, Injectable, Controller, Get } from '@nestjs/common';// 服务层,提供业务逻辑
@Injectable()
export class HelloService {getHello(): string {return 'Hello, NestJS Module!';}
}// 控制器层,处理请求
@Controller()
export class HelloController {constructor(private readonly helloService: HelloService) {}@Get()getHello(): string {return this.helloService.getHello();}
}// 定义模块,组织控制器和服务
@Module({imports: [],            // 导入其他模块controllers: [HelloController],providers: [HelloService],exports: [HelloService], // 可导出给其他模块使用
})
export class HelloModule {}

讲解总结

  • 模块(Module) 是 NestJS 应用的组织单位,使用 @Module 装饰器定义,包含控制器、服务和导入的模块。

  • 控制器(Controller) 负责处理客户端请求,定义路由和请求方法。

  • 服务(Provider) 封装业务逻辑,支持依赖注入(DI),解耦业务与控制层。

  • 模块之间通过导入(imports)和导出(exports)实现功能复用和共享,方便拆分大型应用为多个独立子模块。

  • 模块化架构提高应用可维护性和扩展性,便于团队协作和功能拆分。

  • NestJS 的模块设计结合了依赖注入和面向对象编程思想,令开发体验更现代化且高效。

掌握 NestJS 的模块化架构是构建清晰、结构良好的企业级应用的基础。

四、NestJS的依赖注入

概念介绍

依赖注入(Dependency Injection,简称 DI)是一种设计模式,通过将对象的依赖(例如服务)由框架自动提供,而不是由对象自行创建,从而实现代码解耦和模块间松耦合。

NestJS 内置强大的依赖注入容器,自动管理服务实例的创建和生命周期,使组件之间的依赖关系清晰且易于维护。通过构造函数注入(constructor injection)是 NestJS DI 的核心方式。


示例代码

下面示例展示如何在 NestJS 中通过依赖注入使用服务:

import { Injectable, Controller, Get } from '@nestjs/common';// 定义服务,提供业务逻辑
@Injectable()
export class UserService {getUser() {return { id: 1, name: 'Alice' };}
}// 定义控制器,依赖注入 UserService
@Controller('users')
export class UserController {constructor(private readonly userService: UserService) {}@Get()getUser() {return this.userService.getUser();}
}

讲解总结

  • @Injectable() 装饰器标记服务类,使其可以被 NestJS 容器管理和注入。

  • 构造函数注入:依赖通过控制器或其他服务的构造函数参数声明,NestJS 自动实例化并传入对应依赖。

  • 依赖注入容器会根据作用域(默认单例)管理服务实例,避免重复创建,提高性能。

  • 依赖注入解耦了类与其依赖,实现高内聚低耦合,有利于单元测试和代码维护。

  • NestJS 还支持自定义作用域(如请求作用域)和手动注入(通过 @Inject() 装饰器),增强灵活性。

依赖注入是 NestJS 核心设计之一,掌握它可以大幅提升项目结构的清晰度和扩展性。

五、NestJS的守卫

概念介绍

守卫(Guard)是 NestJS 中用于控制请求权限的机制,类似于中间件,但专注于授权和权限检查。守卫可以决定请求是否可以继续执行路由处理逻辑,通常用于身份验证、角色权限校验等场景。

守卫实现 CanActivate 接口,返回 true 允许请求继续,返回 false 或抛出异常则拒绝请求。


示例代码

下面示例展示一个简单的守卫,用于检查请求头中是否包含特定令牌:

import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';@Injectable()
export class AuthGuard implements CanActivate {canActivate(context: ExecutionContext,): boolean | Promise<boolean> | Observable<boolean> {const request = context.switchToHttp().getRequest();const token = request.headers['authorization'];// 简单校验:请求头必须有指定的 tokenreturn token === 'my-secret-token';}
}

在控制器中使用守卫:

import { Controller, Get, UseGuards } from '@nestjs/common';@Controller('profile')
@UseGuards(AuthGuard)  // 作用于整个控制器
export class ProfileController {@Get()getProfile() {return { name: 'Alice', role: 'admin' };}
}

讲解总结

  • 守卫通过实现 CanActivate 接口控制请求是否被处理,适合做权限、认证逻辑。

  • 通过 ExecutionContext 获取请求信息(如请求头、用户信息等)。

  • 守卫返回 true 允许请求继续,返回 false 或抛异常拒绝请求。

  • 守卫可以作用于控制器类或单个路由方法,支持灵活配置。

  • NestJS 结合守卫和中间件、拦截器等机制,实现强大的请求生命周期管理。

掌握守卫可帮助构建安全、可控的后端服务,确保敏感接口仅授权用户访问。

六、NestJS 的拦截器

概念介绍

拦截器(Interceptor)是 NestJS 中一种强大的功能,用于拦截和处理函数调用前后逻辑。拦截器可以用于:

  • 修改方法输入参数或返回结果

  • 实现日志记录、缓存、异常处理、性能监控

  • 对请求进行额外处理或响应包装

拦截器实现 NestInterceptor 接口,核心方法 intercept() 接收 ExecutionContextCallHandler,通过 RxJS 操作符处理请求流。


示例代码

下面示例是一个简单的日志拦截器,打印请求开始和结束时间:

import {Injectable,NestInterceptor,ExecutionContext,CallHandler,
} from '@nestjs/common';
import { Observable, tap } from 'rxjs';@Injectable()
export class LoggingInterceptor implements NestInterceptor {intercept(context: ExecutionContext, next: CallHandler): Observable<any> {console.log('Before handling request...');const now = Date.now();return next.handle().pipe(tap(() => console.log(`After handling request... ${Date.now() - now}ms`)),);}
}

使用拦截器:

import { Controller, Get, UseInterceptors } from '@nestjs/common';@Controller('items')
@UseInterceptors(LoggingInterceptor)
export class ItemsController {@Get()findAll() {return ['item1', 'item2'];}
}

讲解总结

  • 拦截器在请求处理前后执行,能够操作请求和响应数据流。

  • 通过 RxJS 操作符(如 tapmap)可以异步处理响应。

  • 拦截器广泛应用于日志、缓存、异常转换、数据格式化等场景。

  • 可以作用于全局、控制器、单个路由,支持灵活配置。

  • 结合守卫和管道,构成 NestJS 完整请求处理链。

掌握拦截器能够极大增强应用的功能扩展性和代码复用性。

七、Express 的模块化架构

概念介绍

Express 默认是一个轻量级的 Node.js Web 框架,支持快速搭建服务器和路由。模块化架构指的是将应用拆分为多个功能模块,每个模块独立管理路由、控制器和中间件,便于代码维护、复用和团队协作。

核心思想:

  • 路由拆分:每个模块有自己独立路由文件,负责特定业务路由。

  • 控制器分离:处理业务逻辑的函数单独放置,保持路由简洁。

  • 中间件复用:公共功能用中间件抽象,跨模块复用。

  • 按功能组织代码:目录结构清晰,易于扩展。

模块化架构让大型项目更易维护,同时也方便测试和协作。


示例代码

假设一个简单的用户模块和商品模块,拆分路由和控制器。

目录结构示例
/app/controllersuserController.jsproductController.js/routesuserRoutes.jsproductRoutes.jsapp.js                   // app.js 中挂载路由前缀(主入口)

userController.js
// 处理用户相关业务逻辑
// controllers/userController.js
exports.getUser = (req, res) => {const userId = req.params.id;// 模拟获取用户信息res.json({ id: userId, name: 'Alice' });
};
productController.js
// 处理商品相关业务逻辑
exports.getProduct = (req, res) => {const productId = req.params.id;// 模拟获取商品信息res.json({ id: productId, name: 'Phone', price: 599 });
};
userRoutes.js(在app.js中绑定了路径前缀 /users
const express = require('express');
const router = express.Router();
const userController = require('../controllers/userController');
// 实际请求 URL: GET /users/:id
router.get('/:id', userController.getUser);module.exports = router;
productRoutes.js(在app.js中绑定了路径前缀 /products
const express = require('express');
const router = express.Router();
const productController = require('../controllers/productController');// 实际请求 URL: GET /products/:id
router.get('/:id', productController.getProduct);module.exports = router;
app.js中挂载路由(主入口)
const express = require('express');
const app = express();const userRoutes = require('./routes/userRoutes');
const productRoutes = require('./routes/productRoutes');// 路由前缀绑定
app.use('/users', userRoutes);    // 所有 user 路由以 /users 开头
app.use('/products', productRoutes);    // 所有 product 路由以 /products 开头app.listen(3000, () => {console.log('Server running on port 3000');
});

实际完整 URL 路径

结合以上配置:

功能请求方法完整 URL 示例控制器函数
获取用户GEThttp://localhost:3000/users/123getUser()
获取商品GEThttp://localhost:3000/products/456getProduct()

如果你还想加 POST、PUT、DELETE 这类方法,也可以在路由里扩展,例如:

router.post('/', userController.createUser);  // POST /users

讲解总结

  • 职责分明:路由只负责请求分发,业务逻辑放在控制器,代码层次清晰。

  • 易于维护:模块化结构使代码易读,方便多人协作和后续功能扩展。

  • 复用性强:公共中间件可跨模块使用,提高代码复用率。

  • 便于测试:模块化让单元测试和集成测试更加简便。

Express 模块化架构适合中大型项目,是构建可维护、扩展性好的 Node.js 应用的推荐方式。

八、Express 的依赖注入

✅ 概念介绍:Express 的依赖注入

Express 本身并不内建依赖注入机制,它是一个极简主义框架。与 NestJS 不同,NestJS 是基于 Angular 风格的完整依赖注入系统构建的。但在 Express 中,你可以使用一些第三方库(如 awilix、inversify 等)手动实现依赖注入,来提升项目的模块化与可测试性。

依赖注入(DI)的目标是:将对象之间的依赖关系“注入”而非硬编码,让代码更解耦、更好测试、更易维护。


✅ 示例代码(使用 awilix 实现 Express 的依赖注入)

1. 安装依赖

npm install awilix awilix-express

2. 项目结构

app/
├── app.js
├── routes/
│   └── userRoutes.js
├── controllers/
│   └── userController.js
├── services/
│   └── userService.js
└── container.js

3. 创建服务层(业务逻辑)

// services/userService.js
class UserService {getUser(id) {return { id, name: 'Alice (DI)' };}
}module.exports = UserService;

4. 创建控制器(接收依赖)

// controllers/userController.js
class UserController {constructor({ userService }) {this.userService = userService;}getUser = (req, res) => {const user = this.userService.getUser(req.params.id);res.json(user);};
}module.exports = UserController;

5. 设置 Awilix 容器

// container.js
const { createContainer, asClass } = require('awilix');
const UserService = require('./services/userService');
const UserController = require('./controllers/userController');const container = createContainer();container.register({userService: asClass(UserService).scoped(),userController: asClass(UserController).scoped(),
});module.exports = container;

6. 路由绑定(通过 awilix-express)

// routes/userRoutes.js
const express = require('express');
const router = express.Router();
const { makeInvoker } = require('awilix-express');// 控制器加载器
const container = require('../container');
const userController = makeInvoker(container.resolve('userController'));router.get('/:id', userController('getUser'));module.exports = router;

7. 应用入口(挂载路由)

// app.js
const express = require('express');
const { scopePerRequest } = require('awilix-express');
const container = require('./container');
const userRoutes = require('./routes/userRoutes');const app = express();
app.use(scopePerRequest(container)); // 关键 DI 挂载点
app.use('/users', userRoutes);app.listen(3000, () => {console.log('Server running on http://localhost:3000');
});

✅ 总结

项目结构描述
userService.js提供独立服务逻辑,可复用
userController.js通过构造函数自动注入依赖
container.js中央依赖注入容器
awilix-express把 DI 自动接入 Express 生命周期中


是否需要我补充 inversify 版本、或者如何结合 JWT数据库服务 等依赖的注入结构?

九、Express的守卫

✅ 概念介绍:Express 的守卫(Guard)

在 NestJS 中,“守卫”(Guard)是用来控制某个请求是否有权限访问的类。但在 Express 中没有“守卫”这一专有概念,不过你可以用 中间件(Middleware) 实现类似“守卫”的功能。

Express 中的“守卫”常用于:

  • 登录校验(是否带有 Token)

  • 权限校验(是否管理员)

  • 请求频率限制、接口黑白名单控制等


✅ 示例代码:实现 Express 中的“守卫”功能

🎯 目标:实现一个 JWT 鉴权“守卫”

我们将创建一个中间件,验证请求是否携带合法的 JWT Token。


1. 安装依赖

npm install jsonwebtoken

2. 编写守卫中间件(authGuard.js

// middlewares/authGuard.js
const jwt = require('jsonwebtoken');
const SECRET = 'your_secret_key'; // 应放在 .env 环境变量中const authGuard = (req, res, next) => {const authHeader = req.headers['authorization'];if (!authHeader || !authHeader.startsWith('Bearer ')) {return res.status(401).json({ message: '未提供有效 Token' });}const token = authHeader.split(' ')[1];try {const payload = jwt.verify(token, SECRET);req.user = payload; // 可用于后续控制器中next(); // 放行} catch (err) {return res.status(403).json({ message: 'Token 无效或已过期' });}
};module.exports = authGuard;

3. 使用守卫中间件保护路由

// routes/userRoutes.js
const express = require('express');
const router = express.Router();
const authGuard = require('../middlewares/authGuard');router.get('/profile', authGuard, (req, res) => {// 只有验证通过的用户才能访问res.json({ message: `欢迎你,${req.user.username}` });
});module.exports = router;

4. 登录接口生成 Token 示例

// routes/auth.js
const express = require('express');
const jwt = require('jsonwebtoken');
const router = express.Router();router.post('/login', (req, res) => {const { username, password } = req.body;// 真实项目应校验数据库if (username === 'admin' && password === '123456') {const token = jwt.sign({ username, role: 'admin' }, 'your_secret_key', { expiresIn: '2h' });return res.json({ token });}res.status(401).json({ message: '账号或密码错误' });
});module.exports = router;

✅ 请求示例

1. 登录获取 Token

POST /login
Content-Type: application/json{"username": "admin","password": "123456"
}

响应:

{"token": "eyJhbGciOi..."
}

2. 访问受保护资源

GET /users/profile
Authorization: Bearer eyJhbGciOi...

✅ 总结

名称实现方式
守卫(Nest)使用 @Injectable()
守卫(Express)使用中间件函数(req, res, next
应用场景JWT、权限控制、接口限流、黑白名单等


如果你想实现角色权限守卫、API 接口签名验证等高级“守卫”,我也可以继续帮你写完整示例。是否需要?

十、Express 拦截器

✅ 概念介绍:Express 的拦截器(Interceptor)

在 NestJS 中,“拦截器”是一个强大的功能,用于扩展请求/响应行为(如统一响应格式、日志记录、异常包装等)。
Express 虽没有原生“拦截器”这个名词,但我们可以通过 中间件(Middleware) 实现“拦截器”功能。

✅ 一句话理解:在 Express 中,“拦截器”是一个特定用途的中间件,用来在请求进入控制器之前/之后进行逻辑处理。


✅ 常见用途:

  • 请求/响应日志记录

  • 请求耗时分析

  • 接口统一响应格式处理

  • 异常捕获与封装

  • 跨域处理


✅ 示例代码

🎯 示例:编写一个记录请求时间和统一响应格式的拦截器中间件


1. 日志与响应包装拦截器 interceptor.js

// middlewares/interceptor.js
module.exports = (req, res, next) => {const startTime = Date.now();// 重写 res.json 方法,实现统一结构const originalJson = res.json.bind(res);res.json = (data) => {const duration = Date.now() - startTime;return originalJson({code: 0,message: 'success',data,duration: `${duration}ms`});};next();
};

2. 应用拦截器中间件到 Express 应用

// app.js
const express = require('express');
const app = express();
const interceptor = require('./middlewares/interceptor');app.use(express.json());
app.use(interceptor); // 全局拦截器app.get('/api/hello', (req, res) => {res.json({ text: 'Hello World!' });
});app.listen(3000, () => {console.log('Server running on http://localhost:3000');
});

🧪 请求示例

GET /api/hello

💡 响应结果(统一格式):

{"code": 0,"message": "success","data": {"text": "Hello World!"},"duration": "2ms"
}

✅ 扩展用法:仅拦截特定路由

app.use('/api/secure', interceptor);

✅ 总结

功能Express 实现方式
拦截器(请求 & 响应)中间件函数包裹 res.jsonres.send
执行顺序注册顺序决定调用链,越早注册越早执行
特点可用作全局或局部中间件


如需实现 异常处理拦截器权限校验拦截器链上接口统一响应结构 等,我也可以提供对应示例。需要的话告诉我即可。

十一、Express 的 JWT 设计链上链下鉴权系统

概念介绍

在 Web3 应用中,链上身份验证通常依赖区块链钱包签名消息(如 MetaMask 签名),而链下服务(如后端 API)使用 JWT(JSON Web Token)维护会话状态,实现权限控制。链上链下鉴权系统结合了这两者:

  • 用户通过钱包签名证明身份(链上认证)

  • 服务器验证签名后签发 JWT,用于后续链下请求鉴权

  • JWT 包含用户地址等信息,携带在请求头,服务器验证后允许访问受保护资源

这种设计避免每次请求都要求钱包签名,提高用户体验,同时保持安全性。


示例代码

以下示例用 Express 和 jsonwebtoken 实现简易链上链下鉴权流程:

const express = require('express');
const jwt = require('jsonwebtoken');
const { ethers } = require('ethers');const app = express();
app.use(express.json());const JWT_SECRET = 'your_jwt_secret';// 生成随机消息供客户端签名
app.get('/auth/message/:address', (req, res) => {const { address } = req.params;const message = `Login to MyDApp at ${Date.now()}`;// 这里应缓存 message 与 address 对应,用于验证res.json({ message });
});// 验证签名并签发 JWT
app.post('/auth/verify', (req, res) => {const { address, signature, message } = req.body;try {// 使用 ethers 验证签名者地址const signerAddress = ethers.utils.verifyMessage(message, signature);if (signerAddress.toLowerCase() !== address.toLowerCase()) {return res.status(401).json({ error: 'Invalid signature' });}// 签名合法,签发 JWTconst token = jwt.sign({ address }, JWT_SECRET, { expiresIn: '1h' });res.json({ token });} catch (error) {res.status(400).json({ error: 'Verification failed' });}
});// 受保护接口,验证 JWT
function authenticateToken(req, res, next) {const authHeader = req.headers['authorization'];const token = authHeader && authHeader.split(' ')[1];if (!token) return res.sendStatus(401);jwt.verify(token, JWT_SECRET, (err, user) => {if (err) return res.sendStatus(403);req.user = user; // 保存解码后的用户信息next();});
}app.get('/protected', authenticateToken, (req, res) => {res.json({ message: `Hello ${req.user.address}, this is protected data.` });
});app.listen(3000, () => {console.log('Server started on port 3000');
});

讲解总结

  • 链上认证:用户通过钱包签名服务器发送的随机消息,证明对该地址的控制权。

  • 链下鉴权:服务器验证签名后,使用 JWT 生成包含用户地址的令牌,客户端持有此令牌访问受保护接口。

  • JWT 验证:服务器中间件检查请求中的 JWT,保证请求合法且未过期。

  • 优势:减少频繁签名操作,提升用户体验;同时保证安全与身份唯一性。

这种模式是典型的 Web3 应用鉴权方案,兼顾区块链身份验证与传统后端权限控制。

十二、Express 的钱包签名(MetaMask)设计链上链下鉴权系统

概念介绍

在 Web3 应用中,用户使用钱包(如 MetaMask)进行链上身份认证。通过钱包签名服务器随机生成的消息(challenge),证明其对某个以太坊地址的控制权。服务器验证签名后,生成链下的 JWT 令牌,用户凭此令牌访问后端受保护资源。

核心流程

  1. 服务器生成随机消息(challenge)并发给客户端。

  2. 客户端用 MetaMask 连接钱包,签名该消息。

  3. 客户端将签名与地址发回服务器。

  4. 服务器验证签名,确认用户身份后,颁发 JWT。

  5. 后续请求携带 JWT 进行链下身份验证。

该方案结合链上签名的安全性和链下 JWT 的高效性,实现用户友好且安全的认证授权。


示例代码

const express = require('express');
const jwt = require('jsonwebtoken');
const { ethers } = require('ethers');const app = express();
app.use(express.json());const JWT_SECRET = 'your_jwt_secret';// 简单内存存储,实际项目应用数据库或缓存
const challenges = {};// 1. 客户端请求获取挑战消息
app.get('/auth/challenge/:address', (req, res) => {const { address } = req.params;const challenge = `登录验证消息:${Date.now()}`;challenges[address.toLowerCase()] = challenge;res.json({ challenge });
});// 2. 客户端提交签名和地址进行验证
app.post('/auth/verify', (req, res) => {const { address, signature } = req.body;const challenge = challenges[address.toLowerCase()];if (!challenge) {return res.status(400).json({ error: 'Challenge not found' });}try {// 验证签名是否匹配地址const signer = ethers.utils.verifyMessage(challenge, signature);if (signer.toLowerCase() !== address.toLowerCase()) {return res.status(401).json({ error: '签名验证失败' });}// 验证成功,签发 JWT,1 小时过期const token = jwt.sign({ address }, JWT_SECRET, { expiresIn: '1h' });// 可删除已使用的挑战,防止重放攻击delete challenges[address.toLowerCase()];res.json({ token });} catch (error) {res.status(400).json({ error: '签名验证异常' });}
});// 3. JWT 验证中间件
function authenticateToken(req, res, next) {const authHeader = req.headers['authorization'];if (!authHeader) return res.sendStatus(401);const token = authHeader.split(' ')[1];if (!token) return res.sendStatus(401);jwt.verify(token, JWT_SECRET, (err, user) => {if (err) return res.sendStatus(403);req.user = user;next();});
}// 4. 受保护资源示例
app.get('/protected', authenticateToken, (req, res) => {res.json({ message: `欢迎 ${req.user.address},访问受保护资源成功。` });
});app.listen(3000, () => {console.log('服务器运行于端口 3000');
});

讲解总结

  • 挑战消息(challenge):防止重放攻击,确保每次认证的唯一性。

  • 钱包签名:客户端用 MetaMask 调用 eth_signpersonal_sign 签名 challenge,证明地址所有权。

  • 签名验证:服务器用 ethers.utils.verifyMessage 验证签名对应的地址是否正确。

  • JWT 令牌:签名验证通过后,服务器生成 JWT,客户端持有该令牌访问后端资源,无需每次都签名。

  • 安全防护:使用 challenge 阶段限制重放,JWT 过期和服务器验证保护接口安全。

这种设计模式是当前主流 Web3 应用链上链下鉴权方案,兼具安全性和使用便利。

十三、Express 构建 DApp 的后端微服务架构

概念介绍

DApp(去中心化应用)通常需要后端服务来处理链上数据索引、用户身份管理、业务逻辑处理等。使用 Express 构建后端微服务架构,意味着将系统拆分成多个小型服务模块,每个模块专注单一职责,通过 API 接口相互通信,便于维护、扩展和独立部署。

关键点:

  • 模块化设计:每个微服务负责不同功能(如用户认证、交易处理、事件监听等)。

  • API 网关:统一入口,路由请求到不同微服务。

  • 异步消息队列(如 RabbitMQ/Kafka)用于微服务间解耦和异步通信。

  • 链上链下数据分离:微服务可专注链上事件处理或链下数据存储。

  • 使用 JWT 或钱包签名做鉴权

  • Docker 容器化部署,支持弹性扩缩。


示例代码

示例中展示一个简单的微服务结构示意,用 Express 实现用户服务和事件服务,并通过 HTTP 请求互相调用。

用户服务 user-service.js
const express = require('express');
const app = express();
app.use(express.json());app.post('/login', (req, res) => {// 处理用户登录,返回 tokenres.json({ token: 'user-jwt-token' });
});app.get('/profile/:address', (req, res) => {const { address } = req.params;// 查询用户链下数据res.json({ address, name: 'Alice', assets: [] });
});app.listen(3001, () => {console.log('User service running on port 3001');
});
事件服务 event-service.js
const express = require('express');
const axios = require('axios');
const app = express();
app.use(express.json());app.post('/process-event', async (req, res) => {const eventData = req.body;// 处理链上事件逻辑,比如入库、触发业务等// 调用用户服务示例:查询用户信息try {const response = await axios.get(`http://localhost:3001/profile/${eventData.userAddress}`);console.log('用户信息', response.data);} catch (err) {console.error('调用用户服务失败', err);}res.json({ status: 'event processed' });
});app.listen(3002, () => {console.log('Event service running on port 3002');
});

讲解总结

  • 职责分离:用户身份管理与链上事件处理分别独立微服务,互不影响,方便独立维护和升级。

  • 服务间通信:使用 HTTP REST 调用(示例中用 axios),也可用消息队列解耦。

  • 扩展性好:服务可以水平扩展、独立部署,提高系统可用性和稳定性。

  • 安全性:每个微服务独立实现鉴权机制,保护数据安全。

  • 链上数据处理:事件服务负责监听链上事件,异步处理后写入链下数据库,优化响应速度。

  • 容器化与自动化部署:结合 Docker 和 Kubernetes 做微服务编排和管理。

Express 结合微服务架构,是构建高效、灵活的 Web3 后端服务的常见方案。

http://www.xdnf.cn/news/947755.html

相关文章:

  • 可下载旧版app屏蔽更新的app市场
  • 判断是否是润年
  • 【投稿优惠】2025年航天技术 、雷达信号与无人机应用国际会议 (ATRA 2025)
  • Fail2ban开源入侵检测,保护SSH,NGINX等
  • 2025盘古石杯决赛【手机取证】
  • 手机平板能效生态设计指令EU 2023/1670标准解读
  • SQL Server 触发器调用存储过程实现发送 HTTP 请求
  • AI 导游:开启智能旅游新时代
  • CRMEB 中 PHP 短信扩展开发:涵盖一号通、阿里云、腾讯云、创蓝
  • 基于matlab策略迭代和值迭代法的动态规划
  • 基于 CNN-SHAP 分析卷积神经网络的多分类预测【MATLAB】
  • Matlab | 基于matlab的图像去噪的原理及实现
  • 【MATLAB第119期】基于MATLAB的KRR多输入多输出全局敏感性分析模型运用(无目标函数,考虑代理模型)
  • (原创改进)73-CEEMDAN-VMD-SSA-LSSVM功率/风速时间序列预测!
  • Linux 文本比较与处理工具:comm、uniq、diff、patch、sort 全解析
  • Selenium4+Pytest自动化测试框架
  • 基于 Three.js 的 3D 模型快照生成方案
  • FOUPK3云服务平台主体
  • Kafka主题运维全指南:从基础配置到故障处理
  • 消息队列生产问题解决方案全攻略
  • 【C#】多级缓存与多核CPU
  • (12)-Fiddler抓包-Fiddler设置IOS手机抓包
  • Mysql8 忘记密码重置,以及问题解决
  • 数据可视化交互
  • 计算机网络自定向下:第二章复习
  • GPIO(通用输入输出)与LPUART(低功耗通用异步收发传输器)简述
  • 简繁体智能翻译软件
  • 大数据清洗加工概述
  • 【c语言】安全完整性等级
  • Vue 3 + WebSocket 实战:公司通知实时推送功能详解