# 姜十三论坛(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 撤销 | | 部署 | 官方:`deploy/` Docker Compose(Next SSR + Go + Postgres,反代自备);本地开发仅 Compose 起数据库 | --- ## 功能概览 ### 用户 - 注册 / 登录 / 登出(注册即登录,免二次跳转) - 个人资料编辑:昵称、头像、邮箱、个性签名 - 修改密码(改密后全端踢下线) - 个人主页:发帖列表、评论列表(分页)、签到积分 - 每日签到(积分 + 连续签到天数) ### 内容 - 帖子发帖 / 编辑 / 删除(作者本人或管理员) - 帖子类型:普通帖 / 问答帖 - 帖子置顶 / 推荐(仅管理员) - 评论楼层制:主评论 = 楼层,子评论不占楼层,最多 10 层嵌套 - 楼层分页 + 内存拼装评论树(单次查询,无 N+1) - Markdown 渲染(代码高亮、引用、列表等) - 帖子点赞(联合唯一索引防重复) ### 通知 - 站内通知:评论你的帖子、回复你的评论、点赞你的帖子 - 未读红点 SSR 直出,实时靠 WebSocket 推送,回前台时 HTTP 校准 - 单条标记已读 / 全部已读 ### 管理后台 - 公告管理(草稿 / 发布、标签预设色) - 仪表盘 ### SEO - 全站 SSR/ISR,SEO 关键内容在初始 HTML 中 - 帖子详情页输出 `DiscussionForumPosting` 类型 JSON-LD - 动态 `sitemap.ts` + `robots.ts`(由 Next.js 对外提供,后端不重复挂 `/sitemap.xml` / `/robots.txt`) ### 安全 - 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 ├── deploy/ # 官方运行时(web + api + Postgres,反代自备) ├── docker-compose.yml # 仅本地开发 Postgres ├── 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 ``` **工作目录必须是 `backend/`。** 上传文件与 JWT 密钥落在 `backend/data/`(头像 `backend/data/uploads/avatars/`)。不要在仓库根或 `backend/cmd/jiang13/` 执行 `go run`,否则会另建一套 data,数据库里头像 URL 会对不上文件。启动日志应含 `workPath=.../backend` 和 `dataDir=.../backend/data`。需要覆盖时设置 `JIANG13_WORK_PATH` 指向 `backend/`。 后端默认监听 `: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`。`JIANG13_WORK_PATH` 可强制指定后端根目录(`app.ini` 与 `data/` 所在处)。 ### 前端(`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-` 前缀。 若本机曾跑过早期构建,浏览器里可能还留着 `Path=/api/auth` 的 `j13_refresh`(HttpOnly,页面脚本删不掉)。打开开发者工具 → Application → Cookies,删掉该条,或对该源执行「清除站点数据」。之后只应存在 `Path=/` 的三枚 cookie。 --- ## 部署 **官方运行时是 `deploy/docker-compose.yml`(Next SSR + Go + Postgres,不含反代)。** 说明与升级命令见 [deploy/README.md](./deploy/README.md),Nginx 示例见 [deploy/nginx.example.conf](./deploy/nginx.example.conf),契约见 [deploy/CONTRACT.md](./deploy/CONTRACT.md)。 本地开发仍用仓库根 `start.bat` / `start.sh` 与根目录 `docker-compose.yml`(只起 Postgres)。不要把开发流程改成必须打应用镜像。 更新应用(镜像已发布时): ```bash cd deploy docker compose pull docker compose up -d ``` 后台一键更新、预编译包不在本期。Cloudflare Workers 不是官方运行时。 ### 生产环境要点 1. **官方 Compose:** `DEV_MODE=false`,强随机 `JWT_SECRET` 与 `POSTGRES_PASSWORD`;由你现有的 Nginx/Caddy 提供 HTTPS,并把页面与 `/api`、`/api/ws`、`/uploads` 挂到同一 Host。 2. **CORS:** 设 `SITE_URL` 为对外 origin(不要改 `router.go` 写死域名)。 3. **自管部署:** 仍须同源反代。Workers 前端须保持 `middleware.ts` 文件名(OpenNext 不识别 `proxy.ts`),实时通道由反代承接 `/api/ws`。 ### WebSocket(开发 vs 生产) 浏览器发起 WebSocket 握手**一定会带 `Origin`**。后端拒绝空 Origin,这只挡住 curl / 脚本探测,**不影响 `npm run dev`,也不影响生产浏览器**。 **开发环境(`DEV_MODE=true`)** - 页面在 `http://localhost:3000`,WS 直连 `ws://localhost:3001/api/ws`(Next.js rewrite 不透传 `Upgrade`)。 - 二者端口不同,不是浏览器意义上的同源。后端因此放行 Origin 主机为 `localhost` / `127.0.0.1` 的**任意端口**。 - Cookie 不按端口隔离,`:3000` 上的 `j13_token` 会随握手带到 `:3001`。不要把 `DEV_MODE` 改成 `false` 再跑这对端口,否则 Origin `localhost:3000` 对不上 Host `localhost:3001`,握手会被拒。 **生产环境(`DEV_MODE=false`)** - Origin 的 host(含端口)必须与请求 `Host` **完全一致**。请用 Nginx/Caddy 把前端与 `/api/`、`/api/ws` 挂到同一域名,例如 `https://bbs.example.com`。 - 反向代理需透传 `Upgrade`、`Connection`,并保留原始 `Host` / `Origin`。 - 生产 cookie 使用 `__Host-` 前缀,要求 `Secure`、`Path=/`、**不能设 Domain**。因此 API **不能**放到 `api.example.com` 这类另一子域,否则登录 cookie 与 WS 都会失效。 - Cloudflare Workers / OpenNext **不能**透传任意 WebSocket。若前端部署在 Workers,实时通道必须由同源反代(Nginx 等)承接 `/api/ws`,而不是指望 Workers 升级连接。 - 可用 `NEXT_PUBLIC_WS_URL` 覆盖前端连线地址;一旦覆盖,Origin 仍须与后端看到的 Host 一致。 --- ## 开发约定 - **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