feat: opaque session、安装/发帖 SSR 与最小 Admin 后台
浏览器登录改为 DB sessions(可吊销);敏感词与 OIDC PEM 入 settings; 落地安装向导、注册发帖与 /admin 仪表盘/板块/审核/设置。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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) 供参考。
|
||||
|
||||
|
||||
@@ -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`)— SSR;SameSite=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、Logo、Favicon、OG 图、ICP
|
||||
- [x] 站点品牌文案:名称、标语、简介、keywords、Logo 字标、ICP — SSR(Logo/Favicon/OG 上传未迁)
|
||||
- [ ] SQLite 一键备份与下载
|
||||
|
||||
---
|
||||
|
||||
@@ -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 id(Cookie `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 | 默认 |
|
||||
|
||||
@@ -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`]((仅 main)frontend/src/api/client.ts)、[`frontend/src/api/types.ts`]((仅 main)frontend/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 |
|
||||
|
||||
@@ -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=Lax;HTTPS 时 Secure;TTL 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 等;窗口秒与次数来自 settings(Admin 可改基础四项+窗口)
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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(板块 Feed;id 可带伪静态后缀) |
|
||||
| `/post/:id` | PostDetailPage |
|
||||
| `/compose` | ComposePage 发帖 |
|
||||
| `/post/:id/edit` | ComposePage 编辑 |
|
||||
| `/profile` | ProfilePage(需登录) |
|
||||
| `/user/:id` | UserProfilePage |
|
||||
| `/favorites` | FavoritesPage |
|
||||
| `/projects` | ProjectsPage(Gitea 码桶) |
|
||||
| `/links` | LinksPage |
|
||||
| `/messages` | MessagesPage |
|
||||
| `/page/:slug` | SitePageView |
|
||||
| `*` | NotFoundPage |
|
||||
### 1.2 前台
|
||||
|
||||
重定向:`/boards` → `/admin/boards`。
|
||||
| 路径 | 说明 | SSR |
|
||||
|------|------|-----|
|
||||
| `/` | Feed | 已迁 |
|
||||
| `/board/:id` | 板块 Feed | 已迁 |
|
||||
| `/post/:id` | 帖详情 + 评论/赞/藏 | 已迁 |
|
||||
| `/compose` | 发帖(normal;Markdown 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` | 重定向前台登录 | 已迁 |
|
||||
|
||||
未迁(原 SPA):reports / users / badges / media / pages / links / SMTP / 完整 Limits 等。
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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 HMAC(CSRF 等;勿提交;非登录 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) 为准。
|
||||
|
||||
|
||||
@@ -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 HMAC;OIDC 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`(品牌/限流/敏感词)。
|
||||
@@ -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 |
|
||||
|
||||
**新站目标**:用户首屏即可看到帖文 / 列表的服务端渲染(SSR)HTML;SEO 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` 的可选后缀 |
|
||||
|
||||
Reference in New Issue
Block a user