200, // 图片尺寸 'margin' => 10, // 边距 'foreground_color' => '#000000', // 前景色(黑色) 'background_color' => '#FFFFFF', // 背景色(白色) 'error_correction' => 'high', // 容错级别:low, medium, quartile, high 'encoding' => 'UTF-8', // 编码 'round_block_size' => false, // 圆角模块 'validate_result' => false, // 验证结果 ]; // 合并配置 $config = array_merge($defaultOptions, $options); // 生成文件名 if (empty($filename)) { $filename = $this->generateFilename($content); } // 确保是PNG格式 $filename = $this->ensurePngExtension($filename); // 生成二维码 $imageData = $this->generateQrCode($content, $config); // 保存文件 $saveInfo = $this->saveToFile($imageData, $filename); return [ 'file_url' => $saveInfo['path'] ]; } catch (Exception $e) { return [ 'success' => false, 'message' => '二维码生成失败', 'error' => $e->getMessage(), 'content' => $content ]; } } /** * 生成二维码图片数据 */ private function generateQrCode(string $content, array $config): string { // 创建二维码对象 $qrCode = QrCode::create($content) ->setEncoding(new Encoding($config['encoding'])) ->setErrorCorrectionLevel($this->getErrorCorrectionLevel($config['error_correction'])) ->setSize($config['size']) ->setMargin($config['margin']) ->setRoundBlockSizeMode($config['round_block_size'] ? RoundBlockSizeMode::Margin : RoundBlockSizeMode::None) ->setForegroundColor(new Color( $this->hexToR($config['foreground_color']), $this->hexToG($config['foreground_color']), $this->hexToB($config['foreground_color']) )) ->setBackgroundColor(new Color( $this->hexToR($config['background_color']), $this->hexToG($config['background_color']), $this->hexToB($config['background_color']) )); // 创建写入器 $writer = new PngWriter(); // 生成二维码 $result = $writer->write($qrCode); // 如果需要验证 if ($config['validate_result']) { $reader = new \Endroid\QrCode\Reader\Reader(); try { $reader->read($result->getString()); } catch (\Exception $e) { throw new Exception('二维码验证失败: ' . $e->getMessage()); } } return $result->getString(); } /** * 保存图片到文件 */ private function saveToFile(string $imageData, string $filename): array { // 创建存储目录(按日期分类) $dateDir = date('Ymd'); $saveDir = 'uploads/qrcode/' . $dateDir . '/'; $relativePath = $saveDir . $filename; // 获取完整路径 $rootPath = app()->getRootPath(); $fullPath = $rootPath . 'public/' . $relativePath; // 确保目录存在 $dirPath = dirname($fullPath); if (!is_dir($dirPath)) { if (!mkdir($dirPath, 0755, true)) { throw new Exception('无法创建目录: ' . $dirPath); } } // 保存文件 if (file_put_contents($fullPath, $imageData) === false) { throw new Exception('无法保存文件到: ' . $fullPath); } // 验证文件是否保存成功 if (!file_exists($fullPath) || filesize($fullPath) === 0) { throw new Exception('文件保存失败或文件大小为0'); } // 生成可访问的URL $domain = Request::domain(); $fileUrl = $domain . '/' . $relativePath; return [ 'filename' => $filename, 'path' => $relativePath, 'full_path' => $fullPath, 'url' => $fileUrl ]; } /** * 生成唯一的文件名 */ private function generateFilename(string $content): string { $timestamp = time(); $hash = substr(md5($content . $timestamp), 0, 8); return 'qrcode_' . date('YmdHis') . '_' . $hash . '.png'; } /** * 确保文件扩展名为.png */ private function ensurePngExtension(string $filename): string { $pathinfo = pathinfo($filename); // 如果没有扩展名或扩展名不是png,则添加.png if (!isset($pathinfo['extension']) || strtolower($pathinfo['extension']) !== 'png') { $filename = ($pathinfo['filename'] ?? 'qrcode') . '.png'; } return $filename; } /** * 获取错误纠正级别 */ private function getErrorCorrectionLevel(string $level): ErrorCorrectionLevel { $levels = [ 'low' => ErrorCorrectionLevel::Low, 'medium' => ErrorCorrectionLevel::Medium, 'quartile' => ErrorCorrectionLevel::Quartile, 'high' => ErrorCorrectionLevel::High, ]; return $levels[$level] ?? ErrorCorrectionLevel::High; } /** * 从十六进制颜色获取R值 */ private function hexToR(string $hex): int { $hex = ltrim($hex, '#'); if (strlen($hex) == 3) { $hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2]; } return hexdec(substr($hex, 0, 2)); } /** * 从十六进制颜色获取G值 */ private function hexToG(string $hex): int { $hex = ltrim($hex, '#'); if (strlen($hex) == 3) { $hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2]; } return hexdec(substr($hex, 2, 2)); } /** * 从十六进制颜色获取B值 */ private function hexToB(string $hex): int { $hex = ltrim($hex, '#'); if (strlen($hex) == 3) { $hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2]; } return hexdec(substr($hex, 4, 2)); } /** * 生成二维码并返回Base64编码 * @param string $content 二维码内容 * @param array $options 配置选项 * @return string Base64编码的图片 */ public function generateBase64(string $content, array $options = []): string { try { $defaultOptions = [ 'size' => 300, 'margin' => 10, 'foreground_color' => '#000000', 'background_color' => '#FFFFFF', 'error_correction' => 'high', ]; $config = array_merge($defaultOptions, $options); $imageData = $this->generateQrCode($content, $config); return 'data:image/png;base64,' . base64_encode($imageData); } catch (Exception $e) { throw new Exception('生成Base64二维码失败: ' . $e->getMessage()); } } /** * 直接生成并输出二维码图片 * @param string $content 二维码内容 * @param array $options 配置选项 * @return \think\Response */ public function generateResponse(string $content, array $options = []) { try { $defaultOptions = [ 'size' => 300, 'margin' => 10, 'foreground_color' => '#000000', 'background_color' => '#FFFFFF', 'error_correction' => 'high', ]; $config = array_merge($defaultOptions, $options); $imageData = $this->generateQrCode($content, $config); return response($imageData) ->contentType('image/png') ->header('Cache-Control', 'public, max-age=3600'); } catch (Exception $e) { return response('二维码生成失败: ' . $e->getMessage()) ->contentType('text/plain') ->code(500); } } }