douyin-archive/lib/minio-examples.ts
2025-10-20 13:06:06 +08:00

318 lines
7.5 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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,
};