目录结构 #
一、项目根目录 #
1.1 根目录结构 #
text
hello/
├── _build/ # 编译输出
├── assets/ # 前端资源
├── config/ # 配置文件
├── deps/ # 依赖包
├── lib/ # 源代码
├── priv/ # 私有资源
├── test/ # 测试文件
├── .formatter.exs # 格式化配置
├── .gitignore # Git忽略文件
├── mix.exs # 项目定义
├── mix.lock # 依赖锁定
└── README.md # 项目说明
1.2 核心文件说明 #
| 文件/目录 | 说明 |
|---|---|
| mix.exs | 项目定义、依赖配置 |
| mix.lock | 依赖版本锁定 |
| config/ | 环境配置文件 |
| lib/ | 应用源代码 |
| priv/ | 数据库迁移、静态资源 |
| test/ | 测试文件 |
二、lib目录详解 #
2.1 lib目录结构 #
text
lib/
├── hello/ # 业务逻辑层
│ ├── application.ex # OTP应用
│ ├── repo.ex # Ecto仓库
│ ├── accounts/ # Accounts Context
│ │ ├── accounts.ex # Context模块
│ │ ├── user.ex # User Schema
│ │ └── user_token.ex # UserToken Schema
│ └── mailer.ex # 邮件发送
│
└── hello_web/ # Web层
├── controllers/ # 控制器
│ ├── page_controller.ex
│ ├── page_html.ex
│ └── page_html/
│ └── home.html.heex
├── components/ # 组件
│ ├── core_components.ex
│ └── layouts.ex
├── endpoint.ex # HTTP入口
├── router.ex # 路由定义
├── telemetry.ex # 监控指标
└── gettext.ex # 国际化
2.2 业务逻辑层 (lib/hello) #
text
lib/hello/
├── application.ex # OTP应用定义
├── repo.ex # Ecto仓库配置
├── accounts/ # 用户账户Context
│ ├── accounts.ex # 公共API
│ ├── user.ex # 用户Schema
│ └── user_token.ex # 令牌Schema
└── mailer.ex # 邮件配置
application.ex
elixir
defmodule Hello.Application do
use Application
@impl true
def start(_type, _args) do
children = [
HelloWeb.Telemetry,
Hello.Repo,
{DNSCluster, query: Application.get_env(:hello, :dns_cluster_query) || :ignore},
{Phoenix.PubSub, name: Hello.PubSub},
HelloWeb.Endpoint
]
opts = [strategy: :one_for_one, name: Hello.Supervisor]
Supervisor.start_link(children, opts)
end
end
repo.ex
elixir
defmodule Hello.Repo do
use Ecto.Repo,
otp_app: :hello,
adapter: Ecto.Adapters.Postgres
end
2.3 Web层 (lib/hello_web) #
text
lib/hello_web/
├── controllers/ # 控制器目录
│ ├── page_controller.ex # 页面控制器
│ ├── page_html.ex # 页面视图模块
│ └── page_html/ # 页面模板目录
│ └── home.html.heex # 首页模板
│
├── components/ # 组件目录
│ ├── core_components.ex # 核心组件
│ └── layouts.ex # 布局组件
│
├── endpoint.ex # Endpoint定义
├── router.ex # 路由定义
├── telemetry.ex # 监控配置
├── gettext.ex # 国际化配置
└── error_html.ex # 错误页面
三、config目录详解 #
3.1 配置文件结构 #
text
config/
├── config.exs # 基础配置
├── dev.exs # 开发环境配置
├── runtime.exs # 运行时配置
└── test.exs # 测试环境配置
3.2 配置文件说明 #
config.exs - 基础配置
elixir
import Config
config :hello,
ecto_repos: [Hello.Repo]
config :hello, HelloWeb.Endpoint,
url: [host: "localhost"],
render_errors: [
formats: [html: HelloWeb.ErrorHTML, json: HelloWeb.ErrorJSON],
layout: false
],
pubsub_server: Hello.PubSub,
live_view: [signing_salt: "your_salt"]
config :logger, :console,
format: "$time $metadata[$level] $message\n",
metadata: [:request_id]
import_config "#{config_env()}.exs"
dev.exs - 开发环境配置
elixir
import Config
config :hello, HelloWeb.Endpoint,
http: [ip: {127, 0, 0, 1}, port: 4000],
check_origin: false,
code_reloader: true,
debug_errors: true,
secret_key_base: "dev_secret_key"
config :hello, HelloWeb.Endpoint,
live_reload: [
patterns: [
~r"priv/static/(?!uploads/).*(js|css|png|jpeg|jpg|gif|svg)$",
~r"lib/hello_web/(controllers|live|components)/.*(ex|heex)$"
]
]
config :hello, Hello.Repo,
username: "postgres",
password: "postgres",
hostname: "localhost",
database: "hello_dev",
stacktrace: true,
show_sensitive_data_on_connection_error: true,
pool_size: 10
runtime.exs - 运行时配置
elixir
import Config
if config_env() == :prod do
database_url =
System.get_env("DATABASE_URL") ||
raise """
environment variable DATABASE_URL is missing.
"""
config :hello, Hello.Repo,
url: database_url,
pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10")
secret_key_base =
System.get_env("SECRET_KEY_BASE") ||
raise """
environment variable SECRET_KEY_BASE is missing.
"""
config :hello, HelloWeb.Endpoint,
http: [
ip: {0, 0, 0, 0},
port: String.to_integer(System.get_env("PORT") || "4000")
],
secret_key_base: secret_key_base
end
四、priv目录详解 #
4.1 priv目录结构 #
text
priv/
├── repo/
│ ├── migrations/ # 数据库迁移文件
│ │ ├── 20240101000001_create_users.exs
│ │ └── 20240101000002_create_posts.exs
│ └── seeds.exs # 种子数据
│
└── static/ # 编译后的静态资源
├── assets/
├── images/
└── favicon.ico
4.2 迁移文件示例 #
elixir
defmodule Hello.Repo.Migrations.CreateUsers do
use Ecto.Migration
def change do
create table(:users) do
add :email, :string, null: false
add :hashed_password, :string, null: false
add :confirmed_at, :naive_datetime
timestamps()
end
create unique_index(:users, [:email])
end
end
4.3 种子数据 #
elixir
alias Hello.Accounts
for email <- ["alice@example.com", "bob@example.com"] do
Accounts.register_user(%{email: email, password: "password123"})
end
五、assets目录详解 #
5.1 assets目录结构 #
text
assets/
├── css/
│ ├── app.css # 主样式文件
│ └── phoenix.css # Phoenix默认样式
│
├── js/
│ └── app.js # 主JavaScript文件
│
├── vendor/
│ └── topbar.js # 第三方库
│
└── tailwind.config.js # Tailwind配置
5.2 CSS文件示例 #
css
defmodule HelloWeb.Layouts do
use HelloWeb, :html
embed_templates "layouts/*"
end
5.3 JavaScript文件 #
javascript
import "phoenix_html"
import {Socket} from "phoenix"
import {LiveSocket} from "phoenix_live_view"
import topbar from "../vendor/topbar"
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
let liveSocket = new LiveSocket("/live", Socket, {
params: {_csrf_token: csrfToken}
})
topbar.config({barColors: {0: "#29d"}, shadowColor: "rgba(0, 0, 0, .3)"})
window.addEventListener("phx:page-loading-start", _info => topbar.show(300))
window.addEventListener("phx:page-loading-stop", _info => topbar.hide())
liveSocket.connect()
window.liveSocket = liveSocket
六、test目录详解 #
6.1 test目录结构 #
text
test/
├── hello/ # 业务逻辑测试
│ └── accounts_test.exs
│
├── hello_web/ # Web层测试
│ ├── controllers/
│ │ └── page_controller_test.exs
│ └── live/
│ └── user_live_test.exs
│
├── support/ # 测试辅助
│ ├── fixtures/
│ │ └── accounts_fixtures.ex
│ ├── conn_case.ex
│ └── data_case.ex
│
└── test_helper.exs # 测试配置
6.2 测试文件示例 #
elixir
defmodule HelloWeb.PageControllerTest do
use HelloWeb.ConnCase
test "GET /", %{conn: conn} do
conn = get(conn, ~p"/")
assert html_response(conn, 200) =~ "Peace of mind from prototype to production"
end
end
七、Context设计模式 #
7.1 Context结构 #
text
lib/hello/
├── accounts/ # Accounts Context
│ ├── accounts.ex # 公共API
│ ├── user.ex # User Schema
│ ├── user_token.ex # UserToken Schema
│ └── accounts.ex # 业务逻辑
│
├── blog/ # Blog Context
│ ├── blog.ex # 公共API
│ ├── post.ex # Post Schema
│ └── comment.ex # Comment Schema
│
└── shop/ # Shop Context
├── shop.ex # 公共API
├── product.ex # Product Schema
└── order.ex # Order Schema
7.2 Context模块示例 #
elixir
defmodule Hello.Accounts do
alias Hello.Repo
alias Hello.Accounts.User
def list_users do
Repo.all(User)
end
def get_user!(id), do: Repo.get!(User, id)
def get_user_by_email(email) do
Repo.get_by(User, email: email)
end
def register_user(attrs) do
%User{}
|> User.registration_changeset(attrs)
|> Repo.insert()
end
def update_user(%User{} = user, attrs) do
user
|> User.changeset(attrs)
|> Repo.update()
end
def delete_user(%User{} = user) do
Repo.delete(user)
end
def change_user(%User{} = user, attrs \\ %{}) do
User.changeset(user, attrs)
end
end
八、编译输出目录 #
8.1 _build目录 #
text
_build/
├── dev/
│ └── lib/
│ ├── hello/
│ ├── phoenix/
│ └── ecto/
│
└── test/
└── lib/
8.2 deps目录 #
text
deps/
├── phoenix/
├── phoenix_ecto/
├── ecto/
├── ecto_sql/
├── postgrex/
├── phoenix_live_view/
└── ...
九、文件命名规范 #
9.1 命名约定 #
| 类型 | 文件名 | 模块名 |
|---|---|---|
| 控制器 | user_controller.ex | UserController |
| 视图 | user_html.ex | UserHTML |
| 模板 | index.html.heex | - |
| Schema | user.ex | User |
| Context | accounts.ex | Accounts |
| 迁移 | 20240101_create_users.exs | CreateUsers |
| 测试 | user_test.exs | UserTest |
9.2 目录命名 #
text
lib/hello/accounts/ # 小写下划线
lib/hello_web/controllers/ # 小写下划线
test/hello_web/ # 小写下划线
十、总结 #
10.1 核心目录 #
| 目录 | 说明 |
|---|---|
| lib/hello/ | 业务逻辑层 |
| lib/hello_web/ | Web层 |
| config/ | 配置文件 |
| priv/repo/ | 数据库迁移 |
| assets/ | 前端资源 |
| test/ | 测试文件 |
10.2 设计原则 #
- 业务逻辑与Web分离: lib/hello/ vs lib/hello_web/
- Context模式: 按领域组织代码
- 约定优于配置: 遵循Phoenix约定
- 模块化: 每个文件一个模块
10.3 下一步 #
现在你已经了解了Phoenix的目录结构,接下来让我们学习 路由基础,深入了解Phoenix的路由系统!
最后更新:2026-03-28