From 0dad0e18ec5588784eb3eccc590220688b0a692f Mon Sep 17 00:00:00 2001 From: Rao <1227431568@qq.com> Date: Thu, 3 Apr 2025 13:27:58 +0800 Subject: [PATCH] rht --- apps/server/package.json | 2 +- .../models/share-code/share-code.service.ts | 90 ++++-- .../src/app/admin/deptsettingpage/page.tsx | 256 ++++-------------- .../admin/sharecode/sharecodegenerator.tsx | 31 ++- .../admin/sharecode/sharecodevalidator.tsx | 10 +- pnpm-lock.yaml | 2 +- web-dist.zip | Bin 0 -> 443118 bytes 7 files changed, 168 insertions(+), 223 deletions(-) create mode 100644 web-dist.zip diff --git a/apps/server/package.json b/apps/server/package.json index 6e80ee0..c87f443 100755 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -27,7 +27,7 @@ "@nestjs/jwt": "^10.2.0", "@nestjs/platform-express": "^10.0.0", "@nestjs/platform-socket.io": "^10.3.10", - "@nestjs/schedule": "^4.1.0", + "@nestjs/schedule": "^4.1.2", "@nestjs/websockets": "^10.3.10", "@nice/common": "workspace:*", "@nice/tus": "workspace:*", diff --git a/apps/server/src/models/share-code/share-code.service.ts b/apps/server/src/models/share-code/share-code.service.ts index bc3be5a..3adf097 100755 --- a/apps/server/src/models/share-code/share-code.service.ts +++ b/apps/server/src/models/share-code/share-code.service.ts @@ -3,6 +3,9 @@ import { customAlphabet } from 'nanoid-cjs'; import { db } from '@nice/common'; import { Cron, CronExpression } from '@nestjs/schedule'; import { ResourceService } from '@server/models/resource/resource.service'; +import * as fs from 'fs' +import * as path from 'path' + export interface ShareCode { id: string; @@ -44,7 +47,7 @@ export class ShareCodeService { const resource = await this.resourceService.findUnique({ where: { fileId }, }); - console.log('完整 fileId:', fileId); // 确保与前端一致 + this.logger.log('完整 fileId:', fileId); // 确保与前端一致 if (!resource) { throw new NotFoundException('文件不存在'); @@ -53,7 +56,7 @@ export class ShareCodeService { // 生成分享码 const code = this.generateCode(); const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24小时后过期 - + //const expiresAt = new Date(Date.now() + 10 * 1000); // 24小时后过期 // 查找是否已有分享码记录 const existingShareCode = await db.shareCode.findUnique({ where: { fileId }, @@ -96,7 +99,7 @@ export class ShareCodeService { async validateAndUseCode(code: string): Promise { try { - console.log(`尝试验证分享码: ${code}`); + this.logger.log(`尝试验证分享码: ${code}`); // 查找有效的分享码 const shareCode = await db.shareCode.findFirst({ @@ -107,10 +110,10 @@ export class ShareCodeService { }, }); - console.log('查询结果:', shareCode); + this.logger.log('查询结果:', shareCode); if (!shareCode) { - console.log('分享码无效或已过期'); + this.logger.log('分享码无效或已过期'); return null; } @@ -132,21 +135,74 @@ export class ShareCodeService { } // 每天清理过期的分享码 + //@Cron('*/30 * * * * *') @Cron(CronExpression.EVERY_DAY_AT_MIDNIGHT) async cleanupExpiredShareCodes() { try { - const result = await db.shareCode.deleteMany({ + const shareCodes = await db.shareCode.findMany({ where: { OR: [{ expiresAt: { lt: new Date() } }, { isUsed: true }], + } + }) + this.logger.log('需要清理的分享码:', shareCodes); + shareCodes.forEach(code => { + this.cleanupUploadFolder(code.fileId); + }) + const result = await db.shareCode.deleteMany({ + where: { + fileId: { + in: shareCodes.map(code => code.fileId) + } }, }); - - this.logger.log(`Cleaned up ${result.count} expired share codes`); + const deleteResource = await this.resourceService.deleteMany({ + where: { + fileId: { + in: shareCodes.map(code => code.fileId) + } + } + }) + this.logger.log(`Cleaned up ${result.count} ${deleteResource.count} expired share codes`); } catch (error) { this.logger.error('Failed to cleanup expired share codes', error); } } + async cleanupUploadFolder(file?: string) { + //const uploadDir = path.join(__dirname, '../../../uploads'); + const uploadDir = path.join('/data/uploads/', file || ''); + this.logger.log('uploadDir:', uploadDir); + try { + if (!fs.existsSync(uploadDir)) { + this.logger.warn(`Upload directory does not exist: ${uploadDir}`); + return; + } + // 递归删除文件夹及其内容 + this.deleteFolderRecursive(uploadDir); + this.logger.log(`Cleaned up upload folder: ${uploadDir}`); + } catch (error) { + this.logger.error('读取上传目录失败:', error); + return; + } + } + private deleteFolderRecursive(dirPath: string) { + if (fs.existsSync(dirPath)) { + fs.readdirSync(dirPath).forEach((file) => { + const filePath = path.join(dirPath, file); + if (fs.statSync(filePath).isDirectory()) { + // 递归删除子目录 + this.deleteFolderRecursive(filePath); + } else { + // 删除文件 + fs.unlinkSync(filePath); + this.logger.log(`Deleted file: ${filePath}`); + } + }); + // 删除空文件夹 + fs.rmdirSync(dirPath); + this.logger.log(`Deleted folder: ${dirPath}`); + } + } // 获取分享码信息 async getShareCodeInfo(code: string): Promise { try { @@ -192,10 +248,10 @@ export class ShareCodeService { // 根据分享码获取文件 async getFileByShareCode(code: string) { - console.log('收到验证分享码请求,code:', code); + this.logger.log('收到验证分享码请求,code:', code); const shareCode = await this.validateAndUseCode(code); - console.log('验证分享码结果:', shareCode); + this.logger.log('验证分享码结果:', shareCode); if (!shareCode) { throw new NotFoundException('分享码无效或已过期'); @@ -205,8 +261,9 @@ export class ShareCodeService { const resource = await this.resourceService.findUnique({ where: { fileId: shareCode.fileId }, }); - console.log('获取到的资源信息:', resource); + this.logger.log('获取到的资源信息:', resource); const { filename } = resource.meta as any as ResourceMeta + const fileUrl = resource?.url if (!resource) { throw new NotFoundException('文件不存在'); } @@ -216,22 +273,23 @@ export class ShareCodeService { fileId: shareCode.fileId, fileName: filename || 'downloaded_file', code: shareCode.code, - expiresAt: shareCode.expiresAt + expiresAt: shareCode.expiresAt, + url: fileUrl, }; - console.log('返回给前端的数据:', response); // 添加日志 + this.logger.log('返回给前端的数据:', response); // 添加日志 return response; } // 根据文件ID生成分享码 async generateShareCodeByFileId(fileId: string) { try { - console.log('收到生成分享码请求,fileId:', fileId); + this.logger.log('收到生成分享码请求,fileId:', fileId); const result = await this.generateShareCode(fileId); - console.log('生成分享码结果:', result); + this.logger.log('生成分享码结果:', result); return result; } catch (error) { - console.error('生成分享码错误:', error); + this.logger.error('生成分享码错误:', error); return error } diff --git a/apps/web/src/app/admin/deptsettingpage/page.tsx b/apps/web/src/app/admin/deptsettingpage/page.tsx index f73e5fb..aa56901 100755 --- a/apps/web/src/app/admin/deptsettingpage/page.tsx +++ b/apps/web/src/app/admin/deptsettingpage/page.tsx @@ -1,233 +1,95 @@ -import { useTusUpload } from "@web/src/hooks/useTusUpload"; + import { ShareCodeGenerator } from "../sharecode/sharecodegenerator"; import { ShareCodeValidator } from "../sharecode/sharecodevalidator"; -import { useState, useRef, useCallback, useEffect } from "react"; -import { message, Progress, Button, Tabs, DatePicker, Form } from "antd"; -import { UploadOutlined, DeleteOutlined, InboxOutlined } from "@ant-design/icons"; +import { message, Tabs, Form, Spin } from "antd"; import { env } from '../../../env' import { TusUploader } from "@web/src/components/common/uploader/TusUploader"; +import { useState } from "react"; const { TabPane } = Tabs; - export default function DeptSettingPage() { - const [uploadedFileId, setUploadedFileId] = useState(''); - const [uploadedFileName, setUploadedFileName] = useState(''); - const [fileNameMap, setFileNameMap] = useState>({}); - const [uploadedFiles, setUploadedFiles] = useState<{ id: string, name: string }[]>([]); - const [isDragging, setIsDragging] = useState(false); - const [expireTime, setExpireTime] = useState(null); - const dropRef = useRef(null); const [form] = Form.useForm(); - const [currentFile, setCurrentFile] = useState([]) const uploadFileId = Form.useWatch(["file"], form)?.[0] - // 使用您的 useTusUpload hook - const { uploadProgress, isUploading, uploadError, handleFileUpload } = useTusUpload(); - - // 清除已上传文件 - const handleClearFile = () => { - setUploadedFileId(''); - setUploadedFileName(''); - setUploadedFiles([]); - setFileNameMap({}); - }; - - // 处理文件上传 - const handleFileSelect = async (file: File) => { - // 限制:如果已有上传文件,则提示用户 - if (uploadedFiles.length > 0) { - message.warning('只能上传一个文件,请先删除已上传的文件'); - return; - } - - const fileKey = `file-${Date.now()}`; // 生成唯一的文件标识 - - handleFileUpload( - file, - async (result) => { - setUploadedFileId(result.fileId); - setUploadedFileName(result.fileName); - - // 添加到已上传文件列表 - setUploadedFiles([{ id: result.fileId, name: file.name }]); - - // 在前端保存文件名映射(用于当前会话) - setFileNameMap({ - [result.fileId]: file.name - }); - - // 上传成功后保存原始文件名到数据库 - try { - console.log('正在保存文件名到数据库:', result.fileName, '对应文件ID:', result.fileId); - - const response = await fetch(`http://${env.SERVER_IP}:${env.SERVER_PORT}/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) => { - message.error('上传失败:' + error.message); - }, - fileKey - ); - }; - - // 处理多个文件上传 - 已移除 - // const handleFilesUpload = (file: File) => { - // handleFileSelect(file); - // }; - - // 处理文件删除 - const handleDeleteFile = async (fileId: string) => { - try { - // 可以添加删除文件的API调用 - // const response = await fetch(`http://${env.SERVER_IP}:${env.SERVER_PORT}/upload/delete/${fileId}`, { - // method: 'DELETE' - // }); - - // if (!response.ok) { - // throw new Error('删除文件失败'); - // } - - // 无论服务器删除是否成功,前端都需要更新状态 - setUploadedFiles([]); - setUploadedFileId(''); - setUploadedFileName(''); - setFileNameMap({}); - - message.success('文件已删除'); - } catch (error) { - console.error('删除文件错误:', error); - message.error('删除文件失败'); - } - }; - - // 拖拽相关处理函数 - const handleDragEnter = useCallback((e: React.DragEvent) => { - e.preventDefault(); - e.stopPropagation(); - setIsDragging(true); - }, []); - - const handleDragLeave = useCallback((e: React.DragEvent) => { - e.preventDefault(); - e.stopPropagation(); - setIsDragging(false); - }, []); - - const handleDragOver = useCallback((e: React.DragEvent) => { - e.preventDefault(); - e.stopPropagation(); - setIsDragging(true); - }, []); - - const handleDrop = useCallback((e: React.DragEvent) => { - e.preventDefault(); - e.stopPropagation(); - setIsDragging(false); - - handleFileSelect(e.dataTransfer.files[0]); - }, []); - - + const [isGetingFileId, setIsGetingFileId] = useState(false); // 处理分享码生成成功 const handleShareSuccess = (code: string) => { message.success('分享码生成成功:' + code); // 可以在这里添加其他逻辑,比如保存到历史记录 - }; - + } // 处理分享码验证成功 - const handleValidSuccess = async (fileId: string, fileName: string) => { + const handleValidSuccess = async (fileUrl: string, fileName: string) => { + setIsGetingFileId(true); try { // 构建下载URL(包含文件名参数) - const downloadUrl = `/upload/download/${fileId}?fileName=${encodeURIComponent(fileName)}`; - const response = await fetch(downloadUrl); - if (!response.ok) { - throw new Error('文件下载失败'); - } - - // 创建下载链接 - const blob = await response.blob(); - const url = window.URL.createObjectURL(blob); + console.log('文件url:', fileUrl); + const downloadUrl = `http://${env.SERVER_IP}:${env.FILE_PORT}/uploads/${fileUrl}`; + console.log('下载URL:', downloadUrl); const link = document.createElement('a'); - link.href = url; + link.href = downloadUrl; // 直接使用传入的 fileName link.download = fileName; - + link.target = '_blank'; // 在新标签页中打开 // 触发下载 document.body.appendChild(link); link.click(); document.body.removeChild(link); - window.URL.revokeObjectURL(url); message.success('文件下载开始'); } catch (error) { console.error('下载失败:', error); message.error('文件下载失败'); + } finally { + setIsGetingFileId(false); } }; return ( -
- 文件分享中心 - - - {/* 文件上传区域 */} -
- 第一步:上传文件 - {/* 如果没有已上传文件,显示上传区域 */} -
- - - -
-
+ <> + { + isGetingFileId ? + () : + (
+ 文件分享中心 + + + {/* 文件上传区域 */} +
+ 第一步:上传文件 + {/* 如果没有已上传文件,显示上传区域 */} +
+ + + +
+
- {/* 生成分享码区域 */} -
- 第二步:生成分享码 - -
-
+ {/* 生成分享码区域 */} +
+ 第二步:生成分享码 + +
+ + + {/* 使用分享码区域 */} + +
+ 使用分享码下载文件 + +
+
+
+
) + } + + ) - {/* 使用分享码区域 */} - -
- 使用分享码下载文件 - -
-
-
-
- ); } \ No newline at end of file diff --git a/apps/web/src/app/admin/sharecode/sharecodegenerator.tsx b/apps/web/src/app/admin/sharecode/sharecodegenerator.tsx index bb92116..efc4b87 100755 --- a/apps/web/src/app/admin/sharecode/sharecodegenerator.tsx +++ b/apps/web/src/app/admin/sharecode/sharecodegenerator.tsx @@ -14,7 +14,7 @@ interface ShareCodeGeneratorProps { interface ShareCodeResponse { code?: string; expiresAt?: Date; -} +} export const ShareCodeGenerator: React.FC = ({ fileId, onSuccess, @@ -39,6 +39,10 @@ export const ShareCodeGenerator: React.FC = ({ setCurrentFileId(fileId); }, [fileId]) const generateCode = async () => { + if (!fileId) { + message.error('请先上传文件'); + return; + } setLoading(true); console.log('开始生成分享码,fileId:', fileId, 'fileName:', fileName); try { @@ -48,7 +52,7 @@ export const ShareCodeGenerator: React.FC = ({ setIsGenerate(true); setExpiresAt(data.expiresAt ? new Date(data.expiresAt) : null); onSuccess?.(data.code); - message.success('分享码生成成功'); + //message.success('分享码生成成功'); } catch (error) { console.error('生成分享码错误:', error); message.error('生成分享码失败: ' + (error instanceof Error ? error.message : '未知错误')); @@ -56,6 +60,26 @@ export const ShareCodeGenerator: React.FC = ({ setLoading(false); } }; + function copyToClipboard(text) { + if (navigator.clipboard) { + return navigator.clipboard.writeText(text); + } else { + const textarea = document.createElement('textarea'); + textarea.value = text; + document.body.appendChild(textarea); + textarea.select(); + document.execCommand('copy'); + document.body.removeChild(textarea); + return Promise.resolve(); + } + } + + // 组件使用 + const handleCopy = (code) => { + copyToClipboard(code) + .then(() => console.log('复制成功')) + .catch(() => console.error('复制失败')); + }; return (
@@ -96,7 +120,8 @@ export const ShareCodeGenerator: React.FC = ({