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.
 
 
 
 
 
 
npmrun b12d4475b4 fix: move Go environment configuration to build step for consistency 2 days ago
.claude feat: initialize frontend and backend structure with TypeScript, Go, and Vite 2 days ago
frontend feat: initialize frontend and backend structure with TypeScript, Go, and Vite 2 days ago
internal feat: initialize frontend and backend structure with TypeScript, Go, and Vite 2 days ago
.drone.yml fix: move Go environment configuration to build step for consistency 2 days ago
.gitignore feat: initialize frontend and backend structure with TypeScript, Go, and Vite 2 days ago
Caddyfile feat: initialize frontend and backend structure with TypeScript, Go, and Vite 2 days ago
Makefile feat: initialize frontend and backend structure with TypeScript, Go, and Vite 2 days ago
README.md feat: initialize frontend and backend structure with TypeScript, Go, and Vite 2 days ago
embed_dev.go feat: initialize frontend and backend structure with TypeScript, Go, and Vite 2 days ago
embed_prod.go feat: initialize frontend and backend structure with TypeScript, Go, and Vite 2 days ago
go.mod feat: initialize frontend and backend structure with TypeScript, Go, and Vite 2 days ago
go.sum feat: initialize frontend and backend structure with TypeScript, Go, and Vite 2 days ago
main.go feat: initialize frontend and backend structure with TypeScript, Go, and Vite 2 days ago

README.md

个人项目展示网站

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+。