乐闻世界logo
搜索文章和话题

面试题手册

Koa 文件上传功能的实现方法和最佳实践

Koa 核心不包含文件上传功能,需要通过中间件实现。最常用的文件上传中间件是 koa-body 或 koa-multer,它们提供了强大的文件上传处理能力。1. 使用 koa-body 处理文件上传:安装:npm install koa-body基本配置:const koaBody = require('koa-body');app.use(koaBody({ multipart: true, // 启用文件上传 formidable: { maxFileSize: 100 * 1024 * 1024, // 最大文件大小 100MB keepExtensions: true, // 保留文件扩展名 uploadDir: './uploads', // 上传目录 multiples: true // 支持多文件上传 }}));单文件上传: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 };});多文件上传: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 处理文件上传:安装:npm install koa-multer基本配置: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); } }});单文件上传: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 } };});多文件上传:// 最多上传 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 })) };});混合上传(文件 + 字段):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. 文件上传安全措施: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 库处理上传的图片。npm install sharpconst 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. 分片上传:对于大文件,实现分片上传功能。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. 文件上传最佳实践:安全措施:限制文件大小验证文件类型验证文件扩展名使用随机文件名存储在非 Web 可访问目录性能优化:使用流式处理大文件实现分片上传使用 CDN 存储文件异步处理文件用户体验:提供上传进度支持断点续传显示上传状态提供预览功能错误处理:捕获上传错误清理失败的文件提供友好的错误信息记录上传日志
阅读 0·2月21日 15:54

如何开发自定义 Koa 中间件以及常见中间件类型

Koa 中间件是一个 async 函数,接收两个参数:ctx(上下文对象)和 next(下一个中间件的调用函数)。开发自定义中间件需要遵循特定的模式和最佳实践。中间件基本结构:async function myMiddleware(ctx, next) { // 前置逻辑 console.log('请求进入'); // 调用下一个中间件 await next(); // 后置逻辑 console.log('请求处理完成');}// 使用中间件app.use(myMiddleware);常见中间件类型:日志中间件:function loggerMiddleware(ctx, next) { const start = Date.now(); await next(); const ms = Date.now() - start; console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);}认证中间件: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'); }}错误处理中间件: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); }}CORS 中间件: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();}请求体解析中间件: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();}中间件开发最佳实践:命名规范: 使用描述性的函数名,如 authMiddleware、loggerMiddleware错误处理: 使用 try-catch 捕获错误,避免影响其他中间件性能优化: 避免在中间件中执行耗时操作配置化: 支持配置参数,提高中间件灵活性文档完善: 提供清晰的使用文档和示例可配置中间件示例: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 }));中间件组合:const compose = require('koa-compose');const middleware = compose([ loggerMiddleware, authMiddleware, errorHandler]);app.use(middleware);开发高质量中间件的关键是理解洋葱模型的执行流程,合理使用前置和后置逻辑,并确保中间件的独立性和可复用性。
阅读 0·2月21日 15:54

Koa 与 Express 框架的详细对比和选择建议

Koa 与 Express 是两个流行的 Node.js Web 框架,它们各有特点和适用场景。理解它们的差异有助于在实际项目中做出正确的选择。1. 核心设计理念:Express:内置大量功能(路由、中间件、模板引擎等)提供开箱即用的解决方案采用传统的回调函数模式中间件链式调用Koa:极简核心,只提供最基础的功能通过中间件扩展功能采用现代 async/await 模式洋葱模型中间件机制2. 中间件机制对比:Express 中间件: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 afterKoa 中间件: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 after3. 代码风格对比:Express 回调风格: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 风格: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: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: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 错误处理: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 错误处理: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 内置路由: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 需要路由中间件: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:更轻量级,核心只有约 2KBasync/await,代码更简洁洋葱模型,中间件控制更灵活内存占用相对较低8. 学习曲线对比:Express:文档丰富,社区活跃学习曲线平缓大量教程和示例适合初学者Koa:需要理解 async/await需要理解洋葱模型需要选择合适的中间件适合有一定经验的开发者9. 适用场景对比:Express 适合:快速开发原型传统 Web 应用需要大量内置功能的项目团队成员对 async/await 不熟悉需要稳定成熟的框架Koa 适合:现代 Web 应用需要精细控制中间件的项目追求代码简洁和可维护性团队熟悉现代 JavaScript需要更好的错误处理10. 迁移建议:从 Express 迁移到 Koa:// Expressapp.get('/users/:id', async (req, res, next) => { try { const user = await User.findById(req.params.id); res.json(user); } catch (err) { next(err); }});// Koaapp.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
阅读 0·2月21日 15:54

Kubernetes Ingress 是什么?它如何实现外部访问集群内服务?

Kubernetes Ingress 是一种 API 对象,用于管理外部访问集群内服务的规则,通常是 HTTP 和 HTTPS 路由。Ingress 提供了基于域名和路径的路由、TLS 终止等功能。Ingress 的作用路由规则:根据域名和路径将流量路由到不同的 Service负载均衡:在多个 Service 实例之间分发流量SSL/TLS 终止:在 Ingress 层面处理 HTTPS,简化后端配置基于名称的虚拟主机:支持多个域名指向同一个集群路径重写:支持 URL 路径重写Ingress ControllerIngress Controller 是实现 Ingress 功能的组件,它监听 Ingress 资源的变化并配置负载均衡器。常见的 Ingress ControllerNGINX Ingress Controller:最流行的 Ingress Controller基于 NGINX/OpenResty功能丰富,性能优秀支持高级路由、限流、认证等Traefik:云原生设计自动服务发现支持 Let's Encrypt 自动证书配置简单HAProxy Ingress:基于 HAProxy高性能支持高级负载均衡算法Istio Gateway:服务网格的一部分支持高级流量管理集成 mTLS、流量镜像等AWS ALB Ingress Controller:专门为 AWS 设计使用 AWS Application Load Balancer原生集成 AWS 服务Ingress 资源示例基本路由apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: simple-ingress annotations: nginx.ingress.kubernetes.io/rewrite-target: /spec: rules: - host: example.com http: paths: - path: /app1 pathType: Prefix backend: service: name: app1-service port: number: 80 - path: /app2 pathType: Prefix backend: service: name: app2-service port: number: 80TLS 配置apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: tls-ingressspec: tls: - hosts: - secure.example.com secretName: tls-secret rules: - host: secure.example.com http: paths: - path: / pathType: Prefix backend: service: name: secure-service port: number: 443默认后端apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: default-backend-ingressspec: defaultBackend: service: name: default-service port: number: 80 rules: - host: example.com http: paths: - path: /api pathType: Prefix backend: service: name: api-service port: number: 80Ingress 注解(Annotations)注解用于配置 Ingress Controller 的特定行为。NGINX Ingress Controller 常用注解重写路径:nginx.ingress.kubernetes.io/rewrite-target: /$2启用 SSL 重定向:nginx.ingress.kubernetes.io/ssl-redirect: "true"限流配置:nginx.ingress.kubernetes.io/limit-rps: "10"nginx.ingress.kubernetes.io/limit-connections: "5"CORS 配置:nginx.ingress.kubernetes.io/enable-cors: "true"nginx.ingress.kubernetes.io/cors-allow-origin: "*"认证配置:nginx.ingress.kubernetes.io/auth-type: basicnginx.ingress.kubernetes.io/auth-secret: basic-auth自定义错误页面:nginx.ingress.kubernetes.io/custom-http-errors: "404,503"Ingress vs Service| 特性 | Ingress | Service ||------|---------|---------|| 协议 | HTTP/HTTPS | TCP/UDP || 路由 | 基于域名和路径 | 基于端口 || 负载均衡 | L7(应用层) | L4(传输层) || SSL 终止 | 支持 | 不支持 || 使用场景 | Web 应用 | 通用服务 |Ingress vs LoadBalancer| 特性 | Ingress | LoadBalancer ||------|---------|--------------|| 成本 | 低(共享 IP) | 高(每个 Service 一个 IP) || 路由能力 | 强(域名、路径) | 弱(仅端口) || SSL 终止 | 支持 | 部分支持 || 适用场景 | 多个 HTTP/HTTPS 服务 | 少量服务或非 HTTP 服务 |部署 Ingress Controller部署 NGINX Ingress Controller# 添加 Helm 仓库helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginxhelm repo update# 安装helm install ingress-nginx ingress-nginx/ingress-nginx \ --namespace ingress-nginx \ --create-namespace验证安装kubectl get pods -n ingress-nginxkubectl get svc -n ingress-nginx最佳实践使用命名空间隔离:将 Ingress Controller 部署在独立的命名空间配置资源限制:为 Ingress Controller 设置合理的 CPU 和内存限制启用监控:监控 Ingress Controller 的性能指标使用 TLS:为生产环境配置 TLS 证书配置健康检查:确保后端 Service 的健康检查正常使用注解优化:根据应用需求配置合适的注解备份配置:定期备份 Ingress 配置版本管理:跟踪 Ingress Controller 的版本更新故障排查查看 Ingress 状态:kubectl get ingresskubectl describe ingress <ingress-name>查看 Ingress Controller 日志:kubectl logs -n ingress-nginx <pod-name>测试 DNS 解析:nslookup example.com检查 Service 和 Endpoint:kubectl get svckubectl get endpoints验证证书:kubectl get secret tls-secret -o yaml
阅读 0·2月21日 15:53

Kubernetes Deployment 的作用是什么?它如何实现滚动更新和回滚?

Kubernetes Deployment 是用于管理 Pod 和 ReplicaSet 的声明式更新控制器,它提供了声明式的应用部署和更新能力。Deployment 的核心功能声明式管理:通过 YAML 文件定义期望状态,Kubernetes 自动实现当前状态到期望状态的转换。滚动更新:支持零停机时间的应用更新,逐步替换旧版本的 Pod。回滚能力:可以轻松回滚到之前的版本,支持查看更新历史。自动扩缩容:支持手动或自动调整 Pod 的副本数量。自愈能力:当 Pod 故障或被删除时,自动创建新的 Pod 以维持期望的副本数。Deployment 的工作原理Deployment 通过 ReplicaSet 管理 Pod:ReplicaSet:确保指定数量的 Pod 副本始终运行。Pod 模板:定义 Pod 的规格,包括容器镜像、资源限制等。更新策略:控制滚动更新的行为,如最大不可用 Pod 数量、最大激增 Pod 数量。Deployment 的 YAML 示例apiVersion: apps/v1kind: Deploymentmetadata: name: nginx-deploymentspec: replicas: 3 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.14.2 ports: - containerPort: 80更新策略Deployment 支持两种更新策略:RollingUpdate(默认):逐步替换 Pod,确保始终有可用 Pod可配置 maxUnavailable 和 maxSurge适用于大多数应用Recreate:先删除所有旧 Pod,再创建新 Pod会导致短暂的服务中断适用于无法同时运行新旧版本的应用滚动更新参数maxUnavailable:更新过程中最多允许多少个 Pod 不可用(默认 25%)maxSurge:更新过程中最多允许多少个额外的 Pod(默认 25%)回滚操作查看更新历史:kubectl rollout history deployment/nginx-deployment回滚到上一个版本:kubectl rollout undo deployment/nginx-deployment回滚到指定版本:kubectl rollout undo deployment/nginx-deployment --to-revision=2扩缩容手动扩缩容:kubectl scale deployment/nginx-deployment --replicas=5自动扩缩容(HPA):apiVersion: autoscaling/v2kind: HorizontalPodAutoscalermetadata: name: nginx-hpaspec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: nginx-deployment minReplicas: 2 maxReplicas: 10 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 50Deployment 与其他控制器的区别vs ReplicaSet:Deployment 是 ReplicaSet 的上层控制器Deployment 提供更新和回滚能力,ReplicaSet 只能维持副本数vs StatefulSet:Deployment 适用于无状态应用StatefulSet 适用于有状态应用,提供稳定的网络标识和持久化存储vs DaemonSet:Deployment 可以在任意节点运行指定数量的 PodDaemonSet 确保在每个节点上运行一个 Pod 副本最佳实践使用声明式配置:始终使用 YAML 文件定义 Deployment,而不是命令式命令。设置合理的资源限制:为容器设置 CPU 和内存的 requests 和 limits。配置健康检查:使用 livenessProbe 和 readinessProbe 确保应用健康。使用多阶段构建:优化镜像大小,提高部署速度。设置适当的更新策略:根据应用特性选择 RollingUpdate 或 Recreate。使用标签和注解:为 Deployment 添加有意义的标签和注解,便于管理和追踪。监控更新过程:使用 kubectl rollout status 监控更新进度。
阅读 0·2月21日 15:53

Kubernetes ConfigMap 和 Secret 的区别是什么?如何使用它们管理应用配置?

Kubernetes ConfigMap 和 Secret 是用于管理配置数据的两种重要资源,它们允许将配置与容器镜像分离,提高应用的可移植性和安全性。ConfigMapConfigMap 用于存储非敏感的配置数据,如应用配置文件、命令行参数、环境变量等。ConfigMap 的创建方式从字面值创建:kubectl create configmap my-config --from-literal=key1=value1 --from-literal=key2=value2从文件创建:kubectl create configmap my-config --from-file=config.properties从目录创建:kubectl create configmap my-config --from-file=./config-dir/从 YAML 文件创建:apiVersion: v1kind: ConfigMapmetadata: name: my-configdata: key1: value1 key2: value2 app.properties: | server.port=8080 database.url=jdbc:mysql://localhost:3306/mydbConfigMap 的使用方式作为环境变量:apiVersion: v1kind: Podmetadata: name: my-podspec: containers: - name: my-container image: nginx env: - name: KEY1 valueFrom: configMapKeyRef: name: my-config key: key1作为命令行参数:apiVersion: v1kind: Podmetadata: name: my-podspec: containers: - name: my-container image: nginx command: ["/bin/sh", "-c"] args: ["echo $(KEY1)"] env: - name: KEY1 valueFrom: configMapKeyRef: name: my-config key: key1挂载为文件:apiVersion: v1kind: Podmetadata: name: my-podspec: containers: - name: my-container image: nginx volumeMounts: - name: config-volume mountPath: /etc/config volumes: - name: config-volume configMap: name: my-configSecretSecret 用于存储敏感信息,如密码、OAuth 令牌、SSH 密钥、证书等。Secret 的类型Opaque:默认类型,用于存储任意用户数据kubernetes.io/service-account-token:用于存储 Service Account 令牌kubernetes.io/dockercfg:用于存储 Docker registry 凭据kubernetes.io/dockerconfigjson:用于存储 Docker registry JSON 配置kubernetes.io/basic-auth:用于存储基本认证凭据kubernetes.io/ssh-auth:用于存储 SSH 认证凭据kubernetes.io/tls:用于存储 TLS 证书Secret 的创建方式从字面值创建:kubectl create secret generic my-secret --from-literal=username=admin --from-literal=password=secret123从文件创建:kubectl create secret generic my-secret --from-file=./username.txt --from-file=./password.txt从 YAML 文件创建:apiVersion: v1kind: Secretmetadata: name: my-secrettype: Opaquedata: username: YWRtaW4= password: c2VjcmV0MTIz注意:Secret 的 data 字段中的值必须是 Base64 编码的。使用 stringData:apiVersion: v1kind: Secretmetadata: name: my-secrettype: OpaquestringData: username: admin password: secret123stringData 会自动进行 Base64 编码。Secret 的使用方式Secret 的使用方式与 ConfigMap 类似:作为环境变量:apiVersion: v1kind: Podmetadata: name: my-podspec: containers: - name: my-container image: nginx env: - name: USERNAME valueFrom: secretKeyRef: name: my-secret key: username挂载为文件:apiVersion: v1kind: Podmetadata: name: my-podspec: containers: - name: my-container image: nginx volumeMounts: - name: secret-volume mountPath: /etc/secrets volumes: - name: secret-volume secret: secretName: my-secret拉取镜像:apiVersion: v1kind: Podmetadata: name: my-podspec: containers: - name: my-container image: my-private-registry/my-image imagePullSecrets: - name: registry-secretConfigMap 与 Secret 的区别| 特性 | ConfigMap | Secret ||------|-----------|--------|| 数据类型 | 非敏感数据 | 敏感数据 || 存储方式 | 明文存储 | Base64 编码(非加密) || 访问控制 | 普通 RBAC | 更严格的 RBAC || 大小限制 | 1 MiB | 1 MiB || 挂载方式 | 文件、环境变量 | 文件、环境变量 |安全最佳实践使用 Secret 存储敏感数据:永远不要将密码、密钥等敏感信息存储在 ConfigMap 中。启用 Secret 加密:使用 KMS(Key Management Service)对 etcd 中的 Secret 进行加密。限制 Secret 访问:使用 RBAC 限制对 Secret 的访问权限。使用临时文件:将 Secret 挂载为临时文件(tmpfs),避免持久化到磁盘。定期轮换密钥:定期更新 Secret 中的敏感信息。使用外部密钥管理:对于高安全性要求,考虑使用外部密钥管理系统(如 HashiCorp Vault)。审计 Secret 访问:启用审计日志,记录对 Secret 的访问。注意事项Base64 不是加密:Secret 中的数据只是 Base64 编码,不是加密,需要额外的安全措施。大小限制:ConfigMap 和 Secret 都有 1 MiB 的大小限制,超出限制需要拆分。版本管理:ConfigMap 和 Secret 的更新不会自动触发 Pod 重启,需要使用 Deployment 的滚动更新或手动重启。不可变性:可以将 ConfigMap 和 Secret 设置为不可变(immutable),提高性能和安全性。
阅读 0·2月21日 15:53

Koa 洋葱模型的执行机制和实际应用场景有哪些

Koa 的洋葱模型(Onion Model)是其最核心的设计特性,通过 async/await 实现中间件的执行流程控制。在洋葱模型中,中间件按照注册顺序执行,形成类似洋葱的层级结构。执行流程如下:请求从外层中间件进入,逐层向内传递每个中间件在 await next() 之前执行"前置逻辑"执行 await next() 进入下一层中间件到达最内层后,开始逐层向外返回每个中间件在 await next() 之后执行"后置逻辑"最终响应从最外层中间件返回客户端代码示例: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-后置洋葱模型的优势:清晰的执行顺序:前置和后置逻辑分离,代码结构清晰灵活的控制:每个中间件可以决定是否继续执行下游统一的错误处理:通过 try-catch 可以捕获所有中间件的错误中间件复用:可以在不同位置复用中间件逻辑请求/响应处理:方便在请求进入和响应返回时执行不同逻辑实际应用场景:日志记录:在前后置逻辑中记录请求和响应信息错误处理:在外层中间件统一捕获和处理错误认证授权:在前置逻辑中验证用户身份响应时间统计:计算请求处理总耗时响应格式化:在后置逻辑中统一处理响应格式
阅读 0·2月21日 15:53

Koa 错误处理机制和最佳实践详解

Koa 的错误处理机制非常优雅,通过 try-catch 和事件系统提供了多种错误处理方式。正确处理错误是构建健壮应用的关键。1. 使用 ctx.throw() 抛出错误:app.use(async (ctx) => { if (!ctx.query.token) { ctx.throw(401, 'Token is required'); } ctx.body = 'Success';});2. 使用 try-catch 捕获错误: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. 错误处理中间件: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. 自定义错误类: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. 全局错误监听:app.on('error', (err, ctx) => { // 记录错误日志 console.error('Server error:', err); // 发送错误通知(如邮件、Slack等) sendErrorNotification(err, ctx); // 上报错误到监控系统 reportToMonitoring(err, ctx);});6. 404 处理:// 在所有路由之后添加 404 处理app.use(async (ctx) => { ctx.status = 404; ctx.body = { error: 'Not Found', code: 'NOT_FOUND', path: ctx.url };});7. 异步错误处理:// 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. 错误处理最佳实践:// 完整的错误处理示例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. 常见错误处理场景:// 数据库错误处理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; }});错误处理要点:使用 try-catch 包裹可能出错的代码创建自定义错误类,提供更清晰的错误信息区分开发环境和生产环境的错误响应实现全局错误监听,统一记录和上报为不同类型的错误提供适当的 HTTP 状态码确保错误不会泄露敏感信息在洋葱模型的最外层添加错误处理中间件
阅读 0·2月21日 15:53

Koa 中 Cookie 和 Session 管理的实现方法

Koa 的 Cookie 和 Session 管理是构建 Web 应用的基础功能,Koa 核心提供了 Cookie 操作,而 Session 需要通过中间件实现。1. Cookie 操作:Koa 核心内置了 Cookie 功能,通过 ctx.cookies 对象进行操作。设置 Cookie: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:app.use(async (ctx) => { const username = ctx.cookies.get('username'); ctx.body = `Hello ${username}`;});删除 Cookie:app.use(async (ctx) => { ctx.cookies.set('username', null, { maxAge: 0, path: '/' }); ctx.body = 'Cookie deleted';});2. Session 管理:使用 koa-session 中间件实现 Session 功能。安装:npm install koa-session基本配置: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 使用:// 设置 Sessionapp.use(async (ctx) => { if (ctx.path === '/login') { ctx.session.user = { id: 1, name: 'John', role: 'admin' }; ctx.body = 'Logged in'; }});// 获取 Sessionapp.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'); } }});// 删除 Sessionapp.use(async (ctx) => { if (ctx.path === '/logout') { ctx.session = null; ctx.body = 'Logged out'; }});3. Redis Session 存储:对于生产环境,建议使用 Redis 存储 Session。安装:npm install koa-session koa-redis配置 Redis Session: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. 认证中间件示例:// 认证中间件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 认证。安装:npm install jsonwebtoken koa-jwt生成 Token: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: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. 完整的认证流程示例: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. 安全最佳实践:Cookie 安全:始终设置 httpOnly: true 防止 XSS生产环境使用 secure: true 仅 HTTPS设置 sameSite: 'strict' 防止 CSRF使用签名 Cookie 防止篡改Session 安全:使用强随机密钥设置合理的过期时间生产环境使用 Redis 存储登出时清除 SessionJWT 安全:使用强密钥设置合理的过期时间使用 HTTPS 传输实现 Token 刷新机制其他安全措施:限制登录尝试次数实现密码强度验证记录认证日志定期更新密钥
阅读 0·2月21日 15:53