Vapor上传文件存入DerivedData而非项目Public文件夹问题咨询
问题描述
通过Vapor服务端实现文件上传功能时,上传文件始终被存储到DerivedData文件夹,而非项目结构内的Public文件夹。
已确认文件在对应路径创建成功,但路径位于DerivedData目录下,不符合项目Public目录的静态资源对外访问规则,需要明确问题成因和正确的文件访问方案。
文件上传实现代码如下:
func create(request: Request) async throws -> HTTPStatus { try CreateDogRequest.validate(content: request) let createDogRequest = try request.content.decode(CreateDogRequest.self) guard let userId = try request.auth.require(User.self).id else { throw Abort(.notFound) } let dogId = UUID() let directory = DirectoryConfiguration.detect() let publicFolder = "Public" let folderPath = URL(fileURLWithPath: directory.workingDirectory) .appendingPathComponent(publicFolder, isDirectory: true) .appendingPathComponent("dogProfiles", isDirectory: true) .appendingPathComponent(userId.uuidString, isDirectory: true) .appendingPathComponent(dogId.uuidString, isDirectory: true) print("Starting writing to path: \(folderPath)") let filePath = folderPath.appendingPathComponent("hello" + ".jpg", isDirectory: false) try FileManager.default.createDirectory(at: folderPath, withIntermediateDirectories: true) let data = Data(buffer: createDogRequest.dogImage.data) try data.write(to: filePath, options: .atomic) print("file uploaded at: \(filePath.relativePath)") return .ok }
运行后打印的文件实际存储路径为:/Users/<USERNAME>/Library/Developer/Xcode/DerivedData/<PROJECT_HANDLE>/Build/Products/Debug/Public/dogProfiles/62C340CE-262B-4DE4-9E2A-99B3B3126BB6/hello.jpg
调试DirectoryConfiguration类时发现,detect()方法会检测服务是否通过Xcode启动,相关逻辑如下:
#if Xcode if workingDirectory.contains("DerivedData") { Logger(label: "codes.vapor.directory-config") .warning("No custom working directory set for this scheme, using \(workingDirectory)") } #endif
存在矛盾现象:将文件放入Resources文件夹后,使用DirectoryConfiguration.detect()方法拼接路径读取该文件可正常定位,无路径异常。对应的文件读取代码如下:
let directory = DirectoryConfiguration.detect() let configDir = "Resources" let path = URL(fileURLWithPath: directory.workingDirectory) .appendingPathComponent(configDir, isDirectory: true) .appendingPathComponent("file.txt", isDirectory: false) .relativePath
核心疑问:相同路径拼接逻辑下,为何读取Resources目录文件正常,写入Public目录时文件会落到DerivedData路径,以及对应的正确修复方案。
问题成因
- Xcode默认启动Vapor项目时,会将进程工作目录设置为DerivedData下的编译产物目录,而非项目源码根目录,
DirectoryConfiguration.detect()默认取当前进程工作目录,因此拼接出来的路径全部指向DerivedData下的build目录。 - 读取Resources目录正常的原因是:Xcode编译Vapor项目时,会自动将源码目录下
Resources文件夹的所有内容复制到编译产物的对应目录,因此哪怕工作目录在DerivedData,拼接Resources路径也能读到编译时复制过去的文件。 - 写入Public目录时,文件直接写入当前工作目录(即DerivedData下的编译产物目录)下的Public文件夹,自然不会落到源码目录的Public文件夹。且编译产物目录会在Clean Build、版本切换时被清空,写入的文件不仅无法匹配源码目录的静态资源规则,还存在丢失风险。
修复方案
本地Xcode调试场景
- 打开Xcode,选中当前运行的Scheme,点击「Edit Scheme」
- 找到「Run」配置下的「Options」标签
- 勾选「Working Directory」分类下的「Use custom working directory」,路径选择项目根目录(即
Package.swift文件所在的文件夹) - 重新运行项目即可,此时
DirectoryConfiguration.detect()拿到的工作目录为项目真实根目录,写入的文件会直接存到源码下的Public文件夹,匹配静态资源访问规则。
生产部署场景
- 不要依赖Xcode默认工作目录逻辑,启动Vapor服务时通过
--working-dir参数手动指定工作目录为项目根目录,示例启动命令:./Run serve --env production --working-dir /path/to/your/project/root - 不建议将用户上传的动态文件存储在Public目录:Public目录一般存放固定静态资源(前端打包产物、默认图标、固定素材等不会动态修改的内容),用户上传的动态文件建议单独存放在项目根目录下新建的
uploads专属目录,自行编写路由实现带鉴权的文件访问,不要直接开放静态目录映射,避免路径遍历等安全风险。如果需要开放静态访问,单独为uploads目录配置静态文件中间件即可。
内容的提问来源于stack exchange,提问作者kalafun
相关产品推荐
相关产品推荐

