You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 08:33:35