Moodle环境下JS/HTML向PHP传拍照图片及存储的技术问题
Moodle驾照照片提交活动开发问题
我正在Moodle平台开发一项用户提交驾照照片的活动,要求用户必须即时拍摄照片,不能从本地目录选择上传,照片需保存至Moodle系统供教师审阅。目前面临两个核心问题:
- 希望不通过POST/GET转base64的方式,将前端(JavaScript/HTML)的图片传输至后端PHP;
- 不熟悉Moodle File API的使用方法及图片存储位置。
现有代码实现
HTML代码
<table> <tr> <td><video autoplay="true" id="video"></video></td> <td><canvas id="canvas" width="300" height="300"></canvas></td> <td><button id="btn"> Take Picture </button></td> <td><button id="sub"> 提交 </button></td> <td><canvas id="photo"></canvas></td> </tr> </table>
JavaScript代码
const video = document.getElementById('video') const btn = document.getElementById('btn') const canvas = document.getElementById('canvas') const submit = document.getElementById('sub') const photo = document.getElementById('photo') var base64 function getBase64Image(img) { canvas.width = 500; canvas.height = 375; var pht = canvas.getContext("2d"); pht.drawImage(img, 0, 0); var dataURL = canvas.toDataURL("image/png"); alert('已转为base64'); return dataURL.replace(/^data:image\/?[A-z]*;base64,/); } if ('mediaDevices' in navigator && 'getUserMedia' in navigator.mediaDevices) { navigator.mediaDevices.getUserMedia({video: true}) .then(stream => { video.srcObject = stream }) .catch(error => { console.log('访问摄像头时出错。') }) } submit.addEventListener('click', () =>{ var data = new FormData(); data.append('front', base64); var xmlhttp = new XMLHttpRequest() xmlhttp.open("POST", "view.php"); xmlhttp.send(data); alert('已提交'); }) btn.addEventListener('click', () =>{ base64 = getBase64Image(video); })
PHP代码
if(isset($_POST['front'])) { $img = $_POST['front']; // 定义文件信息 $context = context_module::instance($cm->id); $component = 'mod_driverslicense'; // 你的活动组件名称 $filearea = 'submissions'; // 照片要保存的文件区域名称 $itemid = 0; // 与照片关联的项目ID(通常无特定ID时设为0) $filename = 'test7.png'; // 文件名 $filecontent = base64_decode($img); // 文件内容(二进制) // 将文件保存到Moodle $file = new stdClass(); $file->component = $component; $file->contextid = $context->id; $file->filearea = $filearea; $file->itemid = $itemid; $file->filepath = '/'; $file->filename = $filename; $file->timecreated = time(); $file->timemodified = time(); $fs = get_file_storage(); $uploaded_file = $fs->create_file_from_string($file, $filecontent); // 检查文件是否保存成功 if ($uploaded_file) { \core\notification::info('文件已成功保存。'); } else { \core\notification::info('文件保存失败。'); } }
解决方案
1. 不使用Base64,直接传输二进制图片到后端
通过canvas.toBlob()将画布内容转为Blob对象,直接添加到FormData发送,避免Base64编码的额外开销:
修改后的JavaScript代码
const video = document.getElementById('video') const btn = document.getElementById('btn') const canvas = document.getElementById('canvas') const submit = document.getElementById('sub') const photo = document.getElementById('photo') let capturedBlob = null; // 初始化摄像头 if ('mediaDevices' in navigator && 'getUserMedia' in navigator.mediaDevices) { navigator.mediaDevices.getUserMedia({video: true}) .then(stream => { video.srcObject = stream }) .catch(error => { console.log('访问摄像头时出错。') }) } // 拍照逻辑 btn.addEventListener('click', () => { canvas.width = 500; canvas.height = 375; const ctx = canvas.getContext("2d"); ctx.drawImage(video, 0, 0, 500, 375); // 将画布内容转为Blob对象 canvas.toBlob(blob => { capturedBlob = blob; // 在photo画布预览拍摄结果 const url = URL.createObjectURL(blob); const img = new Image(); img.onload = () => { photo.width = img.width; photo.height = img.height; photo.getContext("2d").drawImage(img, 0, 0); URL.revokeObjectURL(url); }; img.src = url; alert('已拍摄照片'); }, 'image/png'); }); // 提交逻辑 submit.addEventListener('click', () =>{ if (!capturedBlob) { alert('请先拍摄照片'); return; } const data = new FormData(); // 将Blob作为文件添加到FormData,指定文件名 data.append('front', capturedBlob, `drivers_license_${Date.now()}.png`); const xmlhttp = new XMLHttpRequest() xmlhttp.open("POST", "view.php"); xmlhttp.onload = () => { alert(xmlhttp.status === 200 ? '提交成功' : '提交失败'); }; xmlhttp.send(data); })
修改后的PHP接收代码
if(isset($_FILES['front']) && $_FILES['front']['error'] === UPLOAD_ERR_OK) { $tempFilePath = $_FILES['front']['tmp_name']; $filename = $_FILES['front']['name']; // 定义文件关联信息 $context = context_module::instance($cm->id); $component = 'mod_driverslicense'; $filearea = 'submissions'; // 用用户ID+时间戳作为itemid,避免同用户多次提交覆盖 $itemid = $USER->id . '_' . time(); $filepath = '/'; // 构建文件对象 $file = new stdClass(); $file->component = $component; $file->contextid = $context->id; $file->filearea = $filearea; $file->itemid = $itemid; $file->filepath = $filepath; $file->filename = $filename; $file->timecreated = time(); $file->timemodified = time(); // 从临时文件直接保存到Moodle $fs = get_file_storage(); $uploaded_file = $fs->create_file_from_pathname($file, $tempFilePath); // 反馈保存结果 if ($uploaded_file) { \core\notification::info('文件已成功保存。'); } else { \core\notification::info('文件保存失败。'); } }
2. Moodle File API使用说明及存储位置
API核心用法要点
- 上下文(Context):必须指定文件所属上下文,活动模块用
context_module::instance($cm->id),用户个人文件用context_user::instance($USER->id)。 - 组件(Component):格式为
mod_xxx,对应你的活动插件名称(如mod_driverslicense),需确保插件已正确注册。 - 文件区域(Filearea):自定义分类名称,如
submissions用于区分用户提交的文件,同一组件下可设多个filearea。 - Itemid:用于关联文件到具体记录(如用户提交ID),建议用
$USER->id + 时间戳或活动内的提交ID,避免文件覆盖。 - 存储对象:通过
get_file_storage()获取,使用create_file_from_string(从字符串)或create_file_from_pathname(从文件路径)保存文件。
文件实际存储位置
Moodle文件默认存在moodledata/filedir目录下,采用哈希分目录结构(文件哈希前2位+接下来2位作为子目录),不会直接显示你设置的文件名和路径。可通过以下方式访问:
- 站点管理 -> 服务器 -> 文件管理,找到对应组件和filearea的文件;
- 通过API生成可访问的URL:
// 示例:获取已保存文件的访问链接 $fs = get_file_storage(); $files = $fs->get_directory_files($context->id, $component, $filearea, $itemid, '/', false); foreach ($files as $file) { $fileurl = moodle_url::make_pluginfile_url( $file->get_contextid(), $file->get_component(), $file->get_filearea(), $file->get_itemid(), $file->get_filepath(), $file->get_filename() ); echo '<a href="' . $fileurl . '">查看驾照照片</a>'; }
权限控制
需在活动插件的db/access.php中添加教师查看提交的权限:
$capabilities = array( 'mod/driverslicense:viewsubmissions' => array( 'captype' => 'read', 'contextlevel' => CONTEXT_MODULE, 'archetypes' => array( 'teacher' => CAP_ALLOW, 'editingteacher' => CAP_ALLOW, 'manager' => CAP_ALLOW ) ), );
内容的提问来源于stack exchange,提问作者Krupp J
相关产品推荐
相关产品推荐

