import { Injectable, NotFoundException, BadRequestException, ForbiddenException, } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { Group } from '../../entities/group.entity'; import { GroupMember } from '../../entities/group-member.entity'; import { User } from '../../entities/user.entity'; import { CreateGroupDto, UpdateGroupDto, JoinGroupDto } from './dto/group.dto'; import { GroupMemberRole } from '../../common/enums'; import { ErrorCode, ErrorMessage } from '../../common/interfaces/response.interface'; import { CacheService } from '../../common/services/cache.service'; @Injectable() export class GroupsService { private readonly CACHE_PREFIX = 'group'; private readonly CACHE_TTL = 300; // 5 minutes constructor( @InjectRepository(Group) private groupRepository: Repository, @InjectRepository(GroupMember) private groupMemberRepository: Repository, @InjectRepository(User) private userRepository: Repository, private cacheService: CacheService, ) {} /** * 创建小组 */ async create(userId: string, createGroupDto: CreateGroupDto) { const user = await this.userRepository.findOne({ where: { id: userId } }); if (!user) { throw new NotFoundException({ code: ErrorCode.USER_NOT_FOUND, message: ErrorMessage[ErrorCode.USER_NOT_FOUND], }); } // 检查用户创建的小组数量 const ownedGroupsCount = await this.groupRepository.count({ where: { ownerId: userId }, }); if (!user.isMember && ownedGroupsCount >= 1) { throw new BadRequestException({ code: ErrorCode.GROUP_LIMIT_EXCEEDED, message: '非会员最多只能创建1个小组', }); } if (user.isMember && ownedGroupsCount >= 10) { throw new BadRequestException({ code: ErrorCode.GROUP_LIMIT_EXCEEDED, message: '会员最多只能创建10个小组', }); } // 如果是创建子组,检查父组是否存在且用户是否为会员 if (createGroupDto.parentId) { if (!user.isMember) { throw new ForbiddenException({ code: ErrorCode.NO_PERMISSION, message: '非会员不能创建子组', }); } const parentGroup = await this.groupRepository.findOne({ where: { id: createGroupDto.parentId }, }); if (!parentGroup) { throw new NotFoundException({ code: ErrorCode.GROUP_NOT_FOUND, message: '父组不存在', }); } } // 创建小组 const group = this.groupRepository.create({ ...createGroupDto, ownerId: userId, maxMembers: createGroupDto.maxMembers || 50, }); await this.groupRepository.save(group); // 将创建者添加为小组成员(角色为 owner) const member = this.groupMemberRepository.create({ groupId: group.id, userId: userId, role: GroupMemberRole.OWNER, }); await this.groupMemberRepository.save(member); return this.findOne(group.id); } /** * 加入小组(使用原子更新防止并发竞态条件) */ async join(userId: string, joinGroupDto: JoinGroupDto) { const { groupId, nickname } = joinGroupDto; const user = await this.userRepository.findOne({ where: { id: userId } }); if (!user) { throw new NotFoundException({ code: ErrorCode.USER_NOT_FOUND, message: ErrorMessage[ErrorCode.USER_NOT_FOUND], }); } const group = await this.groupRepository.findOne({ where: { id: groupId } }); if (!group) { throw new NotFoundException({ code: ErrorCode.GROUP_NOT_FOUND, message: ErrorMessage[ErrorCode.GROUP_NOT_FOUND], }); } // 检查是否已经是成员 const existingMember = await this.groupMemberRepository.findOne({ where: { groupId, userId }, }); if (existingMember) { throw new BadRequestException({ code: ErrorCode.ALREADY_IN_GROUP, message: ErrorMessage[ErrorCode.ALREADY_IN_GROUP], }); } // 检查用户加入的小组数量 const joinedGroupsCount = await this.groupMemberRepository.count({ where: { userId }, }); if (!user.isMember && joinedGroupsCount >= 3) { throw new BadRequestException({ code: ErrorCode.JOIN_GROUP_LIMIT_EXCEEDED, message: ErrorMessage[ErrorCode.JOIN_GROUP_LIMIT_EXCEEDED], }); } // 使用原子更新:只有当当前成员数小于最大成员数时才成功 const updateResult = await this.groupRepository .createQueryBuilder() .update(Group) .set({ currentMembers: () => 'currentMembers + 1', }) .where('id = :id', { id: groupId }) .andWhere('currentMembers < maxMembers') .execute(); // 如果影响的行数为0,说明小组已满 if (updateResult.affected === 0) { throw new BadRequestException({ code: ErrorCode.GROUP_FULL, message: ErrorMessage[ErrorCode.GROUP_FULL], }); } // 添加成员记录 const member = this.groupMemberRepository.create({ groupId, userId, nickname, role: GroupMemberRole.MEMBER, }); await this.groupMemberRepository.save(member); return this.findOne(groupId); } /** * 退出小组 */ async leave(userId: string, groupId: string) { const member = await this.groupMemberRepository.findOne({ where: { groupId, userId }, }); if (!member) { throw new NotFoundException({ code: ErrorCode.NOT_IN_GROUP, message: ErrorMessage[ErrorCode.NOT_IN_GROUP], }); } // 组长不能直接退出 if (member.role === GroupMemberRole.OWNER) { throw new BadRequestException({ code: ErrorCode.NO_PERMISSION, message: '组长不能退出小组,请先转让组长或解散小组', }); } await this.groupMemberRepository.remove(member); // 更新小组成员数 const group = await this.groupRepository.findOne({ where: { id: groupId } }); if (group) { group.currentMembers = Math.max(0, group.currentMembers - 1); await this.groupRepository.save(group); } return { message: '退出成功' }; } /** * 获取小组详情 */ async findOne(id: string) { // 尝试从缓存获取 const cached = this.cacheService.get(id, { prefix: this.CACHE_PREFIX, }); if (cached) { return cached; } const group = await this.groupRepository.findOne({ where: { id }, relations: ['owner', 'members', 'members.user'], }); if (!group) { throw new NotFoundException({ code: ErrorCode.GROUP_NOT_FOUND, message: ErrorMessage[ErrorCode.GROUP_NOT_FOUND], }); } const result = { ...group, members: group.members.map((member) => ({ id: member.id, userId: member.userId, username: member.user.username, avatar: member.user.avatar, nickname: member.nickname, role: member.role, joinedAt: member.joinedAt, })), }; // 缓存结果 this.cacheService.set(id, result, { prefix: this.CACHE_PREFIX, ttl: this.CACHE_TTL, }); return result; } /** * 获取用户的小组列表 */ async findUserGroups(userId: string) { const members = await this.groupMemberRepository.find({ where: { userId }, relations: ['group', 'group.owner'], }); return members.map((member) => ({ ...member.group, myRole: member.role, myNickname: member.nickname, })); } /** * 更新小组信息 */ async update(userId: string, groupId: string, updateGroupDto: UpdateGroupDto) { const group = await this.groupRepository.findOne({ where: { id: groupId } }); if (!group) { throw new NotFoundException({ code: ErrorCode.GROUP_NOT_FOUND, message: ErrorMessage[ErrorCode.GROUP_NOT_FOUND], }); } // 检查权限(只有组长和管理员可以修改) await this.checkPermission(userId, groupId, [ GroupMemberRole.OWNER, GroupMemberRole.ADMIN, ]); Object.assign(group, updateGroupDto); await this.groupRepository.save(group); // 清除缓存 this.cacheService.del(groupId, { prefix: this.CACHE_PREFIX }); return this.findOne(groupId); } /** * 设置成员角色 */ async updateMemberRole( userId: string, groupId: string, targetUserId: string, role: GroupMemberRole, ) { // 只有组长可以设置管理员 await this.checkPermission(userId, groupId, [GroupMemberRole.OWNER]); const member = await this.groupMemberRepository.findOne({ where: { groupId, userId: targetUserId }, }); if (!member) { throw new NotFoundException({ code: ErrorCode.NOT_IN_GROUP, message: '该用户不在小组中', }); } // 不能修改组长角色 if (member.role === GroupMemberRole.OWNER) { throw new BadRequestException({ code: ErrorCode.NO_PERMISSION, message: '不能修改组长角色', }); } member.role = role; await this.groupMemberRepository.save(member); return { message: '角色设置成功' }; } /** * 踢出成员 */ async kickMember(userId: string, groupId: string, targetUserId: string) { // 组长和管理员可以踢人 await this.checkPermission(userId, groupId, [ GroupMemberRole.OWNER, GroupMemberRole.ADMIN, ]); const member = await this.groupMemberRepository.findOne({ where: { groupId, userId: targetUserId }, }); if (!member) { throw new NotFoundException({ code: ErrorCode.NOT_IN_GROUP, message: '该用户不在小组中', }); } // 不能踢出组长 if (member.role === GroupMemberRole.OWNER) { throw new BadRequestException({ code: ErrorCode.NO_PERMISSION, message: '不能踢出组长', }); } await this.groupMemberRepository.remove(member); // 更新小组成员数 const group = await this.groupRepository.findOne({ where: { id: groupId } }); if (group) { group.currentMembers = Math.max(0, group.currentMembers - 1); await this.groupRepository.save(group); } return { message: '成员已移除' }; } /** * 解散小组 */ async disband(userId: string, groupId: string) { const group = await this.groupRepository.findOne({ where: { id: groupId } }); if (!group) { throw new NotFoundException({ code: ErrorCode.GROUP_NOT_FOUND, message: ErrorMessage[ErrorCode.GROUP_NOT_FOUND], }); } // 只有组长可以解散 if (group.ownerId !== userId) { throw new ForbiddenException({ code: ErrorCode.NO_PERMISSION, message: '只有组长可以解散小组', }); } group.isActive = false; await this.groupRepository.save(group); return { message: '小组已解散' }; } /** * 检查权限 */ private async checkPermission( userId: string, groupId: string, allowedRoles: GroupMemberRole[], ) { const member = await this.groupMemberRepository.findOne({ where: { groupId, userId }, }); if (!member) { throw new ForbiddenException({ code: ErrorCode.NOT_IN_GROUP, message: ErrorMessage[ErrorCode.NOT_IN_GROUP], }); } if (!allowedRoles.includes(member.role)) { throw new ForbiddenException({ code: ErrorCode.NO_PERMISSION, message: ErrorMessage[ErrorCode.NO_PERMISSION], }); } } }