import { Injectable, Logger, NotFoundException } from '@nestjs/common'; import { customAlphabet } from 'nanoid-cjs'; import { db } from '@nice/common'; import { ShareCode, GenerateShareCodeResponse } from './types'; import { Cron, CronExpression } from '@nestjs/schedule'; import { ResourceService } from '@server/models/resource/resource.service'; @Injectable() export class ShareCodeService { private readonly logger = new Logger(ShareCodeService.name); // 生成8位分享码,使用易读的字符 private readonly generateCode = customAlphabet( '23456789ABCDEFGHJKLMNPQRSTUVWXYZ', 8 ); constructor(private readonly resourceService: ResourceService) {} async generateShareCode(fileId: string, fileName?: string): Promise { try { // 检查文件是否存在 const resource = await this.resourceService.findUnique({ where: { fileId }, }); console.log('完整 fileId:', fileId); // 确保与前端一致 if (!resource) { throw new NotFoundException('文件不存在'); } // 生成分享码 const code = this.generateCode(); const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24小时后过期 // 查找是否已有分享码记录 const existingShareCode = await db.shareCode.findUnique({ where: { fileId }, }); if (existingShareCode) { // 更新现有记录,但保留原有文件名 await db.shareCode.update({ where: { fileId }, data: { code, expiresAt, isUsed: false, // 只在没有现有文件名且提供了新文件名时才更新文件名 ...(fileName && !existingShareCode.fileName ? { fileName } : {}) }, }); } else { // 创建新记录 await db.shareCode.create({ data: { code, fileId, expiresAt, isUsed: false, fileName: fileName || null, }, }); } this.logger.log(`Generated share code ${code} for file ${fileId}`); return { code, expiresAt, }; } catch (error) { this.logger.error('Failed to generate share code', error); throw error; } } async validateAndUseCode(code: string): Promise { try { console.log(`尝试验证分享码: ${code}`); // 查找有效的分享码 const shareCode = await db.shareCode.findFirst({ where: { code, isUsed: false, expiresAt: { gt: new Date() }, }, }); console.log('查询结果:', shareCode); if (!shareCode) { console.log('分享码无效或已过期'); return null; } // 标记分享码为已使用 // await db.shareCode.update({ // where: { id: shareCode.id }, // data: { isUsed: true }, // }); // 记录使用日志 this.logger.log(`Share code ${code} used for file ${shareCode.fileId}`); // 返回完整的分享码信息,包括文件名 return shareCode; } catch (error) { this.logger.error('Failed to validate share code', error); return null; } } // 每天清理过期的分享码 @Cron(CronExpression.EVERY_DAY_AT_MIDNIGHT) async cleanupExpiredShareCodes() { try { const result = await db.shareCode.deleteMany({ where: { OR: [ { expiresAt: { lt: new Date() } }, { isUsed: true }, ], }, }); this.logger.log(`Cleaned up ${result.count} expired share codes`); } catch (error) { this.logger.error('Failed to cleanup expired share codes', error); } } // 获取分享码信息 async getShareCodeInfo(code: string): Promise { try { return await db.shareCode.findFirst({ where: { code }, }); } catch (error) { this.logger.error('Failed to get share code info', error); return null; } } // 检查文件是否已经生成过分享码 async hasActiveShareCode(fileId: string): Promise { try { const activeCode = await db.shareCode.findFirst({ where: { fileId, isUsed: false, expiresAt: { gt: new Date() }, }, }); return !!activeCode; } catch (error) { this.logger.error('Failed to check active share code', error); return false; } } // 获取文件的所有分享记录 async getFileShareHistory(fileId: string) { try { return await db.shareCode.findMany({ where: { fileId }, orderBy: { createdAt: 'desc' }, }); } catch (error) { this.logger.error('Failed to get file share history', error); return []; } } }