后端: - ORDER BY 白名单(sanitizeOrder)堵住 ?order= SQL 注入,补回归测试 - 登录限速(每 IP 10 次失败/10 分钟 429)、TLS/反代下 Secure cookie、NewAPI 构造器 - Delete/setTags/MergeTags/DeleteTag 包事务;Archive 去 500 篇上限 - 列表接口裁剪:不传 content_md,长文 content_html 截 600,新增 content_len;health 探 DB 前端: - EditorView 路由复用串写修复(RouterView :key + sync watch 回写原文章) - v-html 出口统一过 DOMPurify(sanitizeHtml),stripTags 改 DOMParser - 列表竞态防护(Home/Tag/Posts 请求序号)、TagView 分页修复 - 侧栏接口 30s 缓存去重;one:unauthorized 监听器泄漏修复 - 删 styles.css 498 行重复块;移除 tailwind/marked/vue-tsc 死依赖;CommandPalette a11y 语义
ONE · 一个博客
一个能跑通全流程的博客 MVP:后台登录 → 写文章(长文 / 短文)→ 发布 → 前台按「07 融合 + Twitter 信息流」风格展示。
- 后端:Go(
net/http+database/sql,SQLite / PostgreSQL 双支持),在backend/ - 前台 + 后台:Vue 3 + Vue Router + Vite,在
frontend/(pnpm 管理依赖) - Markdown:服务端用
goldmark渲染入库,编辑器实时预览用marked+DOMPurify
一条命令跑起来
make dev # 后端 8080 + 前端 3000(前端带热更新,已配好 /api 代理)
打开 http://localhost:3000 是前台,http://localhost:3000/admin 是后台。
停掉:make stop
想要单端口(前端构建产物由 Go 直接托管,接近生产形态):
make start # 先 pnpm run build,再起 Go,只开 8080
其它:make web 只构建前端、make server 只起后端、make test 跑后端测试、make clean 清构建产物、make db-reset 清空本地 SQLite 数据。
后台登录
默认账号密码来自环境变量,未设置时是 admin / admin(启动日志会打印出来):
ONE_ADMIN_USER=admin ONE_ADMIN_PASSWORD=换一个 make start
登录态是服务端签发的 httpOnly cookie(HMAC-SHA256,7 天有效),同时支持 Authorization: Bearer <token>,方便用 curl / 脚本写文章。
环境变量
| 变量 | 默认 | 说明 |
|---|---|---|
ONE_ADDR |
:8080 |
监听地址 |
ONE_DB_DRIVER |
sqlite |
sqlite 或 postgres |
ONE_DB_DSN |
./data/one.db |
数据库连接串(Postgres 示例:postgres://user:pass@localhost/one?sslmode=disable) |
ONE_ADMIN_USER |
admin |
后台用户名 |
ONE_ADMIN_PASSWORD |
admin |
后台密码 |
ONE_SECRET |
随机生成 | 会话签名密钥;不设的话重启后登录态失效 |
ONE_SITE_URL |
http://localhost:8080 |
RSS 里的站点地址,部署时务必改成真实域名 |
ONE_WEB_DIST |
./frontend/dist |
前端构建产物目录 |
ONE_DATA_DIR |
./data |
SQLite 数据目录 |
目录结构
backend/ Go 后端(模块名 oneblog)
main.go 路由装配、静态托管 SPA、优雅退出
internal/config 环境变量
internal/db SQLite / PostgreSQL 连接与 `?` → `$n` 占位符重写
internal/model 数据结构
internal/store schema 迁移 + 全部 SQL
internal/render goldmark 渲染、阅读时长估算、摘要截取
internal/api 公开接口 + RSS(/api/*、/rss.xml)
internal/admin 后台接口与登录鉴权(/api/admin/*)
internal/httpx JSON 读写助手
frontend/ Vue 3 前端(pnpm)
src/styles.css 设计 token 与正文排版(07 风格)
src/views 前台:时间线 / 详情 / 归档 / 标签 / 关于
src/admin 后台:登录 / 列表 / 编辑器 / 标签 / 设置
src/components 左栏导航、右栏卡片、时间线行
原有那一版 Vue 前端(07 风格重写之前)保留在 git 历史的第一个 commit edee708 里,
需要对照或回滚:git checkout edee708 -- frontend/src。
接口
公开(无需登录):
GET /api/site 站点设置
GET /api/posts?kind=&tag=&q=&page=&size= 已发布文章(kind: long|short)
GET /api/posts/:slug 文章详情
GET /api/archive 按年 → 月分组
GET /api/tags 标签与计数
GET /rss.xml (/feed 同) RSS 2.0
后台(需登录):
POST /api/admin/login {username,password} → {token}
POST /api/admin/logout
GET /api/admin/me
GET /api/admin/posts?status=&kind=&q=&page=&size=
POST /api/admin/posts 新建
GET /api/admin/posts/:id
PUT /api/admin/posts/:id 更新(编辑器自动保存走这里)
DELETE /api/admin/posts/:id
GET /api/admin/tags
POST /api/admin/tags {name}
PUT /api/admin/tags/:id {name}
DELETE /api/admin/tags/:id
GET /api/admin/settings
PUT /api/admin/settings
Post 的关键字段:kind(long / short)、title、slug、summary、content_md、content_html、status、published_at、reading_minutes、tags[]。
短文没有可见标题,但 title 仍会由正文首句生成,供归档与标签列表索引。
部署
生产形态是「一个二进制 + 一个静态目录」:
make build # 产出 ./one-server,前端已打进 web/dist
ONE_ADDR=:8080 \
ONE_SITE_URL=https://your.domain \
ONE_DB_DRIVER=postgres \
ONE_DB_DSN="postgres://user:pass@127.0.0.1/one?sslmode=disable" \
ONE_SECRET=一串随机长字符串 \
ONE_ADMIN_PASSWORD=强密码 \
./one-server
要点:
- 静态资源:Go 直接托管
web/dist,未知路径回落到index.html,所以/post/xxx、/admin刷新都不会 404。 - 反向代理:前面挂 Nginx / Caddy 时把
/反代到ONE_ADDR即可;如果只在内网监听,可以不挂。 - SQLite 部署:把
data/挂到持久卷;并发写很低的博客足够用(已开 WAL + busy_timeout)。 - PostgreSQL 部署:改
ONE_DB_DRIVER与ONE_DB_DSN,schema 会在启动时自动创建,无需手动迁移。 - systemd 示例:
[Unit]
Description=ONE blog
After=network.target
[Service]
WorkingDirectory=/opt/one
Environment=ONE_ADDR=:8080 ONE_SITE_URL=https://your.domain ONE_SECRET=xxx ONE_ADMIN_PASSWORD=xxx
ExecStart=/opt/one/one-server
Restart=always
[Install]
WantedBy=multi-user.target
- 备份:SQLite 直接拷
data/one.db(停写更稳);Postgres 用pg_dump。
已验证
make start 后:后台登录 → 写长文与短文 → 发布 → 前台时间线(长文标题 + 摘要 / 短文正文铺开)、详情、归档、标签、RSS 全部可见;
编辑器改正文 1.5 秒后自动保存到服务端;桌面与移动宽度无横向滚动;SQLite 路径全流程跑通,PostgreSQL 的占位符重写有单元测试覆盖(本机没有 Postgres 实例,未做端到端验证)。
待确认
- 仓库地址:本仓库是在本地
/Users/cjun/Code/one新建的 git 仓库,还没有 remote。给个地址就能推上去并绑进工作区。 - 鉴权方式:目前是账号密码 + 签名 cookie。想改成纯 token 也可以,接口已经兼容
Authorization: Bearer。 - 图片上传:MVP 未做,正文里先用图片外链。