跳转到主要内容

上传

将文件上传到 API,并保存到文件夹或数据库中。

简介​

当我们需要将文件发送到 REST API 服务时,在 Netuno 中有多种方式可以实现这种开发。

通常,文件会放在 HTTP 请求体中,并以 multipart 格式组织,这是前端最常见的做法,例如当我们填写表单并且其中有一个用于选择文件的字段时。

另一种方式是,当我们希望 REST API 完全使用 JSON 时,可以将文件字节以 Base64 编码作为 JSON 对象中的值发送。

文件​

通过 _req.file 可以传入字段名,从而获取已上传文件的对象,如下所示:

const 我的文件 = _req.file("文件");

也就是说,_req 包含我们在 HTTP 请求体中接收到的所有数据。

通过 file 方法可以获取表示已上传文件的对象。

如果没有任何文件,获取到的值可能为 null。

图片​

当我们接收图片时,通常需要调整其尺寸,以保证一致性并减少图片占用的空间。

const 我的文件 = _req.getFile("图片");
if (我的文件 != null
&& (我的文件.isExtension("jpeg")
|| 我的文件.isExtension("jpg")
|| 我的文件.isExtension("png")
)) {
const 我的图片 = _image.init(我的文件)
.resize(200, 200)
.file(我的文件.baseName() +".jpg", "jpeg");
_header.contentTypeJPG();
_out.copy(我的图片);
}

借助多语言低代码编程资源 Image,我们可以操作图片,进行裁剪、调整尺寸、创建新图片等。

如何在 JSON 中使用 Base64​

要在发送给服务的 JSON 中上传文件,我们可以定义一个属性,其值为:

data:image/png;base64,...

其中 ... 后跟以 Base64 编码的文件字节序列。

文件类型跟在 data 后面,例如:

  • PNG:image/png。
  • JPG:image/jpeg
  • TXT:text/plain
  • PDF:application/pdf
  • XLSX:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet

请参见以下示例:将一个小的 PNG 图片文件在 JSON 中发送到服务:

{
"文件": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAYAAABWKLW/AAAFIGlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNS41LjAiPgogPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgeG1sbnM6ZXhpZj0iaHR0cDovL25zLmFkb2JlLmNvbS9leGlmLzEuMC8iCiAgICB4bWxuczpwaG90b3Nob3A9Imh0dHA6Ly9ucy5hZG9iZS5jb20vcGhvdG9zaG9wLzEuMC8iCiAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIgogICAgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIKICAgZXhpZjpDb2xvclNwYWNlPSIxIgogICBleGlmOlBpeGVsWERpbWVuc2lvbj0iMyIKICAgZXhpZjpQaXhlbFlEaW1lbnNpb249IjMiCiAgIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiCiAgIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIKICAgdGlmZjpJbWFnZUxlbmd0aD0iMyIKICAgdGlmZjpJbWFnZVdpZHRoPSIzIgogICB0aWZmOlJlc29sdXRpb25Vbml0PSIyIgogICB0aWZmOlhSZXNvbHV0aW9uPSI3Mi8xIgogICB0aWZmOllSZXNvbHV0aW9uPSI3Mi8xIgogICB4bXA6TWV0YWRhdGFEYXRlPSIyMDI1LTEyLTI5VDE5OjEyOjI3WiIKICAgeG1wOk1vZGlmeURhdGU9IjIwMjUtMTItMjlUMTk6MTI6MjdaIj4KICAgPHhtcE1NOkhpc3Rvcnk+CiAgICA8cmRmOlNlcT4KICAgICA8cmRmOmxpCiAgICAgIHhtcE1NOmFjdGlvbj0icHJvZHVjZWQiCiAgICAgIHhtcE1NOnNvZnR3YXJlQWdlbnQ9IkFmZmluaXR5IERlc2lnbmVyIDEuMTAuNSIKICAgICAgeG1wTU06d2hlbj0iMjAyNS0wMi0yM1QyMjoxNDozM1oiLz4KICAgICA8cmRmOmxpCiAgICAgIHN0RXZ0OmFjdGlvbj0icHJvZHVjZWQiCiAgICAgIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFmZmluaXR5IDMuMC4xIgogICAgICBzdEV2dDp3aGVuPSIyMDI1LTEyLTI5VDE5OjEyOjI3WiIvPgogICAgPC9yZGY6U2VxPgogICA8L3htcE1NOkhpc3Rvcnk+CiAgPC9yZGY6RGVzY3JpcHRpb24+CiA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgo8P3hwYWNrZXQgZW5kPSJyIj8+UGhcxwAAAYFpQ0NQc1JHQiBJRUM2MTk2Ni0yLjEAACiRdZG7SwNBEIe/JEqCRhS0sFAIEq3iG4I2ggmigkiIEXw1yZmHkMdxlyDBVrANKIg2vgr9C7QVrAVBUQSx1lbRRuWc84QEMbPMzre/3Rl2Z8EeSSsZvaYPMtm8Fh4PeObmFzzOZ9y004yL3qiiq6Oh0BRV7f0Wmxmvu81a1c/9a/XLcV0Bm0t4RFG1vPCE8NRqXjV5S7hFSUWXhU+EfZpcUPjG1GMWP5mctPjTZC0SDoK9SdiTrOBYBSspLSMsL8ebSReU3/uYL3HHs7MzEjvE29AJM04AD5OMEcRPP8My++lmgB5ZUSW/7yd/mpzkKjKrFNFYIUmKPD5RC1I9LjEhelxGmqLZ/7991RODA1Z1dwBqHw3jtROcm/BVMoyPA8P4OgTHA5xny/m5fRh6E71U1rx70LgOpxdlLbYNZxvQeq9GteiP5BC3JxLwcgwN89B8BXWLVs9+9zm6g8iafNUl7OxCl5xvXPoGe8Rn71E5kqUAAAAJcEhZcwAACxMAAAsTAQCanBgAAAAySURBVAiZAScA2P8BJYrEogr//l3h5+qlBP4HCF3n/AAA/wP7WwLu7PKk+eHWAPzWyKSmfRbbIj23ZQAAAABJRU5ErkJggg=="
}

我们可以按以下方式获取上传的文件:

const 我的文件 = _req.getFile("文件");
if (我的文件 != null) {
_out.json(
_val.map()
.set("结果", true)
.set("名称", 我的文件.name())
.set("大小字节", 我的文件.size())
.set("大小KB", 我的文件.sizeKB())
.set("大小MB", 我的文件.sizeMB())
.set("大小GB", 我的文件.sizeGB())
);
} else {
_out.json(
_val.map()
.set("结果", false)
);
}

以上服务生成的输出示例:

{
"名称": "文件.png",
"结果": true,
"大小": 1849,
"大小KB": 1
}

对象​

如果文件位于 JSON 内部子对象结构中:

{
"人员": {
"文件": "data:image/png;base64,..."
}
}

可以按以下方式获取:

const 我的文件 = _req.getValues("人员").getFile("头像");

数组​

如果文件位于 JSON 内部数组结构中:

{
"文件列表": [
"data:image/png;base64,..."
]
}

可以按以下方式获取:

const 我的文件 = _req.getValues("文件列表").getFile(0);

OpenAPI​

Netuno 支持与 OpenAPI 集成,并使用 OpenAPI 定义来验证 HTTP 请求。

关于 Netuno 中的 OpenAPI 的更多内容。

使用 file 类型时,Netuno 会验证该值是否以 JSON 中的文件格式开头,例如:

  • data:image/png;base64,...

要验证是否在 JSON 对象的某个属性中接收文件:

post.in.json
{
"summary": "文件上传",
"description": "在 JSON 对象中接收文件的服务。",
"type": "object",
"properties": {
"文件": {
"type": "file"
}
},
"required": [
"文件"
]
}

要验证是否在 JSON 数组中接收文件列表:

post.in.json
{
"summary": "多文件上传",
"description": "在 JSON 数组中接收文件列表的服务。",
"properties": {
"文件列表": {
"type": "array",
"items": {
"type": "file"
}
}
},
"required": [
"文件列表"
]
}

保存文件​

通常,当服务接收到文件时,我们需要将其保存,例如:

  • 数据库:与记录关联的文件,例如用户的文件。
  • Storage:应用内部用于管理通用文件的文件夹。
  • 文件夹:将文件放到系统中的任意文件夹。

数据库​

在表单字段管理中,Netuno 支持 image 和 file 类型,即:

  • Image:通用图片文件,JPG 和 PNG。
  • File:任意类型的文件。

假设“产品”表单中有一个“图片”字段,我们可以按以下方式保存通过上传发送的文件:

使用 DB Form 的示例:​

注意 _db.form 的使用:

services/产品/图片/post.js
const 产品Uid = _req.getString("uid");
const 产品图片 = _req.getFile("图片");

if (产品图片 == null) {
_header.status(400);
_out.json(
_val.map()
.set("结果", false)
.set("错误", "图片必填")
);
_exec.stop();
}

const 数据库结果 = _db.form("产品")
.set("图片", 产品图片)
.where(_db.where("uid").equals(产品Uid))
.update();

if (数据库结果.getInt("产品") == 1) {
_out.json(
_val.map()
.set("结果", true)
);
} else {
_header.status(404);
_out.json(
_val.map()
.set("结果", false)
.set("错误", "uid-产品不存在")
);
}

使用经典 update 的示例:​

注意 _db.update 的使用:

services/产品/图片/post.js
const 产品Uid = _req.getString("uid");
const 产品图片 = _req.getFile("图片");

if (产品图片 == null) {
_header.status(400);
_out.json(
_val.map()
.set("结果", false)
.set("错误", "图片必填")
);
_exec.stop();
}

const 数据库结果 = _db.update(
"产品",
产品Uid,
_val.map()
.set("图片", 产品图片)
);

if (数据库结果 == 1) {
_out.json(
_val.map()
.set("结果", true)
);
} else {
_header.status(404);
_out.json(
_val.map()
.set("结果", false)
.set("错误", "uid-产品不存在")
);
}

从数据库下载文件​

要呈现已存储的文件,我们应创建一个提供文件字节的服务。

对于保存到数据库中的文件(如上述示例),文件本身会放入应用的 Storage 中,而数据库中只存储文件的最终名称。

应用的 Storage 是一个用于存储和组织通用文件结构的文件夹。

因此,在上面的示例中,表单 产品 和字段 图片 的数据库文件存储在:

  • storage/database/产品/图片/*

Netuno 会自动管理存储文件的名称,以避免文件名重复。

要获取文件,可以创建一个展示产品图片文件的服务,例如:

services/产品/图片/get.js
const 产品Uid = _req.getString("uid");

// DB Form:
const 数据库产品 = _db.form("产品")
.where(_db.where("uid").equals(产品Uid))
.first();
// 使用经典 DB 的替代方式:
// const 数据库产品 = _db.get("产品", 产品Uid)

if (数据库产品 == null) {
_header.status(404);
_out.json(
_val.map()
.set("结果", false)
.set("错误", "产品未找到")
);
_exec.stop();
}

const 数据库产品图片名称 = 数据库产品.getString("图片");

const 存储产品图片文件 = _storage.database(
"产品",
"图片",
数据库产品图片名称
);

if (存储产品图片文件.extension() == "jpg" || 存储产品图片文件.extension() == "jpeg") {
_header.contentTypeJPG();
} else {
_header.contentTypePNG();
}

_header.noCache();

_out.copy(存储产品图片文件.inputStream());

注意,_storage 资源(Storage)用于获取文件:

_storage.database(
"产品", // 表单/表名称。
"图片", // 字段/列名称。
数据库产品图片名称 // 文件名。
);

数据库中只存储文件名,而通过 _storage.database 我们可以获取文件本身。

Storage​

我们可以将文件直接保存到应用的 storage 中。

为了避免与数据库文件混在一起,我们应使用 filesystem 文件夹,它用于存储未与数据库关联的通用文件。

为了接收文件,本示例演示如何将 POST 服务中发送的文件直接存储到 Storage:

services/文件/post.js
const 文件 = _req.getFile("文件");
_storage.filesystem("服务器", "上传").ensurePath();
文件.save(_storage.filesystem("服务器", "上传", 文件.name()));

文件存储在应用的 storage 文件夹中,路径为:

  • storage/filesystem/服务器/上传/*

Storage 资源用于获取文件夹和文件路径:

_storage.filesystem(
"服务器", // filesystem 中子文件夹的名称。
"上传", // 内部子文件夹的名称或路径。
文件.name() // 文件的最终名称。
);

查看更多 Storage 文档。

要下载文件,本示例演示如何通过 GET 服务获取文件字节:

services/文件/get.js
const 文件名称 = _req.getString("文件");

const 文件 = _storage.filesystem(
"服务器",
"上传",
文件名称
).file();

if (文件.exists() == false) {
_header.status(404);
_out.json(
_val.map()
.set("结果", false)
.set("错误", "文件不存在")
);
_exec.stop();
}

_header.downloadFile(文件.name());
_out.copy(文件.input());

文件夹​

我们可以将文件直接保存到计算机上的任意文件夹。

在这种情况下,有两个非常有用的资源:

  • _app - 可以获取应用中的任意文件夹或文件。
  • _os - 可以获取计算机中的任意文件夹或文件。

查看相应资源 App 和 OS 的文档,这两个资源都有 folder 和 file 方法,分别用于指定文件夹或文件的路径。

为了接收文件,本示例演示如何将 POST 服务中发送的文件直接存储到应用中的某个文件夹:

services/文件/post.js
const 文件 = _req.getFile("文件");
const 文件夹 = _app.folder("临时");
if (文件夹.exists() == false) {
文件夹.mkdir();
}
文件.save(_app.file(`临时/${文件.name()}`));

上面的代码会在应用中创建 临时 文件夹,并将接收到的文件保存到该文件夹中。

要下载文件,本示例演示如何通过 GET 服务获取文件字节:

services/文件/get.js
const 文件名称 = _req.getString("文件");
const 文件 = _app.file(`临时/${文件名称}`);

if (文件.exists() == false) {
_header.status(404);
_out.json(
_val.map()
.set("结果", false)
.set("错误", "文件不存在")
);
_exec.stop();
}

_header.downloadFile(文件.name());
_out.copy(文件.input());

结论​

如你所见,我们可以将文件上传集成到 REST API 服务中,甚至与 OpenAPI 集成。

我们可以将文件保存到数据库、应用内的 storage 文件夹或计算机上的任意文件夹。

查看本教程中使用的、可用于操作文件的资源文档:

在数据库中,我们可以将图片保存到 image 类型字段,也可以将任意类型的文件保存到 file 类型字段;无论是图片还是通用文件,保存或获取文件的方式在各種情况下都相同。