2026-02-24 11:28:12 +08:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace app\common\service;
|
|
|
|
|
|
|
|
|
|
use support\exception\BusinessException;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 二维码生成服务
|
|
|
|
|
*/
|
|
|
|
|
class QrCodeService
|
|
|
|
|
{
|
|
|
|
|
/**
|
|
|
|
|
* 生成物品二维码
|
|
|
|
|
*
|
|
|
|
|
* @param int $itemId 物品ID
|
|
|
|
|
* @param string $appUrl 应用访问地址
|
|
|
|
|
* @return string 二维码内容URL
|
|
|
|
|
*/
|
|
|
|
|
public function generateItemQrCode(int $itemId, string $appUrl): string
|
|
|
|
|
{
|
2026-02-24 17:35:58 +08:00
|
|
|
//return rtrim($appUrl, '/') . '/mobile/inv/claim/' . $itemId;
|
|
|
|
|
return "https://mweb-fdesk.hnxinyouhe.com/pages/scan/external-scan/" . $itemId;
|
2026-02-24 11:28:12 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 生成二维码图片并上传到 OSS
|
|
|
|
|
*
|
|
|
|
|
* @param int $itemId 物品ID
|
|
|
|
|
* @param string $content 二维码内容
|
|
|
|
|
* @param string $fileName 文件名
|
|
|
|
|
* @return array 返回OSS路径和完整URL
|
|
|
|
|
* @throws BusinessException
|
|
|
|
|
*/
|
|
|
|
|
public function generateAndUploadQrCode(int $itemId, string $content, string $fileName): array
|
|
|
|
|
{
|
|
|
|
|
// 使用 endroid/qr-code 库生成二维码
|
|
|
|
|
// 如果未安装,使用 API 方式生成
|
|
|
|
|
|
|
|
|
|
$tempPath = $this->generateQrCodeImage($content, $fileName);
|
|
|
|
|
|
|
|
|
|
if (!$tempPath) {
|
|
|
|
|
throw new BusinessException('二维码生成失败');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// 上传到 OSS
|
|
|
|
|
$ossService = new OssService();
|
|
|
|
|
$ossPath = 'qr/' . $fileName;
|
|
|
|
|
$ossUrl = $ossService->uploadImage($tempPath, $ossPath);
|
|
|
|
|
|
|
|
|
|
// 删除临时文件
|
|
|
|
|
if (file_exists($tempPath)) {
|
|
|
|
|
unlink($tempPath);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
'path' => $ossPath,
|
|
|
|
|
'url' => $ossUrl,
|
|
|
|
|
];
|
|
|
|
|
} catch (\Throwable $e) {
|
|
|
|
|
// 删除临时文件
|
|
|
|
|
if (file_exists($tempPath)) {
|
|
|
|
|
unlink($tempPath);
|
|
|
|
|
}
|
|
|
|
|
throw new BusinessException('二维码上传失败:' . $e->getMessage());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 生成二维码图片(使用 QR Server API 或本地库)
|
|
|
|
|
*
|
|
|
|
|
* @param string $content 二维码内容
|
|
|
|
|
* @param string $fileName 文件名
|
|
|
|
|
* @return string 临时文件路径,失败返回 false
|
|
|
|
|
*/
|
|
|
|
|
private function generateQrCodeImage(string $content, string $fileName)
|
|
|
|
|
{
|
|
|
|
|
$tempDir = runtime_path() . '/qr_temp/';
|
|
|
|
|
if (!is_dir($tempDir)) {
|
|
|
|
|
mkdir($tempDir, 0755, true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$tempPath = $tempDir . $fileName;
|
|
|
|
|
|
|
|
|
|
// 方式1: 使用 endroid/qr-code 库(推荐)
|
|
|
|
|
if (class_exists('\Endroid\QrCode\QrCode')) {
|
|
|
|
|
return $this->generateWithEndroid($content, $tempPath);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 方式2: 使用在线 API(备用方案)
|
|
|
|
|
return $this->generateWithApi($content, $tempPath);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 使用 endroid/qr-code 生成二维码
|
|
|
|
|
*/
|
|
|
|
|
private function generateWithEndroid(string $content, string $tempPath): string
|
|
|
|
|
{
|
|
|
|
|
try {
|
|
|
|
|
// 5.x 版本使用静态工厂方法
|
|
|
|
|
if (method_exists(\Endroid\QrCode\QrCode::class, 'create')) {
|
|
|
|
|
$qrCode = \Endroid\QrCode\QrCode::create($content)
|
|
|
|
|
->setSize(300)
|
|
|
|
|
->setMargin(10);
|
|
|
|
|
} else {
|
|
|
|
|
// 4.x 及以下版本
|
|
|
|
|
$qrCode = new \Endroid\QrCode\QrCode($content);
|
|
|
|
|
if (method_exists($qrCode, 'setSize')) {
|
|
|
|
|
$qrCode->setSize(300);
|
|
|
|
|
}
|
|
|
|
|
if (method_exists($qrCode, 'setMargin')) {
|
|
|
|
|
$qrCode->setMargin(10);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 获取 Writer
|
|
|
|
|
if (class_exists('\Endroid\QrCode\Writer\PngWriter')) {
|
|
|
|
|
$writer = new \Endroid\QrCode\Writer\PngWriter();
|
|
|
|
|
} else {
|
|
|
|
|
// 旧版本可能使用不同的命名空间
|
|
|
|
|
$writer = new \Endroid\QrCode\Writer();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$result = $writer->write($qrCode);
|
|
|
|
|
|
|
|
|
|
// 保存文件
|
|
|
|
|
if (method_exists($result, 'getString')) {
|
|
|
|
|
file_put_contents($tempPath, $result->getString());
|
|
|
|
|
} else {
|
|
|
|
|
// 更旧版本可能直接返回字符串
|
|
|
|
|
file_put_contents($tempPath, $result);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $tempPath;
|
|
|
|
|
} catch (\Throwable $e) {
|
|
|
|
|
// 如果 endroid 库使用失败,回退到在线 API
|
|
|
|
|
error_log('endroid/qr-code 生成失败: ' . $e->getMessage() . ',使用在线 API 备用方案');
|
|
|
|
|
return $this->generateWithApi($content, $tempPath);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 使用在线 API 生成二维码(备用方案)
|
|
|
|
|
*/
|
|
|
|
|
private function generateWithApi(string $content, string $tempPath): string
|
|
|
|
|
{
|
|
|
|
|
$apiUrl = 'https://api.qrserver.com/v1/create-qr-code/?';
|
|
|
|
|
$params = [
|
|
|
|
|
'size' => '300x300',
|
|
|
|
|
'data' => $content,
|
|
|
|
|
'margin' => '10',
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
$url = $apiUrl . http_build_query($params);
|
|
|
|
|
|
|
|
|
|
$ch = curl_init();
|
|
|
|
|
curl_setopt($ch, CURLOPT_URL, $url);
|
|
|
|
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
|
|
|
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
|
|
|
|
|
$imageData = curl_exec($ch);
|
|
|
|
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
|
|
|
curl_close($ch);
|
|
|
|
|
|
|
|
|
|
if ($httpCode !== 200 || !$imageData) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
file_put_contents($tempPath, $imageData);
|
|
|
|
|
return $tempPath;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 删除 OSS 上的二维码文件
|
|
|
|
|
*
|
|
|
|
|
* @param string $ossPath OSS路径
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
public function deleteQrCode(string $ossPath): bool
|
|
|
|
|
{
|
|
|
|
|
try {
|
|
|
|
|
$ossService = new OssService();
|
|
|
|
|
return $ossService->deleteFile($ossPath);
|
|
|
|
|
} catch (\Throwable $e) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|