listmonk 开发者环境搭建指南:Go 后端 + Vue 前端的双进程开发模式与生产构建
【免费下载链接】listmonkHigh performance, self-hosted, newsletter and mailing list manager with a modern dashboard. Single binary app.项目地址: https://gitcode.com/GitHub_Trending/li/listmonk
导读
listmonk 是一个高吞吐、自托管的新闻通讯与邮件列表管理工具,整体采用"Go 后端 + Vue 前端"的双组件架构。开发者环境与生产环境的关键差异在于:开发模式下前后端各自独立运行、支持热重载,而生产环境则通过单二进制文件将所有静态资源内嵌交付。本篇指南基于仓库内 developer-setup.md 展开,覆盖前置依赖、首次初始化、本地/容器化/DevContainer 三种开发运行方式,以及make dist的生产构建全流程,并结合 Makefile、dev/docker-compose.yml、config.toml.sample 与 cmd/init.go 源码逐层印证,帮助你从零搭建一个可热重载、可调试的 listmonk 开发环境,并理解其最终如何被打包为单个自包含二进制。
双组件架构:Go 后端与 Vue 前端为何要分开运行
listmonk 由两个相对独立的技术栈组成(见 developer-setup.md 开篇说明):
- Go 后端:负责 HTTP API、数据库访问、邮件发送(SMTP/Postback)、弹回处理、批量导入、订阅者/列表/活动管理及计划任务等核心业务。入口为 cmd/main.go,通过 cmd/init.go 完成配置加载、数据库连接、SQL 查询预编译、消息器初始化与 HTTP 服务器装配。
- Vue 前端:位于 frontend/,是基于 Vue 2 + Buefy(Bulma)的管理面板,通过
/api/*与后端交互。另含独立的 email-builder(Vue 3 + TypeScript)邮件可视化编辑器,构建产物被拷贝进前端资源目录。
开发模式下两者独立运行,分别拥有自己的开发服务器、端口与热重载机制,互不阻塞。生产构建时二者合流:前端构建产物被内嵌进 Go 二进制,最终交付单文件应用。这也解释了为什么文档要求先分别启动两个进程,而非像生产环境那样直接运行一个二进制。
前置依赖
根据 developer-setup.md 的 Pre-requisites 章节,开发 listmonk 需要以下环境:
| 依赖 | 用途 | 备注 |
|---|---|---|
go | 编译并运行后端 | 项目使用 go.mod,建议 clone 到 Go src 路径之外 |
nodejs+yarn | 编译并运行前端 | 仅在参与前端开发时需要 |
| PostgreSQL | 后端的数据存储 | 本地无安装时,可用 demo DB 容器(docker compose up demo-db)替代 |
从 dev/app.Dockerfile 可看到仓库开发镜像锁定的版本基线:golang:1.24.1与node:16,同时设置CGO_ENABLED=0。当前仓库 frontend/package.json 使用yarn@1.22.22(packageManager 字段固定),依赖 Vue^2.7.14、Buefy^0.9.25、vite^5.4.21与 vuex/vue-router/vue-i18n 等;node 版本若与本仓库开发期基线差异过大(尤其是 Node 16 这类 EOL 版本),可能出现依赖安装或构建失败,建议优先以容器化方式开发以获得一致的构建环境。
关于 Postgres 连接参数的快速参考
config.toml.sample 给出了后端默认的数据库配置段,这正是开发环境需要准备的外部依赖:
[db] host = "localhost" port = 5432 user = "listmonk" password = "listmonk" # Ensure that this database has been created in Postgres. database = "listmonk" ssl_mode = "disable" max_open = 25 max_idle = 25 max_lifetime = "300s" # Optional space separated Postgres DSN params. eg: "application_name=listmonk gssencmode=disable" params = ""从 cmd/init.go 的initDB()实现可以看到,这些字段会被拼装为 Postgres DSN(host=... port=... user=... password=... dbname=... sslmode=...),并据此配置连接池:SetMaxOpenConns/SetMaxIdleConns/SetConnMaxLifetime一一对应max_open、max_idle、max_lifetime;params则作为额外的空格分隔 DSN 参数原样追加(如application_name=listmonk gssencmode=disable)。数据库本身(database字段指定的库)必须预先创建,--install只负责建表与写入种子数据,不会替你创建数据库。
首次初始化:克隆、配置、建库三步走
克隆仓库
文档给出的命令是:
git clone https://github.com/knadh/listmonk.git项目使用 go.mod(见仓库根目录 go.mod),因此建议克隆到 Go src 路径之外,避免与 Go Modules 的目录约定冲突。当前仓库对应的克隆地址为https://gitcode.com/GitHub_Trending/li/listmonk。
配置 config.toml
cp config.toml.sample config.toml # 编辑 config.toml,填入你自己的数据库等配置config.toml.sample 只含两个段落:[app]的address(默认localhost:9000,即开发服务器监听地址)与[db]的数据库连接。在容器化开发中,dev/config.toml 提供了另一套取值:address = "0.0.0.0:9000"、db.host = "db"、用户/密码/库名均为listmonk-dev,与 dev 容器内的 Postgres 服务名对齐。
小技巧:若尚未生成配置文件,后端还支持
./listmonk --new-config直接在--config指定的路径生成一份示例配置(见 cmd/init.go 与 cmd/init.go 的--new-config标志说明)。
首次构建与建库
make dist # 构建 listmonk 二进制 ./listmonk --install # 首次执行数据库安装(建表 + 种子数据)--install交互式执行时会有提示并要求输入y确认;从 cmd/install.go 可以看到安装流程包括:读取并执行 SQL 建表(installSchema)、写入示例列表(installLists)、示例订阅者(installSubs)、示例模板(installTemplates)、示例活动(installCampaign),并可通过环境变量LISTMONK_ADMIN_USER/LISTMONK_ADMIN_PASSWORD(最少 3/8 字符)预置超级管理员;若未设置,则首次访问网页时引导创建。--install --idempotent变体会先检查settings表是否存在,已初始化则跳过,这在 docker-compose.yml 的容器入口命令(./listmonk --install --idempotent --yes ...)中被用来保证幂等启动。
后续开发运行不需要再走make dist,直接使用make run即可(二者区别见下文)。
提示:文档特别推荐 mailhog——一个带 Web UI 的独立 mock SMTP 服务器——用于开发与测试阶段的邮件收验。dev 容器套件中已内置 MailHog(SMTP
:1025、UI:8025),本地开发也可自行启动并将 SMTP 配置指向它。
运行开发环境:本地、容器(Makefile)与 DevContainer 三种方式
文档提供了三种开发运行方式,运行成功后统一访问http://localhost:8080(前端管理面板)。
方式一:本地运行(推荐用于日常前后端开发)
make run # 启动后端 dev server,监听 :9000 make run-frontend # 启动 Vue 前端 dev server,监听 :8080,/api/* 代理到 :9000两个命令对应 Makefile 与 Makefile 的两个 target:
make run:等价于CGO_ENABLED=0 go run -ldflags="... -X 'main.frontendDir=frontend/dist'" ./cmd。注意它通过main.frontendDir变量把frontendDir指向frontend/dist(cmd/main.go 的默认值正是frontend/dist),意味着即使在后端 dev 模式下,若前端目录已有构建产物,也会被当作静态资源加载。make run-frontend:等价于在 frontend/ 目录执行yarn dev(即vite),由 Vite 提供热更新。
代理机制:查看 frontend/vite.config.js 的server.proxy配置,前端开发服务器会把以下路径代理到LISTMONK_API_URL || 'http://127.0.0.1:9000':
^/$^/(api|webhooks|subscription|public|health)^/admin/login^/(admin\/custom\.(css|js))
也就是说,/api/*、/webhooks/*、/subscription/*、/public/*、/health以及登录页与管理面板自定义 CSS/JS 等请求都会转发到运行在:9000的 Go 后端;前端端口可用环境变量LISTMONK_FRONTEND_PORT覆盖(默认 8080)。这正是文档所述"所有/api/*调用被代理到 :9000 应用"的源码依据。
方式二:容器化运行(Makefile 驱动)
make init-dev-docker # 构建镜像并初始化容器化数据库 make dev-docker # 启动整套容器套件(前端 + 后端 + PG + MailHog + Adminer) make rm-dev-docker # 拆除整套容器(连同数据库卷一起删除)三个 target 的实现见 Makefile:
init-dev-docker:先build-dev-docker(进入 dev/ 执行docker compose build),然后执行docker compose run --rm backend sh -c "make dist && ./listmonk --install --idempotent --yes --config dev/config.toml"——即在容器内完成构建 + 幂等建库,使用的是 dev/config.toml。dev-docker:进入 dev/ 执行docker compose up,启动 dev/docker-compose.yml 定义的整套服务。rm-dev-docker:执行docker compose down -v,连同数据库数据卷一并清除,属于彻底清理。
从 dev/docker-compose.yml 可以看到套件包含 5 个服务:
| 服务 | 端口 | 说明 |
|---|---|---|
backend | 9000 | Go 后端,命令为make run-backend-docker,使用dev/config.toml,挂载整个仓库到容器 |
front | 8080 | Vue 前端,命令为make run-frontend,LISTMONK_API_URL=http://backend:9000 |
db | 5432 | postgres:13,账号listmonk-dev,数据持久化于 volume |
mailhog | 1025(SMTP) /8025(UI) | 开发邮件捕获 |
adminer | 8070 | 数据库 Web 管理界面(访问容器内:8080) |
backend与front两个容器都挂载了宿主机整个仓库目录(../:/app),这正是 dev/README.md 所述的设计目标:避免每次代码变更都完整docker build,只需重启容器即可。dev/README 还指出,前端代码改动无需任何额外操作(yarn watch + 目录挂载自动生效),后端 Go 代码改动则需重新运行make dev-docker触发重新编译。另注意backend容器把宿主机 Go module 缓存挂载进容器(${GOPATH:-${HOME}/go}/pkg/mod/cache),可加速依赖拉取。
方式三:DevContainer(VS Code 远程开发)
文档步骤:在 VS Code 中打开仓库 → 命令面板(Command Palette)→ 选择"Dev Containers: Rebuild and Reopen in Container"。容器会自动完成数据库初始化并启动前后端服务。
补充说明:当前仓库(截至本仓库快照)并未在根目录包含
.devcontainer/目录或docker compose up demo-db对应的一等服务定义(源码检索未发现devcontainer相关文件,demo-db服务也仅在本文档中被提及)。因此:
- DevContainer 与
demo-db属于官方文档描述的能力/历史用法,实际可用性取决于你克隆的上游仓库版本与 VS Code Dev Containers 扩展的远程容器探测逻辑;- 在当前仓库内,容器化开发的权威入口是 dev/docker-compose.yml 与 Makefile 的
init-dev-docker/dev-docker流程,建议优先使用该路径;- 若确需 DevContainer,可在 VS Code 中直接使用"Rebuild and Reopen in Container"后观察其自动探测结果,或自行按 dev/app.Dockerfile 的基线(golang:1.24.1 + node:16)编写容器配置。
三种方式的取舍对比
| 维度 | 本地 | 容器(Makefile) | DevContainer |
|---|---|---|---|
| 依赖隔离 | 依赖本机 Go/Node/Postgres | 完全隔离,环境一致性最好 | 同容器,但配置在 IDE 内 |
| 启动速度 | 最快(无容器开销) | 首次构建镜像较慢,之后仅重启容器 | 首次构建镜像较慢 |
| 热重载 | 后端需重编译,前端 yarn watch | 前端自动,后端需重启容器 | 同容器 |
| 适用场景 | 日常快速迭代,前端+后端联调 | 环境统一/多人协作/不想污染本机 | 偏好 IDE 内一体化开发 |
前端开发内部结构速览
文档指向了 frontend/README.md 作为前端结构的入门。要点如下(有助于在开发中快速定位):
- 全局注入:frontend/src/main.js 中全局挂载 Buefy、vue-i18n,以及
$api(来自 frontend/src/api/index.js 的 API 调用集合)与$utils(frontend/src/utils.js),组件内以this.$api、this.$utils访问;常量集中在 frontend/src/constants.js。 - 全局状态:采用 Vuex 集中存储几乎所有 API 响应(
models,定义在 frontend/src/store/index.js),并有全局loading状态(如loading.campaigns)供各组件显示 spinner。 - 字段命名约定(重要):GET API 响应的 JSON 字段名会被自动 camelCase 化(如
content_type→contentType),而向后端发送时需手动 snake_case;例外是/api/config、/api/settings等调用,通过api/index.js中的preserveCase: true保留原样。改动前端代码时务必遵守这一约定,否则字段名不匹配会导致联调异常。 - 图标方案:Buefy 默认用 Material Design Icons(
mdi-前缀),listmonk 只选取少量图标打包为 web font(Fontello)。需要新增图标时,将 frontend/fontello/config.json 拖入 Fontello 官网选择图标,下载后将config.json、fontello.woff2、css/fontello.css分别覆盖回 frontend/fontello、frontend/src/assets/icons/。
生产构建:make dist产出单文件二进制
make dist是文档给出的生产构建命令,其含义(见 Makefile)是依次执行build、build-frontend与pack-bin三个步骤:
- build(Makefile):
CGO_ENABLED=0 go build -o listmonk -ldflags="-s -w -X 'main.buildString=...' -X 'main.versionString=...'" ./cmd。-ldflags注入版本与构建信息;CGO_ENABLED=0产出纯静态二进制。版本号解析逻辑在 Makefile:优先取 git describe 标签,其次 VERSION 文件,最后回退v0.0.0。 - build-frontend(Makefile):先后构建 email-builder(
yarn build,产物frontend/email-builder/dist拷入frontend/public/static/email-builder)与主前端(frontend目录内yarn build,产物 frontend/dist)。构建前还会先执行依赖安装(yarn install),产物跟随 frontend/package.json 的prebuild跑一次 ESLint 检查。 - pack-bin(Makefile):调用 stuffbin 将静态资源"塞"进二进制——即 Makefile 定义的
STATIC清单:config.toml.sample、schema.sql、queries/、permissions.json、static/public → /public、static/email-templates、frontend/dist → /admin、i18n/ → /i18n。
运行时 cmd/init.go 的initFS()会先尝试从可执行文件解出内嵌文件系统(stuffbinUnStuff);解不出时(例如直接用go run开发运行)才回退到本地文件系统,从appDir、frontendDir及用户指定的static-dir/i18n-dir按需加载静态资源——这正是make run与make dist产物行为差异的底层原因。最终listmonk是一个自包含二进制:内置数据库 schema、SQL 查询、Go 后端、Vue 管理面板与邮件模板,可单独部署,无需附带任何静态目录(--install首次初始化时从内嵌 FS 读 schema 与种子数据)。
常用开发命令速查表
| 命令 | 作用 | 对应实现 |
|---|---|---|
make run | 启动 Go 后端 dev server(:9000) | Makefile |
make run-frontend | 启动 Vue 前端 dev server(:8080,API 代理到 9000) | Makefile |
make init-dev-docker | 构建开发镜像 + 幂等初始化容器内数据库 | Makefile |
make dev-docker | 启动整套开发容器(front/backend/PG/MailHog/Adminer) | Makefile |
make rm-dev-docker | 拆除容器并删除数据库卷 | Makefile |
make dist | 生产构建:Go 二进制 + 前端构建 + stuffbin 内嵌静态资源 | Makefile |
make test | 运行全部 Go 测试(go test ./...) | Makefile |
./listmonk --install | 首次数据库初始化(建表 + 种子数据) | cmd/install.go |
./listmonk --install --idempotent --yes | 幂等建库,用于脚本/容器首次启动 | cmd/install.go |
./listmonk --new-config | 在--config指定路径生成示例配置 | cmd/init.go |
./listmonk --version | 显示构建版本信息 | cmd/main.go |
小结
listmonk 的开发环境搭建核心在于理解"前后端分离、按需合流"的运行模型:开发期用make run+make run-frontend双进程协作(或容器化的make init-dev-docker+make dev-docker),借助 Vite 代理打通:8080与:9000;交付期用make dist把 Vue 构建产物经 stuffbin 内嵌进 Go 二进制,得到单一自包含可执行文件。配置上只需准备一份config.toml与一个可用的 PostgreSQL 实例(容器套件已替你备好)。开发过程中遇到问题,可参考 dev/README.md(容器套件说明)与 frontend/README.md(前端结构与命名约定),并善用make test验证后端改动。
【免费下载链接】listmonkHigh performance, self-hosted, newsletter and mailing list manager with a modern dashboard. Single binary app.项目地址: https://gitcode.com/GitHub_Trending/li/listmonk
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考