Koa
在前端技术的跃进浪潮中,一个框架脱颖而出 —— Koa.js,它是由Express原班人马打造的新一代Node.js框架。为什么它能成为时下热议的焦点?因为Koa.js以其简洁的设计,强大的功能和对现代JavaScript特性(如async/await)的天然支持,重新定义了后端的开发模式。
简洁:Koa.js提供了一个轻量的函数库,让你能够快速搭建服务器。
现代化:它采用最新的JS特性,使得代码更加直观且易于管理。
灵活:通过中间件机制,你可以轻松扩展功能,实现定制化的解决方案。
性能:Koa.js注重性能优化,可以建立更快、更稳定的网络应用。
不仅如此,Koa.js的优雅编程体验和提升的开发效率,让前端工程师的技能得到了全方位的提升。它不是简单的技术更迭,而是前端领域的一次革新旅程。

查看更多相关内容
Koa 性能优化的策略和最佳实践Koa 的性能优化是构建高性能应用的关键。通过合理配置和优化策略,可以显著提升应用的响应速度和并发处理能力。
**1. 中间件优化:**
**减少中间件数量:**
```javascript
// 不好的做法:过多的中间件
app.use(middleware1);
app.use(middleware2);
app.use(middleware3);
app.use(middleware4);
app.use(middleware5);
// 好的做法:合并功能相似的中间件
app.use(compose([middleware1, middleware2]));
app.use(compose([middleware3, middleware4]));
```
**优化中间件顺序:**
```javascript
// 将高频使用的中间件放在前面
app.use(loggerMiddleware); // 日志
app.use(corsMiddleware); // CORS
app.use(bodyParserMiddleware); // 解析请求体
app.use(authMiddleware); // 认证
app.use(routerMiddleware); // 路由
```
**避免在中间件中执行耗时操作:**
```javascript
// 不好的做法:在中间件中执行数据库查询
app.use(async (ctx, next) => {
const user = await User.findById(ctx.session.userId);
ctx.state.user = user;
await next();
});
// 好的做法:按需加载数据
app.use(async (ctx, next) => {
await next();
// 在需要时再加载数据
});
```
**2. 异步处理优化:**
**使用 Promise.all 并行处理:**
```javascript
// 不好的做法:串行执行
app.use(async (ctx) => {
const user = await User.findById(id);
const posts = await Post.findByUserId(id);
const comments = await Comment.findByUserId(id);
ctx.body = { user, posts, comments };
});
// 好的做法:并行执行
app.use(async (ctx) => {
const [user, posts, comments] = await Promise.all([
User.findById(id),
Post.findByUserId(id),
Comment.findByUserId(id)
]);
ctx.body = { user, posts, comments };
});
```
**使用缓存减少重复查询:**
```javascript
const cache = new Map();
app.use(async (ctx) => {
const cacheKey = `user:${ctx.params.id}`;
// 检查缓存
if (cache.has(cacheKey)) {
ctx.body = cache.get(cacheKey);
return;
}
// 查询数据库
const user = await User.findById(ctx.params.id);
cache.set(cacheKey, user);
ctx.body = user;
});
```
**3. 数据库连接池优化:**
```javascript
const { Pool } = require('pg');
// 配置连接池
const pool = new Pool({
host: 'localhost',
port: 5432,
database: 'mydb',
user: 'user',
password: 'password',
max: 20, // 最大连接数
min: 5, // 最小连接数
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000
});
app.use(async (ctx, next) => {
const client = await pool.connect();
ctx.state.db = client;
try {
await next();
} finally {
client.release();
}
});
```
**4. 响应压缩:**
使用 `koa-compress` 中间件压缩响应。
```bash
npm install koa-compress
```
```javascript
const compress = require('koa-compress');
app.use(compress({
filter: (contentType) => {
return /text/i.test(contentType);
},
threshold: 2048, // 大于 2KB 时压缩
gzip: {
flush: require('zlib').constants.Z_SYNC_FLUSH
},
deflate: {
flush: require('zlib').constants.Z_SYNC_FLUSH
},
br: false // 禁用 brotli
}));
```
**5. 静态资源优化:**
```javascript
const serve = require('koa-static');
const { createGzip } = require('zlib');
const { createReadStream, createWriteStream } = require('fs');
// 配置静态资源服务
app.use(serve('./public', {
maxage: 365 * 24 * 60 * 60 * 1000, // 1 年缓存
gzip: true,
brotli: true
}));
// 预压缩静态资源
function precompressStaticFiles() {
const files = fs.readdirSync('./public');
files.forEach(file => {
if (file.endsWith('.js') || file.endsWith('.css')) {
const filePath = path.join('./public', file);
const gzipPath = filePath + '.gz';
const readStream = createReadStream(filePath);
const writeStream = createWriteStream(gzipPath);
const gzip = createGzip();
readStream.pipe(gzip).pipe(writeStream);
}
});
}
```
**6. HTTP/2 支持:**
```javascript
const http2 = require('http2');
const fs = require('fs');
const server = http2.createSecureServer({
key: fs.readFileSync('server.key'),
cert: fs.readFileSync('server.crt')
}, app.callback());
server.listen(3000);
```
**7. 集群模式:**
使用 Node.js 的 cluster 模块充分利用多核 CPU。
```javascript
const cluster = require('cluster');
const numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
console.log(`Master ${process.pid} is running`);
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', (worker, code, signal) => {
console.log(`Worker ${worker.process.pid} died`);
cluster.fork();
});
} else {
app.listen(3000);
console.log(`Worker ${process.pid} started`);
}
```
**8. 监控和性能分析:**
```javascript
const prometheus = require('prom-client');
// 创建指标
const httpRequestDuration = new prometheus.Histogram({
name: 'http_request_duration_seconds',
help: 'Duration of HTTP requests in seconds',
labelNames: ['method', 'route', 'code']
});
// 中间件收集指标
app.use(async (ctx, next) => {
const start = Date.now();
await next();
const duration = (Date.now() - start) / 1000;
httpRequestDuration.observe(
{
method: ctx.method,
route: ctx.path,
code: ctx.status
},
duration
);
});
// 指标端点
app.use(async (ctx) => {
if (ctx.path === '/metrics') {
ctx.set('Content-Type', prometheus.register.contentType);
ctx.body = await prometheus.register.metrics();
}
});
```
**9. 代码分割和懒加载:**
```javascript
// 懒加载路由
app.use(async (ctx, next) => {
if (ctx.path.startsWith('/admin')) {
const adminRoutes = await import('./routes/admin');
adminRoutes.default.routes()(ctx, next);
} else if (ctx.path.startsWith('/api')) {
const apiRoutes = await import('./routes/api');
apiRoutes.default.routes()(ctx, next);
} else {
await next();
}
});
```
**10. 性能优化最佳实践:**
1. **中间件优化:**
- 减少中间件数量
- 合并功能相似的中间件
- 优化中间件执行顺序
- 避免在中间件中执行耗时操作
2. **异步处理:**
- 使用 Promise.all 并行处理
- 实现缓存机制
- 使用连接池管理数据库连接
- 避免阻塞主线程
3. **资源优化:**
- 压缩响应内容
- 启用静态资源缓存
- 使用 CDN 加速
- 预压缩静态资源
4. **架构优化:**
- 使用集群模式
- 实现负载均衡
- 使用 HTTP/2
- 实现微服务架构
5. **监控和调优:**
- 实现性能监控
- 使用性能分析工具
- 定期进行性能测试
- 持续优化和改进
服务端 · 2月21日 15:54
Koa 测试框架选择和测试最佳实践Koa 测试是保证应用质量的重要环节。通过合理的测试策略和工具,可以确保应用的稳定性和可靠性。
**1. 测试框架选择:**
常用的 Koa 测试框架包括:
- **Jest**:Facebook 开发的测试框架,功能全面
- **Mocha + Chai**:灵活的测试框架组合
- **Supertest**:专门用于 HTTP 测试的库
**2. 基本测试设置:**
**安装依赖:**
```bash
npm install --save-dev jest supertest @types/jest @types/supertest
```
**Jest 配置:**
```javascript
// jest.config.js
module.exports = {
testEnvironment: 'node',
coverageDirectory: 'coverage',
collectCoverageFrom: [
'src/**/*.js',
'!src/**/*.test.js'
],
testMatch: [
'**/__tests__/**/*.js',
'**/?(*.)+(spec|test).js'
]
};
```
**3. 基本路由测试:**
```javascript
const request = require('supertest');
const app = require('../app');
describe('Basic routes', () => {
test('GET / should return Hello Koa', async () => {
const response = await request(app.callback())
.get('/')
.expect(200);
expect(response.text).toBe('Hello Koa');
});
test('GET /not-found should return 404', async () => {
const response = await request(app.callback())
.get('/not-found')
.expect(404);
});
});
```
**4. 中间件测试:**
```javascript
const Koa = require('koa');
const loggerMiddleware = require('../middleware/logger');
describe('Logger middleware', () => {
test('should log request information', async () => {
const app = new Koa();
const logs = [];
// Mock console.log
const originalLog = console.log;
console.log = (...args) => logs.push(args.join(' '));
app.use(loggerMiddleware);
app.use(async (ctx) => {
ctx.body = 'test';
});
await request(app.callback()).get('/test');
console.log = originalLog;
expect(logs.length).toBeGreaterThan(0);
expect(logs[0]).toContain('GET /test');
});
});
```
**5. 认证中间件测试:**
```javascript
const authMiddleware = require('../middleware/auth');
describe('Auth middleware', () => {
test('should allow access with valid token', async () => {
const ctx = {
headers: { authorization: 'Bearer valid-token' },
state: {}
};
const next = jest.fn();
await authMiddleware(ctx, next);
expect(next).toHaveBeenCalled();
expect(ctx.state.user).toBeDefined();
});
test('should deny access without token', async () => {
const ctx = {
headers: {},
state: {},
throw: jest.fn()
};
const next = jest.fn();
await authMiddleware(ctx, next);
expect(next).not.toHaveBeenCalled();
expect(ctx.throw).toHaveBeenCalledWith(401, 'Unauthorized');
});
});
```
**6. API 测试:**
```javascript
describe('User API', () => {
test('POST /api/users should create user', async () => {
const userData = {
name: 'John Doe',
email: 'john@example.com',
password: 'password123'
};
const response = await request(app.callback())
.post('/api/users')
.send(userData)
.expect(201);
expect(response.body).toHaveProperty('id');
expect(response.body.name).toBe(userData.name);
expect(response.body.email).toBe(userData.email);
expect(response.body).not.toHaveProperty('password');
});
test('GET /api/users/:id should return user', async () => {
const userId = 1;
const response = await request(app.callback())
.get(`/api/users/${userId}`)
.expect(200);
expect(response.body).toHaveProperty('id', userId);
expect(response.body).toHaveProperty('name');
expect(response.body).toHaveProperty('email');
});
test('PUT /api/users/:id should update user', async () => {
const userId = 1;
const updateData = {
name: 'Updated Name'
};
const response = await request(app.callback())
.put(`/api/users/${userId}`)
.send(updateData)
.expect(200);
expect(response.body.name).toBe(updateData.name);
});
test('DELETE /api/users/:id should delete user', async () => {
const userId = 1;
await request(app.callback())
.delete(`/api/users/${userId}`)
.expect(204);
});
});
```
**7. 错误处理测试:**
```javascript
describe('Error handling', () => {
test('should handle 404 errors', async () => {
const response = await request(app.callback())
.get('/non-existent-route')
.expect(404);
expect(response.body).toHaveProperty('error');
expect(response.body).toHaveProperty('code', 'NOT_FOUND');
});
test('should handle validation errors', async () => {
const invalidData = {
name: '',
email: 'invalid-email'
};
const response = await request(app.callback())
.post('/api/users')
.send(invalidData)
.expect(400);
expect(response.body).toHaveProperty('error');
expect(response.body).toHaveProperty('code', 'VALIDATION_ERROR');
});
test('should handle server errors', async () => {
// Mock a database error
jest.spyOn(User, 'create').mockRejectedValue(new Error('Database error'));
const response = await request(app.callback())
.post('/api/users')
.send({ name: 'Test', email: 'test@example.com' })
.expect(500);
expect(response.body).toHaveProperty('error');
expect(response.body).toHaveProperty('code', 'INTERNAL_ERROR');
});
});
```
**8. 集成测试:**
```javascript
describe('Integration tests', () => {
let authToken;
beforeAll(async () => {
// Setup: create test user and get token
const response = await request(app.callback())
.post('/api/auth/login')
.send({
email: 'test@example.com',
password: 'password123'
});
authToken = response.body.token;
});
afterAll(async () => {
// Cleanup: delete test data
await request(app.callback())
.delete('/api/test/cleanup');
});
test('should create and retrieve post', async () => {
// Create post
const createResponse = await request(app.callback())
.post('/api/posts')
.set('Authorization', `Bearer ${authToken}`)
.send({
title: 'Test Post',
content: 'Test content'
})
.expect(201);
const postId = createResponse.body.id;
// Retrieve post
const getResponse = await request(app.callback())
.get(`/api/posts/${postId}`)
.expect(200);
expect(getResponse.body.title).toBe('Test Post');
expect(getResponse.body.content).toBe('Test content');
});
});
```
**9. 性能测试:**
```javascript
describe('Performance tests', () => {
test('should handle 1000 requests in reasonable time', async () => {
const startTime = Date.now();
const requests = [];
for (let i = 0; i < 1000; i++) {
requests.push(
request(app.callback())
.get('/api/users')
.expect(200)
);
}
await Promise.all(requests);
const duration = Date.now() - startTime;
expect(duration).toBeLessThan(5000); // Should complete in < 5 seconds
});
});
```
**10. 测试最佳实践:**
1. **测试组织:**
- 按功能模块组织测试文件
- 使用 describe 分组相关测试
- 使用有意义的测试名称
2. **测试隔离:**
- 每个测试应该独立运行
- 使用 beforeAll/afterAll 设置和清理
- 使用 beforeEach/afterEach 重置状态
3. **Mock 和 Stub:**
- Mock 外部依赖
- 使用测试数据库
- 避免测试真实网络请求
4. **覆盖率:**
- 设置覆盖率目标(通常 > 80%)
- 定期检查覆盖率报告
- 关注关键路径的覆盖率
5. **持续集成:**
- 在 CI/CD 流程中运行测试
- 测试失败时阻止部署
- 自动生成覆盖率报告
6. **测试数据:**
- 使用工厂模式创建测试数据
- 使用固定的测试数据
- 清理测试数据避免污染
```javascript
// 测试数据工厂
const userFactory = (overrides = {}) => ({
name: 'Test User',
email: 'test@example.com',
password: 'password123',
...overrides
});
// 使用工厂
test('should create user', async () => {
const userData = userFactory({ name: 'Custom Name' });
const response = await request(app.callback())
.post('/api/users')
.send(userData)
.expect(201);
expect(response.body.name).toBe('Custom Name');
});
```
服务端 · 2月21日 15:54
Koa 路由系统的使用方法和最佳实践Koa 核心不包含路由功能,需要通过中间件实现。最常用的路由中间件是 `@koa/router`,它提供了强大的路由定义和管理功能。
**安装路由中间件:**
```bash
npm install @koa/router
```
**基本路由使用:**
```javascript
const Koa = require('koa');
const Router = require('@koa/router');
const app = new Koa();
const router = new Router();
// 定义路由
router.get('/', async (ctx) => {
ctx.body = 'Hello Koa';
});
router.get('/users/:id', async (ctx) => {
const id = ctx.params.id;
ctx.body = `User ${id}`;
});
router.post('/users', async (ctx) => {
const user = ctx.request.body;
ctx.body = { success: true, user };
});
// 注册路由中间件
app.use(router.routes());
app.use(router.allowedMethods());
app.listen(3000);
```
**路由参数:**
1. **路径参数:**
```javascript
router.get('/users/:id', async (ctx) => {
const { id } = ctx.params;
ctx.body = `User ID: ${id}`;
});
// 多个参数
router.get('/posts/:postId/comments/:commentId', async (ctx) => {
const { postId, commentId } = ctx.params;
ctx.body = `Post: ${postId}, Comment: ${commentId}`;
});
```
2. **查询参数:**
```javascript
router.get('/search', async (ctx) => {
const { keyword, page, limit } = ctx.query;
ctx.body = { keyword, page, limit };
});
```
3. **正则表达式路由:**
```javascript
router.get(/^\/users\/(\d+)$/, async (ctx) => {
const id = ctx.params[0];
ctx.body = `User ID: ${id}`;
});
```
**路由方法:**
```javascript
router.get('/resource', handler); // GET
router.post('/resource', handler); // POST
router.put('/resource', handler); // PUT
router.delete('/resource', handler); // DELETE
router.patch('/resource', handler); // PATCH
router.all('/resource', handler); // 所有方法
```
**路由前缀:**
```javascript
const apiRouter = new Router({
prefix: '/api/v1'
});
apiRouter.get('/users', handler); // 实际路径: /api/v1/users
apiRouter.post('/login', handler); // 实际路径: /api/v1/login
app.use(apiRouter.routes());
```
**路由嵌套:**
```javascript
const userRouter = new Router({
prefix: '/users'
});
userRouter.get('/', async (ctx) => {
ctx.body = 'User list';
});
userRouter.get('/:id', async (ctx) => {
ctx.body = `User ${ctx.params.id}`;
});
const commentRouter = new Router({
prefix: '/:userId/comments'
});
commentRouter.get('/', async (ctx) => {
ctx.body = `Comments for user ${ctx.params.userId}`;
});
userRouter.use(commentRouter.routes());
app.use(userRouter.routes());
```
**路由中间件:**
```javascript
// 单个路由中间件
router.get('/protected', authMiddleware, async (ctx) => {
ctx.body = 'Protected content';
});
// 多个路由中间件
router.post('/admin',
authMiddleware,
adminMiddleware,
async (ctx) => {
ctx.body = 'Admin content';
}
);
// 路由级中间件
router.use(async (ctx, next) => {
console.log('Router middleware');
await next();
});
```
**路由分组和模块化:**
```javascript
// routes/users.js
const Router = require('@koa/router');
const router = new Router({ prefix: '/users' });
router.get('/', async (ctx) => {
ctx.body = 'User list';
});
router.get('/:id', async (ctx) => {
ctx.body = `User ${ctx.params.id}`;
});
module.exports = router;
// app.js
const userRoutes = require('./routes/users');
app.use(userRoutes.routes());
app.use(userRoutes.allowedMethods());
```
**路由命名和重定向:**
```javascript
router.get('user', '/users/:id', async (ctx) => {
ctx.body = `User ${ctx.params.id}`;
});
router.redirect('/old-path', '/new-path');
router.redirect('/old-path', 'user', { id: 123 });
```
**错误处理:**
```javascript
router.get('/error', async (ctx) => {
ctx.throw(404, 'User not found');
});
// 使用 allowedMethods 处理不允许的方法
app.use(router.allowedMethods({
throw: true,
notImplemented: () => new NotImplemented(),
methodNotAllowed: () => new MethodNotAllowed()
}));
```
**最佳实践:**
1. 按功能模块划分路由文件
2. 使用路由前缀组织 API 版本
3. 合理使用路由中间件进行权限控制
4. 统一错误处理和响应格式
5. 为路由添加描述性注释
6. 使用 TypeScript 增强类型安全
服务端 · 2月21日 15:54
Koa 文件上传功能的实现方法和最佳实践Koa 核心不包含文件上传功能,需要通过中间件实现。最常用的文件上传中间件是 `koa-body` 或 `koa-multer`,它们提供了强大的文件上传处理能力。
**1. 使用 koa-body 处理文件上传:**
**安装:**
```bash
npm install koa-body
```
**基本配置:**
```javascript
const koaBody = require('koa-body');
app.use(koaBody({
multipart: true, // 启用文件上传
formidable: {
maxFileSize: 100 * 1024 * 1024, // 最大文件大小 100MB
keepExtensions: true, // 保留文件扩展名
uploadDir: './uploads', // 上传目录
multiples: true // 支持多文件上传
}
}));
```
**单文件上传:**
```javascript
app.use(async (ctx) => {
const file = ctx.request.files.file;
if (!file) {
ctx.throw(400, 'No file uploaded');
}
// 获取文件信息
const fileInfo = {
name: file.name,
size: file.size,
path: file.path,
type: file.type,
lastModifiedDate: file.lastModifiedDate
};
ctx.body = {
message: 'File uploaded successfully',
file: fileInfo
};
});
```
**多文件上传:**
```javascript
app.use(async (ctx) => {
const files = ctx.request.files.files;
if (!files) {
ctx.throw(400, 'No files uploaded');
}
// 处理单个文件或多个文件
const fileList = Array.isArray(files) ? files : [files];
const uploadedFiles = fileList.map(file => ({
name: file.name,
size: file.size,
path: file.path,
type: file.type
}));
ctx.body = {
message: `${uploadedFiles.length} files uploaded`,
files: uploadedFiles
};
});
```
**2. 使用 koa-multer 处理文件上传:**
**安装:**
```bash
npm install koa-multer
```
**基本配置:**
```javascript
const multer = require('koa-multer');
// 存储配置
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, './uploads/');
},
filename: function (req, file, cb) {
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
cb(null, file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname));
}
});
const upload = multer({
storage: storage,
limits: {
fileSize: 100 * 1024 * 1024 // 100MB
},
fileFilter: function (req, file, cb) {
// 文件类型过滤
const allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
if (allowedTypes.includes(file.mimetype)) {
cb(null, true);
} else {
cb(new Error('Invalid file type'), false);
}
}
});
```
**单文件上传:**
```javascript
app.use(upload.single('file'));
app.use(async (ctx) => {
const file = ctx.req.file;
ctx.body = {
message: 'File uploaded successfully',
file: {
originalname: file.originalname,
filename: file.filename,
path: file.path,
size: file.size,
mimetype: file.mimetype
}
};
});
```
**多文件上传:**
```javascript
// 最多上传 10 个文件
app.use(upload.array('files', 10));
app.use(async (ctx) => {
const files = ctx.req.files;
ctx.body = {
message: `${files.length} files uploaded`,
files: files.map(file => ({
originalname: file.originalname,
filename: file.filename,
path: file.path,
size: file.size,
mimetype: file.mimetype
}))
};
});
```
**混合上传(文件 + 字段):**
```javascript
app.use(upload.fields([
{ name: 'avatar', maxCount: 1 },
{ name: 'documents', maxCount: 5 }
]));
app.use(async (ctx) => {
const files = ctx.req.files;
const body = ctx.req.body;
ctx.body = {
message: 'Files uploaded successfully',
avatar: files.avatar[0],
documents: files.documents,
data: body
};
});
```
**3. 文件上传安全措施:**
```javascript
const path = require('path');
const fs = require('fs');
app.use(koaBody({
multipart: true,
formidable: {
maxFileSize: 10 * 1024 * 1024, // 限制文件大小
keepExtensions: true,
uploadDir: './uploads',
filter: function ({ name, originalFilename, mimetype }) {
// 文件类型验证
const allowedTypes = [
'image/jpeg',
'image/png',
'image/gif',
'application/pdf'
];
return allowedTypes.includes(mimetype);
}
}
}));
// 文件验证中间件
async function validateFile(ctx, next) {
const file = ctx.request.files.file;
if (!file) {
ctx.throw(400, 'No file uploaded');
}
// 验证文件大小
const maxSize = 10 * 1024 * 1024; // 10MB
if (file.size > maxSize) {
// 删除已上传的文件
fs.unlinkSync(file.path);
ctx.throw(400, 'File size exceeds limit');
}
// 验证文件类型
const allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
if (!allowedTypes.includes(file.type)) {
fs.unlinkSync(file.path);
ctx.throw(400, 'Invalid file type');
}
// 验证文件扩展名
const ext = path.extname(file.name).toLowerCase();
const allowedExts = ['.jpg', '.jpeg', '.png', '.gif'];
if (!allowedExts.includes(ext)) {
fs.unlinkSync(file.path);
ctx.throw(400, 'Invalid file extension');
}
await next();
}
app.use(validateFile);
```
**4. 图片处理:**
使用 `sharp` 库处理上传的图片。
```bash
npm install sharp
```
```javascript
const sharp = require('sharp');
app.use(async (ctx) => {
const file = ctx.request.files.file;
if (!file) {
ctx.throw(400, 'No file uploaded');
}
// 生成缩略图
const thumbnailPath = file.path.replace(/(\.[\w\d]+)$/, '_thumb$1');
await sharp(file.path)
.resize(200, 200, {
fit: 'cover',
position: 'center'
})
.toFile(thumbnailPath);
// 压缩图片
const compressedPath = file.path.replace(/(\.[\w\d]+)$/, '_compressed$1');
await sharp(file.path)
.jpeg({ quality: 80 })
.toFile(compressedPath);
ctx.body = {
message: 'Image processed successfully',
original: file.path,
thumbnail: thumbnailPath,
compressed: compressedPath
};
});
```
**5. 分片上传:**
对于大文件,实现分片上传功能。
```javascript
const fs = require('fs');
const path = require('path');
app.use(async (ctx) => {
const { chunkIndex, totalChunks, fileId } = ctx.request.body;
const file = ctx.request.files.chunk;
const chunkDir = path.join('./uploads', fileId);
const chunkPath = path.join(chunkDir, `chunk_${chunkIndex}`);
// 创建分片目录
if (!fs.existsSync(chunkDir)) {
fs.mkdirSync(chunkDir, { recursive: true });
}
// 保存分片
const reader = fs.createReadStream(file.path);
const writer = fs.createWriteStream(chunkPath);
reader.pipe(writer);
// 检查是否所有分片都已上传
const uploadedChunks = fs.readdirSync(chunkDir).length;
if (uploadedChunks === parseInt(totalChunks)) {
// 合并分片
const finalPath = path.join('./uploads', `${fileId}${path.extname(file.name)}`);
const writeStream = fs.createWriteStream(finalPath);
for (let i = 0; i < totalChunks; i++) {
const chunkPath = path.join(chunkDir, `chunk_${i}`);
const chunkData = fs.readFileSync(chunkPath);
writeStream.write(chunkData);
fs.unlinkSync(chunkPath);
}
writeStream.end();
fs.rmdirSync(chunkDir);
ctx.body = {
message: 'File upload completed',
path: finalPath
};
} else {
ctx.body = {
message: `Chunk ${chunkIndex} uploaded`,
progress: `${uploadedChunks}/${totalChunks}`
};
}
});
```
**6. 文件上传最佳实践:**
1. **安全措施:**
- 限制文件大小
- 验证文件类型
- 验证文件扩展名
- 使用随机文件名
- 存储在非 Web 可访问目录
2. **性能优化:**
- 使用流式处理大文件
- 实现分片上传
- 使用 CDN 存储文件
- 异步处理文件
3. **用户体验:**
- 提供上传进度
- 支持断点续传
- 显示上传状态
- 提供预览功能
4. **错误处理:**
- 捕获上传错误
- 清理失败的文件
- 提供友好的错误信息
- 记录上传日志
服务端 · 2月21日 15:54
Koa 框架的核心设计理念和与 Express 的区别是什么Koa 是由 Express 原班人马打造的下一代 Node.js Web 框架,其核心设计理念是更轻量、更富有表现力、更健壮。与 Express 相比,Koa 最大的区别在于它不使用中间件链式调用,而是采用洋葱模型(Onion Model)的中间件机制。
Koa 的洋葱模型通过 async/await 实现中间件的执行流程,每个中间件可以控制下游中间件的执行,并在下游执行完成后继续处理上游逻辑。这种设计使得中间件的执行顺序更加清晰,错误处理更加优雅。
Koa 的核心特性包括:
1. 轻量级核心:Koa 核心只有约 2KB,不绑定任何中间件
2. 现代 JavaScript:全面支持 async/await,避免回调地狱
3. 上下文对象:将 request 和 response 封装为 ctx 对象,简化操作
4. 更好的错误处理:通过 try-catch 和事件机制统一处理错误
5. 中间件组合:支持中间件的灵活组合和复用
Koa 1.x 使用 Generator 函数,Koa 2.x 全面转向 async/await,这是 Koa 2 的重大升级点。Koa 的设计哲学是"做更少的事情",将更多功能交给社区中间件实现。
在实际应用中,Koa 适合构建高性能的 Web 应用和 API 服务,特别是在需要精细控制请求处理流程的场景下表现优异。
服务端 · 2月21日 15:54
如何开发自定义 Koa 中间件以及常见中间件类型Koa 中间件是一个 async 函数,接收两个参数:ctx(上下文对象)和 next(下一个中间件的调用函数)。开发自定义中间件需要遵循特定的模式和最佳实践。
**中间件基本结构:**
```javascript
async function myMiddleware(ctx, next) {
// 前置逻辑
console.log('请求进入');
// 调用下一个中间件
await next();
// 后置逻辑
console.log('请求处理完成');
}
// 使用中间件
app.use(myMiddleware);
```
**常见中间件类型:**
1. **日志中间件:**
```javascript
function loggerMiddleware(ctx, next) {
const start = Date.now();
await next();
const ms = Date.now() - start;
console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
}
```
2. **认证中间件:**
```javascript
async function authMiddleware(ctx, next) {
const token = ctx.headers.authorization;
if (!token) {
ctx.throw(401, 'Unauthorized');
}
try {
const user = await verifyToken(token);
ctx.state.user = user;
await next();
} catch (error) {
ctx.throw(401, 'Invalid token');
}
}
```
3. **错误处理中间件:**
```javascript
async function errorHandler(ctx, next) {
try {
await next();
} catch (err) {
ctx.status = err.status || 500;
ctx.body = {
error: err.message,
code: err.code
};
ctx.app.emit('error', err, ctx);
}
}
```
4. **CORS 中间件:**
```javascript
async function corsMiddleware(ctx, next) {
ctx.set('Access-Control-Allow-Origin', '*');
ctx.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
ctx.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
if (ctx.method === 'OPTIONS') {
ctx.status = 204;
return;
}
await next();
}
```
5. **请求体解析中间件:**
```javascript
async function bodyParser(ctx, next) {
if (ctx.method !== 'POST' && ctx.method !== 'PUT') {
return await next();
}
const chunks = [];
for await (const chunk of ctx.req) {
chunks.push(chunk);
}
const body = Buffer.concat(chunks).toString();
ctx.request.body = JSON.parse(body);
await next();
}
```
**中间件开发最佳实践:**
1. **命名规范:** 使用描述性的函数名,如 `authMiddleware`、`loggerMiddleware`
2. **错误处理:** 使用 try-catch 捕获错误,避免影响其他中间件
3. **性能优化:** 避免在中间件中执行耗时操作
4. **配置化:** 支持配置参数,提高中间件灵活性
5. **文档完善:** 提供清晰的使用文档和示例
**可配置中间件示例:**
```javascript
function createLogger(options = {}) {
const {
format = 'default',
includeQuery = false
} = options;
return async function logger(ctx, next) {
const start = Date.now();
await next();
const ms = Date.now() - start;
let log = `${ctx.method} ${ctx.url} - ${ms}ms`;
if (includeQuery && Object.keys(ctx.query).length) {
log += ` ${JSON.stringify(ctx.query)}`;
}
console.log(log);
};
}
// 使用配置化中间件
app.use(createLogger({ includeQuery: true }));
```
**中间件组合:**
```javascript
const compose = require('koa-compose');
const middleware = compose([
loggerMiddleware,
authMiddleware,
errorHandler
]);
app.use(middleware);
```
开发高质量中间件的关键是理解洋葱模型的执行流程,合理使用前置和后置逻辑,并确保中间件的独立性和可复用性。
服务端 · 2月21日 15:54
Koa 与 Express 框架的详细对比和选择建议Koa 与 Express 是两个流行的 Node.js Web 框架,它们各有特点和适用场景。理解它们的差异有助于在实际项目中做出正确的选择。
**1. 核心设计理念:**
**Express:**
- 内置大量功能(路由、中间件、模板引擎等)
- 提供开箱即用的解决方案
- 采用传统的回调函数模式
- 中间件链式调用
**Koa:**
- 极简核心,只提供最基础的功能
- 通过中间件扩展功能
- 采用现代 async/await 模式
- 洋葱模型中间件机制
**2. 中间件机制对比:**
**Express 中间件:**
```javascript
const express = require('express');
const app = express();
app.use((req, res, next) => {
console.log('Middleware 1');
next();
console.log('Middleware 1 after');
});
app.use((req, res, next) => {
console.log('Middleware 2');
res.send('Hello Express');
});
// 执行顺序:Middleware 1 -> Middleware 2 -> Middleware 1 after
```
**Koa 中间件:**
```javascript
const Koa = require('koa');
const app = new Koa();
app.use(async (ctx, next) => {
console.log('Middleware 1 before');
await next();
console.log('Middleware 1 after');
});
app.use(async (ctx, next) => {
console.log('Middleware 2 before');
await next();
console.log('Middleware 2 after');
ctx.body = 'Hello Koa';
});
// 执行顺序:Middleware 1 before -> Middleware 2 before ->
// Middleware 2 after -> Middleware 1 after
```
**3. 代码风格对比:**
**Express 回调风格:**
```javascript
app.get('/users/:id', (req, res, next) => {
User.findById(req.params.id, (err, user) => {
if (err) return next(err);
Post.findByUserId(user.id, (err, posts) => {
if (err) return next(err);
res.json({ user, posts });
});
});
});
```
**Koa async/await 风格:**
```javascript
app.get('/users/:id', async (ctx) => {
const user = await User.findById(ctx.params.id);
const posts = await Post.findByUserId(user.id);
ctx.body = { user, posts };
});
```
**4. 请求/响应处理对比:**
**Express:**
```javascript
app.get('/', (req, res) => {
// 请求信息
const url = req.url;
const method = req.method;
const query = req.query;
const body = req.body;
// 响应设置
res.status(200);
res.json({ message: 'Hello' });
// 或
res.send('Hello');
// 或
res.render('index', { title: 'Hello' });
});
```
**Koa:**
```javascript
app.get('/', async (ctx) => {
// 请求信息
const url = ctx.url;
const method = ctx.method;
const query = ctx.query;
const body = ctx.request.body;
// 响应设置
ctx.status = 200;
ctx.body = { message: 'Hello' };
// 或
ctx.type = 'text/html';
ctx.body = '<h1>Hello</h1>';
});
```
**5. 错误处理对比:**
**Express 错误处理:**
```javascript
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({
error: err.message
});
});
// 抛出错误
app.get('/error', (req, res, next) => {
const err = new Error('Something went wrong');
err.status = 500;
next(err);
});
```
**Koa 错误处理:**
```javascript
app.use(async (ctx, next) => {
try {
await next();
} catch (err) {
ctx.status = err.status || 500;
ctx.body = {
error: err.message
};
ctx.app.emit('error', err, ctx);
}
});
// 抛出错误
app.get('/error', async (ctx) => {
ctx.throw(500, 'Something went wrong');
});
```
**6. 路由功能对比:**
**Express 内置路由:**
```javascript
const express = require('express');
const router = express.Router();
router.get('/users', getUsers);
router.post('/users', createUser);
router.get('/users/:id', getUser);
router.put('/users/:id', updateUser);
router.delete('/users/:id', deleteUser);
app.use('/api', router);
```
**Koa 需要路由中间件:**
```javascript
const Router = require('@koa/router');
const router = new Router();
router.get('/users', getUsers);
router.post('/users', createUser);
router.get('/users/:id', getUser);
router.put('/users/:id', updateUser);
router.delete('/users/:id', deleteUser);
app.use(router.routes());
app.use(router.allowedMethods());
```
**7. 性能对比:**
**Express:**
- 成熟稳定,经过大量生产环境验证
- 中间件链式调用,性能相对较低
- 回调函数,可能存在回调地狱
- 内存占用相对较高
**Koa:**
- 更轻量级,核心只有约 2KB
- async/await,代码更简洁
- 洋葱模型,中间件控制更灵活
- 内存占用相对较低
**8. 学习曲线对比:**
**Express:**
- 文档丰富,社区活跃
- 学习曲线平缓
- 大量教程和示例
- 适合初学者
**Koa:**
- 需要理解 async/await
- 需要理解洋葱模型
- 需要选择合适的中间件
- 适合有一定经验的开发者
**9. 适用场景对比:**
**Express 适合:**
- 快速开发原型
- 传统 Web 应用
- 需要大量内置功能的项目
- 团队成员对 async/await 不熟悉
- 需要稳定成熟的框架
**Koa 适合:**
- 现代 Web 应用
- 需要精细控制中间件的项目
- 追求代码简洁和可维护性
- 团队熟悉现代 JavaScript
- 需要更好的错误处理
**10. 迁移建议:**
从 Express 迁移到 Koa:
```javascript
// Express
app.get('/users/:id', async (req, res, next) => {
try {
const user = await User.findById(req.params.id);
res.json(user);
} catch (err) {
next(err);
}
});
// Koa
app.get('/users/:id', async (ctx) => {
const user = await User.findById(ctx.params.id);
ctx.body = user;
});
```
**总结:**
| 特性 | Express | Koa |
|------|---------|-----|
| 核心大小 | 较大 | 极小(2KB) |
| 中间件模式 | 链式调用 | 洋葱模型 |
| 异步处理 | 回调函数 | async/await |
| 路由 | 内置 | 需要中间件 |
| 学习曲线 | 平缓 | 较陡 |
| 社区生态 | 成熟 | 快速发展 |
| 性能 | 良好 | 优秀 |
| 适用场景 | 传统应用 | 现代应用 |
选择建议:
- 如果追求快速开发和稳定性,选择 Express
- 如果追求代码质量和现代化,选择 Koa
- 如果团队熟悉 async/await,优先选择 Koa
- 如果需要大量内置功能,选择 Express
服务端 · 2月21日 15:54
Koa 洋葱模型的执行机制和实际应用场景有哪些Koa 的洋葱模型(Onion Model)是其最核心的设计特性,通过 async/await 实现中间件的执行流程控制。在洋葱模型中,中间件按照注册顺序执行,形成类似洋葱的层级结构。
执行流程如下:
1. 请求从外层中间件进入,逐层向内传递
2. 每个中间件在 await next() 之前执行"前置逻辑"
3. 执行 await next() 进入下一层中间件
4. 到达最内层后,开始逐层向外返回
5. 每个中间件在 await next() 之后执行"后置逻辑"
6. 最终响应从最外层中间件返回客户端
代码示例:
```javascript
const Koa = require('koa');
const app = new Koa();
app.use(async (ctx, next) => {
console.log('1 - 前置');
await next();
console.log('1 - 后置');
});
app.use(async (ctx, next) => {
console.log('2 - 前置');
await next();
console.log('2 - 后置');
});
app.use(async (ctx) => {
console.log('3 - 核心处理');
ctx.body = 'Hello Koa';
});
// 执行顺序:1-前置 -> 2-前置 -> 3-核心处理 -> 2-后置 -> 1-后置
```
洋葱模型的优势:
1. 清晰的执行顺序:前置和后置逻辑分离,代码结构清晰
2. 灵活的控制:每个中间件可以决定是否继续执行下游
3. 统一的错误处理:通过 try-catch 可以捕获所有中间件的错误
4. 中间件复用:可以在不同位置复用中间件逻辑
5. 请求/响应处理:方便在请求进入和响应返回时执行不同逻辑
实际应用场景:
- 日志记录:在前后置逻辑中记录请求和响应信息
- 错误处理:在外层中间件统一捕获和处理错误
- 认证授权:在前置逻辑中验证用户身份
- 响应时间统计:计算请求处理总耗时
- 响应格式化:在后置逻辑中统一处理响应格式
服务端 · 2月21日 15:53
Koa 错误处理机制和最佳实践详解Koa 的错误处理机制非常优雅,通过 try-catch 和事件系统提供了多种错误处理方式。正确处理错误是构建健壮应用的关键。
**1. 使用 ctx.throw() 抛出错误:**
```javascript
app.use(async (ctx) => {
if (!ctx.query.token) {
ctx.throw(401, 'Token is required');
}
ctx.body = 'Success';
});
```
**2. 使用 try-catch 捕获错误:**
```javascript
app.use(async (ctx, next) => {
try {
await next();
} catch (err) {
ctx.status = err.status || 500;
ctx.body = {
error: err.message,
code: err.code || 'INTERNAL_ERROR'
};
ctx.app.emit('error', err, ctx);
}
});
```
**3. 错误处理中间件:**
```javascript
async function errorHandler(ctx, next) {
try {
await next();
} catch (err) {
ctx.status = err.status || 500;
// 开发环境返回详细错误信息
if (app.env === 'development') {
ctx.body = {
error: err.message,
stack: err.stack,
code: err.code
};
} else {
// 生产环境返回简化的错误信息
ctx.body = {
error: 'Internal Server Error',
code: 'INTERNAL_ERROR'
};
}
// 触发应用错误事件
ctx.app.emit('error', err, ctx);
}
}
app.use(errorHandler);
```
**4. 自定义错误类:**
```javascript
class AppError extends Error {
constructor(status, message, code) {
super(message);
this.status = status;
this.code = code;
this.name = 'AppError';
}
}
class NotFoundError extends AppError {
constructor(message = 'Resource not found') {
super(404, message, 'NOT_FOUND');
this.name = 'NotFoundError';
}
}
class ValidationError extends AppError {
constructor(message = 'Validation failed') {
super(400, message, 'VALIDATION_ERROR');
this.name = 'ValidationError';
}
}
// 使用自定义错误
app.use(async (ctx) => {
const user = await findUser(ctx.params.id);
if (!user) {
throw new NotFoundError('User not found');
}
ctx.body = user;
});
```
**5. 全局错误监听:**
```javascript
app.on('error', (err, ctx) => {
// 记录错误日志
console.error('Server error:', err);
// 发送错误通知(如邮件、Slack等)
sendErrorNotification(err, ctx);
// 上报错误到监控系统
reportToMonitoring(err, ctx);
});
```
**6. 404 处理:**
```javascript
// 在所有路由之后添加 404 处理
app.use(async (ctx) => {
ctx.status = 404;
ctx.body = {
error: 'Not Found',
code: 'NOT_FOUND',
path: ctx.url
};
});
```
**7. 异步错误处理:**
```javascript
// Koa 会自动捕获 async 函数中的错误
app.use(async (ctx) => {
const data = await fetchData(); // 如果这里抛出错误,会被捕获
ctx.body = data;
});
// 对于 Promise 链,确保正确处理
app.use(async (ctx) => {
try {
const result = await someAsyncOperation()
.then(data => processData(data))
.catch(err => {
throw new AppError(400, 'Processing failed', 'PROCESS_ERROR');
});
ctx.body = result;
} catch (err) {
ctx.throw(err.status || 500, err.message);
}
});
```
**8. 错误处理最佳实践:**
```javascript
// 完整的错误处理示例
const Koa = require('koa');
const app = new Koa();
// 自定义错误类
class AppError extends Error {
constructor(status, message, code) {
super(message);
this.status = status;
this.code = code;
}
}
// 错误处理中间件
app.use(async (ctx, next) => {
try {
await next();
} catch (err) {
// 设置状态码
ctx.status = err.status || 500;
// 构建错误响应
const errorResponse = {
error: err.message,
code: err.code || 'INTERNAL_ERROR',
timestamp: new Date().toISOString()
};
// 开发环境包含堆栈信息
if (app.env === 'development') {
errorResponse.stack = err.stack;
}
ctx.body = errorResponse;
// 触发错误事件
ctx.app.emit('error', err, ctx);
}
});
// 全局错误监听
app.on('error', (err, ctx) => {
console.error(`[${new Date().toISOString()}] Error:`, err.message);
console.error('Path:', ctx.url);
console.error('Stack:', err.stack);
});
// 业务路由
app.use(async (ctx) => {
if (ctx.path === '/error') {
throw new AppError(500, 'Something went wrong', 'SERVER_ERROR');
}
if (ctx.path === '/not-found') {
ctx.throw(404, 'Resource not found');
}
ctx.body = 'Hello Koa';
});
app.listen(3000);
```
**9. 常见错误处理场景:**
```javascript
// 数据库错误处理
app.use(async (ctx, next) => {
try {
await next();
} catch (err) {
if (err.code === '23505') { // PostgreSQL 唯一约束冲突
ctx.throw(409, 'Resource already exists');
} else if (err.code === '23503') { // 外键约束冲突
ctx.throw(400, 'Invalid reference');
} else {
throw err;
}
}
});
// 验证错误处理
app.use(async (ctx, next) => {
try {
await next();
} catch (err) {
if (err.name === 'ValidationError') {
ctx.throw(400, err.message);
}
throw err;
}
});
```
**错误处理要点:**
1. 使用 try-catch 包裹可能出错的代码
2. 创建自定义错误类,提供更清晰的错误信息
3. 区分开发环境和生产环境的错误响应
4. 实现全局错误监听,统一记录和上报
5. 为不同类型的错误提供适当的 HTTP 状态码
6. 确保错误不会泄露敏感信息
7. 在洋葱模型的最外层添加错误处理中间件
服务端 · 2月21日 15:53
Koa 中 Cookie 和 Session 管理的实现方法Koa 的 Cookie 和 Session 管理是构建 Web 应用的基础功能,Koa 核心提供了 Cookie 操作,而 Session 需要通过中间件实现。
**1. Cookie 操作:**
Koa 核心内置了 Cookie 功能,通过 `ctx.cookies` 对象进行操作。
**设置 Cookie:**
```javascript
app.use(async (ctx) => {
// 基本设置
ctx.cookies.set('name', 'value');
// 带选项的设置
ctx.cookies.set('username', 'john', {
maxAge: 3600000, // 有效期(毫秒)
expires: new Date('2025-12-31'), // 过期时间
path: '/', // 路径
domain: '.example.com', // 域名
secure: true, // 仅 HTTPS
httpOnly: true, // 仅 HTTP,防止 XSS
sameSite: 'strict', // CSRF 保护
signed: true // 签名 Cookie
});
ctx.body = 'Cookie set';
});
```
**获取 Cookie:**
```javascript
app.use(async (ctx) => {
const username = ctx.cookies.get('username');
ctx.body = `Hello ${username}`;
});
```
**删除 Cookie:**
```javascript
app.use(async (ctx) => {
ctx.cookies.set('username', null, {
maxAge: 0,
path: '/'
});
ctx.body = 'Cookie deleted';
});
```
**2. Session 管理:**
使用 `koa-session` 中间件实现 Session 功能。
**安装:**
```bash
npm install koa-session
```
**基本配置:**
```javascript
const session = require('koa-session');
const sessionConfig = {
key: 'koa.sess', // Cookie 名称
maxAge: 86400000, // 有效期(毫秒)
autoCommit: true, // 自动提交
overwrite: true, // 覆盖
httpOnly: true, // 仅 HTTP
signed: true, // 签名
rolling: false, // 每次请求更新过期时间
renew: false, // 快过期时自动续期
secure: false, // 仅 HTTPS
sameSite: null, // SameSite 策略
};
app.keys = ['your-secret-key']; // 必须设置用于签名
app.use(session(sessionConfig, app));
```
**Session 使用:**
```javascript
// 设置 Session
app.use(async (ctx) => {
if (ctx.path === '/login') {
ctx.session.user = {
id: 1,
name: 'John',
role: 'admin'
};
ctx.body = 'Logged in';
}
});
// 获取 Session
app.use(async (ctx) => {
if (ctx.path === '/profile') {
const user = ctx.session.user;
if (user) {
ctx.body = `Welcome ${user.name}`;
} else {
ctx.throw(401, 'Not logged in');
}
}
});
// 删除 Session
app.use(async (ctx) => {
if (ctx.path === '/logout') {
ctx.session = null;
ctx.body = 'Logged out';
}
});
```
**3. Redis Session 存储:**
对于生产环境,建议使用 Redis 存储 Session。
**安装:**
```bash
npm install koa-session koa-redis
```
**配置 Redis Session:**
```javascript
const session = require('koa-session');
const RedisStore = require('koa-redis');
const redisStore = RedisStore({
host: 'localhost',
port: 6379,
password: 'your-password',
db: 0
});
const sessionConfig = {
store: redisStore,
key: 'koa.sess',
maxAge: 86400000,
httpOnly: true,
signed: true
};
app.keys = ['your-secret-key'];
app.use(session(sessionConfig, app));
```
**4. 认证中间件示例:**
```javascript
// 认证中间件
async function authMiddleware(ctx, next) {
if (!ctx.session.user) {
ctx.throw(401, 'Unauthorized');
}
await next();
}
// 使用认证中间件
router.get('/protected', authMiddleware, async (ctx) => {
ctx.body = `Welcome ${ctx.session.user.name}`;
});
```
**5. JWT Token 认证:**
使用 `jsonwebtoken` 和 `koa-jwt` 实现 JWT 认证。
**安装:**
```bash
npm install jsonwebtoken koa-jwt
```
**生成 Token:**
```javascript
const jwt = require('jsonwebtoken');
app.use(async (ctx) => {
if (ctx.path === '/login') {
const { username, password } = ctx.request.body;
// 验证用户
const user = await authenticateUser(username, password);
// 生成 Token
const token = jwt.sign(
{ id: user.id, name: user.name },
'your-secret-key',
{ expiresIn: '24h' }
);
ctx.body = { token };
}
});
```
**验证 Token:**
```javascript
const jwt = require('koa-jwt');
app.use(jwt({
secret: 'your-secret-key'
}).unless({
path: [/^\/public/, '/login', '/register']
}));
// 访问用户信息
app.use(async (ctx) => {
ctx.body = ctx.state.user;
});
```
**6. 完整的认证流程示例:**
```javascript
const Koa = require('koa');
const Router = require('@koa/router');
const session = require('koa-session');
const jwt = require('jsonwebtoken');
const koaJwt = require('koa-jwt');
const app = new Koa();
const router = new Router();
// Session 配置
app.keys = ['secret-key'];
app.use(session({
key: 'koa.sess',
maxAge: 86400000
}, app));
// JWT 中间件
app.use(koaJwt({
secret: 'jwt-secret'
}).unless({
path: [/^\/api\/auth/]
}));
// 登录路由
router.post('/api/auth/login', async (ctx) => {
const { username, password } = ctx.request.body;
// 验证用户
const user = await User.findOne({ username });
if (!user || !await user.comparePassword(password)) {
ctx.throw(401, 'Invalid credentials');
}
// 设置 Session
ctx.session.user = {
id: user.id,
name: user.name
};
// 生成 JWT Token
const token = jwt.sign(
{ id: user.id, name: user.name },
'jwt-secret',
{ expiresIn: '24h' }
);
ctx.body = {
token,
user: {
id: user.id,
name: user.name
}
};
});
// 受保护的路由
router.get('/api/user/profile', async (ctx) => {
ctx.body = ctx.state.user;
});
// 登出路由
router.post('/api/auth/logout', async (ctx) => {
ctx.session = null;
ctx.body = { message: 'Logged out' };
});
app.use(router.routes());
```
**7. 安全最佳实践:**
1. **Cookie 安全:**
- 始终设置 `httpOnly: true` 防止 XSS
- 生产环境使用 `secure: true` 仅 HTTPS
- 设置 `sameSite: 'strict'` 防止 CSRF
- 使用签名 Cookie 防止篡改
2. **Session 安全:**
- 使用强随机密钥
- 设置合理的过期时间
- 生产环境使用 Redis 存储
- 登出时清除 Session
3. **JWT 安全:**
- 使用强密钥
- 设置合理的过期时间
- 使用 HTTPS 传输
- 实现 Token 刷新机制
4. **其他安全措施:**
- 限制登录尝试次数
- 实现密码强度验证
- 记录认证日志
- 定期更新密钥
服务端 · 2月21日 15:53