使用@mysql/xdevapi将ArrayBuffer存入MySQL Blob异常及回读方案咨询
问题背景
需要实现通过HTML input控件获取用户上传的文件(以图片为主,支持任意类型),借助@mysql/xdevapi将文件对应的ArrayBuffer存入MySQL的Blob类型字段。
所用技术栈
- 客户端框架:Angular + Electron.js
- 数据库连接:通过
@mysql/xdevapi实现MySQL访问
已实现逻辑
- 监听input控件获取用户选择的文件,代码如下:
processInputFile(event) { var file: File = event.target.files[0]; file.arrayBuffer().then((value) => { this.fileToStore = value; }); }
- 编写MySQL更新逻辑,目标为更新
step表的user_comment、user_file字段,代码如下:
UpdateStep = (step: TestSteps, fileToStore: ArrayBuffer): Promise<void> => { return new Promise<void>((resolve, reject) => { if (this.session === undefined) { reject(new Error('session is undefined')); return; } var sch = this.session.getSchema("gtb"); var step_table = sch.getTable('step'); step_table.update().set('user_comment', step.user_comment).set('user_file', fileToStore).where('id = ' + step.id).execute().catch(error => { console.error(error); }); resolve(); }) }
异常现象
原始文件大小为165KB,但从MySQL Workbench中下载存储的Blob字段内容仅得到2KB的无效文件,需要定位问题原因,同时实现从数据库读取对应文件、支持用户下载的完整逻辑。
问题根因
存入文件体积异常由两个问题共同导致:
- 异步逻辑错误:调用
execute()后未等待数据库写入操作执行完成就直接触发resolve(),写入过程抛出的类型不匹配、连接异常等错误仅在内部catch打印日志,没有向外抛出,外层逻辑无法感知写入失败,经常出现流程走完但数据未正常落库的情况。 - 二进制参数类型不匹配:
@mysql/xdevapi不支持直接识别原生ArrayBuffer作为Blob字段入参,直接传入时驱动会将ArrayBuffer转为对象字符串标识(类似[object ArrayBuffer]的短文本)存入字段,完全没有写入真实的二进制文件内容,这是文件体积只有2KB的核心原因。
修复方案
1. 写入逻辑修复
首先需要将前端拿到的ArrayBuffer转为Node.js原生Buffer类型(Electron环境下可直接调用Node API,无需额外依赖),这是@mysql/xdevapi识别Blob二进制类型的正确格式,同时修正Promise异步等待逻辑,确保写入完成后再返回结果。
修正后的写入代码:
UpdateStep = (step: TestSteps, fileToStore: ArrayBuffer): Promise<void> => { return new Promise<void>(async (resolve, reject) => { try { if (this.session === undefined) { throw new Error('session is undefined'); } // 核心转换:ArrayBuffer转Node.js Buffer,适配xdevapi的Blob入参要求 const fileBuffer = Buffer.from(fileToStore); const sch = this.session.getSchema("gtb"); const step_table = sch.getTable('step'); // 等待写入执行完成再返回,改用参数绑定避免SQL注入 await step_table.update() .set('user_comment', step.user_comment) .set('user_file', fileBuffer) .where('id = :stepId') .bind('stepId', step.id) .execute(); resolve(); } catch (error) { console.error(error); reject(error); } }) }
字段类型提示:MySQL的TINYBLOB类型最大仅支持存储255KB内容,超过就会自动截断。如果后续需要存储更大的文件,建议将user_file字段类型修改为MEDIUMBLOB(最大支持16MB)或LONGBLOB(最大支持4GB)。
优化建议:建议在step表额外新增
file_name(存储原始文件名)、file_mime(存储文件MIME类型)两个字段,避免下载时出现文件后缀不匹配、无法正常打开的问题。
2. 文件读取与下载逻辑实现
从xdevapi读取到的Blob字段值本身就是Buffer类型,可以直接转为前端Blob对象,通过临时URL触发浏览器下载,示例代码:
// 读取数据库存储的文件 const getStepFile = async (stepId: number): Promise<{fileName: string, fileBuffer: Buffer, mime: string}> => { const sch = this.session.getSchema("gtb"); const step_table = sch.getTable('step'); const result = await step_table.select('user_file', 'file_name', 'file_mime') .where('id = :id') .bind('id', stepId) .execute(); const rows = result.fetchAll(); if (rows.length === 0) throw new Error('对应步骤的文件记录不存在'); return { fileBuffer: rows[0][0], fileName: rows[0][1], mime: rows[0][2] } } // 触发用户下载 const downloadStepFile = async (stepId: number) => { const { fileBuffer, fileName, mime } = await getStepFile(stepId); // 转为前端Blob对象,指定正确MIME类型 const blob = new Blob([fileBuffer], { type: mime }); const tempUrl = URL.createObjectURL(blob); // 创建临时a标签触发下载 const downloadLink = document.createElement('a'); downloadLink.href = tempUrl; downloadLink.download = fileName; document.body.appendChild(downloadLink); downloadLink.click(); // 清理临时DOM和URL对象 document.body.removeChild(downloadLink); URL.revokeObjectURL(tempUrl); }
内容的提问来源于stack exchange,提问作者Mosky
相关产品推荐
相关产品推荐

