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

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调试场景

  1. 打开Xcode,选中当前运行的Scheme,点击「Edit Scheme」
  2. 找到「Run」配置下的「Options」标签
  3. 勾选「Working Directory」分类下的「Use custom working directory」,路径选择项目根目录(即Package.swift文件所在的文件夹)
  4. 重新运行项目即可,此时DirectoryConfiguration.detect()拿到的工作目录为项目真实根目录,写入的文件会直接存到源码下的Public文件夹,匹配静态资源访问规则。

生产部署场景

  1. 不要依赖Xcode默认工作目录逻辑,启动Vapor服务时通过--working-dir参数手动指定工作目录为项目根目录,示例启动命令:
    ./Run serve --env production --working-dir /path/to/your/project/root
  2. 不建议将用户上传的动态文件存储在Public目录:Public目录一般存放固定静态资源(前端打包产物、默认图标、固定素材等不会动态修改的内容),用户上传的动态文件建议单独存放在项目根目录下新建的uploads专属目录,自行编写路由实现带鉴权的文件访问,不要直接开放静态目录映射,避免路径遍历等安全风险。如果需要开放静态访问,单独为uploads目录配置静态文件中间件即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 23:15:45