= 1 小时,到期前 5 分钟就刷新 if (!$force) { $cached = json_decode((string)Setting::get($familyId, 'cache_sts_aliyun', ''), true); if (is_array($cached) && !empty($cached['expires']) && $cached['expires'] > time() + 300 && ($cached['role'] ?? '') === $roleName) { return $memo[$memoKey] = $cached + ['ok' => true, 'error' => '']; } } // IMDSv2:先拿一次性 token(拿不到就降级走 v1,兼容老实例) $token = ''; $t = Http::send('PUT', self::BASE . '../api/token', ['X-aliyun-ecs-metadata-token-ttl-seconds: 21600'], null, self::TIMEOUT); if ($t['ok'] && trim((string)$t['body']) !== '') { $token = trim($t['body']); } $auth = $token !== '' ? ["X-aliyun-ecs-metadata-token: {$token}"] : []; $credsUrl = self::BASE . 'ram/security-credentials/'; if ($roleName === '') { $r = Http::send('GET', $credsUrl, $auth, null, self::TIMEOUT); if (!$r['ok']) { return $memo[$memoKey] = self::fail('无法访问 ECS 元数据服务(这台服务器可能不是阿里云 ECS)'); } $first = trim((string)strtok((string)$r['body'], "\r\n")); if ($first === '') { return $memo[$memoKey] = self::fail('该实例没有绑定 RAM 角色'); } $roleName = $first; } $r = Http::send('GET', $credsUrl . rawurlencode($roleName), $auth, null, self::TIMEOUT); if (!$r['ok']) { return $memo[$memoKey] = self::fail("取 RAM 角色 {$roleName} 的临时凭据失败"); } $json = json_decode((string)$r['body'], true); if (!is_array($json) || empty($json['AccessKeyId']) || empty($json['AccessKeySecret'])) { $code = is_array($json) ? (string)($json['Code'] ?? '') : ''; return $memo[$memoKey] = self::fail('元数据返回的内容看不懂' . ($code !== '' ? "({$code})" : '')); } $cred = [ 'ok' => true, 'id' => (string)$json['AccessKeyId'], 'secret' => (string)$json['AccessKeySecret'], 'token' => (string)($json['SecurityToken'] ?? ''), 'expires' => !empty($json['Expiration']) ? (int)strtotime((string)$json['Expiration']) : time() + 1800, 'error' => '', 'role' => $roleName, ]; Setting::set($familyId, 'cache_sts_aliyun', json_encode($cred, JSON_UNESCAPED_UNICODE)); return $memo[$memoKey] = $cred; } /** 本机地域,用于判断内网 endpoint 是否可用(1 小时缓存) */ public static function region(int $familyId, bool $force = false): string { static $memo = null; if ($memo !== null && !$force) { return $memo; } if (!$force) { $c = json_decode((string)Setting::get($familyId, 'cache_ecs_region', ''), true); if (is_array($c) && !empty($c['expires']) && $c['expires'] > time()) { return $memo = (string)($c['region'] ?? ''); } } $r = Http::send('GET', self::BASE . 'region-id', [], null, self::TIMEOUT); $region = $r['ok'] ? trim((string)$r['body']) : ''; Setting::set($familyId, 'cache_ecs_region', json_encode(['region' => $region, 'expires' => time() + 3600])); return $memo = $region; } /** 清掉全部元数据缓存(换角色/改配置后调用) */ public static function clearCache(int $familyId): void { foreach (['cache_sts_aliyun', 'cache_ecs_region', 'cache_oss_internal'] as $k) { Setting::set($familyId, $k, ''); } } private static function fail(string $msg): array { return ['ok' => false, 'id' => '', 'secret' => '', 'token' => '', 'expires' => 0, 'error' => $msg, 'role' => '']; } }