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::imagick(); 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() ?? date('Ymd') . '/' . md5((string)time()) . '.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 = $filename; $image_model->image_thumb = $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('上传失败'); } } return success('操作成功', [ 'path' => $storage . '/' . $image_model->image_path, 'thumb_path' => $storage . '/' . $image_model->image_thumb, 'filemd5' => $image_model->file_md5, 'filesha1' => $image_model->file_sha1 ]); } catch (\Throwable $th) { dump($th);exit; return error($th->getMessage()); } return error('上传失败'); } /** * 检查路径 * * @param string $path * @return string */ private function checkPath($path): string { $ok = false; $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, []); throw_if(empty($options), '上传配置错误'); 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'), ]; } /** * 获取水印配置 * * @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 ) = $this->getUploadOptions('upload_video'); // 是否需要根据文件MD5值检查文件是否已存在 $video = $filemd5_unique ? SysVideoUploadRecordModel::md5($filemd5)->find() : null; if (is_null($video)) { // 检查是否需要保留原文件名 $name_rule = fn() => $filename_keep ? $this->filenameGenerator($file) : null; $filename = Filesystem::disk('video')->putFile($param['module'], $file, $name_rule()); // 保存视频 $video = new SysVideoUploadRecordModel(); $video->language_id = request()->lang_id; $video->module = $param['module']; $video->video_path = $filename; $video->file_size = $file->getSize(); $video->file_type = $file->getOriginalMime(); $video->file_md5 = $filemd5; $video->file_sha1 = $filesha1; if (!$video->save()) { return error('上传失败'); } } return success('上传成功', [ 'path' => $storage . '/' . $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()); } $filemd5 = $file->md5(); $filesha1 = $file->sha1(); // 获取附件上传配置 list( 'filename_keep' => $filename_keep, 'filemd5_unique' => $filemd5_unique ) = $this->getUploadOptions('upload_attachment'); // 是否需要根据文件MD5值检查文件是否已存在 $attachment = $filemd5_unique ? SysAttachmentUploadRecordModel::md5($filemd5)->find() : null; if (is_null($attachment)) { // 检查是否需要保留原文件名 $name_rule = fn() => $filename_keep ? $this->filenameGenerator($file) : null; $filename = Filesystem::disk('public')->putFile('attachments', $file, $name_rule()); // 保存视频 $attachment = new SysAttachmentUploadRecordModel(); $attachment->language_id = request()->lang_id; $attachment->attachment_path = $filename; $attachment->file_size = $file->getSize(); $attachment->file_type = $file->getOriginalMime(); $attachment->file_md5 = $filemd5; $attachment->file_sha1 = $filesha1; if (!$attachment->save()) { return error('上传失败'); } } $storage = config('filesystem.disks.public.url'); return success('上传成功', [ 'path' => $storage . '/' . $attachment->attachment_path, 'file_md5' => $attachment->file_md5, 'file_sha1' => $attachment->file_sha1 ]); } catch (\Throwable $th) { return error($th->getMessage()); } return error('上传失败'); } }