 xuyueandClaude Opus 5.5
|
64994b9499
|
perf(判题结果): 全班一起交、判题排队时不再每人每 2 秒问一次结果
Deploy / deploy (push) Canceled after 0s
原来交上去 5 秒没结果就每 2 秒(流程图 3 秒)拉一次,分不清 WebSocket 断了还是队列长:
50 人一起交、队列排一分钟时每秒二十几个请求,全在和判题抢那两个核。
- 收到这一条的任何推送(服务端订阅时就回一帧当前状态)就认定 WebSocket 是通的,
轮询退到 20 秒一次,只防推送丢失;连接断了才回快档,2 秒起按 1.5 倍退到 10 秒
- 拉失败不再永久停掉(掉线那几秒拉失败是常事),连着失败 5 次才放弃
- 页面在后台时不拉;比赛题目页、榜单、班级页的定时刷新也一样,切回来立刻补一次
- 代码判题和流程图评分共用 shared/composables/fallbackPoll.ts
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
2026-10-16 14:37:08 +08:00 |
|
 xuyueandClaude Opus 5.5
|
e7b95d211d
|
fix(题目): 修掉题目页重构审查查出的问题——提交途中换题会把结果算到新题上、运行例子把对的菱形判错、点评完「下一题」不跳等
题目页重构之后按「功能对照 / 状态 / 结果页 / 布局与后端」四路审查了一遍,查出的问题一起修:
提交与换题
- 提交、画流程图途中换题:A 题格式化好的代码会盖掉 B 的草稿,A 判对了把 B 标成已解决、
弹 B 的点评、题单里跳回题单。现在每个请求回来先核对题目,轮询和推送回来的也核对提交编号
- 画着流程图换到不能画的题,语言退到题目的第一门、还被记成偏好;现在回到学生习惯的那门
- 强制点评评完点「继续 / 下一题」,学生之前在看「题目」页签的话会停在原题
- 离开题目页之后判题、流程图的 WebSocket 一直连着,现在闲 15 分钟断开
运行例子
- 输出开头的空格被削掉,打印菱形这类题对的代码也显示「没对上」
- C 的编译警告拼进了「你的输出」(后台生成测试点也会写进 .out)
- Python 的语法错误(中文标点)被说成「下标有没有越界、除数是不是 0」
- 比赛里仍然把空格画成「·」、把不同的地方标红
入口与协作
- 比赛里不给「课堂统计」、不绑协作:房间和统计都按题号找,比赛题号和公开题撞号
- 「看他交过什么」按整名匹配,不再混进学号相近的同学
- 协作中点上下文条、相似题、下一题开新标签,协作不断
- 题单入口给管理员的「⋯」里留「本题提交」
结果与题面
- 「去自己输入拿 51 这些数试试」取到了题面 HTML 里的颜色值;「题目原话」露出 "
- 流程图 A 级的学生标题下还写着「还没交过」
- 「我的提交」上方补回「答案错误 × 3」这样的计数
- 流程图历次评分能点开看那一版的图;读分数失败不再说「还没交过流程图」;手机上能看流程图点评
- 「结果」页签有新结果没看时加粗上色,看过就恢复
- 空画布拖进第一个节点画布会跳一下;画布底下空着一条
- 比赛没开始写成「已结束」、倒计时超过一个月算错;分隔条拖到头工具栏被挤坏;题单里的题目页有备案页脚
- 流程图历次分数只读需要的几列(一个学生一道题 23 次提交:96 kB → 2 kB)
- 比赛密码、比赛没开始等几条学生看得到的英文报错换成中文
设计文档照实际做法更新了第 5.5、5.6、6、8、9、10、11 节。
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
2026-09-29 00:07:16 +08:00 |
|
 xuyueandClaude Opus 5.5
|
26e22e2542
|
refactor(题目): 提交状态挪进 store——手机上切页签、桌面切分栏,判题结果和点评不再丢
提交按钮那个组件(SubmitCode)在编辑器的工具栏里,而提交的全部状态和判完之后的
反应都长在它身上:手机上交完切到「描述」页签(Naive 的页签切走即卸载)、桌面切到
「题目 / 自测」模式,编辑器一卸载,判题结果、AI 提示、失败计数、通过后的点评、
题单里的回题单全丢了。
- oj/store/submission.ts:这次提交、判题跟踪、语法检查报错、结果面板开合、冷却、
AI 提示(useSubmissionHint)
- SubmissionEffects.vue:挂在题目页这一层,失败计数、标成已解决、烟花、强制点评
(连同「先点评再换题」的导航拦截)、回题单
- SubmitCode 只剩按钮和结果面板
- WebSocket 通道和判题监控的收尾改成 onScopeDispose:onUnmounted 在 store 里挂不上
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
2026-09-28 13:36:35 +08:00 |
|
 xuyueandClaude Opus 5.5
|
a63bf6ea07
|
feat(题目): 通过之后直接给出这节课的下一题;在题目页里换题时不再带着上一道题的结果和评价弹窗
Deploy / deploy (push) Canceled after 0s
72% 的提交一次就通过,每次通过之后学生都要回首页或再问一遍题号去找下一道。
- 结果面板在通过时显示 LessonNext:取 /me/class-activity(老师布置的优先,否则推断),
给出排在当前题后面、还没通过的第一道,后面都做完了再从头找;全做完了显示「都做完了」
并指向题单。当前题不在列表里、比赛、题单入口都不显示
- 评价弹窗关掉后重新打开结果面板:点弹窗时面板会被当成点了外面收起来,「下一题」就在面板里
- 换题(同一组件只换路由参数,有了题号直达和下一题之后成了常规操作)时收掉上一道题的
结果面板、语法错误、还在跟的提交(新增 useSubmissionMonitor.reset),并取消通过后
那两个 1.5 秒的定时器——否则会给还没做的新题弹评价,上一道判完时还会把新题标成已解决
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
2026-09-27 22:58:19 +08:00 |
|
xuyue
|
d25b5dbe6d
|
update
Deploy / deploy (push) Canceled after 0s
|
2026-09-27 19:36:28 +08:00 |
|
 xuyueandClaude Opus 5
|
64facc5701
|
fix(WebSocket): 断线不重连、评分结果会丢、登出后连接还活着
Deploy / deploy (push) Has been cancelled
排查 WS 这一块时发现的一批问题,多数是「机制写了但从没生效过」。
## 重连
`disconnect()` 里 `enableAutoReconnect = false`,而 `connect()` 从不改回 true ——
登出再登录后,这条连接就永远失去了自动重连能力(configUpdate 那条 watch 上尤其
明显)。改成用 `closedByUser` 表达「用户主动断开」的意图,和 `enableAutoReconnect`
这个**配置**分开。
`scheduleDisconnect` 的回调里断完紧接着一句 `enableAutoReconnect = true`,而
close 是异步的 —— 等 onclose 跑到时标志已经翻回来了,1 秒后又自动连上。那个
「15 分钟空闲省资源」从来没真正断开过。现在只断开,不做事后翻转。
重连的 setTimeout 没存句柄,组件卸载后照样触发 `connect()`,在已销毁的组件上
又建一条连接。现在 `disconnect()` 里 clearTimeout。
退避从「线性 ×5 次」改成「指数 + 抖动、30 秒封顶、次数不封顶」。原来 1+2+3+4+5
只有 15 秒,后端 deploy 重启一次就超了,之后这条连接死到用户刷新为止。抖动是
为了避免一个班几十台机器在同一毫秒一起冲回刚起来的后端。另挂 online /
visibilitychange,网络恢复或切回标签页立刻重连,不必等退避走完。
所有 socket 回调改成闭包住局部 ws 并在入口 `if (ws !== this.ws) return`,旧连接
迟到的 onclose 不再污染新连接的状态 —— 也是让 `disconnect()` 能被 onclose 识别
出来的关键。
## 订阅重放
`pendingSubmissionId` 一发送成功就清空,它只解决了「还没连上就 subscribe」,
**没解决断线重连**。而真正会丢结果的恰恰是后者:服务端收到 subscribe 会回一份
当前状态,掉线期间错过的推送就是靠这次重放补回来的;不重新订阅,重连后只收得到
「将来」的事件,可结果已经是过去式了。改成订阅意图保留到显式 `unsubscribe()`,
每次 onConnected 都重发。
这套逻辑原来只有 SubmissionWebSocket 有,FlowchartWebSocket 是 send 失败打一行
日志了事 —— socket 一掉,那次评分结果就再也回不来,按钮一直转圈。提成公共基类
SubscribingWebSocket,两条通道共用。
流程图另加轮询兜底:提交后 5 秒 WS 还没出结果就每 3 秒拉一次,读 status 2/3
结算,3 分钟上限。判题那边一直有兜底,流程图这边没有,而 Redis pub/sub 是发完
不管的,worker 推的那一刻连接不在就永远丢了。
`useSubmissionMonitor` 里 `watch(wsStatus, ..., { immediate: true })` 的回调在
watch() **返回之前**就同步跑了,已经连着时 `unwatch` 还是 null,if 不成立,
watcher 永远停不掉:每提交一次泄漏一个,往后每次重连它们都会把各自那个早就判完
的旧 submissionId 重新订阅一遍。整块删掉,直接 subscribe —— 基类已经管了时序。
WS handler 原来不校验 submissionId。学生同时开着几道题的页面时,每条连接都订在
同一个用户 topic 上,别的页面的评分结果会被当成自己的。
## 会话
握手时校验过一次会话就再也不管了,这条连接却能挂几个小时:用户在别的标签页
登出、或者会话本身到期,旧 socket 照样收推送。加一条 60 秒一轮的巡检,用
Redis EXPIRE 一条命令同时完成「判断存在」和「续期」(续期是必要的:只开着页面
挂 WS 的人一次 HTTP 请求都不发,不该被算成不活跃踢下线)。Redis 抛错时整轮
放弃,绝不因为一次抖动把全班踢下线。
禁用只改数据库的 isDisabled 列、不动 Redis 里的会话,巡检永远发现不了。加
`session:revoked` 频道主动通知,**两种作用域不能混**:
{ token } 用户登出。只断这一张会话 —— 同一个人在别的设备上是另一张
会话,按 userId 广播会把他手机上的登录一起踢掉
{ userId } 账号被禁用。所有设备都得断
先发一帧 force_logout 再隔 100ms 断开。只断不发的话前端只看到一次普通掉线,
会照常重连、页面上还显示着登录态。token 不进帧里 —— 那是 httpOnly cookie 的值,
推到 WS 上就等于交给了 JS,匹配全在服务端做。
前端在协议层拦截 force_logout(和 pong 一样,不下发给业务 handler),并主动
disconnect —— 否则会一路 401 重连到退避上限,正是这机制要消掉的浪费。表现刻意
和 utils/api.ts 里 account-disabled / login-required 两支保持一致:同一件事从
HTTP 和 WS 两条路进来,学生看到的不该有两个样子。
## 开销与安全
`void handleMessage(...)` 是裸的,里面有两次 DB 查询和一个会抛的 schema.parse,
库抖一下就是一个 unhandled rejection(隔壁 bridgeSubmissionEvents 两处都接住了,
只有这里漏了)。
ping 提到用户查询之前。原来的顺序是「先查 user 再看消息类型」,每个客户端每
30 秒都要为一次心跳打一趟数据库。禁用用户不会因此漏网:推送路径上 bridge 会查,
subscribe 这条真正读数据的路径下面照样查。
bridge 两条 per-user 通道都先看 `server.subscriberCount(topic)`,没人订阅就别
查库了 —— 判题高峰期绝大多数事件的目标用户此刻并不在线。
flowchart 评分失败原来把 error.message 原样推给学生、前端直接弹出来,AI provider
的地址和内部报错就这么进了浏览器。改成真实原因写服务端日志。
加每连接令牌桶(20 突发 + 每秒回填 2)。一条 subscribe 在服务端是一到两次数据库
查询,一个学生开着一条 socket 狂发就能压住库。正常流量离阈值几十倍远。
升级时校验 Origin。会话 cookie 是 SameSite=Lax、WS 握手不是导航,跨站页面本来就
带不上 cookie,所以这是防御纵深不是唯一防线。同源放行;本机开发(Vite 5173 →
API 3000)自动放行,且只在两边都是本机时成立 —— 生产环境 url.hostname 是正式
域名,这条永远不触发;跨域部署走 ALLOWED_WS_ORIGINS。不发 Origin 的一律放行:
真正的攻击面是带着受害者 cookie 的浏览器页面,而浏览器一定会带 Origin。
顺带:useConfigWebSocket 的 handler 从 onMounted 挪到同步注册(调用方在 setup
阶段就 connect() 了),删掉每条消息打完整内容的 console.log 和死字段
ws.data.username。
## 没动的
题目页上并没有两条 /ws/submissions —— Form.vue 里 SubmitFlowchart 和 SubmitCode
是 v-if/v-else,互斥。学生实际是 2 条连接:全站一条 /ws/config + 一条
/ws/submissions,正常,不必合并。
## 验证
55 个用例,分六组打桩跑(假 WebSocket + 假计时器;会话/限流/吊销三组对着真
Redis):
重连语义 7 断开后不再自我复活、卸载后计时器已取消、退避封顶、
online 立即重连、旧 onclose 不污染新连接
订阅重放 9 未就绪时补发、重连后重新订阅、unsubscribe 后不再重放
会话巡检 8 EXPIRE 三态、只断失效的、同 token 只查一次、
Redis 抖动时一个都不踢
Origin/限流 13 跨站与跨端口拒绝、生产不因 localhost 开后门、
突发额度、按时间回填
强制登出 13 登出只断同 token(别的设备不受牵连)、禁用断所有设备、
巡检先通知再断
前端登出 5 两支表现、收到后不再重连、两条通道只处理一次
前两组做了改动前/后对比,老代码该挂的都挂了 —— 「重连后自动重新订阅」正是这么
跑出来的,此前我以为 pendingSubmissionId 已经覆盖了这种情况。
apps/api 的 tsc 和 apps/web 的 vue-tsc 都干净,仓库既有测试照常通过。
**SubmitFlowchart.vue 的轮询兜底未经运行时验证** —— 在 SFC 内,没搭组件挂载
环境,只过了类型检查和人工核对。要验的话,停掉 worker 提交一次流程图,看 5 秒
后是否转入轮询、3 分钟后是否给出超时提示。
这批改动动了 WS 的行为面(限流会断连接、Origin 会拒绝、巡检会踢会话),上线前
建议手测:提交代码看判题、切流程图看评分、后台改配置看全站生效、开两个标签页
在一个里登出、禁用一个在线学生看另一端反应。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2026-08-27 05:07:26 -06:00 |
|
 xuyueandClaude Opus 5
|
6ac458e6f3
|
refactor(WebSocket): 判题/流程图推送改用驼峰,去掉没人读的三个字段
submission_id / time_cost / memory_cost / err_info 是 OJ2 两端自己定的线上格式,
没有第三方消费(/ws/submissions 只有 apps/web 一个客户端),没理由留着 snake。
flowchart 那条更别扭:同一个对象里 submission_id 是 snake、criteriaDetails 是驼峰。
三个字段直接删掉而不是改名 —— 它们是从 statistic_info 原样抄出来的一份,前端
一处都没读过(useSubmissionMonitor 只用 submissionId / result / status)。耗时和
错误信息在提交详情里本来就有,判完了去拉一次就是,不必让推送顺带背一份 JSONB
的形状。score 保留,它不涉及大小写。
**没动的都是有外部约束的**,别顺手一起改:
- 发给判题沙箱的请求体(language_config / max_cpu_time / max_memory /
test_case_id / io_mode)和它回的字段(cpu_time / memory / test_case)——
那是沙箱的 API,不是我们的
- 测试点 info 文件的键,沙箱直接读那个文件
- submission.statistic_info 里的 time_cost / err_info / ast_results ——
判题机按这套写,12 万条历史提交就是这形状
- acm_problems_status、progress_detail 这些存量 JSONB
- AST 规则键(for_loop)、成就指标(accepted_count)、reaction 语义键 ——
那是词汇表标识符不是字段名,for_loop 还要映射到 tree-sitter 的 while_statement
验证:起 dev 栈(api + worker + 沙箱),学生账号真提一次代码,抓 /ws/submissions
的帧:
{"type":"submission_update","submissionId":"bf13b7f0…","result":6,"status":"pending"}
{"type":"submission_update","submissionId":"bf13b7f0…","result":7,"status":"judging"}
{"type":"submission_update","submissionId":"bf13b7f0…","result":-2,"status":"finished","score":0}
subscribe 帧也换成 submissionId 并被接受(否则会回一个 error 帧,没有)。
流程图那条路径要 AI 评分才跑得起来,本机没配,只做了类型检查。
前后端同一个 docker 栈一起构建部署,没有版本错配窗口。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2026-08-27 00:48:54 -06:00 |
|
 xuyueandClaude Opus 5
|
5c772319e9
|
refactor(契约): 公告两侧形状分开,判题状态码从契约派生
Deploy / deploy (push) Has been cancelled
## 公告:一个 schema 兼两种形态,兼出两处谎
前端 utils/types.ts 只手抄了**后台那份**公告形状,oj 侧也拿它当类型用。可
apps/api/src/routes/content.ts 的 `/announcements` 列表既不下发 content 也不
下发 visible —— 于是类型声称列表行有这两个键、运行时都是 undefined。今天没炸
只是因为组件恰好没读(正文是点开后另拉一次详情)。
契约那边则是另一头:announcementSchema 把 content 写成 `.optional()`,让一个
schema 同时兼列表和详情。代价是**详情**拿到的 content 类型也成了
`string | undefined`,组件只能 ?? 兜底。
现在两边都按后台侧早就在用的套路拆开:content 必填,列表用
`.omit({ content: true })` 派生。四个类型各归各位:
oj 列表 AnnouncementListItem 无 content、无 visible
oj 详情 Announcement 有 content、无 visible
后台列表 AdminAnnouncementListItem 无 content、有 visible
后台详情 AdminAnnouncement 有 content、有 visible
前端手抄的 Announcement / AnnouncementEdit / AnnouncementListItem 全部删掉,
AnnouncementEdit 改成从请求体派生(`CreateAnnouncementRequest & { id: number }`)。
后端 content.ts 的列表端点跟着换成 announcementListItemSchema —— 它本来就没
传 content,输出一字不变。
## SUBMISSION_RESULT 不再手抄
`-2 | -1 | 0 | ... | 10` 这 11 个码是从后端抄的,改成 `JudgeStatus | 9`:
后端那部分从契约派生,9 是前端本地的「正在提交」伪状态(后端永远不下发,
所以契约里没有,见 constants.ts 的 SubmissionStatus.submitting)。
这样 CLAUDE.md 说的「三处同步」才真的有人守:**实测过**,往
judgeStatusSchema 加一个 `z.literal(11)`,constants.ts 的 JUDGE_STATUS 立刻
报 TS2741 缺 '11' 的映射,加不上标签就编译不过。
顺带 useSubmissionMonitor.ts 里两处裸 `9`(各带一句 `// 9 = submitting`)
换成 SubmissionStatus.submitting,注释就不用写了。
## 顺手
TestcaseUploadedReturns 这个改名 re-export 去掉,直接用契约的
UploadTestCaseResponse(全仓 2 处引用)。
**没动 transforms.ts。** 之前把它记成「旧前端字段名的化石」,看下来判断有误:
filterResult 里 difficulty 要查 DIFFICULTY 映射表转中文、rate 要 getACRate 算、
status 要把 myStatus 翻成 passed/failed/not_test —— 是实打实的视图模型,不是
单纯改名。改它只会把计算逻辑挪个地方。
## 验证
tsc(apps/api) 0 error、check:routes 168 条无遮蔽、vue-tsc 0 error、vite build
通过。因为动了后端响应 schema,起服务实跑了公告的四条路径:
- curl 三个端点逐个核对键集:oj 列表无 content/visible 且只出可见的那条、
oj 详情有 content、后台列表有 visible 无 content 且两条都在。
- 浏览器里 oj 公告列表渲染正常、点开正文能出来;后台列表两行齐全;
编辑页表单载入正确;改标题保存 PUT 200,库里 title 变了、visible/top 没被
带歪,保存后跳回列表并重新拉取。
(agent-browser 的 `find text 保存 click` 打不到 naive-ui 的按钮 handler ——
不发请求也不报错,一开始误判成保存坏了。改用 DOM 上直接 .click() 就正常,
是自动化的坑,不是应用的问题。)
冒烟用的两条公告已删干净。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2026-08-25 22:54:01 -06:00 |
|
 xuyueandClaude Opus 5
|
0b7b08f2cc
|
refactor(前端): 去掉 { error, data } 信封,api2 改名 api
Deploy / deploy (push) Has been cancelled
信封是 Django 时代的形状:拦截器手工造一个**恒为 null** 的 error 字段,再把
真正的载荷塞进 data。后端 http.ts 的 success 其实只返回 { data },那个 error
从头到尾没人用 —— 全站成功路径读 res.error 的只有 admin/api.ts 的
resetPassword 一处,而它自己就是个把信封拆开再重新包一遍的 shim。
代价是每个调用点都要 .data 一次:47 个组件、3 个 api 层文件、200 多处。
现在拦截器直接返回 response.data.data,ApiResponse<T> 退化成 T,文件末尾那句
`as unknown as Api2Client` 的类型谎言也少了一层。失败路径不动,仍然 reject
`{ error: 错误码, data: 文案 }` —— 和成功路径不对称是故意的,成功没有错误码
可言,接口注释里写清楚了。
顺带把 api2 改回 api:utils/ 下早就没有 api.ts 了,"2" 是迁移期用来和旧
client 区分的,现在只剩下让人多想一秒的作用。
## 怎么改的
**没有全局 sed。** 先把客户端的返回类型从 Promise<ApiResponse<T>> 改成
Promise<T>,让 vue-tsc 把每一处报出来(210 条),再按它给的 file:line:col
精确删 `.data`(192 处),剩下的手工处理:
- 6 处 `const { data } = await ...` 解构 → `const data = await ...`
- 3 个 api 层函数(getProfile / getProblem / getSubmission)自己手工造信封,
改成直接返回值;getProfile 的返回类型跟着从 ApiResponse<Profile|null>
变成 Profile|null
**类型检查抓不到的,人工把剩下的每一处 `.data` 过了一遍** —— 载荷本身带
data 字段、或者载荷是索引签名时,`res.data` 照样过类型。这一遍捞出三条真 bug:
- `getTutorialList` 的载荷是 `{ [key: string]: TutorialListItem[] }`(按
python / c 分组)。索引签名让 `res.data` 编译通过、运行时是 undefined ——
改完信封之后教程列表会**两个 tab 全空且不报错**。实跑确认过修好了。
- `createExercise` / `updateExercise` 返回 `res.data`,而 Exercise 自己有
data 字段(练习内容)。两个调用方都不看返回值,所以类型和运行时都不响。
- `getSimilarProblems` 的 `.then(r => ({ ...r, data: r.data.map(...) }))`
删掉 .data 之后变成往对象里摊一个数组,能跑但形状是错的。
另外两处是**对的**,加了注释免得下次被"顺手清理"掉:
StatisticsPanel 的 `res.data` 是契约 submissionStatisticsSchema 自己的 data
字段(每个学生一行);download.ts 是独立 axios 实例,`res.data` 是 axios 的
响应体(zip 二进制,不走信封)。
## 验证
tsc(apps/api) 0 error、check:routes 168 条无遮蔽、vue-tsc 0 error、vite build
通过。**因为这改动碰的是每一个请求,静态检查不够,起了全套服务用浏览器实跑:**
- oj 侧 12 个页面 + 后台 13 个页面逐个打开,断言没有重定向、console 无报错。
- 关键页面进一步断言渲染出了真数据(后台用户列表 3 行、题目列表 10 行、
站点配置表单三个输入框有值、教程列表分组正确)。
- 三条写路径实打:重置密码(库里 student123 → 531554,表格当场刷新)、
公告可见性开关(走 getAnnouncement + editAnnouncement,就是手改解构那处,
库里 visible t → f)、提交代码(POST → 判题机真跑出 -2 → 提交列表和详情页
都正确渲染状态、语言、代码)。
- /rank 有一条 `{error: "class-missing"}` 的未捕获 reject,stash 掉本次改动
复现同样报错,**是既有问题**,不在本次范围内。
本地 dev 库为了打通后台测试改了三处,都只影响本机:devadmin 补了 email 和
user_profile 行(原来缺这两样,getProfile 报 profile-not-found,AUTHED 存不
进去,所有 /admin 路由被守卫弹回首页)、密码重置成 devpass123。冒烟用的教程/
公告/提交三条测试数据已删干净,题目和用户的提交计数也回滚了。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2026-08-25 22:42:20 -06:00 |
|
xuyue
|
ae1fb329b5
|
feat(阶段1): 搬入 ojnext 为 apps/web,未改业务代码
|
2026-08-06 21:18:16 -06:00 |
|