SwiftUI iOS应用向Vapor 4多格式图片上传代码实现求助
SwiftUI客户端 + Vapor 4后端多格式图片上传实现
一、iOS客户端(SwiftUI)代码
图片选择与上传逻辑
使用PhotoPicker筛选支持的图片类型,将图片数据连同文件名一起发送到后端:
import SwiftUI import PhotosUI struct ImageUploadView: View { @State private var selectedItem: PhotosPickerItem? @State private var uploadStatus: String = "" // 允许选择的图片类型UTI private let allowedUTIs = [ UTType.jpeg, UTType.png, UTType.heic ] var body: some View { VStack(spacing: 20) { PhotosPicker(selection: $selectedItem, matching: .init(allowedUTIs)) { Text("选择图片") .padding() .background(Color.blue) .foregroundColor(.white) .cornerRadius(8) } Text(uploadStatus) Button("上传图片") { guard selectedItem != nil else { uploadStatus = "请先选择图片" return } uploadImage(selectedItem!) } .disabled(selectedItem == nil) } .padding() } private func uploadImage(_ item: PhotosPickerItem) { uploadStatus = "上传中..." Task { do { let imageData = try await item.loadTransferable(type: Data.self) guard let data = imageData, let fileName = item.displayFilename else { throw NSError(domain: "UploadError", code: -1, userInfo: [NSLocalizedDescriptionKey: "无法获取图片数据"]) } guard let url = URL(string: "http://你的后端地址/upload") else { return } var request = URLRequest(url: url) request.httpMethod = "POST" // 构建multipart/form-data请求体 let boundary = UUID().uuidString request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") var body = Data() body.append("--\(boundary)\r\n".data(using: .utf8)!) body.append("Content-Disposition: form-data; name=\"image\"; filename=\"\(fileName)\"\r\n".data(using: .utf8)!) body.append("Content-Type: \(mimeType(for: fileName))\r\n\r\n".data(using: .utf8)!) body.append(data) body.append("\r\n--\(boundary)--\r\n".data(using: .utf8)!) request.httpBody = body let (_, response) = try await URLSession.shared.data(for: request) guard let httpResp = response as? HTTPURLResponse, httpResp.statusCode == 200 else { uploadStatus = "上传失败" return } uploadStatus = "上传成功" } catch { uploadStatus = "上传出错:\(error.localizedDescription)" } } } // 根据文件名返回对应MIME类型 private func mimeType(for fileName: String) -> String { let ext = fileName.components(separatedBy: ".").last?.lowercased() switch ext { case "jpg", "jpeg": return "image/jpeg" case "png": return "image/png" case "heic": return "image/heic" default: return "application/octet-stream" } } }
二、Vapor 4服务端代码
上传路由与格式校验逻辑
解析客户端的multipart请求,通过扩展名+文件头双重校验图片格式,合法则保存到指定目录:
import Vapor func routes(_ app: Application) throws { // 图片上传路由 app.post("upload") { req async throws -> HTTPStatus in // 解析表单中的文件 let form = try await req.content.decode(UploadForm.self) // 第一步:校验扩展名 guard let fileExt = form.image.filename.split(separator: ".").last?.lowercased(), ["jpg", "jpeg", "png", "heic"].contains(fileExt) else { throw Abort(.badRequest, reason: "仅支持jpg/jpeg/png/heic格式") } // 第二步:校验文件头(防止扩展名伪造) let fileSignature = try form.image.data.readBytes(length: 4) guard isValidImageSignature(signature: fileSignature, ext: fileExt) else { throw Abort(.badRequest, reason: "图片格式不符合要求") } // 创建保存目录(项目Public下的images文件夹) let saveDir = app.directory.publicDirectory.appending(path: "images") try FileManager.default.createDirectory(at: saveDir, withIntermediateDirectories: true) // 生成唯一文件名避免冲突 let uniqueName = UUID().uuidString + "." + fileExt let savePath = saveDir.appending(path: uniqueName) // 写入文件 try form.image.data.write(to: savePath) return .ok } } // 定义表单结构 struct UploadForm: Content { var image: File } // 文件头校验工具函数 func isValidImageSignature(signature: [UInt8]?, ext: String) -> Bool { guard let sig = signature else { return false } switch ext { case "jpg", "jpeg": return sig[0] == 0xFF && sig[1] == 0xD8 && sig[2] == 0xFF case "png": return sig[0] == 0x89 && sig[1] == 0x50 && sig[2] == 0x4E && sig[3] == 0x47 case "heic": return sig[0] == 0x66 && sig[1] == 0x74 && sig[2] == 0x79 && sig[3] == 0x70 default: return false } }
服务端基础配置
在configure.swift中启用静态文件服务,方便后续访问上传的图片:
import Vapor func configure(_ app: Application) throws { // 启用静态文件服务 app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory)) try routes(app) }
三、相关文档与库说明
客户端侧
PhotosPicker:苹果原生SwiftUI组件,支持通过UTI精准筛选系统相册中的文件类型。URLSession:原生网络API,无需第三方库即可构建multipart/form-data请求完成文件上传。
服务端侧
- Vapor
File类型:Vapor内置的文件解析类型,自动处理multipart表单中的文件数据。 - Vapor
FileMiddleware:提供静态文件托管能力,可直接通过域名访问/images/xxx路径的上传图片。 - 文件头校验:基于图片格式的公开标准(如PNG的文件头标识为
89 50 4E 47),避免仅通过扩展名校验的安全漏洞。
内容的提问来源于stack exchange,提问作者Edvard
相关产品推荐
相关产品推荐

