名称: bknd-crud-delete 描述: 通过SDK或REST API从Bknd实体中删除记录。涵盖deleteOne、deleteMany、软删除模式、级联考虑、响应处理和常见模式。
CRUD 删除
使用SDK或REST API从您的Bknd数据库中删除记录。
先决条件
- Bknd项目正在运行(本地或部署)
- 实体存在要删除的记录
- SDK已配置或API端点已知
- 记录ID或过滤条件已知
- 了解任何关系/依赖项
何时使用UI模式
- 快速一次性删除
- 手动数据清理
- 可视化验证要删除的内容
UI步骤: 管理面板 > 数据 > 选择实体 > 点击记录 > 删除按钮 > 确认
何时使用代码模式
- 用户发起删除的应用程序逻辑
- 自动化数据清理/维护
- 批量删除
- 软删除实现
代码方法
步骤1:设置SDK客户端
import { Api } from "bknd";
const api = new Api({
host: "http://localhost:7654",
});
// 如果需要认证:
api.updateToken("您的JWT令牌");
步骤2:删除单个记录
使用deleteOne(entity, id):
const { ok, data, error } = await api.data.deleteOne("posts", 1);
if (ok) {
console.log("已删除帖子:", data.id);
} else {
console.error("失败:", error.message);
}
步骤3:处理响应
响应对象:
type DeleteResponse = {
ok: boolean; // 成功/失败
data?: { // 删除的记录(如果成功)
id: number;
// ...删除记录的所有字段
};
error?: { // 错误信息(如果失败)
message: string;
code: string;
};
};
步骤4:删除多个记录(批量)
使用deleteMany(entity, where):
// 删除所有归档的帖子
const { ok, data } = await api.data.deleteMany("posts", {
status: { $eq: "archived" },
});
// data包含已删除的记录
console.log("已删除", data.length, "个帖子");
重要: where子句是必需的,以防止意外删除所有记录。
// 删除旧会话
await api.data.deleteMany("sessions", {
last_active: { $lt: "2024-01-01" },
});
// 按多个条件删除
await api.data.deleteMany("logs", {
level: { $eq: "debug" },
created_at: { $lt: "2024-06-01" },
});
步骤5:删除前验证
始终验证记录存在或检查计数后再删除:
// 检查记录是否存在
const { data: existing } = await api.data.readOne("posts", id);
if (!existing) {
throw new Error("帖子未找到");
}
await api.data.deleteOne("posts", id);
// 批量删除前检查计数
const { data: countResult } = await api.data.count("logs", {
level: { $eq: "debug" },
});
console.log(`即将删除 ${countResult.count} 条记录`);
REST API方法
删除一个
curl -X DELETE http://localhost:7654/api/data/posts/1
带认证删除
curl -X DELETE http://localhost:7654/api/data/posts/1 \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
删除多个
# 删除所有归档的帖子
curl -X DELETE "http://localhost:7654/api/data/posts?where=%7B%22status%22%3A%22archived%22%7D"
# URL解码后的where:{"status":"archived"}
React集成
删除按钮
import { useApp } from "bknd/react";
import { useState } from "react";
function DeleteButton({ postId, onDeleted }: { postId: number; onDeleted?: () => void }) {
const { api } = useApp();
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
async function handleDelete() {
if (!confirm("确定要删除这个帖子吗?")) {
return;
}
setLoading(true);
setError(null);
const { ok, error: apiError } = await api.data.deleteOne("posts", postId);
setLoading(false);
if (ok) {
onDeleted?.();
} else {
setError(apiError.message);
}
}
return (
<>
<button onClick={handleDelete} disabled={loading}>
{loading ? "删除中..." : "删除"}
</button>
{error && <p className="error">{error}</p>}
</>
);
}
使用SWR重新验证
import { mutate } from "swr";
function useDeletePost() {
const { api } = useApp();
async function deletePost(id: number) {
const { ok, error } = await api.data.deleteOne("posts", id);
if (ok) {
// 重新验证列表
mutate("posts");
}
return { ok, error };
}
return { deletePost };
}
乐观删除
function useOptimisticDelete() {
const { api } = useApp();
const [posts, setPosts] = useState<Post[]>([]);
async function deletePost(id: number) {
// 乐观:立即移除
const originalPosts = [...posts];
setPosts((prev) => prev.filter((p) => p.id !== id));
// 实际删除
const { ok } = await api.data.deleteOne("posts", id);
if (!ok) {
// 失败时回滚
setPosts(originalPosts);
}
return { ok };
}
return { posts, deletePost };
}
完整示例
import { Api } from "bknd";
const api = new Api({ host: "http://localhost:7654" });
// 认证
await api.auth.login({ email: "admin@example.com", password: "password" });
// 简单删除
const { ok, data } = await api.data.deleteOne("posts", 1);
if (ok) {
console.log("已删除:", data.title);
}
// 带验证的删除
const postId = 5;
const { data: post } = await api.data.readOne("posts", postId);
if (post) {
await api.data.deleteOne("posts", postId);
}
// 批量删除:移除旧的归档帖子
const { data: deleted } = await api.data.deleteMany("posts", {
status: { $eq: "archived" },
created_at: { $lt: "2023-01-01" },
});
console.log("已删除", deleted.length, "个旧的归档帖子");
// 清理过期的会话
await api.data.deleteMany("sessions", {
expires_at: { $lt: new Date().toISOString() },
});
常见模式
软删除(推荐用于用户数据)
代替永久删除,标记为已删除:
// 软删除:设置时间戳
async function softDelete(api: Api, entity: string, id: number) {
return api.data.updateOne(entity, id, {
deleted_at: new Date().toISOString(),
});
}
// 恢复软删除的记录
async function restore(api: Api, entity: string, id: number) {
return api.data.updateOne(entity, id, {
deleted_at: null,
});
}
// 查询未删除的记录
async function findActive(api: Api, entity: string, query = {}) {
return api.data.readMany(entity, {
...query,
where: {
...query.where,
deleted_at: { $isnull: true },
},
});
}
// 永久删除超过30天的软删除记录
async function purgeDeleted(api: Api, entity: string) {
const thirtyDaysAgo = new Date();
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
return api.data.deleteMany(entity, {
deleted_at: { $lt: thirtyDaysAgo.toISOString() },
});
}
带确认的删除
async function deleteWithConfirmation(
api: Api,
entity: string,
id: number,
confirm: () => Promise<boolean>
) {
const { data } = await api.data.readOne(entity, id);
if (!data) {
return { ok: false, error: { message: "记录未找到" } };
}
const confirmed = await confirm();
if (!confirmed) {
return { ok: false, error: { message: "用户取消" } };
}
return api.data.deleteOne(entity, id);
}
级联删除(手动)
当Bknd不自动级联时,先删除子记录:
async function cascadeDelete(api: Api, userId: number) {
// 先删除子记录
await api.data.deleteMany("posts", { author_id: { $eq: userId } });
await api.data.deleteMany("comments", { user_id: { $eq: userId } });
await api.data.deleteMany("likes", { user_id: { $eq: userId } });
// 然后删除父记录
return api.data.deleteOne("users", userId);
}
带进度的批量删除
async function batchDelete(
api: Api,
entity: string,
ids: number[],
onProgress?: (done: number, total: number) => void
) {
const results = [];
for (let i = 0; i < ids.length; i++) {
const result = await api.data.deleteOne(entity, ids[i]);
results.push(result);
onProgress?.(i + 1, ids.length);
}
return results;
}
// 使用
const idsToDelete = [1, 5, 12, 23];
await batchDelete(api, "posts", idsToDelete, (done, total) => {
console.log(`已删除 ${done}/${total}`);
});
删除前归档
async function archiveAndDelete(
api: Api,
sourceEntity: string,
archiveEntity: string,
id: number
) {
// 读取当前记录
const { data: record } = await api.data.readOne(sourceEntity, id);
if (!record) {
return { ok: false, error: { message: "记录未找到" } };
}
// 创建归档副本
await api.data.createOne(archiveEntity, {
...record,
original_id: record.id,
archived_at: new Date().toISOString(),
});
// 删除原始记录
return api.data.deleteOne(sourceEntity, id);
}
条件删除
async function deleteIf(
api: Api,
entity: string,
id: number,
condition: (record: any) => boolean
) {
const { data } = await api.data.readOne(entity, id);
if (!data) {
return { ok: false, error: { message: "记录未找到" } };
}
if (!condition(data)) {
return { ok: false, error: { message: "条件不满足" } };
}
return api.data.deleteOne(entity, id);
}
// 仅当草稿时删除
await deleteIf(api, "posts", 1, (post) => post.status === "draft");
常见陷阱
记录未找到
问题: 删除返回无数据或错误。
修复: 先检查记录是否存在:
const { data: existing } = await api.data.readOne("posts", id);
if (!existing) {
console.error("帖子未找到");
return;
}
await api.data.deleteOne("posts", id);
外键约束
问题: 删除父记录时FOREIGN KEY constraint failed。
修复: 先删除或取消链接子记录:
// 选项1:删除子记录
await api.data.deleteMany("comments", { post_id: { $eq: postId } });
await api.data.deleteOne("posts", postId);
// 选项2:取消链接子记录(如果外键可为空)
await api.data.updateMany(
"comments",
{ post_id: { $eq: postId } },
{ post_id: null }
);
await api.data.deleteOne("posts", postId);
未检查响应
问题: 假设成功而不验证。
修复: 始终检查ok:
// 错误
await api.data.deleteOne("posts", id);
console.log("已删除!"); // 可能失败!
// 正确
const { ok, error } = await api.data.deleteOne("posts", id);
if (!ok) {
console.error("删除失败:", error.message);
return;
}
console.log("已删除!");
意外大量删除
问题: 删除比预期多的记录。
修复: 始终使用具体的where子句并验证计数:
// 危险 - 可能删除比预期多的记录
await api.data.deleteMany("posts", { status: { $eq: "draft" } });
// 更安全 - 先检查计数
const { data: count } = await api.data.count("posts", {
status: { $eq: "draft" }
});
console.log(`即将删除 ${count.count} 个帖子`);
if (count.count > 100) {
throw new Error("记录过多 - 中止");
}
缺少认证
问题: Unauthorized错误。
修复: 删除前进行认证:
await api.auth.login({ email, password });
// 或
api.updateToken(savedToken);
await api.data.deleteOne("posts", id);
硬删除无撤销
问题: 意外删除重要数据。
修复: 对可恢复数据使用软删除:
// 代替硬删除
await api.data.deleteOne("posts", id);
// 使用软删除
await api.data.updateOne("posts", id, {
deleted_at: new Date().toISOString(),
});
删除前无确认
问题: 用户意外删除数据。
修复: 始终确认破坏性操作:
// 在前端
function handleDelete(id: number) {
if (!confirm("删除这个帖子?此操作无法撤销。")) {
return;
}
api.data.deleteOne("posts", id);
}
验证
删除后,验证记录已消失:
const { ok } = await api.data.deleteOne("posts", 1);
if (ok) {
const { data } = await api.data.readOne("posts", 1);
console.log("记录存在:", data !== null); // 应为false
}
或通过管理面板:管理面板 > 数据 > 选择实体 > 搜索已删除记录。
应该与不应该
应该:
- 在假设成功前检查
ok - 删除前验证记录存在
- 对用户数据使用软删除
- 处理外键约束
- 破坏性操作前与用户确认
- 批量删除前检查计数
- 考虑永久删除前归档
不应该:
- 假设deleteOne总是成功
- 在有外键约束时先删除父记录再删除子记录
- 不经确认硬删除用户数据
- 删除后忘记重新验证缓存
- 使用deleteMany时没有具体的where子句
- 删除受保护实体时无认证
相关技能
- bknd-crud-read - 删除前验证记录
- bknd-crud-update - 更新代替删除(软删除)
- bknd-crud-create - 重新创建意外删除的记录
- bknd-define-relationship - 理解外键约束
- bknd-bulk-operations - 大规模删除模式