From 6b0bd13fb7869feb8d352d93c3e1b078a85ccab8 Mon Sep 17 00:00:00 2001 From: jsasg <735273025@qq.com> Date: Wed, 11 Jun 2025 14:46:40 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=9B=BE=E7=89=87=E4=B8=8A=E4=BC=A0?= =?UTF-8?q?=E6=B0=B4=E5=8D=B0=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/admin/controller/v1/SiteConfig.php | 38 +++++++ app/admin/controller/v1/Upload.php | 143 ++++++++++++++++++++++++- 2 files changed, 179 insertions(+), 2 deletions(-) diff --git a/app/admin/controller/v1/SiteConfig.php b/app/admin/controller/v1/SiteConfig.php index c1ca70e2..165bc36f 100644 --- a/app/admin/controller/v1/SiteConfig.php +++ b/app/admin/controller/v1/SiteConfig.php @@ -141,6 +141,44 @@ class SiteConfig return $ret; } + // 根据分组获取配置 + public function getByGroupUniqueLabel($unique_label) + { + $configs = SysConfigModel::alias('c') + ->field([ + 'c.id', + 'c.title', + 'c.name', + 'c.value', + ]) + ->join(SysConfigGroupModel::getTable(). ' g', 'g.id = c.group_id') + ->where('g.language_id', '=', request()->lang_id) + ->where('g.unique_label', '=', $unique_label) + ->where('g.status', '=', 1) + ->order(['c.sort' => 'asc', 'c.id' => 'desc']) + ->select() + ->each(function ($item) { + // 修改字段为null的输出为空字符串 + $keys = array_keys($item->toArray()); + foreach ($keys as $key) { + if (is_null($item[$key])) { + $item[$key] = ''; + } + } + return $item; + }) + ->toArray(); + if (!empty($configs)) { + $configs_map = []; + foreach ($configs as $cfg) { + $configs_map[$cfg['name']] = $cfg; + } + return $configs_map; + } + + return []; + } + // 更新配置 public function update() { diff --git a/app/admin/controller/v1/Upload.php b/app/admin/controller/v1/Upload.php index e9d58ec8..24a077e2 100644 --- a/app/admin/controller/v1/Upload.php +++ b/app/admin/controller/v1/Upload.php @@ -7,6 +7,7 @@ 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; /** @@ -44,9 +45,68 @@ class Upload $image_model = SysImageUploadRecordModel::md5($filemd5)->find(); if (is_null($image_model)) { $filename = Filesystem::disk('image')->putFile($param['module'], $file); - // 生成缩略图 - $image_manager = new ImageManager(new \Intervention\Image\Drivers\Imagick\Driver()); + // 处理图片 + $image_manager = ImageManager::imagick(); $image = $image_manager->read('.' . $storage . '/' . $filename); + + // 水印 + list($enabled, $type, $text_options, $image_options) = $this->getWatermarkOptions(); + if ($enabled) { + // 图片水印 + if ($type == 'IMAGE' && $image_options['image'] != '') { + // 读取水印图片 + $watermark_image = $image_manager->read($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($text_options['font']); + // 设置字体大小 + $font->size($text_options['size']); + // 设置字体颜色及透明度 + $opacity = dechex((int)ceil(255 * ($text_options['opacity'] / 100))); + $font->color($text_options['color'] . $opacity); + }); + // 文字尺寸 + $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); @@ -79,6 +139,85 @@ class Upload return error('上传失败'); } + /** + * 获取水印配置 + * + * @return array + */ + private function getWatermarkOptions(): array + { + $config_model = new \app\admin\controller\v1\SiteConfig; + $watermark_config = $config_model->getByGroupUniqueLabel('watermark'); + return [ + 'enalbed' => 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_font_family.value', ''), + 'size' => (float)data_get($watermark_config, 'watermark_text_size.value', 12), + 'color' => data_get($watermark_config, 'watermark_text_color.value', '#000000'), + 'position' => data_get($watermark_config, 'watermark_position.value', '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)data_get($watermark_config, 'watermark_opacity.value', 100), + ], + 'image_options' => [ + 'image' => data_get($watermark_config, 'watermark_image_value.value', ''), + 'width' => data_get($watermark_config, 'watermark_image_width.value', null), + 'height' => data_get($watermark_config, 'watermark_image_height.value', null), + 'position' => data_get($watermark_config, 'watermark_position.value', '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)data_get($watermark_config, 'watermark_opacity.value', 100), + ], + ]; + } + /** + * 计算文体水印偏移量 + * + * @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'); + } + } /** * 上传视频