如何在Elixir Phoenix博客中同步priv目录帖子与数据库并添加评论
Elixir/Phoenix博客:文件转数据库同步 + 评论功能实现
一、文件到数据库的可靠同步方案
1. 启动时一次性同步(替代你写的GenServer)
不用自己折腾GenServer,直接在应用启动流程里加同步任务就行,简单可靠:
首先写一个同步模块,遍历priv/posts下的Markdown文件,对比slug同步到数据库:
defmodule MyApp.PostSync do alias MyApp.Blog.Post alias MyApp.Repo def sync do # 遍历所有md文件 Path.wildcard("priv/posts/*.md") |> Enum.each(&sync_single_post/1) end defp sync_single_post(file_path) do # 从文件名提取slug slug = Path.basename(file_path, ".md") content = File.read!(file_path) # 从markdown第一行提取标题(如果你的文章开头是# 标题的话) title = extract_title(content) case Repo.get_by(Post, slug: slug) do # 数据库没有就插入 nil -> %Post{slug: slug, title: title, content: content} |> Repo.insert!() # 数据库已有,对比内容哈希再决定是否更新 existing_post -> content_hash = :crypto.hash(:md5, content) existing_hash = :crypto.hash(:md5, existing_post.content) if content_hash != existing_hash do existing_post |> Post.changeset(%{content: content, updated_at: DateTime.utc_now()}) |> Repo.update!() end end end defp extract_title(content) do content |> String.split("\n") |> Enum.find(&String.starts_with?(&1, "# ")) |> case do nil -> slug line -> String.trim_leading(line, "# ") end end end
然后在lib/my_app/application.ex的启动函数里加入这个同步任务,确保服务启动时自动执行:
def start(_type, _args) do children = [ MyApp.Repo, MyAppWeb.Endpoint, # 启动时执行一次同步 {Task, fn -> MyApp.PostSync.sync() end} ] Supervisor.start_link(children, strategy: :one_for_one) end
2. 开发时实时同步(可选)
如果开发时改了Markdown文件想自动同步到数据库,可以用file_system库监听目录变化:
- 先加依赖:
{:file_system, "~> 0.2"} - 写个简单的GenServer监听
priv/posts,文件变化时调用上面的sync_single_post/1就行,逻辑很直观。
二、评论功能的数据库关联实现
1. 定义关联模型
先给Post和Comment建立一对多关联:
# lib/my_app/blog/post.ex schema "posts" do field :slug, :string field :title, :string field :content, :string # 关联评论 has_many :comments, MyApp.Blog.Comment timestamps() end # lib/my_app/blog/comment.ex schema "comments" do field :author, :string field :email, :string # 可选,用于评论回复通知 field :content, :text # 关联所属文章 belongs_to :post, MyApp.Blog.Post timestamps() end
2. 生成评论表迁移
运行mix ecto.gen.migration create_comments,然后修改迁移文件:
defmodule MyApp.Repo.Migrations.CreateComments do use Ecto.Migration def change do create table(:comments) do add :author, :string add :email, :string add :content, :text # 外键关联posts表,删除文章时自动删评论 add :post_id, references(:posts, on_delete: :delete_all) timestamps() end # 加索引提升查询速度 create index(:comments, [:post_id]) end end
然后跑mix ecto.migrate创建表。
3. 查询带评论的文章
在Blog context里加个函数,获取文章时预加载评论:
def get_post_with_comments(slug) do Repo.get_by(Post, slug: slug) |> Repo.preload(:comments) end
之后在控制器里调用这个函数,把文章和评论一起传给模板渲染就行。
三、偷懒方案:不用全转数据库也能加评论
如果不想把所有文章都迁去数据库,也可以直接给评论表加post_slug字段,用slug关联文章:
- 评论表去掉
post_id,加post_slug :string - 查询评论时用
post_slug == 当前文章slug过滤 - 优点:不用动现有文章,缺点:没法用数据库外键约束,要是改了文章文件名(slug),对应的评论就找不到了,适合小博客临时用。
内容的提问来源于stack exchange,提问作者SevB
相关产品推荐
相关产品推荐

