name: phoenix-api-gen
description: 根据 OpenAPI 规范或自然语言描述生成完整的 Phoenix JSON API。创建上下文、Ecto 模式、迁移、控制器、JSON 视图/渲染器、路由条目、包含工厂的 ExUnit 测试、认证插件和租户作用域。适用于构建新的 Phoenix REST API、添加 CRUD 端点、脚手架资源或将 OpenAPI YAML 转换为 Phoenix 项目。
securitySchemes 生成认证插件。YYYYMMDDHHMMSS)*JSON 模块,旧版本使用 *View)关于项目结构、命名、上下文模式,请参阅 references/phoenix-conventions.md。
关键规则:
- 每个有界域对应一个上下文(例如 Accounts、Billing、Notifications)。
- 上下文是公共 API —— 控制器从不直接调用 Repo。
- 模式位于上下文下:MyApp.Accounts.User。
- 控制器委托给上下文;返回 {:ok, resource} 或 {:error, changeset}。
- 使用带有 action_fallback/1 的 FallbackController 来处理错误元组。
关于模式、变更集、迁移的详细信息,请参阅 references/ecto-patterns.md。
关键规则:
- 始终使用 timestamps(type: :utc_datetime_usec)。
- 二进制 ID:@primary_key {:id, :binary_id, autogenerate: true} + @foreign_key_type :binary_id。
- 当创建/更新字段不同时,分离 create_changeset/2 和 update_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 或查找字段>] 上添加复合索引。
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
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/1、insert/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
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){:error, changeset} 和 {:error, :not_found}