问题描述

  1. 精度丢失:在数据库里,floatdouble 类型以二进制近似存储小数,会引发精度丢失。比如计算 0.1 * 3 时,结果可能是 0.30000000000000004,在商品价格计算等对精度要求高的场景下不可接受。
  2. 类型转换:使用 decimal 类型存储小数,从数据库取出的数据默认是字符串类型,在代码里需要手动转换为 number 类型,否则会影响后续计算和业务逻辑。

解决方案

1. 使用 decimal 类型配合 transformer

decimal 类型以十进制形式存储数据,能精确表示和计算小数。借助 transformer 可在数据存入数据库和从数据库取出时自动转换类型。

import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, ValueTransformer } from 'typeorm';

// 定义转换器
const decimalTransformer: ValueTransformer = {
  to: (value: number) => value, // 存入数据库时,直接使用原始值
  from: (value: string) => parseFloat(value), // 从数据库取出时,将字符串转换为 number 类型
};

@Entity()
export class CouponBatch {
  @PrimaryGeneratedColumn()
  id: number;

  // ... 已有代码 ...

  @Column({     type: 'decimal',     name: 'value',     nullable: false,
    precision: 10, // 总位数
    scale: 2,      // 小数位数
    transformer: decimalTransformer
  })
  value: number;

  @Column({     type: 'decimal',     name: 'min_price',     nullable: false,
    precision: 10,     scale: 2,          transformer: decimalTransformer
  })
  minPrice: number;

  // ... 已有代码 ...
}

2. 说明

  • precision:指定 decimal 类型的总位数,即整数部分和小数部分的位数之和。
  • scale:指定小数部分的位数。
  • transformer:包含 tofrom 两个方法,to 方法在数据存入数据库时调用,from 方法在从数据库取出数据时调用。

DTO验证,使用自定义装饰器

//文件目录src/utils/validators/is-two-decimal.validator
import { applyDecorators } from '@nestjs/common';
import { IsNumber, ValidateBy, ValidationOptions, ValidatorConstraint, ValidatorConstraintInterface, ValidationArguments } from 'class-validator';

// 定义验证约束
@ValidatorConstraint({ name: 'isTwoDecimal', async: false })
export class IsTwoDecimal implements ValidatorConstraintInterface {
  validate(value: any, args: ValidationArguments) {
    // 检查值是否为数字且小数位数最多为两位
    if (typeof value !== 'number') {
      return false;
    }
    const decimalPart = value.toString().split('.')[1];
    return !decimalPart || decimalPart.length <= 2;
  }

  defaultMessage(args: ValidationArguments) {
    return `${args.property} 必须为最多两位小数`;
  }
}

// 自定义装饰器工厂函数
export function IsTwoDecimalDecorator(validationOptions?: ValidationOptions) {
  const defaultValidationOptions = {
    ...validationOptions,
    // 合并默认错误消息,如果用户没有提供自定义消息,则使用默认消息
    message: validationOptions?.message || (args => new IsTwoDecimal().defaultMessage(args)),
  };

  return applyDecorators(
    IsNumber({}, defaultValidationOptions),
    ValidateBy(
      {
        name: 'isTwoDecimal',
        // 使用 IsTwoDecimal 实例作为验证器
        validator: new IsTwoDecimal(),
      },
      defaultValidationOptions,
    ),
  );
}

DTO中使用装饰器

import { IsTwoDecimalDecorator } from 'src/utils/validators/is-two-decimal.validator';
 @IsTwoDecimalDecorator({ message: '最低价最多两位小数' })
  minPrice: number;

总结

  • 若对精度要求高,推荐使用 decimal 类型配合 transformer,能保证数据的精确存储和计算,同时在代码里方便处理 number 类型数据。
  • 若对精度要求不高,可使用 floatdouble 类型,但要注意可能出现的精度丢失问题。
最后修改:2025 年 05 月 14 日
如果觉得我的文章对你有用,请随意赞赏