330 lines
22 KiB
Markdown
330 lines
22 KiB
Markdown
# API 契约与项目生成
|
||
|
||
当前生成目录和启动扩展采用新布局,见 [项目维护指南](PROJECT.md)。`@app(defaultResponse: Name)` 在入口选择 App 默认响应。`@body(mode: raw|multipart)` 和 `@produces` 支持自动 Handler,手写 Handler 仅为显式选择的扩展。
|
||
|
||
首次接入请先看 [README](README.md):包含固定远程版本安装、可运行项目、完整 `.api` 示例和已有服务接入步骤。本页用于查阅语法与生成规则;下方带 `--framework-dir` 的命令用于框架源码开发。
|
||
|
||
本阶段目标:从 `.api` 定义生成可运行的 Go 项目;定义、绑定、校验与 OpenAPI 保持一致;重复生成不覆盖业务实现;业务代码继续独立于网络层。实现入口是 `cmd/go-omni/apigen`,运行时契约编译器是 `contract`。
|
||
|
||
## 与 go-zero 的对应关系
|
||
|
||
调研参考了 go-zero 官方的 [API DSL](https://go-zero.dev/reference/api-dsl/)、[参数规则](https://go-zero.dev/reference/api-dsl/parameter/)、[goctl API](https://go-zero.dev/reference/cli-guide/api/) 和 [项目结构](https://go-zero.dev/concepts/project-structure/)。借鉴其声明接口后生成项目的工作方式:统一的类型定义、路由声明、分组、前缀和命名中间件。实现独立编写,不引入 go-zero 或 Gin 运行时依赖。
|
||
|
||
| 使用体验 | go-omni 实现 |
|
||
| --- | --- |
|
||
| 新建接口项目 | `go-omni api new <directory>` |
|
||
| 按契约生成、更新 | `go-omni api generate --api user.api --dir .` |
|
||
| 熟悉的命令别名 | `api go -api user.api -dir .` |
|
||
| 校验契约 | `api validate --api user.api` |
|
||
| CI 检查生成产物是否过期 | `api generate --api user.api --dir . --check` |
|
||
| 业务逻辑目录 | `internal/logic/<group>`,使用标准 context.Context |
|
||
| 依赖装配 | `internal/svc` |
|
||
| 接口文档 | 自动生成 `docs/openapi.json`,同时提供 `/openapi.json` |
|
||
|
||
这是明确限定的 go-zero 风格方言,**不是完整 goctl 兼容实现**。不支持的声明和校验标签会在生成前报错。
|
||
|
||
## 立即试用
|
||
|
||
源码开发时,从 go-omni 仓库运行,显式引用本地框架:
|
||
|
||
```sh
|
||
go run ./cmd/go-omni api new \
|
||
--module example.com/omniDemo --framework-dir . ../omniDemo
|
||
|
||
cd ../omniDemo
|
||
go mod tidy
|
||
go run .
|
||
```
|
||
|
||
访问 `http://127.0.0.1:8080/api/greet/Alice` 可得到带 `data.message` 的统一响应。`api new` 的问候示例包含可运行逻辑;自定义契约新生成的 Logic 会明确返回 501,等待业务实现。
|
||
|
||
用现成的多分组定义生成项目,在 go-omni 仓库执行:
|
||
|
||
```sh
|
||
go run ./cmd/go-omni new --api examples/contracts/user.api \
|
||
--module example.com/userService --framework-dir . \
|
||
--log-output file --log-format json ../userService
|
||
```
|
||
|
||
`api generate` 在新目录中也会生成完整项目。默认只监听 localhost;`GO_OMNI_HTTP_ADDR` 修改地址,`GO_OMNI_TRANSPORT=native` 切换网络层。配置顺序为默认值 → `etc/config.yaml` → 环境变量。文件日志支持现有轮转配置。远程试用使用与 CLI 一致的固定版本,见 [发布说明](RELEASE.md)。
|
||
|
||
API 项目现在支持组合数据库、存储和日志:
|
||
|
||
```sh
|
||
go-omni new --api user.api --module example.com/userService \
|
||
--database mysql --storage local --log-output file ./userService
|
||
```
|
||
|
||
- `--database none|memory|mysql`:API 默认 `none`,`memory` 是不创建数据库连接的别名;MySQL 生成连接池、配置、事务入口和 readiness 探针。
|
||
- `--storage none|local|s3|r2|oss`:默认 none,生成统一 Files / Uploads / Downloads / UploadLinks 依赖;本地签名对象端点为 `/storage/object`。
|
||
- `--log-output stdout|stderr|file` 与 `--log-format json|text`:配置和无配置文件启动使用一致默认值。
|
||
|
||
初次选项记录在 `.goOmni/apiManifest.json`,以后直接 `api generate` 会继承。已有项目通过参数切换选项会报错,因为初始化代码属于业务方;可在新目录生成对照后手动迁移。`--force` 不允许覆盖 API 项目。new 与 api new 统一生成契约项目,不再使用旧式 CRUD 目录。
|
||
|
||
MySQL 密钥只从 `GO_OMNI_DB_DSN` 获取,非秘密池配置位于 YAML,环境变量优先。存储使用原有 `GO_OMNI_STORAGE_*`;API 项目不会额外生成公开的签名发放接口,鉴权和对象权限由业务实现。初始化失败会清理已打开资源,退出清理可重复调用。
|
||
|
||
HTTP 请求类型不能推导数据库表结构;迁移和 Repository 由业务自己维护。当前目录的完整示例见 [managedService](examples/managedService/README.md)。MySQL、事务审计和鉴权实现可参考 [旧目录用户管理示例](examples/userService/README.md),接入时按 [项目指南](PROJECT.md) 装配到 config/svc/logic。
|
||
|
||
## 接口定义
|
||
|
||
```go
|
||
syntax = "v1"
|
||
|
||
type UpdateUserReq {
|
||
UserID int64 `path:"userId,range=[1:1000000]"`
|
||
Notify bool `form:"notify,default=false"`
|
||
Name string `json:"name" binding:"required,max=50"`
|
||
Age *int `json:"age,optional,range=[0:150]"`
|
||
}
|
||
|
||
type UserResp {
|
||
UserID int64 `json:"userId"`
|
||
Name string `json:"name"`
|
||
}
|
||
|
||
@server (
|
||
group: user
|
||
prefix: /api/v1
|
||
)
|
||
service user-api {
|
||
@doc "Update a user"
|
||
@handler updateUser
|
||
put /users/:userId (UpdateUserReq) returns (UserResp)
|
||
}
|
||
```
|
||
|
||
类型和 Go 字段使用导出命名;JSON、query、path 名称使用 lowerCamelCase;Header 使用 HTTP 标准名称。响应类型定义业务返回值。未配置时使用默认公共体;当前源码支持在 `.api` 中声明公共响应模板,选择字段和结构,并按分组复用或由单接口覆盖,详见 [公共响应体](RESPONSE.md)。
|
||
|
||
支持:
|
||
|
||
- 可省略的 `syntax = "v1"`;`info` 中的 title、version 和描述性元数据。
|
||
- 单独的 `type X { ... }` 与 `type (...)`;基础标量、指针、嵌套命名类型、切片、字符串键 map。嵌套对象只使用 JSON 字段。
|
||
- 相对 `.api` 导入及 `import (...)`,重复导入去重;拒绝循环和越过入口目录的导入。单文件 1 MiB、总计 8 MiB、最多 64 个文件,限制导入/类型深度及 schema 展开规模。
|
||
- 同一 service 名称的多个块;`@server` 的 group、prefix、middleware、auth、errors、response,group 可以写 `admin/user`。
|
||
- 每个路由必须有 `@handler`;可选单行/多行 `@doc`、`@auth`、`@errors`、`@response`;GET/POST/PUT/PATCH/DELETE/HEAD/OPTIONS。
|
||
- 可省略请求或响应;有响应默认 200,无响应默认 204。Omni 扩展 `@status 201`、`@status 202`;204 不允许响应类型,HEAD 不允许响应体,GET/HEAD 不允许 JSON 请求体。
|
||
- 每个请求字段只使用一个来源:`json`、`path`、`form`、`header`。本版 `form` 明确表示 URL query,不解析 multipart 或表单 body。query 切片用重复键;header/path 使用标量。
|
||
- 来源标签中的 `optional` / `omitempty`、标量 `default`、标量 `options=a|b`、数值 `range=[1:100]`(支持开区间和单边界)。
|
||
- `binding` 的 `required`、`min`、`max`、`len`、`email`。长度按 Unicode 字符或切片元素数;范围边界必须有限且位于 ±2^53 内。
|
||
|
||
暂不支持匿名嵌入、别名、递归类型、未声明的外部 Go 类型、自定义 JSON 编解码、`[]byte`、泛型、`json:",string"`、任意 validator 标签、go-zero 的 JWT/签名/SSE 声明,以及客户端 SDK 生成。认证验证通过声明关联的命名中间件实现。未列出的语法会明确报错。
|
||
|
||
业务错误、具名鉴权、多行文档及下述开发命令从 CLI `v0.7.0-preview.1` 提供。公共响应模板从 `v0.8.0-preview` 提供,CLI 与业务项目中的框架依赖需一起升级。
|
||
|
||
## 业务错误:声明、代码和文档共用一份定义
|
||
|
||
**错误目录也可以只在 Go 中维护。** 从 `v0.11.1-preview` 起,生成路由没有声明错误列表时,继承 `omni.WithErrors(catalog)` 注册的 App 目录;`.api` 继续定义接口和返回格式,无需重复登记错误。全局中间件提前返回同样适用。显式引用 `.api` 错误时才使用路由独立目录,规则见 [目录继承](errors/README.md)。CLI 不执行运行时 Go 配置,因此静态文档不会自动枚举只在 Go 中定义的具体错误。
|
||
|
||
从 `v0.10.0-preview` 起,推荐用 `errorCatalog` 同时定义逻辑标识、对外业务码、提示和 HTTP 状态:
|
||
|
||
```text
|
||
errorCatalog CommonErrors (
|
||
defaultStatus: 200
|
||
errors: {
|
||
"UsernameInvalid": {"key": "USERNAME_INVALID", "code": 400002, "message": "用户名格式不正确"},
|
||
"PasswordInvalid": {"key": "PASSWORD_INVALID", "code": 400002, "message": "密码格式不正确"},
|
||
"Forbidden": {"key": "FORBIDDEN", "status": 403, "code": 403001, "message": "没有权限"}
|
||
}
|
||
)
|
||
```
|
||
|
||
对象键是生成函数名;`key` 是唯一逻辑标识,允许项目自己的命名;`code` 是对外业务码,可以重复。数字码需搭配 `codeType: int32` 或 `int64` 的公共响应;字符串码搭配 `string`。目录不规定返回体字段。
|
||
|
||
省略 `defaultStatus` 时为 400;设为 200 时,只作用于目录里未单独写 `status` 的条目。生成路由自动绑定它实际声明的错误目录,在 App 中间件之前生效。业务只调用 `apierrors.UsernameInvalid()`;无需再维护 `codes` / `messages`,也无需修改 main。手写路由可复用生成的 `apierrors.Catalog()`。
|
||
|
||
同一 HTTP 200 下,OpenAPI 用 `anyOf` 保留成功 DTO 和错误体,日志与指标仍识别业务失败。完整语法、路由作用域与手写项目配置见 [统一错误目录](errors/README.md),可运行契约见 [errors.api](examples/contracts/errors.api)。
|
||
|
||
从 `v0.11.0-preview` 起,目录条目支持 `params` 有序列表与 `{name}` 提示模板:
|
||
|
||
```json
|
||
"TextTooLong": {
|
||
"key": "TEXT_TOO_LONG", "code": 400002,
|
||
"message": "{label}不能超过{max}个字符",
|
||
"params": [
|
||
{"name": "label", "type": "string", "example": "企业名称"},
|
||
{"name": "max", "type": "int", "example": 100}
|
||
]
|
||
}
|
||
```
|
||
|
||
生成 `apierrors.TextTooLong(label string, max int)`,返回时 Message 已填好;固定提示保持无参数函数。声明顺序决定参数顺序。模板校验、运行时缺参/类型错误、转义及非 HTTP 使用规则见 [提示模板参数](errors/README.md)。OpenAPI 的错误目录保留参数定义,响应示例使用具体文案。
|
||
|
||
原有声明继续支持:
|
||
|
||
```go
|
||
error (
|
||
Unauthenticated 401 "UNAUTHENTICATED" "authentication required"
|
||
Forbidden 403 "FORBIDDEN" "permission denied"
|
||
UserNotFound 404 "USER_NOT_FOUND" "user not found"
|
||
EmailTaken 409 "EMAIL_TAKEN" "email already in use"
|
||
)
|
||
```
|
||
|
||
也可写单条 `error EmailTaken 409 "EMAIL_TAKEN" "email already in use"`,或把错误目录放进相对导入的 `errors.api`。
|
||
名称用 UpperCamelCase,HTTP 状态允许 200–599 中可携带响应体的最终状态(排除 204/205/304),旧声明的逻辑错误码用大写字母/数字/下划线且全局唯一,message 是可公开返回的稳定提示。`Catalog` 为生成函数保留名称。
|
||
|
||
`@server (errors: Unauthenticated)` 声明组级错误;路由 `@errors UserNotFound, EmailTaken` 追加到组级列表并去重。
|
||
`@errors none` 清空 `.api` 分组继承的错误列表,随后按未设置路由目录处理,继承 App 错误目录。未声明、重复声明或生成符号冲突会报告源码位置。
|
||
|
||
生成 `internal/apierrors/errorsGen.go`:
|
||
|
||
```go
|
||
return nil, apierrors.EmailTaken()
|
||
// 可用 apierrors.CodeEmailTaken 引用同一个错误码。
|
||
```
|
||
|
||
每次调用返回新错误,避免并发修改共享错误对象。生成器不会自动把任意数据库错误转成业务错误;由 Service/适配层调用这些函数。
|
||
内部 Cause 可能进入访问日志,敏感驱动报错应像 userService 示例一样先归类再转换。
|
||
|
||
OpenAPI 对每个状态按选定的响应模板生成结构和命名 examples,同一 409 下多个错误不会相互覆盖;平铺模式的纯业务状态还列出 code enum。
|
||
默认框架错误状态与自动添加的鉴权 401 保留开放的 code 字段,容纳校验器/中间件实际错误。
|
||
`x-omni-errors` 提供错误目录与路由关联。目录解析已定义的错误,不自动推断数据库或其他未声明错误的业务含义,也不穷举所有运行时错误。
|
||
|
||
## 鉴权:声明与路由注册一起变化
|
||
|
||
```go
|
||
auth UserAuth (
|
||
type: bearer
|
||
middleware: Auth
|
||
description: "Application access token"
|
||
)
|
||
|
||
@server (
|
||
group: user
|
||
prefix: /api/v1
|
||
auth: UserAuth
|
||
errors: Unauthenticated
|
||
)
|
||
service user-api {
|
||
@errors Forbidden, EmailTaken
|
||
@handler createUser
|
||
@status 201
|
||
post /users (CreateUserReq) returns (UserResp)
|
||
|
||
@auth none
|
||
@errors none
|
||
@handler publicStatus
|
||
get /status
|
||
}
|
||
```
|
||
|
||
`auth` 是具名契约,`middleware` 指向实际 hook;生成器把它放在普通 middleware 前面执行,并在注册任何路由前检查 hook 非空。
|
||
新 hook 未实现时返回 501,已有手写 hook 保留。需要在 hook 中验证凭证、失效时间、签发者、权限等;声明不会自动实现登录或 JWT 校验。
|
||
|
||
- `type: bearer`:对应 HTTP Bearer,可选 `bearerFormat: JWT`,这只是文档提示。
|
||
- `type: apiKey`:必须同时声明 `in: header` 或 `in: cookie` 和 `name`,例如 `X-API-Key` 或 `sessionId`。
|
||
- `auth: UserAuth` 可在组级使用;路由 `@auth OtherAuth` 替换,`@auth none` 撤销继承的声明鉴权。普通 middleware 继续保留。
|
||
- 不要再把该 auth 的 hook 重复写进普通 middleware。不支持 query token、多个方案 AND/OR、OAuth2 scopes 或隐含认证策略。
|
||
- 被保护路由自动描述 401;403 和具体错误码由 `@errors` 明确声明。未声明 auth 的旧项目仍按其原有 middleware 执行。
|
||
|
||
文档生成 `components.securitySchemes` 与逐接口 `security`;显式公共接口使用空数组。
|
||
结构遵循 [OpenAPI 3.0.3 Security Requirement](https://spec.openapis.org/oas/v3.0.3.html#security-requirement-object)。
|
||
|
||
## 文档和日常开发命令
|
||
|
||
保留单行 `@doc "摘要"`,也可以:
|
||
|
||
```go
|
||
@doc (
|
||
summary: "Create a user"
|
||
description: "Writes user data and audit in the same transaction"
|
||
tags: users, administration
|
||
deprecated: false
|
||
)
|
||
```
|
||
|
||
未设置 tags 时使用 group 分组。`info` 的 description/desc 也会进入 OpenAPI;两者不能同时声明。
|
||
|
||
在生成项目目录内,不必重复输入 API 路径:
|
||
|
||
```sh
|
||
go-omni api validate # 发现 manifest 中的入口并校验
|
||
go-omni api inspect # 方法、路径、Handler、鉴权与业务错误总览
|
||
go-omni api generate --dry-run # 预览文件变化、契约变更和 Logic 提示,完全不写文件
|
||
go-omni api generate # 实际生成,保留手写业务
|
||
go-omni api generate --check # 只读检查产物漂移及明确的 Logic 签名错误,可用于 CI
|
||
go-omni api export > openapi.json # 独立导出纯 JSON,无需启动服务
|
||
go test ./...
|
||
```
|
||
|
||
`--dry-run` 和 `--check` 不能组合。新项目预览也不创建目录;预览不是审批或锁定,实际生成会重新检查文件状态。
|
||
契约变化会定位到相关 Logic 文件,涵盖嵌套 DTO、原始 body 和流式返回。生成后签名不一致的问题仍可由 `--check` 检出;项目类型别名/build tags 等不确定情况提示 REVIEW,最终用 `go test ./...` 验证。详见 [契约变更提示](PROJECT.md#契约变更提示)。
|
||
在项目外可显式传 `--api`;生成时传 `--dir` 可定位其他项目。不会猜测多个 .api 中哪个是入口。
|
||
路由总览的 `(undeclared)` 仅表示没有声明 auth;旧式中间件仍可能做认证。
|
||
外发文档可用 export;项目内受生成器管理的 `docs/openapi.json` 请通过 generate 更新,以同步文件哈希。
|
||
|
||
## 迁移扩展
|
||
|
||
- 动态 JSON 字段 `any` / `interface{}`,包含切片和 map;外围契约仍严格。
|
||
- `scalar Name (...)` 显式引用项目类型,声明 JSON 输入/输出类型及文本绑定;支持已有 JSON/Text 转换方法。
|
||
- `@body (mode: json/raw/multipart; maxBytes: ...)`:按路由限制请求体。
|
||
- `@binding custom` 生成保留的绑定 hook;`@handlerMode manual` 生成保留的整个 Handler,仅用于自定义协议。raw/multipart 默认生成自动 Handler,将受限流交给 Logic。
|
||
- `@produces "text/plain"` 等声明无 DTO 成功输出;自动模式的 Logic 返回 `*httpx.Stream`,Handler 负责流式发送和关闭。默认状态 200。manual 模式保留项目自己的输出实现。
|
||
- 自动 multipart Handler 检查未读部分和结束边界;损坏/截断默认返回 400,超限为 413。公共状态、码和文案仍可用项目错误目录配置,见 [流式接入与错误处理](PROJECT.md#自动上传回调与下载)。
|
||
|
||
迁移扩展起始于 v0.9.0,原有手写扩展说明见 [迁移扩展指南](MIGRATION.md)。当前根目录入口和自动流式 Handler 要求 CLI 与框架依赖一起使用 v0.12.0-preview;版本说明和新目录维护规则见 [项目指南](PROJECT.md)。不兼容全部 goctl 语法。
|
||
|
||
## 参数语义
|
||
|
||
无 optional/default 的字段按“是否出现”判断必填,`false`、`0`、`""` 是已提供的值。额外声明 `binding:"required"` 才会拒绝标量零值。默认值只在字段缺失时使用,不替换显式的空值。
|
||
|
||
业务需要区分“没传”和“传了零值”时使用 `*T` 加 optional。指针允许 JSON null;普通非指针字段拒绝 null(动态 `any` 允许);`binding:"required"` 拒绝指针 null。缺失与 null 都会得到 nil,本版没有三态 Nullable 类型。响应端的 `optional` 不等于 Go 编码器的 `omitempty`;需要省略零值时显式使用 `omitempty`。
|
||
|
||
JSON 必须是一个 object,Content-Type 为 application/json。默认最多 1 MiB,包含 chunked/未知长度和尾部空白;超限返回 413。重复 JSON key、未知字段、多个 JSON 值、标量 query/header 重复值、格式和约束错误返回 400;错误 Content-Type 返回 415。失败不会部分修改目标 DTO;错误详情只保留在内部 cause,公共响应不回显原始参数。
|
||
|
||
`contract.Binder` 在服务启动时编译,随后并发复用,不会每次请求重新分析标签。CLI 用相同编译器产生文档,因此来源、必填、默认、nullable、枚举、范围和长度具有同一来源。响应类型和约束由业务实现遵守,不在每次响应上再次校验;非 nullable 集合请返回空集合而不是 nil。
|
||
|
||
## 生成目录和业务边界
|
||
|
||
```text
|
||
api/ # 开发者维护的契约及导入
|
||
main.go # 启动、日志、优雅退出
|
||
etc/config.yaml
|
||
internal/config/
|
||
internal/svc/ # 依赖初始化和释放
|
||
internal/types/typesGen.go
|
||
internal/apierrors/errorsGen.go # 声明错误后生成
|
||
internal/handler/routesGen.go
|
||
internal/handler/openapiGen.go
|
||
internal/handler/<group>/handlersGen.go
|
||
internal/logic/<group>/logic.go
|
||
internal/logic/<group>/<operation>.go
|
||
internal/repository/<group>/repository.go
|
||
internal/middleware/registryGen.go
|
||
internal/middleware/<name>.go
|
||
docs/openapi.json
|
||
.goOmni/apiManifest.json
|
||
```
|
||
|
||
Logic 方法接收标准 `context.Context` 与生成的请求 DTO;Handler 调用 Logic 并完成响应包装。网络适配器仍经 Omni/httpx 接入。Repository 是业务所有的扩展位置,不自动生成虚构 CRUD 查询。
|
||
|
||
命名中间件按声明顺序组合,第一个在最外层。新 hook 初始返回 501,必须实现后才放行;registry 自动维护,hook 文件只创建一次。缺失 hook 在注册任何路由前报错。文档通过 `x-omni-middleware` 标出命名 hook,不把名称误当成已实现的认证协议。
|
||
|
||
## 安全更新与 CI
|
||
|
||
在生成项目中安装/使用与框架版本相符的 go-omni CLI:
|
||
|
||
```sh
|
||
go-omni api validate --api api/user.api
|
||
go-omni api generate --api api/user.api --dir .
|
||
go-omni api generate --api api/user.api --dir . --check
|
||
go test ./...
|
||
```
|
||
|
||
`main.go`、`Gen.go` 和 OpenAPI 由生成器管理,manifest 保存其 SHA-256。手动修改后更新会报冲突。Logic、Repository、配置、依赖装配和中间件实现按字节保留;只补缺失文件。删除接口/分组时只移除旧的生成文件,保留业务代码;已删除类型仍被旧业务引用时,需要明确迁移该业务文件,编译器会提示。
|
||
|
||
`--check` 只读,检查产物是否过期,以及所需骨架是否缺失。契约复制到项目后,请编辑项目内的 API;外部定义与已存在副本不一致时会拒绝覆盖。一个项目由一个 API 入口管理,可以通过导入拆分文件。
|
||
|
||
生成器拒绝输出路径穿越、受管理路径上的符号链接、大小写路径冲突和无所有权的覆盖。生成前统一校验/格式化,锁文件防止两个生成器同时修改;文件以原子替换写入,普通写入失败会回滚。本版不是整个目录的崩溃原子事务:进程被强杀或机器断电后,应按 Git 恢复生成产物和 manifest,再重试。请将 manifest 与生成产物一并提交,也不要在生成过程中编辑这些文件。
|
||
|
||
生成器不自动安装依赖、不访问数据库、不推送仓库、不替业务实现权限规则。
|
||
|
||
## 验证
|
||
|
||
```sh
|
||
go test -race -p=2 ./contract ./openapi ./cmd/go-omni/...
|
||
python3 scripts/verifyManagedProject.py
|
||
```
|
||
|
||
Go 测试覆盖契约拒绝规则、多源绑定、零值/缺失、默认/范围/嵌套集合、文档约束、幂等更新、只读检查、业务保留、删除路由、路径边界及生成项目编译。Python 验收会在临时目录生成项目,分别启动 nethttp/native,验证真实 HTTP 请求、中间件、日志 requestId 和优雅退出;结束后清理自己创建的进程和临时文件。结果记录在 `performance/managedProjectLocal.json`;完整记录见 [第一版验收](performance/managedProjectAcceptance.md)。
|