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

iOS Swift中使用multipart/form-data上传图片数组仅最后一张成功的问题排查

问题分析与解决方案

你遇到的问题根源在于multipart/form-data的边界格式错误——你在循环里给每个图片都添加了请求结束的边界标记(--boundary--),导致服务器处理完第一个图片后就认为整个请求已经结束,后续的图片数据都被忽略了。

核心错误点

multipart请求的正确结构应该是:

  • 每个表单字段(这里是每张图片)用--{boundary}作为分隔符
  • 所有字段结束后,才用--{boundary}--标记整个请求的结束

而你的代码在循环内每次都用了--boundary--(结束标记),直接破坏了整个请求的结构,服务器自然只会识别到最后一段有效数据。

修正后的代码

下面是修复后的完整代码,同时还优化了几个潜在问题:

func uploadImage<T>(resource: Resource<T>, paramName: String, fileName: [String], images: [PLMUploadImage], completionHandler: @escaping (Result<T, PLMError>) -> Void) {
    let boundary = UUID().uuidString
    var urlRequest = URLRequest(url: resource.url)
    urlRequest.httpMethod = "POST"
    urlRequest.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
    
    if let accessToken = resource.accessToken {
        urlRequest.setValue(
            "\(PLMLOGINAPICONSTANT.BEARER)\(accessToken)",
            forHTTPHeaderField: PLMLOGINAPICONSTANT.AUTHORIZATION)
    }
    
    var data = Data()
    
    for (index, img) in images.enumerated() {
        // 1. 添加当前图片的分隔边界(注意是--boundary,不是--boundary--)
        data.append("--\(boundary)\r\n".data(using: .utf8)!)
        
        // 2. 设置表单字段的Content-Disposition
        let fileName = "\(index).jpg"
        data.append("Content-Disposition: form-data; name=\"\(paramName)\"; filename=\"\(fileName)\"\r\n".data(using: .utf8)!)
        
        // 3. 设置正确的Content-Type:因为你转成了JPEG,所以用image/jpeg而不是image/png
        data.append("Content-Type: image/jpeg\r\n\r\n".data(using: .utf8)!)
        
        // 4. 添加图片数据,同时处理可选值避免崩溃
        guard let imageData = img.image.jpeg(.medium) else {
            DispatchQueue.main.async {
                completionHandler(.failure(.invalidImageData)) // 建议新增这个错误类型
            }
            return
        }
        data.append(imageData)
        
        // 5. 换行分隔下一个字段(如果有的话)
        data.append("\r\n".data(using: .utf8)!)
    }
    
    // 6. 添加整个请求的结束边界
    data.append("--\(boundary)--\r\n".data(using: .utf8)!)
    
    let session = URLSession.shared
    session.uploadTask(with: urlRequest, from: data) { responseData, response, error in
        DispatchQueue.main.async { // 统一回到主线程处理结果
            if let error = error {
                completionHandler(.failure(.networkError(error))) // 建议新增网络错误类型
                return
            }
            
            guard let responseData = responseData else {
                completionHandler(.failure(.emptyResponse)) // 建议新增空响应错误类型
                return
            }
            
            do {
                let result = try JSONDecoder().decode(T.self, from: responseData)
                completionHandler(.success(result))
                
                // 可选:打印响应日志
                if let json = try? JSONSerialization.jsonObject(with: responseData, options: .allowFragments) as? [String: Any] {
                    PLMLogger.prettyPrint(data: json)
                }
            } catch {
                completionHandler(.failure(.decodingError))
            }
        }
    }.resume()
}

额外优化说明

  1. Content-Type匹配:你将图片转成了JPEG格式,所以Content-Type应该设为image/jpeg,之前的image/png会导致服务器识别格式错误。
  2. 可选值安全处理:对img.image.jpeg(.medium)的结果做了guard判断,避免nil导致的崩溃,同时新增了对应的错误类型(你需要在PLMError里补充这些case)。
  3. 主线程统一处理:将所有completion的调用都放到主线程,避免UI更新时的线程问题。
  4. 错误处理完善:补充了网络错误、空响应等场景的错误处理,让逻辑更健壮。

内容的提问来源于stack exchange,提问作者govind kumar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 06:57:26