Cos.php 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
  4. // +----------------------------------------------------------------------
  5. // | Copyright (c) 2016~2023 https://www.crmeb.com All rights reserved.
  6. // +----------------------------------------------------------------------
  7. // | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
  8. // +----------------------------------------------------------------------
  9. // | Author: CRMEB Team <admin@crmeb.com>
  10. // +----------------------------------------------------------------------
  11. namespace crmeb\services\upload\storage;
  12. use crmeb\services\upload\BaseUpload;
  13. use crmeb\exceptions\AdminException;
  14. use crmeb\exceptions\UploadException;
  15. use GuzzleHttp\Psr7\Utils;
  16. use crmeb\services\upload\extend\cos\Client as CrmebClient;
  17. use think\Exception;
  18. /**
  19. * 腾讯云COS文件上传
  20. * Class COS
  21. * @package crmeb\services\upload\storage
  22. */
  23. class Cos extends BaseUpload
  24. {
  25. /**
  26. * 应用id
  27. * @var string
  28. */
  29. protected $appid;
  30. /**
  31. * accessKey
  32. * @var mixed
  33. */
  34. protected $accessKey;
  35. /**
  36. * secretKey
  37. * @var mixed
  38. */
  39. protected $secretKey;
  40. /**
  41. * 句柄
  42. * @var CrmebClient
  43. */
  44. protected $handle;
  45. /**
  46. * 空间域名 Domain
  47. * @var mixed
  48. */
  49. protected $uploadUrl;
  50. /**
  51. * 存储空间名称 公开空间
  52. * @var mixed
  53. */
  54. protected $storageName;
  55. /**
  56. * COS使用 所属地域
  57. * @var mixed|null
  58. */
  59. protected $storageRegion;
  60. /**
  61. * @var string
  62. */
  63. protected $cdn;
  64. /**
  65. * 水印位置
  66. * @var string[]
  67. */
  68. protected $position = [
  69. '1' => 'northwest',//:左上
  70. '2' => 'north',//:中上
  71. '3' => 'northeast',//:右上
  72. '4' => 'west',//:左中
  73. '5' => 'center',//:中部
  74. '6' => 'east',//:右中
  75. '7' => 'southwest',//:左下
  76. '8' => 'south',//:中下
  77. '9' => 'southeast',//:右下
  78. ];
  79. /**
  80. * 初始化
  81. * @param array $config
  82. * @return mixed|void
  83. */
  84. public function initialize(array $config)
  85. {
  86. parent::initialize($config);
  87. $this->accessKey = $config['accessKey'] ?? null;
  88. $this->appid = $config['appid'] ?? null;
  89. $this->secretKey = $config['secretKey'] ?? null;
  90. $this->uploadUrl = $this->checkUploadUrl($config['uploadUrl'] ?? '');
  91. $this->storageName = $config['storageName'] ?? null;
  92. $this->storageRegion = $config['storageRegion'] ?? null;
  93. $this->cdn = $config['cdn'] ?? null;
  94. $this->waterConfig['watermark_text_font'] = 'simfang仿宋.ttf';
  95. }
  96. /**
  97. * 实例化cos
  98. * @return CrmebClient
  99. */
  100. protected function app()
  101. {
  102. if (!$this->accessKey || !$this->secretKey) {
  103. throw new UploadException('请填写存储配置或者更换存储方式');
  104. }
  105. $this->handle = new CrmebClient([
  106. 'accessKey' => $this->accessKey,
  107. 'secretKey' => $this->secretKey,
  108. 'region' => $this->storageRegion ?: 'ap-chengdu',
  109. 'bucket' => $this->storageName,
  110. 'appid' => $this->appid,
  111. 'uploadUrl' => $this->uploadUrl
  112. ]);
  113. return $this->handle;
  114. }
  115. /**
  116. * 上传文件
  117. * @param string|null $file
  118. * @param bool $isStream 是否为流上传
  119. * @param string|null $fileContent 流内容
  120. * @return array|bool|\StdClass
  121. */
  122. protected function upload(string $file = null, bool $isStream = false, string $fileContent = null)
  123. {
  124. if (!$isStream) {
  125. $fileHandle = app()->request->file($file);
  126. if (!$fileHandle) {
  127. return $this->setError('上传的文件不存在');
  128. }
  129. if ($this->validate) {
  130. if (!in_array($fileHandle->getOriginalExtension(), $this->validate['fileExt'])) {
  131. return $this->setError('不合法的文件后缀:'.$fileHandle->getOriginalExtension());
  132. }
  133. if (filesize($fileHandle) > $this->validate['filesize']) {
  134. return $this->setError('文件过大');
  135. }
  136. if (!in_array($fileHandle->getOriginalMime(), $this->validate['fileMime'])) {
  137. return $this->setError('不合法的文件类型:'.$fileHandle->getOriginalMime());
  138. }
  139. }
  140. $key = $this->saveFileName($fileHandle->getRealPath(), $fileHandle->getOriginalExtension());
  141. $body = fopen($fileHandle->getRealPath(), 'rb');
  142. $body = (string)Utils::streamFor($body);
  143. } else {
  144. $key = $file;
  145. $body = $fileContent;
  146. }
  147. try {
  148. $key = $this->getUploadPath($key);
  149. $this->fileInfo->uploadInfo = $this->app()->putObject($key, $body);
  150. $this->fileInfo->filePath = ($this->cdn ?: $this->uploadUrl) . '/' . $key;
  151. $this->fileInfo->realName = isset($fileHandle) ? $fileHandle->getOriginalName() : $key;
  152. $this->fileInfo->fileName = $key;
  153. // $this->fileInfo->filePathWater = $this->water($this->fileInfo->filePath);
  154. // $this->authThumb && $this->thumb($this->fileInfo->filePath);
  155. return $this->fileInfo;
  156. } catch (UploadException $e) {
  157. return $this->setError($e->getMessage());
  158. }
  159. }
  160. /**
  161. * 文件流上传
  162. * @param $fileContent
  163. * @param string|null $key
  164. * @return array|bool|mixed|\StdClass
  165. */
  166. public function stream($fileContent, string $key = null)
  167. {
  168. if (!$key) {
  169. $key = $this->saveFileName();
  170. }
  171. return $this->upload($key, true, $fileContent);
  172. }
  173. /**
  174. * 文件上传
  175. * @param string $file
  176. * @param bool $realName
  177. * @return array|bool|mixed|\StdClass
  178. */
  179. public function move(string $file = 'file', $realName = false)
  180. {
  181. return $this->upload($file);
  182. }
  183. /**
  184. * 缩略图
  185. * @param string $filePath
  186. * @param string $fileName
  187. * @param string $type
  188. * @return array|mixed
  189. */
  190. public function thumb(string $filePath = '', string $fileName = '', string $type = 'all')
  191. {
  192. $filePath = $this->getFilePath($filePath);
  193. $data = ['big' => $filePath, 'mid' => $filePath, 'small' => $filePath];
  194. $this->fileInfo->filePathBig = $this->fileInfo->filePathMid = $this->fileInfo->filePathSmall = $this->fileInfo->filePathWater = $filePath;
  195. if ($filePath) {
  196. $config = $this->thumbConfig;
  197. foreach ($this->thumb as $v) {
  198. if ($type == 'all' || $type == $v) {
  199. $height = 'thumb_' . $v . '_height';
  200. $width = 'thumb_' . $v . '_width';
  201. $key = 'filePath' . ucfirst($v);
  202. // if (systemConfig('image_thumbnail_status', 1) && isset($config[$height]) && isset($config[$width])) {
  203. if (isset($config[$height]) && isset($config[$width])) {
  204. if (!strpos($filePath, '?watermark')) {
  205. $this->fileInfo->$key = $filePath . '?imageMogr2/thumbnail/' . $config[$width] . 'x' . $config[$height];
  206. }
  207. $this->fileInfo->$key = $this->water($this->fileInfo->$key);
  208. $data[$v] = $this->fileInfo->$key;
  209. } else {
  210. $this->fileInfo->$key = $this->water($this->fileInfo->$key);
  211. $data[$v] = $this->fileInfo->$key;
  212. }
  213. }
  214. }
  215. }
  216. return $data;
  217. }
  218. /**
  219. * 水印
  220. * @param string $filePath
  221. * @return mixed|string
  222. */
  223. public function water(string $filePath = '')
  224. {
  225. $filePath = $this->getFilePath($filePath);
  226. $waterConfig = $this->waterConfig;
  227. $waterPath = $filePath;
  228. if ($waterConfig['image_watermark_status'] && $filePath) {
  229. if (strpos($filePath, '?') === false) {
  230. $filePath .= '?watermark';
  231. } else {
  232. $filePath .= '|watermark';
  233. }
  234. switch ($waterConfig['watermark_type']) {
  235. case 1://图片
  236. if (!$waterConfig['watermark_image']) {
  237. throw new AdminException(400722);
  238. }
  239. $waterPath = $filePath .= '/1/image/' . base64_encode($waterConfig['watermark_image']) . '/gravity/' . ($this->position[$waterConfig['watermark_position']] ?? 'northwest') .'/dissolve/'. $waterConfig['watermark_opacity'] .'/dx/' . $waterConfig['watermark_x'] . '/dy/' . $waterConfig['watermark_y'];
  240. break;
  241. case 2://文字
  242. if (!$waterConfig['watermark_text']) {
  243. throw new AdminException(400723);
  244. }
  245. $waterPath = $filePath .= '/2/text/' . base64_encode($waterConfig['watermark_text']) .'/dissolve/'. $waterConfig['watermark_opacity'] . '/font/' . base64_encode($waterConfig['watermark_text_font']) . '/fill/' . base64_encode($waterConfig['watermark_text_color']) . '/fontsize/' . $waterConfig['watermark_text_size'] . '/gravity/' . ($this->position[$waterConfig['watermark_position']] ?? 'northwest') . '/dx/' . $waterConfig['watermark_x'] . '/dy/' . $waterConfig['watermark_y'];
  246. break;
  247. }
  248. }
  249. return $waterPath;
  250. }
  251. /**
  252. * 删除资源
  253. * @param $key
  254. * @return mixed
  255. */
  256. public function delete(string $filePath)
  257. {
  258. try {
  259. return $this->app()->deleteObject($this->storageName, $filePath);
  260. } catch (\Exception $e) {
  261. return $this->setError($e->getMessage());
  262. }
  263. }
  264. /**
  265. * 生成签名
  266. * @return array|mixed
  267. * @throws \Exception
  268. */
  269. public function getTempKeys()
  270. {
  271. $config = [
  272. 'url' => 'https://sts.tencentcloudapi.com/',
  273. 'domain' => 'sts.tencentcloudapi.com',
  274. 'proxy' => '',
  275. 'secretId' => $this->accessKey, // 固定密钥
  276. 'secretKey' => $this->secretKey, // 固定密钥
  277. 'bucket' => $this->storageName, // 换成你的 bucket
  278. 'region' => $this->storageRegion, // 换成 bucket 所在园区
  279. 'durationSeconds' => 1800, // 密钥有效期
  280. 'allowPrefix' => '*', // 这里改成允许的路径前缀,可以根据自己网站的用户登录态判断允许上传的具体路径,例子: a.jpg 或者 a/* 或者 * (使用通配符*存在重大安全风险, 请谨慎评估使用)
  281. // 密钥的权限列表。简单上传和分片需要以下的权限,其他权限列表请看 https://cloud.tencent.com/document/product/436/31923
  282. 'allowActions' => [
  283. // 简单上传
  284. 'name/cos:PutObject',
  285. 'name/cos:PostObject',
  286. // 分片上传
  287. 'name/cos:InitiateMultipartUpload',
  288. 'name/cos:ListMultipartUploads',
  289. 'name/cos:ListParts',
  290. 'name/cos:UploadPart',
  291. 'name/cos:CompleteMultipartUpload'
  292. ]
  293. ];
  294. // 获取临时密钥,计算签名
  295. $result = null;
  296. try{
  297. if(array_key_exists('policy', $config)){
  298. $policy = $config['policy'];
  299. }else{
  300. if(array_key_exists('bucket', $config)){
  301. $ShortBucketName = substr($config['bucket'],0, strripos($config['bucket'], '-'));
  302. $AppId = substr($config['bucket'], 1 + strripos($config['bucket'], '-'));
  303. }else{
  304. throw new Exception("bucket== null");
  305. }
  306. if(array_key_exists('allowPrefix', $config)){
  307. if(!(strpos($config['allowPrefix'], '/') === 0)){
  308. $config['allowPrefix'] = '/' . $config['allowPrefix'];
  309. }
  310. }else{
  311. throw new Exception("allowPrefix == null");
  312. }
  313. $policy = array(
  314. 'version'=> '2.0',
  315. 'statement'=> array(
  316. array(
  317. 'action'=> $config['allowActions'],
  318. 'effect'=> 'allow',
  319. 'principal'=> array('qcs'=> array('*')),
  320. 'resource'=> array(
  321. 'qcs::cos:' . $config['region'] . ':uid/' . $AppId . ':' . $config['bucket'] . $config['allowPrefix']
  322. )
  323. )
  324. )
  325. );
  326. }
  327. $policyStr = str_replace('\\/', '/', json_encode($policy));
  328. $Action = 'GetFederationToken';
  329. $Nonce = rand(10000, 20000);
  330. $Timestamp = time();
  331. $Method = 'POST';
  332. if(array_key_exists('durationSeconds', $config)){
  333. if(!(is_integer($config['durationSeconds']))){
  334. throw new Exception("durationSeconds must be a int type");
  335. }
  336. }
  337. $params = array(
  338. 'SecretId'=> $config['secretId'],
  339. 'Timestamp'=> $Timestamp,
  340. 'Nonce'=> $Nonce,
  341. 'Action'=> $Action,
  342. 'DurationSeconds'=> $config['durationSeconds'],
  343. 'Version'=>'2018-08-13',
  344. 'Name'=> 'cos',
  345. 'Region'=> $config['region'],
  346. 'Policy'=> urlencode($policyStr)
  347. );
  348. $params['Signature'] = $this->getSignature($params, $config['secretKey'], $Method, $config);
  349. $url = $config['url'];
  350. $ch = curl_init($url);
  351. if(array_key_exists('proxy', $config)){
  352. $config['proxy'] && curl_setopt($ch, CURLOPT_PROXY, $config['proxy']);
  353. }
  354. curl_setopt($ch, CURLOPT_HEADER, 0);
  355. curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,0);
  356. curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,0);
  357. curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  358. curl_setopt($ch, CURLOPT_POST, 1);
  359. curl_setopt($ch, CURLOPT_POSTFIELDS, $this->json2str($params));
  360. $result = curl_exec($ch);
  361. if(curl_errno($ch)) $result = curl_error($ch);
  362. curl_close($ch);
  363. $result = json_decode($result, 1);
  364. if (isset($result['Response'])) {
  365. $result = $result['Response'];
  366. if(isset($result['Error'])){
  367. throw new Exception("get cam failed");
  368. }
  369. $result['startTime'] = $result['ExpiredTime'] - $config['durationSeconds'];
  370. }
  371. $result = $this->backwardCompat($result);
  372. $result['url'] = $this->uploadUrl . '/';
  373. $result['type'] = 'COS';
  374. $result['cdn'] = $this->cdn;
  375. $result['bucket'] = $this->storageName;
  376. $result['region'] = $this->storageRegion;
  377. return $result;
  378. }catch(\Exception $e){
  379. if($result == null){
  380. $result = "error: " . + $e->getMessage();
  381. }else{
  382. $result = json_encode($result);
  383. }
  384. throw new Exception($result);
  385. }
  386. }
  387. /**
  388. * 计算临时密钥用的签名
  389. * @param $opt
  390. * @param $key
  391. * @param $method
  392. * @param $config
  393. * @return string
  394. */
  395. public function getSignature($opt, $key, $method, $config)
  396. {
  397. $formatString = $method . $config['domain'] . '/?' . $this->json2str($opt, 1);
  398. $sign = hash_hmac('sha1', $formatString, $key);
  399. $sign = base64_encode($this->_hex2bin($sign));
  400. return $sign;
  401. }
  402. public function _hex2bin($data)
  403. {
  404. $len = strlen($data);
  405. return pack("H" . $len, $data);
  406. }
  407. // obj 转 query string
  408. public function json2str($obj, $notEncode = false)
  409. {
  410. ksort($obj);
  411. $arr = array();
  412. if (!is_array($obj)) {
  413. return $this->setError($obj . " must be a array");
  414. }
  415. foreach ($obj as $key => $val) {
  416. array_push($arr, $key . '=' . ($notEncode ? $val : rawurlencode($val)));
  417. }
  418. return join('&', $arr);
  419. }
  420. // v2接口的key首字母小写,v3改成大写,此处做了向下兼容
  421. public function backwardCompat($result)
  422. {
  423. if (!is_array($result)) {
  424. return $this->setError($result . " must be a array");
  425. }
  426. $compat = array();
  427. foreach ($result as $key => $value) {
  428. if (is_array($value)) {
  429. $compat[lcfirst($key)] = $this->backwardCompat($value);
  430. } elseif ($key == 'Token') {
  431. $compat['sessionToken'] = $value;
  432. } else {
  433. $compat[lcfirst($key)] = $value;
  434. }
  435. }
  436. return $compat;
  437. }
  438. /**
  439. * 桶列表
  440. * @param string|null $region
  441. * @param bool $line
  442. * @param bool $shared
  443. * @return array|mixed
  444. * "Name" => "record-1254950941"
  445. * "Location" => "ap-chengdu"
  446. * "CreationDate" => "2019-05-16T08:33:29Z"
  447. * "BucketType" => "cos"
  448. */
  449. public function listbuckets(string $region = null, bool $line = false, bool $shared = false)
  450. {
  451. try {
  452. $res = $this->app()->listBuckets();
  453. return $res['Buckets']['Bucket'] ?? [];
  454. } catch (\Throwable $e) {
  455. return [];
  456. }
  457. }
  458. /**
  459. * 创建桶
  460. * @param string $name
  461. * @param string $region
  462. * @param string $acl public-read=公共独写
  463. * @return bool|mixed
  464. */
  465. public function createBucket(string $name, string $region = '', string $acl = 'public-read')
  466. {
  467. $regionData = $this->getRegion();
  468. $regionData = array_column($regionData, 'value');
  469. if (!in_array($region, $regionData)) {
  470. return $this->setError('COS:无效的区域!');
  471. }
  472. $this->storageRegion = $region;
  473. $app = $this->app();
  474. //检测桶
  475. try {
  476. $app->headBucket($name);
  477. } catch (\Throwable $e) {
  478. //桶不存在返回404
  479. if (strstr('404', $e->getMessage())) {
  480. return $this->setError('COS:' . $e->getMessage());
  481. }
  482. }
  483. //创建桶
  484. try {
  485. $res = $app->createBucket($name . '-' . $this->appid, '', $acl);
  486. } catch (\Throwable $e) {
  487. if (strstr('[curl] 6', $e->getMessage())) {
  488. return $this->setError('COS:无效的区域!!');
  489. } else if (strstr('Access Denied.', $e->getMessage())) {
  490. return $this->setError('COS:无权访问');
  491. }
  492. return $this->setError('COS:' . $e->getMessage());
  493. }
  494. return $res;
  495. }
  496. /**
  497. * 删除桶
  498. * @param string $name
  499. * @return bool|mixed
  500. */
  501. public function deleteBucket(string $name)
  502. {
  503. try {
  504. $this->app()->deleteBucket($name);
  505. return true;
  506. } catch (\Throwable $e) {
  507. return $this->setError($e->getMessage());
  508. }
  509. }
  510. /**
  511. * @param string $name
  512. * @param string|null $region
  513. * @return array|object
  514. */
  515. public function getDomian(string $name, string $region = null)
  516. {
  517. $this->storageRegion = $region;
  518. try {
  519. $res = $this->app()->getBucketDomain($name);
  520. //$domainRules = $res['DomainRule'];
  521. //return [$domainRules['Name']];
  522. $domainRules = $res['DomainRules'];
  523. return array_column($domainRules, 'Name');
  524. } catch (\Throwable $e) {
  525. }
  526. return [];
  527. }
  528. /**
  529. * 绑定域名
  530. * @param string $name
  531. * @param string $domain
  532. * @param string|null $region
  533. * @return bool|mixed
  534. */
  535. public function bindDomian(string $name, string $domain, string $region = null)
  536. {
  537. $this->storageRegion = $region;
  538. $parseDomin = parse_url($domain);
  539. try {
  540. $res = $this->app()->putBucketDomain($name, '', [
  541. 'Name' => $parseDomin['host'],
  542. 'Status' => 'ENABLED',
  543. 'Type' => 'REST',
  544. 'ForcedReplacement' => 'CNAME'
  545. ]);
  546. if (method_exists($res, 'toArray')) {
  547. $res = $res->toArray();
  548. }
  549. if ($res['RequestId'] ?? null) {
  550. return true;
  551. }
  552. } catch (\Throwable $e) {
  553. if ($message = $this->setMessage($e->getMessage())) {
  554. return $this->setError($message);
  555. }
  556. return $this->setError($e->getMessage());
  557. }
  558. return false;
  559. }
  560. /**
  561. * 处理
  562. * @param string $message
  563. * @return string
  564. */
  565. protected function setMessage(string $message)
  566. {
  567. $data = [
  568. 'The specified bucket does not exist.' => '指定的存储桶不存在。',
  569. 'Please add CNAME/TXT record to DNS then try again later. Please allow up to 10 mins before your DNS takes effect.' => '请将CNAME记录添加到DNS,然后稍后重试。在DNS生效前,请等待最多10分钟。'
  570. ];
  571. $msg = $data[$message] ?? '';
  572. if ($msg) {
  573. return $msg;
  574. }
  575. foreach ($data as $item) {
  576. if (strstr($message, $item)) {
  577. return $item;
  578. }
  579. }
  580. return '';
  581. }
  582. /**
  583. * 设置跨域
  584. * @param string $name
  585. * @param string $region
  586. * @return bool
  587. */
  588. public function setBucketCors(string $name, string $region)
  589. {
  590. $this->storageRegion = $region;
  591. try {
  592. $res = $this->app()->putBucketCors($name, [
  593. 'AllowedHeader' => ['*'],
  594. 'AllowedMethod' => ['PUT', 'GET', 'POST', 'DELETE', 'HEAD'],
  595. 'AllowedOrigin' => ['*'],
  596. 'ExposeHeader' => ['ETag', 'Content-Length', 'x-cos-request-id'],
  597. 'MaxAgeSeconds' => 12
  598. ]);
  599. if (isset($res['RequestId'])) {
  600. return true;
  601. }
  602. } catch (\Throwable $e) {
  603. return $this->setError($e->getMessage());
  604. }
  605. return false;
  606. }
  607. /**
  608. * 地域
  609. * @return mixed|\string[][]
  610. */
  611. public function getRegion()
  612. {
  613. return [
  614. [
  615. 'value' => 'ap-chengdu',
  616. 'label' => '成都'
  617. ],
  618. [
  619. 'value' => 'ap-shanghai',
  620. 'label' => '上海'
  621. ],
  622. [
  623. 'value' => 'ap-guangzhou',
  624. 'label' => '广州'
  625. ],
  626. [
  627. 'value' => 'ap-nanjing',
  628. 'label' => '南京'
  629. ],
  630. [
  631. 'value' => 'ap-beijing',
  632. 'label' => '北京'
  633. ],
  634. [
  635. 'value' => 'ap-chongqing',
  636. 'label' => '重庆'
  637. ],
  638. [
  639. 'value' => 'ap-shenzhen-fsi',
  640. 'label' => '深圳金融'
  641. ],
  642. [
  643. 'value' => 'ap-shanghai-fsi',
  644. 'label' => '上海金融'
  645. ],
  646. [
  647. 'value' => 'ap-beijing-fsi',
  648. 'label' => '北京金融'
  649. ],
  650. [
  651. 'value' => 'ap-hongkong',
  652. 'label' => '中国香港'
  653. ],
  654. [
  655. 'value' => 'ap-singapore',
  656. 'label' => '新加坡'
  657. ],
  658. [
  659. 'value' => 'ap-mumbai',
  660. 'label' => '孟买'
  661. ],
  662. [
  663. 'value' => 'ap-jakarta',
  664. 'label' => '雅加达'
  665. ],
  666. [
  667. 'value' => 'ap-seoul',
  668. 'label' => '首尔'
  669. ],
  670. [
  671. 'value' => 'ap-bangkok',
  672. 'label' => '曼谷'
  673. ],
  674. [
  675. 'value' => 'ap-tokyo',
  676. 'label' => '东京'
  677. ],
  678. [
  679. 'value' => 'na-siliconvalley',
  680. 'label' => '硅谷(美西)'
  681. ],
  682. [
  683. 'value' => 'na-ashburn',
  684. 'label' => '弗吉尼亚(美东)'
  685. ],
  686. [
  687. 'value' => 'na-toronto',
  688. 'label' => '多伦多'
  689. ],
  690. [
  691. 'value' => 'sa-saopaulo',
  692. 'label' => '圣保罗'
  693. ],
  694. [
  695. 'value' => 'eu-frankfurt',
  696. 'label' => '法兰克福'
  697. ],
  698. [
  699. 'value' => 'eu-moscow',
  700. 'label' => '莫斯科'
  701. ]
  702. ];
  703. }
  704. }