From 3f3b7d8f1812ce91bd837890aceb9b0abfa347c8 Mon Sep 17 00:00:00 2001 From: MatrixSeven Date: Tue, 5 Aug 2025 18:02:00 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E6=B7=BB=E5=8A=A0=E9=93=BE=E6=8E=A5QR?= =?UTF-8?q?=E6=94=AF=E6=8C=81=EF=BC=8C=E5=88=A0=E9=99=A4=E6=97=A0=E7=94=A8?= =?UTF-8?q?=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chuan-next/package.json | 2 + chuan-next/src/app/HomePage-new.tsx | 80 ++++---- chuan-next/src/app/api/room-info/route.ts | 7 + chuan-next/src/components/QRCodeDisplay.tsx | 68 +++++++ .../components/webrtc/WebRTCFileUpload.tsx | 110 +++++----- chuan-next/src/hooks/useTabManager.ts | 8 +- chuan-next/yarn.lock | 188 +++++++++++++++++- internal/services/file_service.go | 168 ---------------- 8 files changed, 369 insertions(+), 262 deletions(-) create mode 100644 chuan-next/src/components/QRCodeDisplay.tsx delete mode 100644 internal/services/file_service.go diff --git a/chuan-next/package.json b/chuan-next/package.json index 954bbf1..bc30b4b 100644 --- a/chuan-next/package.json +++ b/chuan-next/package.json @@ -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", diff --git a/chuan-next/src/app/HomePage-new.tsx b/chuan-next/src/app/HomePage-new.tsx index bc6851f..2bfaa86 100644 --- a/chuan-next/src/app/HomePage-new.tsx +++ b/chuan-next/src/app/HomePage-new.tsx @@ -50,19 +50,21 @@ export default function HomePage() { 文本传输 文本 + 开发中 共享桌面 桌面 + 开发中 @@ -74,49 +76,43 @@ export default function HomePage() { - { - 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; - } - }} - /> +
+
+
+ +
+

文字传输

+

此功能正在开发中...

+
+

+ 🚧 敬请期待!我们正在为您开发更便捷的文字传输功能 +

+
+

+ 目前请使用文件传输功能 +

+
+
- +
+
+
+ +
+

桌面共享

+

此功能正在开发中...

+
+

+ 🚧 敬请期待!我们正在为您开发实时桌面共享功能 +

+
+

+ 目前请使用文件传输功能 +

+
+
diff --git a/chuan-next/src/app/api/room-info/route.ts b/chuan-next/src/app/api/room-info/route.ts index 68ef2e3..c27fdee 100644 --- a/chuan-next/src/app/api/room-info/route.ts +++ b/chuan-next/src/app/api/room-info/route.ts @@ -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}`, { diff --git a/chuan-next/src/components/QRCodeDisplay.tsx b/chuan-next/src/components/QRCodeDisplay.tsx new file mode 100644 index 0000000..7dc3994 --- /dev/null +++ b/chuan-next/src/components/QRCodeDisplay.tsx @@ -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(null); + const [error, setError] = useState(''); + + 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 ( +
+

{error}

+
+ ); + } + + return ( +
+ {title && ( +

{title}

+ )} +
+ +
+
+ ); +} diff --git a/chuan-next/src/components/webrtc/WebRTCFileUpload.tsx b/chuan-next/src/components/webrtc/WebRTCFileUpload.tsx index bbbe1a4..3e37f07 100644 --- a/chuan-next/src/components/webrtc/WebRTCFileUpload.tsx +++ b/chuan-next/src/components/webrtc/WebRTCFileUpload.tsx @@ -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 && (
-
-
- -
-

- 取件码生成成功! -

-

分享以下信息给接收方

-
- -
- {/* 取件码 */} -
- -
-
-
- {pickupCode} -
-
- + {/* 左上角状态提示 - 类似已选择文件的风格 */} +
+
+
+ +
+
+

取件码生成成功!

+

分享以下信息给接收方

+
- {/* 取件链接 */} + {/* 中间区域:取件码 + 分隔线 + 二维码 */} +
+ {/* 左侧:取件码 */} +
+ +
+
+ {pickupCode} +
+
+ +
+ + {/* 分隔线 - 大屏幕显示竖线,移动端隐藏 */} +
+ + {/* 右侧:二维码 */} {pickupLink && ( -
- -
-
-
- {pickupLink} -
-
- +
+ +
+ +
+
+ 使用手机扫码快速访问
)}
- {/* 使用提示 */} -
-

- 💡 使用提示:接收方输入取件码或访问取件链接即可下载文件 -

-
+ {/* 底部:取件链接 */} + {pickupLink && ( +
+
+
+
+ {pickupLink} +
+
+ +
+
+ )}
)}
diff --git a/chuan-next/src/hooks/useTabManager.ts b/chuan-next/src/hooks/useTabManager.ts index f8da63e..2166827 100644 --- a/chuan-next/src/hooks/useTabManager.ts +++ b/chuan-next/src/hooks/useTabManager.ts @@ -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; } diff --git a/chuan-next/yarn.lock b/chuan-next/yarn.lock index 9f83bfc..80acb87 100644 --- a/chuan-next/yarn.lock +++ b/chuan-next/yarn.lock @@ -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" diff --git a/internal/services/file_service.go b/internal/services/file_service.go deleted file mode 100644 index 94db41a..0000000 --- a/internal/services/file_service.go +++ /dev/null @@ -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 -}