import { Injectable, Logger, NotFoundException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository, LessThanOrEqual } from 'typeorm'; import { Cron, CronExpression } from '@nestjs/schedule'; import { Notice, NoticeStatus, NoticeType } from './notice.entity'; import { CreateNoticeDto } from './dto/create-notice.dto'; import { NoticeGateway } from './notice.gateway'; @Injectable() export class NoticeService { private readonly logger = new Logger(NoticeService.name); constructor( @InjectRepository(Notice) private readonly noticeRepository: Repository, private readonly noticeGateway: NoticeGateway, ) {} async create(createNoticeDto: CreateNoticeDto): Promise { const notice = this.noticeRepository.create({ ...createNoticeDto, scheduledAt: createNoticeDto.scheduledAt ? new Date(createNoticeDto.scheduledAt) : null, }); const savedNotice = await this.noticeRepository.save(notice); // 如果没有设置计划时间,立即发送 if (!savedNotice.scheduledAt) { await this.sendNotice(savedNotice); } return savedNotice; } async findAll(userId?: number): Promise { const query = this.noticeRepository.createQueryBuilder('notice'); if (userId) { query.where('notice.userId = :userId OR notice.userId IS NULL', { userId }); } return query.orderBy('notice.createdAt', 'DESC').getMany(); } async findById(id: number): Promise { const notice = await this.noticeRepository.findOne({ where: { id } }); if (!notice) { throw new NotFoundException(`Notice with ID ${id} not found`); } return notice; } async markAsRead(id: number, userId?: number): Promise { const notice = await this.findById(id); // 检查权限:只能标记自己的通知或广播通知为已读 if (notice.userId && userId && notice.userId !== userId) { throw new NotFoundException(`Notice with ID ${id} not found`); } notice.status = NoticeStatus.READ; notice.readAt = new Date(); return this.noticeRepository.save(notice); } async getUserUnreadCount(userId: number): Promise { return this.noticeRepository.count({ where: [ { userId, status: NoticeStatus.SENT }, { userId: null, status: NoticeStatus.SENT }, // 广播通知 ], }); } private async sendNotice(notice: Notice): Promise { try { // 通过WebSocket发送通知 if (notice.userId) { // 发送给特定用户 this.noticeGateway.sendToUser(notice.userId, { type: 'notice', data: notice, }); } else { // 广播通知 this.noticeGateway.broadcast({ type: 'notice', data: notice, }); } // 更新状态 notice.status = NoticeStatus.SENT; notice.sentAt = new Date(); await this.noticeRepository.save(notice); this.logger.log(`Notice ${notice.id} sent successfully`); } catch (error) { this.logger.error(`Failed to send notice ${notice.id}:`, error); notice.status = NoticeStatus.FAILED; await this.noticeRepository.save(notice); } } // 定时任务:每分钟检查需要发送的通知 @Cron(CronExpression.EVERY_MINUTE) async handleScheduledNotices(): Promise { const now = new Date(); const pendingNotices = await this.noticeRepository.find({ where: { status: NoticeStatus.PENDING, scheduledAt: LessThanOrEqual(now), }, }); for (const notice of pendingNotices) { await this.sendNotice(notice); } if (pendingNotices.length > 0) { this.logger.log(`Processed ${pendingNotices.length} scheduled notices`); } } // 发送系统通知的便捷方法 async sendSystemNotice(title: string, content: string, userId?: number): Promise { return this.create({ title, content, type: NoticeType.SYSTEM, userId, }); } // 发送广播通知的便捷方法 async sendBroadcast(title: string, content: string): Promise { return this.create({ title, content, type: NoticeType.BROADCAST, }); } }