Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ NEXTAUTH_SECRET=your-secret-key-at-least-32-chars-long
AUTH_TRUST_HOST=true

# Zitadel
AUTH_ZITADEL_ID=""
AUTH_ZITADEL_SECRET=""
AUTH_ZITADEL_ISSUER=""
# AUTH_ZITADEL_ID=
# AUTH_ZITADEL_SECRET=
# AUTH_ZITADEL_ISSUER=

# S3
S3_REGION=
S3_BUCKET_NAME=
S3_ACCESS_KEY_ID=
S3_SECRET_ACCESS_KEY=
# 可选配置,用于支持其他兼容S3的存储服务
S3_ENDPOINT=
S3_ENDPOINT=
S3_BASE_PATH= # Optional, base path for S3 operations. Can be single or multi-level directory
Original file line number Diff line number Diff line change
Expand Up @@ -107,46 +107,31 @@ export async function POST(
*/
async function compressFiles(transferCodeId: string) {
try {
// 获取所有文件列表
const files = await prisma.file.findMany({
where: {
transferCodes: {
some: {
transferCodeId
}
},
isDirectory: false // 只获取文件,不包括文件夹
// 更新状态为处理中,但不设置初始进度
await prisma.transferCode.update({
where: {id: transferCodeId},
data: {
compressStatus: "PROCESSING",
compressProgress: 0 // 进度从0开始,由文件处理过程实时更新
}
})

let processedFiles = 0
const totalFiles = files.length

// 创建压缩包
for (const file of files) {
try {
// 从S3下载文件并添加到压缩包
await fileService.addFileToCompress(file.id, transferCodeId)

// 更新进度
processedFiles++
const progress = Math.round((processedFiles / totalFiles) * 100)

await prisma.transferCode.update({
where: {id: transferCodeId},
data: {
compressProgress: progress
}
})
} catch (error) {
console.error(`Error processing file ${file.id}:`, error)
}
}
// 直接使用finalizeCompress方法,一次性处理所有文件
try {
await fileService.finalizeCompress(transferCodeId)
} catch (error) {
// 即使压缩过程中有错误,也继续执行

// 完成压缩
await fileService.finalizeCompress(transferCodeId)
// 检查当前进度
const currentCode = await prisma.transferCode.findUnique({
where: {id: transferCodeId},
select: {compressProgress: true}
})

// 更新压缩状态
if (!currentCode) throw error
}

// 更新压缩状态为完成
await prisma.transferCode.update({
where: {id: transferCodeId},
data: {
Expand All @@ -157,6 +142,29 @@ async function compressFiles(transferCodeId: string) {

} catch (error) {
console.error("Compress files error:", error)

try {
// 尝试获取当前压缩进度
const currentCode = await prisma.transferCode.findUnique({
where: {id: transferCodeId},
select: {compressProgress: true, compressStatus: true}
})

// 如果压缩进度已经很高,可能文件已经可用,将状态设为完成
if (currentCode && currentCode.compressProgress >= 80) {
await prisma.transferCode.update({
where: {id: transferCodeId},
data: {
compressStatus: "COMPLETED",
compressProgress: 100
}
})
return
}
} catch (innerError) {
console.error("获取压缩进度失败:", innerError)
}

// 更新压缩状态为失败
await prisma.transferCode.update({
where: {id: transferCodeId},
Expand Down
4 changes: 3 additions & 1 deletion app/api/(user)/transfer-codes/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,10 +198,12 @@ export async function DELETE(request: Request) {

// 2.1 尝试删除压缩包(如果存在)
try {
// 使用S3StorageService的方法获取正确的压缩包S3键
const compressKey = s3Service.getCompressS3Key(transferCodeId)
// 不需要检查压缩包是否存在,直接尝试删除
const command = new DeleteObjectCommand({
Bucket: S3_CONFIG.bucket,
Key: `compress/${transferCodeId}/archive.zip`,
Key: compressKey,
});
await s3Client.send(command);
console.log(`已删除传输码 ${transferCodeId} 的压缩包`);
Expand Down
42 changes: 21 additions & 21 deletions components/settings/transfer-code-list/transfer-code-list.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
"use client"

import { useEffect, useState } from "react"
import { DataTable } from "@/components/ui/data-table"
import { Button } from "@/components/ui/button"
import {useEffect, useState} from "react"
import {DataTable} from "@/components/ui/data-table"
import {Button} from "@/components/ui/button"
import {
AlertDialog,
AlertDialogAction,
Expand All @@ -14,18 +14,18 @@ import {
AlertDialogTitle,
} from "@/components/ui/alert-dialog"
import axios from "axios"
import { toast } from "sonner"
import { getApiErrorMessage } from "@/lib/utils/error-messages"
import { Skeleton } from "@/components/ui/skeleton"
import { Input } from "@/components/ui/input"
import {toast} from "sonner"
import {getApiErrorMessage} from "@/lib/utils/error-messages"
import {Skeleton} from "@/components/ui/skeleton"
import {Input} from "@/components/ui/input"
import {Trash2, CircleOff, RefreshCw, Circle} from "lucide-react"
import { TransferCode, TransferCodeListProps } from "./types"
import {TransferCode, TransferCodeListProps} from "./types"

export function TransferCodeList({
type,
getColumnsAction,
onRefreshRef,
}: TransferCodeListProps) {
type,
getColumnsAction,
onRefreshRef,
}: TransferCodeListProps) {
// 状态管理
const [data, setData] = useState<TransferCode[]>([]) // 存储传输码数据
const [loading, setLoading] = useState(true) // 加载状态
Expand Down Expand Up @@ -54,13 +54,13 @@ export function TransferCodeList({

// 暴露刷新方法给父组件
useEffect(() => {
if (onRefreshRef) {
onRefreshRef.current = fetchData
}
if (onRefreshRef) onRefreshRef.current = fetchData
}, [onRefreshRef])

// 组件挂载时获取数据
useEffect(() => {void fetchData()}, [type])
useEffect(() => {
void fetchData()
}, [type])

// 批量删除处理
const handleBatchDelete = async () => {
Expand Down Expand Up @@ -119,7 +119,7 @@ export function TransferCodeList({
const columns = getColumnsAction({onRefresh: fetchData})

// 过滤数据
const filteredData = data.filter(item =>
const filteredData = data.filter(item =>
item.code.toLowerCase().includes(searchQuery.toLowerCase()) ||
(item.comment && item.comment.toLowerCase().includes(searchQuery.toLowerCase()))
)
Expand All @@ -140,7 +140,7 @@ export function TransferCodeList({
disabled={loading}
className="text-yellow-700 dark:text-yellow-500 hover:text-yellow-600 ml-3"
>
<RefreshCw className="h-4 w-4" />
<RefreshCw className="h-4 w-4"/>
</Button>
{selectedRows.length > 0 && (
<>
Expand All @@ -150,7 +150,7 @@ export function TransferCodeList({
disabled={isDeleting || isDisabling}
size="sm"
>
<CircleOff className="h-4 w-4" />
<CircleOff className="h-4 w-4"/>
禁用
</Button>
<Button
Expand All @@ -159,7 +159,7 @@ export function TransferCodeList({
disabled={isDeleting || isDisabling}
size="sm"
>
<Circle className="h-4 w-4" />
<Circle className="h-4 w-4"/>
启用
</Button>
<Button
Expand All @@ -169,7 +169,7 @@ export function TransferCodeList({
size="icon"
className="text-destructive hover:text-destructive dark:text-red-500 dark:hover:text-red-600"
>
<Trash2 className="h-4 w-4" />
<Trash2 className="h-4 w-4"/>
</Button>
</>
)}
Expand Down
5 changes: 5 additions & 0 deletions lib/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ const s3ConfigSchema = z.object({
bucket: z.string({ required_error: 'S3_BUCKET_NAME 未配置' }),
accessKeyId: z.string({ required_error: 'S3_ACCESS_KEY_ID 未配置' }),
secretAccessKey: z.string({ required_error: 'S3_SECRET_ACCESS_KEY 未配置' }),
basePath: z.string().optional(),
})

// 验证S3配置
Expand All @@ -100,6 +101,7 @@ function validateS3Config() {
bucket: process.env.S3_BUCKET_NAME,
accessKeyId: process.env.S3_ACCESS_KEY_ID,
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
basePath: process.env.S3_BASE_PATH,
})
} catch (error) {
if (error instanceof z.ZodError) {
Expand All @@ -126,6 +128,9 @@ export const S3_CONFIG = {
validateS3Config()
return process.env.S3_BUCKET_NAME
},
get basePath() {
return process.env.S3_BASE_PATH || ''
},
ignoreErrors: process.env.IGNORE_S3_ERRORS === 'true' || false,
// 敏感配置只在服务器端环境提供
get accessKeyId() {
Expand Down
16 changes: 15 additions & 1 deletion lib/s3/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,20 @@ export class S3StorageService {
* @returns 完整的S3路径
*/
getFullS3Key(s3BasePath: string, relativePath: string): string {
return `${s3BasePath}/${relativePath}`
const globalBasePath = S3_CONFIG.basePath.trim()
const path = `${s3BasePath}/${relativePath}`
return globalBasePath ? `${globalBasePath}/${path}` : path
}

/**
* 获取压缩文件的完整S3路径
* @param transferCodeId 传输码ID
* @returns 压缩文件的完整S3路径
*/
getCompressS3Key(transferCodeId: string): string {
const compressPath = `compress/${transferCodeId}/archive.zip`
const globalBasePath = S3_CONFIG.basePath.trim()
return globalBasePath ? `${globalBasePath}/${compressPath}` : compressPath
}

/**
Expand Down Expand Up @@ -74,6 +87,7 @@ export class S3StorageService {
throw new Error('S3 bucket not configured')
}

// 这里不使用getFullS3Key,因为params.Key应该已经是完整路径
const {url, fields} = await createPresignedPost(s3Client, {
Bucket: S3_CONFIG.bucket,
Key: params.Key,
Expand Down
Loading