175 lines
4.8 KiB
TypeScript
Executable File
175 lines
4.8 KiB
TypeScript
Executable File
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<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 [];
|
||
}
|
||
}
|
||
} |