feat: opaque session、安装/发帖 SSR 与最小 Admin 后台

浏览器登录改为 DB sessions(可吊销);敏感词与 OIDC PEM 入 settings;
落地安装向导、注册发帖与 /admin 仪表盘/板块/审核/设置。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-29 05:44:16 +08:00
parent 3f50316ad0
commit fde5f628ec
80 changed files with 4148 additions and 1849 deletions

View File

@@ -29,7 +29,7 @@
| 维度 | 当前实现 | 重构时 |
|------|----------|--------|
| **产品** | 论坛功能全集(见模块地图) | **必须对齐**功能与规则 |
| **运维** | 单二进制 + 内嵌 SPA + SQLite + `app.ini` | **可选保留**;可换容器 / PG / 分离部署 |
| **运维** | 单二进制 + Env 引导 + SQLite/PG/MySQL + `forum_settings` 热更 | **本分支已落地**;无 `app.ini` |
规格文档把「用户能做什么」写死;把「怎么打包发布」放在 [07-config-ops.md](07-config-ops.md) 供参考。

View File

@@ -15,53 +15,53 @@
- [ ] 浅色 / 暗色主题;跟随系统偏好并本地记忆
- [ ] 响应式:平板/手机收起侧栏
- [ ] 长列表虚拟滚动或等价流畅方案
- [ ] Feed 排序:`latest`(最新发帖)/ `reply`(最新回复)/ `hot`(热门)
- [ ] 板块筛选:全部 + 单板块
- [x] Feed 排序:`latest`(最新发帖)/ `reply`(最新回复)/ `hot`(热门)
- [x] 板块筛选:全部 + 单板块
- [ ] 列表样式可配:`title` | `excerpt` | `thumbnail`
- [ ] 搜索:关键词、标签、作者、仅标题(`title_only`
- [ ] 右栏:热门帖、标签云、最新评论、最新用户、友链(可开关排序)
- [ ] 登录用户右栏/侧边:签到与抽奖入口
- [ ] 下拉刷新(移动端)
- [ ] 可选伪静态:`/post/123.html` 等形式(后缀后台可配)
- [ ] 404 页
- [x] 404 页
---
## B. 认证与个人中心
- [ ] 注册(用户名、密码、昵称、邮箱;可选邮箱验证码)
- [x] 注册(用户名、密码、昵称、邮箱;可选邮箱验证码)— SSR `/register`
- [ ] 图形验证码接口(注册流程)
- [ ] 登录 / 登出(会话 Cookie
- [x] 登录 / 登出(opaque session Cookie `jiang13_session`)— SSRSameSite=Lax登出/禁言/改密吊销
- [ ] 忘记密码:邮箱验证码 + 重置
- [ ] 注册配置接口:是否首用户、邮件是否就绪、是否开放注册
- [ ] 首个用户自动成为管理员
- [x] 注册配置:邮件就绪时强制验证码;安装后开放注册(不依赖 SMTP
- [x] ~~首个用户自动成为管理员~~ → 改为仅 `/install` 创建管理员
- [ ] 个人中心:改昵称、签名、密码、上传头像(可裁剪)
- [ ] 个人活动统计:帖数、评数、收藏数、获赞
- [ ] 公开用户主页 `/user/:id`(无邮箱)
- [ ] 禁言用户无法使用需登录写接口
- [x] 禁言用户无法使用需登录写接口(中间件 + compose 门控)
---
## C. 板块
- [ ] 列出板块(含帖数等展示字段)
- [ ] 管理员:创建 / 改 / 删板块
- [ ] 板块名称、描述、图标、色板索引、排序
- [ ] 默认板块保障(空站可引导创建)
- [x] 列出板块(含帖数等展示字段)— 前台侧栏 + Admin
- [x] 管理员:创建 / 改 / 删板块 — SSR `/admin/boards`
- [x] 板块名称、描述、图标、色板索引、排序
- [x] 默认板块保障(空站可引导创建)
---
## D. 帖子(通用)
- [ ] 发帖:选板块、标题、标签、正文
- [ ] 正文图片上传
- [ ] TipTap 富文本能力(见 [06-pages-ux.md](06-pages-ux.md) 编辑器节)
- [x] 发帖:选板块、标题、标签、正文 — SSR `/compose`normal
- [x] 正文图片上传`/compose/upload` + Markdown 插入
- [ ] TipTap 富文本能力(见 [06-pages-ux.md](06-pages-ux.md) 编辑器节)— 本分支改用 Markdown textarea 渐进增强
- [ ] Markdown 编辑模式(与富文本互转/双模)
- [ ] 编辑帖子(时限、锁帖约束)
- [x] 编辑帖子(时限、锁帖约束)— SSR `/post/:id/edit`
- [ ] 删除帖子 → 软删进回收站
- [ ] 修订历史列表与单条详情(可做 diff
- [ ] 点赞切换;收藏切换;收藏列表页
- [ ] 浏览量(可 `skip_view=1` 跳过计数
- [x] 点赞切换;收藏切换;收藏列表页未迁)
- [x] 浏览量 — 详情页计数
- [ ] 举报帖子
- [ ] 内容审核状态展示(作者可见待审/被拒)
@@ -80,7 +80,7 @@
- [ ] 精华 / 取消
- [ ] 禁止编辑edit lock
- [ ] 禁止评论 / 结贴comments lock
- [ ] 审核通过 / 拒绝(拒绝可通知作者)
- [x] 审核通过 / 拒绝(拒绝可通知作者)— SSR `/admin/moderation`
- [ ] 回收站:恢复 / 彻底删除
---
@@ -108,7 +108,7 @@
- [ ] @ 提及 → 通知
- [ ] 回复提醒(站内信 + 可选邮件)
- [ ] 审核中 / 被拒评论可见性规则
- [ ] 管理员:通过 / 拒绝 / 回收站 / 查看评论修订
- [x] 管理员:通过 / 拒绝待审评论 — SSR `/admin/moderation`(回收站/修订未迁)
---
@@ -155,11 +155,13 @@
---
## K. Gitea 码桶
## K. Gitea 码桶**后置**
- [ ] 后台开关、Base URL、Token、同步间隔
- [ ] 手动同步 + 后台定时同步
- [ ] 前台 `/projects` 列表与搜索
> 本迭代**不做**产品化同步:不启后台定时任务、不挂管理入口。表结构与 settings 键可保留兼容。
- [ ] 后置后台开关、Base URL、Token、同步间隔
- [ ] (后置)手动同步 + 后台定时同步
- [ ] (后置)前台 `/projects` 列表与搜索
---
@@ -183,11 +185,11 @@
## N. 管理后台其它
- [ ] 仪表盘:计数 + 待审帖/评/举报/友链 + 最近帖
- [ ] 敏感词文件读写
- [ ] 论坛限流与字数等 Limits
- [x] 仪表盘:用户/帖/板块计数 + 待审帖/评 — SSR `/admin/dashboard`(举报/友链待迁)
- [x] 敏感词`forum_settings.filter_words` 读写 + 热更 — SSR `/admin/settings`
- [x] 基础限流post/comment/register/login/window— SSR完整 Limits 字数等未迁
- [ ] SMTP 配置与测试信
- [ ] 站点品牌名称、标语、简介、keywords、LogoFaviconOG 图、ICP
- [x] 站点品牌文案名称、标语、简介、keywords、Logo 字标、ICP — SSRLogo/Favicon/OG 上传未迁)
- [ ] SQLite 一键备份与下载
---

View File

@@ -38,7 +38,7 @@ erDiagram
User ||--o{ Media : uploads
```
另有:`ForumSetting`(键值)、`OAuthClient` / `OAuthAuthCode``GiteaRepo``SitePage`
另有:`ForumSetting`(键值)、`Session`(浏览器 opaque 会话)、`OAuthClient` / `OAuthAuthCode``GiteaRepo``SitePage`
---
@@ -46,6 +46,18 @@ erDiagram
说明:`json:"-"` 表示默认 API 序列化隐藏;软删列 `deleted_at` 表示 GORM soft delete。
### 2.0 sessions
| 列 | 类型 | 说明 |
|----|------|------|
| id | string(64) PK | 密码学随机 opaque idCookie `jiang13_session` 的值) |
| user_id | uint index | 用户 |
| expires_at | time index | 过期;默认 TTL 7 天,滑动续期 |
| created_at / last_seen_at | time | |
| ip / user_agent | string | 可选审计 |
登出删单行;禁言 / 改密删该用户全部 session。每次请求以 DB 中 `users.role` / 禁言为准。
### 2.1 users
| 字段 | 类型 | 约束 | 说明 |
@@ -391,8 +403,15 @@ Metric`tenure_days` | `likes_received` | `creator_income`
| oidc_group_claim | groups |
| oidc_admin_group | gitea-admin |
| oidc_user_group | gitea-users |
| oidc_rsa_private_pem | (空)启用 OIDC 时懒生成并写入;未启用不落盘 `.oidc_rsa.pem` |
### 6.5 Gitea 同步
### 6.4b 敏感词
| Key | 默认 |
|-----|------|
| filter_words | 默认词表文本;启动时从旧 `filter_words.txt` 导入(若键为空) |
### 6.5 Gitea 同步(**后置**,键保留兼容)
| Key | 默认 |
|-----|------|
@@ -401,6 +420,8 @@ Metric`tenure_days` | `likes_received` | `creator_income`
| gitea_token | |
| gitea_sync_interval_min | 60 |
本迭代不启同步任务;见 [02-features.md](02-features.md) §K。
### 6.6 存储
| Key | 默认 |

View File

@@ -1,25 +1,40 @@
# 04 · HTTP API 合约
> **读者**实现后端 / BFF / 前端数据层的 AI
> **前置**[03-data-model.md](03-data-model.md)
> **源码**[`router/router.go`](../../routers/setup.go)、[`frontend/src/api/client.ts`]((仅 mainfrontend/src/api/client.ts)、[`frontend/src/api/types.ts`]((仅 mainfrontend/src/api/types.ts)、[`middleware/auth.go`](../../modules/auth/auth.go)
> **读者**机器客户端 / 集成方;浏览器 UI **不**使用本文件作为主路径
> **前置**[03-data-model.md](03-data-model.md)、[08-gitea-ssr-architecture.md](08-gitea-ssr-architecture.md)
> **源码**[`routers/setup.go`](../../routers/setup.go)、[`modules/auth/auth.go`](../../modules/auth/auth.go)
不要求 OpenAPI YAML以下表格 + JSON 形状即为合约。新站可加 `/v1` 前缀,但**字段名建议保持**以便对照迁移。
本分支(`rebuild/gitea-ssr`)浏览器走 **`routers/web` 模板 + 表单**。
下列 JSON 合约保留作历史对照与未来机器 API**当前进程仅注册** health / OIDC / robots / sitemap / media 等机器相关路由,论坛 CRUD 的 `/api/*` 已从路由表移除handler 源码可删可留,不以 SPA 兼容为目的)。
`main` 分支 SPA 仍完整实现下表;对照请 checkout `main`
---
## 1. 通用约定
## 1. 本分支已注册的机器入口
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/health` | 探活 |
| GET | `/robots.txt` | 抓取规则 |
| GET | `/sitemap.xml` | 站点地图 |
| GET/POST | `/oauth/*``/.well-known/openid-configuration` | OIDC Provider |
| GET | `/media/thumb/*``/uploads/*` | 媒体 |
---
## 2. 历史 JSON 合约main / 对照,本分支默认不挂载)
以下章节描述原 SPA 使用的 `/api` 形状,便于迁移业务语义;**实现 UI 时请用 web 表单,勿恢复双轨。**
### 通用约定(历史)
| 项 | 约定 |
|----|------|
| Base | 同源;前端 `credentials: 'same-origin'` |
| Base | 同源;`credentials: 'same-origin'` |
| 成功 | HTTP 2xx + JSON body |
| 失败 | 非 2xx + `{ "error": "人类可读中文或英文消息" }` |
| 鉴权 | Cookie `jiang13_token`HttpOnly部分也接受 Authorization Bearer以实现为准 |
| 内容类型 | JSON 默认;部分写接口用 `multipart/form-data`FormData |
| OptionalAuth | 有 cookie 则解析用户,无则游客继续 |
| RequireAuth | 必须登录且未禁言 |
| RequireAdmin | 必须 `role=admin` |
| 失败 | 非 2xx + `{ "error": "..." }` |
| 鉴权 | Cookie `jiang13_session`opaque机器 OIDC 用 Bearer |
### 分页形态差异
@@ -31,11 +46,11 @@
---
## 2. 基础设施 / SEO / 静态
## 3. 基础设施 / SEO / 静态(节选,仍有效)
| 方法 | 路径 | 鉴权 | 说明 |
|------|------|------|------|
| GET | `/health` | 无 | `{ "status": "ok" }`DB ping 失败则非 ok以实现为准 |
| GET | `/health` | 无 | `{ "status": "ok" }` |
| GET | `/robots.txt` | 无 | 文本 |
| GET | `/sitemap.xml` | 无 | XML |
| GET | `/media/thumb/*filepath` | 无 | 缩略图 / WebP 等 |
@@ -282,8 +297,8 @@
| PUT | `/settings/mail` | MailConfig |
| POST | `/settings/mail/test` | `{ to }` |
| PUT | `/settings/oidc` | OIDCConfig |
| PUT | `/settings/gitea` | GiteaSyncConfig |
| POST | `/settings/gitea/sync` | 手动同步 |
| PUT | `/settings/gitea` | **后置**501 |
| POST | `/settings/gitea/sync` | **后置**501 |
| PUT | `/settings/storage` | StorageConfig |
| PUT | `/settings/branding` | SiteBranding |
| POST | `/settings/branding/upload` | Form kind=`logo`\|`favicon`\|`og_image`, file |

View File

@@ -8,14 +8,16 @@
## 1. 注册与引导
源:[`handler/handlers.go`](../../routers/api/handlers.go) `APIRegisterConfig`、[`service/auth.go`](../../services/auth.go)
源:[`routers/web/auth.go`](../../routers/web/auth.go)、[`services/auth.go`](../../services/auth.go)
| 规则 | 细节 |
|------|------|
| 首用户 = 管理员 | `UserCount() == 0` 时注册的用户 `role=admin` |
| 开放注册 | `register_open = (userCount == 0) \|\| mailReady` |
| 邮箱验证码 | `require_email_code = mailReady`;邮件未就绪时首用户仍可无码注册 |
| 后续用户 | 邮件未配置则注册关闭,直到管理员配好 SMTP |
| 管理员 | **仅** `/install` 向导创建(不再「首注册变管理员」) |
| 开放注册 | 安装完成后开放;不依赖 SMTP |
| 邮箱验证码 | `require_email_code = mailReady`;邮件未就绪时可无码注册 |
| 会话 | Cookie `jiang13_session` = opaque id`sessions`HttpOnly + SameSite=LaxHTTPS 时 SecureTTL 7 天滑动续期 |
| 吊销 | 登出删当前 session禁言 / 重置密码删该用户全部 session |
| HMAC 密钥 | `{DATA}/.jwt_secret` 仅 CSRF 等 HMAC**不是**浏览器登录 JWT |
密码bcrypt最小长度来自 `password_min_len`(默认 6
@@ -206,8 +208,8 @@ stateDiagram-v2
## 11. 敏感词与限流
- 敏感词文件`data/filter_words.txt`;发帖/评/私信等路径过滤
- 限流动作键post / comment / register / login / report / message / friend_link 等;窗口秒与次数来自 settings
- 敏感词:`forum_settings.filter_words`Admin SSR 可改并热更);旧文件可导入
- 限流动作键post / comment / register / login / report / message / friend_link 等;窗口秒与次数来自 settingsAdmin 可改基础四项+窗口)
---

View File

@@ -10,50 +10,46 @@
## 1. 路由表
### 1.1 认证(无 MainLayout 壳或独立简洁壳)
> **本分支(`rebuild/gitea-ssr`**:浏览器 UI 走 `routers/web` 模板 + 表单,**不依赖**论坛 JSON `/api`。下表「SSR」列表示是否已迁。
| 路径 | 页面 | 说明 |
|------|------|------|
| `/login` | LoginPage | |
| `/register` | RegisterPage | 读 register/config可能关闭 |
| `/forgot-password` | ForgotPasswordPage | 依赖邮件 |
### 1.1 认证
### 1.2 前台MainLayout
| 路径 | 说明 | SSR |
|------|------|-----|
| `/login` | 登录 / 登出 | 已迁 |
| `/register` | 注册(邮件就绪时要验证码) | 已迁 |
| `/forgot-password` | 忘记密码 | 未迁 |
| 路径 | 页面 |
|------|------|
| `/` | HomePage全部 Feed |
| `/board/:id` | HomePage板块 Feedid 可带伪静态后缀) |
| `/post/:id` | PostDetailPage |
| `/compose` | ComposePage 发帖 |
| `/post/:id/edit` | ComposePage 编辑 |
| `/profile` | ProfilePage需登录 |
| `/user/:id` | UserProfilePage |
| `/favorites` | FavoritesPage |
| `/projects` | ProjectsPageGitea 码桶) |
| `/links` | LinksPage |
| `/messages` | MessagesPage |
| `/page/:slug` | SitePageView |
| `*` | NotFoundPage |
### 1.2 前台
重定向:`/boards``/admin/boards`
| 路径 | 说明 | SSR |
|------|------|-----|
| `/` | Feed | 已迁 |
| `/board/:id` | 板块 Feed | 已迁 |
| `/post/:id` | 帖详情 + 评论/赞/藏 | 已迁 |
| `/compose` | 发帖normalMarkdown textarea + 图片上传) | 已迁 |
| `/post/:id/edit` | 编辑帖 | 已迁 |
| `/profile` | 个人中心 | pending |
| `/user/:id` | 公开用户页 | 未注册 |
| `/favorites` | 收藏 | pending |
| `/projects` | Gitea 码桶 | 后置 |
| `/links` | 友链 | pending |
| `/messages` | 私信 | pending |
| `/page/:slug` | 站点单页 | 未注册 |
| `*` | 404 / pending | 已迁 |
### 1.3 后台AdminLayout需管理员
### 1.3 后台Admin SSR表单 + CSRF不挂管理 JSON `/api`
| 路径 | 页面 |
|------|------|
| `/admin` `/admin/dashboard` | 仪表盘 |
| `/admin/boards` | 板块管理 |
| `/admin/pages` | 单页列表 |
| `/admin/pages/new``/admin/pages/:id/edit` | 单页编辑 |
| `/admin/links` | 友链与申请 |
| `/admin/posts` | 帖子审核/运营 |
| `/admin/comments` | 评论 |
| `/admin/reports` | 举报 |
| `/admin/users` | 用户 |
| `/admin/badges` | 徽章定义 |
| `/admin/media` | 媒体 |
| `/admin/settings` | 系统设置(多 Tab |
| 路径 | 说明 | SSR |
|------|------|-----|
| `/admin` | 重定向 dashboard | 已迁 |
| `/admin/dashboard` | 概览计数 | 已迁 |
| `/admin/boards` | 板块 CRUD | 已迁 |
| `/admin/moderation` | 待审帖/评 通过/拒绝 | 已迁 |
| `/admin/settings` | 品牌 + 基础限流 + 敏感词 | 已迁 |
| `/admin/login` | 重定向前台登录 | 已迁 |
未迁(原 SPAreports / users / badges / media / pages / links / SMTP / 完整 Limits 等。
---

View File

@@ -2,26 +2,106 @@
> **读者**:部署与运维、以及实现配置层的 AI
> **前置**[README.md](README.md)
> **源码**[`app.ini.example`](../../app.ini.example)、[`config/`](../../config/)、[`README.md`](../../README.md)、[`handler/seo.go`](../../routers/api/seo.go)、[`handler/seo_bot.go`](../../routers/api/seo_bot.go)、[`embed_static/`]((仅 main 分支embed_static/)
> **源码**[`config/`](../../config/)、[`README.md`](../../README.md)、[`routers/api/seo.go`](../../routers/api/seo.go)、[`routers/install/`](../../routers/install/)
运维形态可改;下列描述**现网**行为,便于迁移数据与对齐环境变量语义
运维形态可改;下列描述**本分支**行为。
---
## 1. 进程配置优先级
## 0. 首次安装Gitea 式)
**命令行显式参数 > 环境变量 > `app.ini` > 内置默认**
| 项 | 说明 |
|----|------|
| 锁文件 | `data/install.lock` |
| 向导 | `GET/POST /install``templates/install.tmpl` |
| 未锁定 | 除 `/install``/ssr-assets/*``/health` 外重定向到向导 |
| 管理员 | 仅安装向导创建;不再「首个注册用户变管理员」 |
| 向导内容 | 站点名 + 管理员账号(数据库已在进程启动时连上) |
| 旧数据 | 启动时若已有用户且无锁,自动补写锁 |
| CLI | 环境变量 | INI | 默认 | 说明 |
|-----|----------|-----|------|------|
| `--port` | `JIANG13_HTTP_PORT` | `[server] HTTP_PORT` | 3000 | 监听端口 |
| `--data` | `JIANG13_DATA` | `[paths] DATA` | `data` | 数据目录 |
| `--jwt-secret` | `JIANG13_JWT_SECRET` | `[security] JWT_SECRET` | 自动生成 | JWT 密钥 |
| `--config` | `JIANG13_CONFIG` | | `{work}/app.ini` | 配置文件路径 |
| `--work-path` | `JIANG13_WORK_PATH` | | 可执行文件目录 | 工作目录 |
| `--service` | | | | install/uninstall/start/stop/restart/status |
**无 `app.ini`。** 引导仅 CLI / Env。
`app.ini` 示例见 [`app.ini.example`](../../app.ini.example)。业务配置邮件、OIDC、Gitea、存储、品牌等**DB `forum_settings`**,管理后台热更新,不必写进 ini。
---
## 1. 配置分层与重启边界
| 层 | 存什么 | 变更方式 | 需重启 |
|----|--------|----------|--------|
| **Bootstrap** | `DATA``HTTP_PORT`/`ADDR``DB_TYPE` + DSN/连接参数、工作目录 | CLI / Env | **是** |
| **密钥文件** | App HMAC`data/.jwt_secret`文件名历史遗留OIDC RSA **仅启用时**写入 settings可选遗留文件迁移 | 自动生成 | HMAC 换钥需重启 |
| **站点运行时** | 品牌、邮件、OIDC 开关、限流、敏感词、存储、伪静态… | DB `forum_settings` | **否**(热更) |
**优先级:** 命令行显式参数 > 环境变量 > 内置默认。
### 进程引导
| CLI | 环境变量 | 默认 | 说明 |
|-----|----------|------|------|
| `--port` | `JIANG13_HTTP_PORT` | 3000 | 监听端口 |
| `--http-addr` | `JIANG13_HTTP_ADDR` | (空=全接口) | 监听地址 |
| `--data` | `JIANG13_DATA` | `data` | 数据目录 |
| `--work-path` | `JIANG13_WORK_PATH` | 可执行文件目录 | 工作目录 |
| `--db-type` | `JIANG13_DB_TYPE` | `sqlite` | `sqlite` \| `postgres` \| `mysql` |
| `--db-dsn` | `JIANG13_DB_DSN` | sqlite 默认 `{DATA}/jiang13.db` | 完整 DSN优先 |
| `--db-host` 等 | `JIANG13_DB_HOST` / `USER` / `PASS` / `NAME` / `SSLMODE` | | DSN 为空时拼接pg/mysql |
| `--service` | | | install/uninstall/start/stop/restart/status |
`{DATA}/.jwt_secret`**App HMAC 密钥**CSRF 双提交等),启动时自动生成。**不是**浏览器登录 JWT。`--config` / `--jwt-secret` / `JIANG13_JWT_SECRET` 已废弃。
浏览器登录DB `sessions` + Cookie `jiang13_session`。OIDC 对外 token 仍为 JWT私钥在 `forum_settings.oidc_rsa_private_pem`(启用时懒加载;未启用不生成 `.oidc_rsa.pem`)。
业务配置邮件、OIDC、存储、品牌、敏感词等**DB `forum_settings`**,管理后台热更新。
### 数据库 Env 示例
**SQLite默认**
```bash
JIANG13_DATA=/data
# 可不设 DB_*;库文件 = $JIANG13_DATA/jiang13.db
```
**PostgreSQL**
```bash
JIANG13_DB_TYPE=postgres
JIANG13_DB_DSN="postgres://forum:secret@db:5432/jiang13?sslmode=disable"
# 或拆分:
# JIANG13_DB_HOST=db:5432
# JIANG13_DB_USER=forum
# JIANG13_DB_PASS=secret
# JIANG13_DB_NAME=jiang13
# JIANG13_DB_SSLMODE=disable
```
**MySQL / MariaDB**
```bash
JIANG13_DB_TYPE=mysql
JIANG13_DB_DSN="forum:secret@tcp(db:3306)/jiang13?parseTime=true&loc=Local&charset=utf8mb4"
```
连库失败时进程**退出并打印 Env 提示**,不会静默回落 sqlite。
### Docker Compose 多库示意
```yaml
services:
jiang13:
image: hangzhang714128/jiang13-forum:latest
environment:
JIANG13_DB_TYPE: postgres
JIANG13_DB_DSN: postgres://forum:secret@postgres:5432/jiang13?sslmode=disable
volumes:
- jiang13-data:/data
depends_on: [postgres]
postgres:
image: postgres:16-alpine
environment:
POSTGRES_USER: forum
POSTGRES_PASSWORD: secret
POSTGRES_DB: jiang13
```
---
@@ -29,19 +109,19 @@
```text
data/
├── jiang13.db # SQLite 主库
├── install.lock # 安装完成锁(与 DB 引擎无关)
├── jiang13.db # 仅 SQLite 时的主库文件(含 sessions / forum_settings
├── jiang13.log # 运行日志
├── filter_words.txt # 敏感词
├── .jwt_secret # 自动生成的 JWT 密钥(勿提交仓库
├── filter_words.txt # 遗留:启动时可导入 settings新源以 DB 为准
├── .jwt_secret # App HMACCSRF 等;勿提交;非登录 JWT
├── .oidc_rsa.pem # 遗留:仅启用 OIDC 且从文件迁移时可能存在;新站优先 DB
├── uploads/
│ ├── avatars/
│ ├── posts/
│ └── site/ # 品牌资源等
└── jiang13_backup_*.db # 后台导出备份
│ └── site/
└── jiang13_backup_*.db # SQLite 一键备份(其它引擎请用库方工具)
```
开发时后端常与 `dist/data` 共用,避免 dev 与产物数据分裂(见根 README
---
## 3. 部署方式(现网)
@@ -55,8 +135,6 @@ data/
构建约定见 [`.cursor/rules/build-scripts.mdc`](../../.cursor/rules/build-scripts.mdc)Windows 用 `build.bat`,勿直接 `make` / `.\build.ps1`
容器常用环境变量与上表 `JIANG13_*` 一致。旧镜像权限问题:数据目录属主 uid 1000。
---
## 4. 存储后端
@@ -66,7 +144,7 @@ data/
| `local` | 文件落在 `data/uploads`URL 通常 `/uploads/...` |
| `s3` | S3 兼容endpoint、bucket、密钥、public_base_url、prefix、force_path_style |
`image_delivery``webp`(默认,经 `/media/thumb`)或 `original`上传始终可保留原图策略以实现为准。
`image_delivery``webp`(默认,经 `/media/thumb`)或 `original`
媒体索引表 `media` 供后台列表;启动时可后台 SyncMediaIndex。
@@ -82,20 +160,15 @@ data/
| meta description | 站点简介优先,否则标语;帖文则摘要 |
| meta keywords | 站点 keywords |
| canonical | 绝对 URL |
| og:type / site_name / locale / title / description / url / image | |
| twitter:card / title / description / image | |
| JSON-LD | 结构化数据(站点或 Article |
| robots | 个别页可 noindex以实现为准 |
### 现网额外机制(可废弃)
| og:* / twitter:* | |
| JSON-LD | 结构化数据 |
| robots | 个别页可 noindex |
| 机制 | 说明 |
|------|------|
| SPA 壳注入 | `embed_static`(仅 `main` 分支) 注入 title / branding JSON**无帖文 DOM** |
| 爬虫 HTML | User-Agent 命中时 [`seo_bot.go`](../../routers/api/seo_bot.go) 返回简易 HTML |
| robots.txt / sitemap.xml | 动态生成 |
重构验收:普通浏览器「查看网页源代码」应能看到帖文正文,而不仅是空 div + script
重构验收:普通浏览器「查看网页源代码」应能看到帖文正文。
---
@@ -103,43 +176,33 @@ data/
设置:`permalink_enabled``permalink_ext`(默认 `html`)。
规范路径示例:
- `/post/123.html`
- `/user/1.html`
- `/board/2.html`
- `/page/about.html`
路由应同时接受无后缀与有后缀形式。解析逻辑见 [`service/permalink.go`](../../services/permalink.go)。
---
## 7. 安全相关运维注意
| 项 | 说明 |
|----|------|
| JWT 密钥 | 生产必须固定且保密;勿提交 `.jwt_secret` |
| Cookie | `jiang13_token` HttpOnly;生产应 Secure + 合适 SameSite |
| 上传 | 类型/大小限制(头像 MB、帖图策略 |
| 敏感词 | 后台可改;影响发帖评论私信等 |
| OAuth 密钥 | 仅存 bcrypt 哈希;创建时明文只回显一次 |
| 备份 | 含用户哈希与私信,下载需管理员权限、传输加密 |
| HMAC / OIDC | 勿提交 `.jwt_secret`OIDC PEM 优先在 DB遗留 `.oidc_rsa.pem` 亦勿提交 |
| Cookie | `jiang13_session` HttpOnly + SameSite=Lax生产 HTTPS 下 Secure |
| 上传 | 类型/大小限制 |
| 敏感词 | `forum_settings.filter_words`,后台可改热更 |
| 备份 | SQLite 文件备份含哈希与私信PG/MySQL 用官方工具 |
---
## 8. 健康检查
`GET /health` → JSON `status`Docker / 负载均衡探活依赖此接口;实现应在 DB 不可用时返回非 200。
`GET /health` → JSON `status`。DB 不可用时非 200。
---
## 9. 从旧站迁数据建议
1. 导出 / 复制 `jiang13.db`或 dump 到新库并映射表
2. 复制 `uploads/``filter_words.txt`
3. 迁移 `forum_settings` 键值(或后台重新配置)
4. 会话:旧 JWT 密钥兼容一阶段,或强制全员重登
5. OIDC 客户端:`oauth_clients` 表 + 重新下发密钥(若无法迁移哈希
1. SQLite复制 `jiang13.db`或 dump 到 PG/MySQL 并映射表
2. 复制 `uploads/``.jwt_secret`HMAC 兼容);敏感词若仍在文件可启动导入
3. 迁移 `forum_settings` 或后台重配(含 `filter_words`
4. OIDC`oauth_clients` + settings 中 PEM或遗留 `.oidc_rsa.pem` 一次迁移)
5. 旧站 JWT Cookie 无效用户需重新登录opaque session
表语义以 [03-data-model.md](03-data-model.md) 为准。

View File

@@ -10,59 +10,77 @@
| 层 | 选择 |
|----|------|
| 公开页渲染 | Go `html/template` **真 SSR**(完整 HTML含帖文/列表 DOM |
| 渐进增强 | `web_src/` 少量 CSS/JS构建后嵌入 |
| 公开页渲染 | Go `html/template` **真 SSR** |
| 浏览器写操作 | `routers/web` HTML 表单 POST + CSRF + PRG |
| JSON `/api` | **仅机器客户端**OIDC 等);不服务已迁页面 UI |
| 渐进增强 | `web_src/``public/assets/``/ssr-assets/` |
| 发布 | 单二进制 + `go:embed` |
| 业务语义 | 仍以本目录 `01``07` 为准 |
| 不做 | React/Next 公开页 SPA用户与爬虫双轨 HTML |
| 业务语义 | `01``07`;冲突时改代码并回写规格 |
| 不做 | React SPA、爬虫/用户双轨 HTML、为旧 SPA 保留死代码 |
---
## 开发分支与对照
## 分支
| 分支 | 用途 |
|------|------|
| `main` | 现网 **React SPA** 对照,勿在此做破坏性 SSR 替换 |
| `rebuild/gitea-ssr` | **唯一** Gitea 式重构开发分支 |
对照运行:
```bash
git checkout main # 旧 SPA
# 或
git worktree add ../jiang13-spa main
```
| `main` | React SPA 对照git checkout / worktree |
| `rebuild/gitea-ssr` | 唯一重建分支 |
---
## 目录职责(本分支已落地)
## 目录
```text
cmd/jiang13/ # 入口
config/ # 配置
models/ # GORM 模型(原 model/
services/ # 业务逻辑(原 service/
routers/
setup.go # 路由总装(原 router/
web/ # HTML SSR
api/ # JSON API原 handler/
setup.go
install/ # INSTALL_LOCK 未置位时的安装向导
web/ # HTML + 表单
api/ # 精简机器接口health / OIDC / robots / sitemap / media
modules/
auth/ # JWT / 限流等(原 middleware/
webrender/ # 模板渲染
seo/ # PageMeta 等
templates/ # Go 模板embed
web_src/ # CSS/JS 源码
public/assets/ # 构建产物URL 前缀 `/ssr-assets/`
docs/rebuild-spec/ # 产品规格
.cursor/rules/ # AI 开发规则
webctx/ # Doer / CSRF / Flash / HTML / Redirect
auth/
webrender/
seo/
templates/
install.tmpl
post-install.tmpl
base/ home/ post/ shared/ status/ auth/ admin/
services/
web_src/ → public/assets/
```
**已删除(勿恢复)** `frontend/``embed_static/`。SPA 对照仅看 `main`
**已删除:** `frontend/``embed_static/``ServePublicSPA`、爬虫双轨 HTML、首注册变管理员 bootstrap、`app.ini`、浏览器 JWT Cookie 登录
**配置:** 引导 = CLI/Env`DB_*`);运行时 = `forum_settings` 热更;`.jwt_secret` = App HMACOIDC PEM 启用时进 settings。详见 [07-config-ops.md](07-config-ops.md)。
**会话:** Cookie `jiang13_session` → 表 `sessions`;可吊销。
**数据库:** GORM 方言 `sqlite`(默认)| `postgres` | `mysql`;连库失败不回落。
**后置:** Gitea 仓库同步(不启后台任务)。
---
## 渲染原则
## 安装
1. 用户访问已迁移路径时,「查看源代码」须可见内容 DOM而非空壳。
2. JSON `/api` 留给交互增强与后台;**不得**作为公开页首屏唯一数据来源。
3. 模板默认 HTML 转义;可信 HTML已消毒正文用明确的安全管道禁止随意 `| safe`
- 锁文件:`data/install.lock`
- 未安装:除 `/install``/ssr-assets/*``/health` 外一律重定向到安装向导
- 管理员仅由安装向导创建;已有用户数据启动时会自动补写锁
- 向导不选库:库由启动 Env 决定
---
## 渲染与交互原则
1. 已迁路径「查看源代码」须含内容 DOM。
2. UI 读写不依赖 `/api` 灌首屏或写操作(`/compose/upload` 为同站表单辅助 JSON带 CSRF
3. 模板默认转义;`safeHTML` 仅用于消毒 + 门控后正文/评论 HTML。
4. 未迁路径用 `status/pending.tmpl` 或 404不维护 SPA 占位语义。
5. 会话 Cookie`jiang13_session``SameSite=Lax`HTTPS 下 `Secure`
### 已迁路径(摘要)
公开写:`/install``/login``/logout``/register``/compose``/post/:id/edit`、帖详情评论/赞/藏。
Admin`/admin/dashboard``/admin/boards``/admin/moderation``/admin/settings`(品牌/限流/敏感词)。

View File

@@ -37,7 +37,7 @@
| 后端 | Go · Gin · GORM · SQLite |
| 前端 | React 18 SPA · TipTap · Tailwind · TanStack Virtual |
| 发布 | Vite 构建 → `go:embed` 打进单二进制 |
| 认证 | bcrypt + JWT Cookie`jiang13_token` |
| 认证 | bcrypt + DB opaque session Cookie`jiang13_session` |
演示站https://bbs.iioio.com/
@@ -64,7 +64,7 @@ flowchart LR
| 非真 SSR | 生产入口(`main``embed_static`)只注入 title / branding / Open Graph**不渲染帖文 DOM** | 刷新先出壳再灌数据,体验不如 SSR |
| 爬虫双轨 | [`routers/api/seo_bot.go`](../../routers/api/seo_bot.go) 对爬虫返回独立 HTML | 用户与爬虫看到的不是同一套渲染路径 |
| 无正式 migration | Schema 靠 GORM `AutoMigrate`[`models/db.go`](../../models/db.go) | 升级靠「加字段」,难做破坏性迁移与审计 |
| Cookie JWT | 无 session 表,密钥在 `data/.jwt_secret` | 可保留语义,实现可换成更好的会话方案 |
| Cookie JWT(旧) | 浏览器登录曾用 JWT Cookie | **本分支已改为** DB `sessions` + opaque Cookie `jiang13_session``.jwt_secret` 仅 CSRF/HMAC |
**新站目标**:用户首屏即可看到帖文 / 列表的服务端渲染SSRHTMLSEO meta 与正文同源。技术选型自定Next.js / Nuxt / Remix / 其它均可)。
@@ -77,13 +77,13 @@ flowchart LR
- [02-features.md](02-features.md) 中列出的功能能力
- [03-data-model.md](03-data-model.md) 中的实体关系与枚举含义(表名可改,语义对齐)
- [05-business-rules.md](05-business-rules.md) 中的数值与状态机(积分、审核、门控、悬赏分成等)
- 角色模型:游客 / 用户 / 认证用户(`verified` 免审)/ 管理员;首个注册用户为管理员
- 角色模型:游客 / 用户 / 认证用户(`verified` 免审)/ 管理员;**管理员仅由 `/install` 创建**(不再首注册变管理员
### 建议兼容(降低迁移成本)
- [04-api.md](04-api.md) 的 JSON 字段命名与路径形状(可做版本前缀,但旧字段名便于对照)
- Cookie 名 `jiang13_token` 或提供清晰的会话迁移方案
- 数据目录语义:`jiang13.db``uploads/``filter_words.txt`
- Cookie 名 `jiang13_session`opaque session id重建分支不做 `jiang13_token` 双读)
- 数据目录语义:`jiang13.db``uploads/`;敏感词在 `forum_settings.filter_words`(旧 `filter_words.txt` 可导入)
### 可以彻底改
@@ -102,7 +102,8 @@ flowchart LR
| SSR | 服务端渲染 | 首屏 HTML 含正文,非纯客户端壳 |
| SPA | 单页应用 | 当前前台实现形态 |
| OIDC | 开放身份连接 | 本站可作 Provider供 Gitea 等 SSO |
| JWT | JSON Web Token | 当前登录凭证,存 Cookie |
| JWT | JSON Web Token | OIDC 对外 `id_token`/`access_token` 仍用;**浏览器登录不用 JWT** |
| Opaque session | 不透明会话 | Cookie 只存随机 id服务端 `sessions` 表可吊销 |
| Feed | 信息流 | 首页 / 板块帖列表 |
| 门控 | Content gate | 登录可见 / 回复可见 / 积分可见区块 |
| 伪静态 | Permalink | 如 `/post/123.html` 的可选后缀 |