实战指南:触发器开发、本地测试与 Serverless Devs 部署)
后端微服务云原生【免费下载链接】midway A Node.js Serverless Framework for front-end/full-stack developers. Build the application for next decade. Works on AWS, Alibaba Cloud, Tencent Cloud and traditional VM/Container. Super easy integrate with React and Vue. 项目地址https://gitcode.com/gh_mirrors/mi/midway点击查看免费下载本文以 Midway 的 Serverless 能力为主线系统讲解如何将 Midway 应用部署到阿里云函数计算Function Compute简称 FC。内容包括内置运行时下的纯函数开发Event、HTTP、API Gateway、Timer、OSS、MNS 六类触发器、基于midwayjs/fc-starter的本地开发与单元测试、基于 Serverless Devs 的s.yaml配置与一键部署脚本以及自定义运行时下标准 Web 应用Koa/Express/Egg监听 9000 端口的上云方案。读完本文你将掌握从触发器编写、本地验证到云端部署的完整闭环并理解 fc-starter 底层的请求识别与分发机制。阿里云 Serverless 与函数计算FC概述阿里云 Serverless 是国内最早提供 Serverless 计算服务的团队之一依托阿里云强大的云基础设施能力持续进行技术突破淘宝、支付宝、钉钉、高德等均已将 Serverless 应用到生产业务云上 Serverless 产品也已在数万家企业中落地。阿里云 Serverless 包含多个产品例如函数计算 FC、轻量应用服务器 SAE 等。本文主要使用其中的**函数计算FC**部分围绕函数触发器的使用、测试与部署展开。三种部署类型根据函数运行容器形态的不同阿里云函数计算提供了多种部署方式常见的三种类型对比如下名称功能限制说明部署介质内置运行时Built-in runtime不支持流式请求和响应不支持过大的请求和响应只能部署函数接口无需自定义端口平台构建 zip 包进行部署zip 包部署自定义运行时Custom Runtime—可以部署标准应用启动 9000 端口使用平台提供的系统镜像构建 zip 包进行部署zip 包部署自定义容器Custom Container—可以部署标准应用启动 9000 端口自行控制全部环境依赖通过 Dockerfile 进行部署Dockerfile 部署其中内置运行时面向纯函数场景最简单直接自定义运行时 / 自定义容器面向需要完整 Web 框架Koa、Express、Egg 等的标准应用。平台侧还提供了多种函数创建方式控制台、CLI、IDE 等开发者可根据习惯选择。纯函数开发基于内置运行时的触发器编写下面以内置运行时部署的纯函数为例介绍六类常用触发器的编写方式。所有触发器都通过ServerlessTrigger装饰器直接绑定在方法上函数类通过Provide()注册到 IoC 容器并通过Inject()注入midwayjs/faas提供的Context。触发器的核心ServerlessTrigger与ServerlessTriggerTypeServerlessTrigger装饰器定义在midwayjs/core中其第一个参数是触发器的类型。从源码中的枚举定义packages/core/src/interface.ts可以看到export enum ServerlessTriggerType { EVENT event, HTTP http, API_GATEWAY apigw, OS os, CDN cdn, LOG log, TIMER timer, MQ mq, KAFKA kafka, HSF hsf, MTOP mtop, SSR ssr, }本文涉及的 Event、HTTP、API_GATEWAYapigw、TIMER、OSOSS、MQMNS均在其中。装饰器对每种触发器类型都有对应的重载签名见 packages/core/src/decorator/faas/serverlessTrigger.tsHTTP与API_GATEWAY类型还需要传入path、method等路由元数据其余事件类型一般只传类型本身。Event 触发器Event 触发器发布的是一个不含触发器的纯函数是最简单的类型。你可以直接通过 event 手动触发参数也可以在平台上绑定其他触发器。示例代码如下import { Provide, Inject, ServerlessTrigger, ServerlessTriggerType } from midwayjs/core; import { Context } from midwayjs/faas; Provide() export class HelloAliyunService { Inject() ctx: Context; ServerlessTrigger(ServerlessTriggerType.EVENT) async handleEvent(event: any) { return event; } }方法的入参即平台下发的原始事件返回值会直接作为函数执行结果返回。HTTP 触发器阿里云的 HTTP 触发器与 API Gateway 是两套独立的体系HTTP 触发器独立于 API 网关专门服务于 HTTP 场景相比 API 网关更容易使用和配置。示例import { Provide, Inject, ServerlessTrigger, ServerlessTriggerType } from midwayjs/core; import { Context } from midwayjs/faas; Provide() export class HelloAliyunService { Inject() ctx: Context; ServerlessTrigger(ServerlessTriggerType.HTTP, { path: /, method: get, }) async handleHTTPEvent(Query() name midway) { return hello ${name}; } }这里通过Query()参数装饰器从 query string 中取出name参数并提供了默认值midway。API Gateway 触发器API 网关在阿里云函数体系中比较特殊它类似于创建一个无触发器函数再通过平台网关将其绑定到特定路径。示例import { Provide, Inject, ServerlessTrigger, ServerlessTriggerType } from midwayjs/core; import { Context } from midwayjs/faas; Provide() export class HelloAliyunService { Inject() ctx: Context; ServerlessTrigger(ServerlessTriggerType.API_GATEWAY, { path: /api_gateway_aliyun, method: post, }) async handleAPIGatewayEvent(Body() name) { return hello ${name}; } }Timer 定时触发器定时任务触发器用于定期执行函数。需要注意的是测试完函数后请及时关闭触发器的自动执行避免产生过多扣费。import { Provide, Inject, ServerlessTrigger, ServerlessTriggerType } from midwayjs/core; import { Context } from midwayjs/faas; import type { TimerEvent } from midwayjs/fc-starter; Provide() export class HelloAliyunService { Inject() ctx: Context; ServerlessTrigger(ServerlessTriggerType.TIMER) async handleTimerEvent(event: TimerEvent) { this.ctx.logger.info(event); return hello world; } }事件结构Timer 消息返回的结构由TimerEvent类型描述形如{ triggerTime: 2024-01-01T00:00:00.000Z, triggerName: timer, payload: }其中triggerTime为触发时间的 JSON 格式字符串triggerName为触发器名称payload为定时任务配置的负载参数。OSS 触发器OSS 用于存储资源文件是阿里云的对象存储产品。当 OSS 中文件被创建或更新时会触发对应函数执行。示例import { Provide, Inject, ServerlessTrigger, ServerlessTriggerType } from midwayjs/core; import { Context } from midwayjs/faas; import type { OSSEvent } from midwayjs/fc-starter; Provide() export class HelloAliyunService { Inject() ctx: Context; ServerlessTrigger(ServerlessTriggerType.OS) async handleOSSEvent(event: OSSEvent) { // xxx } }事件结构OSS 消息返回的结构由FC.OSSEvent类型描述完整示例{ events: [ { eventName: ObjectCreated:PutObject, eventSource: acs:oss, eventTime: 2017-04-21T12:46:37.000Z, eventVersion: 1.0, oss: { bucket: { arn: acs:oss:cn-shanghai:123456789:bucketname, name: testbucket, ownerIdentity: 123456789, virtualBucket: }, object: { deltaSize: 122539, eTag: 688A7BF4F233DC9C88A80BF985AB7329, key: image/a.jpg, size: 122539 }, ossSchemaVersion: 1.0, ruleId: 9adac8e253828f4f7c0466d941fa3db81161e853 }, region: cn-shanghai, requestParameters: { sourceIPAddress: 140.205.128.221 }, responseElements: { requestId: 58F9FF2D3DF792092E12044C }, userIdentity: { principalId: 123456789 } } ] }MNS 消息队列触发器MNS消息服务触发器用于响应消息队列中的新消息。需要注意两点阿里云消息队列的 Topic 和 Queue 会产生一定费用默认提供的消息队列格式为 JSON。import { Provide, Inject, ServerlessTrigger, ServerlessTriggerType } from midwayjs/core; import { Context } from midwayjs/faas; import type { MNSEvent } from midwayjs/fc-starter; Provide() export class HelloAliyunService { Inject() ctx: Context; ServerlessTrigger(ServerlessTriggerType.MQ) async handleMNSEvent(event: MNSEvent) { // ... } }事件结构MNS 消息返回的结构由FC.MNSEvent类型描述{ Context: user custom info, TopicOwner: 1186202104331798, Message: hello topic, Subscriber: 1186202104331798, PublishTime: 1550216302888, SubscriptionName: test-fc-subscibe, MessageMD5: BA4BA9B48AC81F0F9C66F6C909C39DBB, TopicName: test-topic, MessageId: 2F5B3C281B283D4EAC694B7425288675 }触发器配置的边界代码与 s.yaml 的分工触发器的更多配置是平台相关的会写在s.yaml中例如定时任务的执行时间间隔等具体见下文部署章节。建议遵循代码里只写与函数签名强相关的元数据平台相关配置全部下沉到 yaml的原则便于后续维护。事件类型定义与 TypeScript 类型支持FC 相关的类型定义由适配器adapter导出。为了让ctx.originContext的类型定义保持正确需要在src/interface.ts中添加一行// src/interface.ts import type {} from midwayjs/fc-starter;同时midwayjs/fc-starter还提供了各类 Event 事件的类型定义可按需导入// 事件类型 import type { OSSEvent, MNSEvent, SLSEvent, CDNEvent, TimerEvent, APIGatewayEvent, TableStoreEvent, } from midwayjs/fc-starter; // InitializeContext 类型 import type { InitializeContext } from midwayjs/fc-starter;这些类型在 packages-serverless/midway-fc-starter/src/interface.ts 中均有完整定义OSSEvent包含events数组与单条 OSS 事件详情MNSEvent是字符串、流式结构MNSStreamEvent与 JSON 结构MNSJSONEvent的联合类型TimerEvent包含triggerTime、triggerName、payload三个字段APIGatewayEvent则包含path、httpMethod、headers、queryParameters、pathParameters、body、isBase64Encoded等网关事件字段。本地开发与测试本地开发HTTP 触发器与 API Gateway 类型可以通过本地npm run dev以类似传统应用的方式进行开发调试而其他类型的触发器Event、Timer、OSS、MNS 等无法通过dev在本地开发只能通过执行npm run test进行测试。本地测试基础createFunctionApp 与 mockContext函数应用的测试与传统应用类似使用createFunctionApp方法创建函数应用使用close方法关闭。基础写法import { Application, Context, Framework } from midwayjs/faas; import { mockContext } from midwayjs/fc-starter; import { createFunctionApp } from midwayjs/mock; describe(test/hello_aliyun.test.ts, () { it(should get result from event trigger, async () { // 创建函数应用 const app: Application await createFunctionAppFramework(join(__dirname, ../), { initContext: mockContext(), }); // ... await close(app); }); });mockContext方法用于模拟一个 FC Context 数据结构你也可以自定义类似结构或修改其中的部分数据例如import { Application, Context, Framework } from midwayjs/faas; import { mockContext } from midwayjs/fc-starter; import { createFunctionApp } from midwayjs/mock; describe(test/hello_aliyun.test.ts, () { it(should get result from event trigger, async () { // 创建函数应用 const app: Application await createFunctionAppFramework(join(__dirname, ../), { initContext: Object.assign(mockContext(), { function: { name: ***, handler: *** } }), }); // ... await close(app); }); });从 packages-serverless/midway-fc-starter/src/mock.ts 的源码可以看到mockContext()返回的默认结构包含requestId、credentialsSTS 临时凭证、functionname/handler/memory/timeout/initializer 等、servicename/logProject/logStore/qualifier/versionId、region、accountId、logger等字段与真实 FC 运行时的上下文对齐测试时可据此按需覆写。各触发器的测试方法不同触发器有不同的测试方法常见触发器的测试方式如下。Event 触发器通过getServerlessInstance获取类实例直接调用实例方法并传入参数进行测试。import { HelloAliyunService } from ../src/function/hello_aliyun; describe(test/hello_aliyun.test.ts, () { it(should get result from event trigger, async () { // ... const instance await app.getServerlessInstanceHelloAliyunService(HelloAliyunService); expect(await instance.handleEvent(hello world)).toEqual(hello world); // ... }); });HTTP 触发器与应用测试类似通过createFunctionApp创建函数应用再用createHttpRequest发起请求。import { HelloAliyunService } from ../src/function/hello_aliyun; describe(test/hello_aliyun.test.ts, () { it(should get result from http trigger, async () { // ... const result await createHttpRequest(app).get(/).query({ name: zhangting, }); expect(result.text).toEqual(hello zhangting); // ... }); });API Gateway 触发器与 HTTP 测试相同通过createFunctionApp创建函数应用再用createHttpRequest发起请求。import { createHttpRequest } from midwayjs/mock; describe(test/hello_aliyun.test.ts, () { it(should get result from http trigger, async () { // ... const result await createHttpRequest(app).post(api_gateway_aliyun).send({ name: zhangting, }); expect(result.text).toEqual(hello zhangting); // ... }); });Timer 定时触发器与 HTTP 测试不同通过createFunctionApp创建函数应用后用getServerlessInstance获取整个类的实例调用特定方法进行测试。平台传入的结构可以通过mockTimerEvent方法快速创建import { HelloAliyunService } from ../src/function/hello_aliyun; import { mockTimerEvent } from midwayjs/fc-starter; describe(test/hello_aliyun.test.ts, () { it(should get result from timer trigger, async () { // ... const instance await app.getServerlessInstanceHelloAliyunService(HelloAliyunService); expect(await instance.handleTimerEvent(mockTimerEvent())).toEqual(hello world); // ... }); });OSS 触发器通过createFunctionApp创建函数应用用getServerlessInstance获取整个类的实例调用特定方法进行测试。平台传入的结构可以通过mockOSSEvent方法快速创建import { HelloAliyunService } from ../src/function/hello_aliyun; import { mockOSSEvent } from midwayjs/fc-starter; describe(test/hello_aliyun.test.ts, () { it(should get result from oss trigger, async () { // ... const instance await app.getServerlessInstanceHelloAliyunService(HelloAliyunService); expect(await instance.handleOSSEvent(mockOSSEvent())).toEqual(hello world); // ... }); });MNS 触发器同样通过createFunctionApp创建函数应用用getServerlessInstance获取类的实例后调用方法平台传入的结构通过mockMNSEvent方法快速创建import { HelloAliyunService } from ../src/function/hello_aliyun; import { mockMNSEvent } from midwayjs/fc-starter; describe(test/hello_aliyun.test.ts, () { it(should get result from oss trigger, async () { // ... const instance await app.getServerlessInstanceHelloAliyunService(HelloAliyunService); expect(await instance.handleMNSEvent(mockMNSEvent())).toEqual(hello world); // ... }); });可以看到midwayjs/fc-starter的 mock 工具mockContext、mockTimerEvent、mockOSSEvent、mockMNSEvent、mockCDNEvent、mockSLSEvent、mockTableStoreEvent等与真实平台事件结构完全对齐使得单元测试无需真实云资源即可完成。纯函数部署内置运行时 Serverless Devs下面简要说明如何使用 Serverless Devs 将内置运行时的纯函数部署到阿里云。1. 确认启动器starter在项目根目录的f.yml的provider配置段中确保启动器为midwayjs/fc-starterprovider: name: aliyun starter: midwayjs/fc-startermidwayjs/fc-starter是 Midway 为阿里云函数计算提供的适配器实现源码位于 packages-serverless/midway-fc-starter主实现见 packages-serverless/midway-fc-starter/src/index.ts。2. 安装 Serverless Devs 工具阿里云使用 Serverless Devs 工具进行函数部署可以全局安装$ npm install serverless-devs/s -g安装后需要配置云账号密钥密钥配置的具体步骤请参考 Serverless Devs 官方文档中的密钥配置Key Configuration说明。3. 编写 Serverless Devs 描述文件在项目根目录创建s.yaml添加如下内容edition: 1.0.0 name: midwayApp # 项目名称 access: default # 密钥别名 vars: service: name: fc-build-demo description: demo for fc-deploy component services: project-0981cd9b07: component: devsapp/fc props: region: cn-hangzhou service: ${vars.service} function: name: hello # 函数名 handler: helloHttpService.handleHTTPEvent codeUri: . initializer: helloHttpService.initializer customDomains: - domainName: auto protocol: HTTP routeConfigs: - path: /* serviceName: ${vars.service.name} functionName: helloHttpService-handleHTTPEvent triggers: - name: http type: http config: methods: - GET authType: anonymous其中handler对应函数类名与方法的组合如helloHttpService.handleHTTPEventinitializer为初始化入口triggers定义了 HTTP 触发器的方法与鉴权方式。每次新增函数都需要调整s.yaml文件。为此Midway 提供了midwayjs/serverless-yaml-generator工具用于把装饰器中的函数信息写入s.yaml{ scripts: { generate: serverless-yaml-generator, }, devDependencies: { midwayjs/serverless-yaml-generator: ^1.0.0, }, }执行下面的命令即可把现有函数信息填充到s.yaml中并生成入口文件以便排查问题$ npm run generate该工具会以函数名作为 key 在s.yaml中查找配置如果函数已存在会覆盖特定字段如 handler、http 触发器方法等如果函数不存在会新增一个函数工具不会写入 http 路由方式为简化后续更新你可以提供一个/*路由示例中即如此。建议用户在装饰器中只定义基础的函数名、函数 handler 以及基础触发器信息如 http 触发器的 path 和 method其余配置全部写在yaml中。s.yaml的完整配置较为复杂可参考 Serverless Devs 官方文档中的描述文件规范Description File Specification。4. 编写部署脚本由于部署包含构建、拷贝等多个步骤可以编写一个部署脚本统一这一过程。例如在项目根目录新建deploy.sh#!/bin/bash set -e # 构建产物目录 export BUILD_DIST$PWD/.serverless # 构建开始时间毫秒 export BUILD_START_TIME$(date %s%3N) echo Building Midway Serverless Application # 打印当前目录 cwd echo Current Working Directory: $PWD # 打印产物目录 BUILD_DIST echo Build Directory: $BUILD_DIST # 安装当前项目依赖 npm i # 执行构建 ./node_modules/.bin/tsc || return 1 # 生成入口文件 ./node_modules/.bin/serverless-yaml-generator || return 1 # 如果 .serverless 目录存在删除后重新创建 if [ -d $BUILD_DIST ]; then rm -rf $BUILD_DIST fi mkdir $BUILD_DIST # 将 dist、*.json、*.yml 拷贝到 .serverless 目录 cp -r dist $BUILD_DIST cp *.yaml $BUILD_DIST 2/dev/null || : cp *.json $BUILD_DIST 2/dev/null || : # 将入口文件移动到 .serverless 目录 mv *.js $BUILD_DIST 2/dev/null || : # 进入 .serverless 目录 cd $BUILD_DIST # 安装线上依赖 npm install --production echo Build success # 在 .serverless 目录中执行部署 s deploy可以把deploy.sh挂到package.json的deploy命令上后续直接执行npm run deploy完成部署{ scripts: { deploy: sh deploy.sh } }注意事项deploy.sh仅在 mac 上测试过其他平台请自行调整脚本内容可以根据业务逻辑调整例如拷贝的文件等。自定义运行时部署标准 Web 应用上云当纯函数无法满足需求、需要完整 Web 框架能力时可以使用自定义运行时部署标准项目。1. 创建项目自定义运行时可以部署标准项目由于需要提供 9000 端口需要创建 Midway 的 koa/express/egg 项目。项目初始化请参考 创建第一个应用。2. 调整监听端口为 9000为了避免影响本地开发仅在入口bootstrap.js中添加端口配置const { Bootstrap } require(midwayjs/bootstrap); // 显式将用户代码作为组件引入 Bootstrap.configure({ globalConfig: { koa: { port: 9000, } } }).run()不同框架修改端口的方式参考Koa 修改端口Egg 修改端口Express 修改端口3. 平台部署配置在阿里云函数计算控制台创建函数时选择运行环境例如Node.js 18选择代码上传方式例如本地上传 zip 包启动命令指定为node bootstrap.js监听端口设置为9000。配置完成后上传压缩包即可完成部署。从源码看 fc-starter 的运行时机制理解了上层用法后深入 packages-serverless/midway-fc-starter/src/index.ts 的BootstrapStarter实现可以更清楚地把握部署到阿里云后函数的真实执行路径。启动与导出onStartBootstrapStarter继承自midwayjs/faas的AbstractBootstrapStarter见 packages/faas/src/starter.ts。在onStart中若未设置NODE_ENV默认置为production检测到FC_FUNC_CODE_PATH环境变量时开启MIDWAY_SERVERLESS_REPLACE_LOGGER用 Midway 日志替换 FC 默认上下文日志并禁用控制台颜色MIDWAY_LOGGER_DISABLE_COLORS保证日志可被 FC 控制台正确采集根据initializeMethodName默认initializer与handlerName生成导出的函数对象供平台按 handler 名调用。请求分发onRequest 中的三类场景识别onRequest(event, context, oldContext)是每次函数调用的统一入口它通过事件形态区分三类场景HTTP 模式event.constructor.name为IncomingMessage或EventEmitter属于自定义运行时下标准 HTTP 请求直达API Gateway 模式event.headers存在且同时包含queryParameters与httpMethod字段事件模式其余所有场景Event、Timer、OSS、MNS 等。识别之后HTTP/API Gateway 走this.framework.wrapHttpRequest包装为 HTTP 上下文事件模式则构造{ originEvent, originContext, logger }这样的纯事件上下文最终统一交给midwayjs/faas框架的invokeTriggerFunction完成路由匹配与函数调用见 packages/faas/src/framework.ts。这也解释了为什么 HTTP/API Gateway 触发器可以像传统应用一样npm run dev本地开发而事件类触发器只能通过测试验证。事件格式化与 Buffer 处理对于阿里云事件触发器平台传入的入参可能是 bufferBootstrapStarter会先toString(utf8)再尝试JSON.parse将原始事件还原为对象后挂到ctx.originEvent上。而在 HTTP 模式下对于post、put、delete且未带 body 的请求会通过raw-body以10mb上限读取请求体保证表单/JSON 提交都能被正确解析。错误处理与响应格式化函数执行出错时会按SERVERLESS_OUTPUT_ERROR_STACK或运行环境local/development决定是否输出错误堆栈否则返回500 Internal Server Error。同时midwayjs/faas的formatHttpResponse会根据返回内容自动推断响应类型字符串 → text/plain、Buffer → application/octet-stream 并 base64、对象 → application/json并组装isBase64Encoded / statusCode / headers / body标准结构见 packages/faas/src/framework.ts。Mock 数据结构与测试闭环mock.ts 中提供的mockContext、mockTimerEvent、mockOSSEvent、mockMNSEvent、mockCDNEvent、mockSLSEvent、mockTableStoreEvent与上文各触发器的真实事件结构一一对应是本地单元测试配合midwayjs/mock的createFunctionApp能够脱离云端跑通的关键。整个链路闭环为装饰器声明触发器 → fc-starter 在云端识别事件并分发 → faas 框架匹配 handler 并执行 → mock 工具在本地还原同等结构完成验证。小结本文从阿里云函数计算的三种部署类型出发完整覆盖了 Midway 在 FC 上的两条主线一是内置运行时下的纯函数开发六类触发器编写、类型定义、本地开发与测试二是 Serverless Devs 驱动的自动化部署f.yml启动器确认、s.yaml编写、serverless-yaml-generator自动生成、deploy.sh一键构建部署并补充了自定义运行时下标准 Koa/Express/Egg 项目监听 9000 端口的上云方案。结合midwayjs/fc-starter与midwayjs/faas的源码实现开发者可以清晰理解函数从本地测试到云端执行的完整调用链路从而在实际项目中根据业务形态纯事件处理还是完整 Web 服务选择正确的部署路线。赞分享后端微服务云原生【免费下载链接】midway A Node.js Serverless Framework for front-end/full-stack developers. Build the application for next decade. Works on AWS, Alibaba Cloud, Tencent Cloud and traditional VM/Container. Super easy integrate with React and Vue. 项目地址https://gitcode.com/gh_mirrors/mi/midway点击查看免费下载相关推荐使用 midwayjs/serverless-fc-starter 在阿里云函数计算 FC 上运行 Midway 应用使用 midwayjs/serverless fc starter 在阿里云函数计算 FC 上运行 Midway 应用 本指南围绕 packages serv后端微服务云原生7-Zip ZS终极指南六大压缩引擎如何让你的文件管理效率翻倍7 Zip ZS终极指南六大压缩引擎如何让你的文件管理效率翻倍 7 Zip ZS是一款在经典7 Zip基础上扩展了六大现代压缩引擎的强大开源压缩工具它支持B桌面应用CLIKratos Serverless部署终极指南AWS Lambda与阿里云函数计算实践Kratos Serverless部署终极指南AWS Lambda与阿里云函数计算实践 Kratos作为云原生时代的终极Go微服务框架提供了强大的功能和灵活后端微服务RPC框架Web框架云原生上一篇如何快速获取抖音内容3种高效的批量下载方案对比下一篇AI-For-Beginners 迁移学习实战用预训练 CNN 快速构建图像分类器与理想猫可视化创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考