Elixir/Phoenix 开发助手。运行并解读 mix test、credo、dialyzer、format。生成符合 OTP 规范的模块(Contexts、Schemas、GenServers 等)。调试编译错误,协助 Ecto 迁移与查询。适用于编写模块、修复测试、重构代码及理解 OTP 模式。
Elixir Dev. 运行混合命令
功能概述
Elixir Dev. 运行混合命令是一项面向实际任务的技能,主要用于请参看参考文献/ mix- commands. md 以获取完整命令的参考文献;Test. 解释失败 : 1. (MatchError) - 模式匹配失败;
核心要点
- . 请检查返回值形状;
- . (Ecto.;
- NoResults)。
使用与执行
它将相关步骤、工具调用和结果整理方式集中到统一流程中,帮助使用者更快完成目标并减少重复操作。使用时应结合输入条件选择合适的执行方式,核对必要参数、依赖环境与输出内容,并按原始要求处理异常情况。从功能定位来看,该技能强调把分散的操作要求整理成清晰、可复用的处理流程,使用户能够围绕既定目标快速准备输入、选择执行方式并获得结构化结果。实际使用前应先确认任务范围、数据来源、运行环境、必要权限和关键参数,再依据技能说明逐步执行;
结果检查与注意事项
若输入条件不完整,应先补齐信息或采用保守配置,避免因错误假设导致结果偏离需求。执行过程中需要关注工具调用是否成功、接口或依赖是否可用、输出格式是否符合预期,并对异常提示、缺失字段和边界情况进行处理;涉及批量任务时,还应保存进度,避免中断后重复操作。该技能适合用于一次性任务,也可以接入自动化工作流,与其他技能或上层代理配合完成更完整的业务链路;在组合使用时,应明确每一步的输入输出关系,并避免不同步骤之间出现参数冲突。
Elixir 开发
运行 Mix 命令
完整命令参考请见 references/mix-commands.md。
测试
# 运行全部测试 mix test # 指定文件或行号 mix test test/my_app/accounts_test.exs:42 # 按标签筛选 mix test --only integration # 仅重跑失败用例(需在前一次运行中启用 --failed 标志) mix test --failed # 启用覆盖率统计 mix test --cover
解读测试失败信息:
** (MatchError)—— 模式匹配失败;检查返回值结构是否符合预期。** (Ecto.NoResultsError)—— 对不存在的 ID 调用了Repo.get!;改用Repo.get或补充种子数据(seed data)。** (DBConnection.OwnershipError)—— 缺少async: true配置或未正确设置 sandbox。no function clause matching—— 函数参数元数(arity)错误,或传入了意外类型的参数。
Credo
mix credo --strict mix credo suggest --format json mix credo explain MyApp.Module # 解释指定模块中的问题
常见 Credo 修复方式:
Credo.Check.Readability.ModuleDoc—— 添加@moduledoc注释。Credo.Check.Refactor.CyclomaticComplexity—— 提取辅助函数以降低圈复杂度。Credo.Check.Design.TagTODO—— 处理或移除 TODO 注释。
Dialyzer
mix dialyzer mix dialyzer --format short
常见 Dialyzer 警告含义:
The pattern can never match—— 不可达代码(dead code),或模式中类型不匹配。Function has no local return—— 所有执行路径均会崩溃;检查内部调用逻辑。The call will never return—— 调用了总会抛出异常的函数。- 修复建议:添加
@spec类型签名;万不得已时可使用@dialyzer {:nowarn_function, func: arity}抑制警告。
代码格式化
mix format mix format --check-formatted # CI 模式 —— 若存在未格式化代码则退出码为 1
模块生成规范
所有公开函数必须包含 @moduledoc、@doc 和 @spec。
Context 模块
defmodule MyApp.Notifications do
@moduledoc """
管理通知投递与用户偏好设置。
"""
import Ecto.Query
alias MyApp.Repo
alias MyApp.Notifications.Notification
@doc "按时间倒序列出指定用户的全部通知。"
@spec list_notifications(String.t(), keyword()) :: [Notification.t()]
def list_notifications(user_id, opts \\ []) do
limit = Keyword.get(opts, :limit, 50)
Notification
|> where(user_id: ^user_id)
|> order_by(desc: :inserted_at)
|> limit(^limit)
|> Repo.all()
end
end
Schema 模块
defmodule MyApp.Notifications.Notification do
@moduledoc """
推送/邮件/SMS 通知的数据结构定义。
"""
use Ecto.Schema
import Ecto.Changeset
@type t :: %__MODULE__{}
@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id
@timestamps_opts [type: :utc_datetime_usec]
schema "notifications" do
field :channel, Ecto.Enum, values: [:push, :email, :sms]
field :title, :string
field :body, :string
field :delivered_at, :utc_datetime_usec
field :user_id, :binary_id
timestamps()
end
@required ~w(channel title body user_id)a
@doc false
def changeset(notification, attrs) do
notification
|> cast(attrs, @required ++ [:delivered_at])
|> validate_required(@required)
|> validate_length(:title, max: 255)
end
end
OTP 模式
GenServer、Supervisor、Agent、Task 等典型 OTP 模式的详细说明请参阅 references/otp-patterns.md。
何时选用何种模式
| 模式 | 适用场景 |
|---|---|
| GenServer | 需要维护状态并支持同步/异步调用的进程(如缓存、限流器、连接池) |
| Agent | 仅需简单封装状态,无复杂业务逻辑 |
| Task | 一次性异步任务,可选择“即发即弃”或等待结果 |
| Task.Supervisor | 受监督的“即发即弃”任务 |
| Supervisor | 管理子进程生命周期(启动、重启策略等) |
| Registry | 基于名称或键进行进程查找 |
| DynamicSupervisor | 运行时动态启动子进程 |
GenServer 模板
defmodule MyApp.RateLimiter do
@moduledoc "令牌桶限流器。"
use GenServer
# 客户端 API
def start_link(opts) do
name = Keyword.get(opts, :name, __MODULE__)
GenServer.start_link(__MODULE__, opts, name: name)
end
@spec check_rate(String.t()) :: :ok | {:error, :rate_limited}
def check_rate(key), do: GenServer.call(__MODULE__, {:check, key})
# 服务端回调
@impl true
def init(opts) do
{:ok, %{limit: Keyword.get(opts, :limit, 100), window_ms: 60_000, buckets: %{}}}
end
@impl true
def handle_call({:check, key}, _from, state) do
now = System.monotonic_time(:millisecond)
{count, state} = increment(state, key, now)
if count <= state.limit, do: {:reply, :ok, state}, else: {:reply, {:error, :rate_limited}, state}
end
defp increment(state, key, now) do
# 实现细节
end
end
常见编译错误
| 错误信息 | 原因 | 修复方法 |
|---|---|---|
module X is not available |
依赖缺失或模块名拼写错误 | 检查 mix.exs 中的 deps 配置,并核对模块名 |
undefined function X/N |
未导入(import)或未别名引用(alias)该函数所在模块 | 添加 import、alias,或使用完整模块路径调用 |
(CompileError) redefining module |
模块名重复定义 | 重命名其中一个模块 |
protocol not implemented |
缺少对应协议(protocol)的实现 | 为你的 struct 添加 defimpl |
cannot use ^x outside of match |
Pin 操作符(^)使用位置错误 |
将其移至模式匹配上下文中(如 case、函数头、receive 等) |
Ecto 查询模式
动态过滤条件
def list(filters) do
Enum.reduce(filters, base_query(), fn
{:status, val}, q -> where(q, [r], r.status == ^val)
{:since, dt}, q -> where(q, [r], r.inserted_at >= ^dt)
{:search, term}, q -> where(q, [r], ilike(r.name, ^"%#{term}%"))
_, q -> q
end)
|> Repo.all()
end
预加载(Preloading)
# 查询时预加载(单次查询 + JOIN) from(p in Post, join: a in assoc(p, :author), preload: [author: a]) # 分离式预加载(两次查询) Post |> Repo.all() |> Repo.preload(:author) # 嵌套预加载 Repo.preload(posts, [comments: :author])
聚合查询(Aggregates)
from(o in Order,
where: o.tenant_id == ^tenant_id,
group_by: o.status,
select: {o.status, count(o.id), sum(o.amount)}
)
|> Repo.all()
Phoenix LiveView 基础
挂载(Mount)与事件处理(Handle Events)
defmodule MyAppWeb.DashboardLive do
use MyAppWeb, :live_view
@impl true
def mount(_params, _session, socket) do
{:ok, assign(socket, items: [], loading: true)}
end
@impl true
def handle_event("delete", %{"id" => id}, socket) do
MyApp.Items.delete_item!(id)
{:noreply, assign(socket, items: MyApp.Items.list_items())}
end
@impl true
def render(assigns) do
~H"""
<%= item.name %>
"""
end
end
通过 PubSub 实现实时通信
# 在 mount 中订阅
def mount(_, _, socket) do
if connected?(socket), do: Phoenix.PubSub.subscribe(MyApp.PubSub, "items")
{:ok, assign(socket, items: list_items())}
end
# 在 Context 中广播事件
def create_item(attrs) do
with {:ok, item} <- %Item{} |> Item.changeset(attrs) |> Repo.insert() do
Phoenix.PubSub.broadcast(MyApp.PubSub, "items", {:item_created, item})
{:ok, item}
end
end
# 在 LiveView 中处理消息
def handle_info({:item_created, item}, socket) do
{:noreply, update(socket, :items, &[item | &1])}
end
热门AI工具
相关专题
Vite项目打包部署专题汇集生产构建、产物预览、输出目录、资源路径、Nginx与GitHub Pages部署及常见白屏和路由404问题的已审核文章,帮助读者完成上线流程。
0
2026.10.28
Vite搭配Vue.js专题汇集创建Vue 3项目、配置开发环境、路径别名、Vue插件、代理、多环境构建与调试的已审核文章,帮助读者完成从初始化到日常开发的配置。
0
2026.10.28
Vite项目目录专题汇集入口文件、src源码、public静态资源、路径别名、项目根目录和构建输出目录相关的已审核文章,帮助读者理解各文件夹的用途与引用方式。
0
2026.10.28
本专题介绍Gradle插件开发的基本流程,涵盖插件类编写、自定义任务注册、扩展参数设计和插件应用方法,帮助你理解插件如何组织构建逻辑,减少多个项目之间的重复配置。
0
2026.10.22
本专题介绍Gradle Wrapper的基本作用,涵盖文件组成、生成方法、版本配置和任务调用,帮助你理解项目如何使用指定的Gradle版本,减少不同开发环境带来的构建差异。
0
2026.10.22
本专题整理Golangci-lint常用Linters的用途与配置方法,覆盖错误处理、无效代码、安全、复杂度、命名和依赖检查,帮助项目合理选规则。
0
2026.10.22
本专题介绍Gradle Java项目构建方法,涵盖Java插件配置、依赖声明、源码编译、测试执行和打包检查,帮助你理解各阶段的任务关系,逐步完成项目构建。
0
2026.10.22
本专题汇总PHP常见时间戳漏洞,包括2038年溢出、时区漂移、时间戳伪造、重放攻击、时间盲注与日志审计失效等,并给出升级64位环境、使用 DateTimeImmutable、统一UTC、严格超时校验、预处理绑定、hash_equals 比对等修复方案,帮助开发者快速排查和加固时间相关代码。
0
2026.10.22
本专题聚焦Golangci-lint YAML配置文件,讲清运行参数、规则启停、目录排除、问题过滤及严重级别设置,便于团队统一检查标准。
0
2026.10.22
热门下载
相关下载
精品课程
共6课时 | 12.1万人学习
共79课时 | 161.6万人学习
共6课时 | 54.7万人学习
最新文章