476 lines
20 KiB
PHP
476 lines
20 KiB
PHP
<?php
|
|
declare (strict_types = 1);
|
|
|
|
namespace app\admin\controller\v1;
|
|
|
|
use app\admin\model\v1\SysImageUploadRecordModel;
|
|
use app\admin\model\v1\SysVideoUploadRecordModel;
|
|
use app\admin\model\v1\SysAttachmentUploadRecordModel;
|
|
use Intervention\Image\ImageManager;
|
|
use Intervention\Image\Typography\FontFactory;
|
|
use think\facade\Filesystem;
|
|
use filesystem\Qiniu;
|
|
|
|
/**
|
|
* 文件上传控制器
|
|
*/
|
|
class Upload
|
|
{
|
|
// 上传图片
|
|
public function image()
|
|
{
|
|
$param = request()->param(['module' => 'unknown']);
|
|
if (is_null($param)) {
|
|
return error('请确定请求参数正确');
|
|
}
|
|
$file = request()->file('image');
|
|
if (is_null($file)) {
|
|
return error('请确定上传对象或字段是否正确');
|
|
}
|
|
|
|
try {
|
|
$max_size = strtobytes(env('ADMIN_API.MAX_IMAGE_SIZE', '1mb'));
|
|
$validate = validate([
|
|
'module' => 'require|max:64',
|
|
'image' => "fileSize:$max_size|fileExt:jpg,jpeg,png,gif,webp"
|
|
]);
|
|
if (!$validate->check(['module' => $param['module'], 'image' => $file])) {
|
|
return error($validate->getError());
|
|
}
|
|
|
|
$storage = config('filesystem.disks.image.url');
|
|
|
|
$filemd5 = $file->md5();
|
|
$filesha1 = $file->sha1();
|
|
|
|
// 获取图片上传配置
|
|
list(
|
|
'filename_keep' => $filename_keep,
|
|
'filemd5_unique' => $filemd5_unique,
|
|
'filetype_to' => $filetype_to,
|
|
) = $this->getUploadOptions('upload_image');
|
|
|
|
// 获取文件大小
|
|
$file_size = $file->getSize();
|
|
// 获取文件mime类型
|
|
$mime_type = $file->getOriginalMime();
|
|
|
|
// 是否需要根据文件MD5值检查文件是否已存在
|
|
$image_model = $filemd5_unique ? SysImageUploadRecordModel::md5($filemd5)->find() : null;
|
|
if (is_null($image_model)) {
|
|
// 检查是否需要保留原文件名生成器
|
|
$name_rule = fn() => $filename_keep ? $this->filenameGenerator($file) : null;
|
|
|
|
// 处理图片
|
|
$image_manager = ImageManager::gd();
|
|
if ($filetype_to == 'original') {
|
|
$filename = Filesystem::disk('image')->putFile($param['module'], $file, $name_rule());
|
|
$image = $image_manager->read('.' . $storage . '/' . $filename);
|
|
}
|
|
else if ($filetype_to == 'webp') {
|
|
$image = $image_manager->read($file->getRealPath());
|
|
// 转换为webp格式
|
|
$webp = $image->toWebp(75);
|
|
$root = config('filesystem.disks.image.root');
|
|
$filename = $param['module'] . '/' . ($name_rule() ? $name_rule()() : date('Ymd') . '/' . md5((string)time() . random_str(8))) . '.webp';
|
|
$webp->save($this->checkPath($root . '/' . $filename));
|
|
// 获取webp文件大小
|
|
$file_size = $webp->size();
|
|
// 获取webp文件mime类型
|
|
$mime_type = $webp->mimetype();
|
|
}
|
|
|
|
// 水印
|
|
list(
|
|
'enabled' => $enabled,
|
|
'type' => $type,
|
|
'text_options' => $text_options,
|
|
'image_options' => $image_options
|
|
) = $this->getWatermarkOptions();
|
|
if ($enabled) {
|
|
// 图片水印
|
|
if ($type == 'IMAGE' && $image_options['image'] != '') {
|
|
// 读取水印图片
|
|
$watermark_image = $image_manager->read(public_path() . $image_options['image']);
|
|
// 缩放水印图片
|
|
$watermark_image->scale($image_options['width'], $image_options['height']);
|
|
// 绘制水印图片
|
|
$image->place(
|
|
$watermark_image,
|
|
$image_options['position'],
|
|
$image_options['offset_x'],
|
|
$image_options['offset_y'],
|
|
$image_options['opacity']
|
|
);
|
|
}
|
|
// 文字水印
|
|
else if ($type == 'TEXT' && $text_options['txt'] != '') {
|
|
// 原图宽度
|
|
$origin_width = $image->width();
|
|
// 原图高度
|
|
$origin_height = $image->height();
|
|
|
|
$font_factory = new FontFactory(function(FontFactory $font) use($text_options) {
|
|
// 设置字体
|
|
$font->filename(public_path() . $text_options['font']);
|
|
// 设置字体大小
|
|
$font->size($text_options['size']);
|
|
// 设置字体颜色及透明度
|
|
$opacity = $text_options['opacity'] > 0 ? dechex((int)ceil(255 * ($text_options['opacity'] / 100))) : '00';
|
|
$font->color($text_options['color'] . $opacity);
|
|
$font->align('left');
|
|
$font->valign('top');
|
|
});
|
|
// 文字尺寸
|
|
$font_rect = $image->driver()->fontProcessor()->boxSize($text_options['txt'], $font_factory());
|
|
// 计算偏移量
|
|
list($offset_x, $offset_y) = $this->scaleTxtOffsetXYByPosition(
|
|
$text_options['position'],
|
|
$text_options['offset_x'],
|
|
$text_options['offset_y'],
|
|
$origin_width,
|
|
$origin_height,
|
|
$font_rect->width(),
|
|
$font_rect->height()
|
|
);
|
|
// 绘制文字
|
|
$image->text(
|
|
$text_options['txt'],
|
|
$offset_x,
|
|
$offset_y,
|
|
$font_factory()
|
|
);
|
|
}
|
|
$image->save('.'. $storage. '/'. $filename);
|
|
}
|
|
|
|
// 缩略图
|
|
$image->scale(200, 200);
|
|
$idx = strrpos($filename, '.');
|
|
$thumb_filename = mb_substr($filename, 0, $idx) . '_thumb.' . mb_substr($filename, $idx + 1);
|
|
$image->save('.' . $storage . '/' . $thumb_filename);
|
|
|
|
// 保存图片
|
|
$image_model = new SysImageUploadRecordModel();
|
|
$image_model->language_id = request()->lang_id;
|
|
$image_model->module = $param['module'];
|
|
$image_model->image_path = $storage . '/' . $filename;
|
|
$image_model->image_thumb = $storage . '/' . $thumb_filename;
|
|
$image_model->file_size = $file_size;
|
|
$image_model->file_type = $mime_type;
|
|
$image_model->file_md5 = $filemd5;
|
|
$image_model->file_sha1 = $filesha1;
|
|
if (!$image_model->save()) {
|
|
return error('上传失败');
|
|
}
|
|
}
|
|
|
|
if (!str_starts_with($image_model->image_path, $storage)) {
|
|
$image_model->image_path = $storage . '/' . $image_model->image_path;
|
|
}
|
|
if (!str_starts_with($image_model->image_thumb, $storage)) {
|
|
$image_model->image_thumb = $storage . '/' . $image_model->image_thumb;
|
|
}
|
|
return success('操作成功', [
|
|
'path' => $image_model->image_path,
|
|
'thumb_path' => $image_model->image_thumb,
|
|
'filemd5' => $image_model->file_md5,
|
|
'filesha1' => $image_model->file_sha1
|
|
]);
|
|
} catch (\Throwable $th) {
|
|
return error($th->getMessage());
|
|
}
|
|
|
|
return error('上传失败');
|
|
}
|
|
/**
|
|
* 检查路径
|
|
*
|
|
* @param string $path
|
|
* @return string
|
|
*/
|
|
private function checkPath($path): string
|
|
{
|
|
$ok = true;
|
|
$filename = basename($path);
|
|
$dirname = dirname($path);
|
|
if (!is_dir($dirname)) {
|
|
$ok = @mkdir($dirname, 0755, true);
|
|
}
|
|
else if (!is_writable($dirname)) {
|
|
$ok = @chmod($dirname,0755);
|
|
}
|
|
|
|
if ($ok) {
|
|
return $dirname . '/' . $filename;
|
|
}
|
|
|
|
throw new \Exception("上传目标目录不可用");
|
|
}
|
|
/**
|
|
* 文件名生成回调
|
|
*
|
|
* @param \think\file\UploadedFile $file
|
|
* @return callable
|
|
*/
|
|
private function filenameGenerator(\think\file\UploadedFile $file): callable
|
|
{
|
|
return fn() => date('Ymd') . '/' . pathinfo($file->getOriginalName(), PATHINFO_FILENAME);
|
|
}
|
|
/**
|
|
* 获取上传配置
|
|
*
|
|
* @param string $module
|
|
* @return array
|
|
*/
|
|
private function getUploadOptions($module)
|
|
{
|
|
$config_model = new \app\admin\controller\v1\SiteConfig;
|
|
$config = $config_model->getByGroupUniqueLabel('upload');
|
|
$options = data_get($config, $module, []);
|
|
|
|
return [
|
|
'filename_keep' => (int)data_get($options, 'filename_keep.value', 0) == 1,
|
|
'filemd5_unique' => (int)data_get($options, 'filemd5_unique.value', 0) == 1,
|
|
'filetype_to' => data_get($options, 'filetype_to.value', 'original'),
|
|
'save_to' => data_get($options, 'save_to.value', 'local'),
|
|
];
|
|
}
|
|
/**
|
|
* 获取水印配置
|
|
*
|
|
* @return array
|
|
*/
|
|
private function getWatermarkOptions(): array
|
|
{
|
|
$config_model = new \app\admin\controller\v1\SiteConfig;
|
|
$watermark_config = $config_model->getByGroupUniqueLabel('watermark');
|
|
|
|
$opacity = data_get($watermark_config, 'watermark_opacity.value', 100);
|
|
if ($opacity == '') {
|
|
$opacity = 100;
|
|
}
|
|
return [
|
|
'enabled' => data_get($watermark_config, 'watermark_enabled.value', 0) == 1,
|
|
'type' => data_get($watermark_config, 'watermark_type.value', ''),
|
|
'text_options' => [
|
|
'txt' => data_get($watermark_config, 'watermark_text_value.value', ''),
|
|
'font' => data_get($watermark_config, 'watermark_text_font.value', ''),
|
|
'size' => (float)data_get($watermark_config, 'watermark_text_size.value', 12)?:12,
|
|
'color' => data_get($watermark_config, 'watermark_text_color.value', '#000000')?:'#000000',
|
|
'position' => data_get($watermark_config, 'watermark_position.value', 'top-left')?:'top-left',
|
|
'offset_x' => (int)data_get($watermark_config, 'watermark_offset_x.value', 0),
|
|
'offset_y' => (int)data_get($watermark_config, 'watermark_offset_y.value', 0),
|
|
'opacity' => (int)$opacity,
|
|
],
|
|
'image_options' => [
|
|
'image' => data_get($watermark_config, 'watermark_image_value.value', ''),
|
|
'width' => (int)data_get($watermark_config, 'watermark_image_width.value')?:null,
|
|
'height' => (int)data_get($watermark_config, 'watermark_image_height.value')?:null,
|
|
'position' => data_get($watermark_config, 'watermark_position.value', 'top-left')?:'top-left',
|
|
'offset_x' => (int)data_get($watermark_config, 'watermark_offset_x.value', 0),
|
|
'offset_y' => (int)data_get($watermark_config, 'watermark_offset_y.value', 0),
|
|
'opacity' => (int)$opacity,
|
|
]
|
|
];
|
|
}
|
|
/**
|
|
* 计算文本水印偏移量
|
|
*
|
|
* @param string $position
|
|
* @param integer $offset_x
|
|
* @param integer $offset_y
|
|
* @param integer $image_width
|
|
* @param integer $image_height
|
|
* @param integer $txt_width
|
|
* @param integer $txt_height
|
|
* @return array
|
|
*/
|
|
private function scaleTxtOffsetXYByPosition(string $position, int $offset_x, int $offset_y, int $image_width, int $image_height, int $txt_width, int $txt_height)
|
|
{
|
|
switch ($position) {
|
|
case 'top-left':
|
|
// top-left:左上角
|
|
return [$offset_x, $offset_y];
|
|
case 'top-right':
|
|
// top-right:右上角
|
|
return [(int)($image_width-$txt_width-$offset_x), $offset_y];
|
|
case 'top':
|
|
// top:上 - 水平居中
|
|
return [(int)(($image_width-$txt_width+$offset_x)/2), $offset_y];
|
|
case 'left':
|
|
// left:左 - 垂直居中
|
|
return [$offset_x, (int)(($image_height-$txt_height)/2+$offset_y)];
|
|
case 'center':
|
|
// center:水平垂直居中
|
|
return [(int)(($image_width-$txt_width)/2+$offset_x), (int)(($image_height-$txt_height)/2+$offset_y)];
|
|
case 'right':
|
|
// right:右 - 垂直居中
|
|
return [(int)($image_width-$txt_width-$offset_x), (int)(($image_height-$txt_height)/2+$offset_y)];
|
|
case'bottom':
|
|
// bottom:下 - 水平居中
|
|
return [(int)(($image_width-$txt_width+$offset_x)/2), (int)($image_height-$txt_height-$offset_y)];
|
|
case'bottom-left':
|
|
// bottom-left:左下角
|
|
return [$offset_x, (int)($image_height-$txt_height-$offset_y)];
|
|
case'bottom-right':
|
|
// bottom-right:右下角
|
|
return [(int)($image_width-$txt_width-$offset_x), (int)($image_height-$txt_height-$offset_y)];
|
|
default:
|
|
throw new \InvalidArgumentException('Invalid position');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 上传视频
|
|
*/
|
|
public function video()
|
|
{
|
|
$param = request()->param(['module' => 'unknown']);
|
|
if (is_null($param)) {
|
|
return error('请确定请求参数正确');
|
|
}
|
|
$file = request()->file('video');
|
|
if (is_null($file)) {
|
|
return error('请确定上传对象或字段是否正确');
|
|
}
|
|
|
|
try {
|
|
$max_size = strtobytes(env('ADMIN_API.MAX_VIDEO_SIZE', '100mb'));
|
|
$validate = validate([
|
|
'module' => 'require|max:64',
|
|
'video' => "fileSize:$max_size|fileExt:mp4"
|
|
]);
|
|
if (!$validate->check(['module' => $param['module'], 'video' => $file])) {
|
|
return error($validate->getError());
|
|
}
|
|
|
|
$storage = config('filesystem.disks.video.url');
|
|
|
|
$filemd5 = $file->md5();
|
|
$filesha1 = $file->sha1();
|
|
|
|
// 获取视频上传配置
|
|
list(
|
|
'filename_keep' => $filename_keep,
|
|
'filemd5_unique' => $filemd5_unique,
|
|
'save_to' => $save_to,
|
|
) = $this->getUploadOptions('upload_video');
|
|
// 是否需要根据文件MD5值检查文件是否已存在
|
|
$video = $filemd5_unique ? SysVideoUploadRecordModel::md5($filemd5)->find() : null;
|
|
if (is_null($video)) {
|
|
// 保存位置配置 key
|
|
$disk = 'video';
|
|
// 检查是否需要保留原文件名
|
|
$name_rule = fn() => $filename_keep ? $this->filenameGenerator($file) : null;
|
|
|
|
// 保存到七牛云
|
|
if ($save_to == 'qiniu_cloud') {
|
|
$disk = 'video_qiniu';
|
|
$storage = config('filesystem.disks.video_qiniu.path_prefix');
|
|
}
|
|
$filename = Filesystem::disk($disk)->putFile($param['module'], $file, $name_rule());
|
|
|
|
// 保存视频
|
|
$video = new SysVideoUploadRecordModel();
|
|
$video->language_id = request()->lang_id;
|
|
$video->module = $param['module'];
|
|
$video->video_path = $storage . '/' . $filename;
|
|
$video->file_size = $file->getSize();
|
|
$video->file_type = $file->getOriginalMime();
|
|
$video->file_md5 = $filemd5;
|
|
$video->file_sha1 = $filesha1;
|
|
if (!$video->save()) {
|
|
return error('上传失败');
|
|
}
|
|
}
|
|
|
|
if (!str_starts_with($video->video_path, $storage)) {
|
|
$video->video_path = $storage . '/' . $video->video_path;
|
|
}
|
|
return success('上传成功', [
|
|
'path' => $video->video_path,
|
|
'file_md5' => $video->file_md5,
|
|
'file_sha1' => $video->file_sha1
|
|
]);
|
|
} catch (\Throwable $th) {
|
|
return error($th->getMessage());
|
|
}
|
|
|
|
return error('上传失败');
|
|
}
|
|
|
|
/**
|
|
* 附件上传
|
|
*/
|
|
public function attachment()
|
|
{
|
|
$file = request()->file('attachment');
|
|
if (is_null($file)) {
|
|
return error('请确定上传对象或字段是否正确');
|
|
}
|
|
|
|
try {
|
|
$max_size = strtobytes(env('ADMIN_API.MAX_ATTACHMENT_SIZE', '100mb'));
|
|
$validate = validate([
|
|
'attachment' => "fileSize:$max_size|fileExt:biz,bz,bz2,gz,tgz,zip,rar,7z,doc,docx,xls,xlsx,csv,ppt,pptx,pdf,txt,jpg,jpeg,png,webp,ttf"
|
|
]);
|
|
if (!$validate->check(['attachment' => $file])) {
|
|
return error($validate->getError());
|
|
}
|
|
|
|
$storage = config('filesystem.disks.public.url');
|
|
|
|
$filemd5 = $file->md5();
|
|
$filesha1 = $file->sha1();
|
|
|
|
// 获取附件上传配置
|
|
list(
|
|
'filename_keep' => $filename_keep,
|
|
'filemd5_unique' => $filemd5_unique,
|
|
'save_to' => $save_to
|
|
) = $this->getUploadOptions('upload_attachment');
|
|
// 是否需要根据文件MD5值检查文件是否已存在
|
|
$attachment = $filemd5_unique ? SysAttachmentUploadRecordModel::md5($filemd5)->find() : null;
|
|
if (is_null($attachment)) {
|
|
// 保存位置配置 key
|
|
$disk = 'public';
|
|
// 检查是否需要保留原文件名
|
|
$name_rule = fn() => $filename_keep ? $this->filenameGenerator($file) : null;
|
|
|
|
// 保存到七牛云
|
|
if ($save_to == 'qiniu_cloud') {
|
|
$disk = 'public_qiniu';
|
|
$storage = config('filesystem.disks.public_qiniu.path_prefix');
|
|
}
|
|
$filename = Filesystem::disk($disk)->putFile('attachments', $file, $name_rule());
|
|
|
|
// 保存视频
|
|
$attachment = new SysAttachmentUploadRecordModel();
|
|
$attachment->language_id = request()->lang_id;
|
|
$attachment->attachment_path = $storage . '/' . $filename;
|
|
$attachment->file_size = $file->getSize();
|
|
$attachment->file_type = $file->getOriginalMime();
|
|
$attachment->file_md5 = $filemd5;
|
|
$attachment->file_sha1 = $filesha1;
|
|
if (!$attachment->save()) {
|
|
return error('上传失败');
|
|
}
|
|
}
|
|
|
|
if (!str_starts_with($attachment->attachment_path, $storage)) {
|
|
$attachment->attachment_path = $storage . '/' . $attachment->attachment_path;
|
|
}
|
|
return success('上传成功', [
|
|
'path' => $attachment->attachment_path,
|
|
'file_md5' => $attachment->file_md5,
|
|
'file_sha1' => $attachment->file_sha1
|
|
]);
|
|
} catch (\Throwable $th) {
|
|
return error($th->getMessage());
|
|
}
|
|
|
|
return error('上传失败');
|
|
}
|
|
}
|