Skip to content

Commit 30c6759

Browse files
committed
feat: implement monitoring and security features in Node.js backend, including request tracing, metrics monitoring, and API rate limiting with Redis support
1 parent 807c1ef commit 30c6759

File tree

13 files changed

+567
-8
lines changed

13 files changed

+567
-8
lines changed

backend-nodejs/README.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
- [项目结构](#-项目结构)
1414
- [架构设计](#-架构设计)
1515
- [开发指南](#-开发指南)
16+
- [监控和安全](#-监控和安全)
1617
- [与 Go 后端的关系](#-与-go-后端的关系)
1718

1819
---
@@ -515,6 +516,68 @@ pnpm test:watch
515516

516517
---
517518

519+
## 📊 监控和安全
520+
521+
### 功能概览
522+
523+
系统已集成以下监控和安全功能:
524+
525+
- ✅ **请求追踪**:自动生成 TraceID/RequestID,支持分布式追踪
526+
- ✅ **Metrics 监控**:Prometheus 格式指标,支持 QPS、延迟、错误率监控
527+
- ✅ **API 限流**:基于 Redis 的限流保护,防止恶意请求
528+
529+
### 快速使用
530+
531+
#### 1. 请求追踪(自动启用)
532+
533+
```bash
534+
# 发送请求,自动获得追踪信息
535+
curl -v http://localhost:8081/api/tasks
536+
537+
# 响应头包含:
538+
# X-Trace-Id: 550e8400-e29b-41d4-a716-446655440000
539+
# X-Request-Id: 660e8400-e29b-41d4-a716-446655440001
540+
```
541+
542+
#### 2. Metrics 监控
543+
544+
```bash
545+
# 访问 Metrics 端点
546+
curl http://localhost:8081/metrics
547+
548+
# 集成 Prometheus(prometheus.yml)
549+
scrape_configs:
550+
- job_name: 'backend-nodejs'
551+
static_configs:
552+
- targets: ['localhost:8081']
553+
```
554+
555+
#### 3. API 限流
556+
557+
```bash
558+
# 测试登录限流(每分钟最多 5 次)
559+
for i in {1..6}; do
560+
curl -X POST http://localhost:8081/api/auth/login \
561+
-H "Content-Type: application/json" \
562+
-d '{"email":"test@example.com","password":"wrong"}'
563+
done
564+
# 第 6 次会返回 429 Too Many Requests
565+
```
566+
567+
### 详细文档
568+
569+
- 📖 [完整使用文档](docs/MONITORING_AND_SECURITY.md) - 详细的使用说明和配置
570+
- 🚀 [快速参考](docs/QUICK_START_MONITORING.md) - 快速上手指南
571+
- 💡 [使用示例](docs/USAGE_EXAMPLES.md) - 实际使用场景和代码示例
572+
- 🧪 [测试脚本](scripts/test-monitoring.sh) - 自动化测试脚本
573+
574+
### 已应用的限流策略
575+
576+
- **登录接口**:每分钟最多 5 次(防止暴力破解)
577+
- **注册接口**:每小时最多 3 次(防止批量注册)
578+
579+
---
580+
518581
## 🔗 与 Go 后端的关系
519582

520583
### 共享资源

backend-nodejs/cmd/server/main.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ async function main() {
9191
task: container.taskHandlerDeps,
9292
user: container.userHandlerDeps,
9393
auth: container.authHandlerDeps,
94-
}, container.authMiddleware);
94+
}, container.authMiddleware, redis);
9595

9696
// 9. 启动服务器
9797
const address = `http://${config.server.host}:${config.server.port}`;

backend-nodejs/domains/auth/http/router.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
*/
55

66
import type { FastifyInstance } from 'fastify';
7+
import type { RedisClientType } from 'redis';
78
import type { HandlerDependencies } from '../handlers/dependencies.js';
89
import type { RegisterRequest, LoginRequest, RefreshTokenRequest } from './dto/auth.js';
910
import {
@@ -14,18 +15,32 @@ import {
1415
import { registerHandler } from '../handlers/register.handler.js';
1516
import { loginHandler } from '../handlers/login.handler.js';
1617
import { refreshTokenHandler } from '../handlers/refresh_token.handler.js';
18+
import {
19+
createLoginRateLimitMiddleware,
20+
createRegisterRateLimitMiddleware,
21+
} from '../../../infrastructure/middleware/ratelimit.js';
1722

1823
/**
1924
* 注册 Auth 路由
25+
*
26+
* @param app Fastify 实例
27+
* @param deps Handler 依赖
28+
* @param redis Redis 客户端(可选,用于限流)
2029
*/
2130
export function registerAuthRoutes(
2231
app: FastifyInstance,
23-
deps: HandlerDependencies
32+
deps: HandlerDependencies,
33+
redis: RedisClientType | null = null
2434
): void {
25-
// POST /api/auth/register - 用户注册
35+
// 创建限流中间件
36+
const loginRateLimit = createLoginRateLimitMiddleware(redis);
37+
const registerRateLimit = createRegisterRateLimitMiddleware(redis);
38+
39+
// POST /api/auth/register - 用户注册(限流:每小时最多 3 次)
2640
app.post<{ Body: RegisterRequest }>(
2741
'/api/auth/register',
2842
{
43+
preHandler: [registerRateLimit],
2944
schema: {
3045
body: RegisterRequestSchema,
3146
},
@@ -35,10 +50,11 @@ export function registerAuthRoutes(
3550
}
3651
);
3752

38-
// POST /api/auth/login - 用户登录
53+
// POST /api/auth/login - 用户登录(限流:每分钟最多 5 次)
3954
app.post<{ Body: LoginRequest }>(
4055
'/api/auth/login',
4156
{
57+
preHandler: [loginRateLimit],
4258
schema: {
4359
body: LoginRequestSchema,
4460
},
@@ -48,7 +64,7 @@ export function registerAuthRoutes(
4864
}
4965
);
5066

51-
// POST /api/auth/refresh - 刷新 Token
67+
// POST /api/auth/refresh - 刷新 Token(不需要限流,因为需要有效的 refresh token)
5268
app.post<{ Body: RefreshTokenRequest }>(
5369
'/api/auth/refresh',
5470
{

backend-nodejs/infrastructure/bootstrap/server.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ import type { RedisClientType } from 'redis';
1212
import { checkHealth } from '../monitoring/health/health.js';
1313
import type { AuthMiddleware } from '../middleware/types.js';
1414
import { isDomainError } from '../../shared/errors/errors.js';
15+
import { tracingMiddleware } from '../middleware/tracing.js';
16+
import { metricsMiddleware, metricsResponseHook } from '../middleware/metrics.js';
17+
import { register } from '../monitoring/metrics/metrics.js';
1518

1619
/**
1720
* 创建 Fastify 服务器
@@ -40,11 +43,20 @@ export function createServer(config: Config): FastifyInstance {
4043
* 注册全局中间件
4144
*/
4245
export async function registerMiddleware(fastify: FastifyInstance): Promise<void> {
46+
// 请求追踪(必须在最前面,确保所有请求都有 TraceID/RequestID)
47+
fastify.addHook('onRequest', tracingMiddleware);
48+
4349
// CORS
4450
await fastify.register(cors, {
4551
origin: true,
4652
});
4753

54+
// Metrics 中间件(记录请求开始时间)
55+
fastify.addHook('onRequest', metricsMiddleware);
56+
57+
// Metrics 响应 Hook(记录请求指标)
58+
fastify.addHook('onResponse', metricsResponseHook);
59+
4860
// Security headers (手动实现,因为 @fastify/helmet 不支持 Fastify 5)
4961
fastify.addHook('onRequest', async (_request: FastifyRequest, reply: FastifyReply) => {
5062
reply.header('X-Content-Type-Options', 'nosniff');
@@ -111,6 +123,12 @@ export function registerRoutes(
111123
return reply.code(statusCode).send(health);
112124
});
113125

126+
// Prometheus Metrics 端点
127+
fastify.get('/metrics', async (_request: unknown, reply) => {
128+
reply.type('text/plain');
129+
return register.metrics();
130+
});
131+
114132
// 根路径
115133
fastify.get('/', async (_request: unknown, reply) => {
116134
return reply.send({
@@ -128,7 +146,8 @@ export function registerRoutes(
128146
export async function registerDomainRoutes(
129147
fastify: FastifyInstance,
130148
handlerDeps: Record<string, unknown>,
131-
authMiddleware: AuthMiddleware
149+
authMiddleware: AuthMiddleware,
150+
redis: RedisClientType | null = null
132151
): Promise<void> {
133152
// 动态导入并注册 Task 路由
134153
if (handlerDeps.task) {
@@ -153,7 +172,11 @@ export async function registerDomainRoutes(
153172
// 动态导入并注册 Auth 路由(不需要认证中间件)
154173
if (handlerDeps.auth) {
155174
const authRouter = await import('../../domains/auth/http/router.js');
156-
authRouter.registerAuthRoutes(fastify, handlerDeps.auth as Parameters<typeof authRouter.registerAuthRoutes>[1]);
175+
authRouter.registerAuthRoutes(
176+
fastify,
177+
handlerDeps.auth as Parameters<typeof authRouter.registerAuthRoutes>[1],
178+
redis
179+
);
157180
}
158181
}
159182

backend-nodejs/infrastructure/middleware/auth.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,13 @@ import type { FastifyRequest, FastifyReply } from 'fastify';
77
import type { JWTService } from '../../domains/auth/service/jwt_service.js';
88

99
// 扩展 FastifyRequest 类型,添加 user_id 和 email
10+
// 注意:traceId 和 requestId 在 tracing.ts 中定义
1011
declare module 'fastify' {
1112
interface FastifyRequest {
1213
userId?: string;
1314
email?: string;
15+
traceId?: string;
16+
requestId?: string;
1417
}
1518
}
1619

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/**
2+
* Metrics 中间件
3+
* 记录 HTTP 请求的指标(QPS、延迟、错误率)
4+
*/
5+
6+
import type { FastifyRequest, FastifyReply } from 'fastify';
7+
import { httpRequestCounter, httpRequestDuration } from '../monitoring/metrics/metrics.js';
8+
9+
/**
10+
* Metrics 中间件
11+
* 记录每个请求的指标
12+
* 使用 Fastify 的 onRequest hook 记录开始时间,onResponse hook 记录指标
13+
*/
14+
export async function metricsMiddleware(
15+
request: FastifyRequest,
16+
_reply: FastifyReply
17+
): Promise<void> {
18+
// 在请求对象上存储开始时间
19+
(request as any).metricsStartTime = Date.now();
20+
}
21+
22+
/**
23+
* Metrics 响应 Hook
24+
* 在响应发送后记录指标
25+
*/
26+
export async function metricsResponseHook(
27+
request: FastifyRequest,
28+
reply: FastifyReply
29+
): Promise<void> {
30+
const startTime = (request as any).metricsStartTime;
31+
if (!startTime) {
32+
return; // 如果没有开始时间,跳过
33+
}
34+
35+
const duration = (Date.now() - startTime) / 1000; // 转换为秒
36+
const method = request.method;
37+
// 获取路由路径(如果没有 routerPath,使用 URL 路径)
38+
const route = (request as any).routerPath || request.url.split('?')[0]; // 移除查询参数
39+
const statusCode = reply.statusCode;
40+
41+
// 记录请求计数
42+
httpRequestCounter.inc({
43+
method,
44+
route,
45+
status: statusCode.toString(),
46+
});
47+
48+
// 记录请求延迟
49+
httpRequestDuration.observe(
50+
{
51+
method,
52+
route,
53+
},
54+
duration
55+
);
56+
}
57+

0 commit comments

Comments
 (0)