使用 Angular ng-file-upload 上传 Scala Play Framework 图片

Scala Play Framework image upload with Angular ng-file-upload

我在前端使用 Angular ng-file-upload (https://github.com/danialfarid/ng-file-upload) 来管理文件上传过程。

很遗憾,表单包含一个包含多个文件的复杂对象。在服务器端使用 MultipartFormData (https://www.playframework.com/documentation/2.5.x/ScalaBodyParsers) 我已经成功地分解了上传的内容并且可以从 request.body.

读取它

现在,令我惊讶的是,我没有一个简单的 Json Objects,而是一个奇怪的数据类型,在 ng-file-upload 网站上描述为:

(...) server implementations expecting nested data object keys in .key or [key] format. Example: data: {rec: {name: 'N', pic: file}} sent as: rec[name] -> N, rec[pic] -> file
data: {rec: {name: 'N', pic: file}, objectKey: '.k'} sent as: rec.name -> N, rec.pic -> file

到目前为止,我已经设法将所有数据转换为通用的 MultipartFormData.Part 类型,使用 DataPartFilePart 如下所示:

 val opts = body.dataParts.map {
   case (key, values) => DataPart(key, values.head)
 }

 val parts = opts ++ body.files

所以我现在很不幸Iterable[Part]:

0 = {MultipartFormData$DataPart@86271} "DataPart(arabic[active],false)"
1 = {MultipartFormData$DataPart@86273} "DataPart(english[active],true)"
2 = {MultipartFormData$DataPart@86277} "DataPart(english[url],2132132132)"
...
7 = {MultipartFormData$FilePart@76473} "FilePart(english[image],fb_icon_325x325.png,Some(image/png),TemporaryFile(/tmp/playtemp5909927824995768544/multipartBody8348573128070542611asTemporaryFile))"

每个对象 name 包含其 Json 结构的 key 及其相应的 value。现在我想将它解析为对象,而不是 key[level1][level2],在我的例子中:

case class PcBanner(english: PcBanners, arabic: PcBanners, kurdish: PcBanners)
case class PcBanners(active: Boolean, url: Option[String], image: Option[String])`

我希望你明白了。

问题

我知道我可以尝试解析 name 字符串以使其适合对象,但我相信我在中间的某个地方犯了一个错误。 有没有办法将这个结构解析为对象,使用字段名称作为参考?有内置的 Play 功能或类似功能吗?

感谢您的帮助!

正如我在标题中所述,我的案例是发送 图片 。如您所料,我还展示了预览和当前保存在数据库中的文件。

考虑到所有优点和缺点,我决定以 JSON 格式发送所有数据,两种方式。这意味着图像在 JSON 结构中被编码和发送。

尽管上面的解决方案看起来很方便,但实际上在实施过程中会产生新的问题。

  1. 您将很快超过服务器的 POST 请求大小限制。对于 Play 服务器,可以扩展默认的 100kB,但是...
  2. 我很快 运行 发现了一些数据畸形,因为保存为巨大字节字符串的图像可能有一些 sending/parsing 错误。

我没有深入研究这个错误的解决方案,我使用了@danial 的建议:

No have the file sent separately like this
{file: file, otherData: JSON.stringify(myData)}

我的解决方案

如果有人想对我使用类似的方法,我会提出我的答案。 在 front-end 方面,我决定使用 ng-file-upload 库。使用 ngf-selectngf-drop 将其绑定到 HTML 组件,从而启用组件:

<div ngf-drop ngf-select
     ng-model="image"
     ngf-accept="'image/*'"
     ngf-resize="{width: {{width}}, height: {{height}}, quality: 1.0, restoreExif: false}">

    <img ng-show="!!image && !!image.$ngfName" ngf-src="image">

    <img ng-show="(!image || !image.$ngfName)" ng-src="{{ imageUrl }}">
</div>

在上传标签内我放了图片预览。这完美无缺。如果未选择图像,我将使用保存在数据库中的图像。

数据和图像不再共享模型。上传功能如下所示:

return Upload.upload({
    url: url,
    data: {file: images, data: angular.toJson(data)}
}).then(function (resp) {
    console.log(resp);
}, function (error) {
    console.log(error);
});

将以上所有内容放在一起得到了输出数据 object:

{  
   "english":{  
      "active":true,
      "url":"http://google.com"
   },
   "arabic":{  
      "active":true,
      "url":"http://google.com"
   },
   "kurdish":{  
      "active":true,
      "url":"http://google.com"
   }
}

在服务器端,JSON 匹配准备好的案例 class 并使用 build-in Jackson 解析器进行解析,允许轻松 object 操作。图片必须手动选择:

val json = r.body.dataParts("data")
val jsValue = Json.parse(json.head)
val result = jsValue.validate(LocalizedBanner.dataModelFormat) // parse JSON

从 body 中提取文件可以通过内置函数 .file:

完成
val key = s"file[${lang.name}][${imageType.name}]"
body.file(key).map(mp => (mp.ref.file, imageType))

尽情享受吧!