介绍
在本文中,我们将使用 azure 函数和 ComsmosDB 创建一个无服务器 REST API 应用程序。使用的语言是 Node.js/TypeScript。此外,由于我们使用 CosmosDB 作为数据库,因此我们将使用 Azure 函数触发器接收 Change Feed 事件作为奖励。
创建资源
创建资源组
az group create
--name ${AZ_RESOURCE_GROUP}
--location ${AZ_RESOURCE_GROUP_LOCATION}
创建 Azure Function App 资源
在创建 Azure Function App 资源之前,我们需要创建一个 Azure 存储帐户来支持 blob、队列和表存储。
az storage account create
--name ${AZ_STORAGE_ACCOUNT}
--location ${AZ_RESOURCE_GROUP_LOCATION}
--resource-group ${AZ_RESOURCE_GROUP}
--sku Standard_LRS
将上面创建的 Azure 存储帐户的名称作为参数传递给 --storage-account
,以创建 Azure Function App 资源。
az functionapp create
--name ${AZ_FUNCTION}
--storage-account ${AZ_STORAGE_ACCOUNT}
--consumption-plan-location ${AZ_RESOURCE_GROUP_LOCATION}
--resource-group ${AZ_RESOURCE_GROUP}
--functions-version 4
--runtime node
创建 CosmosDB 资源
创建 CosmosDB 资源。始终将--enable-free-tier
传递给true
用于演示或测试目的。
az cosmosdb create
--resource-group ${AZ_RESOURCE_GROUP}
--name ${AZ_COSMOS_DB}
--enable-free-tier true
--enable-analytical-storage false
创建 Azure Function 本地项目
创建项目文件夹
mkdir az-function-rest-api
cd az-function-rest-api
npm init -y
安装库
npm install --save-exact @azure/cosmos
安装开发库
npm install --save-exact -D @azure/functions @types/node azure-functions-core-tools typescript
生成 Azure 函数项目
npx func init --worker-runtime node --language typescript
创建 Http 触发函数
这次我们将创建 4 个函数。
npx func new --name get-todos --template 'HTTP trigger' --authlevel 'anonymous' --language typescript
npx func new --name post-todos --template 'HTTP trigger' --authlevel 'anonymous' --language typescript
npx func new --name delete-todos --template 'HTTP trigger' --authlevel 'anonymous' --language typescript
npx func new --name patch-todos --template 'HTTP trigger' --authlevel 'anonymous' --language typescript
编辑 package.json
{
"name": "az-function-rest-api",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "tsc", # <= 追加
"watch": "tsc -w", # <= 追加
"prestart": "npm run build", # <= 追加
"start": "func start" # <= 追加
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@azure/cosmos": "3.17.1"
},
"devDependencies": {
"@azure/functions": "3.2.0",
"@types/node": "18.11.5",
"azure-functions-core-tools": "4.0.4829",
"typescript": "4.8.4"
}
}
当前项目结构
.
├── delete-todos
│ ├── function.json
│ └── index.ts
├── get-todos
│ ├── function.json
│ └── index.ts
├── host.json
├── local.settings.json
├── package-lock.json
├── package.json
├── patch-todos
│ ├── function.json
│ └── index.ts
├── post-todos
│ ├── function.json
│ └── index.ts
└── tsconfig.json
在本地运行 Azure 函数
npm run start
默认情况下,每个函数的文件夹名称会自动设置为端点路径。
❯ npm run start
> az-function-rest-api@1.0.0 prestart
> npm run build
> az-function-rest-api@1.0.0 build
> tsc
> az-function-rest-api@1.0.0 start
> func start
Azure Functions Core Tools
Core Tools Version: 4.0.4829 Commit hash: N/A (64-bit)
Function Runtime Version: 4.11.2.19273
[2022-10-25T05:54:42.841Z] Worker process started and initialized.
Functions:
delete-todos: [GET,POST] http://localhost:7071/api/delete-todos
get-todos: [GET,POST] http://localhost:7071/api/get-todos
patch-todos: [GET,POST] http://localhost:7071/api/patch-todos
post-todos: [GET,POST] http://localhost:7071/api/post-todos
当我实际发送 HTTP 请求时,响应会正确返回。
curl "http://localhost:7071/api/delete-todos?name=azure"
❯ curl "http://localhost:7071/api/get-todos?name=azure"
Hello, azure. This HTTP triggered function executed successfully.%
端点路径和 HTTP 方法可以从每个函数文件夹中自动生成的function.json
中设置。
-
您可以在
bindings.methods
列表中指定要允许的HTTP 方法。 -
您可以使用
bidnings.route
指定端点路径。路径参数也可以指定为占位符,并且可以从函数中引用。您可以通过添加?
使路径参数成为可选参数。
{
"bindings": [
{
"authLevel": "Anonymous",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": ["get"], # <= postを消去
"route": "todos/{id?}" # <= 追加
},
{
"type": "http",
"direction": "out",
"name": "res"
}
],
"scriptFile": "../dist/get-todos/index.js"
}
编辑get-todo/index.ts
以接收并查看路径参数。路径参数的数据存储在context
对象的bindingData
属性中,可以从函数的参数中接收。
import { AzureFunction, Context, HttpRequest } from "@azure/functions"
const httpTrigger: AzureFunction = async function (context: Context, req: HttpRequest): Promise<void> {
const id = context.bindingData.id; // <= context.bindingData のなかにpath paramのデータが保管されている。
context.res = {
body: {
id
}
};
};
export default httpTrigger;
再次运行该函数,可以看到function.json
中设置的内容已经体现出来了。
npm run start
❯ npm run start
> az-function-rest-api@1.0.0 prestart
> npm run build
> az-function-rest-api@1.0.0 build
> tsc
> az-function-rest-api@1.0.0 start
> func start
Azure Functions Core Tools
Core Tools Version: 4.0.4829 Commit hash: N/A (64-bit)
Function Runtime Version: 4.11.2.19273
[2022-10-25T06:16:51.255Z] Worker process started and initialized.
Functions:
delete-todos: [GET,POST] http://localhost:7071/api/delete-todos
get-todos: [GET] http://localhost:7071/api/todos/{id?} # <= 設定が反映されている。
patch-todos: [GET,POST] http://localhost:7071/api/patch-todos
post-todos: [GET,POST] http://localhost:7071/api/post-todos
如果将路径参数传递给路由并发送 HTTP 请求,则可以确认返回带有路径参数的 id
。
curl -i -X GET http://localhost:7071/api/todos/123
❯ curl -i -X GET http://localhost:7071/api/todos/123
{
"id": 123
}%
创建 REST API
完成的项目结构
.
├── delete-todos
│ ├── function.json
│ └── index.ts
├── get-todos
│ ├── function.json
│ └── index.ts
├── host.json
├── lib
│ ├── db
│ │ └── db-config.ts
│ ├── dtos
│ │ ├── CreateTodoDto.ts
│ │ └── UpdateTodoDto.ts
│ ├── errors
│ │ └── HttpError.ts
│ ├── models
│ │ └── TodoItem.ts
│ ├── repositories
│ │ └── todo-repository.ts
│ ├── services
│ │ └── todo-service.ts
│ └── utils
│ └── generate-id.ts
├── local.settings.json
├── package-lock.json
├── package.json
├── patch-todos
│ ├── function.json
│ └── index.ts
├── post-todos
│ ├── function.json
│ └── index.ts
└── tsconfig.json
获取 todos 函数
import { AzureFunction, Context, HttpRequest } from "@azure/functions";
import { HttpError } from "../lib/errors/HttpError";
import { todoService } from "../lib/services/todo-service";
const httpTrigger: AzureFunction = async function (
context: Context,
req: HttpRequest
): Promise<void> {
const todoId = context.bindingData.id;
try {
if (todoId) {
const result = await todoService.getOne(todoId);
context.res = {
status: 200,
body: result,
};
return;
}
} catch (err) {
if (err instanceof HttpError) {
context.res = {
status: err.StatusCode,
body: {
error: {
message: err.message,
},
},
};
return;
}
}
const result = await todoService.getOnes();
context.res = {
status: 200,
body: result,
};
};
export default httpTrigger;
{
"bindings": [
{
"authLevel": "Anonymous",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": ["get"],
"route": "todos/{id?}"
},
{
"type": "http",
"direction": "out",
"name": "res"
}
],
"scriptFile": "../dist/get-todos/index.js"
}
发布待办事项功能
import { AzureFunction, Context, HttpRequest } from "@azure/functions";
import { todoService } from "../lib/services/todo-service";
const httpTrigger: AzureFunction = async function (
context: Context,
req: HttpRequest
): Promise<void> {
const todoCreateDto = req.body;
const result = await todoService.createOne(todoCreateDto);
context.res = {
status: 201,
body: result,
};
};
export default httpTrigger;
{
"bindings": [
{
"authLevel": "Anonymous",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": ["post"],
"route": "todos"
},
{
"type": "http",
"direction": "out",
"name": "res"
}
],
"scriptFile": "../dist/post-todos/index.js"
}
删除待办事项功能
import { AzureFunction, Context, HttpRequest } from "@azure/functions";
import { HttpError } from "../lib/errors/HttpError";
import { todoService } from "../lib/services/todo-service";
const httpTrigger: AzureFunction = async function (
context: Context,
req: HttpRequest
): Promise<void> {
const todoId = context.bindingData.id;
try {
await todoService.deleteOne(todoId);
} catch (err) {
if (err instanceof HttpError) {
context.res = {
status: err.StatusCode,
body: {
error: {
message: err.message,
},
},
};
return;
}
}
context.res = {
status: 204,
};
};
export default httpTrigger;
{
"bindings": [
{
"authLevel": "Anonymous",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": ["delete"],
"route": "todos/{id}"
},
{
"type": "http",
"direction": "out",
"name": "res"
}
],
"scriptFile": "../dist/delete-todos/index.js"
}
补丁待办事项功能
import { AzureFunction, Context, HttpRequest } from "@azure/functions";
import { HttpError } from "../lib/errors/HttpError";
import { todoService } from "../lib/services/todo-service";
const httpTrigger: AzureFunction = async function (
context: Context,
req: HttpRequest
): Promise<void> {
const todoId = context.bindingData.id;
const updateTodoDto = req.body;
try {
await todoService.updateOne(updateTodoDto, todoId);
} catch (err) {
if (err instanceof HttpError) {
context.res = {
status: err.StatusCode,
body: {
error: {
message: err.message,
},
},
};
return;
}
}
context.res = {
status: 204,
};
};
export default httpTrigger;
{
"bindings": [
{
"authLevel": "Anonymous",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": ["patch"],
"route": "todos/{id}"
},
{
"type": "http",
"direction": "out",
"name": "res"
}
],
"scriptFile": "../dist/patch-todos/index.js"
}
通用模块
模型
export interface TodoItem {
id?: string;
title: string;
isCompleted: boolean;
}
服务
import { CreateTodoDto } from "../dtos/CreateTodoDto";
import { UpdateTodoDto } from "../dtos/UpdateTodoDto";
import { HttpError } from "../errors/HttpError";
import { TodoItem } from "../models/TodoItem";
import { todoRepository } from "../repositories/todo-repository";
import { generateId } from "../utils/generate-id";
const getOnes = async (): Promise<TodoItem[] | any> => {
return await todoRepository.getOnes();
};
const getOne = async (id: string): Promise<any> => {
return await todoRepository.getOneById(id);
};
const createOne = async (dto: CreateTodoDto): Promise<TodoItem> => {
const newTodo: TodoItem = {
id: generateId(),
...dto,
isCompleted: false,
};
return await todoRepository.createOne(newTodo);
};
const deleteOne = async (id: string) => {
if (!id) {
throw new HttpError("Todo item id is not provided", 400);
}
const existingTodo = await todoRepository.getOneById(id);
if (!existingTodo) {
throw new HttpError(`TodoItem(id=${id}) is not found.`, 404);
}
todoRepository.removeOneById(existingTodo.id);
};
const updateOne = async (dto: UpdateTodoDto, id: string) => {
if (!id) {
throw new HttpError("Todo item id is not provided", 400);
}
const existingTodo = await todoRepository.getOneById(id);
if (!existingTodo) {
throw new HttpError(`TodoItem(id=${id}) is not found.`, 404);
}
if (typeof dto.title !== "undefined") {
existingTodo.title = dto.title;
}
if (typeof dto.isCompleted !== "undefined") {
existingTodo.isCompleted = dto.isCompleted;
}
await todoRepository.update(existingTodo);
};
export const todoService = Object.freeze({
getOnes,
getOne,
createOne,
deleteOne,
updateOne,
});
存储库
import { initDB } from "../db/db-config";
const db = initDB();
const getOnes = async () => {
const { container } = await db;
const { resources } = await container.items.readAll().fetchAll();
return resources;
};
const getOneById = async (id: string) => {
const { container } = await db;
const { resource } = await container.item(id, id).read();
return resource;
};
const createOne = async (todo: any) => {
const { container } = await db;
const { resource } = await container.items.create(todo);
return resource;
};
const removeOneById = async (id: string) => {
const { container } = await db;
await container.item(id, id).delete();
};
const update = async (todo: any) => {
const { container } = await db;
await container.item(todo.id, todo.id).replace(todo);
};
export const todoRepository = Object.freeze({
getOnes,
getOneById,
createOne,
removeOneById,
update,
});
数据库设置
import { CosmosClient } from "@azure/cosmos";
const cosmosConfig = {
endpoint: process.env.COSMOSDB_URI,
primaryKey: process.env.COSMOSDB_PRIMARY_KEY,
database: process.env.COSMOSDB_DATABASE,
container: process.env.COSMOSDB_CONTAINER,
partitionKey: {
paths: ["/id"],
},
};
export const initDB = async () => {
const cosmosClient = new CosmosClient({
endpoint: cosmosConfig.endpoint,
key: cosmosConfig.primaryKey,
});
const { database } = await cosmosClient.databases.createIfNotExists({
id: cosmosConfig.database,
});
const { container } = await database.containers.createIfNotExists({
id: cosmosConfig.container,
partitionKey: cosmosConfig.partitionKey,
});
return {
cosmosClient,
database,
container,
};
};
自定义错误
export class HttpError extends Error {
statusCode;
constructor(message, statusCode) {
super(message);
this.statusCode = statusCode;
}
get StatusCode() {
return this.statusCode;
}
}
效用
import * as crypto from "crypto";
export const generateId = () => crypto.randomUUID();
其他配置文件
{
"version": "2.0",
"logging": {
"applicationInsights": {
"samplingSettings": {
"isEnabled": true,
"excludedTypes": "Request"
}
}
},
"extensionBundle": {
"id": "Microsoft.Azure.Functions.ExtensionBundle",
"version": "[3.*, 4.0.0)"
}
您可以使用local.settings.json
设置环境变量。添加COSMOSDB_URI
和COSMOSDB_PRIMARY_KEY
作为环境变量以连接到CosmosDB。为COSMOSDB_DATABASE
和COSMOSDB_CONTAINER
指定任意值。
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "",
"FUNCTIONS_WORKER_RUNTIME": "node",
"COSMOSDB_URI": "https://<COSMOSDB_RESOURCE_NAME>.documents.azure.com:443/",
"COSMOSDB_PRIMARY_KEY": "<COSMOSDB_PRIMARY_KEY>",
"COSMOSDB_DATABASE": "node_azure_functions_db",
"COSMOSDB_CONTAINER": "todos"
}
}
COSMOSDB_URI
和 COSMOSDB_PRIMARY_KEY
的值可以在 azure 门户中的“CosmosDB 资源 => Settgins => Keys”中查看。
或者您可以使用以下命令获取它。
CosmosDB URI
az cosmosdb show
--resource-group ${AZ_RESOURCE_GROUP}
--name ${AZ_COSMOS_DB}
-o tsv
--query "documentEndpoint"
CosmosDB 主键
az cosmosdb keys list
--resource-group ${AZ_RESOURCE_GROUP}
--name ${AZ_COSMOS_DB}
--type "keys"
-o tsv
--query "primaryMasterKey"
调用 REST API
npm run start
❯ npm run start
> az-function-rest-api@1.0.0 prestart
> npm run build
> az-function-rest-api@1.0.0 build
> tsc
> az-function-rest-api@1.0.0 start
> func start
Azure Functions Core Tools
Core Tools Version: 4.0.4829 Commit hash: N/A (64-bit)
Function Runtime Version: 4.11.2.19273
[2022-10-26T02:25:55.113Z] Worker process started and initialized.
Functions:
delete-todos: [DELETE] http://localhost:7071/api/todos/{id}
get-todos: [GET] http://localhost:7071/api/todos/{id?}
patch-todos: [PATCH] http://localhost:7071/api/todos/{id}
post-todos: [POST] http://localhost:7071/api/todos
实际上,点击以下命令来测试 API。
curl -i -X POST -d '{"title":"todo 1"}' http://localhost:7071/api/todos
curl -i -X GET http://localhost:7071/api/todos
curl -i -X GET http://localhost:7071/api/todos/{id}
curl -i -X PATCH -d '{"title":"todo 1 updated"}' http://localhost:7071/api/todos/{id}
curl -i -X DELETE http://localhost:7071/api/todos/{id}
添加待办事项
❯ curl -i -X POST -d '{"title":"todo 1"}' http://localhost:7071/api/todos
HTTP/1.1 201 Created
Content-Type: text/plain; charset=utf-8
Date: Wed, 26 Oct 2022 02:53:40 GMT
Server: Kestrel
Transfer-Encoding: chunked
{
"id": "10cb9c6f-6161-4798-8de5-e213a5e2be1d",
"title": "todo 1",
"isCompleted": false,
"_rid": "8T0VAJe9jpsDAAAAAAAAAA==",
"_self": "dbs/8T0VAA==/colls/8T0VAJe9jps=/docs/8T0VAJe9jpsDAAAAAAAAAA==/",
"_etag": ""c001744e-0000-2300-0000-6358a1350000"",
"_attachments": "attachments/",
"_ts": 1666752821
}%
获取待办事项
❯ curl -i -X GET http://localhost:7071/api/todos
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Date: Wed, 26 Oct 2022 02:55:41 GMT
Server: Kestrel
Transfer-Encoding: chunked
[
{
"id": "10cb9c6f-6161-4798-8de5-e213a5e2be1d",
"title": "todo 1",
"isCompleted": false,
"_rid": "8T0VAJe9jpsDAAAAAAAAAA==",
"_self": "dbs/8T0VAA==/colls/8T0VAJe9jps=/docs/8T0VAJe9jpsDAAAAAAAAAA==/",
"_etag": ""c001744e-0000-2300-0000-6358a1350000"",
"_attachments": "attachments/",
"_ts": 1666752821
}
]%
编辑待办事项
❯ curl -i -X PATCH -d '{"title":"todo 1 updated"}' http://localhost:7071/api/todos/10cb9c6f-6161-4798-8de5-e213a5e2be1d
HTTP/1.1 204 No Content
Content-Type: text/plain; charset=utf-8
Date: Wed, 26 Oct 2022 02:56:31 GMT
Server: Kestrel
通过 ID 获取待办事项
❯ curl -i -X GET http://localhost:7071/api/todos/10cb9c6f-6161-4798-8de5-e213a5e2be1d
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Date: Wed, 26 Oct 2022 02:57:34 GMT
Server: Kestrel
Transfer-Encoding: chunked
{
"id": "10cb9c6f-6161-4798-8de5-e213a5e2be1d",
"title": "todo 1 updated",
"isCompleted": false,
"_rid": "8T0VAJe9jpsDAAAAAAAAAA==",
"_self": "dbs/8T0VAA==/colls/8T0VAJe9jps=/docs/8T0VAJe9jpsDAAAAAAAAAA==/",
"_etag": ""c0013f9b-0000-2300-0000-6358a1e00000"",
"_attachments": "attachments/",
"_ts": 1666752992
}%
确认数据已添加到 Azure 门户端的 CosmosDB。
创建更改提要侦听器函数
npx func new --name change-feed-listener --template 'Azure Cosmos DB trigger' --language typescript
.
├── change-feed-listener # <= 追加
│ ├── function.json
│ └── index.ts
├── delete-todos
│ ├── function.json
│ └── index.ts
├── get-todos
│ ├── function.json
│ └── index.ts
├── host.json
├── lib
│ ├── db
│ │ └── db-config.ts
│ ├── dtos
│ │ ├── CreateTodoDto.ts
│ │ └── UpdateTodoDto.ts
│ ├── errors
│ │ └── HttpError.ts
│ ├── models
│ │ └── TodoItem.ts
│ ├── repositories
│ │ └── todo-repository.ts
│ ├── services
│ │ └── todo-service.ts
│ └── utils
│ └── generate-id.ts
├── local.settings.json
├── package-lock.json
├── package.json
├── patch-todos
│ ├── function.json
│ └── index.ts
├── post-todos
│ ├── function.json
│ └── index.ts
└── tsconfig.json
cosmosDBTrigger
在连接的 CosmosDB 中添加或编辑数据时将执行该函数,并传递该记录的数据。擦除数据时不执行cosmosDBTrigger
。
import { AzureFunction, Context } from "@azure/functions"
const cosmosDBTrigger: AzureFunction = async function (context: Context, documents: any[]): Promise<void> {
if (!!documents && documents.length > 0) {
context.log('Document:', documents);
}
}
export default cosmosDBTrigger;
如果将connectionStringSetting
的值直接替换为连接字符串的值,则会发生错误。将实际值写入local.settings.json
,并将connectionStringSetting
的值保留为COSMOSDB_CONNECTION_STRING_DOCUMENTDB
。
{
"bindings": [
{
"type": "cosmosDBTrigger",
"name": "documents",
"direction": "in",
"leaseCollectionName": "leases",
"connectionStringSetting": "COSMOSDB_CONNECTION_STRING_DOCUMENTDB", # <= local.settings.jsonの値を参照。
"databaseName": "node_azure_functions_db",
"collectionName": "todos",
"createLeaseCollectionIfNotExists": true
}
],
"scriptFile": "../../dist/src/change-feed-listener/index.js"
}
编辑local.settings.json
并添加COSMOSDB_CONNECTION_STRING_DOCUMENTDB
。 COSMOSDB_CONNECTION_STRING_DOCUMENTDB
的值是COSMOSDB_URI
和COSMOSDB_PRIMARY_KEY
的组合。请务必在键名末尾添加_DOCUMENTDB
(需要引用来自function.json
的值)。
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "",
"FUNCTIONS_WORKER_RUNTIME": "node",
"COSMOSDB_URI": "https://<COSMOSDB_NAME>.documents.azure.com:443/",
"COSMOSDB_PRIMARY_KEY": "<COSMOSDB_PRIMARY_KEY>",
"COSMOSDB_DATABASE": "node_azure_functions_db",
"COSMOSDB_CONTAINER": "todos"
# 追加
"COSMOSDB_CONNECTION_STRING_DOCUMENTDB": "AccountEndpoint=https://<COSMOSDB_NAME>.documents.azure.com:443/;AccountKey=<COSMOSDB_PRIMARY_KEY>;"
}
}
或者您可以从 azure 门户中的“CosmosDB 资源 => Settgins => Keys”进行检查。
npm run start
添加了一个新的change-feed-listener: cosmosDBTrigger
作为函数。
❯ npm run start
> az-function-rest-api@1.0.0 prestart
> npm run build
> az-function-rest-api@1.0.0 build
> tsc
> az-function-rest-api@1.0.0 start
> func start
Azure Functions Core Tools
Core Tools Version: 4.0.4829 Commit hash: N/A (64-bit)
Function Runtime Version: 4.11.2.19273
[2022-10-26T05:15:06.386Z] Worker process started and initialized.
Functions:
delete-todos: [DELETE] http://localhost:7071/api/todos/{id}
get-todos: [GET] http://localhost:7071/api/todos/{id?}
patch-todos: [PATCH] http://localhost:7071/api/todos/{id}
post-todos: [POST] http://localhost:7071/api/todos
change-feed-listener: cosmosDBTrigger # <= 追加
当我实际添加数据时。
❯ curl -i -X POST -d '{"title":"todo 1"}' http://localhost:7071/api/todos
HTTP/1.1 201 Created
Content-Type: text/plain; charset=utf-8
Date: Wed, 26 Oct 2022 05:40:11 GMT
Server: Kestrel
Transfer-Encoding: chunked
{
"id": "29c84398-6b37-4dfd-ad8b-45403a62f4d7",
"title": "todo 1",
"isCompleted": false,
"_rid": "8T0VAJe9jpsEAAAAAAAAAA==",
"_self": "dbs/8T0VAA==/colls/8T0VAJe9jps=/docs/8T0VAJe9jpsEAAAAAAAAAA==/",
"_etag": ""cf01af89-0000-2300-0000-6358c83b0000"",
"_attachments": "attachments/",
"_ts": 1666762811
}%
change-feed-listener
日志显示从更改源接收到的数据,确认 change-feed-listener
函数已成功执行。
[2022-10-26T05:40:16.500Z] Executing 'Functions.change-feed-listener' (Reason='New changes on collection todos at 2022-10-26T05:40:16.4919090Z', Id=50591fbf-9ef8-49cc-b34a-abbdca3631d1)
[2022-10-26T05:40:16.519Z] Document: [
[2022-10-26T05:40:16.519Z] {
[2022-10-26T05:40:16.519Z] id: '29c84398-6b37-4dfd-ad8b-45403a62f4d7',
[2022-10-26T05:40:16.519Z] _rid: '8T0VAJe9jpsEAAAAAAAAAA==',
[2022-10-26T05:40:16.519Z] _self: 'dbs/8T0VAA==/colls/8T0VAJe9jps=/docs/8T0VAJe9jpsEAAAAAAAAAA==/',
[2022-10-26T05:40:16.519Z] _ts: 1666762811,
[2022-10-26T05:40:16.519Z] _etag: '"cf01af89-0000-2300-0000-6358c83b0000"',
[2022-10-26T05:40:16.519Z] title: 'todo 1',
[2022-10-26T05:40:16.519Z] isCompleted: false,
[2022-10-26T05:40:16.519Z] _lsn: 11
[2022-10-26T05:40:16.519Z] }
[2022-10-26T05:40:16.519Z] ]
功能部署
只需在项目根目录执行以下命令即可轻松完成部署。
npx func azure functionapp publish ${AZ_FUNCTION}
❯ npx func azure functionapp publish myTestFunction231917910
Setting Functions site property 'netFrameworkVersion' to 'v6.0'
Getting site publishing info...
Creating archive for current directory...
Uploading 1.61 MB [#########################################################]
Upload completed successfully.
Deployment completed successfully.
Syncing triggers...
Functions in myTestFunction231917910:
change-feed-listener - [cosmosDBTrigger]
delete-todos - [httpTrigger]
Invoke url: https://mytestfunction231917910.azurewebsites.net/api/todos/{id}
get-todos - [httpTrigger]
Invoke url: https://mytestfunction231917910.azurewebsites.net/api/todos/{id?}
patch-todos - [httpTrigger]
Invoke url: https://mytestfunction231917910.azurewebsites.net/api/todos/{id}
post-todos - [httpTrigger]
Invoke url: https://mytestfunction231917910.azurewebsites.net/api/todos
您可以使用以下命令查看已部署函数的日志。
npx func azure functionapp logstream ${AZ_FUNCTION}
结尾
我能够使用 Node.js (TypeScript) 和 Azure Functions 创建一个无服务器 REST API 应用程序。如果使用 azure 函数,则可以高速开发应用程序。部署也很容易。您可以轻松地接收来自 CosmosDB 的更改源,因此您可以轻松地添加以 azure 函数为中心的微服务应用程序。
原创声明:本文系作者授权爱码网发表,未经许可,不得转载;
原文地址:https://www.likecs.com/show-308631507.html