This commit is contained in:
Li1304553726 2025-03-21 17:25:39 +08:00
parent 48c22c9348
commit bed7a4e690
10 changed files with 268 additions and 1541 deletions

View File

@ -1,5 +1,6 @@
import { Injectable } from '@nestjs/common';
import { Injectable ,Logger} from '@nestjs/common';
import { BaseService } from '../base/base.service';
import {
UserProfile,
db,
@ -11,6 +12,7 @@ import {
@Injectable()
export class ResourceService extends BaseService<Prisma.ResourceDelegate> {
private readonly logger = new Logger(ResourceService.name);
constructor() {
super(db, ObjectType.RESOURCE);
}
@ -33,4 +35,53 @@ export class ResourceService extends BaseService<Prisma.ResourceDelegate> {
},
});
}
// 添加保存文件名的方法
async saveFileName(fileId: string, fileName: string): Promise<void> {
try {
this.logger.log(`尝试保存文件名 "${fileName}" 到文件 ${fileId}`);
// 首先检查是否已存在 ShareCode 记录
const existingShareCode = await db.shareCode.findUnique({
where: { fileId },
});
if (existingShareCode) {
// 如果记录存在,更新文件名
await db.shareCode.update({
where: { fileId },
data: { fileName },
});
this.logger.log(`更新了现有记录的文件名 "${fileName}" 到文件 ${fileId}`);
} else {
// 如果记录不存在,创建新记录
await db.shareCode.create({
data: {
fileId,
fileName,
code: null, // 这里可以设置为 null 或生成一个临时码
expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000), // 24小时后过期
isUsed: false,
},
});
this.logger.log(`创建了新记录并保存文件名 "${fileName}" 到文件 ${fileId}`);
}
} catch (error) {
this.logger.error(`保存文件名失败文件ID: ${fileId}`, error);
throw error;
}
}
// 添加获取文件名的方法
async getFileName(fileId: string): Promise<string | null> {
try {
const shareCode = await db.shareCode.findUnique({
where: { fileId },
select: { fileName: true },
});
return shareCode?.fileName || null;
} catch (error) {
this.logger.error(`Failed to get filename for ${fileId}`, error);
return null;
}
}
}

View File

@ -16,7 +16,7 @@ export class ShareCodeService {
constructor(private readonly resourceService: ResourceService) {}
async generateShareCode(fileId: string): Promise<GenerateShareCodeResponse> {
async generateShareCode(fileId: string, fileName?: string): Promise<GenerateShareCodeResponse> {
try {
// 检查文件是否存在
const resource = await this.resourceService.findUnique({
@ -32,16 +32,37 @@ export class ShareCodeService {
const code = this.generateCode();
const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24小时后过期
// 创建分享码记录
await db.shareCode.create({
data: {
code,
fileId,
expiresAt,
isUsed: false,
},
// 查找是否已有分享码记录
const existingShareCode = await db.shareCode.findUnique({
where: { fileId },
});
if (existingShareCode) {
// 更新现有记录
await db.shareCode.update({
where: { fileId },
data: {
fileName: fileName || null,
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 {
@ -56,6 +77,8 @@ export class ShareCodeService {
async validateAndUseCode(code: string): Promise<string | null> {
try {
console.log(`尝试验证分享码: ${code}`);
// 查找有效的分享码
const shareCode = await db.shareCode.findFirst({
where: {
@ -65,7 +88,10 @@ export class ShareCodeService {
},
});
console.log('查询结果:', shareCode);
if (!shareCode) {
console.log('分享码无效或已过期');
return null;
}

View File

@ -27,6 +27,7 @@ export interface ShareCode {
createdAt: Date;
expiresAt: Date;
isUsed: boolean;
fileName?: string;
}
export interface GenerateShareCodeResponse {

View File

@ -13,15 +13,18 @@ import {
NotFoundException,
HttpException,
HttpStatus,
Body,
} from '@nestjs/common';
import { Request, Response } from 'express';
import { TusService } from './tus.service';
import { ShareCodeService } from './share-code.service';
import { ResourceService } from '@server/models/resource/resource.service';
@Controller('upload')
export class UploadController {
constructor(
private readonly tusService: TusService,
private readonly shareCodeService: ShareCodeService,
private readonly resourceService: ResourceService,
) {}
// @Post()
// async handlePost(@Req() req: Request, @Res() res: Response) {
@ -42,6 +45,28 @@ export class UploadController {
async handlePost(@Req() req: Request, @Res() res: Response) {
return this.tusService.handleTus(req, res);
}
@Get('share/:code')
async validateShareCode(@Param('code') code: string) {
const fileId = await this.shareCodeService.validateAndUseCode(code);
if (!fileId) {
throw new NotFoundException('分享码无效或已过期');
}
return { fileId };
}
@Get('share/info/:code')
async getShareCodeInfo(@Param('code') code: string) {
const info = await this.shareCodeService.getShareCodeInfo(code);
if (!info) {
throw new NotFoundException('分享码不存在');
}
return info;
}
@Get('share/history/:fileId')
async getFileShareHistory(@Param('fileId') fileId: string) {
return this.shareCodeService.getFileShareHistory(fileId);
}
@Get('/*')
async handleGet(@Req() req: Request, @Res() res: Response) {
return this.tusService.handleTus(req, res);
@ -76,26 +101,61 @@ export class UploadController {
}
}
@Get('share/:code')
async validateShareCode(@Param('code') code: string) {
const fileId = await this.shareCodeService.validateAndUseCode(code);
if (!fileId) {
throw new NotFoundException('分享码无效或已过期');
@Post('filename')
async saveFileName(@Body() data: { fileId: string; fileName: string }) {
try {
console.log('收到保存文件名请求:', data);
// 检查参数
if (!data.fileId || !data.fileName) {
throw new HttpException(
{ message: '缺少必要参数' },
HttpStatus.BAD_REQUEST
);
}
// 保存文件名
await this.resourceService.saveFileName(data.fileId, data.fileName);
console.log('文件名保存成功:', data.fileName, '对应文件ID:', data.fileId);
return { success: true };
} catch (error) {
console.error('保存文件名失败:', error);
throw new HttpException(
{
message: '保存文件名失败',
error: (error instanceof Error) ? error.message : String(error)
},
HttpStatus.INTERNAL_SERVER_ERROR
);
}
return { fileId };
}
@Get('share/info/:code')
async getShareCodeInfo(@Param('code') code: string) {
const info = await this.shareCodeService.getShareCodeInfo(code);
if (!info) {
throw new NotFoundException('分享码不存在');
@Get('download/:fileId')
async downloadFile(@Param('fileId') fileId: string, @Res() res: Response) {
try {
// 获取文件信息
const resource = await this.resourceService.findUnique({
where: { fileId },
});
if (!resource) {
throw new NotFoundException('文件不存在');
}
// 获取原始文件名
const fileName = await this.resourceService.getFileName(fileId) || 'downloaded-file';
// 设置响应头,包含原始文件名
res.setHeader(
'Content-Disposition',
`attachment; filename="${encodeURIComponent(fileName)}"`
);
// 其他下载逻辑...
} catch (error) {
// 错误处理...
}
return info;
}
@Get('share/history/:fileId')
async getFileShareHistory(@Param('fileId') fileId: string) {
return this.shareCodeService.getFileShareHistory(fileId);
}
}

View File

@ -60,7 +60,6 @@
"framer-motion": "^11.15.0",
"hls.js": "^1.5.18",
"idb-keyval": "^6.2.1",
"mind-elixir": "workspace:^",
"mitt": "^3.0.1",
"quill": "2.0.3",
"react": "18.2.0",

View File

@ -9,6 +9,7 @@ const { TabPane } = Tabs;
export default function DeptSettingPage() {
const [uploadedFileId, setUploadedFileId] = useState<string>('');
const [fileNameMap, setFileNameMap] = useState<Record<string, string>>({});
// 使用您的 useTusUpload hook
const { uploadProgress, isUploading, uploadError, handleFileUpload } = useTusUpload({
@ -24,10 +25,47 @@ export default function DeptSettingPage() {
// 处理文件上传
const handleFileSelect = async (file: File) => {
const fileKey = `file-${Date.now()}`; // 生成唯一的文件标识
handleFileUpload(
file,
(result) => {
async (result) => {
setUploadedFileId(result.fileId);
// 在前端保存文件名映射(用于当前会话)
setFileNameMap(prev => ({
...prev,
[result.fileId]: file.name
}));
// 上传成功后保存原始文件名到数据库
try {
console.log('正在保存文件名到数据库:', file.name, '对应文件ID:', result.fileId);
const response = await fetch('/api/upload/filename', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
fileId: result.fileId,
fileName: file.name
}),
});
const responseText = await response.text();
console.log('保存文件名响应:', response.status, responseText);
if (!response.ok) {
console.error('保存文件名失败:', responseText);
message.warning('文件名保存失败,下载时可能无法显示原始文件名');
} else {
console.log('文件名保存成功:', file.name);
}
} catch (error) {
console.error('保存文件名请求失败:', error);
message.warning('文件名保存失败,下载时可能无法显示原始文件名');
}
message.success('文件上传成功');
},
(error) => {
@ -54,20 +92,38 @@ export default function DeptSettingPage() {
// 获取文件名
const contentDisposition = response.headers.get('content-disposition');
let filename = 'downloaded-file';
console.log('Content-Disposition 头:', contentDisposition);
let filename = fileNameMap[fileId] || 'downloaded-file'; // 优先使用本地缓存的文件名
if (contentDisposition) {
const matches = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/.exec(contentDisposition);
if (matches != null && matches[1]) {
// 改进文件名提取逻辑
const filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
const matches = filenameRegex.exec(contentDisposition);
if (matches && matches[1]) {
// 移除引号并解码 URL 编码的文件名
filename = matches[1].replace(/['"]/g, '');
try {
// 尝试解码 URL 编码的文件名
filename = decodeURIComponent(filename);
} catch (e) {
console.warn('文件名解码失败,使用原始文件名');
}
}
}
console.log('提取的文件名:', filename);
// 创建下载链接
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
// 使用获取到的文件名或本地缓存的文件名
link.download = filename;
// 触发下载
document.body.appendChild(link);
link.click();
document.body.removeChild(link);

View File

@ -21,13 +21,13 @@ export const ShareCodeGenerator: React.FC<ShareCodeGeneratorProps> = ({
console.log('开始生成分享码fileId:', fileId);
try {
const response = await fetch(`/upload/share/${fileId}`, {
const response = await fetch(`http://localhost:3000/upload/share/${fileId}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
})
// headers: {
// 'Content-Type': 'application/json',
// },
// body: JSON.stringify({
// })
});
console.log('Current fileId:', fileId); // 确保 fileId 有效
console.log('请求URL:', `/upload/share/${fileId}`);

View File

@ -20,13 +20,12 @@ export const ShareCodeValidator: React.FC<ShareCodeValidatorProps> = ({
setLoading(true);
try {
const response = await fetch(`/api/upload/share/${code.trim()}`);
const response = await fetch(`http://localhost:3000/upload/share/${code.trim()}`);
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.message || '分享码无效或已过期');
}
const data = await response.json();
onValidSuccess(data.fileId);
message.success('验证成功');

View File

@ -539,12 +539,12 @@ model TrainPlan {
model ShareCode {
id String @id @default(cuid())
code String @unique
fileId String
code String? @unique
fileId String? @unique
createdAt DateTime @default(now())
expiresAt DateTime
isUsed Boolean @default(false)
expiresAt DateTime? @map("expires_at")
isUsed Boolean? @default(false)
fileName String? @map("file_name")
@@index([code])
@@index([fileId])
@@index([expiresAt])

File diff suppressed because it is too large Load Diff