使用express-fileupload向SQL Server上传图片失败问题求助
问题描述
我用 Node.js、Express、express-fileupload 和 SQL Server 开发签名图片上传至数据库的功能,执行代码时遇到如下错误:
Cannot bulk load because the file "\tmp\tmp-1-1686629760086" could not be opened
我的存储过程
CREATE PROCEDURE Insert_Signatures_SP (@SignatureUri NVARCHAR(1000), @UserID INT) AS DELETE FROM Signatures WHERE UserID_FK = @UserID; DECLARE @err INT DECLARE @sql NVARCHAR(MAX) SET @sql = N'INSERT INTO Signatures (UserID_FK, [Signature]) SELECT @UserID, BulkColumn FROM OPENROWSET ( Bulk '''+ @SignatureUri +''', SINGLE_BLOB ) AS [Signature]'; EXEC @err = sp_executesql @sql, N'@UserID INT', @UserID; RETURN(@err); GO
EJS 文件
<form action="/admin-signatures-upload" method="post" enctype="multipart/form-data"> <input type="hidden" class="hiddenField" id="userid" name="userid" value="<%= user.UserID %>" required> <button id="<%= user.UserID %>" class="btn1 catbtn" >Upload Signature</button> <div> <input type="file" name="myImage"> </div> </form>
server.js 文件
const fileUpload = require('express-fileupload'); app.use(fileUpload({ useTempFiles : true, tempFileDir : '/tmp/' })); app.post("/admin-signatures-upload", async function (req, res) { try { if (!req.files) { return res.status(400).send("No files were uploaded."); } console.log('Path = ' + file.tempFilePath); await pool.connect(); const resultUserCategories = await pool.request() .input('SignatureUri', file.tempFilePath) .input('UserID', req.body.id) .execute(`Insert_Signatures_SP`); await pool.close(); res.redirect('/admin-signatures'); } catch (error) { } })
解决方案
核心问题
OPENROWSET(BULK ...) 是在SQL Server 服务器端执行的,它读取的文件路径必须是服务器能访问到的路径。你现在传的是 Node.js 服务所在机器的 /tmp 临时文件路径——如果 SQL Server 和 Node.js 不在同一台机器,服务器根本找不到这个文件;就算在同一台机器,SQL Server 服务账号也大概率没有 /tmp 目录的读取权限。
另外你的代码还有几个小bug:
- server.js 里未定义
file变量,应该从req.files中获取上传的文件 - 表单隐藏字段名是
userid,但代码里用req.body.id,参数名不匹配 - 每次请求都调用
pool.connect()和pool.close()不合理,连接池应该复用,无需每次关闭
推荐修复方案(直接传递二进制数据)
绕开文件路径问题,直接把文件的二进制数据传给存储过程:
1. 修改存储过程
去掉 OPENROWSET,直接接受二进制参数插入:
CREATE PROCEDURE Insert_Signatures_SP (@Signature VARBINARY(MAX), @UserID INT) AS DELETE FROM Signatures WHERE UserID_FK = @UserID; INSERT INTO Signatures (UserID_FK, [Signature]) VALUES (@UserID, @Signature); RETURN @@ERROR; GO
2. 修改 server.js
const fileUpload = require('express-fileupload'); app.use(fileUpload({ useTempFiles : true, tempFileDir : '/tmp/' })); app.post("/admin-signatures-upload", async function (req, res) { try { if (!req.files || !req.files.myImage) { return res.status(400).send("No files were uploaded."); } const file = req.files.myImage; const userId = req.body.userid; // 对应表单的name="userid" // 连接池无需每次connect,直接用request即可 await pool.request() .input('Signature', file.data) // 直接传递文件二进制Buffer .input('UserID', userId) .execute('Insert_Signatures_SP'); res.redirect('/admin-signatures'); } catch (error) { console.error(error); res.status(500).send("Upload failed"); } })
备选方案(让SQL Server访问临时文件,不推荐)
如果一定要用 OPENROWSET,需要满足以下条件:
- SQL Server 和 Node.js 部署在同一台机器,或者将临时文件放在双方都能访问的共享目录
- 给 SQL Server 服务账号分配
/tmp目录的读取权限 - 传递完整的绝对路径(比如 Linux 下的
/tmp/tmp-1-1686629760086,Windows 下的C:\tmp\...) - 修复 server.js 里的变量和参数名错误:
const file = req.files.myImage; console.log('Path = ' + file.tempFilePath); // ... .input('UserID', req.body.userid)
内容的提问来源于stack exchange,提问作者kbdev
相关产品推荐
相关产品推荐

