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], }); } } }