前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
  • 基础篇

    • 环境搭建
    • node部署
    • 基础应用
  • 内置模块

    • 1.0 本地路径处理 path
    • 2.0 文件系统操作 fs
    • 3.1 基础调试 console
    • 3.2 本地调试远程服务器上的Node代码
    • 4.1 网络服务 http
    • 4.2 网络服务 http res
    • 4.3 网络服务 http req
    • 4.4 网络服务 http server
    • 4.5 网络服务 http client
    • 4.6 网络服务 https
    • 4.7 网络TCP net
    • 4.8 网络UDP dgram
    • 4.9 域名解析 dns
    • 5.0 网络地址解析 url
    • 5.1 URL查询字符串 querystring
    • 6.1 流操作 stream
    • 6.2 逐行读取 readline
    • 7.1 进程相关 process
    • 7.2 子进程 child
    • 8.1 二进制数据 buffer
    • 8.2 二进制解码 string_decoder
    • 9.1 事件机制 events
    • 9.2 实用工具模块 util
    • 9.3 数据加密 crypto
    • 9.4 MD5入门介绍及crypto模块的应用
    • 9.4 资源压缩 zlib
    • 9.5 集群 cluster
    • 9.6 v8
  • 进阶篇

    • 5分钟入门非对称加密用法
    • cluster
    • cookie_parser深入
    • crypto模块之理论篇
    • express+cookie_parser签名机制深入剖析
    • express+session实现简易身份认证
    • https
    • log4js入门实例
    • node8_napi
    • 使用 async 控制并发
    • 使用 eventproxy 控制并发
    • 使用 superagent 与 cheerio 完成简单爬虫
    • 基于express+muter的文件上传
    • 将图片转成datauri嵌入到html
    • 常用中间件 body_parser 实现解析
    • 日志模块morgan
    • 服务端字符编解码&乱码处理 charset_enc_dec
    • 测试用例mocha,should,istanbul
    • 调试日志打印debug模块
  • Express

    • 1.1 Express概览篇
    • 1.2 Express
  • Koa2

    • 1.0 koa2概览篇
    • 1.1 快速开始
    • 1.2 async await使用
    • 1.3 koa2简析结构
    • 1.4 koa中间件开发与使用
    • 10.1 单元测试
    • 11.1 开发debug
    • 12.1 快速启动
    • 12.2 框架设计
    • 12.3 分层操作
    • 12.4 数据库设计
    • 12.5 路由设计
    • 12.6 webpack4环境搭建
    • 12.7 使用react
    • 12.8 登录注册功能实现
    • 12.9 session登录态判断处理
    • 13.1 import export使用
    • 2.1 原生koa2实现路由
    • 2.2 koa router中间
    • 3.1 GET请求数据获取
    • 3.2 POST请求数据获取
    • 3.3 koa bodyparser中间件
    • 4.1 原生koa2实现静态资源服务器
    • 4.2 koa static中间件
    • 5.1 koa2使用cookie
    • 5.2 koa2实现session
    • 6.1 koa2加载模板引擎
    • 6.2 ejs模板引擎
    • 7.1 busboy模块
    • 7.2 上传文件简单实现
    • 7.3 异步上传图片实现
    • 8.1 mysql模块
    • 8.2 async await封装使用mysql
    • 8.3 项目建表初始化
    • 9.1 原生koa2实现JSONP
    • 9.2 koa jsonp中间件
  • 其他

    • 操作数据库
    • Session 与 Token
    • Cookie、Session、Token、JWT
    • Socket

# 异步上传图片实现

30 秒速记

  • 异步上传 = 不刷新页面:前端 FormData + XHR 发请求,后端还是 busboy 落盘,返回图片 URL 再预览
  • 进度用 xhr.upload.onprogress,loaded / total 算百分比,fetch 目前拿不到上传进度
  • 进度到 100% 只是请求体发完,服务端解析、写盘、响应还没结束,要区分「上传中 / 处理中 / 成功 / 失败」
  • 取消用 xhr.abort(),服务端要监听连接关闭并删掉半截文件
  • 量大以后:前端拿短期签名直传对象存储,缩略图、审核这类重活放队列异步做

异步上传就是不刷新页面:前端用 FormData 包好文件,通过 XHR 发给后端,后端用 busboy 写盘后返回图片地址,前端再插一个 <img> 预览。 和表单提交比,它多了两样东西:能拿到上传进度,能留在当前页面处理结果。实际做的时候要分清阶段,进度条 100% 只代表浏览器把数据发完了,服务端可能还在写盘,这时候应该显示「处理中」,等响应回来再判成功或失败。用户量上来后,一般不再让文件经过自己的 Node 服务,而是前端拿一个短期有效的签名直传对象存储。

理解「异步上传图片实现」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。

工程中还要验证退出码、资源清理和异常输入。属于 event-loop 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。

面试官追问

追问 1进度条已经到 100% 了,图片还要好几秒才出来,用户以为卡了,怎么处理?
参考回答

100% 只是请求体发完了,服务端还在写盘和处理。进度到顶后把文案切成「处理中」,等 onload 拿到响应再显示成功或失败。

追问 2用户点了取消,前端 xhr.abort() 了,服务器上会留下什么?
参考回答

会留一个半截文件。服务端要监听 ctx.req 的 aborted 或 close,连接断了就销毁写流并删掉文件。

追问 3为什么上传不用 fetch?它不是更现代吗?
参考回答

fetch 没有上传进度事件,只能拿到下载进度。要显示上传百分比还得用 XHR,或者用 axios,它在浏览器里底层也是 XHR,配置 onUploadProgress 就行。

追问 4图片多了,带宽全耗在 Node 服务上,怎么优化?
参考回答

改成前端直传对象存储:后端只签发一个几分钟有效、限定 key 前缀、大小和类型的上传凭证,文件不再经过 Node。上传完成后对象存储回调或者前端通知后端落库。

追问 5上传后还要生成缩略图和做内容审核,接口要等这些都做完再返回吗?
参考回答

一般不等。接口写完原图就返回一个任务 id,缩略图和审核丢进队列,前端轮询或者靠推送拿结果。这样接口响应时间不受图片处理影响,失败也能单独重试。

# 异步上传图片实现

# 快速上手

30 秒速记

  • demo 在 daily-code-practice 仓库的 koa2-demo/upload-async 目录
  • 跑法:npm install → node index.js → 打开 http://localhost:3000,点按钮选图
  • 依赖:koa、koa-views + ejs、koa-static、busboy;koa-views 不自带模板引擎,ejs 要单独装
  • 上传的图会出现在 static/image/album/,同时页面显示预览

这个 demo 拉下来 npm install、node index.js,打开 3000 端口就能体验异步上传。 它比上一节多了三样东西:用 koa-views 渲染 EJS 页面,用 koa-static 提供前端 JS 和上传后的图片,前端用 XHR 发请求并显示进度。跑起来如果报模板相关的错,多半是没装 ejs,koa-views 只是个壳,底层引擎要自己装。上传完去 static/image/album 看文件,再看页面预览能不能加载出来,两边都对才算跑通。

demo 地址

https://github.com/poetries/daily-code-practice/tree/master/node/koa/koa2-demo/upload-async

面试官追问

追问 1启动后访问首页报 Cannot find module 'ejs',怎么回事?
参考回答

koa-views 通过 consolidate 调用模板引擎,本身不带 ejs。npm install ejs 就好。

追问 2代码里用 koa-convert 包了一层 koa-static,现在还需要吗?
参考回答

不需要。注释说 koa-static 不支持 Koa 2,那是 koa-static 3.x 的事,4.x 开始就是 async 中间件了,直接 app.use(serve(dir))。

追问 3启动报 EADDRINUSE,怎么处理?
参考回答

3000 端口被占了,lsof -i :3000 找到占用进程关掉,或者改成 app.listen(process.env.PORT || 3000),用环境变量换端口。

追问 4上传成功了,但预览图是裂的,先看什么?
参考回答

打开 Network 看图片请求的地址和状态码。pictureUrl 是 //host/image/album/xxx,对应的文件要在 static/image/album/ 下,koa-static 的根目录是 static,对不上就是 404。

# 源码理解

30 秒速记

  • 读代码按数据走:index.ejs 按钮 → static/js/index.js 选文件、发 XHR → index.js 路由 → util/upload.js 写盘 → 返回 pictureUrl
  • 图片能被访问,是因为写入目录 static/image 正好在 koa-static 的根目录 static 下面
  • 后端不关心前端字段名叫 files 还是别的,busboy 每个文件都会触发 file 事件
  • 先挂 koa-static、再挂业务中间件,静态文件优先命中,没命中的 GET 都会渲染首页

这段源码最好顺着一张图片的旅程来读:从按钮点击,到 XHR 发出,到 busboy 写盘,再到 URL 回来变成 <img>。 前端动态创建一个文件 input 并模拟点击,选完图后用 FormData 装起来 POST 到 /api/picture/upload.json,同时用 upload.onprogress 更新进度。后端 koa-static 先拦掉静态资源请求,剩下的 GET 统统渲染首页,POST 到上传接口就调 uploadFile。文件写进 static/image/album,因为它在静态根目录下,所以返回的 //host/image/album/xxx 能直接访问。

理解「源码理解」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。

工程中还要验证退出码、资源清理和异常输入。属于 module 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。

把一张图片在这个 demo 里的完整旅程串起来看,就不用一个文件一个文件地对照了。

1. 浏览器 GET /            → koa-static 没找到文件 → 业务中间件 GET 分支 → 渲染 view/index.ejs
2. 浏览器 GET /js/index.js → koa-static 命中 static/js/index.js,直接返回
3. 点按钮                   → 动态创建 <input type="file"> 并 click()
4. 选完图片 onchange        → new FormData(),append('files', file)
5. XHR POST /api/picture/upload.json,upload.onprogress 不断回调百分比
6. 业务中间件 POST 分支     → uploadFile(ctx, { fileType: 'album', path: static/image })
7. busboy 解析 ctx.req      → file 事件 → 写入 static/image/album/<随机名>.<后缀>
8. 返回 { success: true, data: { pictureUrl: '//host/image/album/xxx.png' } }
9. 前端解析 JSON           → 插入 <img src=pictureUrl>
10. 浏览器 GET /image/album/xxx.png → koa-static 命中,返回图片
@前端进阶之旅: 代码已经复制到剪贴板

第 2 步和第 10 步说明了一件事:koa-static 的根目录是 static,所以 static/js 和 static/image 都能被直接访问。这对 demo 很方便,但也意味着任何人上传的文件,只要知道地址谁都能访问,而且上传目录和前端代码混在一起,发版时整体覆盖 static 会把用户图片一起删掉。实际项目里会把上传目录和静态代码分开,或者直接用对象存储。

第 7 步和第 8 步之间有一个时间差:代码在读流 end 时就返回了 URL,如果写流还没刷完,第 10 步可能拿到不完整的图片。把成功判定挪到写流

基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
  • 基础篇

    • 环境搭建
    • node部署
    • 基础应用
  • 内置模块

    • 1.0 本地路径处理 path
    • 2.0 文件系统操作 fs
    • 3.1 基础调试 console
    • 3.2 本地调试远程服务器上的Node代码
    • 4.1 网络服务 http
    • 4.2 网络服务 http res
    • 4.3 网络服务 http req
    • 4.4 网络服务 http server
    • 4.5 网络服务 http client
    • 4.6 网络服务 https
    • 4.7 网络TCP net
    • 4.8 网络UDP dgram
    • 4.9 域名解析 dns
    • 5.0 网络地址解析 url
    • 5.1 URL查询字符串 querystring
    • 6.1 流操作 stream
    • 6.2 逐行读取 readline
    • 7.1 进程相关 process
    • 7.2 子进程 child
    • 8.1 二进制数据 buffer
    • 8.2 二进制解码 string_decoder
    • 9.1 事件机制 events
    • 9.2 实用工具模块 util
    • 9.3 数据加密 crypto
    • 9.4 MD5入门介绍及crypto模块的应用
    • 9.4 资源压缩 zlib
    • 9.5 集群 cluster
    • 9.6 v8
  • 进阶篇

    • 5分钟入门非对称加密用法
    • cluster
    • cookie_parser深入
    • crypto模块之理论篇
    • express+cookie_parser签名机制深入剖析
    • express+session实现简易身份认证
    • https
    • log4js入门实例
    • node8_napi
    • 使用 async 控制并发
    • 使用 eventproxy 控制并发
    • 使用 superagent 与 cheerio 完成简单爬虫
    • 基于express+muter的文件上传
    • 将图片转成datauri嵌入到html
    • 常用中间件 body_parser 实现解析
    • 日志模块morgan
    • 服务端字符编解码&乱码处理 charset_enc_dec
    • 测试用例mocha,should,istanbul
    • 调试日志打印debug模块
  • Express

    • 1.1 Express概览篇
    • 1.2 Express
  • Koa2

    • 1.0 koa2概览篇
    • 1.1 快速开始
    • 1.2 async await使用
    • 1.3 koa2简析结构
    • 1.4 koa中间件开发与使用
    • 10.1 单元测试
    • 11.1 开发debug
    • 12.1 快速启动
    • 12.2 框架设计
    • 12.3 分层操作
    • 12.4 数据库设计
    • 12.5 路由设计
    • 12.6 webpack4环境搭建
    • 12.7 使用react
    • 12.8 登录注册功能实现
    • 12.9 session登录态判断处理
    • 13.1 import export使用
    • 2.1 原生koa2实现路由
    • 2.2 koa router中间
    • 3.1 GET请求数据获取
    • 3.2 POST请求数据获取
    • 3.3 koa bodyparser中间件
    • 4.1 原生koa2实现静态资源服务器
    • 4.2 koa static中间件
    • 5.1 koa2使用cookie
    • 5.2 koa2实现session
    • 6.1 koa2加载模板引擎
    • 6.2 ejs模板引擎
    • 7.1 busboy模块
    • 7.2 上传文件简单实现
    • 7.3 异步上传图片实现
    • 8.1 mysql模块
    • 8.2 async await封装使用mysql
    • 8.3 项目建表初始化
    • 9.1 原生koa2实现JSONP
    • 9.2 koa jsonp中间件
  • 其他

    • 操作数据库
    • Session 与 Token
    • Cookie、Session、Token、JWT
    • Socket
完整面试题地址:
作者:程序员poetry
扫码关注作者公众号:「前端进阶之旅」 每天分享技术干货
前端进阶之旅公众号二维码

异步上传图片实现|Nodejs专题系列

# 异步上传图片实现

30 秒速记

  • 异步上传 = 不刷新页面:前端 FormData + XHR 发请求,后端还是 busboy 落盘,返回图片 URL 再预览
  • 进度用 xhr.upload.onprogress,loaded / total 算百分比,fetch 目前拿不到上传进度
  • 进度到 100% 只是请求体发完,服务端解析、写盘、响应还没结束,要区分「上传中 / 处理中 / 成功 / 失败」
  • 取消用 xhr.abort(),服务端要监听连接关闭并删掉半截文件
  • 量大以后:前端拿短期签名直传对象存储,缩略图、审核这类重活放队列异步做

异步上传就是不刷新页面:前端用 FormData 包好文件,通过 XHR 发给后端,后端用 busboy 写盘后返回图片地址,前端再插一个 <img> 预览。 和表单提交比,它多了两样东西:能拿到上传进度,能留在当前页面处理结果。实际做的时候要分清阶段,进度条 100% 只代表浏览器把数据发完了,服务端可能还在写盘,这时候应该显示「处理中」,等响应回来再判成功或失败。用户量上来后,一般不再让文件经过自己的 Node 服务,而是前端拿一个短期有效的签名直传对象存储。

理解「异步上传图片实现」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。

工程中还要验证退出码、资源清理和异常输入。属于 event-loop 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。

面试官追问

追问 1进度条已经到 100% 了,图片还要好几秒才出来,用户以为卡了,怎么处理?
参考回答

100% 只是请求体发完了,服务端还在写盘和处理。进度到顶后把文案切成「处理中」,等 onload 拿到响应再显示成功或失败。

追问 2用户点了取消,前端 xhr.abort() 了,服务器上会留下什么?
参考回答

会留一个半截文件。服务端要监听 ctx.req 的 aborted 或 close,连接断了就销毁写流并删掉文件。

追问 3为什么上传不用 fetch?它不是更现代吗?
参考回答

fetch 没有上传进度事件,只能拿到下载进度。要显示上传百分比还得用 XHR,或者用 axios,它在浏览器里底层也是 XHR,配置 onUploadProgress 就行。

追问 4图片多了,带宽全耗在 Node 服务上,怎么优化?
参考回答

改成前端直传对象存储:后端只签发一个几分钟有效、限定 key 前缀、大小和类型的上传凭证,文件不再经过 Node。上传完成后对象存储回调或者前端通知后端落库。

追问 5上传后还要生成缩略图和做内容审核,接口要等这些都做完再返回吗?
参考回答

一般不等。接口写完原图就返回一个任务 id,缩略图和审核丢进队列,前端轮询或者靠推送拿结果。这样接口响应时间不受图片处理影响,失败也能单独重试。

# 异步上传图片实现

# 快速上手

30 秒速记

  • demo 在 daily-code-practice 仓库的 koa2-demo/upload-async 目录
  • 跑法:npm install → node index.js → 打开 http://localhost:3000,点按钮选图
  • 依赖:koa、koa-views + ejs、koa-static、busboy;koa-views 不自带模板引擎,ejs 要单独装
  • 上传的图会出现在 static/image/album/,同时页面显示预览

这个 demo 拉下来 npm install、node index.js,打开 3000 端口就能体验异步上传。 它比上一节多了三样东西:用 koa-views 渲染 EJS 页面,用 koa-static 提供前端 JS 和上传后的图片,前端用 XHR 发请求并显示进度。跑起来如果报模板相关的错,多半是没装 ejs,koa-views 只是个壳,底层引擎要自己装。上传完去 static/image/album 看文件,再看页面预览能不能加载出来,两边都对才算跑通。

demo 地址

https://github.com/poetries/daily-code-practice/tree/master/node/koa/koa2-demo/upload-async

面试官追问

追问 1启动后访问首页报 Cannot find module 'ejs',怎么回事?
参考回答

koa-views 通过 consolidate 调用模板引擎,本身不带 ejs。npm install ejs 就好。

追问 2代码里用 koa-convert 包了一层 koa-static,现在还需要吗?
参考回答

不需要。注释说 koa-static 不支持 Koa 2,那是 koa-static 3.x 的事,4.x 开始就是 async 中间件了,直接 app.use(serve(dir))。

追问 3启动报 EADDRINUSE,怎么处理?
参考回答

3000 端口被占了,lsof -i :3000 找到占用进程关掉,或者改成 app.listen(process.env.PORT || 3000),用环境变量换端口。

追问 4上传成功了,但预览图是裂的,先看什么?
参考回答

打开 Network 看图片请求的地址和状态码。pictureUrl 是 //host/image/album/xxx,对应的文件要在 static/image/album/ 下,koa-static 的根目录是 static,对不上就是 404。

# 源码理解

30 秒速记

  • 读代码按数据走:index.ejs 按钮 → static/js/index.js 选文件、发 XHR → index.js 路由 → util/upload.js 写盘 → 返回 pictureUrl
  • 图片能被访问,是因为写入目录 static/image 正好在 koa-static 的根目录 static 下面
  • 后端不关心前端字段名叫 files 还是别的,busboy 每个文件都会触发 file 事件
  • 先挂 koa-static、再挂业务中间件,静态文件优先命中,没命中的 GET 都会渲染首页

这段源码最好顺着一张图片的旅程来读:从按钮点击,到 XHR 发出,到 busboy 写盘,再到 URL 回来变成 <img>。 前端动态创建一个文件 input 并模拟点击,选完图后用 FormData 装起来 POST 到 /api/picture/upload.json,同时用 upload.onprogress 更新进度。后端 koa-static 先拦掉静态资源请求,剩下的 GET 统统渲染首页,POST 到上传接口就调 uploadFile。文件写进 static/image/album,因为它在静态根目录下,所以返回的 //host/image/album/xxx 能直接访问。

理解「源码理解」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。

工程中还要验证退出码、资源清理和异常输入。属于 module 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。

把一张图片在这个 demo 里的完整旅程串起来看,就不用一个文件一个文件地对照了。

1. 浏览器 GET /            → koa-static 没找到文件 → 业务中间件 GET 分支 → 渲染 view/index.ejs
2. 浏览器 GET /js/index.js → koa-static 命中 static/js/index.js,直接返回
3. 点按钮                   → 动态创建 <input type="file"> 并 click()
4. 选完图片 onchange        → new FormData(),append('files', file)
5. XHR POST /api/picture/upload.json,upload.onprogress 不断回调百分比
6. 业务中间件 POST 分支     → uploadFile(ctx, { fileType: 'album', path: static/image })
7. busboy 解析 ctx.req      → file 事件 → 写入 static/image/album/<随机名>.<后缀>
8. 返回 { success: true, data: { pictureUrl: '//host/image/album/xxx.png' } }
9. 前端解析 JSON           → 插入 <img src=pictureUrl>
10. 浏览器 GET /image/album/xxx.png → koa-static 命中,返回图片
@前端进阶之旅: 代码已经复制到剪贴板

第 2 步和第 10 步说明了一件事:koa-static 的根目录是 static,所以 static/js 和 static/image 都能被直接访问。这对 demo 很方便,但也意味着任何人上传的文件,只要知道地址谁都能访问,而且上传目录和前端代码混在一起,发版时整体覆盖 static 会把用户图片一起删掉。实际项目里会把上传目录和静态代码分开,或者直接用对象存储。

第 7 步和第 8 步之间有一个时间差:代码在读流 end 时就返回了 URL,如果写流还没刷完,第 10 步可能拿到不完整的图片。把成功判定挪到写流 finish,这个窗口就没了。

面试官追问

追问 1前端 formData.append('files', file),后端代码里没出现 files,它是怎么拿到的?
参考回答

busboy 每遇到一个文件段就触发一次 file 事件,字段名作为第一个参数传进来,代码没用它而已。要校验字段名就在回调里判断 fieldname === 'files',不对就 file.resume() 丢掉。

追问 2pictureUrl 写成 //host/image/...,开头没有 http:,为什么?
参考回答

这是协议相对地址,跟着当前页面的协议走,页面是 https 图片就用 https,避免混合内容警告。现在全站 https 是常态,直接写 https:// 或者只返回相对路径 /image/... 更清楚。

追问 3请求 /js/index.js 为什么不会被渲染成首页?
参考回答

koa-static 挂在业务中间件前面,命中静态文件就直接返回了,不会往下走。要是把两者顺序调换,所有 GET 都会先进渲染分支,前端脚本拿到的就是一段 HTML。

追问 4服务部署在容器里,重启后之前上传的图片全没了,为什么?
参考回答

图片写在容器自己的文件系统里,容器重建就清空了。要把 static/image 挂成持久卷,或者干脆换对象存储,数据库只存 key。

# demo源码目录

30 秒速记

  • 后端:index.js 启动和路由,util/upload.js 解析并写盘
  • 前端:view/index.ejs 页面模板,static/js/index.js 上传交互
  • 数据:static/image 存用户上传的图,运行时才产生,和代码的生命周期完全不同
  • node_modules 按 package.json 装,不跨机器拷贝

这个 demo 的目录可以分成三类:后端代码、前端资源、运行时数据。 index.js 是入口,util/upload.js 负责处理图片流;view/index.ejs 是服务端渲染的页面,static/js/index.js 是浏览器里跑的上传逻辑;static/image 是上传图片的存放目录。我会特别留意最后一个,它虽然放在 static 下面看着像静态资源,其实是用户数据,发版时如果把 static 整个覆盖,用户传的图就全没了。

← 7.2 上传文件简单实现8.1 mysql模块 →

fe