PHP生成海报

/**
 * @description: 获取用户海报URL
 * @param {Request} $request
 * @return {*}
*/
public function getPosterUrl(Request $request)
{
    $tel = '15200000000';
    $posterUrl = self::createPoster($tel);
    $posterUrl = Config::get('app.web_url') . substr($posterUrl, 1);
    return $this->showSuccess($posterUrl);
}

/**
 * @description: 生成用户海报
 * @param {string} $tel
 * @return {*}
*/
private function createPoster(string $tel): string
{
    // ========== 1. 前置校验 ==========
    if (empty($tel)) {
        throw new \InvalidArgumentException('手机号不能为空');
    }
    // 海报保存目录(绝对路径,避免相对路径问题)
    $saveDir = app()->getRootPath() . 'public/poster/';
    // 确保目录存在且有写入权限
    if (!is_dir($saveDir)) {
        mkdir($saveDir, 0755, true);
    }
    if (!is_writable($saveDir)) {
        throw new \RuntimeException('海报目录无写入权限:' . $saveDir);
    }
    $savePath = $saveDir . $tel . '.jpg';
    // 已存在则直接返回(避免重复生成)
    if (file_exists($savePath)) {
        return '/poster/' . $tel . '.jpg';
    }

    // ========== 2. 加载背景图(增加异常处理) ==========
    try {
        $backgroundUrl = '/poster/background.jpg';
        if (empty($backgroundUrl)) {
            throw new \RuntimeException('未配置海报背景图');
        }
        // 处理远程背景图(如果是URL先下载)
        if (str_starts_with($backgroundUrl, 'http')) {
            $backgroundContent = file_get_contents($backgroundUrl);
            if (!$backgroundContent) {
                throw new \RuntimeException('下载背景图失败:' . $backgroundUrl);
            }
            $background = imagecreatefromstring($backgroundContent);
        } else {
            // 本地图片(转为绝对路径)
            $backgroundAbsPath = app()->getRootPath() . 'public' . $backgroundUrl;
            $backgroundInfo = getimagesize($backgroundAbsPath);
            if (!$backgroundInfo) {
                throw new \RuntimeException('背景图无效:' . $backgroundAbsPath);
            }
            $backgroundFun = 'imagecreatefrom' . image_type_to_extension($backgroundInfo[2], false);
            $background = $backgroundFun($backgroundAbsPath);
        }
        if (!$background) {
            throw new \RuntimeException('创建背景图资源失败');
        }
        // 一次性获取背景尺寸(避免重复调用)
        $bgW = imagesx($background);
        $bgH = imagesy($background);
    } catch (\Exception $e) {
        throw new \RuntimeException('加载背景图失败:' . $e->getMessage());
    }

    // ========== 3. 创建画布(优化初始化逻辑) ==========
    $imageRes = imagecreatetruecolor($bgW, $bgH);
    if (!$imageRes) {
        imagedestroy($background); // 提前释放资源
        throw new \RuntimeException('创建画布失败');
    }
    // 开启透明支持(避免黑色背景)
    imagesavealpha($imageRes, true);
    $transparentColor = imagecolorallocatealpha($imageRes, 0, 0, 0, 127);
    imagefill($imageRes, 0, 0, $transparentColor);

    // 复制背景图到画布(复用已获取的尺寸)
    imagecopyresampled($imageRes, $background, 0, 0, 0, 0, $bgW, $bgH, $bgW, $bgH);
    imagedestroy($background); // 背景图用完立即释放

    // ========== 4. 获取小程序二维码(增加超时/错误处理) ==========
    try {
        $accessToken = $this->getAccessToken();
        if (empty($accessToken)) {
            throw new \RuntimeException('获取微信access_token失败');
        }
        $url = 'https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=' . $accessToken;
        $data = [
            'scene' => 'form_id=' . (request()->user_id ?? ''),
            'page' => 'pages/index/index',
            'check_path' => false,
            'env_version' => 'release',
        ];
        // 初始化客户端(增加超时控制)
        $client = new Client(['timeout' => 10]); // 10秒超时
        $response = $client->request('POST', $url, [
            'json' => $data,
            'headers' => ['Content-Type' => 'application/json'],
        ]);
        $content = $response->getBody()->getContents();

        // 校验二维码是否有效(微信返回错误时是JSON,不是图片)
        $json = json_decode($content, true);
        if (json_last_error() === JSON_ERROR_NONE && isset($json['errcode'])) {
            throw new \RuntimeException('获取二维码失败:' . $json['errmsg'] . '(code:' . $json['errcode'] . ')');
        }

        $qrcode = imagecreatefromstring($content);
        if (!$qrcode) {
            throw new \RuntimeException('创建二维码图像资源失败');
        }
        // 二维码尺寸(复用)
        $codeW = imagesx($qrcode);
        $codeH = imagesy($qrcode);
    } catch (\Exception $e) {
        imagedestroy($imageRes); // 释放画布资源
        throw new \RuntimeException('获取二维码失败:' . $e->getMessage());
    }

    // ========== 5. 合成二维码到画布(优化坐标计算) ==========
    $codeX = $bgW - $codeW - 160; // 右间距160
    $codeY = $bgH - $codeH - 400; // 下间距400
    // 确保二维码坐标不超出画布
    $codeX = max(0, $codeX);
    $codeY = max(0, $codeY);
    imagecopyresampled($imageRes, $qrcode, $codeX, $codeY, 0, 0, $codeW, $codeH, $codeW, $codeH);
    imagedestroy($qrcode); // 二维码用完立即释放

    // ========== 6. 保存图片(优化压缩/错误处理) ==========
    $success = imagejpeg($imageRes, $savePath, 90);
    imagedestroy($imageRes); // 最后释放画布
    if (!$success) {
        throw new \RuntimeException('保存海报失败:' . $savePath);
    }

    // ========== 7. 返回相对路径 ==========
    return '/poster/' . $tel . '.jpg';
}