import { Injectable, Logger, NotFoundException, ForbiddenException, BadRequestException } from '@nestjs/common'; import { InjectModel, InjectConnection } from '@nestjs/sequelize'; import { Sequelize } from 'sequelize-typescript'; import { Op } from 'sequelize'; import { v4 as uuidv4 } from 'uuid'; import * as dayjs from 'dayjs'; import { FamilyGroup } from '../models/family-group.model'; import { FamilyMember } from '../models/family-member.model'; import { User } from '../../users/models/user.model'; import { FamilyRole } from '../enums/health-profile.enum'; import { CreateFamilyGroupDto, GenerateInviteCodeDto, JoinFamilyGroupDto, UpdateFamilyMemberDto, FamilyGroupResponseDto, FamilyMemberResponseDto, } from '../dto/family-health.dto'; @Injectable() export class FamilyHealthService { private readonly logger = new Logger(FamilyHealthService.name); constructor( @InjectModel(FamilyGroup) private readonly familyGroupModel: typeof FamilyGroup, @InjectModel(FamilyMember) private readonly familyMemberModel: typeof FamilyMember, @InjectModel(User) private readonly userModel: typeof User, @InjectConnection() private readonly sequelize: Sequelize, ) {} /** * 获取用户的家庭组 */ async getFamilyGroup(userId: string): Promise { // 先查找用户所属的家庭成员记录 const membership = await this.familyMemberModel.findOne({ where: { userId }, }); if (!membership) { return null; } const familyGroup = await this.familyGroupModel.findOne({ where: { id: membership.familyGroupId }, include: [ { model: FamilyMember, as: 'members', include: [{ model: User, as: 'user' }], }, ], }); if (!familyGroup) { return null; } return this.mapGroupToResponse(familyGroup); } /** * 创建家庭组 */ async createFamilyGroup(userId: string, createDto: CreateFamilyGroupDto): Promise { // 检查用户是否已经有家庭组 const existingMembership = await this.familyMemberModel.findOne({ where: { userId }, }); if (existingMembership) { throw new BadRequestException('您已经是一个家庭组的成员,请先退出当前家庭组'); } const transaction = await this.sequelize.transaction(); try { // 创建家庭组 const familyGroup = await this.familyGroupModel.create( { id: uuidv4(), ownerId: userId, name: createDto.name || '我的家庭', }, { transaction }, ); // 将创建者添加为 owner 成员 await this.familyMemberModel.create( { id: uuidv4(), familyGroupId: familyGroup.id, userId, role: FamilyRole.OWNER, canViewHealthData: true, canManageHealthData: true, receiveAlerts: true, }, { transaction }, ); await transaction.commit(); this.logger.log(`用户 ${userId} 创建家庭组 ${familyGroup.id} 成功`); // 重新查询以获取完整数据 return this.getFamilyGroup(userId) as Promise; } catch (error) { await transaction.rollback(); this.logger.error(`创建家庭组失败: ${error instanceof Error ? error.message : '未知错误'}`); throw error; } } /** * 获取或生成邀请码 * 如果用户没有家庭组,自动创建一个 * 如果已有有效邀请码,直接返回 */ async generateInviteCode( userId: string, dto: GenerateInviteCodeDto, ): Promise<{ familyGroupId: string; inviteCode: string; expiresAt: string; qrCodeUrl: string }> { let membership = await this.familyMemberModel.findOne({ where: { userId }, }); // 如果用户没有家庭组,自动创建一个 if (!membership) { this.logger.log(`用户 ${userId} 没有家庭组,自动创建`); await this.createFamilyGroup(userId, { name: '我的家庭' }); membership = await this.familyMemberModel.findOne({ where: { userId }, }); } // 只有 owner 和 admin 可以生成邀请码 if (membership!.role === FamilyRole.MEMBER) { throw new ForbiddenException('只有管理员可以生成邀请码'); } const familyGroup = await this.familyGroupModel.findByPk(membership!.familyGroupId); if (!familyGroup) { throw new NotFoundException('家庭组不存在'); } // 如果已有有效邀请码,直接返回 if (familyGroup.inviteCode && familyGroup.inviteCodeExpiresAt && dayjs(familyGroup.inviteCodeExpiresAt).isAfter(dayjs())) { this.logger.log(`用户 ${userId} 获取家庭组 ${familyGroup.id} 的现有邀请码 ${familyGroup.inviteCode}`); return { familyGroupId: familyGroup.id, inviteCode: familyGroup.inviteCode, expiresAt: familyGroup.inviteCodeExpiresAt.toISOString(), qrCodeUrl: `outlive://family/join?code=${familyGroup.inviteCode}`, }; } // 生成新邀请码 const inviteCode = this.generateUniqueInviteCode(); const expiresAt = dayjs().add(dto.expiresInHours || 24, 'hour').toDate(); familyGroup.inviteCode = inviteCode; familyGroup.inviteCodeExpiresAt = expiresAt; await familyGroup.save(); this.logger.log(`用户 ${userId} 为家庭组 ${familyGroup.id} 生成邀请码 ${inviteCode}`); return { familyGroupId: familyGroup.id, inviteCode, expiresAt: expiresAt.toISOString(), qrCodeUrl: `outlive://family/join?code=${inviteCode}`, }; } /** * 加入家庭组 */ async joinFamilyGroup(userId: string, dto: JoinFamilyGroupDto): Promise { // 检查用户是否已经有家庭组 const existingMembership = await this.familyMemberModel.findOne({ where: { userId }, }); if (existingMembership) { throw new BadRequestException('您已经是一个家庭组的成员,请先退出当前家庭组'); } // 查找邀请码对应的家庭组 const familyGroup = await this.familyGroupModel.findOne({ where: { inviteCode: dto.inviteCode, inviteCodeExpiresAt: { [Op.gt]: new Date() }, }, }); if (!familyGroup) { throw new BadRequestException('邀请码无效或已过期'); } // 检查成员数量 const memberCount = await this.familyMemberModel.count({ where: { familyGroupId: familyGroup.id }, }); if (memberCount >= familyGroup.maxMembers) { throw new BadRequestException('家庭组已满员'); } // 添加成员 await this.familyMemberModel.create({ id: uuidv4(), familyGroupId: familyGroup.id, userId, role: FamilyRole.MEMBER, canViewHealthData: true, canManageHealthData: false, receiveAlerts: true, }); this.logger.log(`用户 ${userId} 加入家庭组 ${familyGroup.id}`); return this.getFamilyGroup(userId) as Promise; } /** * 获取家庭成员列表 */ async getFamilyMembers(userId: string): Promise { const membership = await this.familyMemberModel.findOne({ where: { userId }, }); if (!membership) { throw new NotFoundException('您还没有家庭组'); } const members = await this.familyMemberModel.findAll({ where: { familyGroupId: membership.familyGroupId }, include: [{ model: User, as: 'user' }], }); return members.map(this.mapMemberToResponse); } /** * 更新成员权限 */ async updateFamilyMember( userId: string, memberId: string, updateDto: UpdateFamilyMemberDto, ): Promise { const currentMembership = await this.familyMemberModel.findOne({ where: { userId }, }); if (!currentMembership) { throw new NotFoundException('您还没有家庭组'); } // 只有 owner 和 admin 可以修改成员权限 if (currentMembership.role === FamilyRole.MEMBER) { throw new ForbiddenException('只有管理员可以修改成员权限'); } const targetMember = await this.familyMemberModel.findOne({ where: { id: memberId, familyGroupId: currentMembership.familyGroupId }, include: [{ model: User, as: 'user' }], }); if (!targetMember) { throw new NotFoundException('成员不存在'); } // 不能修改 owner 的权限 if (targetMember.role === FamilyRole.OWNER && currentMembership.role !== FamilyRole.OWNER) { throw new ForbiddenException('不能修改创建者的权限'); } // 更新权限 if (updateDto.canViewHealthData !== undefined) targetMember.canViewHealthData = updateDto.canViewHealthData; if (updateDto.canManageHealthData !== undefined) targetMember.canManageHealthData = updateDto.canManageHealthData; if (updateDto.receiveAlerts !== undefined) targetMember.receiveAlerts = updateDto.receiveAlerts; if (updateDto.relationship !== undefined) targetMember.relationship = updateDto.relationship; await targetMember.save(); this.logger.log(`用户 ${userId} 更新成员 ${memberId} 的权限`); return this.mapMemberToResponse(targetMember); } /** * 移除家庭成员 */ async removeFamilyMember(userId: string, memberId: string): Promise { const currentMembership = await this.familyMemberModel.findOne({ where: { userId }, }); if (!currentMembership) { throw new NotFoundException('您还没有家庭组'); } const targetMember = await this.familyMemberModel.findOne({ where: { id: memberId, familyGroupId: currentMembership.familyGroupId }, }); if (!targetMember) { throw new NotFoundException('成员不存在'); } // 不能移除 owner if (targetMember.role === FamilyRole.OWNER) { throw new ForbiddenException('不能移除创建者'); } // 只有 owner 和 admin 可以移除成员,或者成员自己退出 if ( currentMembership.role === FamilyRole.MEMBER && currentMembership.id !== memberId ) { throw new ForbiddenException('只有管理员可以移除成员'); } await targetMember.destroy(); this.logger.log(`成员 ${memberId} 已从家庭组移除`); } /** * 退出家庭组 */ async leaveFamilyGroup(userId: string): Promise { const membership = await this.familyMemberModel.findOne({ where: { userId }, }); if (!membership) { throw new NotFoundException('您还没有家庭组'); } // owner 不能直接退出,需要先转让或解散 if (membership.role === FamilyRole.OWNER) { throw new BadRequestException('创建者不能直接退出,请先转让管理权或解散家庭组'); } await membership.destroy(); this.logger.log(`用户 ${userId} 退出家庭组`); } /** * 生成唯一邀请码 */ private generateUniqueInviteCode(): string { const chars = 'ABCDEFGHJKMNPQRSTUVWXYZ23456789'; let code = ''; for (let i = 0; i < 6; i++) { code += chars.charAt(Math.floor(Math.random() * chars.length)); } return code; } /** * 映射家庭组到响应 DTO */ private mapGroupToResponse(group: FamilyGroup): FamilyGroupResponseDto { return { id: group.id, ownerId: group.ownerId, name: group.name, inviteCode: group.inviteCode || undefined, inviteCodeExpiresAt: group.inviteCodeExpiresAt?.toISOString() || undefined, maxMembers: group.maxMembers, members: group.members?.map(this.mapMemberToResponse) || [], createdAt: group.createdAt.toISOString(), updatedAt: group.updatedAt.toISOString(), }; } /** * 映射成员到响应 DTO */ private mapMemberToResponse(member: FamilyMember): FamilyMemberResponseDto { return { id: member.id, userId: member.userId, userName: member.user?.name || '未知用户', userAvatar: member.user?.avatar || undefined, role: member.role, relationship: member.relationship || undefined, canViewHealthData: member.canViewHealthData, canManageHealthData: member.canManageHealthData, receiveAlerts: member.receiveAlerts, joinedAt: member.joinedAt.toISOString(), }; } }