Files
go_blog/README.md
T

303 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Go Blog
一个简洁、快速的博客引擎,使用 Go + Gin + Tailwind CSS 构建。支持 SQLite 和 MySQL,首次运行自动完成配置。
## 功能特性
- **文章管理** — 文章的创建、编辑、删除,支持标签分类、自定义发布时间和更新时间
- **Markdown 渲染** — 文章与评论支持 GFM Markdown(表格、任务列表、删除线),代码块语法高亮 + 一键复制,标题锚点链接,图片懒加载与灯箱预览
- **评论系统** — 文章评论功能,后台可审核(通过/拒绝/删除),支持评论设置
- **用户系统** — 用户注册、登录,角色分为管理员(admin)和作者(author
- **角色权限** — 管理员可访问后台管理面板;作者可管理自己的文章
- **个人中心** — 上传头像(支持裁剪)、编辑个人信息、修改密码
- **站点设置** — 自定义站点标题、副标题、favicon 图标
- **导航管理** — 自定义首页导航链接,支持图标
- **瀑布流布局** — 首页文章以瀑布流展示,支持无限滚动加载
- **阅读统计** — 文章阅读量统计,带机器人流量检测
- **附件上传** — 文章支持上传附件,基于内容寻址自动去重
- **RSS 订阅** — 自动生成 RSS Feed`/rss``/feed`),链接使用站点设置的规范地址
- **搜索功能** — 文章全文搜索
- **多语言** — 支持中文 / English,自动检测浏览器语言或手动切换
- **自适应界面** — Tailwind CSS,桌面端和移动端均可正常使用
- **安全加固** — 会话密钥加密随机、Cookie HttpOnly/SameSite=Lax/HTTPS Secure、全站 CSRF、SQL 注入防护(参数化+路由 ID 数值化)、`/uploads` 白名单挂载(SQLite 数据库不可下载)、登录限速(IP+用户名,5 次失败锁 15 分钟)、附件越权校验、上传安全(危险扩展黑名单+magic-bytes 检测+头像 JPEG 重编码)、Gravatar 默认关闭、禁用用户会话实时失效,完整清单见 [SECURITY_TODO.md](./SECURITY_TODO.md)
- **开箱即用** — 首次运行自动创建配置文件、数据库;管理员账号密码为随机生成并仅一次性打印(不再使用 admin/admin
## 技术栈
| 层级 | 库/工具 |
|---|---|
| 路由 | [gin](https://github.com/gin-gonic/gin) |
| ORM | [gorm](https://gorm.io) |
| SQLite | [glebarez/sqlite](https://github.com/glebarez/sqlite)(纯 Go,无需 CGO |
| Session | [gin-contrib/sessions](https://github.com/gin-contrib/sessions) |
| 配置 | [gopkg.in/yaml.v3](https://gopkg.in/yaml.v3) |
| 密码 | [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto/bcrypt) |
| 文件校验 | [gabriel-vasile/mimetype](https://github.com/gabriel-vasile/mimetype) |
| CSS | [Tailwind CSS](https://tailwindcss.com)(构建期静态生成,见 `scripts/build_tailwind.sh` |
## 快速开始
```bash
go run .
```
打开 <http://localhost:8080> 登录。首次运行时会自动创建管理员账号 `admin`,初始密码为**随机生成**并仅一次性打印在日志中——请立即记录并登录后修改(SECURITY_TODO #12,不再使用默认 admin/admin)。
> 前端资源全部本地化(`static/css/app.css` 为 Tailwind 静态构建产物,已提交)。修改 HTML 模板/Go 代码中的 Tailwind 类后,运行 `./scripts/build_tailwind.sh`(需 Node ≥ 18,npx 可用)重新生成并提交新产物;vendor 库更新同理重新下载到 `static/vendor/` 并提交。
> 安全状态一览见下方 [安全加固](#安全加固) 章节,完整修复清单与验证记录见 [SECURITY_TODO.md](./SECURITY_TODO.md)。
## 配置
首次运行时,配置文件会自动生成:
| 平台 | 配置文件路径 | 数据存储路径 |
|---|---|---|
| Linux | `/etc/blog_go/config.yaml` | `/srv/blog_go/` |
| Windows | `./win/etc/blog_go/config.yaml` | `./win/srv/blog_go/` |
```yaml
# config.yaml
database:
type: sqlite # sqlite(默认)或 mysql
dsn: "" # MySQL 连接串,sqlite 模式下忽略
web:
port: "8080" # Web 服务端口,"" 或 "0" 可只启用 socket
socket: "" # unix socket 路径(Linux 部署推荐,见 install_linux.sh
trusted_proxies: # 可信反向代理 IP/CIDR;直接影响 X-Forwarded-For
- 127.0.0.1 # 仅列表内的代理可设置客户端 IP(防 XFF 伪造)
- ::1
path: ./win/srv/blog_go # 数据存储路径(数据库、上传文件)
secret: <自动生成> # Session 加密密钥;缺失时拒绝启动
```
### 使用 MySQL
编辑 `config.yaml`
```yaml
database:
type: mysql
dsn: user:password@tcp(127.0.0.1:3306)/blog_go?charset=utf8mb4&parseTime=True&loc=Local
web:
port: "8080"
```
先创建数据库:
```sql
CREATE DATABASE blog_go CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
```
## 项目结构
| 目录 | 作用 |
|---|---|
| `config/` | 配置加载与自动创建(区分 Linux/Windows 路径) |
| `models/` | 数据模型:用户、文章、标签、评论、附件、阅读统计、站点设置等 + GORM 初始化 |
| `handlers/` | HTTP 请求处理:首页、文章、登录/注册、评论、后台管理、个人中心、RSS、上传校验 |
| `middleware/` | 中间件:登录/角色鉴权、CSRF、HTTPS 检测、安全响应头(CSP/HSTS 等) |
| `i18n/` | 中英文翻译与浏览器语言检测 |
| `templates/` | HTML 模板:layouts 公共布局、pages 前台、admin 后台、user 用户文章 |
| `static/` | 静态资源:Tailwind 产物、前端 Markdown 渲染器、vendor 本地化第三方库(marked、highlight.js 等) |
| `scripts/` | 构建辅助脚本(Tailwind 重建、数据库迁移 SQL |
根目录文件:`main.go`(入口 + 路由注册)、`main_test.go``handlers/_test.go`(测试)、`fresh.conf`fresh 热重载配置)、`install_linux.sh`(Linux 部署脚本)。逐文件说明:
```
go_blog/
├── main.go # 入口,路由注册,中间件装配
├── config/
│ └── config.go # 配置加载 / 自动创建(区分操作系统)
├── models/
│ ├── user.go # 用户模型 + bcrypt
│ ├── article.go # 文章模型
│ ├── article_tag.go # 文章-标签关联
│ ├── article_view.go # 文章阅读统计
│ ├── attachment.go # 附件模型
│ ├── bot_detector.go # 机器人流量检测
│ ├── comment.go # 评论模型
│ ├── comment_config.go # 评论配置
│ ├── config_cache.go # 平台配置缓存
│ ├── db.go # GORM 初始化、自动迁移
│ ├── nav_link.go # 导航链接模型
│ ├── seed.go # 初始数据填充
│ ├── site_setting.go # 站点设置模型
│ ├── tag.go # 标签模型
│ └── upload_config.go # 上传配置
├── middleware/
│ ├── auth.go # 登录验证、角色鉴权、语言检测
│ ├── csrf.go # CSRF 同步器令牌防护
│ ├── https.go # 请求是否 HTTPS 检测(cookie Secure
│ └── security_headers.go # 安全响应头(CSP、HSTS、nosniff 等)
├── handlers/
│ ├── helpers.go # 公共工具函数
│ ├── home.go # 首页
│ ├── article.go # 文章详情、文章列表 API
│ ├── auth.go # 登录
│ ├── login_ratelimit.go # 登录限速(IP+用户名,5 次失败锁定 15 分钟)
│ ├── admin.go # 管理后台首页
│ ├── admin_comment.go # 评论管理
│ ├── admin_user.go # 用户管理
│ ├── admin_analytics.go # 阅读统计
│ ├── attachment.go # 附件上传/删除
│ ├── comment.go # 评论提交
│ ├── my_articles.go # 用户文章管理
│ ├── profile.go # 个人中心
│ ├── rss.go # RSS Feed
│ ├── settings.go # 站点/导航/上传/评论设置
│ └── upload_validator.go # 上传文件校验(扩展名白名单 + magic-bytes
├── i18n/
│ └── i18n.go # 中英文翻译映射 + Accept-Language 检测
├── static/
│ ├── css/app.css # Tailwind 静态构建产物(go:embed 编入二进制)
│ ├── css/input.css # Tailwind 构建输入(@tailwind 指令)
│ ├── css/markdown.css # Markdown 排版样式(go:embed 编入二进制)
│ ├── js/markdown.js # 前端 Markdown 渲染器(BlogMD
│ └── vendor/ # 本地化的第三方前端库(marked/DOMPurify/highlight.js/cropperjs/easymde
├── scripts/
│ └── build_tailwind.sh # 重新生成 Tailwind CSS(需 Node,见下)
├── templates/
│ ├── layouts/base.html # 公共布局(导航栏 + 头像下拉菜单 + 页脚)
│ ├── pages/
│ │ ├── home.html # 首页(瀑布流 + 无限滚动)
│ │ ├── article.html # 文章详情页
│ │ ├── login.html # 登录页
│ │ ├── register.html # 注册页
│ │ ├── profile.html # 个人中心
│ │ ├── search.html # 搜索页
│ │ └── 404.html # 404 页面
│ ├── admin/
│ │ ├── dashboard.html # 管理后台首页
│ │ ├── article_list.html # 文章列表
│ │ ├── article_create.html# 文章编辑
│ │ ├── comment_list.html # 评论审核
│ │ ├── user_list.html # 用户列表
│ │ ├── user_form.html # 用户表单
│ │ ├── analytics_views.html # 阅读统计
│ │ ├── settings_site.html # 站点设置
│ │ ├── settings_navlinks.html # 导航链接设置
│ │ ├── settings_upload.html # 上传设置
│ │ ├── settings_download.html # 下载设置
│ │ └── settings_comment.html # 评论设置
│ └── user/
│ ├── my_articles.html # 我的文章列表
│ └── my_article_form.html # 我的文章编辑
└── win/ # 运行时数据目录(Windows,自动创建)
├── etc/blog_go/
│ └── config.yaml
└── srv/blog_go/
├── blog.db
├── attachments/
├── avatars/
└── logos/
```
## 安全加固
基于 2026-08-19 安全审计与网络上线验证(共 25 项发现,P0–P3 均已修复,详见 [SECURITY_TODO.md](./SECURITY_TODO.md)):
- **认证与会话**
- 会话密钥由 `crypto/rand` 生成 32 字节随机数;配置文件缺失 secret 时拒绝启动(不再静默回退)
- Cookie 加固:`HttpOnly` + `SameSite=Lax`HTTPS 下自动加 `Secure`
- 全站 CSRF 防护(同步器令牌,30+ 表单与 AJAX 全覆盖);登录/注册成功强制会话轮换(防会话固定)
- 登录限速:按 IP+用户名 5 次失败锁定 15 分钟;用户不存在时也执行 bcrypt 比较,抹平计时侧信道
- 禁用/锁定/软删用户的会话实时失效;管理员口令 bcrypt cost 12
- **数据与注入**
- GORM 全参数化查询;管理路由的 `:id` 先解析为数值再入查询(防字符串条件注入)
- `/uploads` 仅白名单挂载子目录,SQLite 数据库文件不可从公网下载;目录列表与路径穿越一律 404
- **上传安全**
- 扩展名白名单 + 危险扩展名黑名单(.html/.svg/.js 等,防同源 Active Content
- 内容 magic-bytes 与声明类型一致性校验(gabriel-vasile/mimetype
- 头像强制解码→256px 缩放→JPEG 重编码后落盘,原始字节一律不落地
- **输出与传输**
- CSP`default-src 'self'`,第三方前端资源已本地化)、`X-Frame-Options: DENY``X-Content-Type-Options: nosniff``Referrer-Policy``Permissions-Policy`HTTPS 下发 HSTS
- 客户端 IP 解析仅信任 `web.trusted_proxies` 名单内代理(防 X-Forwarded-For 伪造);RSS 链接使用站点设置的规范地址(防 Host 头污染)
安全回归用例 50+`handlers/*_test.go``main_test.go``middleware/*_test.go`),`go test -race ./...` 全绿。
## 路由
### 公开路由
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | `/` | 首页 |
| GET | `/search` | 搜索页 |
| GET | `/article/:slug` | 文章详情 |
| GET | `/rss``/feed` | RSS 订阅 |
| GET | `/login` | 登录页 |
| GET | `/register` | 注册页 |
| GET | `/uploads/*` | 静态文件(头像、附件等) |
### 公开 JSON API`/api` 前缀)
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | `/api/articles` | 文章列表 API(无限滚动) |
| POST | `/api/auth/login` | 登录(限流锁定 429 |
| POST | `/api/auth/register` | 注册(用户名冲突 409 |
| POST | `/api/auth/logout` | 退出登录 |
| POST | `/api/article/:slug/comments` | 发表评论(校验错误 400) |
### 管理后台(需管理员权限)
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | `/admin` | 管理后台首页 |
| GET | `/admin/articles` | 文章列表 |
| GET | `/admin/articles/new` | 新建文章表单 |
| GET | `/admin/articles/:id/edit` | 编辑文章表单 |
| GET | `/admin/comments` | 评论管理 |
| GET | `/admin/users` | 用户列表 |
| GET | `/admin/users/new` | 新建用户表单 |
| GET | `/admin/users/:id/edit` | 编辑用户表单 |
| GET | `/admin/analytics/views` | 阅读统计 |
| GET | `/admin/settings/site``/navlinks``/upload``/download``/comments` | 各设置页 |
管理 JSON API(仅管理员,均带 JSON 错误码 + HTTP 语义状态码):
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | `/api/admin/articles` | 创建文章 |
| PUT/DELETE | `/api/admin/articles/:id` | 更新/删除文章 |
| POST | `/api/admin/articles/attachments` | 上传附件(multipart |
| DELETE | `/api/admin/articles/attachments/:id` | 删除附件 |
| GET | `/api/admin/articles/:id/attachments` | 附件列表 |
| POST | `/api/admin/comments/:id/approve``/:id/reject``/:id/delete` | 评论审核操作 |
| POST/PUT/DELETE | `/api/admin/users``/:id` | 用户增改删(自禁/最后管理员均 403) |
| POST | `/api/admin/settings/site` | 站点设置(文本/URL/清除) |
| POST | `/api/admin/settings/site/favicon``/site/logo` | favicon/logo 上传(multipart |
| POST | `/api/admin/settings/navlinks``/upload``/download``/comments` | 各设置保存(`action` 分发) |
### 个人中心(需登录)
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | `/profile` | 编辑个人信息页 |
| POST | `/api/profile` | 保存资料(JSON,密码改密校验 400) |
| POST | `/api/profile/avatar` | 上传头像(multipart、JPEG 重编码) |
| GET | `/my/articles` | 我的文章列表 |
| GET | `/my/articles/new``/:id/edit` | 我的文章表单页 |
| POST/PUT/DELETE | `/api/my/articles``/:id` | 文章增改删(跨作者 404/403) |
| POST | `/api/my/articles/attachments` | 上传附件(multipart |
| DELETE | `/api/my/articles/attachments/:id` | 删除附件 |
| GET | `/api/my/articles/:id/attachments` | 附件列表 |
### API 约定
- 成功:`{"ok": true, "redirect": "...", "data": {...}}``redirect` 为原 302 目标,前端 fetch 后跳转)
- 失败:`{"ok": false, "code": "<i18n 键>", "error": "<按请求语言翻译的文案>"}`;状态码:400 校验失败 / 401 未登录 / 403 无权限 / 404 不存在 / 409 冲突 / 429 限流锁定 / 500 服务错误
- 除文件上传(multipart)外请求体为 `application/json`CSRF 通过 `X-CSRF-Token` 请求头传递
## 用户角色
| 角色 | 个人中心 | 我的文章 | 管理后台 |
| --- | --- | --- | --- |
| `admin` | ✓ | ✓ | ✓ |
| `author` | ✓ | ✓ | ✗ |
## 许可证
MIT