基于Node.Js的EventEmitter,实现一个电影订票系统的网页Demo
时间: 2025-08-19 14:19:56 浏览: 5
<think>好的,我现在需要帮助用户基于Node.js的EventEmitter实现一个电影订票系统的网页Demo。用户之前的问题是关于JavaScript中的EventTarget,现在转向Node.js的EventEmitter,这两者有相似之处,但应用场景不同。用户可能已经了解EventTarget在前端处理DOM事件,现在想在后端使用事件驱动模型。
首先,我需要确认用户的需求。他们想要构建一个电影订票系统的网页示例,使用EventEmitter来处理事件。可能的场景包括座位选择、订票、取消订单等,这些操作可以通过事件驱动的方式来处理。
接下来,我需要考虑如何将EventEmitter应用到系统中。EventEmitter在Node.js中常用于处理异步操作和事件监听,比如当用户订票时触发事件,通知其他部分系统更新状态。例如,当座位被预订时,触发一个事件,更新可用座位数,或者发送确认邮件。
用户提供的引用内容中提到了EventEmitter的基本用法,如创建实例、注册事件监听器、触发事件。我需要确保代码示例涵盖这些方面,并结合实际订票系统的功能。
可能需要定义几个事件,比如'seatSelected'、'bookingConfirmed'、'paymentProcessed'等。每个事件对应相应的监听器函数,处理业务逻辑,比如检查座位是否可用、更新数据库、处理支付等。
还需要考虑如何将后端的事件处理与前端的网页交互结合起来。由于用户提到的是网页Demo,可能需要使用Express框架搭建一个简单的Web服务器,处理HTTP请求,并通过EventEmitter触发和响应事件。
同时,用户可能希望看到代码的结构清晰,模块化,比如将订票逻辑、支付处理、邮件通知等分离到不同的模块中,通过事件进行通信,降低耦合度。
需要注意错误处理,比如当座位已经被预订时,触发一个错误事件,或者回滚操作。EventEmitter的error事件需要特别处理,避免程序崩溃。
另外,用户可能对如何在前端展示实时更新感兴趣,比如使用WebSocket或Server-Sent Events将后端的事件推送到前端,但考虑到用户要求的是示例代码,可能先集中在后端逻辑,使用简单的API端点来演示事件处理。
最后,要确保代码示例简洁,注释清晰,方便用户理解如何将EventEmitter应用到实际项目中。同时,提供相关问题,帮助用户进一步探索,比如如何处理并发订票、如何扩展事件系统等。</think>### 基于 Node.js EventEmitter 的电影订票系统网页 Demo
#### 一、系统设计与核心逻辑
使用 `EventEmitter` 实现电影订票系统的核心事件驱动模型,通过事件解耦订票流程中的各个模块(如座位选择、支付、通知)。以下是关键事件和流程:
| 事件类型 | 触发场景 | 监听器功能 |
|-------------------|-------------------------|---------------------------------------|
| `seatSelected` | 用户选择座位时 | 检查座位是否可用,标记为锁定状态 |
| `paymentSuccess` | 支付成功时 | 更新座位状态为已售,发送确认邮件 |
| `paymentFailed` | 支付失败时 | 释放锁定座位,记录失败日志 |
| `timeout` | 用户操作超时(如15分钟)| 自动释放未支付的锁定座位 |
---
#### 二、代码实现(Express + EventEmitter)
##### 1. 初始化项目与依赖
```bash
npm init -y
npm install express
```
##### 2. 核心业务逻辑 (`ticketSystem.js`)
```javascript
const EventEmitter = require('events');
class TicketSystem extends EventEmitter {
constructor() {
super();
this.seats = new Map(); // 存储座位状态:available(可用)/locked(锁定)/sold(已售)
this.initSeats(50); // 初始化50个座位
}
// 初始化座位
initSeats(total) {
for (let i = 1; i <= total; i++) {
this.seats.set(`A-${i}`, 'available');
}
}
// 选择座位(触发事件)
selectSeat(userId, seatId) {
if (this.seats.get(seatId) === 'available') {
this.seats.set(seatId, 'locked');
this.emit('seatSelected', { userId, seatId, timestamp: Date.now() });
return true;
}
return false;
}
// 处理支付结果
handlePayment(userId, seatId, isSuccess) {
const eventType = isSuccess ? 'paymentSuccess' : 'paymentFailed';
this.emit(eventType, { userId, seatId });
}
}
// 导出单例模式
module.exports = new TicketSystem();
```
##### 3. 事件监听与副作用处理 (`eventHandlers.js`)
```javascript
const ticketSystem = require('./ticketSystem');
const nodemailer = require('nodemailer'); // 需安装:npm install nodemailer
// 邮件通知模块
const transporter = nodemailer.createTransport({
service: 'gmail',
auth: { user: '[email protected]', pass: 'your-password' }
});
// 监听座位选择事件(设置15分钟超时)
ticketSystem.on('seatSelected', ({ userId, seatId, timestamp }) => {
const timeout = 15 * 60 * 1000; // 15分钟
const timer = setTimeout(() => {
ticketSystem.seats.set(seatId, 'available');
console.log(`座位 ${seatId} 因超时被释放`);
}, timeout);
// 支付结果决定是否清除定时器
ticketSystem.once(`payment-${seatId}`, (isSuccess) => {
clearTimeout(timer);
if (!isSuccess) ticketSystem.seats.set(seatId, 'available');
});
});
// 支付成功处理
ticketSystem.on('paymentSuccess', ({ userId, seatId }) => {
ticketSystem.seats.set(seatId, 'sold');
transporter.sendMail({
to: userId + '@example.com',
subject: '订票成功通知',
text: `您已成功购买座位 ${seatId},请于放映前30分钟取票。`
});
});
// 支付失败处理
ticketSystem.on('paymentFailed', ({ userId, seatId }) => {
console.error(`用户 ${userId} 支付座位 ${seatId} 失败`);
});
```
##### 4. Web服务器与API接口 (`app.js`)
```javascript
const express = require('express');
const ticketSystem = require('./ticketSystem');
const app = express();
app.use(express.json());
// API: 查询所有座位状态
app.get('/seats', (req, res) => {
res.json([...ticketSystem.seats.entries()]);
});
// API: 选择座位
app.post('/select-seat', (req, res) => {
const { userId, seatId } = req.body;
const success = ticketSystem.selectSeat(userId, seatId);
res.status(success ? 200 : 400).json({ success });
});
// API: 模拟支付回调
app.post('/pay', (req, res) => {
const { userId, seatId, simulateSuccess = true } = req.body;
ticketSystem.handlePayment(userId, seatId, simulateSuccess);
res.json({ message: '支付处理完成' });
});
app.listen(3000, () => console.log('Server running on port 3000'));
```
---
#### 三、运行与测试
1. 启动服务:`node app.js`
2. 使用 Postman 或 curl 测试:
```bash
# 查询座位状态
curl http://localhost:3000/seats
# 选择座位 A-5
curl -X POST -H "Content-Type: application/json" -d '{"userId":"alice", "seatId":"A-5"}' http://localhost:3000/select-seat
# 模拟支付成功(默认)
curl -X POST -H "Content-Type: application/json" -d '{"userId":"alice", "seatId":"A-5"}' http://localhost:3000/pay
# 模拟支付失败
curl -X POST -H "Content-Type: application/json" -d '{"userId":"bob", "seatId":"A-5", "simulateSuccess":false}' http://localhost:3000/pay
```
---
#### 四、高级优化方向
1. **并发控制**:使用 Redis 分布式锁避免多实例竞争
2. **持久化存储**:将座位状态保存到数据库(如 MongoDB)
3. **实时推送**:结合 Socket.IO 实现前端座位状态实时更新
4. **事务补偿**:通过 Saga 模式处理跨服务的分布式事务
---
### 相关问题
1. 如何在高并发场景下保证座位锁定的原子性?
2. 事件驱动架构与传统 RESTful API 设计在性能上有何差异?
3. 如何用 TypeScript 增强 EventEmitter 的类型安全性?
4. 在微服务架构中如何跨服务传递 EventEmitter 的事件?
[^1]: Node.js 官方文档对 EventEmitter 的说明
[^2]: 《Node.js 设计模式》中关于事件驱动的架构设计
[^3]: Express 中间件与路由管理的最佳实践
阅读全文
相关推荐



















