Skip to content

BaseService 服务基类使用文档

概述

BaseService 是一个通用的服务基类,封装了常见的 CRUD 操作和数据处理逻辑。它基于 TypeORM 的 Repository 模式,提供了标准化的数据访问层实现,包含数据验证、分页查询、软删除等功能。

基础使用

创建自定义服务

typescript
import { Injectable } from '@nestjs/common'
import { InjectRepository } from '@nestjs/typeorm'
import { Repository } from 'typeorm'
import { BaseService } from '@/common/BaseService'
import { SysUser } from './entities/sys-user.entity'

@Injectable()
export class UserService extends BaseService<SysUser, UserService> {
  constructor(
    @InjectRepository(SysUser)
    private readonly userRepository: Repository<SysUser>,
  ) {
    super(SysUser, userRepository)
  }
}

在控制器中使用

typescript
import { Controller } from '@nestjs/common'
import { BaseController } from '@/common/BaseController'
import { UserService } from './user.service'

@Controller('user')
export class UserController extends BaseController<SysUser, UserService> {
  constructor(private readonly userService: UserService) {
    super(userService)
  }
}

API 参考

核心方法

save(dto: SaveDto<T>)

保存数据(新增或更新),包含完整的数据验证逻辑。

参数:

  • dto: SaveDto<T> - 要保存的数据对象

返回: Promise<T> - 保存后的实体对象

特点:

  • 自动去除 createTime 和 updateTime 字段
  • 执行 class-validator 验证
  • 检查 @DbUnique 约束的唯一性
  • 调用 dataValidate 进行数据权限校验
typescript
// 使用示例
const userData = {
  username: 'newuser',
  email: 'user@example.com',
  createUser: 'admin',
}

const savedUser = await userService.save(userData)

add(dto: SaveDto<T>)

新增数据,会自动删除 id 字段确保是新增操作。

参数:

  • dto: SaveDto<T> - 要新增的数据对象

返回: Promise<T> - 新增的实体对象

typescript
const newUser = await userService.add({
  username: 'testuser',
  email: 'test@example.com',
  createUser: 'admin',
})

update(dto: SaveDto<T>)

更新数据,要求必须包含 id 字段。

参数:

  • dto: SaveDto<T> - 要更新的数据对象(必须包含 id)

返回: Promise<T> - 更新后的实体对象

异常: 如果没有提供 id 字段会抛出错误

typescript
const updatedUser = await userService.update({
  id: '1',
  username: 'updated_username',
  updateUser: 'admin',
})

del(ids: string[] | string, updateUser?: string)

软删除数据,将 isDelete 设置为 '1'。

参数:

  • ids: string[] | string - 要删除的记录ID(数组或逗号分隔的字符串)
  • updateUser?: string - 执行删除操作的用户名

返回: Promise<UpdateResult> - 更新结果

typescript
// 删除单条记录
await userService.del('1', 'admin')

// 删除多条记录
await userService.del(['1', '2', '3'], 'admin')

// 使用逗号分隔的ID
await userService.del('1,2,3', 'admin')

getOne(query: FindOptionsWhere<T> & FindOneOptions, isError = true)

根据条件查询单条记录。

参数:

  • query: FindOptionsWhere<T> & FindOneOptions - 查询条件
  • isError: boolean = true - 找不到数据时是否抛出异常

返回: Promise<T | null> - 查询到的实体对象或 null

typescript
// 查询存在的记录
const user = await userService.getOne({ id: '1' })

// 查询不存在时不抛出异常
const user = await userService.getOne({ id: '999' }, false)
// 返回 null 而不是抛出异常

SQL 查询方法

listBy(queryOrm: FindManyOptions, query: QueryListDto, cb?: Function)

分页列表查询的核心方法。

参数:

  • queryOrm: FindManyOptions = {} - TypeORM 查询选项
  • query: QueryListDto = {} - 分页查询参数
  • cb?: Function - 数据处理回调函数

返回: Promise<ResponseListDto<T>> - 分页查询结果

typescript
// 基础分页查询
const result = await userService.listBy({ where: { status: '1' } }, { pageNum: 1, pageSize: 10 })

// 带数据处理的查询
const result = await userService.listBy({ where: { status: '1' } }, { pageNum: 1, pageSize: 10 }, (data) => {
  return data.map((item) => ({
    ...item,
    displayName: `${item.username}(${item.email})`,
  }))
})

sqlOne(query: FindOptionsWhere<T> & FindOneOptions)

底层单条查询方法。

参数:

  • query: FindOptionsWhere<T> & FindOneOptions - 查询条件

返回: Promise<T | null> - 查询结果

typescript
const user = await userService.sqlOne({
  where: { username: 'testuser' },
})

工具方法

sqlLike(value: string)

生成模糊查询条件,自动转义特殊字符。

参数:

  • value: string - 要模糊匹配的值

返回: Like - TypeORM 的 Like 条件对象

typescript
// 安全的模糊查询
const users = await userRepository.find({
  where: {
    username: userService.sqlLike('test'),
  },
})
// 会查找包含 'test' 的用户名,自动处理 % 和 _ 字符

betweenTime(beginEndTime: [string, string])

处理时间范围查询条件。

参数:

  • beginEndTime: [string, string] - 开始时间和结束时间数组

返回: Between - TypeORM 的 Between 条件对象

typescript
// 时间范围查询
const users = await userRepository.find({
  where: {
    createTime: userService.betweenTime(['2024-01-01', '2024-12-31']),
  },
})
// 结束时间会自动加上 23:59:59

betweenDateArr(beginEndTime: [string, string])

生成日期范围数组。

参数:

  • beginEndTime: [string, string] - 开始和结束日期

返回: string[] - 日期字符串数组

typescript
const dateArray = userService.betweenDateArr(['2024-01-01', '2024-01-03'])
// 返回: ['2024-01-01', '2024-01-02', '2024-01-03']

dataValidate(data: { id; updateUser })

数据权限校验方法。

参数:

  • data: { id; updateUser } - 包含ID和更新用户的对象

返回: Promise<boolean> - 验证结果

异常: 权限不足时抛出 HttpException

typescript
// 在自定义方法中使用权限校验
async customUpdate(id: string, data: any, updateUser: string) {
  await this.dataValidate({ id, updateUser })
  // 执行更新逻辑
}

完整示例

用户服务完整实现

typescript
import { Injectable } from '@nestjs/common'
import { InjectRepository } from '@nestjs/typeorm'
import { Repository, Like, Between } from 'typeorm'
import { BaseService } from '@/common/BaseService'
import { SysUser } from './entities/sys-user.entity'
import { QueryListDto } from '@/common/dto'

@Injectable()
export class UserService extends BaseService<SysUser, UserService> {
  constructor(
    @InjectRepository(SysUser)
    private readonly userRepository: Repository<SysUser>,
  ) {
    super(SysUser, userRepository)
  }

  // 自定义列表查询
  async list(
    query: QueryListDto & {
      username?: string
      email?: string
      createTime?: [string, string]
    },
  ) {
    const queryOrm: any = {
      where: {},
      order: { createTime: 'DESC' },
    }

    // 动态构建查询条件
    if (query.username) {
      queryOrm.where.username = this.sqlLike(query.username)
    }

    if (query.email) {
      queryOrm.where.email = this.sqlLike(query.email)
    }

    if (query.createTime) {
      queryOrm.where.createTime = this.betweenTime(query.createTime)
    }

    return this.listBy(queryOrm, query)
  }

  // 自定义统计方法
  async getUserStats() {
    const total = await this.repository.count()
    const activeUsers = await this.repository.count({
      where: { status: '1' },
    })
    const todayUsers = await this.repository.count({
      where: {
        createTime: this.betweenTime([new Date().toISOString().split('T')[0], new Date().toISOString().split('T')[0]]),
      },
    })

    return {
      total,
      active: activeUsers,
      today: todayUsers,
    }
  }

  // 批量操作
  async batchUpdateStatus(ids: string[], status: string, updateUser: string) {
    // 权限校验
    for (const id of ids) {
      await this.dataValidate({ id, updateUser })
    }

    return await this.repository.update(ids, {
      status,
      updateUser,
    })
  }
}

控制器实现

typescript
import { Controller, Get, Query, Post, Body, Param } from '@nestjs/common'
import { BaseController } from '@/common/BaseController'
import { UserService } from './user.service'
import { SysUser } from './entities/sys-user.entity'

@Controller('api/user')
export class UserController extends BaseController<SysUser, UserService> {
  constructor(private readonly userService: UserService) {
    super(userService)
  }

  // 自定义列表查询接口
  @Get('search')
  async search(@Query() query) {
    return this.userService.list(query)
  }

  // 统计接口
  @Get('stats')
  async getStats() {
    return this.userService.getUserStats()
  }

  // 批量更新状态
  @Post('batch-status')
  async batchUpdateStatus(@Body() body, @Req() req) {
    const { ids, status } = body
    return this.userService.batchUpdateStatus(ids, status, req.user.name)
  }

  // 导出用户数据
  @Get('export')
  async exportUsers(@Query() query) {
    const result = await this.userService.list(query)
    // 处理导出逻辑
    return {
      data: result.data,
      filename: `users_${new Date().getTime()}.xlsx`,
    }
  }
}

高级用法

关联查询处理

typescript
@Injectable()
export class UserService extends BaseService<SysUser, UserService> {
  constructor(
    @InjectRepository(SysUser)
    private readonly userRepository: Repository<SysUser>,
  ) {
    super(SysUser, userRepository)
  }

  // 带关联的列表查询
  async listWithRoles(query: QueryListDto) {
    const queryOrm = {
      relations: ['roles'],
      where: { isDelete: '0' },
      order: { createTime: 'DESC' },
    }

    return this.listBy(queryOrm, query, (data) => {
      return data.map((user) => ({
        ...user,
        roleNames: user.roles?.map((role) => role.name).join(', ') || '',
      }))
    })
  }

  // 复杂关联查询
  async getUserDetail(id: string) {
    const user = await this.repository.findOne({
      where: { id },
      relations: ['roles', 'department', 'createdPosts'],
    })

    if (!user) {
      throw new Error('用户不存在')
    }

    return {
      ...user,
      roleName: user.roles?.[0]?.name || '',
      deptName: user.department?.name || '',
    }
  }
}

事务处理

typescript
@Injectable()
export class UserService extends BaseService<SysUser, UserService> {
  constructor(
    @InjectRepository(SysUser)
    private readonly userRepository: Repository<SysUser>,
    private readonly dataSource: DataSource,
  ) {
    super(SysUser, userRepository)
  }

  // 事务操作示例
  async transferUserDept(userId: string, newDeptId: string, updateUser: string) {
    const queryRunner = this.dataSource.createQueryRunner()
    await queryRunner.connect()
    await queryRunner.startTransaction()

    try {
      // 权限校验
      await this.dataValidate({ id: userId, updateUser })

      // 更新用户部门
      await queryRunner.manager.update(SysUser, userId, {
        departmentId: newDeptId,
        updateUser,
      })

      // 记录操作日志
      await queryRunner.manager.insert(UserOperationLog, {
        userId,
        operation: 'TRANSFER_DEPT',
        oldValue: 'old_dept_id',
        newValue: newDeptId,
        operator: updateUser,
      })

      await queryRunner.commitTransaction()
    } catch (error) {
      await queryRunner.rollbackTransaction()
      throw error
    } finally {
      await queryRunner.release()
    }
  }
}

缓存集成

typescript
@Injectable()
export class UserService extends BaseService<SysUser, UserService> {
  constructor(
    @InjectRepository(SysUser)
    private readonly userRepository: Repository<SysUser>,
    @Inject(CACHE_MANAGER) private cacheManager: Cache,
  ) {
    super(SysUser, userRepository)
  }

  // 带缓存的查询
  async getUserById(id: string): Promise<SysUser> {
    const cacheKey = `user:${id}`
    let user = await this.cacheManager.get(cacheKey)

    if (!user) {
      user = await this.getOne({ id })
      if (user) {
        await this.cacheManager.set(cacheKey, user, 3600) // 缓存1小时
      }
    }

    return user
  }

  // 清除缓存
  async clearUserCache(id: string) {
    const cacheKey = `user:${id}`
    await this.cacheManager.del(cacheKey)
  }

  // 重写 save 方法添加缓存清理
  async save(dto: SaveDto<SysUser>) {
    const result = await super.save(dto)
    if (result.id) {
      await this.clearUserCache(result.id)
    }
    return result
  }
}

最佳实践

1. 参数验证

typescript
// 在 DTO 中定义验证规则
import { IsEmail, IsNotEmpty, MinLength } from 'class-validator'

export class CreateUserDto {
  @IsNotEmpty()
  @MinLength(3)
  username: string

  @IsEmail()
  email: string

  @IsNotEmpty()
  password: string
}

// 在 Service 中使用
async createUser(createUserDto: CreateUserDto) {
  return this.save(createUserDto)
}

2. 错误处理

typescript
@Injectable()
export class UserService extends BaseService<SysUser, UserService> {
  // 统一的错误处理
  async safeSave(dto: SaveDto<SysUser>) {
    try {
      return await this.save(dto)
    } catch (error) {
      // 记录错误日志
      this.logger.error(`保存用户失败: ${error.message}`, error.stack)

      // 抛出自定义错误
      throw new HttpException(
        {
          code: 500,
          message: '操作失败',
          detail: error.message,
        },
        500,
      )
    }
  }
}

3. 性能优化

typescript
// 分页查询优化
async listOptimized(query: QueryListDto) {
  // 只查询需要的字段
  const selectFields = ['id', 'username', 'email', 'createTime']

  const queryOrm = {
    select: selectFields,
    where: { isDelete: '0' },
    order: { createTime: 'DESC' }
  }

  return this.listBy(queryOrm, query)
}

// 批量操作优化
async batchUpdate(users: Partial<SysUser>[]) {
  const queryRunner = this.dataSource.createQueryRunner()
  await queryRunner.connect()
  await queryRunner.startTransaction()

  try {
    const promises = users.map(user =>
      this.repository.update(user.id, user)
    )
    await Promise.all(promises)

    await queryRunner.commitTransaction()
  } catch (error) {
    await queryRunner.rollbackTransaction()
    throw error
  } finally {
    await queryRunner.release()
  }
}

4. 数据安全

typescript
// 敏感字段过滤
async getUserProfile(id: string) {
  const user = await this.getOne({ id })

  // 过滤敏感信息
  const { password, salt, ...safeUser } = user
  return safeUser
}

// 字段级别权限控制
async getUserWithPermission(userId: string, currentUser: string) {
  const user = await this.getOne({ id: userId })

  // 检查当前用户是否有权限查看完整信息
  const hasPermission = await this.checkPermission(currentUser, 'view_sensitive_data')

  if (!hasPermission) {
    delete user.phone
    delete user.email
  }

  return user
}

常见问题

Q: 如何处理复杂的查询条件?

A: 可以在 listBy 方法中动态构建查询条件:

typescript
async complexSearch(query: any) {
  const queryOrm: any = { where: {} }

  // 动态构建条件
  if (query.status) {
    queryOrm.where.status = query.status
  }

  if (query.keyword) {
    queryOrm.where = [
      { username: this.sqlLike(query.keyword) },
      { email: this.sqlLike(query.keyword) }
    ]
  }

  return this.listBy(queryOrm, query)
}

Q: 如何实现软删除的恢复功能?

A: 添加恢复方法:

typescript
async restore(ids: string[], updateUser: string) {
  return this.repository.update(ids, {
    isDelete: '0',
    updateUser
  })
}

Q: 如何处理大数据量的分页查询?

A: 使用游标分页或优化查询:

typescript
// 游标分页
async cursorList(cursor: string, limit: number) {
  const queryOrm = {
    where: {
      id: MoreThan(cursor),
      isDelete: '0'
    },
    order: { id: 'ASC' },
    take: limit
  }

  return this.repository.find(queryOrm)
}

// 查询优化
async optimizedList(query: QueryListDto) {
  // 使用索引字段排序
  const queryOrm = {
    where: { isDelete: '0' },
    order: { id: 'DESC' }, // 使用主键排序提高性能
    select: ['id', 'username', 'createTime'] // 只查询必要字段
  }

  return this.listBy(queryOrm, query)
}

Q: 如何集成第三方服务?

A: 在 Service 中注入其他服务:

typescript
@Injectable()
export class UserService extends BaseService<SysUser, UserService> {
  constructor(
    @InjectRepository(SysUser)
    private readonly userRepository: Repository<SysUser>,
    private readonly emailService: EmailService,
    private readonly smsService: SmsService,
  ) {
    super(SysUser, userRepository)
  }

  async createUserWithNotification(userData: any) {
    const user = await this.save(userData)

    // 发送欢迎邮件
    await this.emailService.sendWelcomeEmail(user.email, user.username)

    // 发送短信通知
    await this.smsService.sendWelcomeSms(user.phone, user.username)

    return user
  }
}