上传
This commit is contained in:
@@ -52,7 +52,7 @@ abstract class BaseController
|
||||
}
|
||||
|
||||
// 解析 token
|
||||
$payload = app\util\JwtUtil::decode($token);
|
||||
$payload = \app\Util\JwtUtil::decode($token);
|
||||
if (!$payload) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
<?php
|
||||
|
||||
namespace app\controller;
|
||||
|
||||
use app\Service\UploadService;
|
||||
use Webman\Http\Request;
|
||||
use Webman\Http\Response;
|
||||
|
||||
/**
|
||||
* 文件上传控制器
|
||||
*/
|
||||
class UploadController extends BaseController
|
||||
{
|
||||
private UploadService $uploadService;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->uploadService = new UploadService();
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用文件上传接口
|
||||
*/
|
||||
public function upload(Request $request): Response
|
||||
{
|
||||
try {
|
||||
$userId = $this->getUserId($request); // 可选,没有token则为null
|
||||
$partition = $request->post('partition', 'other');
|
||||
$category = $request->post('category', 'general');
|
||||
|
||||
$result = $this->uploadService->upload($request, $partition, $category, $userId);
|
||||
|
||||
return $this->success($result, '上传成功');
|
||||
} catch (\Exception $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 图片上传接口
|
||||
*/
|
||||
public function uploadImage(Request $request): Response
|
||||
{
|
||||
try {
|
||||
$userId = $this->getUserId($request); // 可选,没有token则为null
|
||||
$category = $request->post('category', 'general');
|
||||
|
||||
$result = $this->uploadService->uploadImage($request, $category, $userId);
|
||||
|
||||
return $this->success($result, '上传成功');
|
||||
} catch (\Exception $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 头像上传接口
|
||||
*/
|
||||
public function uploadAvatar(Request $request): Response
|
||||
{
|
||||
try {
|
||||
$userId = $this->getUserId($request); // 可选,没有token则为null
|
||||
|
||||
$result = $this->uploadService->uploadAvatar($request, $userId);
|
||||
|
||||
return $this->success($result, '头像上传成功');
|
||||
} catch (\Exception $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 二手车图片上传接口
|
||||
*/
|
||||
public function uploadUsedCarImage(Request $request): Response
|
||||
{
|
||||
try {
|
||||
$userId = $this->getUserId($request); // 可选,没有token则为null
|
||||
|
||||
$result = $this->uploadService->uploadUsedCarImage($request, $userId);
|
||||
|
||||
return $this->success($result, '上传成功');
|
||||
} catch (\Exception $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量上传图片(二手车专用)
|
||||
*/
|
||||
public function batchUploadImages(Request $request): Response
|
||||
{
|
||||
try {
|
||||
$userId = $this->getUserId($request); // 可选,没有token则为null
|
||||
$files = $request->files();
|
||||
|
||||
if (empty($files)) {
|
||||
return $this->error('请选择要上传的文件');
|
||||
}
|
||||
|
||||
$results = [];
|
||||
$errors = [];
|
||||
|
||||
foreach ($files['files'] ?? [] as $index => $file) {
|
||||
try {
|
||||
// 创建临时请求对象处理单个文件
|
||||
$tempRequest = new Request(
|
||||
$request->method(),
|
||||
$request->path(),
|
||||
$request->headers(),
|
||||
['file' => $file]
|
||||
);
|
||||
|
||||
$result = $this->uploadService->uploadUsedCarImage($tempRequest, $userId);
|
||||
$results[] = $result;
|
||||
} catch (\Exception $e) {
|
||||
$errors[] = [
|
||||
'index' => $index,
|
||||
'error' => $e->getMessage(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $this->success([
|
||||
'success' => $results,
|
||||
'errors' => $errors,
|
||||
'total' => count($results),
|
||||
'failed' => count($errors),
|
||||
], '批量上传完成');
|
||||
} catch (\Exception $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除文件
|
||||
*/
|
||||
public function deleteFile(Request $request): Response
|
||||
{
|
||||
try {
|
||||
$userId = $this->getUserId($request); // 可选,用于权限验证
|
||||
$fileId = (int)$request->post('id');
|
||||
|
||||
if ($fileId <= 0) {
|
||||
return $this->error('无效的文件ID');
|
||||
}
|
||||
|
||||
$this->uploadService->deleteFile($fileId, $userId);
|
||||
|
||||
return $this->success(null, '删除成功');
|
||||
} catch (\Exception $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件信息
|
||||
*/
|
||||
public function getFileInfo(Request $request): Response
|
||||
{
|
||||
try {
|
||||
$fileId = (int)$request->get('id');
|
||||
|
||||
if ($fileId <= 0) {
|
||||
return $this->error('无效的文件ID');
|
||||
}
|
||||
|
||||
$info = $this->uploadService->getFileInfo($fileId);
|
||||
|
||||
return $this->success($info);
|
||||
} catch (\Exception $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取分区配置
|
||||
*/
|
||||
public function getPartitionConfig(Request $request): Response
|
||||
{
|
||||
try {
|
||||
$configs = UploadService::getPartitionConfigs();
|
||||
|
||||
return $this->success($configs);
|
||||
} catch (\Exception $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
namespace app\Model;
|
||||
|
||||
use support\Model;
|
||||
|
||||
/**
|
||||
* 上传文件记录模型
|
||||
*/
|
||||
class UploadedFiles extends Model
|
||||
{
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'uploaded_files';
|
||||
|
||||
/**
|
||||
* The primary key associated with the table.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $primaryKey = 'id';
|
||||
|
||||
/**
|
||||
* Indicates if the model should be timestamped.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $timestamps = true;
|
||||
|
||||
/**
|
||||
* 可批量赋值的字段
|
||||
*/
|
||||
protected $fillable = [
|
||||
'file_name',
|
||||
'file_path',
|
||||
'file_size',
|
||||
'file_type',
|
||||
'category',
|
||||
'original_name',
|
||||
'extension',
|
||||
'partition',
|
||||
'upload_ip',
|
||||
'user_agent',
|
||||
'user_id',
|
||||
];
|
||||
|
||||
/**
|
||||
* 上传分区配置
|
||||
*/
|
||||
public static array $uploadPartitions = [
|
||||
'image' => [
|
||||
'path' => 'uploads/images',
|
||||
'max_size' => 10 * 1024 * 1024, // 10MB
|
||||
'allowed_types' => ['image/jpeg', 'image/png', 'image/gif', 'image/webp'],
|
||||
'extensions' => ['jpg', 'jpeg', 'png', 'gif', 'webp']
|
||||
],
|
||||
'avatar' => [
|
||||
'path' => 'uploads/avatars',
|
||||
'max_size' => 5 * 1024 * 1024, // 5MB
|
||||
'allowed_types' => ['image/jpeg', 'image/png', 'image/webp'],
|
||||
'extensions' => ['jpg', 'jpeg', 'png', 'webp']
|
||||
],
|
||||
'document' => [
|
||||
'path' => 'uploads/documents',
|
||||
'max_size' => 20 * 1024 * 1024, // 20MB
|
||||
'allowed_types' => [
|
||||
'application/pdf',
|
||||
'application/msword',
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
'application/vnd.ms-excel',
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||
],
|
||||
'extensions' => ['pdf', 'doc', 'docx', 'xls', 'xlsx']
|
||||
],
|
||||
'usedcar' => [
|
||||
'path' => 'uploads/usedcar',
|
||||
'max_size' => 10 * 1024 * 1024, // 10MB
|
||||
'allowed_types' => ['image/jpeg', 'image/png', 'image/webp', 'image/gif'],
|
||||
'extensions' => ['jpg', 'jpeg', 'png', 'webp', 'gif']
|
||||
],
|
||||
'other' => [
|
||||
'path' => 'uploads/others',
|
||||
'max_size' => 5 * 1024 * 1024, // 5MB
|
||||
'allowed_types' => [],
|
||||
'extensions' => []
|
||||
]
|
||||
];
|
||||
|
||||
/**
|
||||
* 关联用户
|
||||
*/
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id', 'id');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取分区配置
|
||||
*/
|
||||
public static function getPartitionConfig(string $partition): ?array
|
||||
{
|
||||
return self::$uploadPartitions[$partition] ?? self::$uploadPartitions['other'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有分区配置
|
||||
*/
|
||||
public static function getAllPartitions(): array
|
||||
{
|
||||
return self::$uploadPartitions;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,339 @@
|
||||
<?php
|
||||
|
||||
namespace app\Service;
|
||||
|
||||
use app\Model\UploadedFiles;
|
||||
use Webman\Http\Request;
|
||||
use support\Redis;
|
||||
|
||||
/**
|
||||
* 文件上传服务
|
||||
* 支持本地存储,可扩展支持 OSS
|
||||
*/
|
||||
class UploadService
|
||||
{
|
||||
/**
|
||||
* 上传分区类型
|
||||
*/
|
||||
public const PARTITION_IMAGE = 'image';
|
||||
public const PARTITION_AVATAR = 'avatar';
|
||||
public const PARTITION_DOCUMENT = 'document';
|
||||
public const PARTITION_USEDCAR = 'usedcar';
|
||||
public const PARTITION_OTHER = 'other';
|
||||
|
||||
/**
|
||||
* 基础存储路径
|
||||
*/
|
||||
private string $basePath;
|
||||
|
||||
/**
|
||||
* 访问域名
|
||||
*/
|
||||
private string $domain;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->basePath = public_path(); // 存储到 public 目录
|
||||
$this->domain = $this->getDomain();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取访问域名
|
||||
*/
|
||||
private function getDomain(): string
|
||||
{
|
||||
// 从环境变量或配置获取
|
||||
$domain = getenv('MAIN_DOMAIN') ?: getenv('APP_URL') ?: '';
|
||||
if (empty($domain)) {
|
||||
$scheme = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http';
|
||||
$host = $_SERVER['HTTP_HOST'] ?? 'localhost';
|
||||
$domain = $scheme . '://' . $host;
|
||||
}
|
||||
return rtrim($domain, '/');
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传文件
|
||||
*
|
||||
* @param Request $request 请求对象
|
||||
* @param string $partition 上传分区 (image/avatar/document/usedcar/other)
|
||||
* @param string $category 文件分类 (可选)
|
||||
* @param int|null $userId 用户ID (可选)
|
||||
* @return array 上传结果
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function upload(Request $request, string $partition = self::PARTITION_OTHER, string $category = 'general', ?int $userId = null): array
|
||||
{
|
||||
$file = $request->file('file');
|
||||
|
||||
if (!$file || !$file->isValid()) {
|
||||
throw new \Exception('文件上传失败或无效');
|
||||
}
|
||||
|
||||
// 获取分区配置
|
||||
$config = UploadedFiles::getPartitionConfig($partition);
|
||||
|
||||
// 获取文件大小(必须在移动文件前获取)
|
||||
$fileSize = $file->getSize();
|
||||
$originalName = $file->getUploadName();
|
||||
|
||||
// 验证文件大小
|
||||
if ($fileSize > $config['max_size']) {
|
||||
$maxSizeMB = round($config['max_size'] / 1024 / 1024, 2);
|
||||
throw new \Exception("文件大小超过限制,最大允许 {$maxSizeMB}MB");
|
||||
}
|
||||
|
||||
// 验证文件类型
|
||||
$mimeType = $file->getUploadMimeType();
|
||||
if (!empty($config['allowed_types']) && !in_array($mimeType, $config['allowed_types'])) {
|
||||
throw new \Exception('不支持的文件类型: ' . $mimeType);
|
||||
}
|
||||
|
||||
// 验证文件扩展名
|
||||
$extension = strtolower($file->getUploadExtension());
|
||||
if (!empty($config['extensions']) && !in_array($extension, $config['extensions'])) {
|
||||
throw new \Exception('不支持的文件扩展名: .' . $extension);
|
||||
}
|
||||
|
||||
// 生成存储路径
|
||||
$datePath = date('Ymd');
|
||||
$relativePath = '/' . $config['path'] . '/' . $datePath;
|
||||
$fullPath = $this->basePath . $relativePath;
|
||||
|
||||
// 创建目录
|
||||
if (!is_dir($fullPath)) {
|
||||
if (!mkdir($fullPath, 0755, true)) {
|
||||
throw new \Exception('创建存储目录失败');
|
||||
}
|
||||
}
|
||||
|
||||
// 生成文件名
|
||||
$fileName = $this->generateFileName($file, $extension);
|
||||
$filePath = $fullPath . '/' . $fileName;
|
||||
|
||||
// 移动上传文件
|
||||
if (!$file->move($filePath)) {
|
||||
throw new \Exception('文件保存失败');
|
||||
}
|
||||
|
||||
// 图片处理(压缩)
|
||||
if (str_starts_with($mimeType, 'image/')) {
|
||||
$this->processImage($filePath, $mimeType);
|
||||
}
|
||||
|
||||
// 记录到数据库
|
||||
$fileUrl = $relativePath . '/' . $fileName;
|
||||
$uploadIp = $request->getRealIp() ?? '0.0.0.0';
|
||||
$userAgent = $request->header('User-Agent', '');
|
||||
|
||||
$uploadedFile = UploadedFiles::create([
|
||||
'file_name' => $fileName,
|
||||
'file_path' => $fileUrl,
|
||||
'file_size' => $fileSize,
|
||||
'file_type' => $mimeType,
|
||||
'category' => $category,
|
||||
'original_name' => $originalName,
|
||||
'extension' => $extension,
|
||||
'partition' => $partition,
|
||||
'upload_ip' => $uploadIp,
|
||||
'user_agent' => $userAgent,
|
||||
'user_id' => $userId,
|
||||
]);
|
||||
|
||||
return [
|
||||
'id' => $uploadedFile->id,
|
||||
'file_name' => $fileName,
|
||||
'file_url' => $fileUrl,
|
||||
'full_url' => $this->domain . $fileUrl,
|
||||
'file_size' => $fileSize,
|
||||
'file_type' => $mimeType,
|
||||
'original_name' => $originalName,
|
||||
'partition' => $partition,
|
||||
'category' => $category,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传图片(专用接口)
|
||||
*
|
||||
* @param Request $request 请求对象
|
||||
* @param string $category 文件分类
|
||||
* @param int|null $userId 用户ID
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function uploadImage(Request $request, string $category = 'general', ?int $userId = null): array
|
||||
{
|
||||
return $this->upload($request, self::PARTITION_IMAGE, $category, $userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传头像(专用接口)
|
||||
*
|
||||
* @param Request $request 请求对象
|
||||
* @param int|null $userId 用户ID
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function uploadAvatar(Request $request, ?int $userId = null): array
|
||||
{
|
||||
return $this->upload($request, self::PARTITION_AVATAR, 'avatar', $userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传二手车图片(专用接口)
|
||||
*
|
||||
* @param Request $request 请求对象
|
||||
* @param int|null $userId 用户ID
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function uploadUsedCarImage(Request $request, ?int $userId = null): array
|
||||
{
|
||||
return $this->upload($request, self::PARTITION_USEDCAR, 'usedcar', $userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除文件
|
||||
*
|
||||
* @param int $fileId 文件ID
|
||||
* @param int|null $userId 用户ID(用于权限验证)
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function deleteFile(int $fileId, ?int $userId = null): bool
|
||||
{
|
||||
$file = UploadedFiles::find($fileId);
|
||||
|
||||
if (!$file) {
|
||||
throw new \Exception('文件不存在');
|
||||
}
|
||||
|
||||
// 权限验证:只能删除自己的文件(管理员除外)
|
||||
if ($userId !== null && $file->user_id != $userId) {
|
||||
throw new \Exception('无权删除此文件');
|
||||
}
|
||||
|
||||
// 删除物理文件
|
||||
$fullPath = $this->basePath . $file->file_path;
|
||||
if (file_exists($fullPath)) {
|
||||
@unlink($fullPath);
|
||||
}
|
||||
|
||||
// 删除数据库记录
|
||||
return $file->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件信息
|
||||
*
|
||||
* @param int $fileId 文件ID
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getFileInfo(int $fileId): array
|
||||
{
|
||||
$file = UploadedFiles::find($fileId);
|
||||
|
||||
if (!$file) {
|
||||
throw new \Exception('文件不存在');
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $file->id,
|
||||
'file_name' => $file->file_name,
|
||||
'file_url' => $file->file_path,
|
||||
'full_url' => $this->domain . $file->file_path,
|
||||
'file_size' => $file->file_size,
|
||||
'file_type' => $file->file_type,
|
||||
'original_name' => $file->original_name,
|
||||
'partition' => $file->partition,
|
||||
'category' => $file->category,
|
||||
'created_at' => $file->created_at->format('Y-m-d H:i:s'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成唯一文件名
|
||||
*/
|
||||
private function generateFileName($file, string $extension): string
|
||||
{
|
||||
$timestamp = time();
|
||||
$random = bin2hex(random_bytes(4));
|
||||
return $timestamp . '_' . $random . '.' . $extension;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理图片(压缩)
|
||||
*/
|
||||
private function processImage(string $filePath, string $mimeType): void
|
||||
{
|
||||
// 检查是否支持 imagick 扩展
|
||||
if (!extension_loaded('imagick')) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$image = new \Imagick($filePath);
|
||||
|
||||
// 限制最大尺寸
|
||||
$maxWidth = 2000;
|
||||
$maxHeight = 2000;
|
||||
$image->thumbnailImage($maxWidth, $maxHeight, true);
|
||||
|
||||
// 自动方向修正
|
||||
if (method_exists($image, 'autoOrient')) {
|
||||
$image->autoOrient();
|
||||
}
|
||||
|
||||
// 根据类型处理
|
||||
$extension = strtolower(pathinfo($filePath, PATHINFO_EXTENSION));
|
||||
if (in_array($extension, ['png', 'jpeg', 'jpg'])) {
|
||||
// 转换为 WebP 并压缩
|
||||
$image->setImageFormat('webp');
|
||||
$image->setImageCompressionQuality(80);
|
||||
$webpPath = preg_replace('/\.(png|jpe?g)$/i', '.webp', $filePath);
|
||||
$image->writeImage($webpPath);
|
||||
|
||||
// 如果转换成功且文件名改变,删除原文件
|
||||
if ($webpPath !== $filePath && file_exists($webpPath)) {
|
||||
@unlink($filePath);
|
||||
}
|
||||
}
|
||||
|
||||
$image->clear();
|
||||
$image->destroy();
|
||||
} catch (\Exception $e) {
|
||||
// 处理失败,保留原图
|
||||
error_log('图片处理失败: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化文件大小
|
||||
*/
|
||||
public static function formatFileSize(int $bytes): string
|
||||
{
|
||||
$units = ['B', 'KB', 'MB', 'GB'];
|
||||
$bytes = max($bytes, 0);
|
||||
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
|
||||
$pow = min($pow, count($units) - 1);
|
||||
|
||||
$bytes /= pow(1024, $pow);
|
||||
|
||||
return round($bytes, 2) . ' ' . $units[$pow];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有分区配置
|
||||
*/
|
||||
public static function getPartitionConfigs(): array
|
||||
{
|
||||
$configs = UploadedFiles::getAllPartitions();
|
||||
foreach ($configs as &$config) {
|
||||
$config['max_size_formatted'] = self::formatFileSize($config['max_size']);
|
||||
$config['allowed_types_formatted'] = implode(', ', $config['extensions']);
|
||||
}
|
||||
return $configs;
|
||||
}
|
||||
}
|
||||
@@ -79,6 +79,18 @@ Route::group('/api/v1/usedcar', function () {
|
||||
Route::get('/pdf/preview', [app\controller\UsedCarController::class, 'previewPdf']);
|
||||
})->middleware($authMiddleware);
|
||||
|
||||
// 文件上传路由(不需要认证)
|
||||
Route::group('/api/v1/upload', function () {
|
||||
Route::post('/file', [app\controller\UploadController::class, 'upload']);
|
||||
Route::post('/image', [app\controller\UploadController::class, 'uploadImage']);
|
||||
Route::post('/avatar', [app\controller\UploadController::class, 'uploadAvatar']);
|
||||
Route::post('/usedcar', [app\controller\UploadController::class, 'uploadUsedCarImage']);
|
||||
Route::post('/batch', [app\controller\UploadController::class, 'batchUploadImages']);
|
||||
Route::post('/delete', [app\controller\UploadController::class, 'deleteFile']);
|
||||
Route::get('/info', [app\controller\UploadController::class, 'getFileInfo']);
|
||||
Route::get('/config', [app\controller\UploadController::class, 'getPartitionConfig']);
|
||||
})->middleware($apiMiddleware);
|
||||
|
||||
// 默认路由
|
||||
Route::fallback(function () {
|
||||
return json(['code' => 404, 'message' => 'Not Found']);
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
-- 上传文件记录表
|
||||
CREATE TABLE IF NOT EXISTS `uploaded_files` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`file_name` varchar(255) NOT NULL COMMENT '存储文件名',
|
||||
`file_path` varchar(500) NOT NULL COMMENT '文件存储路径',
|
||||
`file_size` bigint(20) NOT NULL DEFAULT '0' COMMENT '文件大小(字节)',
|
||||
`file_type` varchar(100) NOT NULL COMMENT '文件MIME类型',
|
||||
`category` varchar(50) DEFAULT 'general' COMMENT '文件分类(general/avatar/document等)',
|
||||
`original_name` varchar(255) DEFAULT NULL COMMENT '原始文件名',
|
||||
`extension` varchar(20) DEFAULT NULL COMMENT '文件扩展名',
|
||||
`partition` varchar(50) DEFAULT 'other' COMMENT '上传分区(image/document/audio等)',
|
||||
`upload_ip` varchar(45) DEFAULT NULL COMMENT '上传IP地址',
|
||||
`user_agent` text COMMENT '用户代理',
|
||||
`user_id` int(11) DEFAULT NULL COMMENT '上传用户ID',
|
||||
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_partition` (`partition`),
|
||||
KEY `idx_category` (`category`),
|
||||
KEY `idx_user_id` (`user_id`),
|
||||
KEY `idx_file_path` (`file_path`(255))
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='上传文件记录表';
|
||||
Reference in New Issue
Block a user