AddonsService.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. <?php
  2. namespace services\common;
  3. use common\enums\AppEnum;
  4. use common\models\common\AddonsInvoker;
  5. use Yii;
  6. use yii\db\ActiveQuery;
  7. use yii\db\Exception;
  8. use yii\helpers\Json;
  9. use yii\web\NotFoundHttpException;
  10. use common\helpers\Url;
  11. use common\helpers\AddonHelper;
  12. use common\models\common\Addons;
  13. use common\enums\StatusEnum;
  14. use common\components\Service;
  15. use common\helpers\StringHelper;
  16. use common\helpers\ArrayHelper;
  17. use common\enums\RedisKeyEnum;
  18. use common\components\BaseAddonConfig;
  19. use common\helpers\Auth;
  20. use Overtrue\Pinyin\Pinyin;
  21. /**
  22. *
  23. * Class AddonsService
  24. * @package services\common
  25. * @author qimall
  26. */
  27. class AddonsService extends Service
  28. {
  29. /**
  30. * @return array
  31. * @throws \yii\web\UnauthorizedHttpException
  32. */
  33. public function getMenus()
  34. {
  35. $models = Addons::find()
  36. ->select(['title', 'name', 'group'])
  37. ->with('bindingMenu')
  38. ->asArray()
  39. ->all();
  40. $isSuperAdmin = Yii::$app->services->auth->isSuperAdmin();
  41. foreach ($models as $k => &$model) {
  42. $model['menuUrl'] = '';
  43. foreach ($model['bindingMenu'] as $bindingMenu) {
  44. if (isset($bindingMenu['route']) && empty($model['menuUrl'])) {
  45. $params = isset($bindingMenu['params']) ? Json::decode($bindingMenu['params']) : [];
  46. $model['menuUrl'] = Url::to(ArrayHelper::merge([$bindingMenu['route']], $params));
  47. if (!$isSuperAdmin && !Auth::verify($bindingMenu['route'])) {
  48. $model['menuUrl'] = '';
  49. }
  50. }
  51. }
  52. if (empty($model['menuUrl'])) {
  53. unset($models[$k]);
  54. } else {
  55. $model['menuUrl'] = urldecode($model['menuUrl']);
  56. }
  57. }
  58. // 创建分类数组
  59. $groups = array_keys(Yii::$app->params['addonsGroup']);
  60. $addons = [];
  61. foreach ($groups as $group) {
  62. !isset($addons[$group]) && $addons[$group] = [];
  63. }
  64. // 模块分类插入
  65. foreach ($models as $record) {
  66. $addons[$record['group']][] = $record;
  67. }
  68. // 删除空模块分类
  69. foreach ($addons as $key => $vlaue) {
  70. if (empty($vlaue)) {
  71. unset($addons[$key]);
  72. }
  73. }
  74. return $addons;
  75. }
  76. /**
  77. * 获取配置文件
  78. *
  79. * @param $name
  80. * @return bool|string
  81. */
  82. public function getConfigClass($name)
  83. {
  84. $class = AddonHelper::getAddonConfig($name);
  85. if (!class_exists($class)) {
  86. return false;
  87. }
  88. return $class;
  89. }
  90. /**
  91. * 获取本地插件列表
  92. *
  93. * @return array
  94. */
  95. public function getLocalList()
  96. {
  97. $addonDir = Yii::getAlias('@addons');
  98. // 获取插件列表
  99. $dirs = array_map('basename', glob($addonDir . '/*'));
  100. $list = Addons::find()
  101. ->where(['in', 'name', $dirs])
  102. ->asArray()
  103. ->all();
  104. $tmpAddons = [];
  105. foreach ($list as $addon) {
  106. $tmpAddons[$addon['name']] = $addon;
  107. }
  108. $addons = [];
  109. foreach ($dirs as $value) {
  110. // 判断是否安装
  111. if (!isset($tmpAddons[$value])) {
  112. $class = AddonHelper::getAddonConfig($value);
  113. // 实例化插件失败忽略执行
  114. if (class_exists($class)) {
  115. $config = new $class;
  116. $addons[$value] = $config->info;
  117. $addons[$value]['group'] = $config->group;
  118. }
  119. }
  120. }
  121. return $addons;
  122. }
  123. /**
  124. * 更新配置
  125. *
  126. * @param $name
  127. * @param BaseAddonConfig $config
  128. * @param $default_config
  129. * @return array|Addons|\yii\db\ActiveRecord|null
  130. * @throws NotFoundHttpException
  131. */
  132. public function update($name, $config, $default_config)
  133. {
  134. if (!($model = $this->findByName($name))) {
  135. $model = new Addons();
  136. }
  137. if (!empty($model->title)) $config->info['title'] = $model->title;
  138. $model->attributes = $config->info;
  139. $model->is_setting = $config->isSetting ? StatusEnum::ENABLED : StatusEnum::DISABLED;
  140. $model->is_rule = $config->isRule ? StatusEnum::ENABLED : StatusEnum::DISABLED;
  141. $model->is_merchant_route_map = $config->isMerchantRouteMap ? StatusEnum::ENABLED : StatusEnum::DISABLED;
  142. $model->group = $config->group;
  143. $model->bootstrap = $config->bootstrap ?? '';
  144. $model->service = $config->service ?? '';
  145. $model->default_config = $default_config;
  146. $model->console = $config->console ?? [];
  147. $model->wechat_message = $config->wechatMessage;
  148. $model->updated_at = time();
  149. // 首先字母转大写拼音
  150. if (($chinese = StringHelper::strToChineseCharacters($model->title)) && !empty($chinese[0])) {
  151. $title_initial = mb_substr($chinese[0][0], 0, 1, 'utf-8');
  152. // $model->title_initial = ucwords((new Pinyin())->abbr($title_initial));
  153. }
  154. if (!$model->save()) {
  155. throw new NotFoundHttpException($model->getErrorMessage());
  156. }
  157. // 更新缓存
  158. Yii::$app->services->addons->updateCacheByName($name);
  159. return $model;
  160. }
  161. /**
  162. * 获取列表
  163. *
  164. * @return array|\yii\db\ActiveRecord[]
  165. */
  166. public function getList()
  167. {
  168. return Addons::find()
  169. ->where(['status' => StatusEnum::ENABLED])
  170. ->asArray()
  171. ->all();
  172. }
  173. /**
  174. * @param $name
  175. * @return array|null|\yii\db\ActiveRecord
  176. */
  177. public function findByName($name)
  178. {
  179. return Addons::find()
  180. ->where(['name' => $name, 'status' => StatusEnum::ENABLED])
  181. ->one();
  182. }
  183. /**
  184. * @param array $names
  185. * @return array|\yii\db\ActiveRecord[]
  186. */
  187. public function findByNames(array $names)
  188. {
  189. return Addons::find()
  190. ->select(['id', 'name', 'title'])
  191. ->where(['status' => StatusEnum::ENABLED])
  192. ->andWhere(['in', 'name', $names])
  193. ->all();
  194. }
  195. /**
  196. * @param $name
  197. * @return array|null|\yii\db\ActiveRecord
  198. */
  199. public function findByNameWithBinding($name, $noCache = false)
  200. {
  201. $cacheKey = 'addonsConfig'. ':' . Yii::$app->id . ':' . $name;
  202. if (!$noCache && Yii::$app->cache->exists($cacheKey)) {
  203. return Yii::$app->cache->get($cacheKey);
  204. }
  205. $data = Addons::find()
  206. ->where(['name' => $name, 'status' => StatusEnum::ENABLED])
  207. ->with([
  208. 'bindingMenu' => function (ActiveQuery $query) {
  209. return $query->andWhere(['app_id' => Yii::$app->id]);
  210. },
  211. 'bindingCover'
  212. ])
  213. ->one();
  214. // 控制台避免写入缓存权限为 644 导致 www 用户组失败
  215. if (Yii::$app->id != AppEnum::CONSOLE && false == Yii::$app->cache->exists($cacheKey) ) {
  216. Yii::$app->cache->set($cacheKey, $data, 7200);
  217. }
  218. return $data;
  219. }
  220. /**
  221. * 获取插件名称列表
  222. *
  223. * @param bool $noCache
  224. * @return array|mixed|\yii\db\ActiveRecord[]
  225. */
  226. public function findAllNames($noCache = false)
  227. {
  228. if (Yii::$app->id == AppEnum::CONSOLE) {
  229. return $this->findAllInitData();
  230. }
  231. $cacheKey = RedisKeyEnum::suffix('addonsName');
  232. if (!$noCache && Yii::$app->cache->exists($cacheKey)) {
  233. return Yii::$app->cache->get($cacheKey);
  234. }
  235. $models = $this->findAllInitData();
  236. Yii::$app->cache->set($cacheKey, $models, 7200);
  237. return $models;
  238. }
  239. /**
  240. * @return array|\yii\db\ActiveRecord[]
  241. */
  242. public function findAllInitData()
  243. {
  244. return Addons::find()
  245. ->select(['name', 'is_merchant_route_map', 'service', 'updated_at'])
  246. ->where(['status' => StatusEnum::ENABLED])
  247. ->asArray()
  248. ->all();
  249. }
  250. /**
  251. * 触发更新缓存
  252. *
  253. * @param $name
  254. */
  255. public function updateCacheByName($name)
  256. {
  257. $this->findAllNames(true);
  258. $this->findByNameWithBinding($name, true);
  259. }
  260. /**
  261. * 创建公共调用
  262. */
  263. public function createInvoker(string $addons_name, array $invokers)
  264. {
  265. if (empty($invokers)) return;
  266. $time = time();
  267. $rows = [];
  268. $new_element_arr = [];
  269. foreach ($invokers as $key => $item) {
  270. foreach ($item as $k => $value) {
  271. $invoker_model = AddonsInvoker::findOne(['invoker_id'=>$value['invoker_id'],'addons_name' => $addons_name,'class'=>$value['class']]);
  272. if(!empty($invoker_model)){
  273. if($invoker_model['class']!=$value['class']||$invoker_model['method']!=$value['method']){
  274. $invoker_model->class = $value['class'];
  275. $invoker_model->method = $value['method'];
  276. $res = $invoker_model->save();
  277. if($res==false) throw new Exception('更新invoker失败');
  278. }
  279. }else{
  280. $row = [];
  281. $row['addons_name'] = $addons_name;
  282. $row['invoker_id'] = $value['invoker_id'];
  283. $row['class'] = $value['class'] ;
  284. $row['method'] = $value['method'];
  285. $row['status'] = StatusEnum::ENABLED;
  286. $row['created_at'] = $time;
  287. $row['updated_at'] = $time;
  288. $rows[] = $row;
  289. }
  290. $new_element_arr[$value['invoker_id']][]=['class'=>$value['class'],'method'=>$value['method'],'addons_name'=>$addons_name];
  291. }
  292. }
  293. $field = ['addons_name', 'invoker_id', 'class', 'method', 'status', 'created_at', 'updated_at'];
  294. // 批量插入数据
  295. if(!empty($rows)){
  296. $res = Yii::$app->db->createCommand()->batchInsert(AddonsInvoker::tableName(), $field, $rows)->execute();
  297. if($res==false) throw new Exception('插入invoker失败');
  298. }
  299. $redis = Yii::$app->redis;
  300. if(!empty($new_element_arr)){
  301. foreach ($new_element_arr as $invoker_id => $new_elements){
  302. $redis_key = RedisKeyEnum::suffix(RedisKeyEnum::GLOBAL_INVOKE, '',true) .':'. $invoker_id;
  303. $invokes = $redis->smembers($redis_key);
  304. $cache_arr = [];
  305. foreach ($invokes as $k=>$v){
  306. $temp_v = json_decode($v,true);
  307. $cache_arr[$temp_v['addons_name']] = $temp_v;
  308. }
  309. foreach ($new_elements as $new_element) {
  310. $cache_arr[$new_element['addons_name']] = $new_element;
  311. }
  312. $redis->del($redis_key);
  313. foreach ($cache_arr as $new_element) {
  314. $redis->sadd($redis_key, json_encode($new_element));
  315. }
  316. }
  317. }
  318. }
  319. }