Files
huangjingfen/pro_v3.5.1_副本/app/services/activity/seckill/StoreSeckillServices.php
apple 434aa8c69d feat(fsgx): 完成全部24项开发任务 Phase1-7
Phase1 后端核心:
- 新增 fsgx_v1.sql 迁移脚本(is_queue_goods/frozen_points/available_points/no_assess)
- SystemConfigServices 返佣设置扩展(周期人数/分档比例/范围/时机)
- StoreOrderCreateServices 周期循环佣金计算
- StoreOrderTakeServices 佣金发放后同步冻结积分
- StoreProductServices/StoreProduct 保存 is_queue_goods

Phase2 后端接口:
- GET /api/hjf/brokerage/progress 佣金周期进度
- GET /api/hjf/assets/overview 资产总览
- HjfPointsServices 每日 frozen_points 0.4‰ 释放定时任务
- PUT /adminapi/hjf/member/{uid}/no_assess 不考核接口
- GET /adminapi/hjf/points/release_log 积分日志接口

Phase3 前端清理:
- hjfCustom.js 路由精简(仅保留 points/log)
- hjfQueue.js/hjfMember.js API 清理/重定向至 CRMEB 原生接口
- pages.json 公排→推荐佣金/佣金记录/佣金规则

Phase4-5 前端改造:
- queue/status.vue 推荐佣金进度页整体重写
- 商品详情/订单确认/支付结果页文案与逻辑改造
- 个人中心/资产页/引导页/规则页文案改造
- HjfQueueProgress/HjfRefundNotice/HjfAssetCard 组件改造
- 推广中心嵌入佣金进度摘要
- hjfMockData.js 全量更新(公排字段→佣金字段)

Phase6 Admin 增强:
- 用户列表新增 frozen_points/available_points 列及不考核操作按钮
- hjfPoints.js USE_MOCK=false 对接真实积分日志接口

Phase7 配置文档:
- docs/fsgx-phase7-config-checklist.md 后台配置与全链路验收清单

Made-with: Cursor
2026-03-23 22:32:19 +08:00

887 lines
40 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\services\activity\seckill;
use app\services\activity\StoreActivityServices;
use app\services\BaseServices;
use app\dao\activity\seckill\StoreSeckillDao;
use app\services\diy\DiyServices;
use app\services\order\StoreOrderServices;
use app\services\other\QrcodeServices;
use app\services\product\ensure\StoreProductEnsureServices;
use app\services\product\label\StoreProductLabelServices;
use app\services\product\product\StoreDescriptionServices;
use app\services\user\UserRelationServices;
use app\services\product\product\StoreProductReplyServices;
use app\services\product\product\StoreProductServices;
use app\services\product\sku\StoreProductAttrServices;
use app\services\product\sku\StoreProductAttrValueServices;
use app\jobs\product\ProductLogJob;
use crmeb\exceptions\AdminException;
use crmeb\services\CacheService;
use crmeb\services\GroupDataService;
use crmeb\services\SystemConfigService;
use crmeb\traits\OptionTrait;
use think\annotation\Inject;
use think\exception\ValidateException;
/**
* Class StoreSeckillServices
* @package app\services\activity\seckill
* @method getSeckillIdsArray(array $ids, array $field)
* @mixin StoreSeckillDao
*/
class StoreSeckillServices extends BaseServices
{
use OptionTrait;
const OPOXMWTJ = 'k8kkOJ';
/**
* 商品活动类型
*/
const TYPE = 1;
/**
* @var StoreSeckillDao
*/
#[Inject]
protected StoreSeckillDao $dao;
/**
* @param array $productIds
* @return array
* @author 等风来
* @email 136327134@qq.com
* @date 2022/11/14
*/
public function getSeckillIdsArrayCache(array $productIds)
{
$list = $this->dao->cacheList();
if (!$list) {
return $this->dao->getSeckillIdsArray($productIds, ['id', 'time_id', 'product_id']);
} else {
$seckill = [];
$time = time();
foreach ($list as $item) {
if ($item['is_del'] == 0 && $item['status'] == 1 && $item['start_time'] <= $time && $item['stop_time'] >= ($time - 86400) && in_array($item['product_id'], $productIds)) {
$seckill[] = [
'id' => $item['id'],
'time_id' => $item['time_id'],
'product_id' => $item['product_id'],
];
}
}
return $seckill;
}
}
/**
* 获取秒杀活动数量
* @param array $where 查询条件
* @return int
*/
public function getCount(array $where)
{
return $this->dao->count($where);
}
/**
* 秒杀是否存在
* @param int $id
* @param string $field
* @return array|int|\think\Model
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getSeckillCount(int $id = 0, string $field = 'time_id')
{
$where = [];
$where[] = ['is_del', '=', 0];
$where[] = ['status', '=', 1];
$config = [];
$currentHour = date('Hi');
/** @var StoreSeckillTimeServices $storeSeckillTimeServices */
$storeSeckillTimeServices = app()->make(StoreSeckillTimeServices::class);
if ($id) {
$time = time();
$where[] = ['id', '=', $id];
$where[] = ['start_time', '<=', $time];
$where[] = ['stop_time', '>=', $time - 86400];
$seckill_one = $this->dao->getOne($where, $field);
if (!$seckill_one) {
throw new ValidateException('活动已结束');
}
$time_id = $seckill_one['time_id'] ?? [];
if ($time_id) {
$time_id = is_string($time_id) ? explode(',', $time_id) : $time_id;
$timeList = $storeSeckillTimeServices->getList(['id' => $time_id, 'status' => 1]);
foreach ($timeList as $value) {
$value['start_time'] = $start = str_replace(':', '', $value['start_time']);
$value['end_time'] = $end = str_replace(':', '', $value['end_time']);
if ($currentHour >= $start && $currentHour < $end) {
$config = $value;
break;
}
}
}
if (!$config) {
throw new ValidateException('活动已结束');
}
//获取秒杀商品状态
if ($config['start_time'] <= $currentHour && $config['end_time'] > $currentHour) {
return $seckill_one;
} else if ($config['start_time'] > $currentHour) {
throw new ValidateException('活动未开始');
} else {
throw new ValidateException('活动已结束');
}
} else {
$timeList = $storeSeckillTimeServices->getList(['status' => 1]);
foreach ($timeList as $value) {
$start = str_replace(':', '', $value['start_time']);
$end = str_replace(':', '', $value['end_time']);
if ($currentHour >= $start && $currentHour < $end) {
$config = $value;
break;
}
}
if (!$config) return 0;
//获取秒杀商品状态
$start = substr_replace($config['start_time'], ':', 2, 0);
$end = substr_replace($config['end_time'], ':', 2, 0);
$startTime = strtotime(date('Y-m-d') . ' ' . $start);
$stopTime = strtotime(date('Y-m-d') . ' ' . $end);
$where[] = ['start_time', '<', $startTime];
$where[] = ['stop_time', '>', $stopTime];
return $this->dao->getCount($where);
}
}
/**
* 保存数据
* @param int $id
* @param array $data
*/
public function saveData(int $id, array $data)
{
/** @var StoreProductServices $storeProductServices */
$storeProductServices = app()->make(StoreProductServices::class);
$productInfo = $storeProductServices->getOne(['is_del' => 0, 'is_verify' => 1, 'id' => $data['product_id']]);
if (!$productInfo) {
throw new AdminException('原商品已移入回收站');
}
if ($productInfo['is_vip_product']) {
throw new AdminException("{$productInfo["store_name"]}】是svip专享");
}
if ($productInfo['is_presale_product']) {
throw new AdminException("{$productInfo["store_name"]}】是预售商品");
}
$data['product_type'] = $productInfo['product_type'];
$data['type'] = $productInfo['type'] ?? 0;
$data['relation_id'] = $productInfo['relation_id'] ?? 0;
$custom_form = $productInfo['custom_form'] ?? [];
$data['custom_form'] = is_array($custom_form) ? json_encode($custom_form) : $custom_form;
$data['system_form_id'] = $productInfo['system_form_id'] ?? 0;
$store_label_id = $productInfo['store_label_id'] ?? [];
$data['store_label_id'] = is_array($store_label_id) ? implode(',', $store_label_id) : $store_label_id;
$ensure_id = $productInfo['ensure_id'] ?? [];
$data['ensure_id'] = is_array($ensure_id) ? implode(',', $ensure_id) : $ensure_id;
$specs = $productInfo['specs'] ?? [];
$data['specs'] = is_array($specs) ? json_encode($specs) : $specs;
if (in_array($data['product_type'], [1, 2, 3])) {
$data['freight'] = 2;
$data['temp_id'] = 0;
$data['postage'] = 0;
} else {
if ($data['freight'] == 1) {
$data['temp_id'] = 0;
$data['postage'] = 0;
} elseif ($data['freight'] == 2) {
$data['temp_id'] = 0;
} elseif ($data['freight'] == 3) {
$data['postage'] = 0;
}
if ($data['freight'] == 2 && !$data['postage']) {
throw new AdminException('请设置运费金额');
}
if ($data['freight'] == 3 && !$data['temp_id']) {
throw new AdminException('请选择运费模版');
}
}
$description = $data['description'];
$detail = $data['attrs'];
$items = $data['items'];
$data['start_time'] = strtotime($data['section_time'][0]);
$data['stop_time'] = strtotime($data['section_time'][1]);
$data['image'] = $data['images'][0] ?? '';
$data['images'] = json_encode($data['images']);
$data['price'] = min(array_column($detail, 'price'));
$data['ot_price'] = min(array_column($detail, 'ot_price'));
$data['quota'] = $data['quota_show'] = array_sum(array_column($detail, 'quota'));
if ($data['quota'] > $productInfo['stock']) {
throw new AdminException('限量不能超过商品库存');
}
$data['stock'] = array_sum(array_column($detail, 'stock'));
unset($data['section_time'], $data['description'], $data['attrs'], $data['items']);
/** @var StoreDescriptionServices $storeDescriptionServices */
$storeDescriptionServices = app()->make(StoreDescriptionServices::class);
/** @var StoreProductAttrServices $storeProductAttrServices */
$storeProductAttrServices = app()->make(StoreProductAttrServices::class);
$id = $this->transaction(function () use ($id, $data, $description, $detail, $items, $storeDescriptionServices, $storeProductAttrServices) {
if ($id) {
$res = $this->dao->update($id, $data);
if (!$res) throw new AdminException('修改失败');
} else {
$data['add_time'] = time();
$res = $this->dao->save($data);
if (!$res) throw new AdminException('添加失败');
$id = (int)$res->id;
}
$storeDescriptionServices->saveDescription((int)$id, $description, 1);
$storeProductAttrServices->setItem('store_product_id', $data['product_id']);
$skuList = $storeProductAttrServices->validateProductAttr($items, $detail, (int)$id, 1);
$storeProductAttrServices->reset();
$valueGroup = $storeProductAttrServices->saveProductAttr($skuList, (int)$id, 1);
$res = true;
foreach ($valueGroup as $item) {
if ($item['quota_show']) $res = $res && CacheService::setStock($item['unique'], (int)$item['quota_show']);
}
if (!$res) {
throw new AdminException('占用库存失败');
}
return $id;
});
$this->dao->cacheTag()->clear();
//保存
$seckill = $this->dao->get($id, ['*'], ['descriptions']);
$this->dao->cacheUpdate($seckill->toArray());
CacheService::redisHandler('product_attr')->clear();
}
/**
* @param int $timeId
* @param int $id
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
* @author 等风来
* @email 136327134@qq.com
* @date 2022/11/4
*/
public function createSeckillListCache(int $timeId, int $id = 0)
{
//创建数据缓存
$list = $this->dao->getListByTime($timeId);
$timeId = (string)$timeId;
if ($id) {
$this->dao->cacheUpdateList($list, $timeId);
} else {
$this->dao->cacheCreate($list, $timeId);
}
}
/**
* 获取列表
* @param array $where
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function systemPage(array $where)
{
[$page, $limit] = $this->getPageValue();
$list = $this->dao->getList($where, $page, $limit);
$count = $this->dao->count($where);
$activityIds = array_column($list, 'activity_id');
$activityInfos = app()->make(StoreActivityServices::class)->getColumn(['id' => $activityIds], 'id,name', 'id');
foreach ($list as &$item) {
$item['store_name'] = $item['title'];
if ($item['status']) {
if ($item['start_time'] > time())
$item['start_name'] = '未开始';
else if (bcadd($item['stop_time'], '86400') < time())
$item['start_name'] = '已结束';
else if (bcadd($item['stop_time'], '86400') > time() && $item['start_time'] < time()) {
$item['start_name'] = '进行中';
}
} else $item['start_name'] = '已结束';
$end_time = $item['stop_time'] ? (date('Y-m-d', (int)$item['stop_time']) . ' 23:59:59') : '';
$item['_stop_time'] = $end_time;
$item['stop_status'] = $item['stop_time'] + 86400 < time() ? 1 : 0;
$item['activity_name'] = $activityInfos[$item['activity_id']]['name'] ?? '';
}
return compact('list', 'count');
}
/**
* 获取秒杀详情
* @param int $id
* @return array|\think\Model|null
*/
public function getInfo(int $id)
{
$info = $this->dao->get($id);
if ($info) {
if ($info['start_time'])
$start_time = date('Y-m-d', (int)$info['start_time']);
if ($info['stop_time'])
$stop_time = date('Y-m-d', (int)$info['stop_time']);
if (isset($start_time) && isset($stop_time))
$info['section_time'] = [$start_time, $stop_time];
else
$info['section_time'] = [];
unset($info['start_time'], $info['stop_time']);
$info['give_integral'] = intval($info['give_integral']);
$info['price'] = floatval($info['price']);
$info['ot_price'] = floatval($info['ot_price']);
$info['postage'] = floatval($info['postage']);
$info['cost'] = floatval($info['cost']);
$info['weight'] = floatval($info['weight']);
$info['volume'] = floatval($info['volume']);
if (!$info['delivery_type']) {
$info['delivery_type'] = [1];
}
if ($info['postage']) {
$info['freight'] = 2;
} elseif ($info['temp_id']) {
$info['freight'] = 3;
} else {
$info['freight'] = 1;
}
/** @var StoreDescriptionServices $storeDescriptionServices */
$storeDescriptionServices = app()->make(StoreDescriptionServices::class);
$info['description'] = $storeDescriptionServices->getDescription(['product_id' => $id, 'type' => 1]);
/** @var StoreProductAttrValueServices $storeProductAttrValueServices */
$storeProductAttrValueServices = app()->make(StoreProductAttrValueServices::class);
$info['attrs'] = $storeProductAttrValueServices->attrList($id, $info['product_id'], 1);
}
return $info;
}
/**
*
* @param int $time
* @return array|mixed
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
* @author 等风来
* @email 136327134@qq.com
* @date 2022/11/4
*/
public function getListByTimeCache(int $time)
{
[$page, $limit] = $this->getPageValue();
$res = $this->dao->cacheList();
$list = [];
$stime = time();
$etime = $stime - 86400;
foreach ($res as $value) {
if (isset($value['is_del']) && $value['is_del'] == 0 && isset($value['start_time']) && $value['start_time'] <= $stime && isset($value['stop_time']) && $value['stop_time'] >= $etime && isset($value['status']) && isset($value['time_id']) && $value['status'] && $value['time_id'] == $time) {
$list[] = $value;
}
}
$newResList = array_slice($list, ($page - 1) * $limit, $limit);
if (!$newResList) {
$newResList = $this->getListByTime($time);
} else {
foreach ($newResList as &$item) {
if ($item['quota'] > 0) {
$percent = (int)(($item['quota_show'] - $item['quota']) / $item['quota_show'] * 100);
$item['percent'] = $percent;
$item['stock'] = $item['quota'];
} else {
$item['percent'] = 100;
$item['stock'] = 0;
}
$item['price'] = floatval($item['price']);
$item['ot_price'] = floatval($item['ot_price']);
}
}
return $newResList;
}
/**
* 获取某个时间段的秒杀列表
* @param int $time
* @param int $limit
* @return array
* @throws \ReflectionException
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getListByTime(int $time)
{
[$page, $limit] = $this->getPageValue();
/** @var StoreActivityServices $storeActivityServices */
$storeActivityServices = app()->make(StoreActivityServices::class);
$activityList = $storeActivityServices->getList(['time_id' => $time, 'type' => 1, 'status' => 1, 'is_del' => 0, 'activityTime' => true], 'id,image');
$seckillInfo = [];
if ($activityList) {
$activityIds = array_column($activityList, 'id');
$field = 'id,product_id,activity_id,title,image,price,ot_price,quota,quota_show,freight,stock,store_label_id';
$seckillInfo = $this->dao->getListByTime($activityIds, [], $field, $page, $limit);
if (count($seckillInfo)) {
$activityList = array_combine($activityIds, $activityList);
/** @var StoreProductLabelServices $storeProductLabelServices */
$storeProductLabelServices = app()->make(StoreProductLabelServices::class);
foreach ($seckillInfo as $key => &$item) {
if ($item['quota'] > 0) {
$percent = (float)sprintf("%.1f", (($item['quota_show'] - $item['quota']) / $item['quota_show'] * 100));
$item['percent'] = $percent;
$item['stock'] = $item['quota'];
} else {
$item['percent'] = 100;
$item['stock'] = 0;
}
$item['price'] = floatval($item['price']);
$item['ot_price'] = floatval($item['ot_price']);
$item['discount_num'] = $item['ot_price'] ? (float)bcmul(bcdiv((string)$item['price'], (string)$item['ot_price'], 4), '10', 1) : 10;
$item['activity_image'] = $activityList[$item['activity_id']]['image'] ?? '';
$item['store_label'] = [];
if ($item['store_label_id']) {
$item['store_label'] = $storeProductLabelServices->getLabelCache($item['store_label_id'], ['id', 'label_name', 'style_type', 'color', 'bg_color', 'border_color', 'icon']);
}
/** @var StoreProductServices $storeProductServices */
$storeProductServices = app()->make(StoreProductServices::class);
$item['brand_name'] = $storeProductServices->productIdByBrandName((int)$item['product_id']);
}
}
}
return $seckillInfo;
}
/**
* 获取秒杀详情
* @param int $id
* @return mixed
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function seckillDetail(int $uid, int $id)
{
//读取秒杀商品缓存信息
$storeInfo = $this->dao->cacheRemember($id, function () use ($id) {
$storeInfo = $this->dao->getOne(['id' => $id], '*,title as store_name', ['descriptions']);
if (!$storeInfo) {
throw new ValidateException('商品不存在');
} else {
$storeInfo = $storeInfo->toArray();
}
return $storeInfo;
});
$storeInfo['activity'] = [];
if ($storeInfo['activity_id']) {
$activityServices = app()->make(StoreActivityServices::class);
$storeInfo['activity'] = $activityServices->getInfo((int)$storeInfo['activity_id'], ['id', 'start_day', 'end_day', 'time_id']);
}
/** @var DiyServices $diyServices */
$diyServices = app()->make(DiyServices::class);
$infoDiy = $diyServices->getProductDetailDiy();
//diy控制参数
if (!isset($infoDiy['showService']) || in_array(3, $infoDiy['showService'])) {
$storeInfo['specs'] = [];
}
$configData = SystemConfigService::more(['site_url', 'routine_contact_type', 'site_name', 'share_qrcode', 'store_self_mention', 'store_func_status', 'product_poster_title']);
$siteUrl = $configData['site_url'] ?? '';
$storeInfo['image'] = set_file_url($storeInfo['image'], $siteUrl);
$storeInfo['image_base'] = set_file_url($storeInfo['image'], $siteUrl);
//品牌名称
/** @var StoreProductServices $storeProductServices */
$storeProductServices = app()->make(StoreProductServices::class);
$productInfo = $storeProductServices->getCacheProductInfo((int)$storeInfo['product_id']);
$storeInfo['brand_name'] = $storeProductServices->productIdByBrandName((int)$storeInfo['product_id'], $productInfo);
$delivery_type = $storeInfo['delivery_type'] ?? $productInfo['delivery_type'];
$storeInfo['delivery_type'] = is_string($delivery_type) ? explode(',', $delivery_type) : $delivery_type;
/**
* 判断配送方式
*/
$storeInfo['delivery_type'] = $storeProductServices->getDeliveryType($storeInfo['type'], $storeInfo['relation_id'], $storeInfo['delivery_type']);
$storeInfo['total'] = $productInfo['sales'] + $productInfo['ficti'];
$storeInfo['store_label'] = $storeInfo['ensure'] = [];
if ($storeInfo['store_label_id']) {
/** @var StoreProductLabelServices $storeProductLabelServices */
$storeProductLabelServices = app()->make(StoreProductLabelServices::class);
$storeInfo['store_label'] = $storeProductLabelServices->getLabelCache($storeInfo['store_label_id'], ['id', 'label_name', 'style_type', 'color', 'bg_color', 'border_color', 'icon']);
}
if ($storeInfo['ensure_id'] && isset($infoDiy['showService']) && in_array(2, $infoDiy['showService'])) {
/** @var StoreProductEnsureServices $storeProductEnsureServices */
$storeProductEnsureServices = app()->make(StoreProductEnsureServices::class);
$storeInfo['ensure'] = $storeProductEnsureServices->getEnsurCache($storeInfo['ensure_id'], ['id', 'name', 'image', 'desc']);
}
$time = $this->getItem('time', '');
$status = $this->getItem('status', '');
$time_id = (int)$this->getItem('time_id', '');
/** @var QrcodeServices $qrcodeService */
$qrcodeService = app()->make(QrcodeServices::class);
if (($configData['share_qrcode'] ?? 0) && request()->isWechat()) {
$storeInfo['code_base'] = $qrcodeService->getTemporaryQrcode('seckill-' . $id . '-' . $uid . '-' . $time . '-' . $status, $uid)->url;
} else {
$storeInfo['code_base'] = $qrcodeService->getWechatQrcodePath($id . '_product_seckill_detail_wap.jpg', '/pages/activity/goods_seckill_details/index?id=' . $id . '&spid=' . $uid . '&time=' . $time . '&status=' . $status);
}
/** @var StoreOrderServices $storeOrderServices */
$storeOrderServices = app()->make(StoreOrderServices::class);
$data['buy_num'] = $storeOrderServices->getBuyCount($uid, 1, $id);
/** @var UserRelationServices $userRelationServices */
$userRelationServices = app()->make(UserRelationServices::class);
$storeInfo['userCollect'] = $userRelationServices->isProductRelationCache(['uid' => $uid, 'relation_id' => $storeInfo['product_id'], 'type' => 'collect', 'category' => UserRelationServices::CATEGORY_PRODUCT]);
$storeInfo['userLike'] = 0;
$storeInfo['uid'] = $uid;
if ($storeInfo['quota'] > 0) {
$percent = (float)sprintf("%.1f", ($storeInfo['quota_show'] - $storeInfo['quota']) / $storeInfo['quota_show'] * 100);
$storeInfo['percent'] = $percent;
$storeInfo['stock'] = $storeInfo['quota'];
} else {
$storeInfo['percent'] = 100;
$storeInfo['stock'] = 0;
}
$storeInfo['last_time'] = 0;
$time_id = $time_id ?: ($storeInfo['activity']['time_id'] ?? []);
$storeInfo['status'] = (int)$storeInfo['status'];
if ($time_id && $storeInfo['status'] == 1) {
$time_id = is_string($time_id) ? explode(',', $time_id) : $time_id;
/** @var StoreSeckillTimeServices $storeSeckillTimeServices */
$storeSeckillTimeServices = app()->make(StoreSeckillTimeServices::class);
$timeList = $storeSeckillTimeServices->getList(['id' => $time_id, 'status' => 1]);
$config = [];
$today = date('Y-m-d');
$currentHour = date('Hi');
if (count($timeList) <= 1) {
$config = $timeList[0] ?? [];
} else {
foreach ($timeList as $value) {
$start = str_replace(':', '', $value['start_time']);
$end = str_replace(':', '', $value['end_time']);
if ($currentHour >= $start && $currentHour < $end) {
$config = $value;
break;
}
}
}
//获取秒杀商品状态
if ($storeInfo['status'] == 1) {
if ($config) {//正在进行中的
$start = str_replace(':', '', $config['start_time']);
$end = str_replace(':', '', $config['end_time']);
if ($start <= $currentHour && $end > $currentHour) {
$storeInfo['status'] = 1;
$storeInfo['last_time'] = strtotime($today . ' ' . $config['end_time']);
} else if ($start > $currentHour) {
$storeInfo['status'] = 2;
} else {
$storeInfo['status'] = 0;
}
} else {
$storeInfo['status'] = 0;
}
}
}
//商品详情
$storeInfo['small_image'] = get_thumb_water($storeInfo['image']);
$data['storeInfo'] = $storeInfo;
$storeInfo['product_id'] = (int)$storeInfo['product_id'];
$data['reply'] = [];
$data['replyChance'] = $data['replyCount'] = 0;
if (isset($infoDiy['showReply']) && $infoDiy['showReply']) {
/** @var StoreProductReplyServices $storeProductReplyService */
$storeProductReplyService = app()->make(StoreProductReplyServices::class);
$reply = $storeProductReplyService->getRecProductReplyCache($storeInfo['product_id'], (int)($infoDiy['replyNum'] ?? 1));
$data['reply'] = $reply ? get_thumb_water($reply, 'small', ['pics']) : [];
[$replyCount, $goodReply, $replyChance] = $storeProductReplyService->getProductReplyData((int)$storeInfo['product_id']);
$data['replyChance'] = $replyChance;
$data['replyCount'] = $replyCount;
}
/** @var StoreProductAttrServices $storeProductAttrServices */
$storeProductAttrServices = app()->make(StoreProductAttrServices::class);
[$productAttr, $productValue] = $storeProductAttrServices->getProductAttrDetailCache($id, $uid, 0, 1, $storeInfo['product_id'], $productInfo);
$data['productAttr'] = $productAttr;
$data['productValue'] = $productValue;
$data['routine_contact_type'] = $configData['routine_contact_type'] ?? 0;
$data['store_func_status'] = (int)($configData['store_func_status'] ?? 1);//门店是否开启
$data['store_self_mention'] = $data['store_func_status'] ? (int)($configData['store_self_mention'] ?? 0) : 0;//门店自提是否开启
$data['site_name'] = $configData['site_name'] ?? '';
$data['share_qrcode'] = $configData['share_qrcode'] ?? 0;
$data['product_poster_title'] = $configData['product_poster_title'] ?? '';
//浏览记录
ProductLogJob::dispatch(['visit', ['uid' => $uid, 'id' => $id, 'product_id' => $storeInfo['product_id']], 'seckill']);
return $data;
}
/**
* 修改秒杀库存
* @param int $num
* @param int $seckillId
* @param string $unique
* @param int $store_id
* @return bool
*/
public function decSeckillStock(int $num, int $seckillId, string $unique = '', int $store_id = 0)
{
return $this->transaction(function () use ($num, $seckillId, $unique) {
$product_id = $this->dao->value(['id' => $seckillId], 'product_id');
$res = false;
if ($product_id) {
if ($unique) {
/** @var StoreProductAttrValueServices $skuValueServices */
$skuValueServices = app()->make(StoreProductAttrValueServices::class);
//减去秒杀商品的sku库存增加销量
$res = false !== $skuValueServices->decProductAttrStock($seckillId, $unique, $num, 1);
//减去当前普通商品sku的库存增加销量
//秒杀商品sku
$suk = $skuValueServices->value(['unique' => $unique, 'product_id' => $seckillId, 'type' => 1], 'suk');
//平台商品sku unique
$productUnique = $skuValueServices->value(['suk' => $suk, 'product_id' => $product_id, 'type' => 0], 'unique');
/** @var StoreProductServices $services */
$services = app()->make(StoreProductServices::class);
//减去普通商品库存
$res = $res && $services->decProductStock($num, $product_id, (string)$productUnique);
}
//减去秒杀库存
$res = $res && false !== $this->dao->decStockIncSales(['id' => $seckillId, 'type' => 1], $num);
}
//更新单个缓存
$info = $this->dao->getOne(['id' => $seckillId], '*', ['descriptions']);
if ($info) {
$info = $info->toArray();
$this->dao->cacheUpdate($info);
}
return $res;
});
}
/**
* 加库存减销量
* @param int $num
* @param int $seckillId
* @param string $unique
* @param int $store_id
* @return bool
*/
public function incSeckillStock(int $num, int $seckillId, string $unique = '', int $store_id = 0)
{
return $this->transaction(function () use ($num, $seckillId, $unique) {
$product_id = $this->dao->value(['id' => $seckillId], 'product_id');
$res = false;
if ($product_id) {
if ($unique) {
/** @var StoreProductAttrValueServices $skuValueServices */
$skuValueServices = app()->make(StoreProductAttrValueServices::class);
//减去秒杀商品的sku库存增加销量
$res = false !== $skuValueServices->incProductAttrStock($seckillId, $unique, $num, 1);
//减去当前普通商品sku的库存增加销量
//秒杀商品sku
$suk = $skuValueServices->value(['unique' => $unique, 'product_id' => $seckillId, 'type' => 1], 'suk');
//平台商品sku unique
$productUnique = $skuValueServices->value(['suk' => $suk, 'product_id' => $product_id, 'type' => 0], 'unique');
/** @var StoreProductServices $services */
$services = app()->make(StoreProductServices::class);
//减去普通商品库存
$res = $res && $services->incProductStock($num, $product_id, (string)$productUnique);
}
//减去秒杀库存
$res = $res && false !== $this->dao->incStockDecSales(['id' => $seckillId, 'type' => 1], $num);
}
//更新单个缓存
$info = $this->dao->getOne(['id' => $seckillId], '*', ['descriptions']);
if ($info) {
$info = $info->toArray();
$this->dao->cacheUpdate($info);
}
return $res;
});
}
/**
* 下单|加入购物车验证秒杀商品库存
* @param int $uid
* @param int $seckillId
* @param int $cartNum
* @param string $unique
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function checkSeckillStock(int $uid, int $seckillId, int $cartNum = 1, string $unique = '')
{
/** @var StoreProductAttrValueServices $attrValueServices */
$attrValueServices = app()->make(StoreProductAttrValueServices::class);
if ($unique == '') {
$unique = $attrValueServices->value(['product_id' => $seckillId, 'type' => 1], 'unique');
}
//检查商品活动状态
$storeSeckillinfo = $this->getSeckillCount($seckillId, '*,title as store_name');
if (!$storeSeckillinfo) {
throw new ValidateException('该活动已下架');
}
if ($storeSeckillinfo['once_num'] < $cartNum) {
throw new ValidateException('每个订单限购' . $storeSeckillinfo['once_num'] . '件');
}
/** @var StoreOrderServices $orderServices */
$orderServices = app()->make(StoreOrderServices::class);
$userBuyCount = $orderServices->getBuyCount($uid, 1, $seckillId);
if ($storeSeckillinfo['num'] < ($userBuyCount + $cartNum)) {
throw new ValidateException('每人总共限购' . $storeSeckillinfo['num'] . '件');
}
if ($storeSeckillinfo['num'] < $cartNum) {
throw new ValidateException('每人限购' . $storeSeckillinfo['num'] . '件');
}
$attrInfo = $attrValueServices->getOne(['product_id' => $seckillId, 'unique' => $unique, 'type' => 1]);
if (!$attrInfo || $attrInfo['product_id'] != $seckillId) {
throw new ValidateException('请选择有效的商品属性');
}
if ($cartNum > $attrInfo['quota']) {
throw new ValidateException('该商品库存不足' . $cartNum);
}
return [$attrInfo, $unique, $storeSeckillinfo];
}
/**
* 获取当前的秒杀时间time
* @return int|string
*/
public function getSeckillTime()
{
$seckillTime = GroupDataService::getData('routine_seckill_time') ?? [];
$currentHour = (int)date('H');
$time = 0;
foreach ($seckillTime as $item) {
$activityEndHour = (int)bcadd((string)$item['time'], (string)$item['continued'], 0);
if ($currentHour >= $item['time'] && $currentHour < $activityEndHour) {
$time = $item['id'];
}
}
return $time;
}
/**
* 秒杀统计
* @return array
*/
public function seckillStatistics($id)
{
/** @var StoreOrderServices $orderServices */
$orderServices = app()->make(StoreOrderServices::class);
$pay_count = $orderServices->getDistinctCount(['type' => 1, 'activity_id' => $id, 'paid' => 1, 'pid' => [0, -1]], 'uid');
$order_count = $orderServices->getDistinctCount(['type' => 1, 'activity_id' => $id, 'pid' => [0, -1]], 'uid');
$all_price = $orderServices->sum(['type' => 1, 'activity_id' => $id, 'paid' => 1, 'refund_type' => [0, 3], 'pid' => [0, -1]], 'pay_price');
$seckillInfo = $this->dao->get($id);
$pay_rate = $seckillInfo['quota'] . '/' . $seckillInfo['quota_show'];
return compact('pay_count', 'order_count', 'all_price', 'pay_rate');
}
/**
* 秒杀参与人统计
* @param $id
* @param string $keyword
* @return array
*/
public function seckillPeople($id, $keyword = '')
{
/** @var StoreOrderServices $orderServices */
$orderServices = app()->make(StoreOrderServices::class);
[$page, $limit] = $this->getPageValue();
$list = $orderServices->seckillPeople($id, $keyword, $page, $limit);
$count = $orderServices->getDistinctCount([['paid', '=', 1], ['type', '=', 1], ['activity_id', '=', $id], ['pid', 'in', [0, -1]], ['real_name|uid|user_phone', 'like', '%' . $keyword . '%']], 'uid', false);
foreach ($list as &$item) {
$item['add_time'] = date('Y-m-d H:i:s', $item['add_time']);
}
return compact('list', 'count');
}
/**
* 秒杀订单统计
* @param $id
* @param array $where
* @return array
*/
public function seckillOrder(int $id, array $where = [])
{
/** @var StoreOrderServices $orderServices */
$orderServices = app()->make(StoreOrderServices::class);
[$page, $limit] = $this->getPageValue();
$list = $orderServices->activityStatisticsOrder($id, 1, $where, $page, $limit);
$where['type'] = 1;
$where['activity_id'] = $id;
$count = $orderServices->count($where);
foreach ($list as &$item) {
if ($item['is_del'] || $item['is_system_del']) {
$item['status'] = '已删除';
} else if ($item['paid'] == 0 && $item['status'] == 0) {
$item['status'] = '待付款';
} else if ($item['paid'] == 1 && $item['status'] == 4 && in_array($item['shipping_type'], [1, 3]) && $item['refund_status'] == 0) {
$item['status'] = '部分发货';
} else if ($item['paid'] == 1 && $item['refund_status'] == 2) {
$item['status'] = '已退款';
} else if ($item['paid'] == 1 && $item['status'] == 5 && $item['refund_status'] == 0) {
$item['status'] = $item['shipping_type'] == 2 ? '部分核销' : '部分收货';
$item['_status'] = 12;//已支付 部分核销
} else if ($item['paid'] == 1 && $item['refund_status'] == 1) {
$item['status'] = '申请退款';
} else if ($item['paid'] == 1 && $item['refund_status'] == 4) {
$item['status'] = '退款中';
} else if ($item['paid'] == 1 && $item['status'] == 0 && in_array($item['shipping_type'], [1, 3]) && $item['refund_status'] == 0) {
$item['status'] = '未发货';
$item['_status'] = 2;//已支付 未发货
} else if ($item['paid'] == 1 && in_array($item['status'], [0, 1]) && $item['shipping_type'] == 2 && $item['refund_status'] == 0) {
$item['status'] = '未核销';
} else if ($item['paid'] == 1 && in_array($item['status'], [1, 5]) && in_array($item['shipping_type'], [1, 3]) && $item['refund_status'] == 0) {
$item['status'] = '待收货';
} else if ($item['paid'] == 1 && $item['status'] == 2 && $item['refund_status'] == 0) {
$item['status'] = '待评价';
} else if ($item['paid'] == 1 && $item['status'] == 3 && $item['refund_status'] == 0) {
$item['status'] = '已完成';
} else if ($item['paid'] == 1 && $item['refund_status'] == 3) {
$item['status'] = '部分退款';
} else {
$item['status'] = '未知';
}
$item['add_time'] = date('Y-m-d H:i:s', $item['add_time']);
$item['pay_time'] = $item['pay_time'] ? date('Y-m-d H:i:s', $item['pay_time']) : '';
}
return compact('list', 'count');
}
}