diff --git a/app/admin/controller/Goods.php b/app/admin/controller/Goods.php index 69e06825..80814c75 100644 --- a/app/admin/controller/Goods.php +++ b/app/admin/controller/Goods.php @@ -18,6 +18,7 @@ use cores\exception\BaseException; use app\store\model\Goods as GoodsModel; use app\store\model\GoodsCategoryRel; use app\store\model\GoodsSku; +use app\store\model\goods\Import as ImportModel; /** * 商品管理控制器 @@ -203,7 +204,7 @@ class Goods extends Controller // exit(); set_time_limit(0); ini_set('memory_limit', '1024M'); - $columns = ['系统编码','标题','型号','该商品苏宁的链接','成本价','库存','京东的价拖链接','前台价',"发货区域","商品备注"]; //设置好告诉浏览器要下载excel文件的headers + $columns = ['系统编码','标题','型号','该商品苏宁的链接','成本价','库存','京东的价拖链接','前台价']; //设置好告诉浏览器要下载excel文件的headers header('Content-Encoding: UTF-8'); header('Content-Type: application/vnd.ms-excel;charset=UTF-8'); header('Content-Disposition: attachment; filename="导出数据-'.date('Y-m-d', time()).'.csv"'); @@ -284,7 +285,13 @@ class Goods extends Controller } public function import(){ - return $this->renderSuccess('加价成功'); + // 新增记录 + $model = new ImportModel; + if ($model->goodsUpdateBatch($this->postData())) { + return $this->renderSuccess('已添加到导入任务中,请在历史导入记录中查看结果'); + } + return $this->renderError($model->getError() ?: '操作失败'); + } diff --git a/app/common/model/Goods.php b/app/common/model/Goods.php index b34dcac3..ff840654 100644 --- a/app/common/model/Goods.php +++ b/app/common/model/Goods.php @@ -206,7 +206,7 @@ class Goods extends BaseModel // 执行查询 $list = $query ->alias($this->name) - ->field(['goods.goods_id','goods.goods_name','cmmdty_model','sort','cost_price_min','stock_total','link','goods_price_min','sale_areas','remark']) + ->field(['goods.goods_id','goods.goods_name','cmmdty_model','link','cost_price_min','stock_total','link_other','goods_price_min']) ->where('is_delete', '=', 0) ->order($sort) ->paginate($listRows); diff --git a/app/job/controller/goods/GoodsUpdateImport.php b/app/job/controller/goods/GoodsUpdateImport.php new file mode 100644 index 00000000..a9f8b72e --- /dev/null +++ b/app/job/controller/goods/GoodsUpdateImport.php @@ -0,0 +1,49 @@ + +// +---------------------------------------------------------------------- +declare (strict_types=1); + +namespace app\job\controller\goods; + +use app\job\service\goods\GoodsUpdateImport as GoodsUpdateImportService; +use cores\BaseJob; +use cores\traits\QueueTrait; +use cores\exception\BaseException; +use think\db\exception\DataNotFoundException; +use think\db\exception\DbException; +use think\db\exception\ModelNotFoundException; + +/** + * 队列任务:商品批量导入 + * Class Import + * @package app\job\controller + */ +class GoodsUpdateImport extends BaseJob +{ + use QueueTrait; + + /** + * 消费队列任务:商品导入 + * @param array $data 参数 [index队列顺序;totalCount商品总数量;list商品列表;storeId商城ID] + * @return bool 返回结果 + * @throws BaseException + * @throws DataNotFoundException + * @throws DbException + * @throws ModelNotFoundException + */ + public function handle(array $data): bool + { + $time = date('H:i:s'); + echo "\n ---- adminImport ---- {$time} ---- \n"; + + $service = new GoodsUpdateImportService; + return $service->batch($data['list'], $data['recordId'], $data['storeId']); + } +} \ No newline at end of file diff --git a/app/job/service/goods/Collector.php b/app/job/service/goods/Collector.php index 7fe16a83..0ccae855 100644 --- a/app/job/service/goods/Collector.php +++ b/app/job/service/goods/Collector.php @@ -18,6 +18,7 @@ use app\common\model\Spec as SpecModel; use app\common\model\Goods as GoodsModel; use app\common\model\GoodsSku as GoodsSkuModel; use app\common\model\GoodsImage as GoodsImageModel; +use app\store\model\GoodsImage as GoodsImageModel1; use app\common\model\store\Setting as SettingModel; use app\common\model\GoodsSpecRel as GoodsSpecRelModel; use app\common\model\goods\Collector as GoodsCollectorModel; @@ -194,6 +195,79 @@ class Collector extends BaseService return true; } + /** + * 后台单个采集 + * [single description] + * @param string $url [description] + * @param array $form [description] + * @param int $storeId [description] + * @return [type] [description] + */ + public function updateGoods(string $url, array $form, int $storeId): bool + { + + try { + // 采集第三方商品数据 + $original = $this->collector($url, $storeId); + + if ($original['spec_type'] == 20) { + $original['spec_type'] = 10; + + $skuList = array_column($original['specData']['skuList'], null, "goods_sku_no"); + $goods_price = $skuList[$original['goods_sku_no']]['goods_price'] ?? 0; + + $original['goods_price'] = $goods_price; + $original['line_price'] = $goods_price; + $original['data_type'] = 1; + $original['link_other'] = $url; + unset($original['specData']); + + } + $form['imageStorage'] = 10; + + // 下载远程商品图片 + $original = $this->thirdPartyImages($original, $form['imageStorage'], $storeId); + } catch (\Throwable $e) { + // var_dump($e->getMessage()); + // exit; + tre($e->getTraceAsString()); + $this->errorLog[] = ['url' => trim($url), 'message' => $e->getMessage()]; + return false; + } + if ($original['goods_price'] == 0) { + return false; + } + + $original['cost_price'] = $form['cost_price_min']; + $data['link_other'] = $url; + //$data['goods_id'] = $form['goods_id']; + $data['goods_no'] = $original['goods_sku_no']; + $data['content'] = $original['content']; + + //重新计算利润和利润率 + $data['goods_price_min'] = $original['goods_price']; + $data['line_price_min'] = $original['goods_price']; + $data['line_price_min'] = $original['goods_price']; + $data['profit'] = $original['goods_price'] - $original['cost_price']; + $profit_rate = (float)$original['goods_price'] > 0 ? ($original['goods_price'] - $original['cost_price']) / $original['goods_price'] : 0.00; + $profit_rate = $profit_rate > 0.0001 ? bcmul((string)$profit_rate, "100", 2) : 0.00; + $data['profit_rate'] = $profit_rate; + // echo "
"; + // print_r($data); + // exit(); + // 事务处理:添加商品 + $model = new GoodsModel(); + $model->transaction(function () use ($form, $data, $original) { + // 添加商品 + GoodsModel::where('goods_id', $form['goods_id'])->update($data); + // 新增商品与图片关联 + GoodsImageModel1::updates((int)$form['goods_id'], $original['imagesIds']); + //更新sku信息 + GoodsSkuModel::where('goods_id', $form['goods_id'])->update(['goods_price' => $original['goods_price'], 'goods_sku_no' => $original['goods_sku_no']]); + }); + + return true; + } /** * 检查采集记录状态是否异常 * @param int $recordId @@ -318,6 +392,7 @@ class Collector extends BaseService 'goods_type' => $form['goods_type'], 'delivery_id' => $form['delivery_id'] ?? 0, 'goods_name' => $original['goods_name'], + 'goods_no' => $original['goods_sku_no'] ?? "", 'data_type' => $original['data_type'],//数据类型 'link' => $original['link'],//采集地址 'spec_type' => $original['spec_type'], @@ -391,6 +466,7 @@ class Collector extends BaseService $data = [ 'goods_type' => $form['goods_type'], 'goods_name' => $original['goods_name'], + 'goods_no' => $original['goods_sku_no'] ?? "", 'data_type' => $original['data_type'],//数据类型 'link' => $original['link'],//采集地址 'spec_type' => $original['spec_type'], diff --git a/app/job/service/goods/GoodsUpdateImport.php b/app/job/service/goods/GoodsUpdateImport.php new file mode 100644 index 00000000..062ee655 --- /dev/null +++ b/app/job/service/goods/GoodsUpdateImport.php @@ -0,0 +1,221 @@ + +// +---------------------------------------------------------------------- +declare (strict_types=1); + +namespace app\job\service\goods; + +use app\common\library\helper; +use app\common\service\BaseService; +use app\common\model\Spec as SpecModel; +use app\common\model\Goods as GoodsModel; +use app\common\model\Category as CategoryModel; +use app\common\model\GoodsSku as GoodsSkuModel; +use app\common\model\Delivery as DeliveryModel; +use app\common\model\UploadFile as UploadFileModel; +use app\common\model\GoodsImage as GoodsImageModel; +use app\common\model\goods\Import as GoodsImportModel; +use app\common\model\GoodsSpecRel as GoodsSpecRelModel; +use app\common\model\goods\Service as GoodsServiceModel; +use app\common\model\goods\ServiceRel as GoodsServiceRelModel; +use app\common\model\GoodsCategoryRel as GoodsCategoryRelModel; +use app\common\enum\goods\Status as GoodsStatusEnum; +use app\common\enum\goods\GoodsType as GoodsTypeEnum; +use app\common\enum\goods\SpecType as GoodsSpecTypeEnum; +use app\common\enum\goods\ImportStatus as GoodsImportStatusEnum; +use app\common\enum\goods\DeductStockType as DeductStockTypeEnum; +use app\common\validate\goods\AdminImport as GoodsAdminImportValidate; +use cores\exception\BaseException; +use think\facade\Log; +use app\common\model\Channel; +use app\common\model\Region; +/** + * 服务类:商品批量导入 + * Class Import + * @package app\job\service\goods + */ +class GoodsUpdateImport extends BaseService +{ + /** + * 错误日志记录 + * @var array + */ + private array $errorLog = []; + + /** + * 导入成功的数量 + * @var int + */ + private int $successCount = 0; + + + /** + * 批量导入商品 + * @param array $list + * @param int $recordId + * @param int $storeId + * @return bool + * @throws BaseException + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\DbException + * @throws \think\db\exception\ModelNotFoundException + */ + public function batch(array $list, int $recordId, int $storeId): bool + { + $service = new \app\job\service\goods\Collector(); + foreach ($list as $item) { + + $data = $this->createData($item, $storeId); + if (!isset($item[6]) || !$item[6]) { + continue; + } + $service->updateGoods($item[6], $data, $storeId); + // 记录导入成功 + $this->successCount++; + } + // 更新导入记录 + $this->updateRecord($recordId, \count($list)); + return true; + } + + /** + * 更新导入记录 + * @param int $recordId 商品导入记录ID + * @param int $currentCount 当前任务导入的商品总量 + * @return void + */ + private function updateRecord(int $recordId, int $currentCount) + { + // 获取导入记录 + $model = GoodsImportModel::detail($recordId); + // 计算导入失败的数量 + $failCount = $currentCount - $this->successCount; + // 更新导入记录 + $model->save([ + 'success_count' => $model['success_count'] + $this->successCount, + 'fail_count' => $model['fail_count'] + $failCount, + 'fail_log' => array_merge($model['fail_log'], $this->errorLog), + ]); + // 判断是否为最后一次队列 + if ($model['total_count'] <= ($model['success_count'] + $model['fail_count'])) { + $model->save(['end_time' => \time(), 'status' => GoodsImportStatusEnum::COMPLETED]); + } + } + + + + /** + * 生成商品数据(用于写入数据库) + * @param array $original + * @param int $storeId + * @return array + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\DbException + * @throws \think\db\exception\ModelNotFoundException + */ + public function createData(array $original, int $storeId): array + { + + // 整理商品数据 + $data = [ + 'goods_id' => $original[0] ?? 0, + 'goods_name' => $original[1] ?? "", + 'cmmdty_model' => $original[2] ?? "", + 'link' => $original[3] ?? "", + 'cost_price_min' => $original[4] ?? "", + 'stock_total' => $original[5] ?? "", + 'link_other' => $original[6] ?? "", + 'goods_price_min' => $original[7] ?? "", + + ]; + return $data; + } + + /** + * ID集字符串转换成数组 + * @param string $value + * @return array|false|string[] + */ + private function ids2array(string $value) + { + return !empty($value) ? \explode(',', $value) : []; + } + + /** + * 创建标准的商品SKU数据 + * @param array $originalSkuList + * @param array $specList + * @return array + */ + private function createSkuList(array $originalSkuList, array $specList): array + { + $data = []; + foreach ($originalSkuList as $item) { + // 设置skuKeys数据 + foreach ($item['specNames'] as $spec) { + $specGroup = helper::arraySearch($specList, 'spec_name', $spec[0]); + $specValue = helper::arraySearch($specGroup['valueList'], 'spec_value', $spec[1]); + $item['skuKeys'][] = [ + 'groupKey' => $specGroup['key'], + 'valueKey' => $specValue['key'] + ]; + } + // 整理SKU数据 + $data[] = [ + 'image_id' => 0, + 'goods_price' => $item[$this->mapSku['goods_price']], + 'line_price' => $item[$this->mapSku['line_price']], + 'stock_num' => $item[$this->mapSku['stock_num']], + 'goods_weight' => $item[$this->mapSku['goods_weight']], + 'goods_sku_no' => $item[$this->mapSku['goods_sku_no']], + 'skuKeys' => $item['skuKeys'], + ]; + } + return $data; + } + + /** + * 创建标准的商品规格数据(树状) + * @param array $originalSkuList + * @return array + */ + private function createSpecList(array &$originalSkuList): array + { + // 生成规格数据 + $data = []; + foreach ($originalSkuList as &$item) { + $tempArr1 = \explode(',', $item['H']); // ['颜色:白色', '尺码:小'] + foreach ($tempArr1 as $val) { + $tempArr2 = \explode(':', $val); // ['颜色','白色'] + $data[$tempArr2[0]][$tempArr2[1]] = 1; + $item['specNames'][] = $tempArr2; + } + } + // 整理为specList格式 + $specList = []; + foreach ($data as $specName => $specValues) { + $groupKey = \count($specList); + $valueList = []; + foreach ($specValues as $specValue => $key) { + $valueList[] = [ + 'key' => \count($valueList), + 'groupKey' => $groupKey, + 'spec_value' => $specValue, + ]; + } + $specList[] = [ + 'key' => $groupKey, + 'spec_name' => $specName, + 'valueList' => $valueList, + ]; + } + return $specList; + } +} \ No newline at end of file diff --git a/app/store/model/goods/Import.php b/app/store/model/goods/Import.php index b6e3a702..857295dc 100644 --- a/app/store/model/goods/Import.php +++ b/app/store/model/goods/Import.php @@ -16,6 +16,7 @@ use think\Paginator; use think\db\exception\DbException; use PhpOffice\PhpSpreadsheet\Exception; use app\job\controller\goods\AdminImport as GoodsAdminImportJob; +use app\job\controller\goods\GoodsUpdateImport as GoodsUpdateImportJob; use app\common\enum\goods\ImportStatus as GoodsImportStatusEnum; use app\common\library\FileLocal; use app\common\library\helper; @@ -93,6 +94,88 @@ class Import extends ImportModel $this->adminDispatchJob($execlData, $recordId); return true; } + /** + * 执行批量导入 + * @param array $form + * @return bool + * @throws BaseException + * @throws Exception + * @throws \PhpOffice\PhpSpreadsheet\Reader\Exception + */ + public function goodsUpdateBatch(array $form): bool + { + // 读取excel文件内容 + //$execlData = $this->readExecl(); + // 接收用户上传的模板文件 + $file = request()->file('file'); + empty($file) && throwError('很抱歉,您没有上传模板文件'); + // 写入到本地临时目录 + $path = FileLocal::writeFile($file, 'batch-goods', self::$storeId); + $file = fopen($path, "r"); + $data = array(); + $i = 0; + while (($line = fgetcsv($file)) !== false) { + if ($i == 0) { + $i++; + continue; + } + // $line是当前行的所有数据,可以根据实际需求进行处理 + $execlData[] = $line; // 将每一行数据保存到$data数组中 + $i++; + } + + fclose($file); + // echo ""; + // print_r($execlData); + // exit(); + // 验证导入的商品数量是否合法 + $this->checkLimit($execlData); + + self::$storeId = $form['store_id'] ?? 0; + // $obj = new \app\job\service\goods\GoodsUpdateImport(); + // $service = new \app\job\service\goods\Collector(); + // foreach ($execlData as $item) { + + // $data = $obj->createData($item, self::$storeId); + // // echo ""; + // // print_r($data); + // // exit(); + // $service->updateGoods($item[6], $data, self::$storeId); + // exit(); + // // // 记录导入成功 + // // $this->successCount++; + // } + + // 新增商品导入记录 + $recordId = $this->addRecord(\count($execlData)); + + + // 调度计划任务 + $this->goodsUpdateJob($execlData, $recordId); + return true; + } + /** + * 调度队列服务执行商品导入 + * @param array $goodsList 商品列表 + * @param int $recordId 商品导入记录ID + * @return void + */ + private function goodsUpdateJob(array $goodsList, int $recordId) + { + // 分批每次导入20条 + $limit = 20; + // 根据商品总数量计算需要的队列任务数量 + $jobCount = \count($goodsList) / $limit; + // 逐次发布队列任务 + for ($i = 0; $i < $jobCount; $i++) { + $data = array_slice($goodsList, $i * $limit, $limit); + GoodsUpdateImportJob::dispatch([ + 'list' => $data, + 'recordId' => $recordId, + 'storeId' => self::$storeId, + ]); + } + } /** * 调度队列服务执行商品导入 * @param array $goodsList 商品列表