提交其他文件
This commit is contained in:
293
app/common/service/qrcode/QrcodeService.php
Normal file
293
app/common/service/qrcode/QrcodeService.php
Normal file
@ -0,0 +1,293 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user