SystemStorageServices.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
  4. // +----------------------------------------------------------------------
  5. // | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
  6. // +----------------------------------------------------------------------
  7. // | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
  8. // +----------------------------------------------------------------------
  9. // | Author: CRMEB Team <admin@crmeb.com>
  10. // +----------------------------------------------------------------------
  11. namespace app\services\system\config;
  12. use app\dao\system\config\SystemStorageDao;
  13. use app\services\BaseServices;
  14. use crmeb\exceptions\AdminException;
  15. use crmeb\services\CacheService;
  16. use crmeb\services\FormBuilder;
  17. use app\services\other\UploadService;
  18. /**
  19. * Class SystemStorageServices
  20. * @package app\services\system\config
  21. */
  22. class SystemStorageServices extends BaseServices
  23. {
  24. /**
  25. * SystemStorageServices constructor.
  26. * @param SystemStorageDao $dao
  27. */
  28. public function __construct(SystemStorageDao $dao)
  29. {
  30. $this->dao = $dao;
  31. }
  32. /**
  33. * @param array $where
  34. * @return array
  35. */
  36. public function getList(array $where)
  37. {
  38. [$page, $limit] = $this->getPageValue();
  39. $config = $this->getStorageConfig((int)$where['type']);
  40. $where['access_key'] = $config['accessKey'];
  41. $list = $this->dao->getList($where, ['*'], $page, $limit, 'add_time');
  42. foreach ($list as &$item) {
  43. $item['cname'] = str_replace('https://', '', $item['domain']);
  44. $item['_add_time'] = date('Y-m-d H:i:s', $item['add_time']);
  45. $item['_update_time'] = date('Y-m-d H:i:s', $item['update_time']);
  46. $service = UploadService::init($item['type']);
  47. $region = $service->getRegion();
  48. foreach ($region as $value) {
  49. if (strstr($item['region'], $value['value'])) {
  50. $item['_region'] = $value['label'];
  51. }
  52. }
  53. }
  54. $count = $this->dao->count($where);
  55. return compact('list', 'count');
  56. }
  57. /**
  58. * @param int $type
  59. * @return array
  60. * @throws \FormBuilder\Exception\FormBuilderException
  61. */
  62. public function getFormStorage(int $type)
  63. {
  64. $upload = UploadService::init($type);
  65. $config = $this->getStorageConfig($type);
  66. $ruleConfig = [];
  67. if (!$config['accessKey']) {
  68. $ruleConfig = [
  69. FormBuilder::input('accessKey', 'AccessKeyId', $config['accessKey'] ?? '')->required(),
  70. FormBuilder::input('secretKey', 'AccessKeySecret', $config['secretKey'] ?? '')->required(),
  71. ];
  72. }
  73. if ($type === 4 && isset($config['appid']) && !$config['appid']) {
  74. $ruleConfig[] = FormBuilder::input('appid', 'APPID', $config['appid'] ?? '')->required();
  75. }
  76. $rule = [
  77. FormBuilder::input('name', '空间名称')->required(),
  78. FormBuilder::select('region', '空间区域')->options($upload->getRegion())->required(),
  79. FormBuilder::radio('acl', '读写权限', 'public-read')->options([
  80. ['label' => '公共读(推荐)', 'value' => 'public-read'],
  81. ['label' => '公共读写', 'value' => 'public-read-write'],
  82. ])->required(),
  83. ];
  84. $rule = array_merge($ruleConfig, $rule);
  85. return create_form('添加云空间', $rule, '/system/config/storage/' . $type);
  86. }
  87. /**
  88. * @param int $type
  89. * @return array
  90. */
  91. public function getStorageConfig(int $type)
  92. {
  93. $config = [
  94. 'accessKey' => '',
  95. 'secretKey' => ''
  96. ];
  97. switch ($type) {
  98. case 2://七牛
  99. $config = [
  100. 'accessKey' => sys_config('qiniu_accessKey', ''),
  101. 'secretKey' => sys_config('qiniu_secretKey', ''),
  102. ];
  103. break;
  104. case 3:// oss 阿里云
  105. $config = [
  106. 'accessKey' => sys_config('accessKey', ''),
  107. 'secretKey' => sys_config('secretKey', ''),
  108. ];
  109. break;
  110. case 4:// cos 腾讯云
  111. $config = [
  112. 'accessKey' => sys_config('tengxun_accessKey', ''),
  113. 'secretKey' => sys_config('tengxun_secretKey', ''),
  114. 'appid' => sys_config('tengxun_appid', ''),
  115. ];
  116. break;
  117. case 5:// cos 京东云
  118. $config = [
  119. 'accessKey' => sys_config('jd_accessKey', ''),
  120. 'secretKey' => sys_config('jd_secretKey', ''),
  121. 'storageRegion' => sys_config('jd_storageRegion', ''),
  122. ];
  123. break;
  124. case 6:// cos 华为云
  125. $config = [
  126. 'accessKey' => sys_config('hw_accessKey', ''),
  127. 'secretKey' => sys_config('hw_secretKey', ''),
  128. ];
  129. break;
  130. case 7:// cos 天翼云
  131. $config = [
  132. 'accessKey' => sys_config('ty_accessKey', ''),
  133. 'secretKey' => sys_config('ty_secretKey', ''),
  134. ];
  135. break;
  136. }
  137. return $config;
  138. }
  139. /**
  140. * @param int $type
  141. * @return array
  142. * @throws \FormBuilder\Exception\FormBuilderException
  143. */
  144. public function getFormStorageConfig(int $type)
  145. {
  146. $config = $this->getStorageConfig($type);
  147. $rule = [
  148. FormBuilder::hidden('type', $type),
  149. FormBuilder::input('accessKey', 'AccessKeyId', $config['accessKey'] ?? '')->required(),
  150. FormBuilder::input('secretKey', 'AccessKeySecret', $config['secretKey'] ?? '')->required(),
  151. ];
  152. if ($type === 4) {
  153. $rule[] = FormBuilder::input('appid', 'APPID', $config['appid'] ?? '')->required();
  154. }
  155. if ($type === 5) {
  156. $rule[] = FormBuilder::input('storageRegion', 'storageRegion', $config['storageRegion'] ?? '')->required();
  157. }
  158. return create_form('配置信息', $rule, '/system/config/storage/config');
  159. }
  160. /**
  161. * 删除空间
  162. * @param int $id
  163. * @return bool
  164. * @throws \think\db\exception\DataNotFoundException
  165. * @throws \think\db\exception\DbException
  166. * @throws \think\db\exception\ModelNotFoundException
  167. */
  168. public function deleteStorage(int $id)
  169. {
  170. $storageInfo = $this->dao->get(['is_delete' => 0, 'id' => $id]);
  171. if (!$storageInfo) {
  172. throw new AdminException('删除的云存储不存在');
  173. }
  174. if ($storageInfo->status) {
  175. throw new AdminException('云存储正在使用中,需要启动其他空间才能删除');
  176. }
  177. try {
  178. $upload = UploadService::init($storageInfo->type);
  179. $res = $upload->deleteBucket($storageInfo->name, $storageInfo->region);
  180. // if (false === $res) {
  181. // throw new AdminException($upload->getError());
  182. // }
  183. } catch (\Throwable $e) {
  184. // throw new AdminException($e->getMessage());
  185. }
  186. $storageInfo->is_delete = 1;
  187. $storageInfo->save();
  188. CacheService::clear();
  189. return true;
  190. }
  191. public function saveConfig(int $type, array $data)
  192. {
  193. //保存配置信息
  194. if (1 !== $type) {
  195. $accessKey = $secretKey = $appid = $storageRegion = '';
  196. if (isset($data['accessKey']) && isset($data['secretKey']) && $data['accessKey'] && $data['secretKey']) {
  197. $accessKey = $data['accessKey'];
  198. $secretKey = $data['secretKey'];
  199. unset($data['accessKey'], $data['secretKey']);
  200. }
  201. if (isset($data['appid']) && $data['appid']) {
  202. $appid = $data['appid'];
  203. unset($data['appid']);
  204. }
  205. if (isset($data['storageRegion']) && $data['storageRegion']) {
  206. $storageRegion = $data['storageRegion'];
  207. unset($data['storageRegion']);
  208. }
  209. if (!$accessKey || !$secretKey) {
  210. return true;
  211. }
  212. /** @var SystemConfigServices $make */
  213. $make = app()->make(SystemConfigServices::class);
  214. switch ($type) {
  215. case 2://七牛
  216. $make->update('qiniu_accessKey', ['value' => json_encode($accessKey)], 'menu_name');
  217. $make->update('qiniu_secretKey', ['value' => json_encode($secretKey)], 'menu_name');
  218. break;
  219. case 3:// oss 阿里云
  220. $make->update('accessKey', ['value' => json_encode($accessKey)], 'menu_name');
  221. $make->update('secretKey', ['value' => json_encode($secretKey)], 'menu_name');
  222. break;
  223. case 4:// cos 腾讯云
  224. $make->update('tengxun_accessKey', ['value' => json_encode($accessKey)], 'menu_name');
  225. $make->update('tengxun_secretKey', ['value' => json_encode($secretKey)], 'menu_name');
  226. $make->update('tengxun_appid', ['value' => json_encode($appid)], 'menu_name');
  227. break;
  228. case 5:// oss 京东云
  229. $make->update('jd_accessKey', ['value' => json_encode($accessKey)], 'menu_name');
  230. $make->update('jd_secretKey', ['value' => json_encode($secretKey)], 'menu_name');
  231. $make->update('jd_storageRegion', ['value' => json_encode($storageRegion)], 'menu_name');
  232. break;
  233. case 6:// oss 华为云
  234. $make->update('hw_accessKey', ['value' => json_encode($accessKey)], 'menu_name');
  235. $make->update('hw_secretKey', ['value' => json_encode($secretKey)], 'menu_name');
  236. break;
  237. case 7:// oss 天翼云
  238. $make->update('ty_accessKey', ['value' => json_encode($accessKey)], 'menu_name');
  239. $make->update('ty_secretKey', ['value' => json_encode($secretKey)], 'menu_name');
  240. break;
  241. }
  242. CacheService::clear();
  243. }
  244. }
  245. /**
  246. * 保存云存储
  247. * @param int $type
  248. * @param array $data
  249. * @return mixed
  250. */
  251. public function saveStorage(int $type, array $data)
  252. {
  253. //保存配置信息
  254. $this->saveConfig($type, $data);
  255. if ($this->dao->count(['name' => $data['name']])) {
  256. throw new AdminException('云空间名称不能重复');
  257. }
  258. //保存云存储
  259. $data['type'] = $type;
  260. $upload = UploadService::init($type);
  261. $res = $upload->createBucket($data['name'], $data['region'], $data['acl']);
  262. if (false === $res) {
  263. throw new AdminException($upload->getError());
  264. }
  265. if (3 === $type) {
  266. $data['region'] = $this->getReagionHost($type, $data['region']);
  267. }
  268. $data['domain'] = $this->getDomain($type, $data['name'], $data['region'], sys_config('tengxun_appid'));
  269. if (2 === $type) {
  270. $domianList = $upload->getDomian($data['name']);
  271. $data['domain'] = $domianList[count($domianList) - 1];
  272. } else {
  273. $data['cname'] = $data['domain'];
  274. }
  275. if (4 === $type) {
  276. $data['name'] = $data['name'] . '-' . sys_config('tengxun_appid');
  277. }
  278. $data['add_time'] = time();
  279. $data['update_time'] = time();
  280. $config = $this->getStorageConfig($type);
  281. $data['access_key'] = $config['accessKey'];
  282. CacheService::clear();
  283. return $this->dao->save($data);
  284. }
  285. /**
  286. * 同步云储存桶
  287. * @param int $type
  288. * @return bool
  289. */
  290. public function synchronization(int $type)
  291. {
  292. $data = [];
  293. switch ($type) {
  294. case 2://七牛
  295. $config = $this->getStorageConfig($type);
  296. $upload = UploadService::init($type);
  297. $list = $upload->listbuckets();
  298. foreach ($list as $item) {
  299. if (!$this->dao->count(['name' => $item['id'], 'access_key' => $config['accessKey']])) {
  300. $data[] = [
  301. 'type' => $type,
  302. 'access_key' => $config['accessKey'],
  303. 'name' => $item['id'],
  304. 'region' => $item['region'],
  305. 'acl' => $item['private'] == 0 ? 'public-read' : 'private',
  306. 'status' => 0,
  307. 'is_delete' => 0,
  308. 'add_time' => time(),
  309. 'update_time' => time()
  310. ];
  311. }
  312. }
  313. break;
  314. case 3:// oss 阿里云
  315. $upload = UploadService::init($type);
  316. $list = $upload->listbuckets();
  317. $config = $this->getStorageConfig($type);
  318. foreach ($list as $item) {
  319. if (!$this->dao->count(['name' => $item['name'], 'access_key' => $config['accessKey']])) {
  320. $region = $this->getReagionHost($type, $item['location']);
  321. $data[] = [
  322. 'type' => $type,
  323. 'access_key' => $config['accessKey'],
  324. 'name' => $item['name'],
  325. 'region' => $region,
  326. 'acl' => 'public-read',
  327. 'domain' => $this->getDomain($type, $item['name'], $region),
  328. 'status' => 0,
  329. 'is_delete' => 0,
  330. 'add_time' => strtotime($item['createTime']),
  331. 'update_time' => time()
  332. ];
  333. }
  334. }
  335. break;
  336. case 4:// cos 腾讯云
  337. $upload = UploadService::init($type);
  338. $list = $upload->listbuckets();
  339. if (!empty($list['Name'])) {
  340. $newList = $list;
  341. $list = [];
  342. $list[] = $newList;
  343. }
  344. $config = $this->getStorageConfig($type);
  345. foreach ($list as $item) {
  346. if (!$this->dao->count(['name' => $item['Name'], 'access_key' => $config['accessKey']])) {
  347. $data[] = [
  348. 'type' => $type,
  349. 'access_key' => $config['accessKey'],
  350. 'name' => $item['Name'],
  351. 'region' => $item['Location'],
  352. 'acl' => 'public-read',
  353. 'status' => 0,
  354. 'domain' => sys_config('tengxun_appid') ? $this->getDomain($type, $item['Name'], $item['Location']) : '',
  355. 'is_delete' => 0,
  356. 'add_time' => strtotime($item['CreationDate']),
  357. 'update_time' => time()
  358. ];
  359. }
  360. }
  361. break;
  362. case 5:// cos 京东云
  363. $upload = UploadService::init($type);
  364. $res = $upload->listbuckets(sys_config('jd_storageRegion'));
  365. $list = $res['Buckets'];
  366. $location = explode('.', $res['@metadata']['effectiveUri'])[1] ?? 'cn-north-1';
  367. $config = $this->getStorageConfig($type);
  368. foreach ($list as $item) {
  369. if (!$this->dao->count(['name' => $item['Name'], 'access_key' => $config['accessKey']])) {
  370. $data[] = [
  371. 'type' => $type,
  372. 'access_key' => $config['accessKey'],
  373. 'name' => $item['Name'],
  374. 'region' => $location,
  375. 'acl' => 'public-read',
  376. 'status' => 0,
  377. 'domain' => $this->getDomain($type, $item['Name'], $location),
  378. 'is_delete' => 0,
  379. 'add_time' => time(),
  380. 'update_time' => time()
  381. ];
  382. }
  383. }
  384. break;
  385. case 6:// cos 华为云
  386. case 7:// cos 天翼云
  387. $upload = UploadService::init($type);
  388. $list = $upload->listbuckets();
  389. if (!empty($list['Name'])) {
  390. $newList = $list;
  391. $list = [];
  392. $list[] = $newList;
  393. }
  394. $config = $this->getStorageConfig($type);
  395. foreach ($list as $item) {
  396. if (!$this->dao->count(['name' => $item['Name'], 'access_key' => $config['accessKey']])) {
  397. $data[] = [
  398. 'type' => $type,
  399. 'access_key' => $config['accessKey'],
  400. 'name' => $item['Name'],
  401. 'region' => $item['Location'],
  402. 'acl' => 'public-read',
  403. 'status' => 0,
  404. 'domain' => $this->getDomain($type, $item['Name'], $item['Location']),
  405. 'is_delete' => 0,
  406. 'add_time' => strtotime($item['CreationDate']),
  407. 'update_time' => time()
  408. ];
  409. }
  410. }
  411. break;
  412. }
  413. if ($data) {
  414. $this->dao->saveAll($data);
  415. }
  416. CacheService::clear();
  417. return true;
  418. }
  419. /**
  420. * @param int $type
  421. * @param string $reagion
  422. * @return mixed|string
  423. */
  424. public function getReagionHost(int $type, string $reagion)
  425. {
  426. $upload = UploadService::init($type);
  427. $reagionList = $upload->getRegion();
  428. foreach ($reagionList as $item) {
  429. if (strstr($item['value'], $reagion) !== false) {
  430. return $item['value'];
  431. }
  432. }
  433. return '';
  434. }
  435. /**
  436. * 获取域名
  437. * @param int $type
  438. * @param string $name
  439. * @param string $reagion
  440. * @param string $appid
  441. * @return string
  442. */
  443. public function getDomain(int $type, string $name, string $reagion, string $appid = '')
  444. {
  445. $domainName = '';
  446. switch ($type) {
  447. case 3:// oss 阿里云
  448. $domainName = 'https://' . $name . '.' . $reagion;
  449. break;
  450. case 4:// cos 腾讯云
  451. $domainName = 'https://' . $name . ($appid ? '-' . $appid : '') . '.cos.' . $reagion . '.myqcloud.com';
  452. break;
  453. case 5:// cos 京东云
  454. $domainName = 'https://' . $name . '.s3.' . $reagion . '.jdcloud-oss.com';
  455. break;
  456. case 6:// cos 华为云
  457. $domainName = 'https://' . $name . '.obs.' . $reagion . '.myhuaweicloud.com';
  458. break;
  459. case 7:// cos 天翼云
  460. $domainName = 'https://' . $name . '.obs.' . $reagion . '.ctyun.cn';
  461. break;
  462. }
  463. return $domainName;
  464. }
  465. /**
  466. * 获取云存储配置
  467. * @param int $type
  468. * @return array|string[]
  469. */
  470. public function getConfig(int $type)
  471. {
  472. $res = ['name' => '', 'region' => '', 'domain' => '', 'cdn' => ''];
  473. try {
  474. $config = $this->dao->get(['type' => $type, 'status' => 1, 'is_delete' => 0]);
  475. if ($config) {
  476. return ['name' => $config->name, 'region' => $config->region, 'domain' => $config->domain, 'cdn' => $config->cdn];
  477. }
  478. } catch (\Throwable $e) {
  479. }
  480. return $res;
  481. }
  482. /**
  483. * 获取修改域名表单
  484. * @param int $id
  485. * @return array
  486. * @throws \FormBuilder\Exception\FormBuilderException
  487. */
  488. public function getUpdateDomainForm(int $id)
  489. {
  490. $storage = $this->dao->get(['id' => $id], ['domain', 'cdn']);
  491. $rule = [
  492. FormBuilder::input('domain', '空间域名', $storage['domain']),
  493. FormBuilder::input('cdn', 'cdn域名', $storage['cdn']),
  494. ];
  495. return create_form('修改空间域名', $rule, '/system/config/storage/domain/' . $id);
  496. }
  497. /**
  498. * 修改域名并绑定
  499. * @param int $id
  500. * @param string $domain
  501. * @return bool
  502. * @throws \think\db\exception\DataNotFoundException
  503. * @throws \think\db\exception\DbException
  504. * @throws \think\db\exception\ModelNotFoundException
  505. */
  506. public function updateDomain(int $id, string $domain, array $data = [])
  507. {
  508. $info = $this->dao->get($id);
  509. if (!$info) {
  510. throw new AdminException('数据不存在');
  511. }
  512. if ($info->domain != $domain) {
  513. $info->domain = $domain;
  514. $upload = UploadService::init($info->type);
  515. //是否添加过域名不存在需要绑定域名
  516. $domainList = $upload->getDomian($info->name, $info->region);
  517. $domainParse = parse_url($domain);
  518. if (false === $domainParse) {
  519. throw new AdminException('域名输入有误');
  520. }
  521. if (!in_array($domainParse['host'], $domainList)) {
  522. //绑定域名到云储存桶
  523. $res = $upload->bindDomian($info->name, $domain, $info->region);
  524. if (false === $res) {
  525. throw new AdminException($upload->getError());
  526. }
  527. }
  528. //七牛云需要通过接口获取cname
  529. if (2 === ((int)$info->type)) {
  530. $resDomain = $upload->getDomianInfo($domain);
  531. $info->cname = $resDomain['cname'] ?? '';
  532. }
  533. $info->save();
  534. }
  535. if ($info->cdn != $data['cdn']) {
  536. $info->cdn = $data['cdn'];
  537. $info->save();
  538. }
  539. CacheService::clear();
  540. return true;
  541. }
  542. }