rht
This commit is contained in:
parent
0008f405fe
commit
6985ee863f
|
@ -0,0 +1,14 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { TrpcService } from '@server/trpc/trpc.service';
|
||||
import { ResourceModule } from '../resource/resource.module';
|
||||
import { ShareCodeService } from './share-code.service';
|
||||
import { ShareCodeRouter } from './share-code.router';
|
||||
|
||||
@Module({
|
||||
imports: [ResourceModule],
|
||||
providers: [TrpcService, ShareCodeService, ShareCodeRouter],
|
||||
exports: [ShareCodeService, ShareCodeRouter],
|
||||
controllers: [],
|
||||
})
|
||||
export class ShareCodeModule { }
|
|
@ -0,0 +1,49 @@
|
|||
import { z } from "zod";
|
||||
import { ShareCodeService } from "./share-code.service";
|
||||
import { TrpcService } from "@server/trpc/trpc.service";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
@Injectable()
|
||||
export class ShareCodeRouter {
|
||||
constructor(
|
||||
private readonly shareCodeService: ShareCodeService,
|
||||
private readonly trpc: TrpcService
|
||||
) {}
|
||||
|
||||
router = this.trpc.router({
|
||||
generateShareCode: this.trpc.procedure
|
||||
.input(z.object({ fileId: z.string() }))
|
||||
.mutation(async ({ input }) => {
|
||||
return this.shareCodeService.generateShareCode(input.fileId);
|
||||
}),
|
||||
validateAndUseCode: this.trpc.procedure
|
||||
.input(z.object({ code: z.string() }))
|
||||
.mutation(async ({ input }) => {
|
||||
return this.shareCodeService.validateAndUseCode(input.code);
|
||||
}),
|
||||
getShareCodeInfo: this.trpc.procedure
|
||||
.input(z.object({ code: z.string() }))
|
||||
.query(async ({ input }) => {
|
||||
return this.shareCodeService.getShareCodeInfo(input.code);
|
||||
}),
|
||||
hasActiveShareCode: this.trpc.procedure
|
||||
.input(z.object({ fileId: z.string() }))
|
||||
.query(async ({ input }) => {
|
||||
return this.shareCodeService.hasActiveShareCode(input.fileId);
|
||||
}),
|
||||
getFileShareHistory: this.trpc.procedure
|
||||
.input(z.object({ fileId: z.string() }))
|
||||
.query(async ({ input }) => {
|
||||
return this.shareCodeService.getFileShareHistory(input.fileId);
|
||||
}),
|
||||
getFileByShareCode: this.trpc.procedure
|
||||
.input(z.object({ code: z.string() }))
|
||||
.query(async ({ input }) => {
|
||||
return this.shareCodeService.getFileByShareCode(input.code);
|
||||
}),
|
||||
generateShareCodeByFileId: this.trpc.procedure
|
||||
.input(z.object({ fileId: z.string() }))
|
||||
.mutation(async ({ input }) => {
|
||||
return this.shareCodeService.generateShareCodeByFileId(input.fileId);
|
||||
}),
|
||||
});
|
||||
}
|
|
@ -0,0 +1,239 @@
|
|||
import { Injectable, Logger, NotFoundException } from '@nestjs/common';
|
||||
import { customAlphabet } from 'nanoid-cjs';
|
||||
import { db } from '@nice/common';
|
||||
import { Cron, CronExpression } from '@nestjs/schedule';
|
||||
import { ResourceService } from '@server/models/resource/resource.service';
|
||||
|
||||
export interface ShareCode {
|
||||
id: string;
|
||||
code: string;
|
||||
fileId: string;
|
||||
createdAt: Date;
|
||||
expiresAt: Date;
|
||||
isUsed: boolean;
|
||||
fileName?: string | null;
|
||||
}
|
||||
export interface GenerateShareCodeResponse {
|
||||
code: string;
|
||||
expiresAt: Date;
|
||||
}
|
||||
|
||||
interface ResourceMeta {
|
||||
filename: string;
|
||||
filetype: string;
|
||||
filesize: string;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class ShareCodeService {
|
||||
private readonly logger = new Logger(ShareCodeService.name);
|
||||
// 生成8位分享码,使用易读的字符
|
||||
private readonly generateCode = customAlphabet(
|
||||
'23456789ABCDEFGHJKLMNPQRSTUVWXYZ',
|
||||
8,
|
||||
);
|
||||
|
||||
constructor(private readonly resourceService: ResourceService) { }
|
||||
|
||||
async generateShareCode(
|
||||
fileId: string,
|
||||
fileName?: string,
|
||||
): Promise<GenerateShareCodeResponse> {
|
||||
try {
|
||||
// 检查文件是否存在
|
||||
const resource = await this.resourceService.findUnique({
|
||||
where: { fileId },
|
||||
});
|
||||
console.log('完整 fileId:', fileId); // 确保与前端一致
|
||||
|
||||
if (!resource) {
|
||||
throw new NotFoundException('文件不存在');
|
||||
}
|
||||
|
||||
// 生成分享码
|
||||
const code = this.generateCode();
|
||||
const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24小时后过期
|
||||
|
||||
// 查找是否已有分享码记录
|
||||
const existingShareCode = await db.shareCode.findUnique({
|
||||
where: { fileId },
|
||||
});
|
||||
|
||||
if (existingShareCode) {
|
||||
// 更新现有记录,但保留原有文件名
|
||||
await db.shareCode.update({
|
||||
where: { fileId },
|
||||
data: {
|
||||
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 {
|
||||
code,
|
||||
expiresAt,
|
||||
};
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to generate share code', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async validateAndUseCode(code: string): Promise<ShareCode | null> {
|
||||
try {
|
||||
console.log(`尝试验证分享码: ${code}`);
|
||||
|
||||
// 查找有效的分享码
|
||||
const shareCode = await db.shareCode.findFirst({
|
||||
where: {
|
||||
code,
|
||||
isUsed: false,
|
||||
expiresAt: { gt: new Date() },
|
||||
},
|
||||
});
|
||||
|
||||
console.log('查询结果:', shareCode);
|
||||
|
||||
if (!shareCode) {
|
||||
console.log('分享码无效或已过期');
|
||||
return null;
|
||||
}
|
||||
|
||||
// 标记分享码为已使用
|
||||
// await db.shareCode.update({
|
||||
// where: { id: shareCode.id },
|
||||
// data: { isUsed: true },
|
||||
// });
|
||||
|
||||
// 记录使用日志
|
||||
this.logger.log(`Share code ${code} used for file ${shareCode.fileId}`);
|
||||
|
||||
// 返回完整的分享码信息,包括文件名
|
||||
return shareCode;
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to validate share code', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 每天清理过期的分享码
|
||||
@Cron(CronExpression.EVERY_DAY_AT_MIDNIGHT)
|
||||
async cleanupExpiredShareCodes() {
|
||||
try {
|
||||
const result = await db.shareCode.deleteMany({
|
||||
where: {
|
||||
OR: [{ expiresAt: { lt: new Date() } }, { isUsed: true }],
|
||||
},
|
||||
});
|
||||
|
||||
this.logger.log(`Cleaned up ${result.count} expired share codes`);
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to cleanup expired share codes', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取分享码信息
|
||||
async getShareCodeInfo(code: string): Promise<ShareCode | null> {
|
||||
try {
|
||||
return await db.shareCode.findFirst({
|
||||
where: { code },
|
||||
});
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to get share code info', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查文件是否已经生成过分享码
|
||||
async hasActiveShareCode(fileId: string): Promise<boolean> {
|
||||
try {
|
||||
const activeCode = await db.shareCode.findFirst({
|
||||
where: {
|
||||
fileId,
|
||||
isUsed: false,
|
||||
expiresAt: { gt: new Date() },
|
||||
},
|
||||
});
|
||||
|
||||
return !!activeCode;
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to check active share code', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取文件的所有分享记录
|
||||
async getFileShareHistory(fileId: string) {
|
||||
try {
|
||||
return await db.shareCode.findMany({
|
||||
where: { fileId },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
});
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to get file share history', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// 根据分享码获取文件
|
||||
async getFileByShareCode(code: string) {
|
||||
console.log('收到验证分享码请求,code:', code);
|
||||
|
||||
const shareCode = await this.validateAndUseCode(code);
|
||||
console.log('验证分享码结果:', shareCode);
|
||||
|
||||
if (!shareCode) {
|
||||
throw new NotFoundException('分享码无效或已过期');
|
||||
}
|
||||
|
||||
// 获取文件信息
|
||||
const resource = await this.resourceService.findUnique({
|
||||
where: { fileId: shareCode.fileId },
|
||||
});
|
||||
console.log('获取到的资源信息:', resource);
|
||||
const { filename } = resource.meta as any as ResourceMeta
|
||||
if (!resource) {
|
||||
throw new NotFoundException('文件不存在');
|
||||
}
|
||||
|
||||
// 直接返回正确的数据结构
|
||||
const response = {
|
||||
fileId: shareCode.fileId,
|
||||
fileName: filename || 'downloaded_file',
|
||||
code: shareCode.code,
|
||||
expiresAt: shareCode.expiresAt
|
||||
};
|
||||
|
||||
console.log('返回给前端的数据:', response); // 添加日志
|
||||
return response;
|
||||
}
|
||||
|
||||
// 根据文件ID生成分享码
|
||||
async generateShareCodeByFileId(fileId: string) {
|
||||
try {
|
||||
console.log('收到生成分享码请求,fileId:', fileId);
|
||||
const result = await this.generateShareCode(fileId);
|
||||
console.log('生成分享码结果:', result);
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('生成分享码错误:', error);
|
||||
return error
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -14,7 +14,7 @@ import { VisitModule } from '@server/models/visit/visit.module';
|
|||
import { WebSocketModule } from '@server/socket/websocket.module';
|
||||
import { RoleMapModule } from '@server/models/rbac/rbac.module';
|
||||
import { TransformModule } from '@server/models/transform/transform.module';
|
||||
|
||||
import { ShareCodeModule } from '@server/models/share-code/share-code.module';
|
||||
import { ResourceModule } from '@server/models/resource/resource.module';
|
||||
|
||||
@Module({
|
||||
|
@ -33,6 +33,7 @@ import { ResourceModule } from '@server/models/resource/resource.module';
|
|||
VisitModule,
|
||||
WebSocketModule,
|
||||
ResourceModule,
|
||||
ShareCodeModule,
|
||||
],
|
||||
controllers: [],
|
||||
providers: [TrpcService, TrpcRouter, Logger],
|
||||
|
|
|
@ -14,7 +14,7 @@ import { RoleMapRouter } from '@server/models/rbac/rolemap.router';
|
|||
import { TransformRouter } from '@server/models/transform/transform.router';
|
||||
import { RoleRouter } from '@server/models/rbac/role.router';
|
||||
import { ResourceRouter } from '../models/resource/resource.router';
|
||||
|
||||
import { ShareCodeRouter } from '@server/models/share-code/share-code.router';
|
||||
@Injectable()
|
||||
export class TrpcRouter {
|
||||
logger = new Logger(TrpcRouter.name);
|
||||
|
@ -32,6 +32,7 @@ export class TrpcRouter {
|
|||
private readonly message: MessageRouter,
|
||||
private readonly visitor: VisitRouter,
|
||||
private readonly resource: ResourceRouter,
|
||||
private readonly shareCode: ShareCodeRouter,
|
||||
) {}
|
||||
getRouter() {
|
||||
return;
|
||||
|
@ -49,6 +50,7 @@ export class TrpcRouter {
|
|||
app_config: this.app_config.router,
|
||||
visitor: this.visitor.router,
|
||||
resource: this.resource.router,
|
||||
shareCode: this.shareCode.router,
|
||||
});
|
||||
wss: WebSocketServer = undefined;
|
||||
|
||||
|
|
|
@ -19,6 +19,13 @@ import { Request, Response } from 'express';
|
|||
import { TusService } from './tus.service';
|
||||
import { ShareCodeService } from './share-code.service';
|
||||
import { ResourceService } from '@server/models/resource/resource.service';
|
||||
|
||||
interface ResourceMeta {
|
||||
filename: string;
|
||||
filetype: string;
|
||||
filesize: string;
|
||||
}
|
||||
|
||||
@Controller('upload')
|
||||
export class UploadController {
|
||||
constructor(
|
||||
|
@ -61,7 +68,7 @@ export class UploadController {
|
|||
where: { fileId: shareCode.fileId },
|
||||
});
|
||||
console.log('获取到的资源信息:', resource);
|
||||
|
||||
const {filename} = resource.meta as any as ResourceMeta
|
||||
if (!resource) {
|
||||
throw new NotFoundException('文件不存在');
|
||||
}
|
||||
|
@ -69,7 +76,7 @@ export class UploadController {
|
|||
// 直接返回正确的数据结构
|
||||
const response = {
|
||||
fileId: shareCode.fileId,
|
||||
fileName:shareCode.fileName || 'downloaded_file',
|
||||
fileName:filename || 'downloaded_file',
|
||||
code: shareCode.code,
|
||||
expiresAt: shareCode.expiresAt
|
||||
};
|
||||
|
|
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 828 B |
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 828 B |
|
@ -1,10 +1,11 @@
|
|||
import { useTusUpload } from "@web/src/hooks/useTusUpload";
|
||||
import { ShareCodeGenerator } from "../sharecode/sharecodegenerator";
|
||||
import { ShareCodeValidator } from "../sharecode/sharecodevalidator";
|
||||
import { useState, useRef, useCallback } from "react";
|
||||
import { message, Progress, Button, Tabs, DatePicker } from "antd";
|
||||
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 { env } from '../../../env'
|
||||
import { TusUploader } from "@web/src/components/common/uploader/TusUploader";
|
||||
const { TabPane } = Tabs;
|
||||
|
||||
export default function DeptSettingPage() {
|
||||
|
@ -15,18 +16,11 @@ export default function DeptSettingPage() {
|
|||
const [isDragging, setIsDragging] = useState(false);
|
||||
const [expireTime, setExpireTime] = useState<Date | null>(null);
|
||||
const dropRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const [form] = Form.useForm();
|
||||
const [currentFile, setCurrentFile] = useState<string[]>([])
|
||||
const uploadFileId = Form.useWatch(["file"], form)?.[0]
|
||||
// 使用您的 useTusUpload hook
|
||||
const { uploadProgress, isUploading, uploadError, handleFileUpload } = useTusUpload({
|
||||
onSuccess: (result) => {
|
||||
setUploadedFileId(result.fileId);
|
||||
setUploadedFileName(result.fileName);
|
||||
message.success('文件上传成功');
|
||||
},
|
||||
onError: (error: Error) => {
|
||||
message.error('上传失败:' + error.message);
|
||||
}
|
||||
});
|
||||
const { uploadProgress, isUploading, uploadError, handleFileUpload } = useTusUpload();
|
||||
|
||||
// 清除已上传文件
|
||||
const handleClearFile = () => {
|
||||
|
@ -43,7 +37,7 @@ export default function DeptSettingPage() {
|
|||
message.warning('只能上传一个文件,请先删除已上传的文件');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const fileKey = `file-${Date.now()}`; // 生成唯一的文件标识
|
||||
|
||||
handleFileUpload(
|
||||
|
@ -110,17 +104,17 @@ export default function DeptSettingPage() {
|
|||
// 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);
|
||||
|
@ -202,150 +196,32 @@ export default function DeptSettingPage() {
|
|||
<TabPane tab="上传分享" key="upload">
|
||||
{/* 文件上传区域 */}
|
||||
<div style={{ marginBottom: '40px' }}>
|
||||
<h3>第一步:上传文件</h3>
|
||||
|
||||
<span className="text-lg block text-zinc-700 py-2">第一步:上传文件</span>
|
||||
{/* 如果没有已上传文件,显示上传区域 */}
|
||||
{uploadedFiles.length === 0 ? (
|
||||
<div
|
||||
ref={dropRef}
|
||||
onDragEnter={handleDragEnter}
|
||||
onDragOver={handleDragOver}
|
||||
onDragLeave={handleDragLeave}
|
||||
onDrop={handleDrop}
|
||||
style={{
|
||||
padding: '20px',
|
||||
border: `2px dashed ${isDragging ? '#1890ff' : '#d9d9d9'}`,
|
||||
borderRadius: '8px',
|
||||
textAlign: 'center',
|
||||
backgroundColor: isDragging ? 'rgba(24, 144, 255, 0.05)' : 'transparent',
|
||||
transition: 'all 0.3s',
|
||||
marginBottom: '20px'
|
||||
}}
|
||||
>
|
||||
<InboxOutlined style={{ fontSize: '48px', color: isDragging ? '#1890ff' : '#d9d9d9' }} />
|
||||
<p>点击或拖拽文件到此区域进行上传</p>
|
||||
<p style={{ fontSize: '12px', color: '#888' }}>只能上传单个文件</p>
|
||||
|
||||
<input
|
||||
type="file"
|
||||
id="file-input"
|
||||
style={{ display: 'none' }}
|
||||
onChange={(e) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (file) {
|
||||
handleFileSelect(file);
|
||||
}
|
||||
}}
|
||||
disabled={isUploading}
|
||||
/>
|
||||
<label
|
||||
htmlFor="file-input"
|
||||
style={{
|
||||
display: 'inline-block',
|
||||
padding: '8px 16px',
|
||||
backgroundColor: '#1890ff',
|
||||
color: 'white',
|
||||
borderRadius: '4px',
|
||||
cursor: 'pointer',
|
||||
marginTop: '10px'
|
||||
}}
|
||||
>
|
||||
<UploadOutlined /> 选择文件
|
||||
</label>
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ marginBottom: '20px' }}>
|
||||
<div style={{
|
||||
padding: '10px',
|
||||
backgroundColor: '#f6ffed',
|
||||
border: '1px solid #b7eb8f',
|
||||
borderRadius: '4px',
|
||||
marginBottom: '10px'
|
||||
}}>
|
||||
<p style={{ color: '#52c41a', margin: 0 }}>
|
||||
您已上传文件,请继续下一步生成分享码
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 已上传文件列表 */}
|
||||
{uploadedFiles.length > 0 && (
|
||||
<div style={{
|
||||
border: '1px solid #f0f0f0',
|
||||
borderRadius: '4px',
|
||||
overflow: 'hidden'
|
||||
}}>
|
||||
{uploadedFiles.map((file) => (
|
||||
<div key={file.id} style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
padding: '10px 15px',
|
||||
backgroundColor: '#fafafa'
|
||||
}}>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
flex: 1
|
||||
}}>
|
||||
<div style={{
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
borderRadius: '50%',
|
||||
backgroundColor: '#52c41a',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginRight: '10px'
|
||||
}}>
|
||||
<span style={{ color: 'white', fontSize: '12px' }}>✓</span>
|
||||
</div>
|
||||
<span>{file.name}</span>
|
||||
</div>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<DeleteOutlined style={{ color: '#ff4d4f' }} />}
|
||||
onClick={() => handleDeleteFile(file.id)}
|
||||
title="删除此文件"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isUploading && (
|
||||
<div style={{ marginTop: '20px' }}>
|
||||
<Progress
|
||||
percent={Math.round(Object.values(uploadProgress)[0] || 0)}
|
||||
status="active"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{uploadError && (
|
||||
<div style={{ color: '#ff4d4f', marginTop: '10px' }}>
|
||||
{uploadError}
|
||||
</div>
|
||||
)}
|
||||
<Form form={form}>
|
||||
<Form.Item name="file">
|
||||
<TusUploader
|
||||
multiple={false}
|
||||
style={"w-full py-4"}
|
||||
></TusUploader>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</div>
|
||||
|
||||
{/* 生成分享码区域 */}
|
||||
{uploadedFileId && (
|
||||
<div style={{ marginBottom: '40px' }}>
|
||||
<h3>第二步:生成分享码</h3>
|
||||
|
||||
<ShareCodeGenerator
|
||||
fileId={uploadedFileId}
|
||||
onSuccess={handleShareSuccess}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div style={{ marginBottom: '40px' }}>
|
||||
<span className="text-lg block text-zinc-700 py-4">第二步:生成分享码</span>
|
||||
<ShareCodeGenerator
|
||||
fileId={uploadFileId}
|
||||
onSuccess={handleShareSuccess}
|
||||
/>
|
||||
</div>
|
||||
</TabPane>
|
||||
|
||||
{/* 使用分享码区域 */}
|
||||
<TabPane tab="下载文件" key="download">
|
||||
<div>
|
||||
<h3>使用分享码下载文件</h3>
|
||||
<span className="text-lg block text-zinc-700 py-4">使用分享码下载文件</span>
|
||||
<ShareCodeValidator
|
||||
onValidSuccess={handleValidSuccess}
|
||||
/>
|
||||
|
|
|
@ -1,14 +1,20 @@
|
|||
|
||||
import React, { useState } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Button, message } from 'antd';
|
||||
import { CopyOutlined } from '@ant-design/icons';
|
||||
import {env} from '../../../env'
|
||||
import { env } from '../../../env'
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { getQueryKey } from '@trpc/react-query';
|
||||
import { api } from '@nice/client';
|
||||
interface ShareCodeGeneratorProps {
|
||||
fileId: string;
|
||||
onSuccess?: (code: string) => void;
|
||||
fileName?: string;
|
||||
}
|
||||
|
||||
interface ShareCodeResponse {
|
||||
code?: string;
|
||||
expiresAt?: Date;
|
||||
}
|
||||
export const ShareCodeGenerator: React.FC<ShareCodeGeneratorProps> = ({
|
||||
fileId,
|
||||
onSuccess,
|
||||
|
@ -17,53 +23,29 @@ export const ShareCodeGenerator: React.FC<ShareCodeGeneratorProps> = ({
|
|||
const [loading, setLoading] = useState(false);
|
||||
const [shareCode, setShareCode] = useState<string>('');
|
||||
const [expiresAt, setExpiresAt] = useState<Date | null>(null);
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
const queryKey = getQueryKey(api.term);
|
||||
const [isGenerate, setIsGenerate] = useState(false);
|
||||
const [currentFileId, setCurrentFileId] = useState<string>('');
|
||||
const generateShareCode = api.shareCode.generateShareCodeByFileId.useMutation({
|
||||
onSuccess: (data) => {
|
||||
queryClient.invalidateQueries({ queryKey });
|
||||
},
|
||||
});
|
||||
useEffect(() => {
|
||||
if (fileId !== currentFileId || !fileId) {
|
||||
setIsGenerate(false);
|
||||
}
|
||||
setCurrentFileId(fileId);
|
||||
}, [fileId])
|
||||
const generateCode = async () => {
|
||||
setLoading(true);
|
||||
console.log('开始生成分享码,fileId:', fileId, 'fileName:', fileName);
|
||||
|
||||
try {
|
||||
const response = await fetch(`http://${env.SERVER_IP}:${env.SERVER_PORT}/upload/share/${fileId}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
fileId
|
||||
})
|
||||
});
|
||||
console.log('Current fileId:', fileId); // 确保 fileId 有效
|
||||
console.log('请求URL:', `http://${env.SERVER_IP}:${env.SERVER_PORT}/upload/share/${fileId}`);
|
||||
console.log('API响应状态:', response.status);
|
||||
|
||||
const responseText = await response.text();
|
||||
console.log('API原始响应:', responseText);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`请求失败: ${response.status} ${responseText || '无错误信息'}`);
|
||||
}
|
||||
|
||||
// 确保响应不为空
|
||||
if (!responseText) {
|
||||
throw new Error('服务器返回空响应');
|
||||
}
|
||||
|
||||
// 尝试解析JSON
|
||||
let data;
|
||||
try {
|
||||
data = JSON.parse(responseText);
|
||||
} catch (e) {
|
||||
console.error('解析响应JSON失败:', e);
|
||||
throw new Error('服务器响应格式错误');
|
||||
}
|
||||
|
||||
console.log('解析后的响应数据:', data); // 调试日志
|
||||
|
||||
if (!data.code) {
|
||||
throw new Error('响应中没有分享码');
|
||||
}
|
||||
|
||||
const data: ShareCodeResponse = await generateShareCode.mutateAsync({ fileId });
|
||||
console.log('生成分享码结果:', data);
|
||||
setShareCode(data.code);
|
||||
setIsGenerate(true);
|
||||
setExpiresAt(data.expiresAt ? new Date(data.expiresAt) : null);
|
||||
onSuccess?.(data.code);
|
||||
message.success('分享码生成成功');
|
||||
|
@ -79,10 +61,10 @@ export const ShareCodeGenerator: React.FC<ShareCodeGeneratorProps> = ({
|
|||
<div style={{ padding: '20px', backgroundColor: '#f8f9fa', borderRadius: '8px' }}>
|
||||
<div style={{ marginBottom: '10px' }}>
|
||||
{/* 添加调试信息 */}
|
||||
<small style={{ color: '#666' }}>文件ID: {fileId}</small>
|
||||
<small style={{ color: '#666' }}>文件ID: {fileId ? fileId : '未选择文件'}</small>
|
||||
</div>
|
||||
|
||||
{!shareCode ? (
|
||||
{!isGenerate ? (
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={generateCode}
|
||||
|
@ -93,7 +75,7 @@ export const ShareCodeGenerator: React.FC<ShareCodeGeneratorProps> = ({
|
|||
</Button>
|
||||
) : (
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<div style={{
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import React, { useState } from 'react';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { Input, Button, message } from 'antd';
|
||||
import styles from './ShareCodeValidator.module.css';
|
||||
import {env} from '../../../env'
|
||||
import { api } from '@nice/client';
|
||||
interface ShareCodeValidatorProps {
|
||||
onValidSuccess: (fileId: string, fileName: string) => void;
|
||||
}
|
||||
|
@ -11,8 +12,16 @@ export const ShareCodeValidator: React.FC<ShareCodeValidatorProps> = ({
|
|||
}) => {
|
||||
const [code, setCode] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { data: result, isLoading } = api.shareCode.getFileByShareCode.useQuery(
|
||||
{ code: code.trim() },
|
||||
{
|
||||
enabled: !!code.trim()
|
||||
}
|
||||
)
|
||||
|
||||
const validateCode = async () => {
|
||||
|
||||
|
||||
const validateCode = useCallback(() => {
|
||||
if (!code.trim()) {
|
||||
message.warning('请输入分享码');
|
||||
return;
|
||||
|
@ -20,31 +29,16 @@ export const ShareCodeValidator: React.FC<ShareCodeValidatorProps> = ({
|
|||
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await fetch(`http://${env.SERVER_IP}:${env.SERVER_PORT}/upload/share/${code.trim()}`);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
throw new Error(errorData.message || '分享码无效或已过期');
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
console.log('验证分享码返回数据:', data);
|
||||
|
||||
if (!data.fileId) {
|
||||
throw new Error('未找到文件ID');
|
||||
}
|
||||
|
||||
const fileName = data.fileName || 'downloaded_file';
|
||||
|
||||
onValidSuccess(data.fileId, fileName);
|
||||
message.success(`验证成功,文件名:${fileName}`);
|
||||
console.log('验证分享码返回数据:', result);
|
||||
onValidSuccess(result.fileId, result.fileName);
|
||||
message.success(`验证成功,文件名:${result.fileName}`);
|
||||
} catch (error) {
|
||||
console.error('验证分享码失败:', error);
|
||||
message.error('分享码无效或已过期');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
},[result])
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
|
|
|
@ -86,6 +86,7 @@ export const TusUploader = ({
|
|||
handleFileUpload(
|
||||
file,
|
||||
(result) => {
|
||||
console.log(result)
|
||||
setCompletedFiles((prev) => [
|
||||
...prev,
|
||||
{
|
||||
|
|
|
@ -10,7 +10,7 @@ interface UploadResult {
|
|||
fileName: string;
|
||||
}
|
||||
|
||||
export function useTusUpload(p0?: { onSuccess: (result: UploadResult) => void; onError: (error: Error) => void; }) {
|
||||
export function useTusUpload() {
|
||||
const [uploadProgress, setUploadProgress] = useState<
|
||||
Record<string, number>
|
||||
>({});
|
||||
|
@ -77,6 +77,7 @@ export function useTusUpload(p0?: { onSuccess: (result: UploadResult) => void; o
|
|||
onSuccess: async (payload) => {
|
||||
if (upload.url) {
|
||||
const fileId = getFileId(upload.url);
|
||||
//console.log(fileId)
|
||||
const url = getResourceUrl(upload.url);
|
||||
setIsUploading(false);
|
||||
setUploadProgress((prev) => ({
|
||||
|
|
|
@ -29,10 +29,10 @@ export type CustomRouteObject =
|
|||
| CustomNonIndexRouteObject;
|
||||
export const routes: CustomRouteObject[] = [
|
||||
{
|
||||
path:'/',
|
||||
element:<DeptSettingPage></DeptSettingPage>,
|
||||
path: "/",
|
||||
element: <DeptSettingPage></DeptSettingPage>,
|
||||
errorElement: <ErrorPage />,
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
export const router = createBrowserRouter(routes);
|
||||
|
|
Loading…
Reference in New Issue