You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
7.2 KiB
7.2 KiB
个人项目展示网站
Go + SQLite 后端,Vite + React 前端,单二进制部署。
技术栈
| 层 | 技术 |
|---|---|
| 后端 | Go 1.26+,stdlib net/http(1.22 原生路由) |
| 数据库 | SQLite(modernc.org/sqlite,纯 Go,WAL 模式) |
| 认证 | bcrypt + JWT(HMAC-SHA256) |
| 前端 | Vite + React 19 + React Router v7 + TanStack Query v5 |
| CSS | Tailwind CSS v4(@tailwindcss/vite) |
| CI | Drone CI(type: exec,宿主机执行) |
| 部署 | Caddy 反代 + systemd |
依赖数量:Go 3 个外部包,npm 8 个外部包。
目录结构
├── main.go # 入口:数据库初始化、HTTP 启动、优雅退出
├── embed_prod.go # //go:embed frontend/dist(生产构建)
├── embed_dev.go # dev tag:不 embed(使用 Vite dev server)
├── Makefile / Caddyfile / .drone.yml
├── internal/
│ ├── auth/ # bcrypt 校验 + JWT 签发/验证 + AdminOnly 中间件
│ ├── db/ # SQLite 初始化、迁移执行、admin 种子、Project CRUD
│ ├── handlers/ # HTTP 处理器(公开接口 + 管理接口 + 图片上传 + 导出)
│ ├── models/ # 数据模型与请求/响应类型
│ ├── router/ # 路由组装、CORS 中间件、Slog 日志、SPA fallback
│ └── storage/ # 图片本地存储(UUID 文件名)
└── frontend/
└── src/
├── api/ # fetch 封装(JWT 注入、错误处理)
├── hooks/ # useAuth(context + localStorage)、useProjects(TanStack Query)
├── components/ # Layout、Navbar、ProjectCard、ProjectGrid、TagFilter、
│ # LoginForm、ProjectForm、ImageUpload、AdminGuard
├── pages/ # HomePage、LoginPage、AdminDashboard、ProjectEditPage、NotFoundPage
└── types/ # TypeScript 类型定义
开发
环境要求
- Go 1.26+
- Node 24+
- 可选:Caddy(生产部署)
启动
分别在两个终端中执行:
# 终端 1:Go 后端(:8882)
make dev-backend
# 等价:ADMIN_PASSWORD=yourpass JWT_SECRET=your-secret go run -tags dev person-home
# 终端 2:Vite dev server(:5173,自动代理 /api → :8882)
make dev-frontend
# 等价:cd frontend && npm run dev
浏览器访问 http://localhost:5173。
环境变量
| 变量 | 必需 | 默认值 | 说明 |
|---|---|---|---|
ADMIN_PASSWORD |
首次运行 | — | admin 用户密码(bcrypt 哈希后存库) |
JWT_SECRET |
是 | change-me-in-production |
JWT 签名密钥(HMAC-SHA256) |
ADDR |
否 | :8882 |
服务监听地址 |
DB_PATH |
否 | data/person-home.db |
SQLite 数据库路径 |
UPLOAD_DIR |
否 | uploads |
图片上传目录 |
CORS_ORIGIN |
否 | http://localhost:5173 |
允许的跨域来源(开发用) |
首次启动时,若 users 表为空,自动创建 admin 用户并使用 ADMIN_PASSWORD 哈希入库。后续重启会跳过种子步骤。
数据库
SQLite 文件保存在 data/ 目录(已 gitignore),启用 WAL 模式,busy_timeout=5000ms。迁移文件在 internal/db/migrations/,按文件名字典序执行,通过 embed 内嵌到二进制。
前端开发
- Vite 代理配置在
vite.config.ts,所有/api和/uploads请求转发到:8882 - 环境变量以
VITE_前缀暴露给客户端,敏感配置不放前端 - Tailwind v4 通过
@tailwindcss/vite插件集成,无需 PostCSS
API 参考
公开接口
| Method | Path | 查询参数 | 说明 |
|---|---|---|---|
GET |
/api/projects |
?tag=go&featured=true&limit=10 |
项目列表(按 sort_order ASC, created_at DESC 排序) |
GET |
/api/projects/{id} |
— | 单个项目详情 |
GET |
/api/tags |
— | 所有去重标签列表 |
认证
| Method | Path | Body | 响应 |
|---|---|---|---|
POST |
/api/auth/login |
{"username":"admin","password":"..."} |
{"token":"eyJ...","expires_at":"..."} |
Token 有效期 24 小时。
管理接口(需 Authorization: Bearer <token>)
| Method | Path | Body | 说明 |
|---|---|---|---|
GET |
/api/admin/projects |
— | 全部项目(含非精选) |
POST |
/api/admin/projects |
Project JSON | 创建项目 |
PUT |
/api/admin/projects/{id} |
Partial Project JSON | 更新项目(支持部分字段) |
DELETE |
/api/admin/projects/{id} |
— | 删除项目(同时清理图片文件) |
POST |
/api/admin/upload |
multipart/form-data(字段名 image) |
上传图片(最大 10MB) |
GET |
/api/admin/export |
— | 导出全部项目为 JSON(Content-Disposition: attachment) |
错误响应
统一格式:{"error": "描述信息"},HTTP 状态码 4xx/5xx。
构建与部署
生产构建
make build
等效于:
cd frontend && npm ci && npm run build
go build -ldflags="-s -w" -o person-home person-home
产出单二进制 person-home(约 11MB,stripped),内嵌前端 dist/ 和 SQLite 迁移。
systemd 服务
# /etc/systemd/system/person-home.service
[Unit]
Description=Person Home Portfolio
After=network.target
[Service]
Type=simple
User=www-data
WorkingDirectory=/var/www/person-home
Environment=ADMIN_PASSWORD=<your-password>
Environment=JWT_SECRET=<your-secret>
ExecStart=/usr/local/bin/person-home
Restart=on-failure
[Install]
WantedBy=multi-user.target
sudo systemctl enable --now person-home
Caddy 配置
# /etc/caddy/Caddyfile
person-home.example.com {
reverse_proxy localhost:8882
}
Go 二进制同时提供 API 和 SPA 静态文件,Caddy 仅负责 TLS 终止和反代。
CI/CD(Drone)
.drone.yml 配置了 type: exec 流水线,构建产物通过 nohup 直接在宿主机启动:
kind: pipeline
type: exec
name: build-and-deploy
steps:
- name: build
commands:
- cd frontend && npm ci && npm run build
- go build -ldflags="-s -w" -o person-home .
- name: deploy
environment:
ADMIN_PASSWORD:
from_secret: admin_password
JWT_SECRET:
from_secret: jwt_secret
commands:
- mkdir -p /opt/person-home
- sudo cp person-home /opt/person-home/person-home
- sudo sh -c 'pkill -x person-home || true; nohup env ADMIN_PASSWORD=$ADMIN_PASSWORD JWT_SECRET=$JWT_SECRET /opt/person-home/person-home >> /tmp/person-home.log 2>&1 &'
when:
branch: main
密钥管理
密钥通过 Drone 的 from_secret 机制注入,不在仓库文件中明文存储。exec runner 从宿主机环境变量或 secrets 文件读取:
# 在 Drone exec runner 宿主机上设置
export ADMIN_PASSWORD=your-password
export JWT_SECRET=your-secret
# 或写入 ~/.drone-secrets.yml
流水线中通过 environment + from_secret 引用,Drone 在运行时将值注入 $ADMIN_PASSWORD、$JWT_SECRET,再由 nohup env ... 传递给二进制。
前置条件:Drone exec runner 主机需预装 Go 1.26+、Node 24+。