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.pswp.cn/diannao/86425.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

游戏开发中的CI/CD优化案例:知名游戏公司Gearbox使用TeamCity简化CI/CD流程

案例背景 关于Gearbox&#xff1a; Gearbox 是一家美国电子游戏公司&#xff0c;总部位于德克萨斯州弗里斯科&#xff0c;靠近达拉斯。Gearbox 成立于1999年&#xff0c;推出过多款史上最具代表性的视频游戏&#xff0c;包括《半衰期》、《战火兄弟连》以及《无主之地》。 团队…

视觉slam--三维刚体运动

线性代数 外积与矩阵乘法的等价性 欧拉角的奇异性--万向死锁 现象 第二个轴旋转度&#xff0c;会导致第三个旋转轴和恶原始坐标轴的第一个旋转轴重合&#xff0c;导致第一次旋转与第三次旋转都使用了同一个轴进行旋转&#xff0c;也就是本质上旋转三次&#xff0c;但是只在两个…

内窥镜检查中基于提示的息肉分割|文献速递-深度学习医疗AI最新文献

Title 题目 Prompt-based polyp segmentation during endoscopy 内窥镜检查中基于提示的息肉分割 01 文献速递介绍 以下是对这段英文内容的中文翻译&#xff1a; ### 胃肠道癌症的发病率呈上升趋势&#xff0c;且有年轻化倾向&#xff08;Bray等人&#xff0c;2018&#x…

CppCon 2015 学习:REFLECTION TECHNIQUES IN C++

关于 Reflection&#xff08;反射&#xff09; 这个概念&#xff0c;总结一下&#xff1a; Reflection&#xff08;反射&#xff09;是什么&#xff1f; 反射是对类型的自我检查能力&#xff08;Introspection&#xff09; 可以查看类的成员变量、成员函数等信息。反射允许枚…

R语言速释制剂QBD解决方案之一

本文是《Quality by Design for ANDAs: An Example for Immediate-Release Dosage Forms》第一个处方的R语言解决方案。 第一个处方研究评估原料药粒径分布、MCC/Lactose比例、崩解剂用量对制剂CQAs的影响。 第二处方研究用于理解颗粒外加硬脂酸镁和滑石粉对片剂质量和可生产…

“详规一张图”——新加坡土地利用数据

在城市规划和土地管理领域&#xff0c;精确且详尽的空间数据是进行有效决策的基石。随着地理信息系统&#xff08;GIS&#xff09;技术的发展&#xff0c;我们能够以前所未有的精度和细节来捕捉、分析和展示土地利用信息。这不仅提升了数据的质量和可靠性&#xff0c;还使得城市…

LabVIEW双光子成像系统技术

双光子成像技术的核心特性 双光子成像通过双低能量光子协同激发机制&#xff0c;展现出显著的技术优势&#xff1a; 深层组织穿透能力&#xff1a;适用于活体组织深度成像 高分辨率观测性能&#xff1a;满足微观结构的精细研究需求 低光毒性特点&#xff1a;减少对样本的损伤…

MySQL自定义函数零基础学习教程

1. 引言 想象一下&#xff0c;你在用计算器做数学题。每次计算"圆形面积"时&#xff0c;你都要输入&#xff1a;3.14 半径 半径。如果能把这个计算步骤保存起来&#xff0c;下次只要输入半径就自动算出面积&#xff0c;那该多方便&#xff01; MySQL自定义函数就…

八股---7.JVM

1. JVM组成 1.1 JVM由哪些部分组成?运行流程? 难易程度:☆☆☆ 出现频率:☆☆☆☆ Java Virtual Machine:Java 虚拟机,Java程序的运行环境(java二进制字节码的运行环境)好处:一次编写,到处运行;自动内存管理,垃圾回收机制程序运行之前,需要先通过编译器将…

企业级AI-DevOps工具链的构成及实现方案

企业级AI-DevOps工具链的构成及实现方案 DevOps在AI大模型研发中的重要性及应用背景一、场景驱动的AI产品研发运营机制二、AI-DevOps生产线建设三、基于DevOps的AI大模型研发机制四、基于DevOps的智能体场景研发机制五、场景驱动的应用评估分析机制 DevOps在AI大模型研发中的重…

在 Spring Boot 项目里,MYSQL中json类型字段使用

前言&#xff1a; 因为程序特殊需求导致&#xff0c;需要mysql数据库存储json类型数据&#xff0c;因此记录一下使用流程 1.java实体中新增字段 private List<User> users 2.增加mybatis-plus注解 TableField(typeHandler FastjsonTypeHandler.class) private Lis…

Python竞赛环境搭建全攻略

Python环境搭建竞赛技术文章大纲 竞赛背景与意义 竞赛的目的与价值Python在竞赛中的应用场景环境搭建对竞赛效率的影响 竞赛环境需求分析 常见竞赛类型&#xff08;算法、数据分析、机器学习等&#xff09;不同竞赛对Python版本及库的要求硬件与操作系统的兼容性问题 Pyth…

在 Win10 上 WSL 安装 Debian 12 后,Linux 如何启动 SMTP 服务?

在 WSL 的 Debian 12 中启动 SMTP 服务&#xff08;以 Postfix 为例&#xff09;&#xff0c;请按以下步骤操作&#xff1a; 1. 安装 Postfix sudo apt update sudo apt install postfix mailutils安装过程中会弹出配置窗口&#xff1a; General type of mail configuration&a…

树莓派超全系列教程文档--(59)树莓派摄像头rpicam-apps

这里写目录标题 rpicam-apps libcamera 文章来源&#xff1a; http://raspberry.dns8844.cn/documentation 原文网址 rpicam-apps 树莓派操作系统 Bookworm 将相机捕捉应用程序从 libcamera-\* 重命名为 rpicam-*。符号链接允许用户暂时使用旧名称。尽快采用新的应用程序名称…

【数据结构】图论最短路径算法深度解析:从BFS基础到全算法综述​

最短路径 导读一、最短路径1.1 单源最短路径1.2 各顶点间的最短路径1.3 最短路径算法 二、BFS算法结语内容回顾下一篇预告&#xff1a;挑战带权最短路径&#xff01; 导读 大家好&#xff0c;很高兴又和大家见面啦&#xff01;&#xff01;&#xff01; 欢迎继续探索图算法的…

中国政务数据安全建设细化及市场需求分析

(基于新《政务数据共享条例》及相关法规) 一、引言 近年来,中国政府高度重视数字政府建设和数据要素市场化配置改革。《政务数据共享条例》(以下简称“《共享条例》”)的发布,与《中华人民共和国数据安全法》(以下简称“《数据安全法》”)、《中华人民共和国个人信息…

Linux信号保存与处理机制详解

Linux信号的保存与处理涉及多个关键机制&#xff0c;以下是详细的总结&#xff1a; 1. 信号的保存 进程描述符&#xff08;task_struct&#xff09;&#xff1a;每个进程的PCB中包含信号相关信息。 pending信号集&#xff1a;记录已到达但未处理的信号&#xff08;未决信号&a…

【Redis】笔记|第10节|京东HotKey实现多级缓存架构

缓存架构 京东HotKey架构 代码结构 代码详情 功能点&#xff1a;&#xff08;如代码有错误&#xff0c;欢迎讨论纠正&#xff09; 多级缓存&#xff0c;先查HotKey缓存&#xff0c;再查Redis&#xff0c;最后才查数据库热点数据重建逻辑使用分布式锁&#xff0c;二次查询更新…

php apache构建 Web 服务器

虚拟机配置流程winsever2016配置Apache、Mysql、php_windows server 2016配置web服务器-CSDN博客 PHP 和 Apache 通过 ​​模块化协作​​ 共同构建 Web 服务器&#xff0c;以下是它们的交互机制和工作流程&#xff1a; ​​一、核心组件分工​​ 组件角色​​Apache​​Web …

二分查找排序讲解

一、二分查找&#xff08;Binary Search&#xff09; 核心思想&#xff1a; 前提&#xff1a;数组必须是 有序的&#xff08;比如从小到大排列&#xff09;。目标&#xff1a;在数组中快速找到某个数&#xff08;比如找 7&#xff09;。方法&#xff1a;每次排除一半的数&…