<?php

namespace app\lib\deploy;

use app\lib\DeployInterface;
use app\lib\client\Aliyun as AliyunClient;
use app\lib\client\AliyunNew as AliyunNewClient;
use app\lib\client\AliyunOSS;
use Exception;

class aliyun implements DeployInterface
{
    private $logger;
    private $AccessKeyId;
    private $AccessKeySecret;
    private $proxy;

    public function __construct($config)
    {
        $this->AccessKeyId = $config['AccessKeyId'];
        $this->AccessKeySecret = $config['AccessKeySecret'];
        $this->proxy = isset($config['proxy']) ? $config['proxy'] == 1 : false;
    }

    public function check()
    {
        if (empty($this->AccessKeyId) || empty($this->AccessKeySecret)) throw new Exception('必填参数不能为空');
        $client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, 'cas.aliyuncs.com', '2020-04-07', $this->proxy);
        $param = ['Action' => 'ListUserCertificateOrder'];
        $client->request($param);
        return true;
    }

    public function deploy($fullchain, $privatekey, $config, &$info)
    {
        if ($config['product'] == 'api') {
            $this->deploy_api($fullchain, $privatekey, $config);
        } elseif ($config['product'] == 'vod') {
            $this->deploy_vod($fullchain, $privatekey, $config);
        } elseif ($config['product'] == 'fc') {
            $this->deploy_fc($fullchain, $privatekey, $config);
        } elseif ($config['product'] == 'fc2') {
            $this->deploy_fc2($fullchain, $privatekey, $config);
        } else {
            [$cert_id, $cert_name] = $this->get_cert_id($fullchain, $privatekey, $config);
            if (!$cert_id) throw new Exception('证书ID获取失败');
            if ($config['product'] == 'cdn') {
                $this->deploy_cdn($cert_id, $cert_name, $config);
            } elseif ($config['product'] == 'dcdn') {
                $this->deploy_dcdn($cert_id, $cert_name, $config);
            } elseif ($config['product'] == 'esa') {
                $this->deploy_esa($cert_id, $cert_name, $config);
            } elseif ($config['product'] == 'oss') {
                $this->deploy_oss($cert_id, $config);
            } elseif ($config['product'] == 'waf') {
                $this->deploy_waf($cert_id, $config);
            } elseif ($config['product'] == 'wafres') {
                $this->deploy_waf_res($cert_id, $config);
            } elseif ($config['product'] == 'waf2') {
                $this->deploy_waf2($cert_id, $config);
            } elseif ($config['product'] == 'ddoscoo') {
                $this->deploy_ddoscoo($cert_id, $config);
            } elseif ($config['product'] == 'live') {
                $this->deploy_live($cert_id, $cert_name, $config);
            } elseif ($config['product'] == 'clb') {
                $this->deploy_clb($cert_id, $cert_name, $config);
            } elseif ($config['product'] == 'alb') {
                $this->deploy_alb($cert_id, $config);
            } elseif ($config['product'] == 'nlb') {
                $this->deploy_nlb($cert_id, $config);
            } elseif ($config['product'] == 'esa_saas') {
                $this->deploy_esa_saas($cert_id, $config);
            } elseif ($config['product'] == 'ga') {
                $this->deploy_ga($cert_id, $config);
            } elseif ($config['product'] == 'upload') {
            } else {
                throw new Exception('未知的产品类型');
            }
            $info['cert_id'] = $cert_id;
            $info['cert_name'] = $cert_name;
        }
    }

    private function get_cert_id($fullchain, $privatekey, $config)
    {
        $certInfo = openssl_x509_parse($fullchain, true);
        if (!$certInfo) throw new Exception('证书解析失败');
        $cert_name = str_replace('*.', '', $certInfo['subject']['CN']) . '-' . $certInfo['validFrom_time_t'];
        $serial_no = strtolower($certInfo['serialNumberHex']);

        if ($config['region'] == 'ap-southeast-1') {
            $endpoint = 'cas.ap-southeast-1.aliyuncs.com';
        } else {
            $endpoint = 'cas.aliyuncs.com';
        }

        $client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, $endpoint, '2020-04-07', $this->proxy);
        $param = [
            'Action' => 'ListUserCertificateOrder',
            'Keyword' => $certInfo['subject']['CN'],
            'OrderType' => 'CERT',
        ];
        try {
            $data = $client->request($param);
        } catch (Exception $e) {
            throw new Exception('查询证书列表失败：' . $e->getMessage());
        }
        $cert_id = null;
        if ($data['TotalCount'] > 0 && !empty($data['CertificateOrderList'])) {
            foreach ($data['CertificateOrderList'] as $cert) {
                if (strtolower($cert['SerialNo']) == $serial_no || strpos(strtolower($cert['SerialNo']), $serial_no) !== false) {
                    $cert_id = $cert['CertificateId'];
                    $cert_name = $cert['Name'];
                    break;
                }
            }
        }
        if ($cert_id) {
            $this->log('找到已上传的证书 CertId=' . $cert_id);
            return [$cert_id, $cert_name];
        }

        $param = [
            'Action' => 'UploadUserCertificate',
            'Name' => $cert_name,
            'Cert' => $fullchain,
            'Key' => $privatekey,
        ];
        try {
            $data = $client->request($param);
        } catch (Exception $e) {
            throw new Exception('上传证书失败：' . $e->getMessage());
        }
        $this->log('证书上传成功！CertId=' . $data['CertId']);
        usleep(500000);
        return [$data['CertId'], $cert_name];
    }

    private function deploy_cdn($cert_id, $cert_name, $config)
    {
        if (empty($config['domain'])) throw new Exception('CDN绑定域名不能为空');
        $client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, 'cdn.aliyuncs.com', '2018-05-10', $this->proxy);
        foreach (explode(',', $config['domain']) as $domain) {
            $param = [
                'Action' => 'SetCdnDomainSSLCertificate',
                'DomainName' => $domain,
                'CertName' => $cert_name,
                'CertType' => 'cas',
                'SSLProtocol' => 'on',
                'CertId' => $cert_id,
            ];
            $client->request($param);
            $this->log('CDN域名 ' . $domain . ' 部署证书成功！');
        }
    }

    private function deploy_dcdn($cert_id, $cert_name, $config)
    {
        if (empty($config['domain'])) throw new Exception('DCDN绑定域名不能为空');
        $client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, 'dcdn.aliyuncs.com', '2018-01-15', $this->proxy);
        foreach (explode(',', $config['domain']) as $domain) {
            $param = [
                'Action' => 'SetDcdnDomainSSLCertificate',
                'DomainName' => $domain,
                'CertName' => $cert_name,
                'CertType' => 'cas',
                'SSLProtocol' => 'on',
                'CertId' => $cert_id,
            ];
            $client->request($param);
            $this->log('DCDN域名 ' . $domain . ' 部署证书成功！');
        }
    }

    private function deploy_esa_saas($cas_id, $config)
    {
        $sitename = $config['esa_sitename'];
        $saas_sitename = $config['esa_saas_sitename'];
        if (empty($sitename)) throw new Exception('ESA站点名称不能为空');
        if (empty($saas_sitename)) throw new Exception('ESA SAAS域名不能为空');

        if ($config['region'] == 'ap-southeast-1') {
            $endpoint = 'esa.ap-southeast-1.aliyuncs.com';
        } else {
            $endpoint = 'esa.cn-hangzhou.aliyuncs.com';
        }

        $client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, $endpoint, '2024-09-10');
        $param = [
            'Action' => 'ListSites',
            'SiteName' => $sitename,
            'SiteSearchType' => 'exact',
        ];
        try {
            $data = $client->request($param, 'GET');
        } catch (Exception $e) {
            throw new Exception('查询ESA站点列表失败：' . $e->getMessage());
        }
        if ($data['TotalCount'] == 0) throw new Exception('ESA站点 ' . $sitename . ' 不存在');
        $this->log('成功查询到' . $data['TotalCount'] . '个ESA站点');
        $site_id = $data['Sites'][0]['SiteId'];
        // 查询对应的saas域名
        $param = [
            'Action' => 'ListCustomHostnames',
            'SiteName' => $saas_sitename,
            'SiteId' => $site_id,
            'SiteSearchType' => 'exact',
        ];
        try {
            $saas_data = $client->request($param, 'GET');
        } catch (Exception $e) {
            throw new Exception('查询ESA saas域名失败：' . $e->getMessage());
        }
        if ($saas_data['TotalCount'] == 0) throw new Exception('ESA saas站点 ' . $saas_sitename . ' 不存在');
        $saas_hostname_id = $saas_data['Hostnames'][0]['HostnameId'];

        $param = [
            'Action' => 'UpdateCustomHostname',
            'HostnameId' => $saas_hostname_id,
            'SslFlag' => 'on',
            'CertType' => 'cas',
            'CasId' => $cas_id,
            'CasRegion' => $config['region'],
        ];
        $this->log('ESA SAAS站点部署参数 ' . json_encode($param));
        try {
            $saas_deploy_result = $client->request($param);
            $this->log('ESA SAAS站点部署结果 ' . json_encode($saas_deploy_result));
        } catch (Exception $e) {
            throw new Exception('部署失败：' . $e->getMessage());
        }
        $this->log('ESA SAAS站点 ' . $saas_sitename . ' 证书添加成功！');
    }

    private function deploy_esa($cas_id, $cert_name, $config)
    {
        $sitename = $config['esa_sitename'];
        if (empty($sitename)) throw new Exception('ESA站点名称不能为空');

        if ($config['region'] == 'ap-southeast-1') {
            $endpoint = 'esa.ap-southeast-1.aliyuncs.com';
        } else {
            $endpoint = 'esa.cn-hangzhou.aliyuncs.com';
        }

        $client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, $endpoint, '2024-09-10');
        $param = [
            'Action' => 'ListSites',
            'SiteName' => $sitename,
            'SiteSearchType' => 'exact',
        ];
        try {
            $data = $client->request($param, 'GET');
        } catch (Exception $e) {
            throw new Exception('查询ESA站点列表失败：' . $e->getMessage());
        }
        if ($data['TotalCount'] == 0) throw new Exception('ESA站点 ' . $sitename . ' 不存在');
        $this->log('成功查询到' . $data['TotalCount'] . '个ESA站点');
        $site_id = $data['Sites'][0]['SiteId'];

        $param = [
            'Action' => 'ListCertificates',
            'SiteId' => $site_id,
        ];
        try {
            $data = $client->request($param, 'GET');
        } catch (Exception $e) {
            throw new Exception('查询ESA站点' . $sitename . '证书列表失败：' . $e->getMessage());
        }
        $this->log('ESA站点 ' . $sitename . ' 查询到' . $data['TotalCount'] . '个SSL证书');

        $exist_cert = null;
        $oldest_cert = null;
        if ($data['TotalCount'] > 0) {
            foreach ($data['Result'] as $cert) {
                if ($cert['Type'] == 'free') continue;
                $domains = explode(',', $cert['SAN']);
                $flag = true;
                foreach ($domains as $domain) {
                    if (!in_array($domain, $config['domainList'])) {
                        $flag = false;
                        break;
                    }
                }
                if ($flag) {
                    $exist_cert = $cert;
                    break;
                }
                if (!$oldest_cert) {
                    $oldest_cert = $cert;
                } elseif (strtotime($cert['CreateTime']) < strtotime($oldest_cert['CreateTime'])) {
                    $oldest_cert = $cert;
                }
            }
        }

        if (!$exist_cert) { //新增证书时，若配额已满，则删除最旧的证书
            $param = [
                'Action' => 'ListInstanceQuotasWithUsage',
                'SiteId' => $site_id,
                'QuotaNames' => 'customHttpCert',
            ];
            try {
                $data = $client->request($param, 'GET');
            } catch (Exception $e) {
                throw new Exception('查询ESA站点证书配额失败：' . $e->getMessage());
            }
            if (!empty($data['Quotas']) && intval($data['Quotas'][0]['Usage']) >= intval($data['Quotas'][0]['QuotaValue']) && $oldest_cert) {
                $param = [
                    'Action' => 'DeleteCertificate',
                    'SiteId' => $site_id,
                    'Id' => $oldest_cert['Id'],
                ];
                try {
                    $client->request($param, 'GET');
                    $this->log('ESA站点 ' . $sitename . ' 删除证书 ' . $oldest_cert['Name'] . ' 成功');
                } catch (Exception $e) {
                    throw new Exception('ESA站点 ' . $sitename . ' 删除证书' . $oldest_cert['Name'] . '失败：' . $e->getMessage());
                }
            }
        }

        $param = [
            'Action' => 'SetCertificate',
            'SiteId' => $site_id,
            'Type' => 'cas',
            'CasId' => $cas_id,
            'Name' => $cert_name,
            'Region' => $config['region'],
        ];

        if ($exist_cert) {
            $param['Id'] = $exist_cert['Id'];

            if (isset($exist_cert['CasId']) && $exist_cert['CasId'] == $cas_id) {
                $this->log('ESA站点 ' . $sitename . ' 证书已配置，无需重复操作');
                return;
            }
        }

        $client->request($param);

        if ($exist_cert) {
            $this->log('ESA站点 ' . $sitename . ' 证书 ' . $exist_cert['Name'] . ' 更新成功');
        } else {
            $this->log('ESA站点 ' . $sitename . ' 证书添加成功！');
        }
    }

    private function deploy_oss($cert_id, $config)
    {
        if (empty($config['domain'])) throw new Exception('OSS绑定域名不能为空');
        if (empty($config['oss_endpoint'])) throw new Exception('OSS Endpoint不能为空');
        if (empty($config['oss_bucket'])) throw new Exception('OSS Bucket不能为空');
        $client = new AliyunOSS($this->AccessKeyId, $this->AccessKeySecret, $config['oss_endpoint']);
        foreach (explode(',', $config['domain']) as $domain) {
            if (empty($domain)) continue;
            $client->addBucketCnameCert($config['oss_bucket'], $domain, $cert_id . '-cn-hangzhou');
            $this->log('OSS域名 ' . $domain . ' 部署证书成功！');
        }
    }

    private function deploy_waf($cert_id, $config)
    {
        if (empty($config['domain'])) throw new Exception('WAF绑定域名不能为空');

        if ($config['region'] == 'ap-southeast-1') {
            $cert_id .= '-ap-southeast-1';
        } else {
            $cert_id .= '-cn-hangzhou';
        }

        $endpoint = 'wafopenapi.' . $config['region'] . '.aliyuncs.com';

        $client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, $endpoint, '2021-10-01', $this->proxy);

        $param = [
            'Action' => 'DescribeInstance',
            'RegionId' => $config['region'],
        ];
        try {
            $data = $client->request($param, 'GET');
        } catch (Exception $e) {
            throw new Exception('获取WAF实例详情失败：' . $e->getMessage());
        }
        if (empty($data['InstanceId'])) throw new Exception('当前账号未找到WAF实例');
        $instance_id = $data['InstanceId'];
        $this->log('获取WAF实例ID成功 InstanceId=' . $instance_id);

        foreach (explode(',', $config['domain']) as $domain) {
            $param = [
                'Action' => 'DescribeDomainDetail',
                'InstanceId' => $instance_id,
                'Domain' => $domain,
                'RegionId' => $config['region'],
            ];
            try {
                $data = $client->request($param, 'GET');
            } catch (Exception $e) {
                throw new Exception('查询CNAME接入详情失败：' . $e->getMessage());
            }
            if (!isset($data['Listen'])) {
                throw new Exception('没有找到' . $domain . '监听器');
            }

            if (isset($data['Listen']['CertId'])) {
                $old_cert_id = $data['Listen']['CertId'];
                if (!empty($old_cert_id) && $old_cert_id == $cert_id) {
                    $this->log('WAF域名 ' . $domain . ' 证书已配置，无需重复操作');
                    return;
                }
            }

            $data['Listen']['CertId'] = $cert_id;
            if (empty($data['Listen']['HttpsPorts'])) {
                $data['Listen']['HttpsPorts'] = [443];
                $data['Listen']['TLSVersion'] = 'tlsv1.1';
                $data['Listen']['EnableTLSv3'] = true;
                $data['Listen']['CipherSuite'] = 1;
            }
            if (count($data['Redirect']['BackendPorts']) == 1 && $data['Redirect']['BackendPorts'][0]['Protocol'] == 'http') {
                $data['Redirect']['BackendPorts'][] = [
                    'ListenPort' => 443,
                    'Protocol' => 'https',
                    'BackendPort' => $data['Redirect']['BackendPorts'][0]['BackendPort'],
                ];
                $data['Redirect']['FocusHttpBackend'] = true;
            }
            $data['Redirect']['Backends'] = $data['Redirect']['AllBackends'];
            $param = [
                'Action' => 'ModifyDomain',
                'InstanceId' => $instance_id,
                'Domain' => $domain,
                'Listen' => json_encode($data['Listen']),
                'Redirect' => json_encode($data['Redirect']),
                'RegionId' => $config['region'],
            ];
            $data = $client->request($param);

            $this->log('WAF域名 ' . $domain . ' 部署证书成功！');
        }
    }

    private function deploy_waf_res($cert_id, $config)
    {
        if (empty($config['waf_resource_id'])) throw new Exception('云产品防护对象ID不能为空');
        $deploy_type = isset($config['deploy_type']) ? intval($config['deploy_type']) : 0;

        if ($config['region'] == 'ap-southeast-1') {
            $cert_id .= '-ap-southeast-1';
        } else {
            $cert_id .= '-cn-hangzhou';
        }

        $endpoint = 'wafopenapi.' . $config['region'] . '.aliyuncs.com';

        $client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, $endpoint, '2021-10-01', $this->proxy);

        $param = [
            'Action' => 'DescribeInstance',
            'RegionId' => $config['region'],
        ];
        try {
            $data = $client->request($param, 'GET');
        } catch (Exception $e) {
            throw new Exception('获取WAF实例详情失败：' . $e->getMessage());
        }
        if (empty($data['InstanceId'])) throw new Exception('当前账号未找到WAF实例');
        $instance_id = $data['InstanceId'];
        $this->log('获取WAF实例ID成功 InstanceId=' . $instance_id);

        foreach (explode(',', $config['waf_resource_id']) as $waf_resource_id) {
            $parts = explode('-', $waf_resource_id);
            $resource_instance_id = $parts[count($parts) - 3] ?? '';
            if (empty($resource_instance_id)) {
                throw new Exception('ResourceInstanceId解析失败：' . $waf_resource_id);
            }
            $param = [
                'Action' => 'DescribeCloudResourceList',
                'InstanceId' => $instance_id,
                'CloudResourceId' => $waf_resource_id,
                'RegionId' => $config['region'],
            ];
            try {
                $data = $client->request($param, 'GET');
            } catch (Exception $e) {
                throw new Exception('查询云产品接入WAF配置失败：' . $e->getMessage());
            }
            if (empty($data['CloudResourceList'])) {
                throw new Exception('WAF云产品接入实例不存在：' . $waf_resource_id);
            }

            if ($deploy_type == 0) {
                $param = [
                    'Action' => 'ModifyCloudResourceDefaultCert',
                    'InstanceId' => $instance_id,
                    'CloudResourceId' => $waf_resource_id,
                    'CertId' => $cert_id,
                    'RegionId' => $config['region'],
                ];
                $client->request($param);
                $this->log('WAF云产品防护对象 ' . $waf_resource_id . ' 部署默认证书成功！');
            } else {
                $param = [
                    'Action' => 'CreateCloudResourceExtensionCert',
                    'InstanceId' => $instance_id,
                    'CloudResourceId' => $waf_resource_id,
                    'CertId' => $cert_id,
                    'RegionId' => $config['region'],
                ];
                $client->request($param);
                $this->log('WAF云产品防护对象 ' . $waf_resource_id . ' 部署扩展证书成功！');

                $this->clean_waf_res_expired_certs($client, $instance_id, $resource_instance_id, $waf_resource_id, $config['region']);
            }
        }
    }

    private function clean_waf_res_expired_certs($client, $instance_id, $resource_instance_id, $waf_resource_id, $region)
    {
        $param = [
            'Action' => 'DescribeResourceInstanceCerts',
            'InstanceId' => $instance_id,
            'ResourceInstanceId' => $resource_instance_id,
            'RegionId' => $region,
        ];
        try {
            $data = $client->request($param, 'GET');
        } catch (Exception $e) {
            $this->log('查询扩展证书列表失败：' . $e->getMessage());
            return;
        }
        if (empty($data['Certs'])) return;

        $now = time();
        foreach ($data['Certs'] as $cert) {
            if (empty($cert['CertIdentifier']) || empty($cert['AfterDate'])) continue;
            $expire_time = strtotime($cert['AfterDate']);
            if ($expire_time !== false && $expire_time < $now) {
                $param = [
                    'Action' => 'DeleteCloudResourceExtensionCert',
                    'InstanceId' => $instance_id,
                    'CloudResourceId' => $waf_resource_id,
                    'CertId' => $cert['CertIdentifier'],
                    'RegionId' => $region,
                ];
                try {
                    $client->request($param);
                    $this->log('已删除过期扩展证书：' . $cert['CertIdentifier']);
                } catch (Exception $e) {
                    $this->log('删除过期扩展证书失败：' . $cert['CertIdentifier'] . ' ' . $e->getMessage());
                }
            }
        }
    }

    private function deploy_waf2($cert_id, $config)
    {
        if (empty($config['domain'])) throw new Exception('WAF绑定域名不能为空');

        $endpoint = 'wafopenapi.' . $config['region'] . '.aliyuncs.com';

        $client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, $endpoint, '2019-09-10', $this->proxy);

        $param = [
            'Action' => 'DescribeInstanceInfo',
            'RegionId' => $config['region'],
        ];
        try {
            $data = $client->request($param, 'GET');
        } catch (Exception $e) {
            throw new Exception('获取WAF实例详情失败：' . $e->getMessage());
        }
        if (empty($data['InstanceInfo']['InstanceId'])) throw new Exception('当前账号未找到WAF实例');
        $instance_id = $data['InstanceInfo']['InstanceId'];
        $this->log('获取WAF实例ID成功 InstanceId=' . $instance_id);

        foreach (explode(',', $config['domain']) as $domain) {
            $param = [
                'Action' => 'CreateCertificateByCertificateId',
                'InstanceId' => $instance_id,
                'Domain' => $domain,
                'CertificateId' => $cert_id,
            ];
            $client->request($param);

            $this->log('WAF域名 ' . $domain . ' 部署证书成功！');
        }
    }

    private function deploy_api($fullchain, $privatekey, $config)
    {
        $groupid = $config['api_groupid'];
        if (empty($groupid)) throw new Exception('API分组ID不能为空');
        if (empty($config['domain'])) throw new Exception('API分组绑定域名不能为空');

        $certInfo = openssl_x509_parse($fullchain, true);
        if (!$certInfo) throw new Exception('证书解析失败');
        $cert_name = str_replace('*.', '', $certInfo['subject']['CN']) . '-' . $certInfo['validFrom_time_t'];

        $endpoint = 'apigateway.' . $config['regionid'] . '.aliyuncs.com';

        $client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, $endpoint, '2016-07-14', $this->proxy);

        foreach (explode(',', $config['domain']) as $domain) {
            $param = [
                'Action' => 'SetDomainCertificate',
                'GroupId' => $groupid,
                'DomainName' => $domain,
                'CertificateName' => $cert_name,
                'CertificateBody' => $fullchain,
                'CertificatePrivateKey' => $privatekey,
            ];
            $client->request($param);

            $this->log('API网关域名 ' . $domain . ' 部署证书成功！');
        }
    }

    private function deploy_ddoscoo($cert_id, $config)
    {
        if (empty($config['domain'])) throw new Exception('绑定域名不能为空');

        $endpoint = 'ddoscoo.' . $config['region'] . '.aliyuncs.com';

        $client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, $endpoint, '2020-01-01', $this->proxy);

        foreach (explode(',', $config['domain']) as $domain) {
            $param = [
                'Action' => 'AssociateWebCert',
                'Domain' => $domain,
                'CertId' => $cert_id,
            ];
            $client->request($param);

            $this->log('DDoS高防域名 ' . $domain . ' 部署证书成功！');
        }
    }

    private function deploy_live($cert_id, $cert_name, $config)
    {
        if (empty($config['domain'])) throw new Exception('视频直播绑定域名不能为空');
        $client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, 'live.aliyuncs.com', '2016-11-01', $this->proxy);
        foreach (explode(',', $config['domain']) as $domain) {
            $param = [
                'Action' => 'SetLiveDomainCertificate',
                'DomainName' => $domain,
                'CertName' => $cert_name,
                'CertType' => 'cas',
                'SSLProtocol' => 'on',
                'CertId' => $cert_id,
            ];
            $client->request($param);
            $this->log('设置视频直播域名 ' . $domain . ' 证书成功！');
        }
    }

    private function deploy_vod($fullchain, $privatekey, $config)
    {
        if (empty($config['domain'])) throw new Exception('视频点播绑定域名不能为空');
        $client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, 'vod.cn-shanghai.aliyuncs.com', '2017-03-21', $this->proxy);
        foreach (explode(',', $config['domain']) as $domain) {
            $param = [
                'Action' => 'SetVodDomainCertificate',
                'DomainName' => $domain,
                'SSLProtocol' => 'on',
                'SSLPub' => $fullchain,
                'SSLPri' => $privatekey,
            ];
            $client->request($param);
            $this->log('视频点播域名 ' . $domain . ' 部署证书成功！');
        }
    }

    private function deploy_fc($fullchain, $privatekey, $config)
    {
        $fc_cname = $config['fc_cname'];
        if (empty($config['domain'])) throw new Exception('函数计算域名不能为空');
        if (empty($fc_cname)) throw new Exception('域名CNAME地址不能为空');

        $certInfo = openssl_x509_parse($fullchain, true);
        if (!$certInfo) throw new Exception('证书解析失败');
        $cert_name = str_replace('*.', '', $certInfo['subject']['CN']) . '-' . $certInfo['validFrom_time_t'];

        $client = new AliyunNewClient($this->AccessKeyId, $this->AccessKeySecret, $fc_cname, '2023-03-30', $this->proxy);

        foreach (explode(',', $config['domain']) as $domain) {
            try {
                $data = $client->request('GET', 'GetCustomDomain', '/2023-03-30/custom-domains/' . $domain);
            } catch (Exception $e) {
                throw new Exception('获取绑定域名信息失败：' . $e->getMessage());
            }
            $this->log('获取函数计算绑定域名信息成功');

            if (isset($data['certConfig']['certificate']) && $data['certConfig']['certificate'] == $fullchain) {
                $this->log('函数计算域名 ' . $domain . ' 证书已配置，无需重复操作');
                return;
            }

            if ($data['protocol'] == 'HTTP') $data['protocol'] = 'HTTP,HTTPS';
            $data['certConfig']['certName'] = $cert_name;
            $data['certConfig']['certificate'] = $fullchain;
            $data['certConfig']['privateKey'] = $privatekey;

            $param = [
                'authConfig' => $data['authConfig'],
                'certConfig' => $data['certConfig'],
                'protocol' => $data['protocol'],
                'routeConfig' => $data['routeConfig'],
                'tlsConfig' => $data['tlsConfig'],
                'wafConfig' => $data['wafConfig'],
            ];
            $client->request('PUT', 'UpdateCustomDomain', '/2023-03-30/custom-domains/' . $domain, $param);

            $this->log('函数计算域名 ' . $domain . ' 部署证书成功！');
        }
    }

    private function deploy_fc2($fullchain, $privatekey, $config)
    {
        $fc_cname = $config['fc_cname'];
        if (empty($config['domain'])) throw new Exception('函数计算域名不能为空');
        if (empty($fc_cname)) throw new Exception('域名CNAME地址不能为空');

        $certInfo = openssl_x509_parse($fullchain, true);
        if (!$certInfo) throw new Exception('证书解析失败');
        $cert_name = str_replace('*.', '', $certInfo['subject']['CN']) . '-' . $certInfo['validFrom_time_t'];

        $client = new AliyunNewClient($this->AccessKeyId, $this->AccessKeySecret, $fc_cname, '2021-04-06', $this->proxy);

        foreach (explode(',', $config['domain']) as $domain) {
            try {
                $data = $client->request('GET', 'GetCustomDomain', '/2021-04-06/custom-domains/' . $domain);
            } catch (Exception $e) {
                throw new Exception('获取绑定域名信息失败：' . $e->getMessage());
            }
            $this->log('获取函数计算绑定域名信息成功');

            if (isset($data['certConfig']['certificate']) && $data['certConfig']['certificate'] == $fullchain) {
                $this->log('函数计算域名 ' . $domain . ' 证书已配置，无需重复操作');
                return;
            }

            if ($data['protocol'] == 'HTTP') $data['protocol'] = 'HTTP,HTTPS';
            $data['certConfig']['certName'] = $cert_name;
            $data['certConfig']['certificate'] = $fullchain;
            $data['certConfig']['privateKey'] = $privatekey;

            $param = [
                'protocol' => $data['protocol'],
                'routeConfig' => $data['routeConfig'],
                'certConfig' => $data['certConfig'],
                'tlsConfig' => $data['tlsConfig'],
                'wafConfig' => $data['wafConfig'],
            ];
            $client->request('PUT', 'UpdateCustomDomain', '/2021-04-06/custom-domains/' . $domain, $param);

            $this->log('函数计算域名 ' . $domain . ' 部署证书成功！');
        }
    }

    private function deploy_clb($cert_id, $cert_name, $config)
    {
        if (empty($config['clb_id'])) throw new Exception('负载均衡实例ID不能为空');
        if (empty($config['clb_port'])) throw new Exception('HTTPS监听端口不能为空');

        $endpoint = 'slb.' . $config['regionid'] . '.aliyuncs.com';
        $client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, $endpoint, '2014-05-15', $this->proxy);

        $param = [
            'Action' => 'DescribeServerCertificates',
            'RegionId' => $config['regionid'],
        ];
        try {
            $data = $client->request($param);
        } catch (Exception $e) {
            throw new Exception('获取服务器证书列表失败：' . $e->getMessage());
        }

        $ServerCertificateId = null;
        foreach ($data['ServerCertificates']['ServerCertificate'] as $cert) {
            if ($cert['IsAliCloudCertificate'] == 1 && $cert['AliCloudCertificateId'] == $cert_id) {
                $ServerCertificateId = $cert['ServerCertificateId'];
                break;
            }
        }
        if (!$ServerCertificateId) {
            $param = [
                'Action' => 'UploadServerCertificate',
                'RegionId' => $config['regionid'],
                'AliCloudCertificateId' => $cert_id,
                'AliCloudCertificateName' => $cert_name,
                'AliCloudCertificateRegionId' => 'cn-hangzhou',
            ];
            try {
                $data = $client->request($param);
            } catch (Exception $e) {
                throw new Exception('服务器证书添加失败：' . $e->getMessage());
            }
            $ServerCertificateId = $data['ServerCertificateId'];
            $this->log('服务器证书添加成功 ServerCertificateId=' . $ServerCertificateId);
        } else {
            $this->log('找到已添加的服务器证书 ServerCertificateId=' . $ServerCertificateId);
        }

        $deploy_type = isset($config['deploy_type']) ? intval($config['deploy_type']) : 0;
        if ($deploy_type == 1) {
            if (empty($config['clb_domain'])) throw new Exception('扩展域名不能为空');
            $domains = explode(',', $config['clb_domain']);
            $param = [
                'Action' => 'DescribeDomainExtensions',
                'RegionId' => $config['regionid'],
                'LoadBalancerId' => $config['clb_id'],
                'ListenerPort' => $config['clb_port'],
            ];
            try {
                $data = $client->request($param);
            } catch (Exception $e) {
                throw new Exception('扩展域名列表查询失败：' . $e->getMessage());
            }
            foreach ($data['DomainExtensions']['DomainExtension'] as $item) {
                if (in_array($item['Domain'], $domains)) {
                    if ($ServerCertificateId == $item['ServerCertificateId']) {
                        $this->log('负载均衡HTTPS扩展域名 ' . $item['Domain'] . ' 证书已配置');
                    } else {
                        $param = [
                            'Action' => 'SetDomainExtensionAttribute',
                            'RegionId' => $config['regionid'],
                            'DomainExtensionId' => $item['DomainExtensionId'],
                            'ServerCertificateId' => $ServerCertificateId,
                        ];
                        $client->request($param);
                        $this->log('负载均衡HTTPS扩展域名 ' . $item['Domain'] . ' 证书更新成功');
                    }
                }
            }
        } else {
            $param = [
                'Action' => 'DescribeLoadBalancerHTTPSListenerAttribute',
                'RegionId' => $config['regionid'],
                'LoadBalancerId' => $config['clb_id'],
                'ListenerPort' => $config['clb_port'],
            ];
            try {
                $data = $client->request($param);
            } catch (Exception $e) {
                throw new Exception('HTTPS监听配置查询失败：' . $e->getMessage());
            }

            if ($data['ServerCertificateId'] == $ServerCertificateId) {
                $this->log('负载均衡HTTPS监听已配置该证书，无需重复操作');
                return;
            }

            $param = [
                'Action' => 'SetLoadBalancerHTTPSListenerAttribute',
                'RegionId' => $config['regionid'],
                'LoadBalancerId' => $config['clb_id'],
                'ListenerPort' => $config['clb_port'],
                'ServerCertificateId' => $ServerCertificateId,
            ];
            $client->request($param);
            $this->log('负载均衡HTTPS监听证书配置成功！');
        }
    }

    private function deploy_alb($cert_id, $config)
    {
        if (empty($config['alb_listener_id'])) throw new Exception('负载均衡监听ID不能为空');

        $endpoint = 'alb.' . $config['regionid'] . '.aliyuncs.com';
        $client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, $endpoint, '2020-06-16', $this->proxy);
        $cert_id = $cert_id . '-cn-hangzhou';
        $deploy_type = isset($config['deploy_type']) ? intval($config['deploy_type']) : 0;

        if ($deploy_type == 1) {
            $param = [
                'Action' => 'ListListenerCertificates',
                'MaxResults' => 100,
                'ListenerId' => $config['alb_listener_id'],
                'CertificateType' => 'Server',
            ];
            try {
                $data = $client->request($param);
            } catch (Exception $e) {
                throw new Exception('获取监听证书列表失败：' . $e->getMessage());
            }
            foreach ($data['Certificates'] as $cert) {
                if ($cert['CertificateId'] == $cert_id) {
                    $this->log('负载均衡监听扩展证书已添加，无需重复操作');
                    return;
                }
            }

            $param = [
                'Action' => 'AssociateAdditionalCertificatesWithListener',
                'ListenerId' => $config['alb_listener_id'],
                'Certificates.1.CertificateId' => $cert_id,
            ];
            $client->request($param);
            $this->log('应用型负载均衡监听扩展证书添加成功！');
        } else {
            $param = [
                'Action' => 'UpdateListenerAttribute',
                'ListenerId' => $config['alb_listener_id'],
                'Certificates.1.CertificateId' => $cert_id,
            ];
            $client->request($param);
            $this->log('应用型负载均衡监听默认证书更新成功！');
        }
    }

    private function deploy_nlb($cert_id, $config)
    {
        if (empty($config['nlb_listener_id'])) throw new Exception('负载均衡监听ID不能为空');

        $endpoint = 'nlb.' . $config['regionid'] . '.aliyuncs.com';
        $client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, $endpoint, '2022-04-30', $this->proxy);
        $cert_id = $cert_id . '-cn-hangzhou';
        $deploy_type = isset($config['deploy_type']) ? intval($config['deploy_type']) : 0;

        if ($deploy_type == 1) {
            $param = [
                'Action' => 'ListListenerCertificates',
                'MaxResults' => 50,
                'ListenerId' => $config['nlb_listener_id'],
                'CertificateType' => 'Server',
            ];
            try {
                $data = $client->request($param);
            } catch (Exception $e) {
                throw new Exception('获取监听证书列表失败：' . $e->getMessage());
            }
            foreach ($data['Certificates'] as $cert) {
                if ($cert['CertificateId'] == $cert_id) {
                    $this->log('负载均衡监听扩展证书已添加，无需重复操作');
                    return;
                }
            }

            $param = [
                'Action' => 'AssociateAdditionalCertificatesWithListener',
                'ListenerId' => $config['nlb_listener_id'],
                'AdditionalCertificateIds.1' => $cert_id,
            ];
            $client->request($param);
            $this->log('网络型负载均衡监听扩展证书添加成功！');
        } else {
            $param = [
                'Action' => 'UpdateListenerAttribute',
                'ListenerId' => $config['nlb_listener_id'],
                'CertificateIds.1' => $cert_id,
            ];
            $client->request($param);
            $this->log('网络型负载均衡监听默认证书更新成功！');
        }
    }

    private function deploy_ga($cert_id, $config)
    {
        if (empty($config['ga_id'])) throw new Exception('全球加速实例ID不能为空');
        if (empty($config['ga_listener_id'])) throw new Exception('全球加速监听ID不能为空');

        $client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, 'ga.cn-hangzhou.aliyuncs.com', '2019-11-20', $this->proxy);
        $cert_id = $cert_id . '-cn-hangzhou';
        $deploy_type = isset($config['deploy_type']) ? intval($config['deploy_type']) : 0;

        if ($deploy_type == 1) {
            if (empty($config['clb_domain'])) throw new Exception('扩展域名不能为空');
            $param = [
                'Action' => 'ListListenerCertificates',
                'RegionId' => 'cn-hangzhou',
                'AcceleratorId' => $config['ga_id'],
                'ListenerId' => $config['ga_listener_id'],
            ];
            try {
                $data = $client->request($param);
            } catch (Exception $e) {
                throw new Exception('扩展域名列表查询失败：' . $e->getMessage());
            }
            $need_add = [];
            foreach (explode(',', $config['clb_domain']) as $domain) {
                $domainExists = false;
                $exist_cert_id = null;
                foreach ($data['Certificates'] as $cert) {
                    if (isset($cert['Domain']) && $domain == $cert['Domain']) {
                        $domainExists = true;
                        $exist_cert_id = $cert['CertificateId'];
                    }
                }
                if ($domainExists) {
                    if ($exist_cert_id == $cert_id) {
                        $this->log('全球加速实例监听扩展域名 ' . $domain . ' 证书已配置');
                        continue;
                    }
                    $param = [
                        'Action' => 'UpdateAdditionalCertificateWithListener',
                        'RegionId' => 'cn-hangzhou',
                        'AcceleratorId' => $config['ga_id'],
                        'ListenerId' => $config['ga_listener_id'],
                        'Domain' => $domain,
                        'CertificateId' => $cert_id,
                    ];
                    $client->request($param);
                    $this->log('全球加速实例监听扩展域名 ' . $domain . ' 替换证书成功！');
                } else {
                    $need_add[] = $domain;
                }
            }
            if (count($need_add) > 0) {
                $param = [
                    'Action' => 'AssociateAdditionalCertificatesWithListener',
                    'RegionId' => 'cn-hangzhou',
                    'AcceleratorId' => $config['ga_id'],
                    'ListenerId' => $config['ga_listener_id'],
                ];
                foreach ($need_add as $index => $domain) {
                    $param['Certificates.' . ($index + 1) . '.Id'] = $cert_id;
                    $param['Certificates.' . ($index + 1) . '.Domain'] = $domain;
                }
                $client->request($param);
                $this->log('全球加速实例监听扩展域名 ' . implode(',', $need_add) . ' 绑定证书成功！');
            }
        } else {
            $param = [
                'Action' => 'UpdateListener',
                'RegionId' => 'cn-hangzhou',
                'AcceleratorId' => $config['ga_id'],
                'ListenerId' => $config['ga_listener_id'],
                'Certificates.1.Id' => $cert_id,
            ];
            $client->request($param);
            $this->log('全球加速实例监听默认证书更新成功！');
        }
    }

    public function setLogger($func)
    {
        $this->logger = $func;
    }

    private function log($txt)
    {
        if ($this->logger) {
            call_user_func($this->logger, $txt);
        }
    }
}
