You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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位作为子目录),不会直接显示你设置的文件名和路径。可通过以下方式访问:

  1. 站点管理 -> 服务器 -> 文件管理,找到对应组件和filearea的文件;
  2. 通过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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 15:59:57