Merge branch 'feature/mzl_dev' of http://219.157.255.213:25311/xinyouhe/fdesk into feature/mzl_dev
This commit is contained in:
@@ -3,8 +3,11 @@
|
|||||||
namespace app\common\controller;
|
namespace app\common\controller;
|
||||||
|
|
||||||
use app\common\service\OssService;
|
use app\common\service\OssService;
|
||||||
|
use app\common\service\UploadedFilesService;
|
||||||
use app\common\traits\ResponseTrait;
|
use app\common\traits\ResponseTrait;
|
||||||
|
use support\Log;
|
||||||
use support\Request;
|
use support\Request;
|
||||||
|
use support\exception\BusinessException;
|
||||||
use Shopwwi\WebmanFilesystem\Facade\Storage;
|
use Shopwwi\WebmanFilesystem\Facade\Storage;
|
||||||
|
|
||||||
class UploadController
|
class UploadController
|
||||||
@@ -22,15 +25,41 @@ class UploadController
|
|||||||
$scene = $request->input('scene', 'image');
|
$scene = $request->input('scene', 'image');
|
||||||
$file = $request->file('file');
|
$file = $request->file('file');
|
||||||
if ($file && $file->isValid()) {
|
if ($file && $file->isValid()) {
|
||||||
$pathUrl = '/uploads/'.$scene.'/'.date('Ymd').'/'.md5($file->getUploadName() . time()).'.'.$file->getUploadExtension();
|
$fileName = md5($file->getUploadName() . time()).'.'.$file->getUploadExtension();
|
||||||
|
$pathUrl = '/uploads/'.$scene.'/'.date('Ymd').'/'.$fileName;
|
||||||
$fullPathUrl = public_path() . $pathUrl;
|
$fullPathUrl = public_path() . $pathUrl;
|
||||||
$file->move($fullPathUrl);
|
$file->move($fullPathUrl);
|
||||||
|
|
||||||
$data = [
|
$data = [
|
||||||
'domain' => getenv('MAIN_DOMAIN'),
|
'domain' => getenv('MAIN_DOMAIN'),
|
||||||
'file_url' => $pathUrl,
|
'file_url' => $pathUrl,
|
||||||
'full_url' => getenv('MAIN_DOMAIN') . $pathUrl,
|
'full_url' => getenv('MAIN_DOMAIN') . $pathUrl,
|
||||||
'scene' => $scene,
|
'scene' => $scene,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// 记录文件信息到数据库
|
||||||
|
try {
|
||||||
|
$uploadedFilesService = new UploadedFilesService();
|
||||||
|
$partition = $scene;
|
||||||
|
$category = $request->input('category', 'general');
|
||||||
|
|
||||||
|
$fileInfo = [
|
||||||
|
'file_name' => $fileName,
|
||||||
|
'file_path' => $pathUrl,
|
||||||
|
'file_size' => $file->getSize(),
|
||||||
|
'file_type' => $file->getUploadMimeType(),
|
||||||
|
'original_name' => $file->getUploadName(),
|
||||||
|
];
|
||||||
|
|
||||||
|
$recordResult = $uploadedFilesService->recordFileInfo($fileInfo, $request, $partition, $category);
|
||||||
|
$data['file_id'] = $recordResult['id'];
|
||||||
|
$data['partition'] = $partition;
|
||||||
|
$data['category'] = $category;
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
// 记录失败不影响上传结果,只记录日志
|
||||||
|
Log::error('记录文件信息失败: ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
return $this->success($data, 'upload success');
|
return $this->success($data, 'upload success');
|
||||||
}
|
}
|
||||||
return $this->error('file not found');
|
return $this->error('file not found');
|
||||||
@@ -43,15 +72,19 @@ class UploadController
|
|||||||
public function uploadFile(Request $request)
|
public function uploadFile(Request $request)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
$scene = $request->input('scene');
|
||||||
$file = $request->file('file');
|
$file = $request->file('file');
|
||||||
|
|
||||||
if (!$file || !$file->isValid()) {
|
if (!$file || !$file->isValid()) {
|
||||||
return $this->error('上传失败');
|
return $this->error('上传失败');
|
||||||
}
|
}
|
||||||
|
if (empty($scene)) {
|
||||||
|
return $this->error('上传场景不能为空');
|
||||||
|
}
|
||||||
$originalName = $file->getUploadName();
|
$originalName = $file->getUploadName();
|
||||||
|
|
||||||
$result = Storage::adapter('oss')
|
$result = Storage::adapter('oss')
|
||||||
->path('vehicle_album/'.date('Ymd'))
|
->path('vehicle_album/' . $scene . '/' . date('Ymd'))
|
||||||
->size(1024*1024*5)
|
->size(1024*1024*5)
|
||||||
->upload($file);
|
->upload($file);
|
||||||
|
|
||||||
@@ -63,6 +96,11 @@ class UploadController
|
|||||||
'file_url' => $ossservice->generatePrivateUrl($result->file_name)
|
'file_url' => $ossservice->generatePrivateUrl($result->file_name)
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// 记录文件信息到数据库
|
||||||
|
$category = $request->input('category', 'general');
|
||||||
|
$res = $this->recordFileInfo($result,$request,$file,$scene,$category);
|
||||||
|
$data = array_merge($data, $res);
|
||||||
|
|
||||||
return $this->success($data);
|
return $this->success($data);
|
||||||
}catch (\Throwable $e){
|
}catch (\Throwable $e){
|
||||||
return $this->error($e->getMessage());
|
return $this->error($e->getMessage());
|
||||||
@@ -73,14 +111,18 @@ class UploadController
|
|||||||
public function uploadImage(Request $request)
|
public function uploadImage(Request $request)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
$scene = $request->input('scene');
|
||||||
$file = $request->file('image');
|
$file = $request->file('image');
|
||||||
|
|
||||||
if (!$file || !$file->isValid()) {
|
if (!$file || !$file->isValid()) {
|
||||||
return $this->error('上传失败');
|
return $this->error('上传失败');
|
||||||
}
|
}
|
||||||
|
if (empty($scene)) {
|
||||||
|
return $this->error('上传场景不能为空');
|
||||||
|
}
|
||||||
|
|
||||||
$result = Storage::adapter('oss')
|
$result = Storage::adapter('oss')
|
||||||
->path('vehicle_album/'.date('Ymd'))
|
->path('vehicle_album/' . $scene . '/' . date('Ymd'))
|
||||||
->size(1024*1024*5)
|
->size(1024*1024*5)
|
||||||
->extYes(['image/jpeg','image/png'])
|
->extYes(['image/jpeg','image/png'])
|
||||||
->extNo(['image/gif'])
|
->extNo(['image/gif'])
|
||||||
@@ -94,6 +136,11 @@ class UploadController
|
|||||||
'url' => $ossservice->generatePrivateUrl($result->file_name)
|
'url' => $ossservice->generatePrivateUrl($result->file_name)
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// 记录文件信息到数据库
|
||||||
|
$category = $request->input('category', 'general');
|
||||||
|
$res = $this->recordFileInfo($result,$request,$file,$scene,$category);
|
||||||
|
$data = array_merge($data, $res);
|
||||||
|
|
||||||
return json(['errno' => 0, 'data' => $data]);
|
return json(['errno' => 0, 'data' => $data]);
|
||||||
// return $this->success($data);
|
// return $this->success($data);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
@@ -102,6 +149,109 @@ class UploadController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function recordFileInfo($result, $request, $file, $scene, $category)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$uploadedFilesService = new UploadedFilesService();
|
||||||
|
$partition = $scene;
|
||||||
|
|
||||||
|
$fileInfo = [
|
||||||
|
'file_name' => $result->file_name,
|
||||||
|
'file_path' => $result->file_name, // OSS存储路径
|
||||||
|
'file_size' => $file->getSize(),
|
||||||
|
'file_type' => $file->getUploadMimeType(),
|
||||||
|
'original_name' => $file->getUploadName(),
|
||||||
|
];
|
||||||
|
|
||||||
|
$recordResult = $uploadedFilesService->recordFileInfo($fileInfo, $request, $partition, $category);
|
||||||
|
$data = [];
|
||||||
|
$data['file_id'] = $recordResult['id'];
|
||||||
|
$data['partition'] = $partition;
|
||||||
|
$data['category'] = $category;
|
||||||
|
return $data;
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
// 记录失败不影响上传结果,只记录日志
|
||||||
|
Log::error('记录文件信息失败: ' . $e->getMessage());
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取文件信息
|
||||||
|
*
|
||||||
|
* @param Request $request
|
||||||
|
* @return \support\Response
|
||||||
|
*/
|
||||||
|
public function getFileInfo(Request $request)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$fileId = $request->input('id');
|
||||||
|
|
||||||
|
if (!$fileId) {
|
||||||
|
return $this->error('文件ID不能为空');
|
||||||
|
}
|
||||||
|
|
||||||
|
$uploadedFilesService = new UploadedFilesService();
|
||||||
|
$fileInfo = $uploadedFilesService->getFileInfo($fileId);
|
||||||
|
|
||||||
|
if (!$fileInfo) {
|
||||||
|
return $this->error('文件不存在');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->success($fileInfo);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return $this->error('获取文件信息失败: ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除文件
|
||||||
|
*
|
||||||
|
* @param Request $request
|
||||||
|
* @return \support\Response
|
||||||
|
*/
|
||||||
|
public function deleteFile(Request $request)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$fileId = $request->input('id');
|
||||||
|
|
||||||
|
if (!$fileId) {
|
||||||
|
return $this->error('文件ID不能为空');
|
||||||
|
}
|
||||||
|
|
||||||
|
$uploadedFilesService = new UploadedFilesService();
|
||||||
|
$result = $uploadedFilesService->deleteFile($fileId);
|
||||||
|
|
||||||
|
if ($result) {
|
||||||
|
return $this->success([], '文件删除成功');
|
||||||
|
} else {
|
||||||
|
return $this->error('文件删除失败');
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return $this->error('文件删除失败: ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取分区配置
|
||||||
|
*
|
||||||
|
* @param Request $request
|
||||||
|
* @return \support\Response
|
||||||
|
*/
|
||||||
|
public function getPartitionConfig(Request $request)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$uploadedFilesService = new UploadedFilesService();
|
||||||
|
$config = $uploadedFilesService->getPartitionConfig();
|
||||||
|
|
||||||
|
return $this->success($config);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return $this->error('获取分区配置失败: ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public function proxy(Request $request)
|
public function proxy(Request $request)
|
||||||
{
|
{
|
||||||
$filePath = $request->get('file'); // 原OSS文件路径
|
$filePath = $request->get('file'); // 原OSS文件路径
|
||||||
|
|||||||
@@ -0,0 +1,169 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace app\common\logic;
|
||||||
|
|
||||||
|
use app\common\traits\crud\CrudLogicTrait;
|
||||||
|
use app\common\model\UploadedFiles;
|
||||||
|
|
||||||
|
class UploadedFilesLogic
|
||||||
|
{
|
||||||
|
use CrudLogicTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 指定当前要操作的模型
|
||||||
|
*/
|
||||||
|
protected string $modelClass = UploadedFiles::class;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据分区获取文件列表
|
||||||
|
*
|
||||||
|
* @param string $partition
|
||||||
|
* @param int $limit
|
||||||
|
* @param int $page
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function getFilesByPartition(string $partition, int $limit = 15, int $page = 1)
|
||||||
|
{
|
||||||
|
$config = [
|
||||||
|
'model' => $this->modelClass,
|
||||||
|
'where' => ['partition' => $partition],
|
||||||
|
'order' => ['created_at' => 'desc', 'id' => 'desc']
|
||||||
|
];
|
||||||
|
|
||||||
|
return $this->logicList($config, $limit, $page);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据分类获取文件列表
|
||||||
|
*
|
||||||
|
* @param string $category
|
||||||
|
* @param int $limit
|
||||||
|
* @param int $page
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function getFilesByCategory(string $category, int $limit = 15, int $page = 1)
|
||||||
|
{
|
||||||
|
$config = [
|
||||||
|
'model' => $this->modelClass,
|
||||||
|
'where' => ['category' => $category],
|
||||||
|
'order' => ['created_at' => 'desc', 'id' => 'desc']
|
||||||
|
];
|
||||||
|
|
||||||
|
return $this->logicList($config, $limit, $page);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据文件类型获取文件列表
|
||||||
|
*
|
||||||
|
* @param string $fileType
|
||||||
|
* @param int $limit
|
||||||
|
* @param int $page
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function getFilesByType(string $fileType, int $limit = 15, int $page = 1)
|
||||||
|
{
|
||||||
|
$config = [
|
||||||
|
'model' => $this->modelClass,
|
||||||
|
'where' => ['file_type' => $fileType],
|
||||||
|
'order' => ['created_at' => 'desc', 'id' => 'desc']
|
||||||
|
];
|
||||||
|
|
||||||
|
return $this->logicList($config, $limit, $page);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 搜索文件
|
||||||
|
*
|
||||||
|
* @param string $keyword
|
||||||
|
* @param int $limit
|
||||||
|
* @param int $page
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function searchFiles(string $keyword, int $limit = 15, int $page = 1)
|
||||||
|
{
|
||||||
|
$config = [
|
||||||
|
'model' => $this->modelClass,
|
||||||
|
'where' => [
|
||||||
|
'or' => [
|
||||||
|
['file_name', 'like', '%' . $keyword . '%'],
|
||||||
|
['original_name', 'like', '%' . $keyword . '%'],
|
||||||
|
['category', 'like', '%' . $keyword . '%']
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'order' => ['created_at' => 'desc', 'id' => 'desc']
|
||||||
|
];
|
||||||
|
|
||||||
|
return $this->logicList($config, $limit, $page);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取文件统计信息
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getFileStatistics(): array
|
||||||
|
{
|
||||||
|
$model = new $this->modelClass();
|
||||||
|
|
||||||
|
$totalFiles = $model->count();
|
||||||
|
$totalSize = $model->sum('file_size');
|
||||||
|
|
||||||
|
$partitionStats = $model->selectRaw('partition, COUNT(*) as count, SUM(file_size) as size')
|
||||||
|
->groupBy('partition')
|
||||||
|
->get()
|
||||||
|
->toArray();
|
||||||
|
|
||||||
|
$categoryStats = $model->selectRaw('category, COUNT(*) as count, SUM(file_size) as size')
|
||||||
|
->groupBy('category')
|
||||||
|
->get()
|
||||||
|
->toArray();
|
||||||
|
|
||||||
|
$typeStats = $model->selectRaw('file_type, COUNT(*) as count, SUM(file_size) as size')
|
||||||
|
->groupBy('file_type')
|
||||||
|
->get()
|
||||||
|
->toArray();
|
||||||
|
|
||||||
|
return [
|
||||||
|
'total_files' => $totalFiles,
|
||||||
|
'total_size' => $totalSize,
|
||||||
|
'partition_stats' => $partitionStats,
|
||||||
|
'category_stats' => $categoryStats,
|
||||||
|
'type_stats' => $typeStats
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清理过期文件
|
||||||
|
*
|
||||||
|
* @param int $days 保留天数
|
||||||
|
* @return int 清理的文件数量
|
||||||
|
*/
|
||||||
|
public function cleanExpiredFiles(int $days = 30): int
|
||||||
|
{
|
||||||
|
$expiredTime = time() - ($days * 24 * 60 * 60);
|
||||||
|
|
||||||
|
$config = [
|
||||||
|
'model' => $this->modelClass,
|
||||||
|
'where' => [
|
||||||
|
['created_at', '<', $expiredTime]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
$files = $this->logicListAll($config);
|
||||||
|
$count = 0;
|
||||||
|
|
||||||
|
foreach ($files as $file) {
|
||||||
|
// 删除物理文件
|
||||||
|
$fullPath = public_path() . $file->file_path;
|
||||||
|
if (file_exists($fullPath)) {
|
||||||
|
unlink($fullPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除数据库记录
|
||||||
|
$this->logicDeleteBy(['where' => ['id' => $file->id]]);
|
||||||
|
$count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $count;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace app\common\model;
|
||||||
|
|
||||||
|
use support\Model;
|
||||||
|
|
||||||
|
class UploadedFiles extends Model
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The connection name for the model.
|
||||||
|
*
|
||||||
|
* @var string|null
|
||||||
|
*/
|
||||||
|
protected $connection = 'mysql';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 $dateFormat = 'U';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 允许自动填充模型字段
|
||||||
|
*/
|
||||||
|
protected $fillable = [
|
||||||
|
'id',
|
||||||
|
'file_name',
|
||||||
|
'file_path',
|
||||||
|
'file_size',
|
||||||
|
'file_type',
|
||||||
|
'category',
|
||||||
|
'original_name',
|
||||||
|
'extension',
|
||||||
|
'partition',
|
||||||
|
'upload_ip',
|
||||||
|
'user_agent',
|
||||||
|
'created_at',
|
||||||
|
'updated_at'
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 不允许自动填充模型字段
|
||||||
|
*/
|
||||||
|
protected $guarded = [];
|
||||||
|
}
|
||||||
@@ -0,0 +1,200 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace app\common\service;
|
||||||
|
|
||||||
|
use app\common\validate\UploadedFilesValidate;
|
||||||
|
use app\common\traits\crud\CrudServiceTrait;
|
||||||
|
use app\common\model\UploadedFiles;
|
||||||
|
use support\Request;
|
||||||
|
use support\exception\BusinessException;
|
||||||
|
|
||||||
|
class UploadedFilesService
|
||||||
|
{
|
||||||
|
|
||||||
|
use CrudServiceTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 指定当前要操作的模型
|
||||||
|
*/
|
||||||
|
protected string $modelClass = UploadedFiles::class;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件上传分区配置
|
||||||
|
*/
|
||||||
|
protected array $uploadPartitions = [
|
||||||
|
'image' => [
|
||||||
|
'path' => 'images',
|
||||||
|
'max_size' => 5 * 1024 * 1024, // 5MB
|
||||||
|
'allowed_types' => ['image/jpeg', 'image/png', 'image/gif', 'image/webp'],
|
||||||
|
'extensions' => ['jpg', 'jpeg', 'png', 'gif', 'webp']
|
||||||
|
],
|
||||||
|
'document' => [
|
||||||
|
'path' => 'documents',
|
||||||
|
'max_size' => 10 * 1024 * 1024, // 10MB
|
||||||
|
'allowed_types' => ['application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'],
|
||||||
|
'extensions' => ['pdf', 'doc', 'docx']
|
||||||
|
],
|
||||||
|
'audio' => [
|
||||||
|
'path' => 'audios',
|
||||||
|
'max_size' => 20 * 1024 * 1024, // 20MB
|
||||||
|
'allowed_types' => ['audio/mp3', 'audio/wav', 'audio/ogg'],
|
||||||
|
'extensions' => ['mp3', 'wav', 'ogg']
|
||||||
|
],
|
||||||
|
'other' => [
|
||||||
|
'path' => 'others',
|
||||||
|
'max_size' => 5 * 1024 * 1024, // 5MB
|
||||||
|
'allowed_types' => [],
|
||||||
|
'extensions' => []
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录文件信息到数据库
|
||||||
|
*
|
||||||
|
* @param array $fileInfo 文件信息
|
||||||
|
* @param Request $request 请求对象
|
||||||
|
* @param string $partition 分区名称
|
||||||
|
* @param string $category 文件分类
|
||||||
|
* @return array
|
||||||
|
* @throws BusinessException
|
||||||
|
*/
|
||||||
|
public function recordFileInfo(array $fileInfo, Request $request, string $partition = 'other', string $category = 'general'): array
|
||||||
|
{
|
||||||
|
|
||||||
|
// 验证必需的文件信息
|
||||||
|
$requiredFields = ['file_name', 'file_path', 'file_size', 'file_type'];
|
||||||
|
foreach ($requiredFields as $field) {
|
||||||
|
if (!isset($fileInfo[$field])) {
|
||||||
|
throw new BusinessException("缺少必需的文件信息字段: {$field}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取文件扩展名
|
||||||
|
$fileExtension = pathinfo($fileInfo['file_name'], PATHINFO_EXTENSION);
|
||||||
|
|
||||||
|
// 记录到数据库
|
||||||
|
$fileData = [
|
||||||
|
'file_name' => $fileInfo['file_name'],
|
||||||
|
'file_path' => $fileInfo['file_path'],
|
||||||
|
'file_size' => $fileInfo['file_size'],
|
||||||
|
'file_type' => $fileInfo['file_type'],
|
||||||
|
'category' => $category,
|
||||||
|
'original_name' => $fileInfo['original_name'] ?? '',
|
||||||
|
'extension' => $fileExtension,
|
||||||
|
'partition' => $partition,
|
||||||
|
'upload_ip' => $request->getRealIp(),
|
||||||
|
'user_agent' => $request->header('User-Agent', ''),
|
||||||
|
];
|
||||||
|
|
||||||
|
$uploadedFile = $this->logicCreate($fileData);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'id' => $uploadedFile->id,
|
||||||
|
'file_name' => $fileInfo['file_name'],
|
||||||
|
'file_path' => $fileInfo['file_path'],
|
||||||
|
'file_url' => getenv('MAIN_DOMAIN') . $fileInfo['file_path'],
|
||||||
|
'file_size' => $fileInfo['file_size'],
|
||||||
|
'file_type' => $fileInfo['file_type'],
|
||||||
|
'category' => $category,
|
||||||
|
'partition' => $partition,
|
||||||
|
'original_name' => $fileInfo['original_name'] ?? '',
|
||||||
|
'extension' => $fileExtension,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取文件信息
|
||||||
|
*
|
||||||
|
* @param int $fileId
|
||||||
|
* @return array|null
|
||||||
|
*/
|
||||||
|
public function getFileInfo(int $fileId): ?array
|
||||||
|
{
|
||||||
|
$file = $this->logicShow(['where' => ['id' => $fileId]]);
|
||||||
|
|
||||||
|
if (!$file) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'id' => $file->id,
|
||||||
|
'file_name' => $file->file_name,
|
||||||
|
'file_path' => $file->file_path,
|
||||||
|
'file_url' => getenv('MAIN_DOMAIN') . $file->file_path,
|
||||||
|
'file_size' => $file->file_size,
|
||||||
|
'file_type' => $file->file_type,
|
||||||
|
'category' => $file->category,
|
||||||
|
'partition' => $file->partition ?? 'other',
|
||||||
|
'original_name' => $file->original_name ?? '',
|
||||||
|
'extension' => $file->extension ?? '',
|
||||||
|
'created_at' => $file->created_at,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除文件
|
||||||
|
*
|
||||||
|
* @param int $fileId
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function deleteFile(int $fileId): bool
|
||||||
|
{
|
||||||
|
$file = $this->logicShow(['where' => ['id' => $fileId]]);
|
||||||
|
|
||||||
|
if (!$file) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除物理文件
|
||||||
|
$fullPath = public_path() . $file->file_path;
|
||||||
|
if (file_exists($fullPath)) {
|
||||||
|
unlink($fullPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除数据库记录
|
||||||
|
return $this->logicDeleteBy(['where' => ['id' => $fileId]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取分区配置
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getPartitionConfig(): array
|
||||||
|
{
|
||||||
|
return $this->uploadPartitions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成文件名
|
||||||
|
*
|
||||||
|
* @param $file
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function generateFileName($file): string
|
||||||
|
{
|
||||||
|
$extension = $file->getUploadExtension();
|
||||||
|
$timestamp = time();
|
||||||
|
$random = substr(md5(uniqid()), 0, 8);
|
||||||
|
|
||||||
|
return $timestamp . '_' . $random . '.' . $extension;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化文件大小
|
||||||
|
*
|
||||||
|
* @param int $bytes
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected 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];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -431,12 +431,12 @@ class Emails
|
|||||||
$enterUser = User::where('id', $enterUserId)->first();
|
$enterUser = User::where('id', $enterUserId)->first();
|
||||||
|
|
||||||
// 固定渠道
|
// 固定渠道
|
||||||
$channel = 8;
|
$channel = 12;
|
||||||
$channelInfo = InquiryChannel::where('id', $channel)->first();
|
$channelInfo = InquiryChannel::where('id', $channel)->first();
|
||||||
|
|
||||||
// 初始化 UID 轮询器
|
// 初始化 UID 轮询器
|
||||||
$uidRotator = new \app\common\utils\RedisUidRotator('email_parsed_result_20250804');
|
$uidRotator = new \app\common\utils\RedisUidRotator('email_parsed_result_20250804');
|
||||||
$uidRotator->setUids([64, 63, 83, 93, 108, 118, 132, 145, 150, 143, 156, 157, 158], false);
|
$uidRotator->setUids([64, 63, 83, 93, 108, 132, 145, 150, 143, 156, 157, 158], false);
|
||||||
|
|
||||||
EmailParsedResult::where('status', 0)
|
EmailParsedResult::where('status', 0)
|
||||||
->chunkById($batchSize, function ($records) use ($delaySeconds, $throttleAfter, $throttleSeconds, $enterUser, $channelInfo, $uidRotator) {
|
->chunkById($batchSize, function ($records) use ($delaySeconds, $throttleAfter, $throttleSeconds, $enterUser, $channelInfo, $uidRotator) {
|
||||||
@@ -452,7 +452,13 @@ class Emails
|
|||||||
$assignedUid = $uidRotator->next();
|
$assignedUid = $uidRotator->next();
|
||||||
|
|
||||||
// 获取分配的用户信息
|
// 获取分配的用户信息
|
||||||
$recipientInfo = User::where('id', $assignedUid)->first();
|
$recipientInfo = User::where('id', $assignedUid)->where('status', 1)->first();
|
||||||
|
if (!$recipientInfo) {
|
||||||
|
flg("用户ID=" . $assignedUid . " 不存在或已离职跳过分配,需要手动移除 UID");
|
||||||
|
|
||||||
|
$assignedUid = $uidRotator->next();
|
||||||
|
$recipientInfo = User::where('id', $assignedUid)->where('status', 1)->first();
|
||||||
|
}
|
||||||
|
|
||||||
// 生成录单
|
// 生成录单
|
||||||
$log = [
|
$log = [
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace app\common\validate;
|
||||||
|
|
||||||
|
use think\Validate;
|
||||||
|
|
||||||
|
class UploadedFilesValidate extends Validate
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 验证规则
|
||||||
|
*/
|
||||||
|
protected $rule = [
|
||||||
|
'file_name' => 'require|max:255',
|
||||||
|
'file_path' => 'require|max:500',
|
||||||
|
'file_size' => 'require|integer|gt:0',
|
||||||
|
'file_type' => 'require|max:100',
|
||||||
|
'category' => 'require|max:50',
|
||||||
|
'original_name' => 'max:255',
|
||||||
|
'extension' => 'max:20',
|
||||||
|
'partition' => 'max:50',
|
||||||
|
'upload_ip' => 'ip',
|
||||||
|
'user_agent' => 'max:500',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证消息
|
||||||
|
*/
|
||||||
|
protected $message = [
|
||||||
|
'file_name.require' => '文件名不能为空',
|
||||||
|
'file_name.max' => '文件名长度不能超过255个字符',
|
||||||
|
'file_path.require' => '文件路径不能为空',
|
||||||
|
'file_path.max' => '文件路径长度不能超过500个字符',
|
||||||
|
'file_size.require' => '文件大小不能为空',
|
||||||
|
'file_size.integer' => '文件大小必须是整数',
|
||||||
|
'file_size.gt' => '文件大小必须大于0',
|
||||||
|
'file_type.require' => '文件类型不能为空',
|
||||||
|
'file_type.max' => '文件类型长度不能超过100个字符',
|
||||||
|
'category.require' => '文件分类不能为空',
|
||||||
|
'category.max' => '文件分类长度不能超过50个字符',
|
||||||
|
'original_name.max' => '原始文件名长度不能超过255个字符',
|
||||||
|
'extension.max' => '文件扩展名长度不能超过20个字符',
|
||||||
|
'partition.max' => '分区名称长度不能超过50个字符',
|
||||||
|
'upload_ip.ip' => '上传IP格式不正确',
|
||||||
|
'user_agent.max' => '用户代理长度不能超过500个字符',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证场景
|
||||||
|
*/
|
||||||
|
protected $scene = [
|
||||||
|
'create' => ['file_name', 'file_path', 'file_size', 'file_type', 'category'],
|
||||||
|
'update' => ['file_name', 'file_path', 'file_size', 'file_type', 'category', 'original_name', 'extension', 'partition'],
|
||||||
|
'search' => ['category', 'partition', 'file_type'],
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证文件上传
|
||||||
|
*/
|
||||||
|
public function sceneUpload()
|
||||||
|
{
|
||||||
|
return $this->append('file_name', 'require|max:255')
|
||||||
|
->append('file_path', 'require|max:500')
|
||||||
|
->append('file_size', 'require|integer|gt:0')
|
||||||
|
->append('file_type', 'require|max:100')
|
||||||
|
->append('category', 'require|max:50')
|
||||||
|
->append('original_name', 'max:255')
|
||||||
|
->append('extension', 'max:20')
|
||||||
|
->append('partition', 'max:50')
|
||||||
|
->append('upload_ip', 'ip')
|
||||||
|
->append('user_agent', 'max:500');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证文件信息更新
|
||||||
|
*/
|
||||||
|
public function sceneUpdateInfo()
|
||||||
|
{
|
||||||
|
return $this->append('file_name', 'require|max:255')
|
||||||
|
->append('category', 'require|max:50')
|
||||||
|
->append('original_name', 'max:255');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,26 +29,26 @@ class Emails
|
|||||||
// });
|
// });
|
||||||
|
|
||||||
if (getenv('ENVIRONMENT') === 'master') {
|
if (getenv('ENVIRONMENT') === 'master') {
|
||||||
// 拉取新邮件 - 每3小时的第0分钟0秒执行
|
// 拉取新邮件 - 每小时的第20分钟0秒执行
|
||||||
new Crontab('0 0 */3 * * *', function(){
|
new Crontab('0 */20 * * * *', function(){
|
||||||
$email = new EmailsUtils();
|
$email = new EmailsUtils();
|
||||||
$email->uids();
|
$email->uids();
|
||||||
});
|
});
|
||||||
|
|
||||||
// 邮件详情 - 每3小时的第10分钟0秒执行
|
// 邮件详情 - 每小时的第25分钟0秒执行
|
||||||
new Crontab('0 10 */3 * * *', function(){
|
new Crontab('0 */25 * * * *', function(){
|
||||||
$email = new EmailsUtils();
|
$email = new EmailsUtils();
|
||||||
$email->details();
|
$email->details();
|
||||||
});
|
});
|
||||||
|
|
||||||
// 邮件处理 - 每3小时的第20分钟0秒执行
|
// 邮件处理 - 每小时的第30分钟0秒执行
|
||||||
new Crontab('0 20 */3 * * *', function(){
|
new Crontab('0 */30 * * * *', function(){
|
||||||
$email = new EmailsUtils();
|
$email = new EmailsUtils();
|
||||||
$email->process();
|
$email->process();
|
||||||
});
|
});
|
||||||
|
|
||||||
// 邮件分派 - 每3小时的第30分钟0秒执行
|
// 邮件分派 - 每小时的第35分钟0秒执行
|
||||||
new Crontab('0 30 */3 * * *', function(){
|
new Crontab('0 */35 * * * *', function(){
|
||||||
$email = new EmailsUtils();
|
$email = new EmailsUtils();
|
||||||
$email->fenpai();
|
$email->fenpai();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ Route::any('/feishu/event', [app\common\controller\NotifyController::class, 'fei
|
|||||||
Route::any('/feishu/callback', [app\common\controller\NotifyController::class, 'feishuCallback']);
|
Route::any('/feishu/callback', [app\common\controller\NotifyController::class, 'feishuCallback']);
|
||||||
// 代理oss文件地址
|
// 代理oss文件地址
|
||||||
Route::any('/proxy/ossurl', [app\common\controller\UploadController::class, 'proxy']);
|
Route::any('/proxy/ossurl', [app\common\controller\UploadController::class, 'proxy']);
|
||||||
|
Route::any('/upload', [app\common\controller\UploadController::class, 'uploadFile']);
|
||||||
|
Route::any('/upload/image', [app\common\controller\UploadController::class, 'uploadImage']);
|
||||||
|
|
||||||
// 公共
|
// 公共
|
||||||
Route::group('/common', function () {
|
Route::group('/common', function () {
|
||||||
|
|||||||
Reference in New Issue
Block a user