240 lines
6.4 KiB
TypeScript
240 lines
6.4 KiB
TypeScript
|
import { Injectable, Logger, NotFoundException } from '@nestjs/common';
|
|||
|
import { customAlphabet } from 'nanoid-cjs';
|
|||
|
import { db } from '@nice/common';
|
|||
|
import { Cron, CronExpression } from '@nestjs/schedule';
|
|||
|
import { ResourceService } from '@server/models/resource/resource.service';
|
|||
|
|
|||
|
export interface ShareCode {
|
|||
|
id: string;
|
|||
|
code: string;
|
|||
|
fileId: string;
|
|||
|
createdAt: Date;
|
|||
|
expiresAt: Date;
|
|||
|
isUsed: boolean;
|
|||
|
fileName?: string | null;
|
|||
|
}
|
|||
|
export interface GenerateShareCodeResponse {
|
|||
|
code: string;
|
|||
|
expiresAt: Date;
|
|||
|
}
|
|||
|
|
|||
|
interface ResourceMeta {
|
|||
|
filename: string;
|
|||
|
filetype: string;
|
|||
|
filesize: string;
|
|||
|
}
|
|||
|
|
|||
|
@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<GenerateShareCodeResponse> {
|
|||
|
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<ShareCode | null> {
|
|||
|
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<ShareCode | null> {
|
|||
|
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<boolean> {
|
|||
|
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 [];
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// 根据分享码获取文件
|
|||
|
async getFileByShareCode(code: string) {
|
|||
|
console.log('收到验证分享码请求,code:', code);
|
|||
|
|
|||
|
const shareCode = await this.validateAndUseCode(code);
|
|||
|
console.log('验证分享码结果:', shareCode);
|
|||
|
|
|||
|
if (!shareCode) {
|
|||
|
throw new NotFoundException('分享码无效或已过期');
|
|||
|
}
|
|||
|
|
|||
|
// 获取文件信息
|
|||
|
const resource = await this.resourceService.findUnique({
|
|||
|
where: { fileId: shareCode.fileId },
|
|||
|
});
|
|||
|
console.log('获取到的资源信息:', resource);
|
|||
|
const { filename } = resource.meta as any as ResourceMeta
|
|||
|
if (!resource) {
|
|||
|
throw new NotFoundException('文件不存在');
|
|||
|
}
|
|||
|
|
|||
|
// 直接返回正确的数据结构
|
|||
|
const response = {
|
|||
|
fileId: shareCode.fileId,
|
|||
|
fileName: filename || 'downloaded_file',
|
|||
|
code: shareCode.code,
|
|||
|
expiresAt: shareCode.expiresAt
|
|||
|
};
|
|||
|
|
|||
|
console.log('返回给前端的数据:', response); // 添加日志
|
|||
|
return response;
|
|||
|
}
|
|||
|
|
|||
|
// 根据文件ID生成分享码
|
|||
|
async generateShareCodeByFileId(fileId: string) {
|
|||
|
try {
|
|||
|
console.log('收到生成分享码请求,fileId:', fileId);
|
|||
|
const result = await this.generateShareCode(fileId);
|
|||
|
console.log('生成分享码结果:', result);
|
|||
|
return result;
|
|||
|
} catch (error) {
|
|||
|
console.error('生成分享码错误:', error);
|
|||
|
return error
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
}
|