王新阳

wangxinyang

PHP获取网站SSL证书有效期

function getSSLCertificateExpiry($domain, $port = 443) {
    // 创建 SSL 上下文,启用证书捕获
    $context = stream_context_create([
        'ssl' => [
            'capture_peer_cert' => true,
            'capture_peer_cert_chain' => true,
            'verify_peer' => true, // 强烈建议启用证书验证
            'verify_peer_name' => true, // 强烈建议启用域名匹配验证
            // 如果在容器或特殊环境中遇到 CA 证书问题,可能需要指定 CA 文件路径
            // 'cafile' => '/path/to/cacert.pem',
        ]
    ]);

    // 建立 SSL 连接
    $socket = @stream_socket_client("ssl://{$domain}:{$port}", $errno, $errstr, 30, STREAM_CLIENT_CONNECT, $context);
    
    if (!$socket) {
        return ['error' => "连接失败: {$errstr} (错误码: {$errno})"];
    }

    // 获取连接参数,提取证书
    $params = stream_context_get_params($socket);
    if (!isset($params['options']['ssl']['peer_certificate'])) {
        return ['error' => '未收到证书'];
    }

    $cert = $params['options']['ssl']['peer_certificate'];
    $certInfo = openssl_x509_parse($cert);
    
    if (!$certInfo) {
        return ['error' => '证书解析失败'];
    }

    // 提取有效期信息
    $validFrom = date('Y-m-d H:i:s', $certInfo['validFrom_time_t']);
    $validTo = date('Y-m-d H:i:s', $certInfo['validTo_time_t']);
    $isExpired = $certInfo['validTo_time_t'] < time();

    return [
        'subject' => $certInfo['subject']['CN'] ?? 'N/A',
        'issuer' => $certInfo['issuer']['CN'] ?? 'N/A',
		'date_of_issue' => $validFrom,
        'expires_at' => $validTo,
        'expires_timestamp' => $certInfo['validTo_time_t'],
        'expired' => $isExpired,
		'left_days' => floor(($certInfo['validTo_time_t']-time())/86400),
    ];
}

$domain = 'www.baidu.com'; // 替换为你要查询的域名
$result = getSSLCertificateExpiry($domain);
if (isset($result['error'])) {
    echo "错误: " . $result['error'] . "\n";
} else {
    echo "证书信息:\n";
    echo "  主题 (CN): " . $result['subject'] . "\n";
    echo "  签发者: " . $result['issuer'] . "\n";
    echo "  颁发时间: " . $result['date_of_issue'] . "\n";
    echo "  到期时间: " . $result['expires_at'] . "\n";
    echo "  是否已过期: " . ($result['expired'] ? '是' : '否') . "\n";
    echo "  剩余天数: " . $result['left_days'] . "\n";
}

输出

证书信息:
  主题 (CN): baidu.com
  签发者: GlobalSign RSA OV SSL CA 2018
  颁发时间: 2025-07-09 07:01:02
  到期时间: 2026-08-10 07:01:01
  是否已过期: 否
  剩余天数: 182

$result = getSSLCertificateExpiry($domain);
获取到的证书所有信息:

Array
(
    [name] => /C=CN/ST=beijing/L=beijing/O=Beijing Baidu Netcom Science Technology Co., Ltd/CN=baidu.com
    [subject] => Array
        (
            [C] => CN
            [ST] => beijing
            [L] => beijing
            [O] => Beijing Baidu Netcom Science Technology Co., Ltd
            [CN] => baidu.com
        )

    [hash] => 7476f7c7
    [issuer] => Array
        (
            [C] => BE
            [O] => GlobalSign nv-sa
            [CN] => GlobalSign RSA OV SSL CA 2018
        )

    [version] => 2
    [serialNumber] => 27025959261604984493724308777
    [serialNumberHex] => 5753597B3F311D38E6629529
    [validFrom] => 250709070102Z
    [validTo] => 260810070101Z
    [validFrom_time_t] => 1752044462
    [validTo_time_t] => 1786345261
    [signatureTypeSN] => RSA-SHA256
    [signatureTypeLN] => sha256WithRSAEncryption
    [signatureTypeNID] => 668
    [purposes] => Array
        (
            [1] => Array
                (
                    [0] => 1
                    [1] => 
                    [2] => sslclient
                )

            [2] => Array
                (
                    [0] => 1
                    [1] => 
                    [2] => sslserver
                )

            [3] => Array
                (
                    [0] => 1
                    [1] => 
                    [2] => nssslserver
                )

            [4] => Array
                (
                    [0] => 
                    [1] => 
                    [2] => smimesign
                )

            [5] => Array
                (
                    [0] => 
                    [1] => 
                    [2] => smimeencrypt
                )

            [6] => Array
                (
                    [0] => 
                    [1] => 
                    [2] => crlsign
                )

            [7] => Array
                (
                    [0] => 1
                    [1] => 1
                    [2] => any
                )

            [8] => Array
                (
                    [0] => 1
                    [1] => 
                    [2] => ocsphelper
                )

            [9] => Array
                (
                    [0] => 
                    [1] => 
                    [2] => timestampsign
                )

        )

    [extensions] => Array
        (
            [keyUsage] => Digital Signature, Key Encipherment
            [basicConstraints] => CA:FALSE
            [authorityInfoAccess] => CA Issuers - URI:http://secure.globalsign.com/cacert/gsrsaovsslca2018.crt
OCSP - URI:http://ocsp.globalsign.com/gsrsaovsslca2018

            [certificatePolicies] => Policy: 1.3.6.1.4.1.4146.1.20
  CPS: https://www.globalsign.com/repository/
Policy: 2.23.140.1.2.2

            [crlDistributionPoints] => 
Full Name:
  URI:http://crl.globalsign.com/gsrsaovsslca2018.crl

            [subjectAltName] => DNS:baidu.com, DNS:baifubao.com, DNS:www.baidu.cn, DNS:www.baidu.com.cn, DNS:mct.y.nuomi.com, DNS:apollo.auto, DNS:dwz.cn, DNS:*.baidu.com, DNS:*.baifubao.com, DNS:*.baidustatic.com, DNS:*.bdstatic.com, DNS:*.bdimg.com, DNS:*.hao123.com, DNS:*.nuomi.com, DNS:*.chuanke.com, DNS:*.trustgo.com, DNS:*.bce.baidu.com, DNS:*.eyun.baidu.com, DNS:*.map.baidu.com, DNS:*.mbd.baidu.com, DNS:*.fanyi.baidu.com, DNS:*.baidubce.com, DNS:*.mipcdn.com, DNS:*.news.baidu.com, DNS:*.baidupcs.com, DNS:*.aipage.com, DNS:*.aipage.cn, DNS:*.bcehost.com, DNS:*.safe.baidu.com, DNS:*.im.baidu.com, DNS:*.baiducontent.com, DNS:*.dlnel.com, DNS:*.dlnel.org, DNS:*.dueros.baidu.com, DNS:*.su.baidu.com, DNS:*.91.com, DNS:*.hao123.baidu.com, DNS:*.apollo.auto, DNS:*.xueshu.baidu.com, DNS:*.bj.baidubce.com, DNS:*.gz.baidubce.com, DNS:*.smartapps.cn, DNS:*.bdtjrcv.com, DNS:*.hao222.com, DNS:*.haokan.com, DNS:*.pae.baidu.com, DNS:*.vd.bdstatic.com, DNS:*.cloud.baidu.com, DNS:click.hm.baidu.com, DNS:log.hm.baidu.com, DNS:cm.pos.baidu.com, DNS:wn.pos.baidu.com, DNS:update.pan.baidu.com
            [extendedKeyUsage] => TLS Web Server Authentication, TLS Web Client Authentication
            [authorityKeyIdentifier] => keyid:F8:EF:7F:F2:CD:78:67:A8:DE:6F:8F:24:8D:88:F1:87:03:02:B3:EB

            [subjectKeyIdentifier] => BA:91:7C:55:A9:8F:1F:B0:02:60:27:BB:D7:D3:03:AF:2D:AB:AD:1D
            [ct_precert_scts] => Signed Certificate Timestamp:
    Version   : v1 (0x0)
    Log ID    : AC:AB:30:70:6C:EB:EC:84:31:F4:13:D2:F4:91:5F:11:
                1E:42:24:43:B1:F2:A6:8C:4F:3C:2B:3B:A7:1E:02:C3
    Timestamp : Jul  9 07:01:09.629 2025 GMT
    Extensions: none
    Signature : ecdsa-with-SHA256
                30:44:02:20:35:DB:47:71:C6:0E:36:D4:9E:87:46:9D:
                8D:5C:1D:19:7F:A9:53:C0:1A:8F:16:2D:C2:03:2B:71:
                0B:C6:1D:53:02:20:22:0E:91:A8:C5:87:93:93:D6:48:
                35:F5:24:7B:F6:F5:FF:3D:56:F3:9D:DB:4C:72:86:2D:
                4A:AD:77:45:52:CF
Signed Certificate Timestamp:
    Version   : v1 (0x0)
    Log ID    : CB:38:F7:15:89:7C:84:A1:44:5F:5B:C1:DD:FB:C9:6E:
                F2:9A:59:CD:47:0A:69:05:85:B0:CB:14:C3:14:58:E7
    Timestamp : Jul  9 07:01:09.640 2025 GMT
    Extensions: none
    Signature : ecdsa-with-SHA256
                30:46:02:21:00:BC:C9:FA:F8:1A:19:CB:22:CF:BF:6D:
                A3:22:F6:A7:36:7B:C5:35:A1:A5:F7:AD:23:B8:59:2D:
                8B:97:09:68:E3:02:21:00:AB:19:F4:52:A5:FB:57:80:
                2C:64:F1:A9:5F:EE:77:DA:7C:97:78:37:85:8B:0D:41:
                CC:85:80:3C:2E:71:5B:81
Signed Certificate Timestamp:
    Version   : v1 (0x0)
    Log ID    : D7:6D:7D:10:D1:A7:F5:77:C2:C7:E9:5F:D7:00:BF:F9:
                82:C9:33:5A:65:E1:D0:B3:01:73:17:C0:C8:C5:69:77
    Timestamp : Jul  9 07:01:09.600 2025 GMT
    Extensions: none
    Signature : ecdsa-with-SHA256
                30:45:02:20:1E:5F:24:19:17:79:DD:66:DA:B1:09:B7:
                11:9F:DA:3C:49:A5:21:7B:10:1E:FF:7C:8F:E8:12:0B:
                45:FE:38:AA:02:21:00:B7:97:A9:BD:A2:27:A1:08:79:
                42:B5:18:DE:4E:76:C1:1D:0D:35:AC:F5:32:3B:05:7C:
                9D:8C:4C:87:77:A8:0C
        )

)

ip地址范围与cidr块互转,判断ip地址是否在给定范围或cidr块中

php版

<?php

class Ip_util{
	/**
	 * 将IP地址范围转换为CIDR块。
	 *
	 * @param string $range 格式为 "start_ip-end_ip" 的字符串。
	 * @return string[] 返回一个包含CIDR格式字符串的数组 (e.g., ["192.168.1.0/24"])。
	 */
	public static function rangeToCIDR($range) {
		list($startIp, $endIp) = explode('-', trim($range));

		$start = ip2long($startIp);
		$end = ip2long($endIp);

		if ($start === false || $end === false || $start > $end) {
			throw new InvalidArgumentException("Invalid IP range: $range");
		}

		$results = [];

		while ($start <= $end) {
			// 寻找以 $start 为起点的最大网络块
			$mask = 32;
			for ($i = 1; $i <= 32; $i++) {
				$blockSize = 1 << $i;
				$maskVal = ~($blockSize - 1);
				$blockStart = $start & $maskVal;

				// 如果这个块的起始地址小于当前start(说明对齐失败),
				// 或者这个块的结束地址超过了范围的结束地址,则不能选择此块
				if ($blockStart < $start || ($blockStart + $blockSize - 1) > $end) {
					break;
				}
				// 否则,这个块是有效的,更新掩码
				$mask = 32 - $i;
			}

			$results[] = long2ip($start) . '/' . $mask;
			$start += 1 << (32 - $mask);
		}

		return $results;
	}

	/**
	 * 将CIDR块转换为IP地址范围。
	 *
	 * @param string $cidr 格式为 "ip_address/prefix_length" 的字符串。
	 * @return string 返回一个IP范围格式的字符串 (e.g., "192.168.1.0-192.168.1.255")。
	 */
	public static function cidrToRange($cidr) {
		list($ip, $prefixLength) = explode('/', trim($cidr));
		
		$ipNum = ip2long($ip);
		
		if ($ipNum === false || !is_numeric($prefixLength) || $prefixLength < 0 || $prefixLength > 32) {
			throw new InvalidArgumentException("Invalid CIDR format: $cidr");
		}
		
		$prefixLength = (int)$prefixLength;

		$netMask = ~((1 << (32 - $prefixLength)) - 1);
		$networkAddress = $ipNum & $netMask;

		$hostBits = 32 - $prefixLength;
		$totalAddressesInSubnet = pow(2, $hostBits);
		$broadcastAddress = $networkAddress + $totalAddressesInSubnet - 1;

		$startIp = long2ip($networkAddress);
		$endIp = long2ip($broadcastAddress);

		return "$startIp-$endIp";
	}

	/**
	 * 检查IP是否在CIDR列表中
	 * @param string $ip
	 * @param array $cidrList
	 * @return bool
	 */
	public static function isInCidrList($ip, $cidrList) {
		$ipLong = ip2long($ip);
		if ($ipLong === false) {
			return false; // 无效IP
		}

		foreach ($cidrList as $cidr) {
			list($networkStr, $prefixLen) = explode('/', trim($cidr));
			$prefixLen = (int)$prefixLen;
			
			if (!is_numeric($prefixLen) || $prefixLen < 0 || $prefixLen > 32) {
				continue; // 跳过无效CIDR
			}

			$networkLong = ip2long($networkStr);
			if ($networkLong === false) {
				continue; // 跳过无效网络地址
			}

			// 计算网络掩码
			$mask = ~((1 << (32 - $prefixLen)) - 1);
			
			// 检查IP和网络地址的网络部分是否相同
			if (($ipLong & $mask) === ($networkLong & $mask)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * 检查IP是否在IP范围列表中
	 * @param string $ip
	 * @param array $rangeList
	 * @return bool
	 */
	public static function isInRangeList($ip, $rangeList) {
		$ipLong = ip2long($ip);
		if ($ipLong === false) {
			return false; // 无效IP
		}

		foreach ($rangeList as $range) {
			list($startStr, $endStr) = explode('-', trim($range));

			$startLong = ip2long($startStr);
			$endLong = ip2long($endStr);

			if ($startLong === false || $endLong === false) {
				continue; // 跳过无效范围
			}

			// 检查IP是否在范围内
			if ($ipLong >= $startLong && $ipLong <= $endLong) {
				return true;
			}
		}
		return false;
	}
}







// --- 示例用法 ---
$ranges = [
    '192.168.1.0-192.168.1.255',
    '192.168.2.0-192.168.5.0',
    '192.168.2.0-192.168.5.128',
    '192.168.2.0-192.168.5.255',
    '192.168.0.0-192.168.255.255',
    '192.1.127.0-192.5.127.255',
    '192.1.127.0-192.5.128.169',
    '192.1.127.0-192.5.127.0'
];

$cidrs=[];

foreach ($ranges as $range) {
    echo "输入范围: $range\n";
    $result = Ip_util::rangeToCIDR($range);
	$cidrs=array_merge($cidrs,$result);
    echo "输出CIDR(s): [" . implode(', ', array_map(function($c) { return "\"$c\""; }, $result)) . "]\n\n";
	echo "逆推:\n";
	foreach($result as $row){
		echo Ip_util::cidrToRange($row),"\n";
	}
	echo "\n\n";
}

$targetIp='192.5.128.150';


echo "目标IP: $targetIp\n\n";

echo "检查IP是否在范围列表中:\n";
foreach ($ranges as $range) {
    $inRange = Ip_util::isInRangeList($targetIp, [$range]);
    echo "  - '$range': " . ($inRange ? "是" : "否") . "\n";
}
echo "总体结果 (在任一范围内): " . (Ip_util::isInRangeList($targetIp, $ranges) ? "是" : "否") . "\n\n";

echo "检查IP是否在CIDR列表中:\n";
foreach ($cidrs as $cidr) {
    $inCidr = Ip_util::isInCidrList($targetIp, [$cidr]);
    echo "  - '$cidr': " . ($inCidr ? "是" : "否") . "\n";
}
echo "总体结果 (在任一CIDR中): " . (Ip_util::isInCidrList($targetIp, $cidrs) ? "是" : "否") . "\n";


?>

js版

<script>
/**
 * IP范围转CIDR列表
 * @param {string} ipRange
 * @returns {string[]}
 */
function rangeToCIDR(ipRange) {
	const [startIp, endIp]=ipRange.split('-');
    const ipToInt = ip => ip.split('.').reduce((acc, x) => (acc << 8) + (+x), 0);
    const intToIp = int => [24, 16, 8, 0].map(s => (int >> s) & 0xFF).join('.');
    
    let start = ipToInt(startIp);
    const end = ipToInt(endIp);
    const results = [];
    
    while (start <= end) {
        // 找到start的最低位1的位置
        let mask = 32;
        for (let i = 1; i <= 32; i++) {
            const blockSize = 1 << i;
            const maskVal = ~(blockSize - 1);
            const blockStart = start & maskVal;
            
            if (blockStart < start || blockStart + blockSize - 1 > end) {
                break;
            }
            mask = 32 - i;
        }
        
        results.push(`${intToIp(start)}/${mask}`);
        start += 1 << (32 - mask);
    }
    
    return results;
}

/**
 * CIDR转IP范围
 * @param {string} cidr
 * @returns {string}
 */
function cidrToRange(cidr) {
    const ipToInt = ip => ip.split('.').reduce((acc, octet, i) => acc + (parseInt(octet) << (24 - i * 8)), 0);
    const intToIp = int => [24, 16, 8, 0].map(shift => (int >>> shift) & 0xFF).join('.');
    
    const [ip, prefixStr] = cidr.split('/');
    const prefix = parseInt(prefixStr, 10);
    const mask = ~0 << (32 - prefix);
    const ipInt = ipToInt(ip);
    const network = ipInt & mask;
    
	return intToIp(network)+'-'+intToIp(network + (1 << (32 - prefix)) - 1);
    return {
        start: intToIp(network),
        end: intToIp(network + (1 << (32 - prefix)) - 1)
    };
}

/**
 * 检查IP是否在CIDR列表中
 * @param {string} ip
 * @param {string[]} cidrList
 * @returns {boolean}
 */
function isIpInCidrList(ip, cidrList) {
    const ipLong = ip.split('.').reduce((acc, x) => (acc << 8) + (+x), 0) >>> 0;
    if (isNaN(ipLong)) return false; // 无效IP

    for (const cidr of cidrList) {
        const [networkStr, prefixLenStr] = cidr.split('/');
        const prefixLen = parseInt(prefixLenStr, 10);
        
        if (isNaN(prefixLen) || prefixLen < 0 || prefixLen > 32) {
            continue; // 跳过无效CIDR
        }

        const networkLong = networkStr.split('.').reduce((acc, x) => (acc << 8) + (+x), 0) >>> 0;
        if (isNaN(networkLong)) {
            continue; // 跳过无效网络地址
        }

        const mask = ~((1 << (32 - prefixLen)) - 1) >>> 0;
        
        if ((ipLong & mask) === (networkLong & mask)) {
            return true;
        }
    }
    return false;
}

/**
 * 检查IP是否在IP范围列表中
 * @param {string} ip
 * @param {string[]} rangeList
 * @returns {boolean}
 */
function isIpInRangeList(ip, rangeList) {
    const ipLong = ip.split('.').reduce((acc, x) => (acc << 8) + (+x), 0) >>> 0;
    if (isNaN(ipLong)) return false; // 无效IP

    for (const range of rangeList) {
        const [startStr, endStr] = range.split('-');

        const startLong = startStr.split('.').reduce((acc, x) => (acc << 8) + (+x), 0) >>> 0;
        const endLong = endStr.split('.').reduce((acc, x) => (acc << 8) + (+x), 0) >>> 0;

        if (isNaN(startLong) || isNaN(endLong)) {
            continue; // 跳过无效范围
        }

        if (ipLong >= startLong && ipLong <= endLong) {
            return true;
        }
    }
    return false;
}



// --- 示例用法 ---
const ranges = [
    '192.168.1.0-192.168.1.255',
    '192.168.2.0-192.168.5.0',
    '192.168.2.0-192.168.5.128',
    '192.168.2.0-192.168.5.255',
    '192.168.0.0-192.168.255.255',
    '192.1.127.0-192.5.127.255',
    '192.1.127.0-192.5.128.169',
    '192.1.127.0-192.5.127.0'
];
cidrs=[];

ranges.forEach(range => {
    console.log(`输入范围: ${range}`);
    const result = rangeToCIDR(range);
	cidrs=cidrs.concat(result);
    console.log(`输出CIDR(s): [${result.map(c => `"${c}"`).join(', ')}]\n`);
	console.log("逆推:\n");
	for(let index in result){
		console.log(cidrToRange(result[index])+"\n");
	}
/*
	$.each(result,function(index,row){
		console.log(cidrToRange(row)+"\n");
	});
*/
	console.log("\n\n");
});

const targetIp = "192.5.128.150";
console.log(`目标IP: ${targetIp}\n`);

console.log("检查IP是否在范围列表中:");
ranges.forEach(range => {
    const inRange = isIpInRangeList(targetIp, [range]);
    console.log(`  - '${range}': ${inRange ? '是' : '否'}`);
});
console.log(`总体结果 (在任一范围内): ${isIpInRangeList(targetIp, ranges) ? '是' : '否'}\n`);

console.log("检查IP是否在CIDR列表中:");
cidrs.forEach(cidr => {
    const inCidr = isIpInCidrList(targetIp, [cidr]);
    console.log(`  - '${cidr}': ${inCidr ? '是' : '否'}`);
});
console.log(`总体结果 (在任一CIDR中): ${isIpInCidrList(targetIp, cidrs) ? '是' : '否'}`);
</script>

批量获取某目录及子孙目录下的所有图片image、视频video、目录dir、目录树dir_tree

/**
 * 批量获取某目录及子孙目录下的所有图片image、视频video、目录dir、目录树dir_tree
 * @param array  $filter_array 要过滤的目录名称列表
 */
function get_file_list($type, $path, $filter_array=array()){
	if(!in_array($type, explode(',','image,video,dir,dir_tree')))return return_data(1,'类型错误:'.$type);

	/**
	 * SELF_FIRST 目录从浅到深返回
	 * CHILD_FIRST 先处理子节点,目录从深到浅返回
	 */
	$iterator = new RecursiveIteratorIterator(
		new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS),
		RecursiveIteratorIterator::SELF_FIRST
	);

	$photos = [];
	$videos = [];
	$dirs = [];
	foreach ($iterator as $SplFileInfo){
		$skip=false;
		foreach($filter_array as $filter){
			if(strpos($SplFileInfo->getRealPath(), $filter)!==false){
				$skip=true;
				break;
			}
		}
		if($skip)continue;
/*
$SplFileInfo 实际是 RecursiveDirectoryIterator 类
参考:https://www.php.net/manual/zh/class.recursivedirectoryiterator.php
常用方法:
getExtension	获取扩展名,如:jpg
getFilename		获取文件名,如:abc.jpg
getPathname		返回创建 SplFileInfo 对象时传入的原始路径字符串(不做任何解析或验证),如:D:\web/pictures\2001\a.jpg
getRealPath		返回文件的规范化的绝对路径,如:D:\web\pictures\2001\a.jpg
getSize
getType
isDir
isFile
isLink	快捷方式,会自动跟随与链接文件
*/
		if($SplFileInfo->isDir()){
			$dirs[$iterator->getDepth()][] = $SplFileInfo->getRealPath();
		}else if($SplFileInfo->isFile()){
			$cur_path = dirname($SplFileInfo->getRealPath());
			$cur_file = $SplFileInfo->getRealPath();
			$cur_size = $SplFileInfo->getSize();
			switch(strtolower($SplFileInfo->getExtension())){
				case 'jpe':
				case 'jpg':
				case 'jpeg':
				case 'png':
					//因生成缩略图时可能出错,所以不展示大图片
					if($cur_size < 20*1024*1024){
						$photos[$cur_path][] = $cur_file;
					}
					break;
				case 'mp4':
					$videos[$cur_path][] = $cur_file;
					break;
			}
/*
			$photos[] = [
				'getRealPath' => $SplFileInfo->getRealPath(),
				'getPathname' => $SplFileInfo->getPathname(),
				'getExtension' => $SplFileInfo->getExtension(),
				'getFilename' => $SplFileInfo->getFilename(),
				'getSize' => $SplFileInfo->getSize(),
				'getType' => $SplFileInfo->getType(),
			];
*/
		}
	}
	ksort($dirs);
	// 构建树形结构
	$dir_tree = [];
	foreach ($dirs as $depth => $paths) {
		foreach ($paths as $path) {
			insertPathIntoTree($dir_tree, $path, $path);
		}
	}
	switch($type){
		case 'image':
			return return_data(0,'',$photos);
		case 'video':
			return return_data(0,'',videos);
		case 'dir':
			return return_data(0,'',dir);
		case 'dir_tree':
			return return_data(0,'',dir_tree);
	}
}
//生成目录树
function insertPathIntoTree(&$tree, $currentPath, $rootPath) {
  // 计算相对路径
  $relativePath = substr($currentPath, strlen($rootPath));
  if (empty($relativePath)) {
    $tree[$currentPath] = [];
    return;
  }

  // 分割路径
  $parts = explode(DIRECTORY_SEPARATOR, trim($relativePath, DIRECTORY_SEPARATOR));

  // 找到父路径
  $parentPath = $rootPath;
  for ($i = 0; $i < count($parts) - 1; $i++) {
    $parentPath .= DIRECTORY_SEPARATOR . $parts[$i];
  }

  // 如果是根目录下的直接子目录
  if ($parentPath === $rootPath) {
    $tree[$currentPath] = [];
    return;
  }

  // 在树中找到父路径对应的节点
  $current = &$tree;
  $pathSoFar = $rootPath;

  for ($i = 0; $i < count($parts) - 1; $i++) {
    $pathSoFar .= DIRECTORY_SEPARATOR . $parts[$i];
    // 查找当前路径在树中的位置
    $found = false;
    foreach ($current as $key => $value) {
      if (strpos($key, $pathSoFar) === 0) {
        $current = &$current[$key];
        $found = true;
        break;
      }
    }
    if (!$found) break;
  }

  // 在父节点中添加当前路径
  $current[$currentPath] = [];
}

php获取图片方向、纠正图片方向

/**
 * 自动纠正图片方向
 * 方向经过旋转的图片,在电脑、手机、浏览器中查看时一般会自动纠正,
 * 但是在生成缩略图、使用某些打印控件打印时,可能会出现方向不正确的情况。
 */
function rotateImage($imagePath) {
    if (!function_exists('exif_read_data')) {
        return false; // EXIF扩展未启用
    }
    
    if (!file_exists($imagePath)) {
        return false; // 文件不存在
    }
    
    $exif = exif_read_data($imagePath);
    
	$orientation=1;
    if ($exif && isset($exif['Orientation'])) {
        $orientation= (int)$exif['Orientation'];
    }

    // 读取图片
    $size = getimagesize($imagePath);
	$width = $size[0];
	$height= $size[1];
	$mime = $size['mime'];
    
    switch ($mime) {
		case 'image/jpeg':
			$image = imagecreatefromjpeg($imagePath);
			break;
		case 'image/png':
			$image = imagecreatefrompng($imagePath);
			break;
		case 'image/gif':
			 $image = imagecreatefromgif($imagePath);
			break;
        default:
   			header('Content-Type: '.$mime);
			echo file_get_contents($imagePath);
            return false;
    }
    
    // 根据方向值进行旋转
    switch ($orientation) {
        case 2:
            // 水平翻转
            imageflip($image, IMG_FLIP_HORIZONTAL);
            break;
        case 3:
            // 旋转180°
            $image = imagerotate($image, 180, 0);
            break;
        case 4:
            // 垂直翻转
            imageflip($image, IMG_FLIP_VERTICAL);
            break;
        case 5:
            // 顺时针90° + 水平翻转
            $image = imagerotate($image, -90, 0);
            imageflip($image, IMG_FLIP_HORIZONTAL);
            break;
        case 6:
            // 顺时针90°
            $image = imagerotate($image, -90, 0);
            break;
        case 7:
            // 逆时针90° + 水平翻转
            $image = imagerotate($image, 90, 0);
            imageflip($image, IMG_FLIP_HORIZONTAL);
            break;
        case 8:
            // 逆时针90°
            $image = imagerotate($image, 90, 0);
            break;
    }
    
    // 输出图片
    switch ($mime) {
        case 'image/jpeg':
            header('Content-Type: image/jpeg');
            imagejpeg($image, null, 90); // 90% 质量
            break;
        case 'image/png':
            header('Content-Type: image/png');
            imagepng($image, null, 9); // 9级压缩
            break;
        case 'image/gif':
            header('Content-Type: image/gif');
            imagegif($image);
            break;
    }

    imagedestroy($image);
}
/*获取图片方向*/
function getImageOrientation($imagePath) {
    if (!function_exists('exif_read_data')) {
        return return_data(1, 'EXIF扩展未启用');
    }
    
    if (!file_exists($imagePath)) {
        return return_data(2, '文件不存在');
    }
    
	$exif = @exif_read_data($imagePath);
	if($exif===null)return return_data(2,'方向无法读取:'.$imagePath);
	
	$orientation=0;
    if ($exif && isset($exif['Orientation'])) {
        $orientation= (int)$exif['Orientation'];
    }

	// 方向值说明:
	// 1 = 正常
	// 2 = 水平翻转
	// 3 = 旋转180°
	// 4 = 垂直翻转
	// 5 = 顺时针90°+水平翻转
	// 6 = 顺时针90°
	// 7 = 逆时针90°+水平翻转
	// 8 = 逆时针90°

	$txt='';
	switch ($orientation) {
		case 1:
			$txt="正常方向 (0°)";
			break;
		case 2:
			$txt="水平翻转";
			break;
		case 3:
			$txt="旋转 180°";
			break;
		case 4:
			$txt="垂直翻转";
			break;
		case 5:
			$txt="顺时针 90° + 水平翻转";
			break;
		case 6:
			$txt="顺时针 90°";
			break;
		case 7:
			$txt="逆时针 90° + 水平翻转";
			break;
		case 8:
			$txt="逆时针 90°";
			break;
		default:
			$txt="未知方向";
			break;
	}
	
	return return_data(0, $orientation.chr(32).$txt, array('orientation'=>$orientation, 'description'=>$txt));
}



function return_data($code=0,$msg='',$data=array()){
	return array('code'=>$code, 'msg'=>$msg, 'data'=>$data);
}

PHP try catch示例

try {
    throw new Exception("错误消息", 404);
} catch (Exception $e) {
    echo "消息: " . $e->getMessage() . "\n"; // 错误消息
    echo "代码: " . $e->getCode() . "\n";     // 404
    echo "文件: " . $e->getFile() . "\n";     // 文件路径
    echo "行号: " . $e->getLine() . "\n";     // 行号
    echo "回溯: " . $e->getTraceAsString();  // 调用栈
}

php判断png/gif/webp是否背景透明

function isPngTransparent($filePath) {
	$image = imagecreatefrompng($filePath);
	if (!$image) return false;
	
	// 获取图像尺寸
	$width = imagesx($image);
	$height = imagesy($image);
	
	// 检查alpha通道
	if (imagecolortransparent($image) >= 0) {
		return true;
	}
	
	// 逐像素检查透明度
	for ($x = 0; $x < $width; $x++) {
		for ($y = 0; $y < $height; $y++) {
			$color = imagecolorat($image, $x, $y);
			$alpha = ($color >> 24) & 0xFF;
			if ($alpha > 0) {
				//imagedestroy($image);
				return true;
			}
		}
	}
	
	imagedestroy($image);
	return false;
}
function isGifTransparent($filePath) {
	$image = imagecreatefromgif($filePath);
	if (!$image) return false;
	
	// 获取透明色索引
	$transparentIndex = imagecolortransparent($image);
	
	// 如果有透明色索引
	if ($transparentIndex >= 0) {
		// 获取调色板中的透明色
		$transparentColor = imagecolorsforindex($image, $transparentIndex);
		if ($transparentColor['alpha'] == 127) {
			//imagedestroy($image);
			return true;
		}
	}
	
	imagedestroy($image);
	return false;
}
function isWebpTransparent($filePath) {
	if (!function_exists('imagecreatefromwebp')) {
		throw new Exception('WebP支持未启用');
	}
	
	$image = imagecreatefromwebp($filePath);
	if (!$image) return false;
	
	$width = imagesx($image);
	$height = imagesy($image);
	
	// WebP支持alpha通道,检查方式类似PNG
	for ($x = 0; $x < $width; $x++) {
		for ($y = 0; $y < $height; $y++) {
			$color = imagecolorat($image, $x, $y);
			$alpha = ($color >> 24) & 0xFF;
			if ($alpha > 0) {
				//imagedestroy($image);
				return true;
			}
		}
	}
	
	imagedestroy($image);
	return false;
}

php遍历获取当前目录及子目录下所有文件

/**
 * SELF_FIRST 目录从浅到深返回
 * CHILD_FIRST 先处理子节点,目录从深到浅返回
 */
function getAllFilesWithSize() {
        $files = [];
        $iterator = new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($this->cacheBaseDir, FilesystemIterator::SKIP_DOTS),
            RecursiveIteratorIterator::CHILD_FIRST
        );
        foreach ($iterator as $SplFileInfo) {
            if ($SplFileInfo->isFile()) {
                $files[] = [
			'getRealPath' => $SplFileInfo->getRealPath(),
			'getPathname' => $SplFileInfo->getPathname(),
			'getExtension' => $SplFileInfo->getExtension(),
			'getFilename' => $SplFileInfo->getFilename(),
			'getSize' => $SplFileInfo->getSize(),
			'getType' => $SplFileInfo->getType(),
                ];
            }
        }
        
        return $files;
    }

参考:
PHP标准库 https://www.php.net/manual/zh/book.spl.php
递归目录迭代器 https://www.php.net/manual/zh/class.recursivedirectoryiterator.php

JSON 网络令牌JWT(JSON Web Token)

参考:https://zhuanlan.zhihu.com/p/12876076909

1、什么是JWT?
JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在各方之间以 JSON 格式安全地传输信息。它通常用于身份验证和授权流程中,特别是在无状态的 RESTful API 和现代 Web 应用中。

2、什么时候应该使用 JSON Web Token?
以下是 JSON Web Token (JWT) 的一些适用场景:

授权(Authorization): 这是 JWT 最常见的用途。用户登录后,后续的每个请求都会携带 JWT,从而允许用户访问该令牌所授权的路由、服务和资源。单点登录(Single Sign On)是当前广泛使用 JWT 的一个功能,因为它具有较小的开销,并且可以轻松跨不同域名使用。
信息交换(Information Exchange): JWT 是在各方之间安全传输信息的一种好方式。由于 JWT 可以签名(例如,使用公钥/私钥对),你可以确认发送者的身份。此外,由于签名是基于头部(header)和负载(payload)计算得出的,你还可以验证内容是否被篡改。

3、JSON Web Token 的结构是什么?
在紧凑形式中,JSON Web Token(JWT)由三部分组成,各部分之间通过点号(.)分隔:

头部(Header)
负载(Payload)
签名(Signature)

JWT有效期可以在 $payload 中设置(建议),也可以通过在 $secretKey 原有秘钥后加日期,动态改变秘钥来实现

PHP生成和验证jwt

//生成JWT令牌
function jwt_create($payload, $secretKey){
	function base64url_encode($data){return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');}
	// 定义 Header、Payload 和密钥
	$header = array('alg' => 'HS256');

	// 编码 Header 和 Payload
	$headerEncoded = base64url_encode(json_encode($header));
	$payloadEncoded = base64url_encode(json_encode($payload));

	// 生成签名(HMAC-SHA256)
	$signatureInput = "$headerEncoded.$payloadEncoded";
	$signature = hash_hmac('sha256', $signatureInput, $secretKey, true);
	$signatureEncoded = base64url_encode($signature);

	// 组合完整 JWT
	$jwt = "$headerEncoded.$payloadEncoded.$signatureEncoded";
	return $jwt;
}
//验证JWT令牌并获取数据
function jwt_verify($jwt,$secretKey){
	function base64url_encode($data){return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');}
	function base64url_decode($data) {$padding = strlen($data) % 4; if($padding > 0){$data .= str_repeat('=', 4 - $padding);} return base64_decode(strtr($data, '-_', '+/'));}
	// 提取已生成的 JWT 各部分
	list($header, $payload, $signature) = explode('.', $jwt);

	// 重新计算签名
	$recomputedSignature = base64url_encode(
		hash_hmac('sha256', "$header.$payload", $secretKey, true)
	);

	// 比较签名
	if($signature === $recomputedSignature){
		$res=json_decode(base64url_decode($payload),true);
		if(is_array($res) && isset($res['userid']))return $res['userid'];
	}
	return false;
}

php获取指定目录的所有子孙目录

方法一:scandir + 递归

function getAllSubdirectories($path) {
    $subdirs = [];
    
    $items = scandir($path);
    foreach ($items as $item) {
        if ($item === '.' || $item === '..') continue;
        
        $fullPath = $path . DIRECTORY_SEPARATOR . $item;
        if (is_dir($fullPath)) {
            // 添加当前目录
            $subdirs[] = $fullPath;
            // 递归获取子目录的子目录
            $subdirs = array_merge($subdirs, getAllSubdirectories($fullPath));
        }
    }
    
    return $subdirs;
}

// 使用示例
$allSubdirs = getAllSubdirectories('/path/to/directory');
print_r($allSubdirs);

方法二:使用 RecursiveDirectoryIterator(推荐)

function getAllSubdirectoriesIterator($path) {
    $subdirs = [];
    
    $iterator = new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS),
        RecursiveIteratorIterator::SELF_FIRST
    );
    
    foreach ($iterator as $fileinfo) {
        if ($fileinfo->isDir()) {
            $subdirs[] = $fileinfo->getPathname();
        }
    }
    
    return $subdirs;
}

// 使用示例
$allSubdirs = getAllSubdirectoriesIterator('/path/to/directory');
print_r($allSubdirs);

分别用js、php实现GS84与GCJ02两种坐标系的互转

高德地图: 使用 GCJ-02 坐标系,也称为火星坐标系。它是基于国际标准 WGS-84 坐标系进行加密处理得到的。
百度地图: 采用 BD-09 坐标系,这是在 GCJ-02 基础上进行二次加密而来的,具有更高的安全性和隐私保护。
天地图: 使用 CGCS2000 坐标系,该坐标系与 WGS-84 存在微小偏差,在要求不高的情况下可以直接与 WGS-84 互换使用。

js

/**
 * WGS84转GCJ02(火星坐标系)
 * @param {number} wgsLon WGS84坐标系的经度
 * @param {number} wgsLat WGS84坐标系的纬度
 * @returns {Array} GCJ02坐标 [经度, 纬度]
 */
function wgs84ToGcj02(wgsLon, wgsLat) {
    const PI = 3.14159265358979324;
    const a = 6378245.0;
    const ee = 0.00669342162296594323;
    
    if (outOfChina(wgsLat, wgsLon)) {
        return [wgsLon, wgsLat];
    }
    
    let dLat = transformLat(wgsLon - 105.0, wgsLat - 35.0);
    let dLon = transformLon(wgsLon - 105.0, wgsLat - 35.0);
    
    const radLat = wgsLat / 180.0 * PI;
    let magic = Math.sin(radLat);
    magic = 1 - ee * magic * magic;
    const sqrtMagic = Math.sqrt(magic);
    
    dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * PI);
    dLon = (dLon * 180.0) / (a / sqrtMagic * Math.cos(radLat) * PI);
    
    const gcjLat = wgsLat + dLat;
    const gcjLon = wgsLon + dLon;
    
    return [gcjLon, gcjLat];
}

/**
 * GCJ02(火星坐标系)转WGS84
 * @param {number} gcjLon GCJ02坐标系的经度
 * @param {number} gcjLat GCJ02坐标系的纬度
 * @returns {Array} WGS84坐标 [经度, 纬度]
 */
function gcj02ToWgs84(gcjLon, gcjLat) {
    const PI = 3.14159265358979324;
    const a = 6378245.0;
    const ee = 0.00669342162296594323;
    
    if (outOfChina(gcjLat, gcjLon)) {
        return [gcjLon, gcjLat];
    }
    
    let dLat = transformLat(gcjLon - 105.0, gcjLat - 35.0);
    let dLon = transformLon(gcjLon - 105.0, gcjLat - 35.0);
    
    const radLat = gcjLat / 180.0 * PI;
    let magic = Math.sin(radLat);
    magic = 1 - ee * magic * magic;
    const sqrtMagic = Math.sqrt(magic);
    
    dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * PI);
    dLon = (dLon * 180.0) / (a / sqrtMagic * Math.cos(radLat) * PI);
    
    const wgsLat = gcjLat - dLat;
    const wgsLon = gcjLon - dLon;
    
    return [wgsLon, wgsLat];
}

function transformLat(x, y) {
    let ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x));
    ret += (20.0 * Math.sin(6.0 * x * Math.PI) + 20.0 * Math.sin(2.0 * x * Math.PI)) * 2.0 / 3.0;
    ret += (20.0 * Math.sin(y * Math.PI) + 40.0 * Math.sin(y / 3.0 * Math.PI)) * 2.0 / 3.0;
    ret += (160.0 * Math.sin(y / 12.0 * Math.PI) + 320 * Math.sin(y * Math.PI / 30.0)) * 2.0 / 3.0;
    return ret;
}

function transformLon(x, y) {
    let ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x));
    ret += (20.0 * Math.sin(6.0 * x * Math.PI) + 20.0 * Math.sin(2.0 * x * Math.PI)) * 2.0 / 3.0;
    ret += (20.0 * Math.sin(x * Math.PI) + 40.0 * Math.sin(x / 3.0 * Math.PI)) * 2.0 / 3.0;
    ret += (150.0 * Math.sin(x / 12.0 * Math.PI) + 300.0 * Math.sin(x / 30.0 * Math.PI)) * 2.0 / 3.0;
    return ret;
}

function outOfChina(lat, lon) {
    return lon < 72.004 || lon > 137.8347 || lat < 0.8293 || lat > 55.8271;
}

// 使用示例
// let gcjCoord = wgs84ToGcj02(116.404, 39.915); // WGS84转GCJ02
// let wgsCoord = gcj02ToWgs84(116.404, 39.915); // GCJ02转WGS84

PHP

/**
 * WGS84转GCJ02(火星坐标系)
 * @param float $wgsLon WGS84坐标系的经度
 * @param float $wgsLat WGS84坐标系的纬度
 * @return array GCJ02坐标 [经度, 纬度]
 */
function wgs84ToGcj02($wgsLon, $wgsLat) {
    $PI = 3.14159265358979324;
    $a = 6378245.0;
    $ee = 0.00669342162296594323;
    
    if (outOfChina($wgsLat, $wgsLon)) {
        return array($wgsLon, $wgsLat);
    }
    
    $dLat = transformLat($wgsLon - 105.0, $wgsLat - 35.0);
    $dLon = transformLon($wgsLon - 105.0, $wgsLat - 35.0);
    
    $radLat = $wgsLat / 180.0 * $PI;
    $magic = sin($radLat);
    $magic = 1 - $ee * $magic * $magic;
    $sqrtMagic = sqrt($magic);
    
    $dLat = ($dLat * 180.0) / (($a * (1 - $ee)) / ($magic * $sqrtMagic) * $PI);
    $dLon = ($dLon * 180.0) / ($a / $sqrtMagic * cos($radLat) * $PI);
    
    $gcjLat = $wgsLat + $dLat;
    $gcjLon = $wgsLon + $dLon;
    
    return array($gcjLon, $gcjLat);
}

/**
 * GCJ02(火星坐标系)转WGS84
 * @param float $gcjLon GCJ02坐标系的经度
 * @param float $gcjLat GCJ02坐标系的纬度
 * @return array WGS84坐标 [经度, 纬度]
 */
function gcj02ToWgs84($gcjLon, $gcjLat) {
    $PI = 3.14159265358979324;
    $a = 6378245.0;
    $ee = 0.00669342162296594323;
    
    if (outOfChina($gcjLat, $gcjLon)) {
        return array($gcjLon, $gcjLat);
    }
    
    $dLat = transformLat($gcjLon - 105.0, $gcjLat - 35.0);
    $dLon = transformLon($gcjLon - 105.0, $gcjLat - 35.0);
    
    $radLat = $gcjLat / 180.0 * $PI;
    $magic = sin($radLat);
    $magic = 1 - $ee * $magic * $magic;
    $sqrtMagic = sqrt($magic);
    
    $dLat = ($dLat * 180.0) / (($a * (1 - $ee)) / ($magic * $sqrtMagic) * $PI);
    $dLon = ($dLon * 180.0) / ($a / $sqrtMagic * cos($radLat) * $PI);
    
    $wgsLat = $gcjLat - $dLat;
    $wgsLon = $gcjLon - $dLon;
    
    return array($wgsLon, $wgsLat);
}

function transformLat($x, $y) {
    $ret = -100.0 + 2.0 * $x + 3.0 * $y + 0.2 * $y * $y + 0.1 * $x * $y + 0.2 * sqrt(abs($x));
    $ret += (20.0 * sin(6.0 * $x * M_PI) + 20.0 * sin(2.0 * $x * M_PI)) * 2.0 / 3.0;
    $ret += (20.0 * sin($y * M_PI) + 40.0 * sin($y / 3.0 * M_PI)) * 2.0 / 3.0;
    $ret += (160.0 * sin($y / 12.0 * M_PI) + 320 * sin($y * M_PI / 30.0)) * 2.0 / 3.0;
    return $ret;
}

function transformLon($x, $y) {
    $ret = 300.0 + $x + 2.0 * $y + 0.1 * $x * $x + 0.1 * $x * $y + 0.1 * sqrt(abs($x));
    $ret += (20.0 * sin(6.0 * $x * M_PI) + 20.0 * sin(2.0 * $x * M_PI)) * 2.0 / 3.0;
    $ret += (20.0 * sin($x * M_PI) + 40.0 * sin($x / 3.0 * M_PI)) * 2.0 / 3.0;
    $ret += (150.0 * sin($x / 12.0 * M_PI) + 300.0 * sin($x / 30.0 * M_PI)) * 2.0 / 3.0;
    return $ret;
}

function outOfChina($lat, $lon) {
    return $lon < 72.004 || $lon > 137.8347 || $lat < 0.8293 || $lat > 55.8271;
}

// 使用示例
// $gcjCoord = wgs84ToGcj02(116.404, 39.915); // WGS84转GCJ02
// $wgsCoord = gcj02ToWgs84(116.404, 39.915); // GCJ02转WGS84

百度地图API在CodeIgniter中的实现:
地理编码、全球逆地理编码、坐标系转换

/**
 * 百度地图开放平台接口
 */
class Baidumap extends MY_Controller{
	public function __construct(){
		parent::__construct();
		
		//百度地图配置
		define('BAIDUMAP_CONFIG', array(
			'ak' => '百度地图AK',
			'sk' => '百度地图SK',
			'host' => 'https://api.map.baidu.com',
		));
	}
	
	//计算请求验证的SN值
	private function caculateAKSN($sk, $uri, $param, $method = 'GET'){
		if($method === 'POST'){
			ksort($param);
		}
		$querystring = http_build_query($param);
		return md5(urlencode($uri.'?'.$querystring.$sk));
	}

	/**
	 * 地理编码
	 * 为保证高德、腾讯、百度地图通用,所以获取的是国测局坐标gcj02ll
	 * https://lbsyun.baidu.com/faq/api?title=webapi/guide/webservice-geocoding-base
	 */
	public function geocoder(){
		$address=trim(G('address'));
		if(empty($address))return json(101,'参数address不能为空');
		$city=trim(G('city'));
		$city OR $city='济南市';
		
		$coord_type=G('coordtype', 'gcj02ll'); //或百度坐标bd09ll
		
		$uri = '/geocoding/v3/';
		//构造请求串数组
		$param = array(
			'address' => $address,
			'city' => $city,
			'ret_coordtype' => $coord_type,
			'output' => 'json',
			'ak' => BAIDUMAP_CONFIG['ak'],
		);

		//调用sn计算函数,默认get请求
		$param['sn'] = $this->caculateAKSN(BAIDUMAP_CONFIG['sk'], $uri, $param);
		$res=my_curl(BAIDUMAP_CONFIG['host'].$uri,'GET',$param);
		$res=json_decode($res,true);
		$res['result']['location']['lng']=(string)$res['result']['location']['lng'];
		if(!is_array($res))return json(102,'请求失败,请重试');
		if($res['status']==0){
			$data=$res['result'];
			$data['lon_lat']=$data['location']['lng'].','.$data['location']['lat'];
			$data['lat_lon']=$data['location']['lat'].','.$data['location']['lng'];
			unset($data['location']);
			
			return json(0,'',$data);
		}else{
			return json($res['status'],$res['message']);
		}
	}

	/**
	 * 坐标转换
	 * 把国测局坐标gcj02ll转为百度坐标
	 * https://lbsyun.baidu.com/faq/api?title=webapi/guide/changeposition-base
	 * coord坐标经度在前
$model 转换方式可选值:
1:amap/tencent to bd09ll
2:gps to bd09ll
3:bd09ll to bd09mc
4:bd09mc to bd09ll
5:bd09ll to amap/tencent
6:bd09mc to amap/tencent
	 */
	public function getconv(){
		$coord=str_replace(',',',',trim(G('coord')));
		if(!preg_match('/^\d+(\.\d+)?,\d+(\.\d+)?$/',$coord))return json(101,'坐标格式错误');
		$model=G('model','1');
		$model=is_id($model) ? (int)$model : 1;
		
		$uri = '/geoconv/v2/';
		
		//需转换的源坐标,多组坐标以";"分隔
		$param=array(
			'coords' => $coord,
			'model' => $model,
			'output' => 'json',
			'ak' => BAIDUMAP_CONFIG['ak'],
		);
		$param['sn'] = $this->caculateAKSN(BAIDUMAP_CONFIG['sk'], $uri, $param);

		$res = my_curl(BAIDUMAP_CONFIG['host'].$uri, 'GET', $param);
		//echo $res;
		$res = json_decode($res,true);
		if(!is_array($res))return json(102,'请求失败,请重试');
		
		if($res['status']!=0){
			return json($res['status'], $res['message']);
		}
		$data=array(
			'lon_lat' => $res['result'][0]['x'].','.$res['result'][0]['y'],
			'lat_lon' => $res['result'][0]['y'].','.$res['result'][0]['x'],
		);
		return json(0,'',$data);
	}

	/**
	 * 全球逆地理编码
	 * https://lbsyun.baidu.com/faq/api?title=webapi/guide/webservice-geocoding-abroad-base
	 * coord坐标纬度在前,格式:纬度,经度
	 */
	public function reverse_geocoder(){
		$coord=str_replace(',',',',trim(G('coord')));
		if(empty($coord))return json(101,'参数coord不能为空');
		if(!preg_match('/^\d+(\.\d+)?,\d+(\.\d+)?$/',$coord))return json(102,'坐标格式错误');
		
		$coordtype=G('coordtype', 'gcj02ll'); //或百度坐标bd09ll
		
		$uri = '/reverse_geocoding/v3';
		
		$param=array(
			'location' => $coord,
			'coordtype' => $coordtype,
			'extensions_poi' => '1',
			'radius' => 50, //poi半径:0-3000米
			'region_data_source' => 1, //行政区划数据的来源:1统计局(把开发区作为行政区划返回),2民政部
			'entire_poi' => 1,
			'sort_strategy' => 'distance',
			//'poi_types' => '交通设施|公交线路|铁路',
			'output' => 'json',
			'ak' => BAIDUMAP_CONFIG['ak'],
		);
		//printr($param);
		$param['sn'] = $this->caculateAKSN(BAIDUMAP_CONFIG['sk'], $uri, $param);
		
		$res = my_curl(BAIDUMAP_CONFIG['host'].$uri, 'GET', $param);
		$res = json_decode($res,true);
		
		if($res['status']!=0){
			return json($res['status'], $res['message']);
		}
		$result=$res['result'];
		$data=array(
			'formatted_address' => $result['formatted_address'],
			'formatted_address_poi' => $result['formatted_address_poi'],
			'addressComponent' => $result['addressComponent'],
		);
		json(0,'',$data);
	}
}
2026-02-10 星期二 农历腊月二十三