Files
2026-03-14 16:20:49 +08:00

293 lines
9.1 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace app\common\service\qrcode;
use think\facade\Request;
use Endroid\QrCode\QrCode;
use Endroid\QrCode\Writer\PngWriter;
use Endroid\QrCode\Color\Color;
use Endroid\QrCode\Encoding\Encoding;
use Endroid\QrCode\ErrorCorrectionLevel;
use Endroid\QrCode\RoundBlockSizeMode;
use Exception;
class QrcodeService
{
/**
* 生成二维码并保存到文件
* @param string $content 二维码内容
* @param string $filename 文件名(可选,不包含路径)
* @param array $options 配置选项
* @return array 返回结果信息
*/
public function saveImage(string $content, string $filename = '', array $options = []): array
{
try {
// 验证内容
if (empty($content)) {
throw new Exception('二维码内容不能为空');
}
// 默认配置
$defaultOptions = [
'size' => 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);
}
}
}