mirror of
https://github.com/MatrixSeven/file-transfer-go.git
synced 2026-02-04 03:25:03 +08:00
feat:添加链接QR支持,删除无用文件
This commit is contained in:
@@ -31,6 +31,7 @@
|
||||
"clsx": "^2.1.1",
|
||||
"lucide-react": "^0.533.0",
|
||||
"next": "15.4.4",
|
||||
"qrcode": "^1.5.4",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
@@ -40,6 +41,7 @@
|
||||
"@eslint/eslintrc": "^3",
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@types/node": "^20",
|
||||
"@types/qrcode": "^1.5.5",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"eslint": "^9",
|
||||
|
||||
@@ -50,19 +50,21 @@ export default function HomePage() {
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="text"
|
||||
className="flex items-center justify-center space-x-2 px-4 py-3 text-sm font-medium rounded-lg transition-all duration-200 hover:bg-slate-50 data-[state=active]:bg-emerald-500 data-[state=active]:text-white data-[state=active]:shadow-md data-[state=active]:hover:bg-emerald-600"
|
||||
className="flex items-center justify-center space-x-2 px-4 py-3 text-sm font-medium rounded-lg transition-all duration-200 hover:bg-slate-50 data-[state=active]:bg-emerald-500 data-[state=active]:text-white data-[state=active]:shadow-md data-[state=active]:hover:bg-emerald-600 relative"
|
||||
>
|
||||
<MessageSquare className="w-4 h-4" />
|
||||
<span className="hidden sm:inline">文本传输</span>
|
||||
<span className="sm:hidden">文本</span>
|
||||
<span className="text-xs bg-orange-100 text-orange-600 px-1.5 py-0.5 rounded ml-1 absolute -top-1 -right-1">开发中</span>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="desktop"
|
||||
className="flex items-center justify-center space-x-2 px-4 py-3 text-sm font-medium rounded-lg transition-all duration-200 hover:bg-slate-50 data-[state=active]:bg-purple-500 data-[state=active]:text-white data-[state=active]:shadow-md data-[state=active]:hover:bg-purple-600"
|
||||
className="flex items-center justify-center space-x-2 px-4 py-3 text-sm font-medium rounded-lg transition-all duration-200 hover:bg-slate-50 data-[state=active]:bg-purple-500 data-[state=active]:text-white data-[state=active]:shadow-md data-[state=active]:hover:bg-purple-600 relative"
|
||||
>
|
||||
<Monitor className="w-4 h-4" />
|
||||
<span className="hidden sm:inline">共享桌面</span>
|
||||
<span className="sm:hidden">桌面</span>
|
||||
<span className="text-xs bg-orange-100 text-orange-600 px-1.5 py-0.5 rounded ml-1 absolute -top-1 -right-1">开发中</span>
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</div>
|
||||
@@ -74,49 +76,43 @@ export default function HomePage() {
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="text" className="mt-0 animate-fade-in-up">
|
||||
<TextTransfer
|
||||
onSendText={async (text: string) => {
|
||||
try {
|
||||
const response = await fetch('/api/create-text-room', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ text }),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || '创建文本房间失败');
|
||||
}
|
||||
|
||||
return data.code;
|
||||
} catch (error) {
|
||||
console.error('创建文本房间失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}}
|
||||
onReceiveText={async (code: string) => {
|
||||
try {
|
||||
const response = await fetch(`/api/get-text-content?code=${code}`);
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || '获取文本内容失败');
|
||||
}
|
||||
|
||||
return data.text;
|
||||
} catch (error) {
|
||||
console.error('获取文本内容失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<div className="max-w-md mx-auto p-8 bg-white/90 backdrop-blur-sm rounded-2xl shadow-lg border border-slate-200">
|
||||
<div className="text-center">
|
||||
<div className="w-16 h-16 mx-auto mb-4 bg-gradient-to-br from-emerald-100 to-emerald-200 rounded-full flex items-center justify-center">
|
||||
<MessageSquare className="w-8 h-8 text-emerald-600" />
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-slate-800 mb-2">文字传输</h3>
|
||||
<p className="text-slate-600 mb-4">此功能正在开发中...</p>
|
||||
<div className="bg-emerald-50 border border-emerald-200 rounded-lg p-4">
|
||||
<p className="text-sm text-emerald-700">
|
||||
🚧 敬请期待!我们正在为您开发更便捷的文字传输功能
|
||||
</p>
|
||||
</div>
|
||||
<p className="text-xs text-slate-500 mt-4">
|
||||
目前请使用文件传输功能
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="desktop" className="mt-0 animate-fade-in-up">
|
||||
<DesktopShare />
|
||||
<div className="max-w-md mx-auto p-8 bg-white/90 backdrop-blur-sm rounded-2xl shadow-lg border border-slate-200">
|
||||
<div className="text-center">
|
||||
<div className="w-16 h-16 mx-auto mb-4 bg-gradient-to-br from-purple-100 to-purple-200 rounded-full flex items-center justify-center">
|
||||
<Monitor className="w-8 h-8 text-purple-600" />
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-slate-800 mb-2">桌面共享</h3>
|
||||
<p className="text-slate-600 mb-4">此功能正在开发中...</p>
|
||||
<div className="bg-purple-50 border border-purple-200 rounded-lg p-4">
|
||||
<p className="text-sm text-purple-700">
|
||||
🚧 敬请期待!我们正在为您开发实时桌面共享功能
|
||||
</p>
|
||||
</div>
|
||||
<p className="text-xs text-slate-500 mt-4">
|
||||
目前请使用文件传输功能
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
</div>
|
||||
</Tabs>
|
||||
|
||||
@@ -7,6 +7,13 @@ export async function GET(request: NextRequest) {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const code = searchParams.get('code');
|
||||
|
||||
if (!code) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Missing code parameter' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
console.log('API Route: Getting room info, proxying to:', `${GO_BACKEND_URL}/api/room-info?code=${code}`);
|
||||
|
||||
const response = await fetch(`${GO_BACKEND_URL}/api/room-info?code=${code}`, {
|
||||
|
||||
68
chuan-next/src/components/QRCodeDisplay.tsx
Normal file
68
chuan-next/src/components/QRCodeDisplay.tsx
Normal file
@@ -0,0 +1,68 @@
|
||||
"use client";
|
||||
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import QRCode from 'qrcode';
|
||||
|
||||
interface QRCodeDisplayProps {
|
||||
value: string;
|
||||
size?: number;
|
||||
className?: string;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export default function QRCodeDisplay({
|
||||
value,
|
||||
size = 200,
|
||||
className = "",
|
||||
title = "扫码传输"
|
||||
}: QRCodeDisplayProps) {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
const [error, setError] = useState<string>('');
|
||||
|
||||
useEffect(() => {
|
||||
const generateQR = async () => {
|
||||
try {
|
||||
if (canvasRef.current && value) {
|
||||
await QRCode.toCanvas(canvasRef.current, value, {
|
||||
width: size,
|
||||
margin: 2,
|
||||
color: {
|
||||
dark: '#1e293b', // slate-800
|
||||
light: '#ffffff'
|
||||
}
|
||||
});
|
||||
setError('');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('生成二维码失败:', err);
|
||||
setError('生成二维码失败');
|
||||
}
|
||||
};
|
||||
|
||||
generateQR();
|
||||
}, [value, size]);
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className={`flex items-center justify-center bg-slate-100 rounded-lg ${className}`}
|
||||
style={{ width: size, height: size }}>
|
||||
<p className="text-sm text-slate-500">{error}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
{title && (
|
||||
<h3 className="text-sm font-medium text-slate-700 text-center mb-3">{title}</h3>
|
||||
)}
|
||||
<div className="flex justify-center">
|
||||
<canvas
|
||||
ref={canvasRef}
|
||||
className="rounded-lg"
|
||||
style={{ maxWidth: '100%', height: 'auto' }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import React, { useState, useRef, useCallback } from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useToast } from '@/components/ui/toast-simple';
|
||||
import { Upload, FileText, Image, Video, Music, Archive, X } from 'lucide-react';
|
||||
import QRCodeDisplay from '@/components/QRCodeDisplay';
|
||||
|
||||
interface FileInfo {
|
||||
id: string;
|
||||
@@ -398,62 +399,77 @@ export function WebRTCFileUpload({
|
||||
{/* 取件码展示 */}
|
||||
{pickupCode && (
|
||||
<div className="border-t border-slate-200 pt-6">
|
||||
<div className="text-center mb-4 sm:mb-6">
|
||||
<div className="w-12 h-12 sm:w-16 sm:h-16 mx-auto mb-4 bg-gradient-to-br from-emerald-500 to-teal-500 rounded-2xl flex items-center justify-center animate-float">
|
||||
<FileText className="w-6 h-6 sm:w-8 sm:h-8 text-white" />
|
||||
</div>
|
||||
<h3 className="text-xl sm:text-2xl font-bold bg-gradient-to-r from-emerald-600 to-teal-600 bg-clip-text text-transparent mb-2">
|
||||
取件码生成成功!
|
||||
</h3>
|
||||
<p className="text-sm sm:text-base text-slate-600">分享以下信息给接收方</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4 sm:space-y-6">
|
||||
{/* 取件码 */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-3">取件码</label>
|
||||
<div className="flex flex-col sm:flex-row gap-3">
|
||||
<div className="flex-1 code-display rounded-xl p-4 sm:p-6 text-center">
|
||||
<div className="text-2xl sm:text-3xl font-bold font-mono bg-gradient-to-r from-emerald-600 to-teal-600 bg-clip-text text-transparent tracking-wider">
|
||||
{pickupCode}
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
onClick={onCopyCode}
|
||||
className="px-4 sm:px-6 py-3 bg-emerald-500 hover:bg-emerald-600 text-white rounded-xl font-medium shadow-lg transition-all duration-200 hover:shadow-xl w-full sm:w-auto"
|
||||
>
|
||||
复制
|
||||
</Button>
|
||||
{/* 左上角状态提示 - 类似已选择文件的风格 */}
|
||||
<div className="flex items-center mb-6">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="w-10 h-10 bg-gradient-to-br from-emerald-500 to-teal-500 rounded-xl flex items-center justify-center">
|
||||
<FileText className="w-5 h-5 text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-slate-800">取件码生成成功!</h3>
|
||||
<p className="text-sm text-slate-600">分享以下信息给接收方</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 取件链接 */}
|
||||
{/* 中间区域:取件码 + 分隔线 + 二维码 */}
|
||||
<div className="flex flex-col lg:flex-row lg:items-start gap-6 lg:gap-8 mb-8">
|
||||
{/* 左侧:取件码 */}
|
||||
<div className="flex-1">
|
||||
<label className="block text-sm font-medium text-slate-700 mb-3">取件码</label>
|
||||
<div className="flex flex-col items-center rounded-xl border border-slate-200 p-6 h-40 justify-center bg-slate-50">
|
||||
<div className="text-2xl font-bold font-mono bg-gradient-to-r from-emerald-600 to-teal-600 bg-clip-text text-transparent tracking-wider">
|
||||
{pickupCode}
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
onClick={onCopyCode}
|
||||
className="w-full px-4 py-2.5 bg-emerald-500 hover:bg-emerald-600 text-white rounded-lg font-medium shadow transition-all duration-200 mt-3"
|
||||
>
|
||||
复制取件码
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* 分隔线 - 大屏幕显示竖线,移动端隐藏 */}
|
||||
<div className="hidden lg:block w-px bg-slate-200 h-64 mt-6"></div>
|
||||
|
||||
{/* 右侧:二维码 */}
|
||||
{pickupLink && (
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-3">取件链接</label>
|
||||
<div className="flex flex-col sm:flex-row gap-3">
|
||||
<div className="flex-1 code-display rounded-xl p-3 sm:p-4">
|
||||
<div className="text-xs sm:text-sm text-slate-700 break-all font-mono">
|
||||
{pickupLink}
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
onClick={onCopyLink}
|
||||
className="px-4 sm:px-6 py-3 bg-blue-500 hover:bg-blue-600 text-white rounded-xl font-medium shadow-lg transition-all duration-200 hover:shadow-xl w-full sm:w-auto"
|
||||
>
|
||||
复制
|
||||
</Button>
|
||||
<div className="flex-1">
|
||||
<label className="block text-sm font-medium text-slate-700 mb-3">扫码传输</label>
|
||||
<div className="flex flex-col items-center rounded-xl border border-slate-200 p-6 h-40 justify-center bg-slate-50">
|
||||
<QRCodeDisplay
|
||||
value={pickupLink}
|
||||
size={120}
|
||||
title=""
|
||||
className="w-auto"
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full px-4 py-2.5 bg-blue-500 text-white rounded-lg font-medium shadow transition-all duration-200 mt-3 text-center">
|
||||
使用手机扫码快速访问
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 使用提示 */}
|
||||
<div className="mt-4 sm:mt-6 p-3 sm:p-4 bg-gradient-to-r from-blue-50 to-indigo-50 rounded-xl border border-blue-200">
|
||||
<p className="text-xs sm:text-sm text-slate-600 text-center">
|
||||
💡 <span className="font-medium">使用提示:</span>接收方输入取件码或访问取件链接即可下载文件
|
||||
</p>
|
||||
</div>
|
||||
{/* 底部:取件链接 */}
|
||||
{pickupLink && (
|
||||
<div className="space-y-3">
|
||||
<div className="flex gap-3">
|
||||
<div className="flex-1 code-display rounded-lg p-3 bg-slate-50 border border-slate-200">
|
||||
<div className="text-sm text-slate-700 break-all font-mono leading-relaxed">
|
||||
{pickupLink}
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
onClick={onCopyLink}
|
||||
className="px-4 py-2.5 bg-blue-500 hover:bg-blue-600 text-white rounded-lg font-medium shadow transition-all duration-200 shrink-0"
|
||||
>
|
||||
复制链接
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -75,10 +75,10 @@ export const useTabManager = (isConnected: boolean, pickupCode: string, isConnec
|
||||
currentMode = '文件传输';
|
||||
break;
|
||||
case 'text':
|
||||
currentMode = '文字传输';
|
||||
currentMode = '文字传输(开发中)';
|
||||
break;
|
||||
case 'desktop':
|
||||
currentMode = '桌面共享';
|
||||
currentMode = '桌面共享(开发中)';
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -87,10 +87,10 @@ export const useTabManager = (isConnected: boolean, pickupCode: string, isConnec
|
||||
targetMode = '文件传输';
|
||||
break;
|
||||
case 'text':
|
||||
targetMode = '文字传输';
|
||||
targetMode = '文字传输(开发中)';
|
||||
break;
|
||||
case 'desktop':
|
||||
targetMode = '桌面共享';
|
||||
targetMode = '桌面共享(开发中)';
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -775,6 +775,13 @@
|
||||
resolved "https://registry.npmmirror.com/@types/json5/-/json5-0.0.29.tgz"
|
||||
integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==
|
||||
|
||||
"@types/node@*":
|
||||
version "24.2.0"
|
||||
resolved "https://registry.npmmirror.com/@types/node/-/node-24.2.0.tgz#cde712f88c5190006d6b069232582ecd1f94a760"
|
||||
integrity sha512-3xyG3pMCq3oYCNg7/ZP+E1ooTaGB4cG8JWRsqqOYQdbWNY4zbaV0Ennrd7stjiJEFZCaybcIgpTjJWHRfBSIDw==
|
||||
dependencies:
|
||||
undici-types "~7.10.0"
|
||||
|
||||
"@types/node@^20":
|
||||
version "20.19.9"
|
||||
resolved "https://registry.npmmirror.com/@types/node/-/node-20.19.9.tgz"
|
||||
@@ -782,6 +789,13 @@
|
||||
dependencies:
|
||||
undici-types "~6.21.0"
|
||||
|
||||
"@types/qrcode@^1.5.5":
|
||||
version "1.5.5"
|
||||
resolved "https://registry.npmmirror.com/@types/qrcode/-/qrcode-1.5.5.tgz#993ff7c6b584277eee7aac0a20861eab682f9dac"
|
||||
integrity sha512-CdfBi/e3Qk+3Z/fXYShipBT13OJ2fDO2Q2w5CIP5anLTLIndQG9z6P1cnm+8zCWSpm5dnxMFd/uREtb0EXuQzg==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/react-dom@^19":
|
||||
version "19.1.6"
|
||||
resolved "https://registry.npmmirror.com/@types/react-dom/-/react-dom-19.1.6.tgz"
|
||||
@@ -1009,7 +1023,12 @@ ajv@^6.12.4:
|
||||
json-schema-traverse "^0.4.1"
|
||||
uri-js "^4.2.2"
|
||||
|
||||
ansi-styles@^4.1.0:
|
||||
ansi-regex@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
|
||||
integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
|
||||
|
||||
ansi-styles@^4.0.0, ansi-styles@^4.1.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz"
|
||||
integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
|
||||
@@ -1209,6 +1228,11 @@ callsites@^3.0.0:
|
||||
resolved "https://registry.npmmirror.com/callsites/-/callsites-3.1.0.tgz"
|
||||
integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
|
||||
|
||||
camelcase@^5.0.0:
|
||||
version "5.3.1"
|
||||
resolved "https://registry.npmmirror.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
|
||||
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
|
||||
|
||||
caniuse-lite@^1.0.30001579:
|
||||
version "1.0.30001731"
|
||||
resolved "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001731.tgz"
|
||||
@@ -1239,6 +1263,15 @@ client-only@0.0.1:
|
||||
resolved "https://registry.npmmirror.com/client-only/-/client-only-0.0.1.tgz"
|
||||
integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==
|
||||
|
||||
cliui@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.npmmirror.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1"
|
||||
integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==
|
||||
dependencies:
|
||||
string-width "^4.2.0"
|
||||
strip-ansi "^6.0.0"
|
||||
wrap-ansi "^6.2.0"
|
||||
|
||||
clsx@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.npmmirror.com/clsx/-/clsx-2.1.1.tgz"
|
||||
@@ -1337,6 +1370,11 @@ debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.4.0:
|
||||
dependencies:
|
||||
ms "^2.1.3"
|
||||
|
||||
decamelize@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.npmmirror.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
|
||||
integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==
|
||||
|
||||
deep-is@^0.1.3:
|
||||
version "0.1.4"
|
||||
resolved "https://registry.npmmirror.com/deep-is/-/deep-is-0.1.4.tgz"
|
||||
@@ -1370,6 +1408,11 @@ detect-node-es@^1.1.0:
|
||||
resolved "https://registry.npmmirror.com/detect-node-es/-/detect-node-es-1.1.0.tgz"
|
||||
integrity sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==
|
||||
|
||||
dijkstrajs@^1.0.1:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.npmmirror.com/dijkstrajs/-/dijkstrajs-1.0.3.tgz#4c8dbdea1f0f6478bff94d9c49c784d623e4fc23"
|
||||
integrity sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==
|
||||
|
||||
doctrine@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.npmmirror.com/doctrine/-/doctrine-2.1.0.tgz"
|
||||
@@ -1386,6 +1429,11 @@ dunder-proto@^1.0.0, dunder-proto@^1.0.1:
|
||||
es-errors "^1.3.0"
|
||||
gopd "^1.2.0"
|
||||
|
||||
emoji-regex@^8.0.0:
|
||||
version "8.0.0"
|
||||
resolved "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
|
||||
integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
|
||||
|
||||
emoji-regex@^9.2.2:
|
||||
version "9.2.2"
|
||||
resolved "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-9.2.2.tgz"
|
||||
@@ -1804,6 +1852,14 @@ fill-range@^7.1.1:
|
||||
dependencies:
|
||||
to-regex-range "^5.0.1"
|
||||
|
||||
find-up@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.npmmirror.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19"
|
||||
integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==
|
||||
dependencies:
|
||||
locate-path "^5.0.0"
|
||||
path-exists "^4.0.0"
|
||||
|
||||
find-up@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.npmmirror.com/find-up/-/find-up-5.0.0.tgz"
|
||||
@@ -1854,6 +1910,11 @@ functions-have-names@^1.2.3:
|
||||
resolved "https://registry.npmmirror.com/functions-have-names/-/functions-have-names-1.2.3.tgz"
|
||||
integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==
|
||||
|
||||
get-caller-file@^2.0.1:
|
||||
version "2.0.5"
|
||||
resolved "https://registry.npmmirror.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
|
||||
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
|
||||
|
||||
get-intrinsic@^1.2.4, get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@^1.2.7, get-intrinsic@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz"
|
||||
@@ -2104,6 +2165,11 @@ is-finalizationregistry@^1.1.0:
|
||||
dependencies:
|
||||
call-bound "^1.0.3"
|
||||
|
||||
is-fullwidth-code-point@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
|
||||
integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
|
||||
|
||||
is-generator-function@^1.0.10:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.npmmirror.com/is-generator-function/-/is-generator-function-1.1.0.tgz"
|
||||
@@ -2376,6 +2442,13 @@ lightningcss@1.30.1:
|
||||
lightningcss-win32-arm64-msvc "1.30.1"
|
||||
lightningcss-win32-x64-msvc "1.30.1"
|
||||
|
||||
locate-path@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.npmmirror.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0"
|
||||
integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==
|
||||
dependencies:
|
||||
p-locate "^4.1.0"
|
||||
|
||||
locate-path@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.npmmirror.com/locate-path/-/locate-path-6.0.0.tgz"
|
||||
@@ -2589,6 +2662,13 @@ own-keys@^1.0.1:
|
||||
object-keys "^1.1.1"
|
||||
safe-push-apply "^1.0.0"
|
||||
|
||||
p-limit@^2.2.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.npmmirror.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
|
||||
integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==
|
||||
dependencies:
|
||||
p-try "^2.0.0"
|
||||
|
||||
p-limit@^3.0.2:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.npmmirror.com/p-limit/-/p-limit-3.1.0.tgz"
|
||||
@@ -2596,6 +2676,13 @@ p-limit@^3.0.2:
|
||||
dependencies:
|
||||
yocto-queue "^0.1.0"
|
||||
|
||||
p-locate@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.npmmirror.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07"
|
||||
integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==
|
||||
dependencies:
|
||||
p-limit "^2.2.0"
|
||||
|
||||
p-locate@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.npmmirror.com/p-locate/-/p-locate-5.0.0.tgz"
|
||||
@@ -2603,6 +2690,11 @@ p-locate@^5.0.0:
|
||||
dependencies:
|
||||
p-limit "^3.0.2"
|
||||
|
||||
p-try@^2.0.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.npmmirror.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
|
||||
integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
|
||||
|
||||
parent-module@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz"
|
||||
@@ -2640,6 +2732,11 @@ picomatch@^4.0.2:
|
||||
resolved "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz"
|
||||
integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==
|
||||
|
||||
pngjs@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.npmmirror.com/pngjs/-/pngjs-5.0.0.tgz#e79dd2b215767fd9c04561c01236df960bce7fbb"
|
||||
integrity sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==
|
||||
|
||||
possible-typed-array-names@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.npmmirror.com/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz"
|
||||
@@ -2682,6 +2779,15 @@ punycode@^2.1.0:
|
||||
resolved "https://registry.npmmirror.com/punycode/-/punycode-2.3.1.tgz"
|
||||
integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==
|
||||
|
||||
qrcode@^1.5.4:
|
||||
version "1.5.4"
|
||||
resolved "https://registry.npmmirror.com/qrcode/-/qrcode-1.5.4.tgz#5cb81d86eb57c675febb08cf007fff963405da88"
|
||||
integrity sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==
|
||||
dependencies:
|
||||
dijkstrajs "^1.0.1"
|
||||
pngjs "^5.0.0"
|
||||
yargs "^15.3.1"
|
||||
|
||||
queue-microtask@^1.2.2:
|
||||
version "1.2.3"
|
||||
resolved "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz"
|
||||
@@ -2757,6 +2863,16 @@ regexp.prototype.flags@^1.5.3, regexp.prototype.flags@^1.5.4:
|
||||
gopd "^1.2.0"
|
||||
set-function-name "^2.0.2"
|
||||
|
||||
require-directory@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
|
||||
integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==
|
||||
|
||||
require-main-filename@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmmirror.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
|
||||
integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==
|
||||
|
||||
resolve-from@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.npmmirror.com/resolve-from/-/resolve-from-4.0.0.tgz"
|
||||
@@ -2840,6 +2956,11 @@ semver@^7.6.0, semver@^7.7.1, semver@^7.7.2:
|
||||
resolved "https://registry.npmmirror.com/semver/-/semver-7.7.2.tgz"
|
||||
integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==
|
||||
|
||||
set-blocking@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmmirror.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
|
||||
integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==
|
||||
|
||||
set-function-length@^1.2.2:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.npmmirror.com/set-function-length/-/set-function-length-1.2.2.tgz"
|
||||
@@ -2980,6 +3101,15 @@ stop-iteration-iterator@^1.1.0:
|
||||
es-errors "^1.3.0"
|
||||
internal-slot "^1.1.0"
|
||||
|
||||
string-width@^4.1.0, string-width@^4.2.0:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
dependencies:
|
||||
emoji-regex "^8.0.0"
|
||||
is-fullwidth-code-point "^3.0.0"
|
||||
strip-ansi "^6.0.1"
|
||||
|
||||
string.prototype.includes@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.npmmirror.com/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz"
|
||||
@@ -3048,6 +3178,13 @@ string.prototype.trimstart@^1.0.8:
|
||||
define-properties "^1.2.1"
|
||||
es-object-atoms "^1.0.0"
|
||||
|
||||
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
dependencies:
|
||||
ansi-regex "^5.0.1"
|
||||
|
||||
strip-bom@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npmmirror.com/strip-bom/-/strip-bom-3.0.0.tgz"
|
||||
@@ -3216,6 +3353,11 @@ undici-types@~6.21.0:
|
||||
resolved "https://registry.npmmirror.com/undici-types/-/undici-types-6.21.0.tgz"
|
||||
integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==
|
||||
|
||||
undici-types@~7.10.0:
|
||||
version "7.10.0"
|
||||
resolved "https://registry.npmmirror.com/undici-types/-/undici-types-7.10.0.tgz#4ac2e058ce56b462b056e629cc6a02393d3ff350"
|
||||
integrity sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==
|
||||
|
||||
unrs-resolver@^1.6.2:
|
||||
version "1.11.1"
|
||||
resolved "https://registry.npmmirror.com/unrs-resolver/-/unrs-resolver-1.11.1.tgz"
|
||||
@@ -3310,6 +3452,11 @@ which-collection@^1.0.2:
|
||||
is-weakmap "^2.0.2"
|
||||
is-weakset "^2.0.3"
|
||||
|
||||
which-module@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.npmmirror.com/which-module/-/which-module-2.0.1.tgz#776b1fe35d90aebe99e8ac15eb24093389a4a409"
|
||||
integrity sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==
|
||||
|
||||
which-typed-array@^1.1.16, which-typed-array@^1.1.19:
|
||||
version "1.1.19"
|
||||
resolved "https://registry.npmmirror.com/which-typed-array/-/which-typed-array-1.1.19.tgz"
|
||||
@@ -3335,11 +3482,50 @@ word-wrap@^1.2.5:
|
||||
resolved "https://registry.npmmirror.com/word-wrap/-/word-wrap-1.2.5.tgz"
|
||||
integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==
|
||||
|
||||
wrap-ansi@^6.2.0:
|
||||
version "6.2.0"
|
||||
resolved "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53"
|
||||
integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==
|
||||
dependencies:
|
||||
ansi-styles "^4.0.0"
|
||||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
y18n@^4.0.0:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.npmmirror.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf"
|
||||
integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==
|
||||
|
||||
yallist@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.npmmirror.com/yallist/-/yallist-5.0.0.tgz"
|
||||
integrity sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==
|
||||
|
||||
yargs-parser@^18.1.2:
|
||||
version "18.1.3"
|
||||
resolved "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0"
|
||||
integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==
|
||||
dependencies:
|
||||
camelcase "^5.0.0"
|
||||
decamelize "^1.2.0"
|
||||
|
||||
yargs@^15.3.1:
|
||||
version "15.4.1"
|
||||
resolved "https://registry.npmmirror.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8"
|
||||
integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==
|
||||
dependencies:
|
||||
cliui "^6.0.0"
|
||||
decamelize "^1.2.0"
|
||||
find-up "^4.1.0"
|
||||
get-caller-file "^2.0.1"
|
||||
require-directory "^2.1.1"
|
||||
require-main-filename "^2.0.0"
|
||||
set-blocking "^2.0.0"
|
||||
string-width "^4.2.0"
|
||||
which-module "^2.0.0"
|
||||
y18n "^4.0.0"
|
||||
yargs-parser "^18.1.2"
|
||||
|
||||
yocto-queue@^0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz"
|
||||
|
||||
@@ -1,168 +0,0 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"chuan/internal/models"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type FileService struct {
|
||||
uploadDir string
|
||||
}
|
||||
|
||||
func NewFileService() *FileService {
|
||||
return &FileService{
|
||||
uploadDir: "./uploads",
|
||||
}
|
||||
}
|
||||
|
||||
// SaveFile 保存上传的文件
|
||||
func (fs *FileService) SaveFile(file multipart.File, header *multipart.FileHeader) (*models.FileInfo, error) {
|
||||
// 生成唯一文件ID
|
||||
fileID := uuid.New().String()
|
||||
|
||||
// 生成取件码
|
||||
code := fs.generateCode()
|
||||
|
||||
// 创建文件路径
|
||||
fileExt := filepath.Ext(header.Filename)
|
||||
fileName := fmt.Sprintf("%s%s", fileID, fileExt)
|
||||
filePath := filepath.Join(fs.uploadDir, fileName)
|
||||
|
||||
// 确保上传目录存在
|
||||
if err := os.MkdirAll(fs.uploadDir, 0755); err != nil {
|
||||
return nil, fmt.Errorf("创建上传目录失败: %v", err)
|
||||
}
|
||||
|
||||
// 创建目标文件
|
||||
dst, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("创建文件失败: %v", err)
|
||||
}
|
||||
defer dst.Close()
|
||||
|
||||
// 复制文件内容
|
||||
size, err := io.Copy(dst, file)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("保存文件失败: %v", err)
|
||||
}
|
||||
|
||||
// 获取文件内容类型
|
||||
contentType := header.Header.Get("Content-Type")
|
||||
if contentType == "" {
|
||||
contentType = fs.getContentType(header.Filename)
|
||||
}
|
||||
|
||||
fileInfo := &models.FileInfo{
|
||||
ID: fileID,
|
||||
FileName: header.Filename,
|
||||
FileSize: size,
|
||||
ContentType: contentType,
|
||||
Code: code,
|
||||
UploadTime: time.Now(),
|
||||
ExpiryTime: time.Now().Add(24 * time.Hour), // 24小时过期
|
||||
FilePath: filePath,
|
||||
DownloadURL: fmt.Sprintf("/download/%s", code),
|
||||
}
|
||||
|
||||
// 存储文件信息到内存(生产环境应使用Redis)
|
||||
store := GetStore()
|
||||
if err := store.StoreFileInfo(fileInfo); err != nil {
|
||||
return nil, fmt.Errorf("存储文件信息失败: %v", err)
|
||||
}
|
||||
|
||||
return fileInfo, nil
|
||||
}
|
||||
|
||||
// generateCode 生成6位取件码
|
||||
func (fs *FileService) generateCode() string {
|
||||
const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
b := make([]byte, 6)
|
||||
rand.Read(b)
|
||||
for i := range b {
|
||||
b[i] = charset[b[i]%byte(len(charset))]
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// getContentType 根据文件扩展名获取内容类型
|
||||
func (fs *FileService) getContentType(filename string) string {
|
||||
ext := strings.ToLower(filepath.Ext(filename))
|
||||
switch ext {
|
||||
case ".pdf":
|
||||
return "application/pdf"
|
||||
case ".epub":
|
||||
return "application/epub+zip"
|
||||
case ".mobi":
|
||||
return "application/x-mobipocket-ebook"
|
||||
case ".txt":
|
||||
return "text/plain"
|
||||
case ".jpg", ".jpeg":
|
||||
return "image/jpeg"
|
||||
case ".png":
|
||||
return "image/png"
|
||||
case ".gif":
|
||||
return "image/gif"
|
||||
case ".mp4":
|
||||
return "video/mp4"
|
||||
case ".avi":
|
||||
return "video/avi"
|
||||
case ".mov":
|
||||
return "video/quicktime"
|
||||
case ".zip":
|
||||
return "application/zip"
|
||||
case ".rar":
|
||||
return "application/x-rar-compressed"
|
||||
case ".7z":
|
||||
return "application/x-7z-compressed"
|
||||
default:
|
||||
return "application/octet-stream"
|
||||
}
|
||||
}
|
||||
|
||||
// GetFileByCode 根据取件码获取文件信息
|
||||
func (fs *FileService) GetFileByCode(code string) (*models.FileInfo, error) {
|
||||
store := GetStore()
|
||||
return store.GetFileInfo(code)
|
||||
}
|
||||
|
||||
// DeleteFile 删除文件
|
||||
func (fs *FileService) DeleteFile(code string) error {
|
||||
fileInfo, err := fs.GetFileByCode(code)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 删除物理文件
|
||||
if err := os.Remove(fileInfo.FilePath); err != nil {
|
||||
return fmt.Errorf("删除文件失败: %v", err)
|
||||
}
|
||||
|
||||
// 从内存存储删除文件信息
|
||||
store := GetStore()
|
||||
store.DeleteFileInfo(code)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConvertEpubToMobi 将EPUB转换为MOBI格式
|
||||
func (fs *FileService) ConvertEpubToMobi(epubPath string) (string, error) {
|
||||
// TODO: 集成Calibre API进行格式转换
|
||||
// 这里暂时返回原文件路径
|
||||
return epubPath, fmt.Errorf("格式转换功能尚未实现")
|
||||
}
|
||||
|
||||
// CleanExpiredFiles 清理过期文件
|
||||
func (fs *FileService) CleanExpiredFiles() error {
|
||||
// TODO: 实现定期清理过期文件的逻辑
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user