diff --git a/README.md b/README.md new file mode 100644 index 0000000..cb44325 --- /dev/null +++ b/README.md @@ -0,0 +1,223 @@ +# 姜十三论坛(jiang13-bbs) + +一个基于 Go + Next.js 的现代社区论坛系统,采用 SSR 直出 + HttpOnly Cookie 认证方案,具备完整的帖子/评论/通知/签到/管理后台功能。 + +> **许可声明:** 本项目为专有软件(Proprietary),源码可见但不构成开源授权。详见 [LICENSE](./LICENSE)。未经书面许可,不得使用、部署或分发。 + +--- + +## 技术栈 + +| 层级 | 技术 | +|------|------| +| 后端 | Go 1.27 · Gin · GORM · PostgreSQL 17 | +| 前端 | Next.js 16 (App Router) · TypeScript · Tailwind CSS v4 | +| 认证 | JWT (HttpOnly Cookie) + Refresh Token 轮转 + CSRF 双提交 + Token Version 撤销 | +| 部署 | Docker Compose(数据库)· 可部署至 Cloudflare Workers(前端) | + +--- + +## 功能概览 + +### 用户 + +- 注册 / 登录 / 登出(注册即登录,免二次跳转) +- 个人资料编辑:昵称、头像、邮箱、个性签名 +- 修改密码(改密后全端踢下线) +- 个人主页:发帖列表、评论列表(分页)、签到积分 +- 每日签到(积分 + 连续签到天数) + +### 内容 + +- 帖子发帖 / 编辑 / 删除(作者本人或管理员) +- 帖子类型:普通帖 / 问答帖 +- 帖子置顶 / 推荐(仅管理员) +- 评论楼层制:主评论 = 楼层,子评论不占楼层,最多 10 层嵌套 +- 楼层分页 + 内存拼装评论树(单次查询,无 N+1) +- Markdown 渲染(代码高亮、引用、列表等) +- 帖子点赞(联合唯一索引防重复) + +### 通知 + +- 站内通知:评论你的帖子、回复你的评论、点赞你的帖子 +- 未读红点 SSR 直出 + 30s 轮询校正 +- 单条标记已读 / 全部已读 + +### 管理后台 + +- 公告管理(草稿 / 发布、标签预设色) +- 仪表盘 + +### SEO + +- 全站 SSR/ISR,SEO 关键内容在初始 HTML 中 +- 帖子详情页输出 `DiscussionForumPosting` 类型 JSON-LD +- 动态 `sitemap.ts` + `robots.ts` + +### 安全 + +- CSP / HSTS / X-Frame-Options: DENY / X-Content-Type-Options / Referrer-Policy +- 速率限制:登录 20/min、注册 10/min、发帖新用户 24h 冷静期 +- Refresh Token 一次性轮转 + 盗用检测(吊销后重放 → 杀全家族) +- 生产 HTTPS 启用 `__Host-` Cookie 前缀 + +--- + +## 项目结构 + +``` +jiang13-bbs/ +├── backend/ # Go 后端 +│ ├── cmd/jiang13/ # 入口 main.go +│ ├── config/ # 配置解析(app.ini + 环境变量) +│ ├── handler/ # HTTP 处理器 +│ ├── middleware/ # 认证 / CSRF / 限流 / 安全头 +│ ├── model/ # GORM 模型 + 数据库初始化 +│ ├── router/ # 路由注册 +│ ├── service/ # 业务逻辑层 +│ ├── app.ini.example # 配置模板 +│ └── go.mod +├── frontend/ # Next.js 前端 +│ ├── app/ # App Router 页面 +│ │ ├── settings/ # 账号设置(资料 + 密码) +│ │ ├── u/[id]/ # 用户主页 +│ │ ├── post/[id]/ # 帖子详情 +│ │ ├── compose/ # 发帖 +│ │ ├── notifications/ # 通知列表 +│ │ ├── admin/ # 管理后台 +│ │ └── ... +│ ├── components/ # 复用组件 +│ ├── lib/ # API 封装 / 工具函数 +│ ├── middleware.ts # SSR 登录态静默轮转 +│ ├── next.config.ts # Rewrite 代理 / 图片缓存 +│ └── .env.example +├── docker-compose.yml # PostgreSQL 容器 +├── LICENSE # 专有软件许可协议 +└── .gitignore +``` + +--- + +## 快速开始 + +### 前置条件 + +- Go 1.27+ +- Node.js 20+ / pnpm 或 npm +- PostgreSQL 17(或通过 Docker Compose 启动) + +### 1. 启动数据库 + +```bash +docker compose up -d postgres +``` + +### 2. 启动后端 + +```bash +cd backend +cp app.ini.example app.ini +# 按需修改 app.ini 中的 DSN / JWT_SECRET / DEV_MODE +go mod download +go run ./cmd/jiang13 +``` + +后端默认监听 `:3001`,首次启动自动执行数据库迁移并写入默认板块。 + +### 3. 启动前端 + +```bash +cd frontend +cp .env.example .env.local +npm install +npm run dev +``` + +前端默认监听 `:3000`,通过 `next.config.ts` rewrite 将 `/api/*` 代理到后端。 + +--- + +## 配置说明 + +### 后端(`backend/app.ini`) + +```ini +[server] +HTTP_PORT = 3001 + +[database] +DSN = postgres://postgres:postgres@localhost:5432/jiang13?sslmode=disable + +[security] +; 留空则自动生成并持久化到 data/.jwt_secret +; 生产环境务必显式指定强随机值 +JWT_SECRET = + +[paths] +DATA = data + +[app] +DEV_MODE = true +``` + +环境变量优先级高于 app.ini:`HTTP_PORT`、`DB_DSN`、`JWT_SECRET`、`DEV_MODE`。 + +### 前端(`frontend/.env.local`) + +```bash +# SSR 与 middleware 直连后端(本地开发) +NEXT_PUBLIC_API_URL=http://localhost:3001 + +# 部署到 Cloudflare Workers 等边缘环境时必须显式配置 +# BACKEND_URL=https://your-backend.example.com +``` + +--- + +## 认证架构 + +采用三 Cookie + Token Version 方案,不使用 localStorage 存储 token: + +| Cookie | 用途 | 属性 | +|--------|------|------| +| `j13_token` | Access JWT(15 分钟) | HttpOnly · SameSite=Lax | +| `j13_refresh` | Refresh Token(7 天,DB 存储 SHA-256) | HttpOnly · SameSite=Lax · Path=/ | +| `j13_csrf` | CSRF Token(JS 可读) | SameSite=Lax | + +- **SSR 直出登录态:** `middleware.ts` 在页面/RSC 请求前本地解码 access token 的 exp,过期时静默调后端 refresh 轮转,新 cookie 注入请求头供 SSR 使用并透传浏览器。 +- **客户端续期:** `lib/api.ts` 的 `fetchWithRefresh` 遇到 401 自动调 `/api/auth/refresh` 重试。 +- **撤销机制:** 改密码 / 封禁 / 管理员强制下线时递增 `User.TokenVersion`,所有旧 JWT 立即失效。 +- **盗用检测:** Refresh Token 一次性轮转,已吊销 token 重放时撤销该用户全部 refresh token。 + +生产 HTTPS 下 cookie 名自动启用 `__Host-` 前缀。 + +--- + +## 部署 + +### 生产环境要点 + +1. **后端:** 设置 `DEV_MODE=false`,指定强随机 `JWT_SECRET`,通过反向代理(Nginx/Caddy)提供 HTTPS。 +2. **前端:** 设置 `BACKEND_URL` 为后端 HTTPS 地址,`npm run build` 后部署。Cloudflare Workers 部署需保持 `middleware.ts` 文件名不变(OpenNext 不识别 `proxy.ts`)。 +3. **数据库:** 修改默认密码,启用 SSL 连接。 +4. **CORS:** 生产环境修改 `router.go` 中的 `AllowOrigins` 为实际域名。 + +--- + +## 开发约定 + +- **Next.js 16:** `params` / `searchParams` / `cookies()` / `headers()` 必须 `await`。 +- **SEO:** 所有公开页面 SSR/ISR,SEO 关键内容(title、description、OG、JSON-LD)必须在初始 HTML 中。 +- **权限:** 权限校验只在 Go 后端执行,前端不做判定。 +- **配色:** 仅修改 `frontend/app/globals.css` 中的 CSS 令牌,禁止在组件内硬编码颜色。 +- **无障碍:** 文字对比度 ≥4.5:1,交互控件边界 ≥3:1(WCAG 2.1 AA)。 + +--- + +## 许可证 + +**专有软件(Proprietary)** — Copyright © 2026 姜十三,保留所有权利。 + +源码仅可见,未经书面许可不得使用、部署、修改或分发。详见 [LICENSE](./LICENSE)。 + +联系方式:aarbbs@88.com diff --git a/frontend/next-env.d.ts b/frontend/next-env.d.ts index ce4e94a..a419cbe 100644 --- a/frontend/next-env.d.ts +++ b/frontend/next-env.d.ts @@ -1,7 +1,7 @@ /// /// -import "./.next/types/routes.d.ts"; -import "./.next/types/root-params.d.ts"; +import "./.next/dev/types/routes.d.ts"; +import "./.next/dev/types/root-params.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/start.bat b/start.bat new file mode 100644 index 0000000..c537258 --- /dev/null +++ b/start.bat @@ -0,0 +1,62 @@ +@echo off +title jiang13-bbs launcher + +echo ============================================ +echo jiang13-bbs Dev Launcher +echo ============================================ +echo. + +REM ---- 1. Start PostgreSQL ---- +echo [1/3] Starting database (Docker Compose)... +docker compose up -d postgres +if errorlevel 1 ( + echo [FAIL] Database failed to start. Is Docker running? + pause + exit /b 1 +) +echo [OK] Database started +echo. + +REM ---- 2. Backend ---- +echo [2/3] Checking backend config... +if not exist "backend\app.ini" ( + copy "backend\app.ini.example" "backend\app.ini" >nul + echo Created backend\app.ini from template +) +if not exist "backend\data" mkdir "backend\data" + +echo Starting backend (port 3001)... +start "jiang13-backend" cmd /k "cd /d %~dp0backend && go run ./cmd/jiang13" +echo [OK] Backend started +echo. + +REM ---- 3. Frontend ---- +echo [3/3] Checking frontend config... +if not exist "frontend\.env.local" ( + copy "frontend\.env.example" "frontend\.env.local" >nul + echo Created frontend\.env.local from template +) + +if not exist "frontend\node_modules" ( + echo First run, installing dependencies... + cd /d %~dp0frontend + call npm install + cd /d %~dp0 +) + +echo Starting frontend (port 3000)... +start "jiang13-frontend" cmd /k "cd /d %~dp0frontend && npm run dev" +echo [OK] Frontend started +echo. + +echo ============================================ +echo All services started! +echo. +echo Frontend: http://localhost:3000 +echo Backend: http://localhost:3001 +echo Database: localhost:5432 +echo. +echo Close each window to stop the service. +echo Stop database: docker compose down +echo ============================================ +pause diff --git a/start.sh b/start.sh new file mode 100644 index 0000000..163e370 --- /dev/null +++ b/start.sh @@ -0,0 +1,91 @@ +#!/usr/bin/env bash +set -euo pipefail + +# ═══════════════════════════════════════════ +# 姜十三论坛 开发环境启动脚本 (Linux/macOS) +# ═══════════════════════════════════════════ + +ROOT_DIR="$(cd "$(dirname "$0")" && pwd)" +cd "$ROOT_DIR" + +echo "══════════════════════════════════════════" +echo " 姜十三论坛 开发环境启动脚本" +echo "══════════════════════════════════════════" +echo "" + +# ---- 1. 启动 PostgreSQL ---- +echo "[1/3] 启动数据库 (Docker Compose)..." +if ! command -v docker &>/dev/null; then + echo "✗ 未检测到 docker,请先安装 Docker" + exit 1 +fi +docker compose up -d postgres +echo "√ 数据库已启动" +echo "" + +# ---- 2. 检查后端配置 ---- +echo "[2/3] 检查后端配置..." +if [ ! -f "backend/app.ini" ]; then + cp "backend/app.ini.example" "backend/app.ini" + echo " 已从模板创建 backend/app.ini,请按需修改配置" +fi +mkdir -p backend/data + +# 启动后端(后台) +echo " 启动后端 (端口 3001)..." +cd "$ROOT_DIR/backend" +go run ./cmd/jiang13 & +BACKEND_PID=$! +cd "$ROOT_DIR" +echo "√ 后端已启动 (PID: $BACKEND_PID)" +echo "" + +# ---- 3. 检查前端配置 ---- +echo "[3/3] 检查前端配置..." +if [ ! -f "frontend/.env.local" ]; then + cp "frontend/.env.example" "frontend/.env.local" + echo " 已从模板创建 frontend/.env.local" +fi + +# 检查 node_modules +if [ ! -d "frontend/node_modules" ]; then + echo " 首次运行,安装前端依赖..." + cd "$ROOT_DIR/frontend" + npm install + cd "$ROOT_DIR" +fi + +# 启动前端(后台) +echo " 启动前端 (端口 3000)..." +cd "$ROOT_DIR/frontend" +npm run dev & +FRONTEND_PID=$! +cd "$ROOT_DIR" +echo "√ 前端已启动 (PID: $FRONTEND_PID)" +echo "" + +echo "══════════════════════════════════════════" +echo " 全部启动完成!" +echo "" +echo " 前端: http://localhost:3000" +echo " 后端: http://localhost:3001" +echo " 数据库: localhost:5432" +echo "" +echo " 后端 PID: $BACKEND_PID" +echo " 前端 PID: $FRONTEND_PID" +echo " 停止服务: kill $BACKEND_PID $FRONTEND_PID && docker compose down" +echo "══════════════════════════════════════════" + +# 捕获 Ctrl+C,清理子进程 +cleanup() { + echo "" + echo "正在停止服务..." + kill $BACKEND_PID 2>/dev/null || true + kill $FRONTEND_PID 2>/dev/null || true + docker compose down 2>/dev/null || true + echo "已停止" +} +trap cleanup EXIT INT TERM + +# 保持脚本运行 +wait