fix: 修复注册和上传素材在 await 前后的并发竞态;补充 SQLite 写法说明
Deploy / deploy (build, debian, 22) (push) Canceled after 0s

- 注册 / 新建用户 / 批量建用户:先算密码哈希,再同步地查重和插入,
  同名并发请求返回 400 而不是撞唯一约束变成 500
- 上传任务素材改用 INSERT ... ON CONFLICT DO UPDATE
- CLAUDE.md 补充同步阻塞、行内大文本列、索引、类型差异等注意事项
- 忽略根目录的 db.sqlite3(线上数据备份)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
xuyueandClaude Opus 5.5 committed 2026-10-26 18:36:54 +08:00
1 parent e6a3da14b0
commit ceba1c68f2
4 files changed
+17 -12

No files matched your search

+6 -3
View File
@@ -30,12 +30,15 @@ async function insertUser(u: {
classname?: string
email?: string
}) {
// 先算哈希(异步),再同步地查重 + 插入:中间没有 await,并发注册同名不会撞唯一约束变成 500
const password = await makePassword(u.password)
if (userExists(u.username)) throw new HttpError(400, "用户已存在")
const res = run(
`INSERT INTO account_user
(password, last_login, is_superuser, username, email, is_staff, is_active, date_joined, role, raw_password, classname)
VALUES ($password, NULL, 0, $username, $email, 0, 1, $now, $role, $raw, $classname)`,
{
password: await makePassword(u.password),
password,
username: u.username,
email: u.email ?? "",
now: now(),
@@ -59,7 +62,6 @@ router.post("/register", async (c) => {
password: z.string().min(6).max(20),
}),
)
if (userExists(payload.username)) throw new HttpError(400, "用户已存在")
await insertUser(payload)
return c.json({ message: "创建成功" })
})
@@ -148,6 +150,8 @@ router.post("/batch", async (c) => {
)
const ts = now()
tx(() => {
// 算哈希期间可能有人建了同名用户,插入前在同一个同步事务里再查一次
if (usernames.some(userExists)) throw new HttpError(400, "有些用户已经存在,创建失败")
for (const u of users) {
run(
`INSERT INTO account_user
@@ -172,7 +176,6 @@ router.post("/create", async (c) => {
classname: z.string().trim().max(50).default(""),
}),
)
if (userExists(payload.username)) throw new HttpError(400, "用户已存在")
const id = await insertUser(payload)
return c.json({ id, message: "创建成功" })
})
+6 -9
View File
@@ -96,15 +96,12 @@ for (const kind of ["challenge", "tutorial"] as const) {
`tasks/${task.task_type}/${task.display}/${validFilename(name)}`,
file,
)
if (existing) {
run(`UPDATE task_taskasset SET file = $file WHERE id = $id`, { file: stored, id: existing.id })
} else {
run(`INSERT INTO task_taskasset (name, file, task_id) VALUES ($name, $file, $task_id)`, {
name,
file: stored,
task_id: task.id,
})
}
// 上面 await 期间可能有同名上传先写入了,用 upsert 而不是按 existing 选 INSERT / UPDATE
run(
`INSERT INTO task_taskasset (name, file, task_id) VALUES ($name, $file, $task_id)
ON CONFLICT (task_id, name) DO UPDATE SET file = excluded.file`,
{ name, file: stored, task_id: task.id },
)
return c.json(assetOut({ name, file: stored }))
})