SeckillLogic.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | likeshop100%开源免费商用商城系统
  4. // +----------------------------------------------------------------------
  5. // | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
  6. // | 开源版本可自由商用,可去除界面版权logo
  7. // | 商业版本务必购买商业授权,以免引起法律纠纷
  8. // | 禁止对系统程序代码以任何目的,任何形式的再发布
  9. // | gitee下载:https://gitee.com/likeshop_gitee
  10. // | github下载:https://github.com/likeshop-github
  11. // | 访问官网:https://www.likeshop.cn
  12. // | 访问社区:https://home.likeshop.cn
  13. // | 访问手册:http://doc.likeshop.cn
  14. // | 微信公众号:likeshop技术社区
  15. // | likeshop团队 版权所有 拥有最终解释权
  16. // +----------------------------------------------------------------------
  17. // | author: likeshopTeam
  18. // +----------------------------------------------------------------------
  19. namespace app\adminapi\logic\marketing;
  20. use app\common\enum\ActivityEnum;
  21. use app\common\enum\OrderEnum;
  22. use app\common\enum\SeckillEnum;
  23. use app\common\model\Goods;
  24. use app\common\model\GoodsActivity;
  25. use app\common\model\GoodsItem;
  26. use app\common\model\Order;
  27. use app\common\model\SeckillActivity;
  28. use app\common\model\SeckillGoods;
  29. use app\common\model\SeckillGoodsItem;
  30. use app\common\service\FileService;
  31. use Exception;
  32. use think\facade\Db;
  33. class SeckillLogic
  34. {
  35. /**
  36. * @notes 优惠券概况
  37. * @author 张无忌
  38. * @date 2021/7/26 16:45
  39. */
  40. public static function survey()
  41. {
  42. $detail['base'] = [
  43. 'total_activity_num' => (new SeckillActivity())->count(),
  44. 'total_browse_volume' => (new SeckillGoods())->alias('SG')
  45. ->join('seckill_activity SA', 'SA.id = SG.seckill_id')
  46. ->sum('SG.browse_volume'),
  47. 'total_sales_amount' => (new SeckillGoodsItem())->alias('SGI')
  48. ->join('seckill_activity SA', 'SA.id = SGI.seckill_id')
  49. ->sum('SGI.sales_amount'),
  50. 'total_sales_volume' => (new SeckillGoodsItem())->alias('SGI')
  51. ->join('seckill_activity SA', 'SA.id = SGI.seckill_id')
  52. ->sum('SGI.sales_volume'),
  53. 'total_closing_order' => (new SeckillGoodsItem())->alias('SGI')
  54. ->join('seckill_activity SA', 'SA.id = SGI.seckill_id')
  55. ->sum('SGI.closing_order')
  56. ];
  57. $detail['sort_browse_volume'] = (new SeckillGoods())->alias('SG')
  58. ->field(['SA.id,SA.name,SG.browse_volume'])
  59. ->join('seckill_activity SA', 'SA.id = SG.seckill_id')
  60. ->group('SG.seckill_id')
  61. ->order('SG.browse_volume desc, SG.id desc')
  62. ->limit(10)
  63. ->select()->toArray();
  64. $detail['sort_sales_volume'] = (new SeckillGoodsItem())->alias('SGI')
  65. ->field(['SA.id,SA.name,SGI.sales_volume'])
  66. ->join('seckill_activity SA', 'SA.id = SGI.seckill_id')
  67. ->group('SGI.seckill_id')
  68. ->order('SGI.sales_volume desc, SGI.id desc')
  69. ->limit(10)
  70. ->select()->toArray();
  71. return $detail;
  72. }
  73. /**
  74. * @notes 获取秒杀活动详细
  75. * @param $params
  76. * @return array
  77. * @throws @\think\db\exception\DataNotFoundException
  78. * @throws @\think\db\exception\DbException
  79. * @throws @\think\db\exception\ModelNotFoundException
  80. * @author 张无忌
  81. * @date 2021/7/26 15:20
  82. */
  83. public static function detail($params)
  84. {
  85. $detail = (new SeckillActivity())
  86. ->withoutField(['create_time', 'update_time', 'delete_time'])
  87. ->with(['goods'])
  88. ->findOrEmpty($params['id'])
  89. ->toArray();
  90. if (!$detail) {
  91. return [];
  92. }
  93. $goodsItem = (new SeckillGoodsItem())->alias('SGI')
  94. ->field('SGI.*, GI.stock')
  95. ->join('goods_item GI', 'GI.id = SGI.item_id')
  96. ->where(['seckill_id'=>$params['id']])
  97. ->select()->toArray();
  98. $detail['start_time'] = date('Y-m-d H:i:s', $detail['start_time']);
  99. $detail['end_time'] = date('Y-m-d H:i:s', $detail['end_time']);
  100. foreach ($detail['goods'] as &$goods) {
  101. $g = (new Goods())->field('total_stock,spec_type,min_price')
  102. ->where(['id'=>$goods['goods_id']])
  103. ->findOrEmpty();
  104. $snap = $goods['goods_snap'];
  105. $goods['id'] = $goods['goods_id'];
  106. $goods['name'] = $snap['name'];
  107. $goods['image'] = $snap['image'];
  108. $goods['total_stock'] = $g['total_stock'];
  109. $goods['spec_type'] = $g['spec_type'];
  110. $goods['sell_price'] = $g['min_price'];
  111. $goods['item'] = [];
  112. foreach ($goodsItem as $item) {
  113. if ($item['goods_id'] == $goods['goods_id']) {
  114. $goods['item'][] = [
  115. 'id' => $item['item_id'],
  116. 'goods_id' => $item['goods_id'],
  117. 'spec_value_str' => $item['spec_value_str'],
  118. 'sell_price' => $item['sell_price'],
  119. 'seckill_price' => $item['seckill_price'],
  120. 'stock' => $item['stock'],
  121. ];
  122. unset($item);
  123. }
  124. }
  125. unset($goods['seckill_id']);
  126. unset($goods['goods_snap']);
  127. unset($goods['goods_id']);
  128. }
  129. return $detail;
  130. }
  131. /**
  132. * @notes 秒杀数据信息
  133. * @param $params
  134. * @return array
  135. * @throws @\think\db\exception\DataNotFoundException
  136. * @throws @\think\db\exception\DbException
  137. * @throws @\think\db\exception\ModelNotFoundException
  138. * @author 张无忌
  139. * @date 2021/7/26 15:31
  140. */
  141. public static function info($params)
  142. {
  143. $detail = (new SeckillActivity())
  144. ->withoutField(['create_time', 'update_time', 'delete_time'])
  145. ->with(['goods'])
  146. ->findOrEmpty($params['id'])
  147. ->toArray();
  148. if (!$detail) {
  149. return [];
  150. }
  151. $goodsItem = (new SeckillGoodsItem())
  152. ->field(true)
  153. ->where(['seckill_id'=>$params['id']])
  154. ->select()->toArray();
  155. $detail['start_time'] = date('Y-m-d H:i:s', $detail['start_time']);
  156. $detail['end_time'] = date('Y-m-d H:i:s', $detail['end_time']);
  157. $detail['status_text'] = SeckillEnum::getSeckillStatusDesc($detail['status']);
  158. $detail['data'] = [
  159. 'browse_volume' => (new SeckillGoods())->where(['seckill_id'=>$params['id']])->sum('browse_volume'),
  160. 'sales_amount' => (new SeckillGoodsItem())->where(['seckill_id'=>$params['id']])->sum('sales_amount'),
  161. 'sales_volume' => (new SeckillGoodsItem())->where(['seckill_id'=>$params['id']])->sum('sales_volume'),
  162. 'closing_order' => (new SeckillGoodsItem())->where(['seckill_id'=>$params['id']])->sum('closing_order')
  163. ];
  164. foreach ($detail['goods'] as &$goods) {
  165. $goods['name'] = $goods['goods_snap']['name'];
  166. $goods['image'] = FileService::getFileUrl($goods['goods_snap']['image']);
  167. $goods['item'] = [];
  168. foreach ($goodsItem as $item) {
  169. if ($item['goods_id'] == $goods['goods_id']) {
  170. $goods['item'][] = [
  171. 'item_id' => $item['item_id'],
  172. 'spec_value_str' => $item['spec_value_str'],
  173. 'sell_price' => $item['sell_price'],
  174. 'seckill_price' => $item['seckill_price'],
  175. 'sales_amount' => $item['sales_amount'],
  176. 'browse_volume' => $goods['browse_volume'],
  177. 'sales_volume' => $item['sales_volume'],
  178. 'closing_order' => $item['closing_order']
  179. ];
  180. unset($item);
  181. }
  182. }
  183. unset($goods['seckill_id']);
  184. unset($goods['goods_snap']);
  185. }
  186. return $detail;
  187. }
  188. /**
  189. * @notes 添加秒杀活动
  190. * @param $params
  191. * @return bool|string
  192. * @author 张无忌
  193. * @date 2021/7/26 11:22
  194. */
  195. public static function add($params)
  196. {
  197. Db::startTrans();
  198. try {
  199. // 验证商品是否正在参与活动
  200. $result = self::checkGoods($params);
  201. if ($result !== false) {
  202. throw new Exception($result.':正在参与活动中,不可重复添加');
  203. }
  204. # 创建活动信息
  205. $seckill = SeckillActivity::create([
  206. 'sn' => create_code(8),
  207. 'name' => $params['name'],
  208. // 'min_buy' => $params['min_buy'],
  209. 'max_buy' => $params['max_buy'],
  210. //'is_coupon' => $params['is_coupon'],
  211. //'is_distribution' => $params['is_distribution'],
  212. 'explain' => $params['explain'] ?? '',
  213. 'start_time' => strtotime($params['start_time']),
  214. 'end_time' => strtotime($params['end_time'])
  215. ]);
  216. # 创建活动商品信息
  217. $goodsItemData = [];
  218. $goodsActivityData = [];
  219. foreach ($params['goods'] as $item) {
  220. $goods = (new Goods())->field([
  221. 'id,name,code,image,video,poster,express_type',
  222. 'express_money,express_template_id,spec_type',
  223. 'min_price,max_price,content'
  224. ])->findOrEmpty(intval($item['goods_id']))->toArray();
  225. if (!$goods) {
  226. throw new Exception('商品不存在');
  227. }
  228. $seckillGoods = SeckillGoods::create([
  229. 'seckill_id' => $seckill['id'],
  230. 'goods_id' => $goods['id'],
  231. 'min_seckill_price' => min(array_column($item['items'], 'seckill_price')),
  232. 'max_seckill_price' => max(array_column($item['items'], 'seckill_price')),
  233. 'goods_snap' => json_encode($goods, JSON_UNESCAPED_UNICODE),
  234. 'virtual_sales_num' => $item['virtual_sales_num'] ?? 0,
  235. 'virtual_click_num' => $item['virtual_click_num'] ?? 0,
  236. ]);
  237. foreach ($item['items'] as $val) {
  238. $goodsItem = (new GoodsItem())
  239. ->where(['goods_id'=>$goods['id'], 'id'=>intval($val['item_id'])])
  240. ->findOrEmpty()->toArray();
  241. if (!$goodsItem) {
  242. throw new Exception('商品规格不存在');
  243. }
  244. $goodsItemData[] = [
  245. 'seckill_gid' => $seckillGoods['id'],
  246. 'seckill_id' => $seckill['id'],
  247. 'goods_id' => $goods['id'],
  248. 'item_id' => $goodsItem['id'],
  249. 'spec_value_str' => $goodsItem['spec_value_str'],
  250. 'sell_price' => $goodsItem['sell_price'],
  251. 'seckill_price' => $val['seckill_price'],
  252. 'item_snap' => json_encode($goodsItem, JSON_UNESCAPED_UNICODE)
  253. ];
  254. $goodsActivityData[] = [
  255. 'activity_type' => ActivityEnum::SECKILL,
  256. 'activity_id' => $seckill['id'],
  257. 'goods_id' => $goods['id'],
  258. 'item_id' => $goodsItem['id'],
  259. ];
  260. }
  261. }
  262. if (!empty($goodsItemData)) {
  263. (new SeckillGoodsItem())->saveAll($goodsItemData);
  264. }
  265. // 商品参与活动的信息
  266. if (!empty($goodsActivityData)) {
  267. (new GoodsActivity())->saveAll($goodsActivityData);
  268. }
  269. Db::commit();
  270. return true;
  271. } catch (Exception $e) {
  272. Db::rollback();
  273. return $e->getMessage();
  274. }
  275. }
  276. /**
  277. * @notes 编辑秒杀活动
  278. * @param $params
  279. * @return bool|string
  280. * @author 张无忌
  281. * @date 2021/7/26 11:32
  282. */
  283. public static function edit($params)
  284. {
  285. Db::startTrans();
  286. try {
  287. $seckillActivity = (new SeckillActivity())->findOrEmpty($params['id'])->toArray();
  288. if (!$seckillActivity) {
  289. throw new Exception('秒杀活动不存在,请刷新列表');
  290. }
  291. // 活动已开启: 只允许编辑部分内容
  292. if ($seckillActivity['status'] != SeckillEnum::SECKILL_STATUS_NOT) {
  293. SeckillActivity::update([
  294. 'name' => $params['name'],
  295. 'explain' => $params['explain'] ?? '',
  296. 'end_time' => strtotime($params['end_time'])
  297. ], ['id'=>$params['id']]);
  298. }
  299. // 活动未开启: 可编辑所有
  300. if ($seckillActivity['status'] == SeckillEnum::SECKILL_STATUS_NOT) {
  301. // 删除旧的活动商品
  302. (new SeckillGoods())->where(['seckill_id'=>$params['id']])->delete();
  303. (new SeckillGoodsItem())->where(['seckill_id'=>$params['id']])->delete();
  304. // 删除旧的商品参与活动的信息
  305. (new GoodsActivity())->where([
  306. 'activity_type' => ActivityEnum::SECKILL,
  307. 'activity_id' => $params['id']
  308. ])
  309. ->useSoftDelete('delete_time',time())
  310. ->delete();
  311. // 验证商品是否正在参与活动
  312. $result = self::checkGoods($params);
  313. if ($result !== false) {
  314. throw new Exception($result.':正在参与活动中,不可重复添加');
  315. }
  316. // 更新活动基本信息
  317. SeckillActivity::update([
  318. 'name' => $params['name'],
  319. // 'min_buy' => $params['min_buy'],
  320. 'max_buy' => $params['max_buy'],
  321. //'is_coupon' => $params['is_coupon'],
  322. //'is_distribution' => $params['is_distribution'],
  323. 'explain' => $params['explain'] ?? '',
  324. 'start_time' => strtotime($params['start_time']),
  325. 'end_time' => strtotime($params['end_time'])
  326. ], ['id'=>$params['id']]);
  327. // 添加新的活动商品
  328. $goodsItemData = [];
  329. $goodsActivityData = [];
  330. foreach ($params['goods'] as $item) {
  331. $goods = (new Goods())->field([
  332. 'id,name,code,image,video,poster,express_type',
  333. 'express_money,express_template_id,spec_type',
  334. 'min_price,max_price,content'
  335. ])->findOrEmpty(intval($item['goods_id']))->toArray();
  336. if (!$goods) {
  337. throw new Exception('商品不存在');
  338. }
  339. $seckillGoods = SeckillGoods::create([
  340. 'seckill_id' => $params['id'],
  341. 'goods_id' => $goods['id'],
  342. 'min_seckill_price' => min(array_column($item['items'], 'seckill_price')),
  343. 'max_seckill_price' => max(array_column($item['items'], 'seckill_price')),
  344. 'goods_snap' => json_encode($goods, JSON_UNESCAPED_UNICODE),
  345. 'virtual_sales_num' => $item['virtual_sales_num'] ?? 0,
  346. 'virtual_click_num' => $item['virtual_click_num'] ?? 0,
  347. ]);
  348. foreach ($item['items'] as $val) {
  349. $goodsItem = (new GoodsItem())
  350. ->where(['goods_id' => $goods['id'], 'id' => intval($val['item_id'])])
  351. ->findOrEmpty()->toArray();
  352. if (!$goodsItem) {
  353. throw new Exception('商品规格不存在');
  354. }
  355. $goodsItemData[] = [
  356. 'seckill_gid' => $seckillGoods['id'],
  357. 'seckill_id' => $params['id'],
  358. 'goods_id' => $goods['id'],
  359. 'item_id' => $goodsItem['id'],
  360. 'spec_value_str' => $goodsItem['spec_value_str'],
  361. 'sell_price' => $goodsItem['sell_price'],
  362. 'seckill_price' => $val['seckill_price'],
  363. 'item_snap' => json_encode($goodsItem, JSON_UNESCAPED_UNICODE)
  364. ];
  365. $goodsActivityData[] = [
  366. 'activity_type' => ActivityEnum::SECKILL,
  367. 'activity_id' => $params['id'],
  368. 'goods_id' => $goods['id'],
  369. 'item_id' => $goodsItem['id'],
  370. ];
  371. }
  372. }
  373. if (!empty($goodsItemData)) {
  374. (new SeckillGoodsItem())->saveAll($goodsItemData);
  375. }
  376. if (!empty($goodsActivityData)) {
  377. (new GoodsActivity())->saveAll($goodsActivityData);
  378. }
  379. }
  380. Db::commit();
  381. return true;
  382. } catch (Exception $e) {
  383. Db::rollback();
  384. return $e->getMessage();
  385. }
  386. }
  387. /**
  388. * @notes 删除秒杀活动
  389. * @param $params
  390. * @return bool
  391. * @author 张无忌
  392. * @date 2021/7/26 18:00
  393. */
  394. public static function delete($params)
  395. {
  396. Db::startTrans();
  397. try {
  398. $order = (new Order())
  399. ->where(['pay_status'=>1, 'order_type'=>OrderEnum::SECKILL_ORDER])
  400. ->where(['seckill_id'=>$params['id']])
  401. ->findOrEmpty();
  402. if (!$order->isEmpty()) {
  403. throw new Exception('该秒杀活动已产生支付订单,不可删除');
  404. }
  405. // 对本秒杀活动的未支付订单进行关闭
  406. (new Order())
  407. ->where(['seckill_id' => $params['id']])
  408. ->where(['pay_status' => 0])
  409. ->update([
  410. 'order_status' => OrderEnum::STATUS_CLOSE,
  411. 'update_time' => time()
  412. ]);
  413. SeckillActivity::destroy($params['id']);
  414. // 删除商品参与活动的信息
  415. $goodsActivityIds = GoodsActivity::where([
  416. 'activity_type' => ActivityEnum::SECKILL,
  417. 'activity_id' => $params['id'],
  418. ])->column('id');
  419. if (count($goodsActivityIds)) {
  420. GoodsActivity::destroy($goodsActivityIds);
  421. }
  422. Db::commit();
  423. return true;
  424. } catch (Exception $e) {
  425. Db::rollback();
  426. return $e->getMessage();
  427. }
  428. }
  429. /**
  430. * @notes 确认开启秒杀活动
  431. * @param $params
  432. * @return bool|string
  433. * @author 张无忌
  434. * @date 2021/7/26 18:05
  435. */
  436. public static function open($params)
  437. {
  438. try {
  439. $seckillActivity = (new SeckillActivity())->findOrEmpty($params['id'])->toArray();
  440. if (!$seckillActivity) {
  441. throw new Exception("活动不存在,请刷新页面再试");
  442. }
  443. if ($seckillActivity['status'] == SeckillEnum::SECKILL_STATUS_END) {
  444. throw new Exception('活动已结束,不可重新开启');
  445. }
  446. SeckillActivity::update([
  447. 'status' => SeckillEnum::SECKILL_STATUS_CONDUCT,
  448. 'update_time' => time()
  449. ], ['id' => $params['id']]);
  450. return true;
  451. } catch (Exception $e) {
  452. return $e->getMessage();
  453. }
  454. }
  455. /**
  456. * @notes 停止关闭活动
  457. * @param $params
  458. * @author 张无忌
  459. * @date 2021/7/23 16:58
  460. */
  461. public static function stop($params)
  462. {
  463. Db::startTrans();
  464. try {
  465. SeckillActivity::update([
  466. 'status' => SeckillEnum::SECKILL_STATUS_END,
  467. 'update_time' => time()
  468. ], ['id'=>$params['id']]);
  469. // 删除商品参与活动的信息
  470. $goodsActivityIds = GoodsActivity::where([
  471. 'activity_type' => ActivityEnum::SECKILL,
  472. 'activity_id' => $params['id'],
  473. ])->column('id');
  474. if (count($goodsActivityIds)) {
  475. GoodsActivity::destroy($goodsActivityIds);
  476. }
  477. Db::commit();
  478. return true;
  479. } catch (\Exception $e) {
  480. Db::rollback();
  481. return $e->getMessage();
  482. }
  483. }
  484. /**
  485. * @notes 验证新增的商品是否已参与活动
  486. * @param $params
  487. * @return bool
  488. * @author 张无忌
  489. * @date 2021/8/4 19:46
  490. */
  491. private static function checkGoods($params)
  492. {
  493. $seckillIds = (new SeckillActivity())->whereIn('status', [1, 2])->column('id');
  494. if ($seckillIds) {
  495. $goodsId = array_column($params['goods'], 'goods_id');
  496. $seckillGoods = (new SeckillGoods())
  497. ->whereIn('seckill_id', $seckillIds)
  498. ->whereIn('goods_id', $goodsId)
  499. ->findOrEmpty()->toArray();
  500. if ($seckillGoods) {
  501. $goods = (new Goods())->findOrEmpty($seckillGoods['goods_id'])->toArray();
  502. return $goods['name'];
  503. }
  504. }
  505. return false;
  506. }
  507. }