SystemService.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  1. <?php
  2. namespace backend\modules\admin\services;
  3. use common\enums\OrderStatusEnum;
  4. use common\enums\RedisKeyEnum;
  5. use common\enums\StatusEnum;
  6. use common\models\census\CensusMall;
  7. use common\models\census\CensusMallGoods;
  8. use common\models\census\CensusMallSingleDay;
  9. use common\models\mall\Mall;
  10. use common\models\order\Order;
  11. use common\models\order\OrderDetail;
  12. use common\models\statistics\UserStatistics;
  13. use common\models\statistics\UserStatisticsTotal;
  14. use common\models\user\User;
  15. use Yii;
  16. /**
  17. * 系统配置
  18. * Namespace backend\modules\admin\services
  19. * @Author:lun
  20. * @Date:2024-09-18 18:07
  21. * @Copyright:copyright(c)2024 广东七件事集团
  22. */
  23. class SystemService extends BaseService
  24. {
  25. /**
  26. * 获取系统配置
  27. * @Author:lun
  28. * @Date:2024-09-18 18:15
  29. * @Copyright:copyright(c)2024 广东七件事集团
  30. * @return array | bool
  31. */
  32. public static function getSystemConfig()
  33. {
  34. $status = 0;// 状态[0=失败,1=成功]
  35. $data = [];// 返回数据
  36. $msg = '';// 返回信息
  37. try {
  38. if (stripos(PHP_OS, 'LINUX') !== false) {// 判断是否是 Linux 系统
  39. $status = 1;
  40. $data = self::getLinuxStatus();
  41. } else {
  42. $status = 0;
  43. $msg = '系统暂不支持';
  44. }
  45. } catch (\Exception $e) {
  46. $msg = '第' . $e->getLine() . '行,错误信息:' .$e->getMessage();
  47. }
  48. return ['status' => $status, 'data' => $data, 'msg' => $msg];
  49. }
  50. /**
  51. * 获取系统状态
  52. * @Author:lun
  53. * @Date:2024-09-18 18:51
  54. * @Copyright:copyright(c)2024 广东七件事集团
  55. * @return array
  56. */
  57. public static function getLinuxStatus()
  58. {
  59. $cpu = self::getCpuUsagePercent();
  60. $memory = self::getMemoryPercent();
  61. $disk = self::getDiskPercent();
  62. $redis = self::getRedisPercent();
  63. $site_num = Mall::getMallCount();
  64. return compact('cpu', 'memory', 'disk', 'redis', 'site_num');
  65. }
  66. /**
  67. * 获取内存使用率
  68. * @Author:lun
  69. * @Date:2024-09-18 18:46
  70. * @Copyright:copyright(c)2024 广东七件事集团
  71. * @return array
  72. */
  73. public static function getMemoryPercent()
  74. {
  75. exec('free -m', $output);
  76. $memoryInfo = explode("\n", $output[1]);
  77. $memoryParts = preg_split('/\s+/', $memoryInfo[0]);
  78. $total = $memoryParts[1];// 总内存
  79. $use_total = $memoryParts[2];// 已使用内存
  80. $freeMemory = $memoryParts[3];// 可用内存
  81. $percent = round(($use_total / $total) * 100, 2);
  82. return compact('use_total','total','percent');
  83. }
  84. /**
  85. * 获取磁盘使用率
  86. * @Author:lun
  87. * @Date:2024-09-19 17:20
  88. * @Copyright:copyright(c)2024 广东七件事集团
  89. * @return array
  90. */
  91. public static function getDiskPercent()
  92. {
  93. $use_total = $percent = 0;
  94. $total = disk_total_space("/"); // 更改为你想检测的磁盘路径
  95. if ($total == 0) return compact('use_total','total','percent');
  96. // 检测磁盘剩余空间
  97. $freeDiskSpace = disk_free_space("/"); // 更改为你想检测的磁盘路径
  98. // 计算磁盘使用率
  99. $use_total = $total - $freeDiskSpace;
  100. $percent = round(($use_total / $total) * 100, 2);
  101. return compact('use_total','total','percent');
  102. }
  103. /**
  104. * 获取CPU使用率
  105. * @Author:lun
  106. * @Date:2024-09-18 18:46
  107. * @Copyright:copyright(c)2024 广东七件事集团
  108. * @return float
  109. */
  110. public static function getCpuUsagePercent() {
  111. $prevStats = self::getCpuStats();
  112. sleep(1);
  113. $currStats = self::getCpuStats();
  114. $prevIdle = $prevStats['idle'] + $prevStats['iowait'];
  115. $prevNonIdle = $prevStats['user'] + $prevStats['nice'] + $prevStats['system'] + $prevStats['irq'] + $prevStats['softirq'];
  116. $prevTotal = $prevIdle + $prevNonIdle;
  117. $currIdle = $currStats['idle'] + $currStats['iowait'];
  118. $currNonIdle = $currStats['user'] + $currStats['nice'] + $currStats['system'] + $currStats['irq'] + $currStats['softirq'];
  119. $currTotal = $currIdle + $currNonIdle;
  120. $diffTotal = $currTotal - $prevTotal;
  121. $diffIdle = $currIdle - $prevIdle;
  122. return round((1 - $diffIdle / $diffTotal) * 100);
  123. }
  124. /**
  125. * 获取CPU使用情况
  126. * @Author:lun
  127. * @Date:2024-09-18 18:46
  128. * @Copyright:copyright(c)2024 广东七件事集团
  129. * @return int[]
  130. */
  131. public static function getCpuStats() {
  132. exec('cat /proc/stat', $output);
  133. $parts = preg_split('/\s+/', $output[0]);
  134. return [
  135. 'user' => (int)$parts[1],
  136. 'nice' => (int)$parts[2],
  137. 'system' => (int)$parts[3],
  138. 'idle' => (int)$parts[4],
  139. 'iowait' => (int)$parts[5],
  140. 'irq' => (int)$parts[6],
  141. 'softirq' => (int)$parts[7]
  142. ];
  143. }
  144. /**
  145. * 获取Redis使用率
  146. * @Author:lun
  147. * @Date:2024-09-19 16:57
  148. * @Copyright:copyright(c)2024 广东七件事集团
  149. * @return array
  150. */
  151. public static function getRedisPercent()
  152. {
  153. $redis = Yii::$app->redis;
  154. // 执行 INFO 命令
  155. $info = $redis->executeCommand('INFO', ['memory']);
  156. $lines = explode("\r\n", $info);
  157. $memoryUsage = [];
  158. foreach ($lines as $line) {
  159. if (strpos($line, ':') !== false) {
  160. list($key, $value) = explode(':', $line, 2);
  161. $memoryUsage[$key] = $value;
  162. }
  163. }
  164. $percent = 0;
  165. $use_total = empty($memoryUsage['use_memory']) ? 0 : $memoryUsage['use_memory'] / (1024 * 1024);// 已使用内存
  166. $total = empty($memoryUsage['maxmemory']) ? 0 : $memoryUsage['maxmemory'] / (1024 * 1024);// 最大内存
  167. if ($use_total > 0 && $total > 0) {// Redis 未开启内存限制,则返回 0
  168. $percent = round(($use_total / $total) * 100, 2);
  169. }
  170. return compact('use_total', 'total', 'percent');
  171. }
  172. /**
  173. * 概括数据
  174. * @return array
  175. */
  176. public static function getWraparound()
  177. {
  178. $oneStatus = [
  179. 'total_money' => 0,
  180. 'total_users' => 0,
  181. 'total_orders' => 0,
  182. 'total_sales' => 0,
  183. 'total_profit' => 0,
  184. ];
  185. // 获取一级概括数据
  186. try {
  187. $mallModel = CensusMall::find()
  188. ->distinct('mall_id')
  189. ->where(['status' => StatusEnum::ENABLED])
  190. ->asArray()
  191. ->orderBy('id desc');
  192. $batchSize = 1000;
  193. foreach ($mallModel->batch($batchSize) as $mallList) {
  194. foreach ($mallList as $item) {
  195. $oneStatus['total_users'] += $item['total_users'];
  196. $oneStatus['total_orders'] += $item['total_orders'];
  197. $oneStatus['total_money'] = bcadd($oneStatus['total_money'], $item['total_pay_money'], 2);
  198. }
  199. }
  200. } catch (\Exception $e) {
  201. // 处理异常
  202. Yii::error("Error fetching mall data: " . $e->getMessage());
  203. }
  204. // 获取一级概括数据
  205. try {
  206. $mallGoodsModel = CensusMallGoods::find()
  207. ->distinct('mall_id')
  208. ->where(['status' => StatusEnum::ENABLED])
  209. ->asArray()
  210. ->orderBy('id desc');
  211. foreach ($mallGoodsModel->batch($batchSize) as $mallGoodsList) {
  212. foreach ($mallGoodsList as $item) {
  213. $oneStatus['total_sales'] += $item['total_sales_volume'];
  214. }
  215. }
  216. } catch (\Exception $e) {
  217. // 处理异常
  218. Yii::error("Error fetching mall goods data: " . $e->getMessage());
  219. }
  220. // 获取总利润(不计算已关闭订单)
  221. try {
  222. $orderDetailModel = OrderDetail::find()
  223. ->where(['status' => [StatusEnum::ENABLED, StatusEnum::DISABLED]])
  224. ->andWhere(['!=', 'order_status', OrderStatusEnum::REPEAL])
  225. ->asArray()
  226. ->orderBy('id desc');
  227. foreach ($orderDetailModel->batch($batchSize) as $orderDetailList) {
  228. foreach ($orderDetailList as $item) {
  229. $profit = bcsub($item['goods_price'], bcmul($item['cost_price'], $item['num'], 2), 2);
  230. $oneStatus['total_profit'] = bcadd($oneStatus['total_profit'], $profit, 2);
  231. }
  232. }
  233. } catch (\Exception $e) {
  234. // 处理异常
  235. Yii::error("Error fetching order detail data: " . $e->getMessage());
  236. }
  237. // 获取销售额排名
  238. try {
  239. $yesterdayDate = date('Ymd', strtotime('-1 day'));
  240. $yesterdayMoneySubquery = (new \yii\db\Query())
  241. ->select('sum(pay_money) as total_money')
  242. ->from(UserStatistics::tableName() . ' ut2')
  243. ->where(['ut.mall_id' => new \yii\db\Expression('ut2.mall_id'), 'ut2.status' => 1, 'ut2.date' => $yesterdayDate]);
  244. $threeStatus['total_achievement_ranking'] = UserStatisticsTotal::find()
  245. ->alias('ut')
  246. ->leftJoin(Mall::tableName() . ' mall', 'mall.id=ut.mall_id')
  247. ->select([
  248. 'sum(ut.pay_money) as total_money',
  249. 'mall.name as mall_name',
  250. 'mall.id as mall_id',
  251. 'IFNULL((' . $yesterdayMoneySubquery->createCommand()->getRawSql() . '), 0) as yesterdayMoney'
  252. ])
  253. ->where(['ut.status' => StatusEnum::ENABLED])
  254. ->groupBy('ut.mall_id')
  255. ->orderBy('total_money desc')
  256. ->limit(10)
  257. ->asArray()
  258. ->all();
  259. foreach ($threeStatus['total_achievement_ranking'] as &$item) {
  260. $item['logo'] = Yii::$app->services->setting->mall->get($item['mall_id'], 'basic.logo');
  261. if (empty($item['logo'])) {
  262. $item['logo'] = Yii::$app->request->hostInfo . '/resources/img/admin/mall-logo.png';
  263. }
  264. }
  265. unset($item);
  266. } catch (\Exception $e) {
  267. // 处理异常
  268. Yii::error("Error fetching achievement ranking data: " . $e->getMessage());
  269. }
  270. // 获取热销商品排名
  271. $threeStatus['sales_volume_ranking'] = CensusMallGoods::find()
  272. ->with([
  273. 'goods' => function ($query) {
  274. $query->select(['id', 'goods_name', 'cover_pic']);
  275. },
  276. 'mall' => function ($query) {
  277. $query->select(['id', 'name']);
  278. }
  279. ])
  280. ->where(['status' => StatusEnum::ENABLED])
  281. ->orderBy('total_pay_money desc')
  282. ->select('total_pay_money, goods_id, mall_id')
  283. ->limit(10)
  284. ->asArray()
  285. ->all();
  286. // 获取热销商品排名
  287. $threeStatus['hot_sales_goods_ranking'] = CensusMallGoods::find()
  288. ->with([
  289. 'goods' => function ($query) {
  290. $query->select(['id', 'goods_name', 'cover_pic']);
  291. },
  292. 'mall' => function ($query) {
  293. $query->select(['id', 'name']);
  294. }
  295. ])
  296. ->where(['status' => StatusEnum::ENABLED])
  297. ->orderBy('total_sales_volume desc')
  298. ->select('total_sales_volume, goods_id, mall_id')
  299. ->limit(10)
  300. ->asArray()
  301. ->all();
  302. $threeStatus['sales_volume_ranking'] = self::getSalesRanking('total_pay_money');
  303. $threeStatus['hot_sales_goods_ranking'] = self::getSalesRanking('total_sales_volume');
  304. return compact('oneStatus', 'threeStatus');
  305. }
  306. public static function getSalesRanking($orderByField): array
  307. {
  308. try {
  309. return CensusMallGoods::find()
  310. ->with([
  311. 'goods' => function ($query) {
  312. $query->select(['id', 'goods_name', 'cover_pic']);
  313. },
  314. 'mall' => function ($query) {
  315. $query->select(['id', 'name']);
  316. }
  317. ])
  318. ->where(['status' => StatusEnum::ENABLED])
  319. ->orderBy($orderByField . ' desc')
  320. ->select([$orderByField, 'goods_id', 'mall_id'])
  321. ->limit(10)
  322. ->asArray()
  323. ->all();
  324. } catch (\Exception $e) {
  325. // 处理异常
  326. Yii::error("Error fetching sales ranking data: " . $e->getMessage());
  327. return [];
  328. }
  329. }
  330. public static function getSaleTrend()
  331. {
  332. // 获取用户活跃度
  333. $currentDate = date('Y-m-d', time());
  334. $saleTrendList = [];
  335. // 获取用户活跃度数据
  336. $censusMall = CensusMallSingleDay::find()
  337. ->where(['status' => StatusEnum::ENABLED])
  338. ->select('date, sum(total_actives) as total_actives, sum(add_users) as add_users, sum(add_pay_people_nums) as add_pay_people_nums')
  339. ->groupBy('date')
  340. ->asArray()
  341. ->all();
  342. if (!empty($censusMall)) $censusMall = array_column($censusMall, null, 'date');
  343. for ($i = 0; $i < 7; $i++) { // 获取最近7天数据
  344. $nowDay = $currentDate;
  345. if ($i > 0) {
  346. $nowDay = date("Y-m-d", strtotime("-" . $i . " day", strtotime($currentDate)));
  347. }
  348. $item = [
  349. 'date' => date("m-d", strtotime($nowDay)),
  350. 'total_actives' => isset($censusMall[$nowDay]) ? $censusMall[$nowDay]['total_actives'] : 0,
  351. 'add_users' => isset($censusMall[$nowDay]) ? $censusMall[$nowDay]['add_users'] : 0,
  352. 'add_pay_users' => isset($censusMall[$nowDay]) ? $censusMall[$nowDay]['add_pay_people_nums'] : 0,
  353. ];
  354. $saleTrendList[] = $item;
  355. }
  356. sort($saleTrendList);
  357. // 销售趋势
  358. return compact('saleTrendList');
  359. }
  360. public static function getSaleTrendByMonth(string $type = 'month')
  361. {
  362. // 获取销售额趋势(因为前端只有周和月所以默认获取前7个月订单)
  363. $currentMonth = date('Y-m', time());
  364. $eventMonth = date('Y-m', strtotime("-7 month", strtotime($currentMonth)));
  365. $orderDetailGroupBy = 'year, month';
  366. if ($type == 'week') {
  367. $orderDetailGroupBy = 'year, month, week';
  368. }
  369. // 销售额(订单支付金额) 利润(总销售额-总成本)
  370. $orderDetailSql = "sum(goods_price) as goods_price, sum(goods_price - cost_price) as profit_price,
  371. YEAR(FROM_UNIXTIME(created_at)) as year, MONTH(FROM_UNIXTIME(created_at)) as month, WEEK(FROM_UNIXTIME(created_at)) as week";
  372. $orderDetailModel = OrderDetail::find()
  373. ->where(['status' => [StatusEnum::ENABLED, StatusEnum::DISABLED]])
  374. ->andWhere(['!=', 'order_status', OrderStatusEnum::REPEAL])
  375. ->andWhere(['>=', 'created_at', strtotime($eventMonth)])
  376. ->select($orderDetailSql)
  377. ->asArray()
  378. ->groupBy($orderDetailGroupBy)
  379. ->orderBy('year desc, month desc')
  380. ->limit(6)
  381. ->all();
  382. return compact('orderDetailModel');
  383. }
  384. }