Swift Alamofire上传multipartFormData报400错误排查求助
Alamofire MultipartFormData 400错误排查
我尝试用Alamofire发送multipartFormData表单数据,多次尝试后始终返回400错误。postId和userId由服务器生成,无需在上传参数中传递,Endpoint配置确认无误。以下是相关代码及服务器接收的Postman示例,请求协助排查问题。
服务器接收的Postman JSON示例
{ "pTostId": null, "userId": 81, "createdAt": null, "modifiedAt": null, "startedAt": "2022-07-29", "endedAt": "2023-07-28", "title": "시연영상2", "memo": "굿", "category": "Animation", "views": 1, "genre": "액션2", "platform": "2", "emoji": "1", "comment": "시연영상 3저장", "image": "https://servername-s3.s3.ap-northeast-2.amazonaws.com/4cb5bbf5-064d-4bb2-a7aa-db7cfa999850-Untitled%20design.png" }
调用代码(AddCardView)
struct AddCardView: View { @ObservedObject var viewModel = CardViewModel.shared @State private var image: UIImage? = nil @State private var card = Card() @State private var isShowingImagePicker = false @Environment(\.colorScheme) var colorScheme @State private var presentAlert = false var body: some View { VStack(spacing: 20) { TextField("Title", text: Binding<String>( get: { self.card.title ?? "" }, set: { self.card.title = $0 } )) .padding() .background(Color.gray.opacity(0.1)) .cornerRadius(10) TextField("Comment", text: Binding<String>( get: { self.card.comment ?? "" }, set: { self.card.comment = $0 } )) .padding() .background(Color.gray.opacity(0.1)) .cornerRadius(10) if let selectedImage = image { Image(uiImage: selectedImage) .resizable() .frame(width: 200, height: 200) } if let image = image { Image(uiImage: image) .resizable() .scaledToFill() .frame(width: 280, height: 400) .cornerRadius(20) .clipped() .onTapGesture { isShowingImagePicker = true } } else { Image(systemName: "camera") .resizable() .scaledToFit() .frame(width: 100, height: 250) .onTapGesture { isShowingImagePicker = true } } Button(action: { viewModel.addCard(image: image, with: card) }) { Text("Add Card") .padding() .background(Color.green) .foregroundColor(.white) .cornerRadius(10) } } .padding() .sheet(isPresented: $isShowingImagePicker) { ImagePicker(image: $image) } } }
Card结构体
struct Card: Codable, Identifiable { var id: Int? { postId } let postId: Int? let userId: Int? var image: String? var comment: String? var title: String? var emoji: String? var platform: String? var genre: String? var views: Int? var memo: String? var category: CardCategory? var createdAt: String? var modifiedAt: String? var startedAt: String? var endedAt: String? var encodedImageURL: URL? { return image?.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed).flatMap { URL(string: $0) } } init() { self.postId = nil self.userId = nil self.image = nil self.comment = nil self.title = nil self.emoji = nil self.platform = nil self.genre = nil self.views = nil self.memo = nil self.category = nil self.createdAt = nil self.modifiedAt = nil self.startedAt = nil self.endedAt = nil } }
APIClient实现
func addCard(image: UIImage?, with model: Card, completionHandler: @escaping () -> Void) { let url = APIEndpoint.addCardURL let header: HTTPHeaders = ["Content-Type": "multipart/form-data"] let parameters = [ "startedAt" : model.startedAt ?? "No startedAt", "endedAt" : model.endedAt ?? "No endedAt", "title" : model.title ?? "No Title", "memo" : model.memo ?? "No memo", "category" : model.category ?? "Animation", "views" : model.views ?? 0, "genre" : model.genre ?? "No genre", "platform" : model.platform ?? "No platform", "emoji" : model.emoji ?? "No emoji", "comment" : model.comment ?? "No comment" ] as [String : Any] AF.upload(multipartFormData: { multipartFormData in for (key, value) in parameters { multipartFormData.append("\(value)".data(using: .utf8)!, withName: key, mimeType: "text/plain") } let imageData = image?.pngData() ?? Data() multipartFormData.append(imageData, withName: "image", fileName: "image.png", mimeType: "image/png") }, to: url, method: .post, headers: header) .responseData { response in guard let statusCode = response.response?.statusCode else { return } switch statusCode { case 200...299: print("카드 등록 성공") completionHandler() case 300...399: print("Redirection messages") case 400: print("400") case 401...499: print("Client error responses") case 500...600: print("Server error responses") default: print("카드 등록 실패") } } }
CardViewModel代码
class CardViewModel: ObservableObject { static let shared = CardViewModel() @Published var cards: [Card] = [] @Published var currentCard: Card? private init() {} private var apiClient = APIClient() func addCard(image: UIImage?, with model: Card) { apiClient.addCard(image: image, with: model) { DispatchQueue.main.async { self.cards.append(model) print("Card added successfully!") } } } }
排查方向建议
- 参数类型不匹配:Postman中
views是整数,但代码里把它转为字符串传递,服务器可能期望整数格式。需确认服务器接收类型,若为整数,直接传递Int类型而非转字符串。 - 默认值无效:部分参数用"No xxx"作为默认值(比如
startedAt),但服务器期望合法日期格式,这类默认值会导致参数无效。需确认字段是否必填,必填则需用户输入合法值。 - Content-Type头错误:Alamofire上传multipartFormData时会自动生成带boundary的Content-Type,手动设置
multipart/form-data会覆盖自动生成的头,导致服务器无法解析表单。建议移除手动设置的该头。 - CardCategory枚举处理:
category是CardCategory?类型,转字符串时需确保枚举原始值与服务器期望一致(比如"Animation"),否则会传递错误值。 - 空图片数据:未选图片时传递空Data给
image字段,服务器可能不接受空文件,需确认是否允许图片为空,不允许则添加图片选择校验。 - 抓包对比:用Charles抓包对比Postman和Alamofire的请求,检查参数名、类型、格式是否完全一致,同时在APIClient中打印请求日志辅助排查。
内容的提问来源于stack exchange,提问作者YUNG LEE
相关产品推荐
相关产品推荐

