add
This commit is contained in:
parent
48c22c9348
commit
bed7a4e690
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ export interface ShareCode {
|
|||
createdAt: Date;
|
||||
expiresAt: Date;
|
||||
isUsed: boolean;
|
||||
fileName?: string;
|
||||
}
|
||||
|
||||
export interface GenerateShareCodeResponse {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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}`);
|
||||
|
|
|
@ -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('验证成功');
|
||||
|
|
|
@ -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])
|
||||
|
|
1529
pnpm-lock.yaml
1529
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue