chore: 去掉未上线前的旧兼容垫片,板块流收口到首页

鉴权只认 cookie、SEO 只走 Next、sort 与推荐对齐;删除 Bearer、Go sitemap、post_type 回填等冗余路径。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-16 05:04:47 +08:00
parent 0e61a14ea3
commit 833bcd33a1
33 changed files with 715 additions and 548 deletions

View File

@@ -13,6 +13,7 @@
4. 所有 SEO 关键内容必须在初始 HTML 中,不接受客户端注入
5. 权限逻辑只在 Go 端,前端不做权限判定
6. 遇到不确定的 Next.js 16 或 Go 1.27 特性,先查官方文档再生成代码
7. 用户未明确告知「已正式部署到生产环境」之前:禁止为旧版本/旧契约写兼容冗余(双写双读、fallback、过渡字段、旧 cookie/旧 API 垫片);直接改到位并删旧代码,保持源码干净规范
---
@@ -72,7 +73,7 @@
- 审核端点 7 个直挂 /api/admin/moderation/*;通过/拒绝发 approved/rejected 通知(拒绝需 reason,评论通过时补发 comment/reply 业务通知);ErrModerationForbidden→403、ErrNotPending→400
- 登录历史 login_logs:成功失败都记录(失败用户名可不存在 user_id=0);GET /api/admin/users/:id/login-logs;用户列表有 online(last_seen_at 5 分钟内绿点)/last_login_ip/last_login_at 列
- 角色变更事务内 invalidateUserSessions(token_version+1 + 撤销 refresh tokens),仅板块授权变化不下线;board_admin 授权必须 ≥1 个有效板块否则 400
- 前端:lib/roles.ts(ROLE_META:owner=Crown/--gold,super=BadgeCheck,admin=ShieldCheck,board=Shield,user=Users;lucide 无 ShieldStar)、RoleBadge 组件(truncate 用于窄列+板块名);/admin/moderation 审核队列页(30s 角标轮询仅入口可见时);Forbidden.tsx 子页守卫视图(HTTP 仍 200);用户管理 /admin/users 首列收窄 grid-cols-[minmax(0,1fr)_78px_...]
- 前端:lib/roles.ts(ROLE_META:owner=Crown/--gold,super=BadgeCheck,admin=ShieldCheck,board=Shield,user=Users;lucide 无 ShieldStar)、RoleBadge 组件(truncate 用于窄列+板块名);/admin/moderation 审核队列页(待审角标 WS `moderation:changed` + 回前台 HTTP 校准,无定时轮询);Forbidden.tsx 子页守卫视图(HTTP 仍 200);用户管理 /admin/users 首列收窄 grid-cols-[minmax(0,1fr)_78px_...]
- 新用户发帖有 24h 冷静期(注册接口无此限制,API 回归需 SQL 回退 created_at);CreatePostRequest.tags 是字符串不是数组
---
@@ -82,12 +83,12 @@
**不使用 localStorage 存储 token**(XSS 可窃取),采用三 Cookie + Token Version 方案:
- `j13_token`:access token JWT,`HttpOnly + SameSite=Lax`,**15 分钟**过期,JS 不可读
- `j13_refresh`:refresh token(DB 存储),`HttpOnly + SameSite=Lax`,`Path=/`(2026-09 起从 /api/auth 放宽,供 Next middleware 在页面请求中续期;clearAuthCookies 同时清 / 与 /api/auth 兼容旧 cookie),**7 天**过期
- `j13_refresh`:refresh token(DB 存储),`HttpOnly + SameSite=Lax`,`Path=/`(供 Next middleware 在页面请求中续期),**7 天**过期
- `j13_csrf`:CSRF token,非 HttpOnly(JS 可读),`SameSite=Lax`,7 天过期
**后端**(`service/auth.go`、`middleware/auth.go`、`middleware/csrf.go`、`middleware/security.go`):
- `User.TokenVersion` 字段:改密码/封禁/管理员强制下线时递增,使所有 JWT 立即失效
- `parseToken` 优先从 `j13_token` cookie 读取,回退到 Authorization header
- `parseToken` 只从 `j13_token` cookie 读取(不接受 Authorization Bearer)
- `ValidateClaims` 每请求查 DB:校验 `token_version` 匹配 + `banned` 状态(不依赖 JWT 缓存值)
- `CSRFMiddleware` 对非 GET 方法校验 `X-CSRF-Token` header 与 `j13_csrf` cookie 一致
- 需登录 API 组挂载顺序:`RequireAuth → CSRFMiddleware`(未登录返回 401 而非 403)
@@ -124,7 +125,7 @@
- 三期(已完成 2026-09-15):帖子流「有新内容」提示 —— `feed:changed` 全员广播(staff 直发/审核通过帖评),首页与板块页 tip,点击 `router.refresh()`;不做自动插行/游客轮询
- 端点 `GET /api/ws`(gorilla/websocket v1.5.3,挂在 r.Static 之后、pubAPI 之前,不走任何中间件组,鉴权在处理器内):cookie j13_token 优先、Bearer 回退,ParseToken+ValidateClaims 双校验失败 401 JSON;checkWSOrigin:无 Origin 放行,dev 允许 localhost/127.0.0.1 任意端口,生产要求 Origin host==Host
- 端点 `GET /api/ws`(gorilla/websocket v1.5.3,挂在 r.Static 之后、pubAPI 之前,不走任何中间件组,鉴权在处理器内):只读 cookie j13_token,ParseToken+ValidateClaims 双校验失败 401 JSON;checkWSOrigin:**必须带 Origin**,dev 允许 localhost/127.0.0.1 任意端口(页面 :3000 直连 :3001),生产要求 Origin host==Host(须反代同源)
- 代码:`backend/realtime/hub.go`(Hub:clients/rooms/userConnCnt 多标签页计数;register 0→1 才上线广播,最后一条连接断开才下线广播;房间 staff、个人房 user:{id};dispatch 满缓冲 forceClose 慢消费者;OnlineUserIDs 快照)、`backend/realtime/client.go`(writePump 唯一写连接:30s 协议 Ping、70s pongWait、send buf 32;readPump 只认 {"type":"ping"} 应用帧,每次心跳复查 ValidateClaims+TouchLastSeen(60s SQL 限频),失败写 **关闭码 4401**)、`handler/realtime.go`;Handlers.Hub 在 router.NewHub()
- 信封单层 JSON `{type,data}`(encoding/json/v2);事件:`hello`(staff 的 data 带 online_user_ids 在线快照)、`pong`、`settings:changed`{accent}(BroadcastAll)、`presence:update`{user_id,online}(只 BroadcastRoom staff);UpdateSettings 保存后广播,accent 为归一化小写 trim 值
- **在线判定新口径:有活跃 WS 即在线**(取代 last_seen_at 5 分钟启发式,更精确;用户管理页绿点由 presence 推送+hello 快照 reconcile,SSR initial.online 仍走旧口径)
@@ -219,6 +220,7 @@
---
## Hard Constraints
- 未正式上线前禁止旧兼容冗余:用户明确说「已正式部署到生产环境」之前,改契约直接改到位并删旧路径,禁止双写/双读/fallback/过渡字段等垫片
- Main comments count as floors; sub-comments do not count as floors
- Comment depth is limited to 10 levels
- 帖子详情页三栏宽度与首页统一,容器同为 max-w-[1440px]、间距同为 gap-6 / xl:gap-7
@@ -248,7 +250,7 @@
- 管理员侧边栏导航在 `app/admin/AdminNav.tsx` 中维护,新增模块通过修改 `NAV_ITEMS` 实现
- 用户管理列表支持分页(默认 20,上限 50)、用户名/昵称/邮箱 ILIKE 搜索、角色与状态筛选
- 用户管理操作后自动重新拉取数据以保证计数一致性;查询状态同步到 URL 参数(`?q=&filter=&page=`)
- 管理员 API 路由需依次经过 RequireAuth → CSRF → RequireAdmin 中间件
- 管理员 API 路由经 RequireStaff → CSRF,功能点再由 RequirePerm 放行
- 用户管理列表展示自己账号时仅显示"当前账号"标识,无操作按钮
- 危险操作(如封禁、降级)需显示明确的 confirm 提示文案
- 移动端用户管理列表使用卡片布局,桌面端使用七列表格布局

View File

@@ -15,6 +15,7 @@ alwaysApply: true
## WebSocket(全站一条连接)
- `GET /api/ws`;dev 直连 `ws://hostname:3001/api/ws`(Next rewrite 不透传 Upgrade)
- Origin **必须存在**;dev 允许 localhost/127.0.0.1 任意端口;生产要求 Origin host == 请求 Host(页面与 `/api/ws` 须同一 Host)
- 事件:`hello` / `pong` / `settings:changed` / `presence:update` / `chat:*` / `notification:new` / `feed:changed` / `moderation:changed`
- 消息铁律:**先落库再 WS 广播**;WS 不可靠投递;未读靠 HTTP 对账
- 角标优先推送:通知落库统一 `OnNotifyNew`;待审变化推 staff `moderation:changed`;**禁止**再恢复 30s 轮询未读/待审

View File

@@ -15,6 +15,7 @@ alwaysApply: true
4. 所有 SEO 关键内容必须在初始 HTML 中,不接受客户端注入
5. 权限逻辑只在 Go 端,前端不做权限判定
6. 遇到不确定的 Next.js 16 或 Go 1.27 特性,先查官方文档再生成代码
7. 用户未明确告知「已正式部署到生产环境」之前:禁止为旧版本/旧契约写兼容冗余(双写双读、fallback、过渡字段、旧 cookie/旧 API 垫片);直接改到位并删旧代码,保持源码干净规范
## 技术栈

View File

@@ -7,6 +7,7 @@ alwaysApply: true
## Hard Constraints
- **未正式上线前禁止旧兼容冗余**:在用户明确告知「已正式部署到生产环境」之前,改 API / cookie / schema / 前端契约时直接改到位并删旧路径;禁止双写、双读、fallback、过渡字段、兼容旧 cookie Path、兼容旧客户端等垫片。源码保持干净、规范;生产上线后再谈兼容。
- 主评论计楼层,子评论不计;评论深度上限 10
- 详情页三栏宽度与首页统一:`max-w-[1440px]`、`gap-6` / `xl:gap-7`
- 禁止操作自己的管理员账号;至少保留一名未封禁管理员;角色白名单