// +---------------------------------------------------------------------- declare (strict_types=1); namespace app\api\service\sharp; use app\api\model\Goods as GoodsModel; use app\api\model\sharp\Active as ActiveModel; use app\api\model\sharp\ActiveGoods as ActiveGoodsModel; use app\api\model\sharp\ActiveTime as ActiveTimeModel; use app\api\model\sharp\Goods as SharpGoodsModel; use app\api\model\sharp\GoodsSku as SharpGoodsSkuModel; use app\common\enum\sharp\ActiveStatus as ActiveStatusEnum; use app\common\enum\sharp\GoodsStatus as GoodsStatusEnum; use app\common\library\helper; use app\common\service\BaseService; use cores\exception\BaseException; use think\model\Collection; /** * 秒杀活动服务类 * Class Active * @package app\api\service\sharp */ class Active extends BaseService { // 活动会场模型 private ActiveModel $ActiveModel; // 活动会场场次模型 private ActiveTimeModel $ActiveTimeModel; /** * 构造方法 * Active constructor. */ public function initialize() { $this->ActiveModel = new ActiveModel; $this->ActiveTimeModel = new ActiveTimeModel; } /** * 获取秒杀活动会场首页数据 * @return array * @throws BaseException * @throws \think\db\exception\DataNotFoundException * @throws \think\db\exception\DbException * @throws \think\db\exception\ModelNotFoundException */ public function getHallHome(): array { // 获取秒杀首页顶部菜单 $tabbar = $this->getActiveTabbar(); if (empty($tabbar)) { return []; } //empty($tabbar) && throwError('很抱歉,暂无秒杀活动'); // 获取活动商品 $goodsList = $this->getGoodsListByActiveTimeId($tabbar[0]['active_time_id']); return compact('tabbar', 'goodsList'); } /** * 获取秒杀活动组件数据 * @param array $goodsParm 商品查询参数 * @return array * @throws \think\db\exception\DataNotFoundException * @throws \think\db\exception\DbException * @throws \think\db\exception\ModelNotFoundException */ public function getSharpModular(array $goodsParm = []): array { // 获取秒杀活动列表 $tabbar = $this->getActiveTabbar(); if (empty($tabbar)) { return ['active' => null, 'goodsList' => []]; } return [ // 秒杀活动 'active' => $tabbar[0], // 活动商品列表 'goodsList' => $this->getGoodsListByActiveTimeId($tabbar[0]['active_time_id'], $goodsParm), ]; } /** * 根据活动场次ID获取商品列表 * @param int $activeTimeId 秒杀会场场次ID * @param array $goodsParm 商品查询参数 * @return Collection|\think\Collection * @throws \think\db\exception\DataNotFoundException * @throws \think\db\exception\DbException * @throws \think\db\exception\ModelNotFoundException */ public function getGoodsListByActiveTimeId(int $activeTimeId, array $goodsParm = []) { // 设置默认查询参数 $model = new ActiveGoodsModel; $goodsParm = $model->setQueryDefaultValue($goodsParm, [ 'status' => 1 // 商品状态: 上架 ]); // 列表数据 $list = ActiveGoodsModel::getGoodsListByActiveTimeId($activeTimeId, $goodsParm); // 隐藏冗余的属性 return $list->hidden(SharpGoodsModel::getHidden(['goods'])); } /** * 获取活动商品详情 * @param int $activeTimeId 秒杀会场场次ID * @param int $sharpGoodsId 秒杀商品ID * @return array * @throws BaseException * @throws \think\db\exception\DataNotFoundException * @throws \think\db\exception\DbException * @throws \think\db\exception\ModelNotFoundException */ public function getActiveGoodsDetail(int $activeTimeId, int $sharpGoodsId): array { // 活动详情 $active = $this->getGoodsActive($activeTimeId, $sharpGoodsId); // 商品详情 $model = new ActiveGoodsModel; $goods = $model->getGoodsActiveDetail($active, $sharpGoodsId, true); return compact('active', 'goods'); } /** * 获取订单提交的商品列表 * @param int $activeTimeId 秒杀会场场次ID * @param int $sharpGoodsId 秒杀商品ID * @param string $goodsSkuId 商品skuID * @param int $goodsNum 购买数量 * @return array * @throws \cores\exception\BaseException * @throws BaseException * @throws \think\db\exception\DataNotFoundException * @throws \think\db\exception\DbException * @throws \think\db\exception\ModelNotFoundException */ public function getCheckoutGoodsList(int $activeTimeId, int $sharpGoodsId, string $goodsSkuId, int $goodsNum): array { // 验证秒杀活动状态 $this->checkGoodsActiveStatus($activeTimeId, $sharpGoodsId); // 秒杀商品详情 $model = new ActiveGoodsModel; $sharpGoods = $model->getGoodsBasic($sharpGoodsId, true); // 商品sku信息 $sharpGoods['skuInfo'] = SharpGoodsSkuModel::getSkuInfo($sharpGoods['goods_id'], $sharpGoodsId, $goodsSkuId); // 商品封面 (优先sku封面) $sharpGoods['goods_image'] = $sharpGoods['skuInfo']['goods_image'] ?: $sharpGoods['goods_image']; // 商品列表 (隐藏用不到的属性) $goodsList = [$sharpGoods->hidden(GoodsModel::getHidden(['content', 'images', 'goods_images']))]; foreach ($goodsList as &$item) { // 商品价格 $item['goods_price'] = $item['skuInfo']['seckill_price']; $item['line_price'] = $item['original_price']; // 记录秒杀商品ID $item['goods_sku_id'] = $item['skuInfo']['goods_sku_id']; $item['goods_source_id'] = $item['sharp_goods_id']; // 商品购买数量 $item['total_num'] = $goodsNum; // 商品购买总金额 $item['total_price'] = helper::bcmul($item['goods_price'], $goodsNum); } return $goodsList; } /** * 活动详情 * @param int $activeTimeId 秒杀会场场次ID * @param int $sharpGoodsId 秒杀商品ID * @return array * @throws BaseException * @throws \think\db\exception\DataNotFoundException * @throws \think\db\exception\DbException * @throws \think\db\exception\ModelNotFoundException */ private function getGoodsActive(int $activeTimeId, int $sharpGoodsId): array { // 获取活动商品的关联信息 $model = $this->getActiveGoods($activeTimeId, $sharpGoodsId); if (empty($model) || !($model['active']['status'] && $model['active_time']['status'])) { throwError('很抱歉,该活动不存在或已结束'); } // 开始/截止时间 $startTime = $model['active']['active_date'] + ($model['active_time']->getData('active_time') * 60 * 60); $endTime = $startTime + (1 * 60 * 60); // 活动商品状态 $activeStatus = $this->getActivcGoodsStatus($startTime, $endTime); // 整理活动数据 return [ 'active_id' => $model['active_id'], 'active_time_id' => $model['active_time_id'], 'active_time' => $model['active_time']['active_time'], 'sales_actual' => $model['sales_actual'], 'start_time' => $this->onFormatTime($startTime), 'end_time' => $this->onFormatTime($endTime), 'active_status' => $activeStatus, 'count_down_time' => $this->getGoodsActiveCountDownTime($activeStatus, $startTime, $endTime), 'store_id' => $model['store_id'], ]; } /** * 验证秒杀活动状态 * @param int $activeTimeId 秒杀会场场次ID * @param int $sharpGoodsId 秒杀商品ID * @return void * @throws BaseException * @throws \think\db\exception\DataNotFoundException * @throws \think\db\exception\DbException * @throws \think\db\exception\ModelNotFoundException */ private function checkGoodsActiveStatus(int $activeTimeId, int $sharpGoodsId) { // 获取活动详情 $active = $this->getGoodsActive($activeTimeId, $sharpGoodsId); if ($active['active_status'] != ActiveStatusEnum::STATE_BEGIN) { throwError('很抱歉,该活动未开始'); } } /** * 获取活动商品的关联信息 * @param int $activeTimeId 秒杀会场场次ID * @param int $sharpGoodsId 秒杀商品ID * @return array|\think\Model * @throws \think\db\exception\DataNotFoundException * @throws \think\db\exception\DbException * @throws \think\db\exception\ModelNotFoundException */ public function getActiveGoods(int $activeTimeId, int $sharpGoodsId) { static $data = []; $index = "{$activeTimeId}_{$sharpGoodsId}"; if (!isset($data[$index])) { $model = ActiveGoodsModel::getGoodsActive($activeTimeId, $sharpGoodsId); !empty($model) && $data[$index] = $model; } return $data[$index]; } /** * 活动商品倒计时 * @param int $activeStatus 活动状态 * @param int $startTime 开始时间 * @param int $endTime 截止时间 * @return false|string */ private function getGoodsActiveCountDownTime(int $activeStatus, int $startTime, int $endTime) { // 活动已开始:结束时间 if ($activeStatus == GoodsStatusEnum::STATE_BEGIN) { return $this->onFormatTime($endTime); } // 活动未开始: 开始时间 if ($activeStatus == GoodsStatusEnum::STATE_SOON) { return $this->onFormatTime($startTime); } return false; } /** * 活动商品状态 * @param int $startTime 开始时间 * @param int $endTime 截止时间 * @return int */ private function getActivcGoodsStatus(int $startTime, int $endTime): int { $nowTime = time(); if ($nowTime < $startTime) { return GoodsStatusEnum::STATE_SOON; } if ($nowTime < $endTime) { return GoodsStatusEnum::STATE_BEGIN; } return GoodsStatusEnum::STATE_END; } /** * 获取秒杀首页顶部菜单 * @return array * @throws \think\db\exception\DataNotFoundException * @throws \think\db\exception\DbException * @throws \think\db\exception\ModelNotFoundException */ private function getActiveTabbar(): array { // 当天的活动 $todyActive = $this->ActiveModel->getNowActive(); $data = []; if (!empty($todyActive)) { // 当前进行中的活动 $data[] = $this->getBeginActive($todyActive); // 获取即将开始的活动 $data = array_merge($data, $this->getSoonActive($todyActive)); } // 获取预告的活动 $data[] = $this->getNoticeActive(); return array_values(array_filter($data)); } /** * 获取当前进行中的活动 * @param $todyActive * @return array * @throws \think\db\exception\DataNotFoundException * @throws \think\db\exception\DbException * @throws \think\db\exception\ModelNotFoundException */ private function getBeginActive($todyActive): array { // 当前的时间点 $model = $this->ActiveTimeModel->getNowActiveTime($todyActive['active_id']); if (empty($model)) return []; // 整理数据 $startTime = $todyActive['active_date'] + ($model->getData('active_time') * 60 * 60); $endTime = $startTime + (1 * 60 * 60); return [ 'active_id' => $todyActive['active_id'], 'active_time_id' => $model['active_time_id'], 'active_time' => $model['active_time'], 'start_time' => $this->onFormatTime($startTime), 'end_time' => $this->onFormatTime($endTime), 'count_down_time' => $this->onFormatTime($endTime), 'status' => ActiveStatusEnum::STATE_BEGIN, 'status_text' => '已开抢', 'status_text2' => '正在疯抢', 'sharp_modular_text' => '正在疯抢', ]; } /** * 获取即将开始的活动 * @param $todyActive * @return array * @throws \think\db\exception\DataNotFoundException * @throws \think\db\exception\DbException * @throws \think\db\exception\ModelNotFoundException */ private function getSoonActive($todyActive): array { // 当前的时间点 $list = $this->ActiveTimeModel->getNextActiveTimes($todyActive['active_id']); if (empty($list) || $list->isEmpty()) return []; // 整理数据 $data = []; foreach ($list as $item) { $startTime = $todyActive['active_date'] + ($item->getData('active_time') * 60 * 60); $endTime = $startTime + (1 * 60 * 60); $data[] = [ 'active_id' => $todyActive['active_id'], 'active_time_id' => $item['active_time_id'], 'active_time' => $item['active_time'], 'start_time' => $this->onFormatTime($startTime), 'end_time' => $this->onFormatTime($endTime), 'count_down_time' => $this->onFormatTime($startTime), 'status' => ActiveStatusEnum::STATE_SOON, 'status_text' => '即将开抢', 'status_text2' => '即将开抢', 'sharp_modular_text' => "{$item['active_time']} 场预告", ]; } return $data; } /** * 获取预告的活动 * @return array * @throws \think\db\exception\DataNotFoundException * @throws \think\db\exception\DbException * @throws \think\db\exception\ModelNotFoundException */ private function getNoticeActive(): array { // 下一场活动 $nextActive = $this->ActiveModel->getNextActive(); if (empty($nextActive)) return []; // 第一个时间点 $model = $this->ActiveTimeModel->getRecentActiveTime($nextActive['active_id']); if (empty($model)) return []; // 整理数据 $startTime = $nextActive['active_date'] + ($model->getData('active_time') * 60 * 60); $endTime = $startTime + (1 * 60 * 60); return [ 'active_id' => $nextActive['active_id'], 'active_time_id' => $model['active_time_id'], 'active_time' => $model['active_time'], 'start_time' => $this->onFormatTime($startTime), 'end_time' => $this->onFormatTime($endTime), 'count_down_time' => $this->onFormatTime($startTime), 'status' => ActiveStatusEnum::STATE_NOTICE, 'status_text' => '预告', 'status_text2' => $this->onFormatTime($startTime) . ' 开始', 'sharp_modular_text' => $this->onFormatTime($startTime) . ' 开始', ]; } /** * 将时间戳格式化为日期时间 (精确到分钟) * @param int $timeStamp 时间戳 * @return false|string */ private function onFormatTime(int $timeStamp) { return date('Y-m-d H:i', $timeStamp); } }