一、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()
接收 ExecutionContext
和 CallHandler
,通過 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 操作符(如
tap
、map
)可以異步處理響應。 -
攔截器廣泛應用于日志、緩存、異常轉換、數據格式化等場景。
-
可以作用于全局、控制器、單個路由,支持靈活配置。
-
結合守衛和管道,構成 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 示例 | 控制器函數 |
---|---|---|---|
獲取用戶 | GET | http://localhost:3000/users/123 | getUser() |
獲取商品 | GET | http://localhost:3000/products/456 | getProduct() |
如果你還想加 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.json 或 res.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 令牌,用戶憑此令牌訪問后端受保護資源。
核心流程:
-
服務器生成隨機消息(challenge)并發給客戶端。
-
客戶端用 MetaMask 連接錢包,簽名該消息。
-
客戶端將簽名與地址發回服務器。
-
服務器驗證簽名,確認用戶身份后,頒發 JWT。
-
后續請求攜帶 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_sign
或personal_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 后端服務的常見方案。