
简介一款基于Flash的摄像头拍照上传源代码包专为需要在网页中集成拍照上传能力的开发者设计能够快速对接ASP.NET、PHP、Java等主流后端语言适用于在线认证、表单头像采集、用户资料上传等需要调用摄像头抓拍并提交图片的业务场景。压缩包为zip格式共5个文件包含2个HTML页面用于展示实际调用方式与示例、1个SWF编译成品可直接部署运行、1个FLA源文件方便二次开发修改以及1个SWD辅助调试文件整体体积仅127KB轻量且目录结构清晰。代码基于网上公开示例自行整理默认输出分辨率为320×240若需其他尺寸可参照FLA源码中的逻辑自行调整摄像头参数与输出配置。目前已有246人学习下载适合希望快速掌握Flash摄像头调用原理并想了解网页端与服务器端数据对接细节的初中级Web开发者参考。 前阵子清理旧硬盘翻出一个压箱底的项目Flash摄像头拍照上传源代码。这套代码是当年给一个招聘网站做的用于在线拍摄个人形象照做完后陆续有同行私信找我要实现思路因为它最被看中的一点就是“一套前端代码后端不管你是asp.net、php还是java都能直接对接”。这篇就把这套老源码的完整实现拆一遍从Flash如何调摄像头到Base64图片数据怎么设计成通用接口再到迁移到现代HTML5方案时怎么复用旧接口。如果你正在维护老系统、或者想了解跨语言上传接口怎么设计这篇应该能给你一些参考。1. 当年为什么选Flash做摄像头拍照不选别的方案1.1 浏览器要调摄像头绕不开插件现在用navigator.mediaDevices.getUserMedia()就能直接打开摄像头但时间退回2010年前后HTML5还在草案阶段浏览器原生能力远没有今天这么完整。当时想在网页里做“拍照上传”市面上主流的方案无非三种ActiveX控件、Java Applet、Flash插件。ActiveX只能跑在IE上系统是Windows还得允许下载控件跨浏览器直接废掉。Java Applet倒是跨平台但用户必须装JRE启动慢而且每次弹安全提示。Flash插件在当时的覆盖率极高浏览器几乎人手一个object标签一嵌就能用跨浏览器、跨操作系统都相对稳定。所以选Flash做摄像头采集不是因为它完美而是在当时的条件下综合成本最低。1.2 Flash摄像头拍照原本就是“截图”思维Flash端处理摄像头并不是在“录像”而是把摄像头当作一位不停往屏幕上送画面的快递员。Camera类负责打开设备、设置分辨率和帧率Video对象负责把连续的视频帧显示到画面里真正拍照的关键动作是用BitmapData.draw(video)把视频当前这一帧“截”下来再交给编码器生成图片数据。这个思路放到今天依然好理解拍照不是保存视频流而是“对视频画面做一次截屏”。对头像、证件照这类静态图片上传场景这种做法最省资源后端也根本无需处理流媒体只要接收一张图片文件即可。2. 源码核心拍照、压缩与Base64编码2.1 摄像头初始化与画面显示下面是Flash端初始化的核心代码ActionScript 3.0编写import flash.media.Camera; import flash.media.Video; import flash.display.BitmapData; import com.adobe.images.JPGEncoder; import com.adobe.utils.Base64; var cam:Camera Camera.getCamera(); if (cam null) { trace(没有检测到可用摄像头); return; } cam.setMode(640, 480, 30); var video:Video new Video(640, 480); video.attachCamera(cam); addChild(video);这里有三个容易被忽略的细节Camera.getCamera()如果返回null说明没有摄像头或被其他程序占用不能直接往下走。setMode(640, 480, 30)里的30是帧率帧率越高画面预览越流畅但拍照本身不依赖它因为最终只截取一帧。attachCamera之后建议等待一两帧再拍照否则第一帧画面可能是黑的。实际项目里我习惯在进入拍照页后延迟500毫秒再允许用户点击“拍照”。2.2 拍照、JPEG压缩和Base64结果核心拍照代码function takePhoto():void { var bmpData:BitmapData new BitmapData(640, 480); bmpData.draw(video); var encoder:JPGEncoder new JPGEncoder(80); var bytes:ByteArray encoder.encode(bmpData); var photoStr:String Base64.encode(bytes); var vars:URLVariables new URLVariables(); vars.photo photoStr; vars.sid 任意业务参数; var req:URLRequest new URLRequest(http://yourdomain.com/upload.php); req.method URLRequestMethod.POST; req.data vars; var loader:URLLoader new URLLoader(); loader.load(req); }BitmapData.draw(video)干的事情就是前面说的“截屏”。截下来之后用JPGEncoder(80)做JPEG压缩80是质量参数调低能减小体积但画质会有损失。实测中640x480的头像图质量80时Base64字符串大约在20KB到40KB之间作为POST参数完全可行。这里为什么不用二进制流直接上传因为Flash的URLVariables只能携带字符串类型的请求参数传原始ByteArray要么需要走URLLoader的文件流模式要么用Socket这类特殊协议这些都会让后端对接变得复杂。转成Base64后结果本质上就是一段普通字符串任何语言的POST表单都能承载这就为“对接任何语言”打下了基础。需要提醒的是AS3标准库里没有现成的Base64和JPEG编码器我使用的是as3corelib这个开源库。源码里需要有com.adobe.images.JPGEncoder和com.adobe.utils.Base64这两个类。3. 接口协议设计为什么能做到“对接任何语言”3.1 一套约定走天下整套代码之所以能对接asp.net、php、java核心不是Flash端写了多复杂的代码而是上传接口被刻意设计成了一个“所有后端都天然支持”的HTTP POST表单请求。协议约定如下参数名类型说明photostring摄像头截图经JPEG编码后再用Base64生成的字符串sidstring业务标识例如用户ID、会话ID按需传递后端只需要做两件事拿到photo参数把它解码成二进制写入文件。这在任何一个后端语言里都是标准能力不需要引入特殊SDK也不需要关注前端是不是Flash。这其实是跨语言对接最朴素也最实用的思路最小化接口自定义程度把请求做得越通用越好。3.2 PHP、ASP.NET、Java三种后端接收示例PHP版?php if ($_SERVER[REQUEST_METHOD] POST isset($_POST[photo])) { $base64 $_POST[photo]; $base64 str_replace( , , $base64); $imgData base64_decode($base64); if ($imgData false || strlen($imgData) 100) { exit(图像数据无效); } $filename date(YmdHis) . _ . mt_rand(1000, 9999) . .jpg; file_put_contents(__DIR__ . /uploads/ . $filename, $imgData); echo $filename; exit; }ASP.NET C#版string photo Request.Form[photo]; if (!string.IsNullOrEmpty(photo)) { byte[] bytes Convert.FromBase64String(photo); string path Server.MapPath(~/uploads/) Guid.NewGuid().ToString(N) .jpg; System.IO.File.WriteAllBytes(path, bytes); Response.Write(Path.GetFileName(path)); }Java Servlet版String photo request.getParameter(photo); if (photo ! null !photo.isEmpty()) { byte[] bytes java.util.Base64.getDecoder().decode(photo); String path getServletContext().getRealPath(/uploads/) UUID.randomUUID().toString() .jpg; java.nio.file.Files.write(java.nio.file.Paths.get(path), bytes); response.getWriter().write(new java.io.File(path).getName()); }三个版本做的事情完全一致。特别说明一下PHP解码时最好做一次str_replace( , , $base64)因为HTTP传输过程中弱智的中间环节可能把Base64里的当成空格处理加上这一行可以兜底。3.3 后端不管前端用不用Flash这套接口设计最值得学习的一点是后端完全不关心前端用什么技术拍照。你上传一段Base64字符串后端不需要知道这个字符串到底是Flash生成的还是以后某个HTML5页面生成的。只要图片格式是JPEG、能解码、能落盘就满足业务需求。这种“前后端通过普通HTTP文本数据交互不依赖任何客户端专用二进制协议”的做法比当时流行的Socket推送、AMF协议、甚至WebService要轻量得多。维护过老项目的人都有体会越是看起来高级的专有协议升级换代时越容易卡脖子。反而是这种“土办法”式的接口熬过了技术换代。4. 实战中踩过的坑和排查链路4.1 摄像头开不起来先查跨域文件Flash播放器有严格的安全沙箱机制。SWF文件放在A域但上传接口或者摄像头权限调用涉及B域就很可能被安全策略拦住表现就是一直拿不到摄像头数据或者上传请求直接失败。解决方法是把crossdomain.xml放到服务器根目录cross-domain-policy allow-access-from domain* / /cross-domain-policy实际测试中只放这一个文件不一定能解决所有问题还需要在AS3代码里显式调用Security.allowDomain(*)。如果SWF是在本地磁盘上调试摄像头权限还会更麻烦建议直接把SWF放到服务器环境里测试不要拿file://协议跑。4.2 上传后后端收到空串或数据被截断这是最典型的坑。用户在本地拍照成功但请求到后端发现$_POST[photo]是空或者图片只有上半张。排查链路一般是这样先用Postman模拟POST一条很长的Base64字符串到后端看后端能否完整保存图片。如果能说明后端本身的限制没配好如果也不能大概率不是Flash的问题。检查服务器端的请求体大小限制。PHP要看post_max_size和upload_max_filesizeASP.NET要用maxRequestLengthJava的Tomcat要看maxPostSize。我见过最隐蔽的是Tomcat默认maxPostSize是2MBBase64字符串一转码就超了。检查Flash端用的URLVariables它默认会做URL编码而Base64字符串里包含、/、这三种特殊字符。理论上URL编码能正确处理但如果有人手动拼接字符串而不是用URLVariables就会丢掉导致解码失败。这也是我坚持用现成URLVariables的原因之一。4.3 用户点了“拒绝”摄像头再也打不开Flash的摄像头授权是浏览器插件级别的设置。如果用户在设置里勾了“记住拒绝”那么Flash端再怎么重试也拿不到摄像头。遇到这种情况只能引导用户去Flash全局设置面板里把当前网站的摄像头权限改成“允许”。更省事的做法是在调用摄像头之前先检测camera.names.length如果长度为0直接提示用户检查浏览器设置避免卡在“拍照”按钮上无从下手。5. 这套老代码在当代的迁移价值前端换壳后端不动5.1 老接口可以直接给HTML5用Flash插件如今已经被浏览器淘汰但当年定的这套“POST一个Base64字符串”接口几乎可以无缝迁移到现代方案。新的前端代码依旧把图片转成Base64字符串还是POST到原来的upload.php或者upload.do后端一行代码都不用改。这是老项目里最值得庆幸的事。技术映射关系大致如下Flash组件HTML5等价物Camera.getCamera()navigator.mediaDevices.getUserMedia({ video: true })Video显示对象页面里的video元素BitmapData.draw(video)canvas.getContext(2d).drawImage(video, 0, 0)JPGEncoder.encode()canvas.toDataURL(image/jpeg, 0.8)Base64.encode()toDataURL结果自带的Base645.2 一份可复用的新前端核心代码const video document.querySelector(video); const canvas document.createElement(canvas); navigator.mediaDevices.getUserMedia({ video: true }) .then(stream { video.srcObject stream; }) .catch(err { console.error(摄像头打开失败, err); }); function takePhotoForOldBackend() { canvas.width video.videoWidth; canvas.height video.videoHeight; canvas.getContext(2d).drawImage(video, 0, 0); const base64 canvas.toDataURL(image/jpeg, 0.8).split(,)[1]; const params new URLSearchParams(); params.append(photo, base64); params.append(sid, 123456); fetch(/upload.php, { method: POST, body: params }) .then(res res.text()) .then(filename console.log(上传成功, filename)); }注意toDataURL(image/jpeg, 0.8)得到的结果前面会带data:image/jpeg;base64,前缀传给老接口时必须把这部分切掉否则后端解码会失败。老代码只认纯Base64字符串不带mime前缀。5.3 迁移中必须处理的差异getUserMedia和Flash有几点本质差异迁移时不能照搬旧逻辑getUserMedia强制要求HTTPS或者localhost环境HTTP环境下浏览器会直接拒绝打开摄像头。项目迁移期间需要把页面切换到HTTPS这是最容易被忽略的一步。老系统里如果还在用IE8、IE9这类老浏览器HTML5方案没有任何意义需要先确认目标用户群是否已经脱离这些浏览器。今天移动端拍照需求也可以用input typefile acceptimage/* captureuser实现但它是“选照片”而不是“拍完直接传”体验和需求不同不要混为一谈。我自己的迁移经历里最后最费力的不是前端代码而是说服业务方把老IE浏览器换掉。一旦运行环境跟上Flash到HTML5的替换其实半天就能完成。这也让我越发觉得当年写这套源码时坚持用普通HTTP协议和Base64字符串是做过最正确的一个技术决策。现在回头再看老代码的语法过时了类库也没人维护了但那套接口设计依旧可以复制到任何新项目里。传图片也好传其他文本数据也好先把协议定义清楚了前后端各自实现自己的部分互不拖累才是这套源码留给我最大的经验。本文还有配套的精品资源点击获取