Golang Gin接口发送附件邮件失败:PDF加载及路径问题排查
问题描述
基于Golang Gin框架开发的求职申请REST API,功能是将用户提交的表单发送至指定邮箱。遇到以下问题:
- 用户提交数据后,数据库
choosefile字段显示“[binary data]” - 收件人打开邮件附件时提示:
Error Failed to load PDF document.
- 调试时输出错误:
2023/03/13 17:28:46 open C:\fakepath\deneme.pdf: The system cannot find the path specified. exit status 1
错误原因分析
- 前端路径伪造:
C:\fakepath\是浏览器为安全机制生成的虚假本地路径,服务器无法访问该路径,直接用这个路径读取文件必然失败。 - 文件接收方式错误:当前用
BindJSON接收choosefile字段,但文件上传不能通过JSON格式传递,必须用multipart/form-data格式,通过Gin的文件上传API接收实际文件内容。 - 数据库存储逻辑错误:错误地将无效路径(或浏览器生成的二进制标识)存入数据库,而非实际文件内容或有效存储路径。
修复方案
1. 调整前端请求格式
前端需将表单提交方式改为multipart/form-data,文件输入控件的name属性设为choosefile。
2. 修正后端文件接收逻辑
替换原有的JSON绑定逻辑,改用Gin的FormFile接收上传文件:
router.POST("/post", func(c *gin.Context) { // 接收上传的文件 file, header, err := c.FormFile("choosefile") if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "文件上传失败"}) return } defer file.Close() // 可选:将文件保存到服务器本地(推荐用于持久化) savePath := "./uploads/" + header.Filename if err := c.SaveUploadedFile(header, savePath); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "文件保存失败"}) return }
3. 修正数据库存储逻辑
方案A:存储文件内容(适合小文件)
将数据库choosefile字段类型改为bytea(PostgreSQL)或BLOB(MySQL),然后读取文件内容存入:
// 读取文件内容 fileContent, err := io.ReadAll(file) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "读取文件内容失败"}) return } // 插入数据库 if _, err := db.Exec("INSERT INTO tablo(choosefile) VALUES ($1)", fileContent); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "数据库错误"}) return }
方案B:存储文件路径(节省数据库空间,推荐)
// 插入数据库(存储服务器本地路径) if _, err := db.Exec("INSERT INTO tablo(choosefile) VALUES ($1)", savePath); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "数据库错误"}) return }
4. 修复邮件附件发送逻辑
如果使用本地存储路径读取文件:
// 读取文件内容 fileContent, err := ioutil.ReadFile(savePath) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "读取附件文件失败"}) return } // Base64编码文件内容 encodedContent := base64.StdEncoding.EncodeToString(fileContent) // 组装邮件内容(修复MIME格式问题,补充文本内容) auth := smtp.PlainAuth("", "xxx@gmail.com", "xxx", "smtp.gmail.com") to := []string{"xxx@gmail.com"} msg := []byte("To: " + strings.Join(to, ",") + "\r\n" + "Subject: JobApply Form Submission\r\n" + "MIME-version: 1.0;\r\n" + "Content-Type: multipart/mixed; boundary=\"boundary123\"\r\n" + "\r\n" + "--boundary123\r\n" + "Content-Type: text/plain; charset=utf-8\r\n" + "\r\n" + "求职申请已提交,附件为简历文件\r\n" + "--boundary123\r\n" + "Content-Type: application/pdf\r\n" + "Content-Transfer-Encoding: base64\r\n" + "Content-Disposition: attachment; filename=\"" + header.Filename + "\"\r\n" + "\r\n" + encodedContent + "\r\n" + "--boundary123--") if err := smtp.SendMail("smtp.gmail.com:587", auth, "example@gmail.com", to, msg); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to send email"}) return } c.JSON(http.StatusOK, gin.H{"status": "success"})
5. 清理冗余代码
原代码中查询数据库并写入本地文件的逻辑(db.Query、os.Create相关部分)若无实际用途,可直接删除以简化代码。
内容的提问来源于stack exchange,提问作者Ozalp code
相关产品推荐
相关产品推荐

