使用Fluent向PostgreSQL提交multipart/form-data时无法自动生成ID
解决Vapor Fluent自动生成ID失败问题
环境
MacOS 13.5.1、Vapor 4.77
场景
尝试通过Vapor 4.77向PostgreSQL数据库提交结构为{"title":"String", "description":"String", "Info":"Data"}的数据,其中info字段支持application/JSON或image/png类型。
问题
Fluent无法自动生成ID,报错提示缺少id字段。手动初始化ID后仍收到错误:
Response String: {"error":true,"reason":"Value required for key at path 'id'. No value associated with key \"id\"."}
相关代码
1. 服务器依赖包配置
dependencies: [ // 💧 A server-side Swift web framework. .package(url: "https://github.com/vapor/vapor.git", from: "4.77.1"), // 🗄 An ORM for SQL and NoSQL databases. .package(url: "https://github.com/vapor/fluent.git", from: "4.8.0"), // 🐘 Fluent driver for Postgres. .package(url: "https://github.com/vapor/fluent-postgres-driver.git", from: "2.7.2"), .package(url: "https://github.com/vapor/multipart-kit.git", from: "4.5.4"), ],
2. 客户端构建multipart/form-data请求代码
// ... let title = "Frederick C. Lee" let description = "Mother had a feeling, I might be too appealing." var body = Data() let boundary = "Boundary-\(UUID().uuidString)" // 构造请求 var request = URLRequest(url: serverURL) request.httpMethod = "POST" request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") // 三个表单字段:1) 标题 2) 描述 3) 信息(Data类型) // 注意:多个'\r\n'是必要的语法 // 1) 标题 body.append("--\(boundary)\r\n".data(using: .utf8)!) body.append("Content-Disposition: form-data; name=\"title\"\r\n".data(using: .utf8)!) body.append("Content-Type: text/plain\r\n\r\n".data(using: .utf8)!) body.append("\(title)\r\n".data(using: .utf8)!) // ----------------- // 2) 描述 body.append("--\(boundary)\r\n".data(using: .utf8)!) body.append("Content-Disposition: form-data; name=\"description\"\r\n".data(using: .utf8)!) body.append("Content-Type: text/plain\r\n\r\n".data(using: .utf8)!) body.append("\(description)\r\n".data(using: .utf8)!) // ----------------- // 3) 信息(Data类型) body.append("--\(boundary)\r\n".data(using: .utf8)!) body.append("Content-Disposition: form-data; name=\"info\"\r\n".data(using: .utf8)!) body.append("Content-Type: application/json\r\n\r\n".data(using: .utf8)!) // 可替换为image/png body.append(infoData!) // 替换为实际的Data数据 body.append("\r\n".data(using: .utf8)!) // 追加结束边界 body.append("--\(boundary)--\r\n".data(using: .utf8)!) request.httpBody = body return request }
3. TurkeyGizzard模型代码
import Fluent import Vapor final class TurkeyGizzard: Model, Content { static let schema = "turkeygizzard" @ID(key: .id) var id: UUID? @Field(key: "title") var title: String @Field(key: "description") var description: String @Field(key: "info") var info: Data? init() {} init(title: String, description: String, info: Data) { self.id = UUID() self.title = title self.info = info } }
4. 迁移代码
struct CreateTurkeyGizzards: AsyncMigration { func prepare(on database: FluentKit.Database) async throws { try await database.schema("turkeygizzard") .id() .field("title", .string) .field("description", .string) .create() } func revert(on database: FluentKit.Database) async throws { try await database.schema("turkeygizzard").delete() } }
解决方案
方案1:修复Fluent自动生成ID的问题
报错核心原因是通过Content协议解析multipart请求时,Fluent的@ID字段默认被视为必填项,但客户端请求未传入id,且手动初始化的构造器不会被解析逻辑调用(解析时使用的是默认init())。
解决步骤:
修改模型的
@ID字段配置,明确由数据库自动生成UUID,同时允许解析时忽略必填校验:@ID(key: .id, generatedBy: .database) var id: UUID?更新迁移代码,补全
info字段:
原迁移代码未添加info字段,会导致数据库表结构不匹配,必须补上:try await database.schema("turkeygizzard") .id(custom: "id", type: .uuid, generatedBy: .database) .field("title", .string, .required) .field("description", .string, .required) .field("info", .data) .create()调整控制器创建逻辑:
直接解析请求数据为模型实例,调用save()即可让Fluent自动生成ID:func createGizzard(req: Request) async throws -> HTTPStatus { let gizzard = try req.content.decode(TurkeyGizzard.self) try await gizzard.save(on: req.db) return .created }
方案2:不使用Fluent,直接操作PostgreSQL
若不想依赖Fluent,可直接用Vapor的PostgreSQL驱动执行SQL:
func createGizzard(req: Request) async throws -> HTTPStatus { let title = try req.content.get(String.self, at: "title") let description = try req.content.get(String.self, at: "description") let info = try req.content.get(Data?.self, at: "info") try await req.db.raw(""" INSERT INTO turkeygizzard (title, description, info) VALUES (?, ?, ?) """) .bind(title) .bind(description) .bind(info) .run() return .created }
额外注意事项
- 确保客户端请求的字段名
info与模型、数据库字段名大小写一致(PostgreSQL默认区分大小写)。 - 手动构造multipart请求时,需严格遵循格式规范,包括边界、换行符的正确使用,避免解析失败。
内容的提问来源于stack exchange,提问作者Frederick C. Lee
相关产品推荐
相关产品推荐

