简介这套手机拍照无确认连续上传系统ASP源码专为需要在移动端实现快速连拍上传场景的开发者准备。基于ASP与IIS环境配合HTTPS协议即可调用手机摄像头通过首页自动生成二维码扫码后进入拍照页每按一次快门便自动上传无需额外确认适合现场取证、巡店记录等高效采集需求。资源压缩包共5个文件1.09MB包含两个核心ASP页面拍照入口与照片管理后台另附JavaScript二维码生成组件、示例图片及说明文件结构简洁便于部署修改。目前已有28人学习浏览可作为快速落地的参考代码。这套源码不仅实现了核心的无缝连拍上传流程还提供可自定义的后台密码与文件管理视图方便按自身域名和存储空间完成配置。对于希望缩短开发周期、了解ASP与手机摄像头交互方案的开发者能直接获得一套精简可运行的原型。1. 手机拍照无确认连续上传系统ASP源码它解决什么卡在哪外勤巡检、仓库验货、工程留痕这类场景里一个人一天举着手机拍上百张照片最烦的不是拍照本身而是每张都得等进度条、点“确认上传”、再切回来拍下一张。这套“手机拍照无确认连续上传系统”要解决的就是把“按快门”和“上传”合并成一个动作拍完自动进队列后台连续发全程不再打断你。我最初以为难点在ASP端怎么写接收真正拆开才发现“无确认”的关键其实在手机浏览器的安全模型上——纯网页永远绕不开文件选择的手势想真正无感必须靠Android WebView那一层把文件选择器接管掉。ASP源码在这里充当的是一个“哑接收端”收图、落盘、回执做得越简单越稳。适合正在维护老ASP系统、想用最低成本给内部加移动拍照能力的小团队。这套方案在三周内跑通是常态但坑也不少。2. 原理与选型无确认的边界在哪ASP为什么还够用2.1 三层交互模型拍照拦截、队列发送、IIS落盘把这套系统拆开看它其实是三层协作不是一层网页逻辑就能扛下来的。第一层是客户端壳。常见做法是Android App内嵌一个WebView页面负责拍照和上传但系统弹出来的文件选择器被WebView的onShowFileChooser回调接管。这个接管是整个“无确认”的命根子页面里放一个input typefile acceptimage/* captureenvironment点击后正常浏览器会弹“拍照/相册/文件”三选一用户还得再点一次。而在WebView里接管回调后可以直接拉起系统相机拍完照片的URI直接回传给页面中间没有任何选择框这就是“无确认”的第一层也是最难的一层。第二层是页面里的上传队列。拿到文件后不能一个for循环同时发十个请求移动端浏览器的并发连接数有限制而且服务端是经典ASP每个大二进制流过来都在阻塞处理。所以要维护一个带并发上限、超时、重试的队列拍一张吞一张传完再传下一张。第三层是IIS ASP收图。它不负责判断业务只负责把multipart里的文件字节取出来用日期目录加时间戳落盘然后回一个JSON表示“收到了”。这一层最容易出问题但问题几乎都能归到IIS配置和权限上跟业务代码关系不大。2.2 为什么是经典ASP零编译、老设备兼容与大并发禁忌很多新人会问都2024年了新写一个拍照上传系统为什么还要用ASP我一般会反问你的服务器是不是Windows Server你的团队是不是没有人会Node/Java你是不是只想给内部几十个人用三条都满足经典ASP就是最省事的方案。零编译是最大优势改一个.asp文件保存刷新页面就生效。现场调参数、改目录、加字段不需要构建发布流程这在老系统的维护场景里特别珍贵。其次经典ASP对老设备的兼容极好——很多企业的扫码墩、PDA、老工业平板还在用IE内核或极老WebView那些设备跑SPA现代框架会卡得没法用但跑简单的表单加XMLHttpRequest完全顺畅。第三经典ASP和Windows Server天然一体不用装运行时、不用配置网关ADODB.Stream、Scripting.FileSystemObject都是系统组件直接CreateObject就能用。但边界一定要说清楚这套东西只适合内网或低并发。经典ASP每个请求占一个线程二进制大请求进来时内存和CPU都不算好看如果上万人同时传图ASP会先翻车。另外如果系统要对外开放、要自动签发HTTPS证书、要跑多实例趁早换别的语言别在ASP上硬撑。2.3 环境准备Win11/Win10配置IIS与ASP两行命令一个注意点很多人搜“win11配置iis asp”“win10如何打开.asp网页”其实答案就是Windows功能里缺了ASP模块。Win10/Win11开发机只需要一条PowerShell命令Enable-WindowsOptionalFeature -Online -FeatureName IIS-ASP -All注意这条命令执行完通常要重启重启后打开IIS管理器默认站点下新建一个test.asp内容写% Now() %浏览器访问http://localhost/test.asp能显示时间就说明ASP已经通了。如果是Windows Server 2016以上用服务器管理器对应的命令Install-WindowsFeature Web-ASP Install-WindowsFeature Web-Mgmt-Console装完还要做三件容易被忽略的事。第一给站点加绑定默认的80端口在公司内网经常被占用常见做法是绑定IP加8080端口这样手机访问时直接走http://192.168.x.x:8080/不用改网关。第二防火墙放行端口netsh advfirewall firewall add rule nameASPUpload dirin actionallow protocolTCP localport8080第三应用程序池的经典模式与32位设置如果这段ASP要顺带读Access数据库老系统十有八九是Access或SQL Server而Access驱动是32位的那要把应用程序池的“启用32位应用程序”设为True同时托管管道模式选“经典”否则运行时才报错日志又看不懂纯玄学。3. 把“无确认”做真WebView拍照直传与连续上传的JS队列3.1 先声明边界纯浏览器只能半无确认Android WebView才闭环这句话值得放在最前面iOS的Safari/微信内置浏览器任何网页都做不到“拍照后不点确认”。系统层级的安全策略锁死了文件选择手势页面只能拿到用户明确选择过的文件。iOS上能优化到的极限是选了照片后自动开始上传省掉“上传”按钮但选照片那一下无论如何省不掉。所以这套源码如果只靠纯HTML在Android Chrome和iOS上都是“半无确认”。真正闭环得靠Android WebView它允许App重写onShowFileChooser把系统文件选择器整个替换掉直接拉起相机拍完把照片URI回传。在这个壳里“按下快门”就是用户的确认之后不会再有任何对话框。这个区别决定了系统边界如果用户群体用的是iPhone就别承诺“完全无确认”至少保留一个相册选择界面如果全是安卓机仓库、外勤常见的工业平板和企业安卓手机那WebView方案能做到拍完即传。3.2 拦截文件选择器onShowFileChooser直接回传拍照结果核心代码在Android端Java写WebChromeClient子类private ValueCallbackUri[] uploadMessage; private Uri photoUri; webView.setWebChromeClient(new WebChromeClient() { Override public boolean onShowFileChooser(WebView webView, ValueCallbackUri[] filePathCallback, FileChooserParams fileChooserParams) { // 每次接管前先把上一次残留的回调清掉否则第二次点击会失灵 if (uploadMessage ! null) { uploadMessage.onReceiveValue(null); uploadMessage null; } uploadMessage filePathCallback; // 页面 input 带了 capture 属性直接把系统相机拉起来 Intent takePhoto new Intent(MediaStore.ACTION_IMAGE_CAPTURE); photoUri createPhotoUri(); // 用 FileProvider 生成 content:// 临时URI takePhoto.putExtra(MediaStore.EXTRA_OUTPUT, photoUri); startActivityForResult(takePhoto, REQUEST_CAMERA); return true; // 返回 true 表示文件选择流程由 App 接管 } }); Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode REQUEST_CAMERA) { if (resultCode RESULT_OK) { // 拍完直接回传 URI页面端拿到文件事件自动触发上传 uploadMessage.onReceiveValue(new Uri[]{photoUri}); } else { // 用户取消拍照也必须回调 null否则页面下次点击 input 无反应 uploadMessage.onReceiveValue(null); } uploadMessage null; } super.onActivityResult(requestCode, resultCode, data); }这里有两个参数级别的坑。第一createPhotoUri()必须用FileProviderAndroid 7.0以上直接传file://路径会被系统拒掉运行时直接FileUriExposedExceptionFileProvider的file_paths.xml要把临时拍照目录配进去。第二onReceiveValue必须在任何分支都被调用包括用户取消。很多WebView拍照功能“第一次能用第二次点没反应”就是因为取消路径没回调页面的回调对象一直被占着。这个习惯值得刻进肌肉记忆。页面端配合的HTML只需要一个隐藏的inputinput typefile idcameraInput acceptimage/* captureenvironment styledisplay:none /显示拍照按钮时直接document.getElementById(cameraInput).click()WebView收到后走上面的接管逻辑。拍照结果回传后input的change事件自动触发连“确认选择”这一步都去掉了。3.3 连续上传不是写个for循环队列、并发、超时与重试拿到照片文件之后最容易犯的错误是这样的for (let i 0; i files.length; i) { upload(files[i]); // 十个请求同时发出WebView 直接卡死 }移动端WebView对同一域名的HTTP连接数限制一般是6个这6个还得包含页面自身资源加载。十个图片同时上传后面几个直接排队阻塞超时后连错一片。正确做法是维护一个显式队列控制并发数。const uploadQueue { pending: [], // 待上传文件 running: 0, // 正在上传数 maxConcurrent: 2, // 并发上限别超过3 timeout: 15000, // 单张超时毫秒 retry: 2, // 失败重试次数 init() { document.getElementById(cameraInput).addEventListener(change, (e) { const files Array.from(e.target.files); files.forEach(f this.pending.push({ file: f, retried: 0 })); e.target.value ; // 清空 value否则连续拍同一张照片不触发 change this.pump(); }); }, pump() { while (this.running this.maxConcurrent this.pending.length 0) { const task this.pending.shift(); this.running; this.upload(task).finally(() { this.running--; this.pump(); }); } }, async upload(task) { try { await postFile(task.file, this.timeout); // 底层用 XHR支持超时 } catch (e) { if (task.retried this.retry) { task.retried; this.pending.push(task); // 塞回队尾等下一轮重发 } else { console.error(upload failed:, task.file.name); // 这里要把失败任务记到 localStorage等网络恢复后再补传 } } } };并发数为什么取2而不是6经典ASP是单线程阻塞模型一个3MB的图在普通办公Wi-Fi下上传要1到3秒两个并发还能接受六个并发会让IIS线程池瞬间被占满其它页面请求全部跟着等。另外重试逻辑里一定要区分“网络断开”和“服务端拒绝”两种错误只有前者值得重试收到服务端回执但回执内容解析失败的情况重试要小心因为服务端可能已经落盘了重试会造成重复文件这就要靠后面说的幂等键来解决。3.4 压缩与进度反馈图片转JPEG和“看不见的确认”手机原图动辄3MB到8MB而这类系统最终只是留痕用不是摄影比赛所以上传前压缩是必选动作。常见做法用Canvas重绘一次async function compressToJpeg(file, maxEdge 1600, quality 0.7) { const url URL.createObjectURL(file); const img new Image(); await new Promise((resolve, reject) { img.onload resolve; img.onerror reject; img.src url; }); const scale Math.min(1, maxEdge / Math.max(img.width, img.height)); const canvas document.createElement(canvas); canvas.width Math.round(img.width * scale); canvas.height Math.round(img.height * scale); canvas.getContext(2d).drawImage(img, 0, 0, canvas.width, canvas.height); URL.revokeObjectURL(url); return new Promise(resolve canvas.toBlob(resolve, image/jpeg, quality)); }参数这样调maxEdge 1600对应打印A5左右还清晰quality 0.7压完普遍在200KB到500KB正好能绕开下面要讲的ASP默认200KB限制。注意canvas.toBlob的第三个参数在部分国产安卓WebView里会被忽略真遇到压缩无效降级用canvas.toDataURL(image/jpeg, 0.7)再转Blob代价是内存峰值高一倍。进度反馈这块别省。无确认不代表无感知用户拍完如果不马上看到“第2张 43%”这类提示几乎一定会怀疑传丢了、重复拍照。用xhr.upload.onprogress驱动页面顶部一条细进度条比任何toast都直观。我之前见过做出来一点反馈都不给的版本结果现场工人拍完总是不放心未传的图堆在本地反而更乱。4. 服务端收口ASP接收二进制流、落盘命名与回执入库4.1 解析multipart/form-data无组件上传类的拆包逻辑服务端拿到的是multipart/form-data格式的POST请求体里面是boundary分隔的多段数据每段包含文件头Content-Disposition和Content-Type、空行、文件字节。经典ASP接收的常见做法是读Request.BinaryRead(Request.TotalBytes)拿全部字节再用ADODB.Stream解析。这里必须先提醒一个前提默认IIS对经典ASP的请求实体大小限制是200KB不改这个限制下面所有解析代码根本执行不到。先改注册表再写代码顺序别反。具体改动在避坑章节。找到boundary之后核心拆包逻辑可以封装成一个ParseUpload()函数用ADODB.Stream把二进制转成ISO-8859-1文本做定位再从原二进制流里把文件字节截出来% Option Explicit Response.ContentType application/json Response.Charset utf-8 Response.CodePage 65001 Dim ct, bd, raw, totalBytes ct Request.ServerVariables(HTTP_CONTENT_TYPE) 从 Content-Type 里提取 boundary例如 ----WebKitFormBoundaryABC bd -- Trim(Mid(ct, InStr(ct, boundary) 9)) totalBytes Request.TotalBytes raw Request.BinaryRead(totalBytes) 把二进制流转成文本以便定位分隔符ISO-8859-1 按单字节映射不会破坏数据 Dim binStream, textStream Set binStream CreateObject(ADODB.Stream) binStream.Type 1 : binStream.Open binStream.Write raw : binStream.Position 0 Set textStream CreateObject(ADODB.Stream) textStream.Type 2 : textStream.Charset iso-8859-1 textStream.Open binStream.CopyTo textStream textStream.Position 0 Dim bodyText bodyText textStream.ReadText 文件头结束位置第一个空行后的 4 字节 Dim headerEnd, dataStart, fileEnd headerEnd InStr(bodyText, vbCrLf vbCrLf) dataStart headerEnd 4 文件体结束位置下一个 --boundary 出现前需去除末尾的 CRLF fileEnd InStr(dataStart, bodyText, -- bd) - 2 从原始二进制流里按字节复制文件部分写入服务器 Dim outStream, chunk, need Set outStream CreateObject(ADODB.Stream) outStream.Type 1 : outStream.Open binStream.Position dataStart - 1 Do While binStream.Position (fileEnd - 1) need (fileEnd - 1) - binStream.Position If need 8192 Then need 8192 chunk binStream.Read(need) outStream.Write chunk Loop Dim savePath savePath Server.MapPath(/upload/) demo.jpg outStream.SaveToFile savePath, 2 outStream.Close : binStream.Close : textStream.Close Response.Write {code:0,path: savePath } %逻辑拆开看其实就三步先把二进制流用单字节编码映射成文本定位文件体的起始和结束位置再回到二进制流从dataStart到fileEnd逐块复制最后用SaveToFile落盘。need每次最多读8192字节是为了控制内存峰值20MB的请求不会一次性撑爆。fileEnd处的-2是去掉文件体末尾的CRLF如果出现“图片能打开但末尾多一个空行”的情况把-2改成0再试。4.2 落盘与命名日期目录加时间戳避免同名覆盖与中文乱码接收端最容易在命名上翻车的是直接用了客户端传来的原始文件名。移动端拍照的文件名通常叫IMG_20240101_123456.jpg单看没毛病但不同手机、不同用户容易重名中文文件名更麻烦经典ASP的编码处理稍有不慎就是乱码而且中文文件名落到Windows文件系统上后续做日志、做下载链接都要处理URL编码。常见做法是自己生成文件名完全忽略客户端传的filename。Dim fso, dirPath, nowDate, nowTime, randNum, fileName, savePath Set fso CreateObject(Scripting.FileSystemObject) nowDate Year(Now) Right(0 Month(Now), 2) Right(0 Day(Now), 2) nowTime Right(0 Hour(Now), 2) Right(0 Minute(Now), 2) Right(0 Second(Now), 2) Randomize randNum Int(Rnd * 900) 100 fileName nowDate _ nowTime _ randNum .jpg dirPath Server.MapPath(/upload/ nowDate) 目录不存在则创建注意父目录 upload 必须对 IIS_IUSRS 开放写权限 If Not fso.FolderExists(dirPath) Then fso.CreateFolder(dirPath) End If savePath dirPath \ fileName时间戳精确到秒再拼三位随机数同秒重名的概率已经压到千分之一以下对这个场景足够。日期目录的好处是后续到期清理非常方便直接删整个文件夹。Randomize务必调用否则同一进程内连续创建时随机序列可能不变。还要注意Server.MapPath(/upload/...)的斜杠和fso.CreateFolder的反斜杠混用VBScript里这两种写法都能工作但拼字符串时别自己把\丢了。4.3 回执与入库JSON响应与一张三字段的留痕表服务端收到图并落盘之后要回一个清晰的JSON给前端队列前端拿到才知道这张传成功了、可以传下一张Response.Write {code:0,msg:,path: fileName ,size: outStream.Size }前端判断code 0即可path是落盘后的文件名后续查证、补传都靠它。这里要特别注意经典ASP的中文编码三件套Response.Charset utf-8、Response.CodePage 65001、文件保存时用Response.BinaryWrite输出字节。少了CodePageJSON里的中文提示大概率是乱码前端用JSON.parse直接报错。留痕信息入库不用建复杂表三到四个字段足够撑起这个系统字段类型说明id自增主键无意义仅唯一标识task_idvarchar(64)外勤任务或单据编号file_pathvarchar(255)服务器相对路径用于下载和复核create_timedatetime上传时间统计外勤时长和轨迹Set conn Server.CreateObject(ADODB.Connection) conn.Open ProviderMicrosoft.Jet.OLEDB.4.0;Data Source Server.MapPath(/data/app.mdb) conn.Execute INSERT INTO photo_log(task_id, file_path, create_time) VALUES( taskId , fileName ,Now()) conn.Close这段是简化写法生产环境建议用Command对象参数化防SQL注入。task_id这个字段值得从一开始就留后面做按任务统计完成度、做秒传、断点续传都以它作为业务键。5. 排查手册这套系统拍照上传最常翻车的五个点与参数修正5.1 超过200KB就被拦AspMaxRequestEntityAllowed是元凶现象手机端拍完图上传请求发出后马上失败服务端IIS返回“404 找不到页面”或“请求实体过长”ASP脚本根本没有执行。原因IIS经典ASP默认只允许请求实体最大204800字节也就是200KB。前端压缩后一张图就算压到300~500KB也会直接撞上限。这个现象网上问得最多因为报错不像500那样直观很多人以为是路径写错。解决修改注册表并重启IISreg add HKLM\SYSTEM\CurrentControlSet\Services\W3SVC\ASP\Parameters /v AspMaxRequestEntityAllowed /t REG_DWORD /d 20971520 /f iisreset20971520是20MB足够应付压缩后的多张连拍如果团队坚持传原图建议调大到50MB但要注意带宽压力。改完必须iisreset不开新进程不生效。另外IIS的requestFiltering层还有一个默认28.6MB的maxAllowedContentLength20MB不会触及但如果调到30MB以上两处都要改。5.2 文件写入0字节或500IIS_IUSRS与目录权限现象上传接口返回成功服务器目录里确实生成了文件但大小是0KB或者保存时直接500事件日志里看到“拒绝访问”。原因进程池账号对upload目录没有写权限。经典ASP默认工作进程是“应用程序池标识”早期IIS版本对应IIS_IUSRS这个账号只有读取和执行权限要在文件夹安全设置里单独加“修改”。解决文件资源管理器里找到upload目录右键属性 → 安全 → 编辑 → 添加输入IIS_IUSRS勾选“修改”应用到子文件夹。如果多个站点共用一个服务器最好按站点目录分开授权不要给整个网站根目录开写权限——否则一旦有上传漏洞别人就能传一个.asp木马进来直接接管站点。5.3 连传到第三张卡死经典ASP的Session锁现象单张上传测试正常但连续上传第三到五张时请求一个接一个超时页面进度条转圈超过30秒服务器CPU不高但就是卡住。原因经典ASP默认启用Session同一个会话的请求默认带同一个CookieIIS会保证同一Session的请求串行执行也就是“Session锁”。前端队列虽然是两个并发但两个请求属于同一个会话第二个必须等第一个释放Session才能执行。手机拍照上传单张耗时1到3秒越积越多看起来就是卡死。解决上传接口页面关闭会话状态在.asp文件第一行加指令% LanguageVBScript EnableSessionStateFalse %注意这个指令必须在HTML之前。如果业务需要登录态判断别依赖Session改用请求头里的token或参数传用户ID服务端查库校验。关闭上传页的Session不会影响其它页面的登录会话可以放心开。5.4 系统预览打不开照片iPhone的HEIC坑现象安卓拍的照片一切都好iPhone传上去的图文件名是.jpg但Windows图片查看器提示“文件损坏或格式不支持”。原因iPhone照片默认是HEIC格式部分WebView上传时把文件类型识别成image/heic或image/jpeg但你拿到字节流才发现文件头是ftypheic。ASP端没有转码能力只负责保存于是HEIC被改名为JPG存了下来后缀骗人。解决这一步必须在Canvas压缩时处理掉。compressToJpeg函数里用canvas.toBlob(resolve, image/jpeg, quality)输出浏览器会自动把HEIC解码重绘成JPEG前提是WebView内核支持HEIC解码Android 10以上和iOS自带WebView都支持。如果不放心还可以在压缩前读文件头判断if (file.type image/heic || file.type image/heif) { // 走 Canvas 重绘强制输出 JPEG }另外一个相关坑是拍照方向部分安卓手机竖拍的照片EXIF里有Orientation6Canvas重绘前不处理方向的话服务器上存的是横图。处理方案是在绘制前读取EXIF Orientation旋转后再画很多老项目就是栽在这上面。5.5 拍照框点了没反应onReceiveValue的回调缺失现象Android WebView里首次点击拍照按钮正常拍完传完第二次再点没有任何反应或者压根第一次就没反应、直接打开文件管理器。原因onShowFileChooser里没有处理“用户取消”分支导致ValueCallback一直被占着WebView认为还在等文件选择结果另一种可能是onShowFileChooser没被触发直接走到了WebView默认的文件选择逻辑。解决先确认回调逻辑onActivityResult无论成功失败都要调onReceiveValue成功后回调URI数组失败后回调null并且在回调后把uploadMessage置空。这个习惯能根治“第二次失灵”。再确认页面input带没带captureenvironment不带capture属性时即使接管了onShowFileChooserFileChooserParams.isCaptureEnabled()是false很多设备会走相册而不是相机。排查顺序是先在原生Chrome打开页面点input看有没有“相机/相册”选项再回WebView里加日志看onShowFileChooser是否被调用两步就能定位问题在哪一层。6. 收尾三十连拍压测清单与两个高性价比的进阶改造6.1 压测步骤从单拍一张到三十连拍的四个观察点上线前别急着给现场用找一台手机连同一Wi-Fi按下面的顺序压一遍第一步单拍一张记下从按下快门到页面出现“已上传”反馈的时间。期望在3到5秒内超过10秒就查压缩参数和带宽。第二步不用压缩直接连拍十张每秒一张不停顿。拍完后去服务器目录数文件find /upload -name *.jpg | wc -lWindows PowerShell用(Get-ChildItem D:\site\upload -Filter *.jpg).Count。数量等于10说明队列没丢再看文件时间戳如果第2张和第3张间隔超过30秒说明Session锁或并发参数有问题。第三步连拍三十张期间把手机切到飞行模式5秒再切回来。结束后对比客户端本地记录和服务端文件数重点看有没有重复文件——出现重复就意味着重试逻辑不幂等需要给每次上传加一个客户端生成的client_id服务端按这个ID查重再做响应。第四步观察服务端IIS日志的状态码分布。全是200正常出现413或404回到AspMaxRequestEntityAllowed出现500查目录权限和ACCESS数据库连接是否超时。6.2 进阶改造幂等键、秒传与到底要不要做缩略图压测完成、基本功能稳定之后值得做的改造按性价比排序第一是幂等键前端在每个文件的FormData里加一个client_id字段用时间戳加随机数就行服务端入库时查client_id是否已存在存在就直接返回成功不重复保存。这一条能消除所有重试带来的重复文件问题改造成本极低建议上线前就做。第二是秒传客户端用spark-md5计算文件MD5上传前先请求一个check.asp?md5xxx服务端查库发现已有同名MD5就直接返回成功前端跳过上传。外勤场景中同一个点位反复拍照很常见秒传能省一半流量。缩略图这个改造我建议看需求再上。经典ASP做图像处理得像变魔术一样依赖WIA COM组件Windows Server Core上还不一定装得了稳定性和兼容性都不可控。更实际的方案是前端压缩时生成一个120px的小图Blob跟着原图一起传上去服务端只存文件、不做处理。一张小图几KB流量压力几乎为零但列表页加载会快一个量级。我见过的案例里凡是后补缩略图需求的最后都后悔没一开始就传双图。这套系统我接手维护过两轮最深的教训是难点从来不在“拍照”那一秒而在把“传完没、丢没丢、重没重复”这件事讲清楚。上线前老老实实做一轮三十连拍压测比看一百遍代码都管用。希望帮到你。本文还有配套的精品资源点击获取