Bknd数据删除操作Skill bknd-crud-delete

这个技能用于在Bknd数据库中执行删除操作,包括单条记录删除、批量删除、软删除等模式,涉及SDK和REST API的使用。关键词:Bknd, 数据库删除, CRUD操作, 后端开发, API集成, 数据管理。

后端开发 0 次安装 0 次浏览 更新于 3/9/2026

名称: 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 - 大规模删除模式