[{"data":1,"prerenderedAt":179},["ShallowReactive",2],{"content:\u002Fposts\u002F2025\u002Fsnippets-nestjs-drizzle-custom-validation":3,"surround:\u002Fposts\u002F2025\u002Fsnippets-nestjs-drizzle-custom-validation":168},{"id":4,"title":5,"body":6,"canonical":145,"categories":146,"date":148,"description":149,"draft":150,"extension":151,"image":145,"meta":152,"navigation":153,"path":154,"permalink":145,"readingTime":155,"recommend":145,"references":145,"seo":160,"sitemap":161,"stem":162,"tags":163,"type":145,"updated":148,"__hash__":167},"content\u002Fposts\u002F2025\u002Fsnippets-nestjs-drizzle-custom-validation.md","Nestjs + Drizzle 项目中一些常用的自定义校验",{"type":7,"value":8,"toc":137},"minimark",[9,13,26,51,54,57,60,78,81,84,94,113,116,119],[10,11,12],"h2",{"id":12},"两个字段是否一致",[14,15,16,17,21,22,25],"p",{},"在注册时，我们经常需要判断 ",[18,19,20],"code",{"code":20},"密码"," 和 ",[18,23,24],{"code":24},"重复密码"," 两个是否一致，我们可以使用这个规则",[27,28,30,43],"tab",{":tabs":29},"[\"代码片段\", \"使用示例\"]",[31,32,34],"template",{"v-slot:tab1":33},"",[35,36,41],"pre",{"className":37,"code":39,"language":40,"meta":33},[38],"language-ts","import { registerDecorator, ValidationArguments, ValidationOptions } from 'class-validator';\n\nexport function IsConfirm(validationOptions?: ValidationOptions) {\n    return (object: Record\u003Cstring, any>, propertyName: string) => {\n        registerDecorator({\n            name: 'IsConfirm',\n            target: object.constructor,\n            propertyName,\n            constraints: [],\n            options: validationOptions,\n            validator: {\n                validate(value: string, args: ValidationArguments) {\n                    return Boolean(value === args.object[`${args.property}_confirm`]);\n                },\n            },\n        });\n    };\n}\n","ts",[18,42,39],{"__ignoreMap":33},[31,44,45],{"v-slot:tab2":33},[35,46,49],{"className":47,"code":48,"language":40,"meta":33},[38],"import { IsConfirmRule, IsNotExistsRule } from '@libs\u002Fcommon'\nimport { ApiProperty } from '@nestjs\u002Fswagger'\nimport { IsNotEmpty, Length } from 'class-validator'\n\nexport class CreateUserDto {\n    @ApiProperty({ description: '用户名', example: 'admin' })\n    @IsNotEmpty({ message: '用户名不能为空' })\n    @Length(5, 20, { message: '用户名长度应为5~20位' })\n    @IsNotExistsRule('user', { message: '用户已存在' })\n    username: string\n\n    @ApiProperty({ description: '密码', example: '123456' })\n    @IsNotEmpty({ message: '密码不能为空' })\n    @Length(5, 20, { message: '请输入5-20位的密码' })\n    @IsConfirmRule({ message: '两次密码输入不一致' }) \u002F\u002F [!code ++]\n    password: string\n\n    @ApiProperty({ description: '确认密码', example: '123456' })\n    @IsNotEmpty({ message: '确认密码不能为空' })\n    password_confirm: string\n\n    @ApiProperty({ description: '角色ID', example: 1 })\n    roleId?: number\n}\n",[18,50,48],{"__ignoreMap":33},[10,52,53],{"id":53},"是否在数据库中存在",[14,55,56],{},"在编辑时，我们需要一些字段的值必须在数据库中存在，我们可以使用这个规则",[14,58,59],{},"比如下方的使用示例: 更新用户时，我们需要该用户名必须存在",[27,61,62,70],{":tabs":29},[31,63,64],{"v-slot:tab1":33},[35,65,68],{"className":66,"code":67,"language":40,"meta":33},[38],"import { schema, SchemaName } from '@labs\u002Fshared';\nimport { registerDecorator, ValidationArguments, ValidationOptions } from 'class-validator';\nimport { eq } from 'drizzle-orm';\n\nimport { db } from '@\u002Fdrizzle\u002Fdb';\n\u002F**\n * field is required in table\n * @param table drizzle schema name\n * @param validationOptions validation message\n * @returns {boolean}\n *\u002F\nexport function IsExists(table: SchemaName, validationOptions?: ValidationOptions) {\n    return (object: Record\u003Cstring, any>, propertyName: string) => {\n        registerDecorator({\n            name: 'IsExists',\n            target: object.constructor,\n            propertyName,\n            constraints: [table],\n            options: validationOptions,\n            validator: {\n                async validate(value: string, args: ValidationArguments) {\n                    if (!args.value)\n                        return false;\n\n                    const [result] = await db\n                        .select()\n                        .from(schema[table])\n                        .where(eq(schema[table][propertyName], args.value));\n                    return Boolean(result);\n                },\n            },\n        });\n    };\n}\n",[18,69,67],{"__ignoreMap":33},[31,71,72],{"v-slot:tab2":33},[35,73,76],{"className":74,"code":75,"language":40,"meta":33},[38],"import { IsConfirmRule, IsExistsRule } from '@libs\u002Fcommon'\nimport { ApiProperty } from '@nestjs\u002Fswagger'\nimport { IsNotEmpty, Length } from 'class-validator'\n\nexport class UpdateUserDto {\n    @ApiProperty({ description: '用户名', example: 'admin' })\n    @IsNotEmpty({ message: '用户名不能为空' })\n    @Length(5, 20, { message: '用户名长度应为5~20位' })\n    @IsExistsRule('user', { message: '用户不存在' }) \u002F\u002F [!code ++]\n    username: string\n}\n",[18,77,75],{"__ignoreMap":33},[10,79,80],{"id":80},"是否在数据库中不存在",[14,82,83],{},"在新增时，我们需要指定的字段在数据库中不能重复，我们可以使用这个规则",[85,86,87],"blockquote",{},[14,88,89,90,93],{},"它在数据库中设置了 ",[18,91,92],{"code":92},"unique"," 或者你希望它在数据库中是唯一的",[27,95,97,105],{":tabs":96},"[\"代码片段\", \"使用\"]",[31,98,99],{"v-slot:tab1":33},[35,100,103],{"className":101,"code":102,"language":40,"meta":33},[38],"import { schema, SchemaName } from '@labs\u002Fshared';\nimport { registerDecorator, ValidationArguments, ValidationOptions } from 'class-validator';\nimport { eq } from 'drizzle-orm';\n\nimport { db } from '@\u002Fdrizzle\u002Fdb';\n\u002F**\n * field is required in table\n * @param table drizzle schema name\n * @param validationOptions validation message\n * @returns {boolean}\n *\u002F\nexport function IsNotExists(table: SchemaName, validationOptions?: ValidationOptions) {\n    return (object: Record\u003Cstring, any>, propertyName: string) => {\n        registerDecorator({\n            name: 'IsNotExists',\n            target: object.constructor,\n            propertyName,\n            constraints: [table],\n            options: validationOptions,\n            validator: {\n                async validate(value: string, args: ValidationArguments) {\n                    if (!args.value)\n                        return false;\n\n                    console.log(table, 'table');\n                    const [result] = await db\n                        .select()\n                        .from(schema[table])\n                        .where(eq(schema[table][propertyName], args.value));\n\n                    return !result;\n                },\n            },\n        });\n    };\n}\n",[18,104,102],{"__ignoreMap":33},[31,106,107],{"v-slot:tab2":33},[35,108,111],{"className":109,"code":110,"language":40,"meta":33},[38],"import { IsConfirmRule, IsNotExistsRule } from '@libs\u002Fcommon'\nimport { ApiProperty } from '@nestjs\u002Fswagger'\nimport { IsNotEmpty, Length } from 'class-validator'\n\nexport class CreateUserDto {\n    @ApiProperty({ description: '用户名', example: 'admin' })\n    @IsNotEmpty({ message: '用户名不能为空' })\n    @Length(5, 20, { message: '用户名长度应为5~20位' })\n    @IsNotExistsRule('user', { message: '用户已存在' }) \u002F\u002F [!code ++]\n    username: string\n}\n",[18,112,110],{"__ignoreMap":33},[10,114,115],{"id":115},"字段是否唯一",[14,117,118],{},"在编辑或者新增时，我们希望这个字段是唯一的。可以使用这个规则",[27,120,121,129],{":tabs":96},[31,122,123],{"v-slot:tab1":33},[35,124,127],{"className":125,"code":126,"language":40,"meta":33},[38],"import { schema, SchemaName } from '@labs\u002Fshared';\nimport { registerDecorator, ValidationArguments, ValidationOptions } from 'class-validator';\nimport { and, eq, ne } from 'drizzle-orm';\n\nimport { db } from '@\u002Fdrizzle\u002Fdb';\n\ninterface IsUniqueOptions extends ValidationOptions {\n    excludeField?: string \u002F\u002F 编辑时排除当前记录的字段名,默认为 'id'\n}\n\n\u002F**\n * 检查字段值是否唯一\n * @param table drizzle schema name\n * @param validationOptions validation options (可选 excludeField 用于编辑场景)\n * @returns {boolean}\n * @example\n * \u002F\u002F 新增场景\n * @IsUnique('user', { message: '用户名已存在' })\n * username: string;\n *\n * \u002F\u002F 编辑场景\n * @IsUnique('user', { message: '用户名已存在', excludeField: 'id' })\n * username: string;\n *\u002F\nexport function IsUnique(table: SchemaName, validationOptions?: IsUniqueOptions) {\n    return (object: Record\u003Cstring, any>, propertyName: string) => {\n        registerDecorator({\n            name: 'IsUnique',\n            target: object.constructor,\n            propertyName,\n            constraints: [table],\n            options: validationOptions,\n            validator: {\n                async validate(value: string, args: ValidationArguments) {\n                    if (!value)\n                        return true; \u002F\u002F 空值由其他验证器处理\n\n                    const excludeField = validationOptions?.excludeField || 'id';\n                    const currentObject = args.object as any;\n\n                    try {\n                        const tableSchema = schema[table];\n                        const column = tableSchema[propertyName];\n\n                        if (!column) {\n                            console.error(`Column ${propertyName} not found in table ${table}`);\n                            return false;\n                        }\n\n                        \u002F\u002F 构建查询条件\n                        const conditions = [eq(column, value)];\n\n                        \u002F\u002F 如果是编辑操作(存在 excludeField),排除当前记录\n                        if (currentObject?.[excludeField]) {\n                            const excludeColumn = tableSchema[excludeField];\n                            if (excludeColumn) {\n                                conditions.push(ne(excludeColumn, currentObject[excludeField]));\n                            }\n                        }\n\n                        const [result] = await db\n                            .select()\n                            .from(tableSchema)\n                            .where(conditions.length > 1 ? and(...conditions) : conditions[0])\n                            .limit(1);\n\n                        \u002F\u002F 如果找不到记录，说明值是唯一的\n                        return !result;\n                    }\n                    catch (error) {\n                        console.error('IsUnique validation error:', error);\n                        return false;\n                    }\n                },\n            },\n        });\n    };\n}\n",[18,128,126],{"__ignoreMap":33},[31,130,131],{"v-slot:tab2":33},[35,132,135],{"className":133,"code":134,"language":40,"meta":33},[38],"import { IsNotEmpty, IsString } from 'class-validator';\n\nimport { IsUnique } from '@\u002Fcommon\u002Fvalidation';\n\nexport class CreateDictTypeDto {\n    id?: number;\n\n    @IsString()\n    @IsNotEmpty()\n    name: string;\n\n    @IsNotEmpty()\n    @IsString()\n    @IsUnique('dictType', { message: '字典类型编码已存在', excludeField: 'id' }) \u002F\u002F [!code ++]\n    code: string;\n\n    status?: boolean;\n\n    remark?: string;\n}\n",[18,136,134],{"__ignoreMap":33},{"title":33,"searchDepth":138,"depth":138,"links":139},4,[140,142,143,144],{"id":12,"depth":141,"text":12},2,{"id":53,"depth":141,"text":53},{"id":80,"depth":141,"text":80},{"id":115,"depth":141,"text":115},null,[147],"代码","2025-12-30 17:14:59","可以直接复制使用的自定义校验规则",false,"md",{},true,"\u002Fposts\u002F2025\u002Fsnippets-nestjs-drizzle-custom-validation",{"text":156,"minutes":157,"time":158,"words":159},"6 min read",5.51,330600,1102,{"title":5,"description":149},{"loc":154},"posts\u002F2025\u002Fsnippets-nestjs-drizzle-custom-validation",[164,165,166],"Nestjs","Drizzle","class-validator","PM6au-zX_vAGolfvV_Oba9Ar_3tzPlWfCy0dJemVfos",[169,174],{"title":170,"path":171,"stem":172,"date":173,"type":145,"children":-1},"BullMQ 初体验","\u002Fposts\u002F2025\u002Ffitst-experience-with-bullmq","posts\u002F2025\u002Ffitst-experience-with-bullmq","2025-12-29 10:36:19",{"title":175,"path":176,"stem":177,"date":178,"type":145,"children":-1},"一些 Nestjs 的自定义装饰器","\u002Fposts\u002F2025\u002Fsnippets-nestjs-custom-decorators","posts\u002F2025\u002Fsnippets-nestjs-custom-decorators","2025-12-31 15:05:56",1789642587954]