You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在KeystoneJS中为多个字段设置联合唯一约束?

在KeystoneJS中实现多字段联合唯一约束

要实现program、type、name三个字段的联合唯一验证,需要从数据库层面的索引约束和应用层的钩子验证两方面入手,既保证数据一致性,又给用户友好的错误提示。

步骤1:修改字段配置并添加数据库联合索引

首先移除type和name字段单独的isIndexed: 'unique'配置(我们需要的是组合唯一,而非单个字段唯一),然后在db.indexes中定义联合唯一索引:

const Redemption = list({
  access: allowAll,
  fields: {
    program: relationship({ ref: 'Program', many: false }),
    type: text({ label: 'Type', validation: { isRequired: true } }),
    name: text({ label: 'name', validation: { isRequired: true } }),
  },
  db: {
    // 定义program、type、name的联合唯一索引
    indexes: [
      { fields: ['program', 'type', 'name'], unique: true },
    ],
  },
  // 后续添加钩子验证
});

步骤2:添加钩子验证实现友好错误提示

数据库索引会阻止重复数据插入,但抛出的错误不够直观。通过validateInput钩子,我们可以在创建/更新时主动检查组合是否重复,并返回清晰的错误信息:

const Redemption = list({
  access: allowAll,
  fields: {
    program: relationship({ ref: 'Program', many: false }),
    type: text({ label: 'Type', validation: { isRequired: true } }),
    name: text({ label: 'name', validation: { isRequired: true } }),
  },
  db: {
    indexes: [
      { fields: ['program', 'type', 'name'], unique: true },
    ],
  },
  hooks: {
    validateInput: async ({ operation, inputData, context, addValidationError }) => {
      // 仅在创建和更新操作时执行验证
      if (!['create', 'update'].includes(operation)) return;

      let programId, type, name;

      if (operation === 'create') {
        // 创建时直接取输入的字段值
        programId = inputData.program?.connect?.id;
        type = inputData.type;
        name = inputData.name;
      } else {
        // 更新时先获取原记录的字段值,兼容部分字段未修改的情况
        const originalItem = await context.db.Redemption.findOne({
          where: { id: context.itemId },
          select: ['program', 'type', 'name'],
        });
        programId = inputData.program?.connect?.id || inputData.program?.id || originalItem.program.id;
        type = inputData.type ?? originalItem.type;
        name = inputData.name ?? originalItem.name;
      }

      // 查询是否存在相同组合的记录(更新时排除当前记录)
      const existingItem = await context.db.Redemption.findOne({
        where: {
          program: { id: programId },
          type,
          name,
          ...(operation === 'update' ? { id: { not: context.itemId } } : {}),
        },
      });

      if (existingItem) {
        addValidationError('program、type与name的组合已存在,无法重复创建');
      }
    },
  },
});

说明

  • 数据库索引是保障数据唯一性的核心,即使绕过Keystone的API直接操作数据库,也能阻止重复数据插入。
  • 钩子验证的作用是在应用层提前拦截重复请求,给用户返回易读的错误信息,避免数据库抛出的原始错误。
  • 更新操作中需要排除当前记录本身,防止因未修改字段而误判为重复。

内容的提问来源于stack exchange,提问作者Palak Jadav

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 03:35:26