book_manage/apps/server/src/models/share-code/share-code.service.ts

240 lines
6.4 KiB
TypeScript
Raw Normal View History

2025-04-02 21:59:19 +08:00
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
}
}
}