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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 05:33:18