OA0 = Omni AI 0
OA0 是一个探索 AI 的论坛
现在注册
已注册用户请  登录
OA0  ›  技能包  ›  phoenix-api-gen: 从 OpenAPI 规范生成完整的 Phoenix JSON API

phoenix-api-gen: 从 OpenAPI 规范生成完整的 Phoenix JSON API

 
  jwt ·  2026-02-01 18:23:32 · 3 次点击  · 0 条评论  

name: phoenix-api-gen
description: 根据 OpenAPI 规范或自然语言描述生成完整的 Phoenix JSON API。创建上下文、Ecto 模式、迁移、控制器、JSON 视图/渲染器、路由条目、包含工厂的 ExUnit 测试、认证插件和租户作用域。适用于构建新的 Phoenix REST API、添加 CRUD 端点、脚手架资源或将 OpenAPI YAML 转换为 Phoenix 项目。


Phoenix API 生成器

工作流程

从 OpenAPI YAML 生成

  1. 解析 OpenAPI 规范 —— 提取路径、模式、请求/响应体。
  2. 将每个模式映射到 Ecto 模式 + 迁移。
  3. 将每个路径映射到控制器动作;按资源上下文分组。
  4. 根据 securitySchemes 生成认证插件。
  5. 生成涵盖成功路径和验证错误的 ExUnit 测试。

从自然语言生成

  1. 从描述中提取资源、字段、类型和关系。
  2. 推断上下文边界(分组相关资源)。
  3. 生成模式、迁移、控制器、视图、路由和测试。
  4. 在写入文件前请用户确认。

文件生成顺序

  1. 迁移(时间戳前缀:YYYYMMDDHHMMSS
  2. Ecto 模式 + 变更集
  3. 上下文模块(CRUD 函数)
  4. 控制器 + FallbackController
  5. JSON 渲染器(Phoenix 1.7+ 使用 *JSON 模块,旧版本使用 *View
  6. 路由作用域 + 管道
  7. 认证插件
  8. 测试 + 工厂

Phoenix 约定

关于项目结构、命名、上下文模式,请参阅 references/phoenix-conventions.md

关键规则:
- 每个有界域对应一个上下文(例如 AccountsBillingNotifications)。
- 上下文是公共 API —— 控制器从不直接调用 Repo。
- 模式位于上下文下:MyApp.Accounts.User
- 控制器委托给上下文;返回 {:ok, resource}{:error, changeset}
- 使用带有 action_fallback/1FallbackController 来处理错误元组。

Ecto 模式

关于模式、变更集、迁移的详细信息,请参阅 references/ecto-patterns.md

关键规则:
- 始终使用 timestamps(type: :utc_datetime_usec)
- 二进制 ID:@primary_key {:id, :binary_id, autogenerate: true} + @foreign_key_type :binary_id
- 当创建/更新字段不同时,分离 create_changeset/2update_changeset/2
- 在变更集中验证必填字段、格式和约束 —— 而不是在控制器中。

多租户

为每个租户作用域的表添加 tenant_id :binary_id。模式如下:

# 在上下文中
def list_resources(tenant_id) do
  Resource
  |> where(tenant_id: ^tenant_id)
  |> Repo.all()
end

# 在插件中 —— 从 conn 提取租户并赋值
defmodule MyAppWeb.Plugs.SetTenant do
  import Plug.Conn
  def init(opts), do: opts
  def call(conn, _opts) do
    tenant_id = get_req_header(conn, "x-tenant-id") |> List.first()
    assign(conn, :tenant_id, tenant_id)
  end
end

始终在 [:tenant_id, <resource_id 或查找字段>] 上添加复合索引。

认证插件

API 密钥

defmodule MyAppWeb.Plugs.ApiKeyAuth do
  import Plug.Conn
  def init(opts), do: opts
  def call(conn, _opts) do
    with [key] <- get_req_header(conn, "x-api-key"),
         {:ok, account} <- Accounts.authenticate_api_key(key) do
      assign(conn, :current_account, account)
    else
      _ -> conn |> send_resp(401, "Unauthorized") |> halt()
    end
  end
end

Bearer 令牌

defmodule MyAppWeb.Plugs.BearerAuth do
  import Plug.Conn
  def init(opts), do: opts
  def call(conn, _opts) do
    with ["Bearer " <> token] <- get_req_header(conn, "authorization"),
         {:ok, claims} <- MyApp.Token.verify(token) do
      assign(conn, :current_user, claims)
    else
      _ -> conn |> send_resp(401, "Unauthorized") |> halt()
    end
  end
end

路由结构

scope "/api/v1", MyAppWeb do
  pipe_through [:api, :authenticated]

  resources "/users", UserController, except: [:new, :edit]
  resources "/teams", TeamController, except: [:new, :edit] do
    resources "/members", MemberController, only: [:index, :create, :delete]
  end
end

测试生成

关于 ExUnit、Mox、工厂模式的详细信息,请参阅 references/test-patterns.md

关键规则:
- 在所有不共享状态的测试上使用 async: true
- 使用 Ecto.Adapters.SQL.Sandbox 进行数据库隔离。
- 使用 ex_machina 或手写的 build/1insert/1 的工厂模块。
- 分别测试上下文和控制器。
- 对于控制器:测试状态码、响应体结构和错误情况。
- 使用 Mox 模拟外部服务 —— 定义行为,在测试中设置期望。

控制器测试模板

defmodule MyAppWeb.UserControllerTest do
  use MyAppWeb.ConnCase, async: true

  import MyApp.Factory

  setup %{conn: conn} do
    user = insert(:user)
    conn = put_req_header(conn, "authorization", "Bearer #{token_for(user)}")
    {:ok, conn: conn, user: user}
  end

  describe "index" do
    test "列出用户", %{conn: conn} do
      conn = get(conn, ~p"/api/v1/users")
      assert %{"data" => users} = json_response(conn, 200)
      assert is_list(users)
    end
  end

  describe "create" do
    test "参数有效时返回 201", %{conn: conn} do
      params = params_for(:user)
      conn = post(conn, ~p"/api/v1/users", user: params)
      assert %{"data" => %{"id" => _}} = json_response(conn, 201)
    end

    test "参数无效时返回 422", %{conn: conn} do
      conn = post(conn, ~p"/api/v1/users", user: %{})
      assert json_response(conn, 422)["errors"] != %{}
    end
  end
end

JSON 渲染器 (Phoenix 1.7+)

defmodule MyAppWeb.UserJSON do
  def index(%{users: users}), do: %{data: for(u <- users, do: data(u))}
  def show(%{user: user}), do: %{data: data(user)}

  defp data(user) do
    %{
      id: user.id,
      email: user.email,
      inserted_at: user.inserted_at
    }
  end
end

写入前检查清单

  • [ ] 迁移使用 timestamps(type: :utc_datetime_usec)
  • [ ] 如果项目使用 UUID,则配置了二进制 ID
  • [ ] 在需要的地方应用了租户作用域
  • [ ] 认证插件已在路由管道中连接
  • [ ] FallbackController 处理 {:error, changeset}{:error, :not_found}
  • [ ] 测试覆盖 200、201、404、422 状态码
  • [ ] 为每个模式定义了工厂
3 次点击  ∙  0 人收藏  
登录后收藏  
目前尚无回复
0 条回复
About   ·   Help   ·    
OA0 - Omni AI 0 一个探索 AI 的社区
沪ICP备2024103595号-2
Developed with Cursor