Next.js中通过Sequelize将文件存入MySQL及后续访问方法
实现方案
首先明确:单个大小在5MB以内的常规附件(文档、图片)可以直接存在MySQL中,单文件超过10MB不推荐存在数据库,会大幅拖慢查询性能,这类大文件建议存磁盘/对象存储,数据库仅保存访问路径即可。以下是存MySQL+应用内访问的完整实现步骤:
1. 先修正现有前端代码的问题
你当前写的上传逻辑有两个明显问题:
- 加了
multiple多文件属性,但代码只读取了e.target.files[0],选中多个文件时只会处理第一个 - 直接拿完整dataURL传后端会携带冗余头信息,浪费传输和存储空间
修正后的前端上传处理逻辑参考:
<Input name="supportingDocuments" accept=".txt, .pdf, .jpg, .jpeg, .docx" id="contained-button-file" multiple type="file" onChange={(e)=>{ const files = Array.from(e.target.files || []); const fileList = []; files.forEach(file => { const fileReader = new FileReader(); fileReader.onload = () => { if (fileReader.readyState === 2) { fileList.push({ name: file.name, type: file.type, dataUrl: fileReader.result }); // 所有文件读取完成后再赋值给表单 if (fileList.length === files.length) { formik.setFieldValue('supportingDocuments', fileList); } } }; fileReader.readAsDataURL(file); }) }} />
表单提交时,把读取到的文件列表通过POST请求发给后端接口即可。
2. MySQL表结构设计
用LONGBLOB类型存文件二进制内容,不要用普通BLOB(最大仅64KB)、MEDIUMBLOB(最大16MB),容易存不下常规PDF/高清图片,建表SQL如下:
CREATE TABLE `supporting_documents` ( `id` int unsigned NOT NULL AUTO_INCREMENT, `file_name` varchar(255) NOT NULL COMMENT '原始文件名', `file_type` varchar(100) NOT NULL COMMENT '文件MIME类型', `file_data` longblob NOT NULL COMMENT '文件二进制内容', `created_at` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3. 编写Next.js接口实现文件入库
禁止直接在前端代码里连MySQL,所有数据库操作必须放在Next.js服务端API路由里实现。注意不要把完整dataURL字符串直接存库,要拆分出base64内容转成二进制Buffer存储,能省30%左右的存储空间。
以App Router为例,新建app/api/documents/route.ts文件处理上传请求:
import { NextResponse } from 'next/server'; import mysql from 'mysql2/promise'; // 初始化数据库连接池,全局复用不要每次请求新建 const pool = mysql.createPool({ host: process.env.MYSQL_HOST, user: process.env.MYSQL_USER, password: process.env.MYSQL_PASSWORD, database: process.env.MYSQL_DATABASE, connectionLimit: 10, }); export async function POST(request: Request) { try { const documents = await request.json(); const insertRes = []; for (const doc of documents) { // 去掉dataURL前缀,比如data:application/pdf;base64, const base64Content = doc.dataUrl.split(',')[1]; // 转二进制Buffer const fileBuffer = Buffer.from(base64Content, 'base64'); const [result] = await pool.execute( 'INSERT INTO supporting_documents (file_name, file_type, file_data) VALUES (?, ?, ?)', [doc.name, doc.type, fileBuffer] ); insertRes.push({ id: (result as any).insertId, name: doc.name }); } return NextResponse.json({ success: true, data: insertRes }); } catch (error) { return NextResponse.json({ success: false, msg: '文件上传失败' }, { status: 500 }); } }
记得把数据库连接配置写到环境变量里,不要硬编码在代码中。
4. 实现应用内文件访问
再写一个GET接口,根据文件ID从数据库读出二进制内容,设置正确的Content-Type响应头返回即可,浏览器会自动处理预览/下载逻辑。
新建app/api/documents/[id]/route.ts文件:
import { NextResponse } from 'next/server'; import mysql from 'mysql2/promise'; const pool = mysql.createPool({ // 和上面一致的连接池配置 host: process.env.MYSQL_HOST, user: process.env.MYSQL_USER, password: process.env.MYSQL_PASSWORD, database: process.env.MYSQL_DATABASE, connectionLimit: 10, }); export async function GET( _request: Request, { params }: { params: { id: string } } ) { try { const [rows] = await pool.execute( 'SELECT file_name, file_type, file_data FROM supporting_documents WHERE id = ?', [params.id] ); const targetDoc = (rows as any[])[0]; if (!targetDoc) { return NextResponse.json({ msg: '文件不存在' }, { status: 404 }); } // 配置响应头 const headers = new Headers(); headers.set('Content-Type', targetDoc.file_type); // 需要强制下载而不是浏览器预览的话,打开下面这行注释 // headers.set('Content-Disposition', `attachment; filename="${encodeURIComponent(targetDoc.file_name)}"`); return new NextResponse(targetDoc.file_data, { headers }); } catch (error) { return NextResponse.json({ msg: '文件加载失败' }, { status: 500 }); } }
前端使用的时候,直接把接口地址填到对应位置即可:
- 图片渲染:
<img src={/api/documents/${docId}} alt="附件" /> - 文件预览/下载:
<a href={/api/documents/${docId}} target="_blank">查看附件</a>
注意事项
- 生产环境必须加文件大小、类型校验,不要完全信任前端传的MIME类型,防止用户上传恶意可执行文件
- 存库的文件单份大小尽量控制在5MB以内,过大会导致数据库查询变慢、接口响应超时
- 数据库操作必须用连接池,避免频繁新建连接打满数据库连接数
- 如果后续要支持大文件上传,建议把文件存到本地磁盘/云存储服务,数据库只存文件的访问路径,不要存二进制内容
内容的提问来源于stack exchange,提问作者shardul
相关产品推荐
相关产品推荐

