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

NestJS关注/取关功能实现报错排查与优化方案求助

NestJS 关注/取关功能问题排查与最优实现

核心错误分析与排查

1. TypeScript类型错误:number无法赋值给User类型

这种错误几乎都是因为直接将用户ID(number)赋值给了User类型的关联数组。比如在User实体中定义了followers: User[]或followings: User[],但代码里直接执行了user.followings.push(targetUserId),这里targetUserId是number类型,和数组的User类型不匹配。

排查步骤:

  • 检查User实体的关联字段定义,确认是User[]类型而非number[]
  • 查看service层代码,避免直接给关联数组push数字ID,必须传入User实例,或用TypeORM的关联API操作

2. SQL查询id为NaN的报错

报错提示“列nan不存在”,说明代码中把NaN当作用户ID传入了SQL语句,常见原因:

  • 参数解析错误:比如@Param('id')获取的是字符串,未做有效转换(如空字符串、非数字字符串转成NaN)
  • 当前用户ID获取失败:自定义的GetCurrentUserId装饰器返回了undefined,后续转成NaN
  • 缺少参数验证:未对传入的ID做数字校验

排查步骤:

  • 在service方法开头添加日志,打印传入的userId和targetId,确认是否存在NaN
  • 控制器参数使用ParseIntPipe强制转换为数字,比如@Param('id', ParseIntPipe) targetId: number
  • 检查GetCurrentUserId装饰器的实现,确保能正确获取到登录用户的有效ID

最优关注/取关实现方案(基于TypeORM)

1. User实体定义(User.entity.ts)

正确配置多对多关联,指定中间表结构:

import { Entity, PrimaryGeneratedColumn, Column, ManyToMany, JoinTable } from 'typeorm';

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

  @Column({ unique: true })
  username: string;

  // 我的粉丝(关注我的人)
  @ManyToMany(() => User, user => user.followings)
  @JoinTable({
    name: 'user_follow_relation',
    joinColumn: { name: 'followed_id', referencedColumnName: 'id' },
    inverseJoinColumn: { name: 'follower_id', referencedColumnName: 'id' },
  })
  followers: User[];

  // 我关注的人
  @ManyToMany(() => User, user => user.followers)
  followings: User[];
}

2. Service层实现(users.service.ts)

使用TypeORM的关联API或QueryBuilder操作,避免类型错误,同时增加重复关注/取关的校验:

import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';

@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(User)
    private usersRepository: Repository<User>,
  ) {}

  // 关注用户
  async followUser(followerId: number, targetId: number) {
    // 禁止关注自己
    if (followerId === targetId) {
      throw new BadRequestException('无法关注自己');
    }

    // 检查用户是否存在
    const [follower, targetUser] = await Promise.all([
      this.usersRepository.findOneBy({ id: followerId }),
      this.usersRepository.findOneBy({ id: targetId }),
    ]);

    if (!follower || !targetUser) {
      throw new NotFoundException('目标用户不存在');
    }

    // 检查是否已关注
    const isFollowed = await this.usersRepository
      .createQueryBuilder('user')
      .innerJoin('user.followings', 'following')
      .where('user.id = :followerId', { followerId })
      .andWhere('following.id = :targetId', { targetId })
      .getExists();

    if (isFollowed) {
      return { message: '已关注该用户' };
    }

    // 建立关联
    await this.usersRepository
      .createQueryBuilder()
      .relation(User, 'followings')
      .of(followerId)
      .add(targetId);

    return { message: '关注成功' };
  }

  // 取关用户
  async unfollowUser(followerId: number, targetId: number) {
    const [follower, targetUser] = await Promise.all([
      this.usersRepository.findOneBy({ id: followerId }),
      this.usersRepository.findOneBy({ id: targetId }),
    ]);

    if (!follower || !targetUser) {
      throw new NotFoundException('目标用户不存在');
    }

    // 检查是否已关注
    const isFollowed = await this.usersRepository
      .createQueryBuilder('user')
      .innerJoin('user.followings', 'following')
      .where('user.id = :followerId', { followerId })
      .andWhere('following.id = :targetId', { targetId })
      .getExists();

    if (!isFollowed) {
      return { message: '未关注该用户' };
    }

    // 移除关联
    await this.usersRepository
      .createQueryBuilder()
      .relation(User, 'followings')
      .of(followerId)
      .remove(targetId);

    return { message: '取关成功' };
  }

  // 获取用户关注列表
  async getFollowings(userId: number) {
    return this.usersRepository.findOne({
      where: { id: userId },
      relations: ['followings'],
      select: ['id', 'username'],
    });
  }

  // 获取用户粉丝列表
  async getFollowers(userId: number) {
    return this.usersRepository.findOne({
      where: { id: userId },
      relations: ['followers'],
      select: ['id', 'username'],
    });
  }
}

3. Controller层实现(users.controller.ts)

使用管道确保参数合法性,简化逻辑:

import { Controller, Post, Delete, Param, UsePipes, ValidationPipe, Get } from '@nestjs/common';
import { UsersService } from './users.service';
import { ParseIntPipe } from '@nestjs/common';
import { GetCurrentUserId } from '../auth/decorators/get-current-user-id.decorator';

@Controller('users')
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  @Post(':targetId/follow')
  @UsePipes(ValidationPipe)
  followUser(
    @GetCurrentUserId() userId: number,
    @Param('targetId', ParseIntPipe) targetId: number,
  ) {
    return this.usersService.followUser(userId, targetId);
  }

  @Delete(':targetId/unfollow')
  @UsePipes(ValidationPipe)
  unfollowUser(
    @GetCurrentUserId() userId: number,
    @Param('targetId', ParseIntPipe) targetId: number,
  ) {
    return this.usersService.unfollowUser(userId, targetId);
  }

  @Get(':userId/followings')
  getFollowings(@Param('userId', ParseIntPipe) userId: number) {
    return this.usersService.getFollowings(userId);
  }

  @Get(':userId/followers')
  getFollowers(@Param('userId', ParseIntPipe) userId: number) {
    return this.usersService.getFollowers(userId);
  }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 18:27:21