318 lines
7.5 KiB
TypeScript
318 lines
7.5 KiB
TypeScript
/**
|
||
* MinIO 工具函数使用示例
|
||
* 这个文件展示了如何使用 minio.ts 中的各种函数
|
||
*/
|
||
|
||
import {
|
||
uploadFile,
|
||
getFileUrl,
|
||
deleteFile,
|
||
listFiles,
|
||
generateUniqueFileName,
|
||
validateFileType,
|
||
validateFileSize,
|
||
downloadFile,
|
||
fileExists,
|
||
getFileInfo,
|
||
} from './minio';
|
||
|
||
// ========================================
|
||
// 1. 上传文件示例
|
||
// ========================================
|
||
|
||
/**
|
||
* 上传用户头像
|
||
*/
|
||
async function uploadAvatar(file: File, userId: string) {
|
||
// 验证文件类型
|
||
const allowedTypes = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
|
||
if (!validateFileType(file.name, allowedTypes)) {
|
||
throw new Error('不支持的图片格式');
|
||
}
|
||
|
||
// 验证文件大小(5MB)
|
||
const maxSize = 5 * 1024 * 1024;
|
||
if (!validateFileSize(file.size, maxSize)) {
|
||
throw new Error('文件大小超过限制(最大5MB)');
|
||
}
|
||
|
||
// 生成唯一文件名,存储在 avatars 目录下
|
||
const path = generateUniqueFileName(file.name, `avatars/${userId}`);
|
||
|
||
// 上传文件
|
||
const url = await uploadFile(file, path, {
|
||
'Content-Type': file.type,
|
||
'User-Id': userId,
|
||
});
|
||
|
||
return { url, path };
|
||
}
|
||
|
||
/**
|
||
* 上传文章封面图
|
||
*/
|
||
async function uploadPostCover(file: File, postId: string) {
|
||
const allowedTypes = ['jpg', 'jpeg', 'png', 'webp'];
|
||
if (!validateFileType(file.name, allowedTypes)) {
|
||
throw new Error('不支持的图片格式');
|
||
}
|
||
|
||
const maxSize = 10 * 1024 * 1024; // 10MB
|
||
if (!validateFileSize(file.size, maxSize)) {
|
||
throw new Error('文件大小超过限制(最大10MB)');
|
||
}
|
||
|
||
// 按日期组织文件
|
||
const date = new Date();
|
||
const year = date.getFullYear();
|
||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||
|
||
const path = generateUniqueFileName(
|
||
file.name,
|
||
`posts/${year}/${month}/covers`
|
||
);
|
||
|
||
const url = await uploadFile(file, path);
|
||
|
||
return { url, path };
|
||
}
|
||
|
||
/**
|
||
* 上传文章内容中的图片
|
||
*/
|
||
async function uploadPostImage(file: File, postId: string) {
|
||
const allowedTypes = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
|
||
if (!validateFileType(file.name, allowedTypes)) {
|
||
throw new Error('不支持的图片格式');
|
||
}
|
||
|
||
const maxSize = 5 * 1024 * 1024; // 5MB
|
||
if (!validateFileSize(file.size, maxSize)) {
|
||
throw new Error('文件大小超过限制(最大5MB)');
|
||
}
|
||
|
||
const date = new Date();
|
||
const year = date.getFullYear();
|
||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||
|
||
const path = generateUniqueFileName(
|
||
file.name,
|
||
`posts/${year}/${month}/images`
|
||
);
|
||
|
||
const url = await uploadFile(file, path);
|
||
|
||
return { url, path };
|
||
}
|
||
|
||
// ========================================
|
||
// 2. 获取文件URL示例
|
||
// ========================================
|
||
|
||
/**
|
||
* 获取文件的公共访问URL
|
||
*/
|
||
function getPublicFileUrl(path: string) {
|
||
return getFileUrl(path);
|
||
}
|
||
|
||
// ========================================
|
||
// 3. 删除文件示例
|
||
// ========================================
|
||
|
||
/**
|
||
* 删除用户头像(更换头像时)
|
||
*/
|
||
async function deleteAvatar(avatarPath: string) {
|
||
try {
|
||
await deleteFile(avatarPath);
|
||
console.log('头像删除成功');
|
||
} catch (error) {
|
||
console.error('删除头像失败:', error);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 删除文章时,删除相关的所有图片
|
||
*/
|
||
async function deletePostImages(postId: string) {
|
||
try {
|
||
// 列出文章相关的所有图片
|
||
const files = await listFiles(`posts/`, true);
|
||
|
||
// 过滤出该文章的图片(根据实际情况调整逻辑)
|
||
const postFiles = files.filter(file =>
|
||
file.name?.includes(postId)
|
||
);
|
||
|
||
// 批量删除
|
||
const paths = postFiles.map(f => f.name).filter((name): name is string => !!name);
|
||
|
||
if (paths.length > 0) {
|
||
const { deleteFiles } = await import('./minio');
|
||
await deleteFiles(paths);
|
||
console.log(`删除了 ${paths.length} 个文件`);
|
||
}
|
||
} catch (error) {
|
||
console.error('删除文章图片失败:', error);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
// ========================================
|
||
// 4. 列出文件示例
|
||
// ========================================
|
||
|
||
/**
|
||
* 获取用户的所有头像历史
|
||
*/
|
||
async function getUserAvatars(userId: string) {
|
||
try {
|
||
const files = await listFiles(`avatars/${userId}/`, false);
|
||
|
||
return files.map(file => ({
|
||
name: file.name,
|
||
size: file.size,
|
||
lastModified: file.lastModified,
|
||
url: file.name ? getFileUrl(file.name) : null,
|
||
}));
|
||
} catch (error) {
|
||
console.error('获取用户头像列表失败:', error);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取某月的所有文章封面
|
||
*/
|
||
async function getPostCoversByMonth(year: number, month: number) {
|
||
try {
|
||
const monthStr = String(month).padStart(2, '0');
|
||
const files = await listFiles(`posts/${year}/${monthStr}/covers/`, false);
|
||
|
||
return files.map(file => ({
|
||
name: file.name,
|
||
size: file.size,
|
||
url: file.name ? getFileUrl(file.name) : null,
|
||
}));
|
||
} catch (error) {
|
||
console.error('获取封面列表失败:', error);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
// ========================================
|
||
// 5. 检查文件是否存在
|
||
// ========================================
|
||
|
||
/**
|
||
* 检查头像是否存在
|
||
*/
|
||
async function checkAvatarExists(avatarPath: string): Promise<boolean> {
|
||
return await fileExists(avatarPath);
|
||
}
|
||
|
||
// ========================================
|
||
// 6. 获取文件信息
|
||
// ========================================
|
||
|
||
/**
|
||
* 获取文件详细信息
|
||
*/
|
||
async function getFileDetails(path: string) {
|
||
try {
|
||
const info = await getFileInfo(path);
|
||
|
||
return {
|
||
size: info.size,
|
||
lastModified: info.lastModified,
|
||
etag: info.etag,
|
||
contentType: info.metaData?.['content-type'],
|
||
};
|
||
} catch (error) {
|
||
console.error('获取文件信息失败:', error);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
// ========================================
|
||
// 7. 下载文件示例
|
||
// ========================================
|
||
|
||
/**
|
||
* 下载文件到本地
|
||
*/
|
||
async function downloadFileToBuffer(path: string): Promise<Buffer> {
|
||
try {
|
||
return await downloadFile(path);
|
||
} catch (error) {
|
||
console.error('下载文件失败:', error);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
// ========================================
|
||
// 8. 在 API Route 中使用示例
|
||
// ========================================
|
||
|
||
/**
|
||
* Next.js API Route 示例:上传文件
|
||
*
|
||
* 使用方法:
|
||
*
|
||
* // app/api/upload/route.ts
|
||
* import { uploadFile, generateUniqueFileName } from '@/lib/minio';
|
||
*
|
||
* export async function POST(request: Request) {
|
||
* const formData = await request.formData();
|
||
* const file = formData.get('file') as File;
|
||
*
|
||
* if (!file) {
|
||
* return Response.json({ error: '没有文件' }, { status: 400 });
|
||
* }
|
||
*
|
||
* const path = generateUniqueFileName(file.name, 'uploads');
|
||
* const url = await uploadFile(file, path);
|
||
*
|
||
* return Response.json({ url, path });
|
||
* }
|
||
*/
|
||
|
||
/**
|
||
* Next.js API Route 示例:删除文件
|
||
*
|
||
* // app/api/delete/route.ts
|
||
* import { deleteFile } from '@/lib/minio';
|
||
*
|
||
* export async function DELETE(request: Request) {
|
||
* const { path } = await request.json();
|
||
*
|
||
* if (!path) {
|
||
* return Response.json({ error: '缺少文件路径' }, { status: 400 });
|
||
* }
|
||
*
|
||
* await deleteFile(path);
|
||
*
|
||
* return Response.json({ success: true });
|
||
* }
|
||
*/
|
||
|
||
// ========================================
|
||
// 导出示例函数
|
||
// ========================================
|
||
|
||
export {
|
||
uploadAvatar,
|
||
uploadPostCover,
|
||
uploadPostImage,
|
||
getPublicFileUrl,
|
||
deleteAvatar,
|
||
deletePostImages,
|
||
getUserAvatars,
|
||
getPostCoversByMonth,
|
||
checkAvatarExists,
|
||
getFileDetails,
|
||
downloadFileToBuffer,
|
||
};
|