3

我想要一个接收项目对象的服务,该对象包含;名称、描述、价格和图片。

  1. 其他属性是可以很容易地作为 Json 对象发送的字符串,但是对于包含图片的最佳解决方案是什么?
  2. 如果 multipart formdata 是最好的解决方案,它是如何在 Lagom 中处理的?
4

1 回答 1

1

您可能需要查看GitHub 上 lagom-recipes 存储库中的文件上传示例。

基本上,这个想法是创建一个额外的 Play 路由器。之后,我们必须告诉 Lagom 按照参考文档中的说明使用它(此功能从 1.5.0 开始可用)。以下是路由器的外观:

class FileUploadRouter(action: DefaultActionBuilder,
                       parser: PlayBodyParsers,
                       implicit val exCtx: ExecutionContext) {
  private def fileHandler: FilePartHandler[File] = {
    case FileInfo(partName, filename, contentType, _) =>
      val tempFile = {
        val f = new java.io.File("./target/file-upload-data/uploads", UUID.randomUUID().toString).getAbsoluteFile
        f.getParentFile.mkdirs()
        f
      }
      val sink: Sink[ByteString, Future[IOResult]] = FileIO.toPath(tempFile.toPath)
      val acc: Accumulator[ByteString, IOResult] = Accumulator(sink)
      acc.map {
        case akka.stream.IOResult(_, _) =>
          FilePart(partName, filename, contentType, tempFile)
      }

  }
  val router = Router.from {
    case POST(p"/api/files") =>
      action(parser.multipartFormData(fileHandler)) { request =>
        val files = request.body.files.map(_.ref.getAbsolutePath)
        Results.Ok(files.mkString("Uploaded[", ", ", "]"))
      }
  }
}

然后,我们简单地告诉 Lagom 使用它

  override lazy val lagomServer =
    serverFor[FileUploadService](wire[FileUploadServiceImpl])
      .additionalRouter(wire[FileUploadRouter].router)

或者,我们可以使用PlayServiceCall该类。这是 Lightbend 团队的 James Roper 提供的关于如何做到这一点的简单草图:

// The type of the service call is NotUsed because we are handling it out of band
def myServiceCall: ServiceCall[NotUsed, Result] = PlayServiceCall { wrapCall =>
  // Create a Play action to handle the request
  EssentialAction { requestHeader =>

    // Now we create the sink for where we want to stream the request to - eg it could
    // go to a file, a database, some other service. The way Play gives you a request
    // body is that you need to return a sink from EssentialAction, and when it gets
    // that sink, it stream the request body into that sink.
    val sink: Sink[ByteString, Future[Done]] = ...

    // Play wraps sinks in an abstraction called accumulator, which makes it easy to
    // work with the result of handling the sink. An accumulator is like a future, but
    // but rather than just being a value that will be available in future, it is a
    // value that will be available once you have passed a stream of data into it.
    // We wrap the sink in an accumulator here.
    val accumulator: Accumulator[ByteString, Done] = Accumulator.forSink(sink)

    // Now we have an accumulator, but we need the accumulator to, when it's done,
    // produce an HTTP response.  Right now, it's just producing akka.Done (or whatever
    // your sink materialized to).  So we flatMap it, to handle the result.
    accumulator.flatMap { done =>

      // At this point we create the ServiceCall, the reason we do that here is it means
      // we can access the result of the accumulator (in this example, it's just Done so
      // not very interesting, but it could be something else).
      val wrappedAction = wrapCall(ServiceCall { notUsed =>

        // Here is where we can do any of the actual business logic, and generate the
        // result that can be returned to Lagom to be serialized like normal

        ...
      })

      // Now we invoke the wrapped action, and run it with no body (since we've already
      // handled the request body with our sink/accumulator.
      wrappedAction(request).run()
    }
  }
}

一般来说,为此目的使用 Lagom 可能不是一个好主意。正如关于PlayServiceCall文档的 GitHub 问题所述:

我们回退到 PlayServiceCall 的许多用例与表示或特定于 HTTP 的使用(I18N、文件上传等)有关,这表明:lagom 服务与表示层的耦合或 lagom 服务与传输层的耦合。

再次引用 James Roper(几年前):

所以目前,Lagom 不支持 multipart/form-data,至少不支持开箱即用。您可以下拉到较低级别的 Play API 来处理它,但也许最好在 Web 网关中处理它,其中处理的任何文件都直接上传到 S3 等存储服务,然后 Lagom 服务可能会存储与之关联的元数据。

您还可以在此处查看讨论,它提供了更多见解。

于 2019-09-10T10:42:07.133 回答