diff --git a/app/admin/controller/Category.php b/app/admin/controller/Category.php new file mode 100644 index 00000000..f652e730 --- /dev/null +++ b/app/admin/controller/Category.php @@ -0,0 +1,88 @@ + +// +---------------------------------------------------------------------- +declare (strict_types=1); + +namespace app\admin\controller; + +use think\response\Json; +use app\store\model\Category as CategoryModel; + +/** + * 商品分类 + * Class Category + * @package app\store\controller\goods + */ +class Category extends Controller +{ + /** + * 商品分类列表 + * @return Json + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\DbException + * @throws \think\db\exception\ModelNotFoundException + */ + public function list(): Json + { + $model = new CategoryModel; + $list = $model->getList(['store_id' =>0]); + return $this->renderSuccess(compact('list')); + } + + /** + * 删除商品分类 + * @param int $categoryId + * @return Json + */ + public function delete(int $categoryId): Json + { + // 分类详情 + $model = CategoryModel::detail($categoryId); + if (!$model->remove()) { + return $this->renderError($model->getError() ?: '删除失败'); + } + return $this->renderSuccess('删除成功'); + } + + /** + * 添加商品分类 + * @return Json + */ + public function add(): Json + { + // 新增记录 + $model = new CategoryModel; + $params = $this->postForm(); + $params['store_id'] = 0; + if ($model->add($params)) { + return $this->renderSuccess('添加成功'); + } + return $this->renderError($model->getError() ?: '添加失败'); + } + + /** + * 编辑商品分类 + * @param int $categoryId + * @return Json + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\DbException + * @throws \think\db\exception\ModelNotFoundException + */ + public function edit(int $categoryId): Json + { + // 分类详情 + $model = CategoryModel::detail($categoryId, ['image']); + // 更新记录 + if ($model->edit($this->postForm())) { + return $this->renderSuccess('更新成功'); + } + return $this->renderError($model->getError() ?: '更新失败'); + } +} diff --git a/app/admin/controller/Files.php b/app/admin/controller/Files.php new file mode 100644 index 00000000..03653749 --- /dev/null +++ b/app/admin/controller/Files.php @@ -0,0 +1,149 @@ + +// +---------------------------------------------------------------------- +declare (strict_types=1); + +namespace app\admin\controller; + +use think\response\Json; +use cores\library\Version; +use app\store\model\UploadFile as UploadFileModel; + +/** + * 文件库管理 + * Class Files + * @package app\store\controller + */ +class Files extends Controller +{ + /** + * 文件列表 + * @return Json + * @throws \cores\exception\BaseException + * @throws \think\db\exception\DbException + */ + public function list(): Json + { + $this->env(); + $model = new UploadFileModel; + $list = $model->getList($this->request->param()); + return $this->renderSuccess(compact('list')); + } + + /** + * 编辑文件 + * @param int $fileId + * @return Json + */ + public function edit(int $fileId): Json + { + // 文件详情 + $model = UploadFileModel::detail($fileId); + // 更新记录 + if ($model->edit($this->postForm())) { + return $this->renderSuccess('更新成功'); + } + return $this->renderError($model->getError() ?: '更新失败'); + } + + /** + * 删除文件(批量) + * @param array $fileIds 文件id集 + * @return Json + * @throws \think\Exception + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\DbException + * @throws \think\db\exception\ModelNotFoundException + */ + public function delete(array $fileIds): Json + { + $model = new UploadFileModel; + if (!$model->setDelete($fileIds)) { + return $this->renderError($model->getError() ?: '操作失败'); + } + return $this->renderSuccess('操作成功'); + } + + /** + * 移动文件到指定分组(批量) + * @param int $groupId + * @param array $fileIds + * @return Json + */ + public function moveGroup(int $groupId, array $fileIds): Json + { + $model = new UploadFileModel; + if (!$model->moveGroup($groupId, $fileIds)) { + return $this->renderError($model->getError() ?: '操作失败'); + } + return $this->renderSuccess('操作成功'); + } + + /** + * 临时方法:环境检测并删除废弃的库文件 + * 文件:vendor/topthink/framework/src/think/Filesystem.php + * 文件:vendor/topthink/framework/src/think/facade/Filesystem.php + * 文件:vendor/topthink/framework/tests/FilesystemTest.php + * 目录:vendor/topthink/framework/src/think/filesystem + * @return void + * @throws \cores\exception\BaseException + */ + private function env() + { + // 判断当前版本小于2.2.7则不执行 + if (Version::compare(Version::getVersion(), '2.2.7') === -1) { + return; + } + // 要删除的文件列表 + $files = [ + 'vendor/topthink/framework/src/think/Filesystem.php', + 'vendor/topthink/framework/src/think/facade/Filesystem.php', + 'vendor/topthink/framework/tests/FilesystemTest.php' + ]; + foreach ($files as $file) { + $filePath = root_path() . $file; + file_exists($filePath) && unlink($filePath); + } + // 要删除的目录列表 + $folders = ['vendor/topthink/framework/src/think/filesystem/']; + foreach ($folders as $folder) { + $folderPath = root_path() . $folder; + is_dir($folderPath) && $this->deleteFolder($folderPath); + } + } + + /** + * 临时方法:递归删除指定目录下所有文件 + * @param $path + * @return void + */ + private function deleteFolder($path): void + { + if (!is_dir($path)) { + return; + } + // 扫描一个文件夹内的所有文件夹和文件 + foreach (scandir($path) as $val) { + // 排除目录中的.和.. + if (!in_array($val, ['.', '..', '.gitignore'])) { + // 如果是目录则递归子目录,继续操作 + if (is_dir($path . $val)) { + // 子目录中操作删除文件夹和文件 + $this->deleteFolder($path . $val . '/'); + // 目录清空后删除空文件夹 + rmdir($path . $val . '/'); + } else { + // 如果是文件直接删除 + unlink($path . $val); + } + } + } + } +} diff --git a/app/admin/controller/Goods.php b/app/admin/controller/Goods.php index 47bd25e3..2b05a80c 100644 --- a/app/admin/controller/Goods.php +++ b/app/admin/controller/Goods.php @@ -12,69 +12,141 @@ declare (strict_types=1); namespace app\admin\controller; -use think\facade\Db; +use think\db\exception\DbException; use think\response\Json; +use cores\exception\BaseException; +use app\store\model\Goods as GoodsModel; /** - * 商城管理 - * Class Store - * @package app\admin\controller + * 商品管理控制器 + * Class Goods + * @package app\store\controller */ class Goods extends Controller { /** - * 强制验证当前访问的控制器方法method - * @var array + * 商品列表 + * @return Json + * @throws DbException */ - protected array $methodRules = [ - 'index' => 'GET', - 'recycle' => 'GET', - 'add' => 'POST', - 'move' => 'POST', - 'delete' => 'POST', - ]; + public function list(): Json + { + // 获取列表记录 + $model = new GoodsModel; + $list= $model->getList($this->request->param()); + return $this->renderSuccess(compact('list')); + } /** - * 商城列表 + * 根据商品ID集获取列表记录 + * @param array $goodsIds * @return Json - * @throws \think\db\exception\DbException */ - public function index(): Json + public function listByIds(array $goodsIds): Json { + // 获取列表记录 + $model = new GoodsModel; + $list = $model->getListByIds($goodsIds); + return $this->renderSuccess(compact('list')); + } - $params = $this->request->param(); - $sort = $this->request->param('sort', 'id'); - $order = $this->request->param('order', 'desc'); - $where = []; - if (isset($params['channel']) && $params['channel']) { - $where[] = ['channel', '=', $params['channel']]; - } - if (isset($params['min_profit_rate']) && $params['min_profit_rate']) { - $where[] = ['profit_rate', '>=', $params['min_profit_rate']]; - } - if (isset($params['max_profit_rate']) && $params['max_profit_rate']) { - $where[] = ['profit_rate', '<=', $params['max_profit_rate']]; - } - if (isset($params['min_price']) && $params['min_price']) { - $where[] = ['net_price', '>=', $params['min_price']]; - } - if (isset($params['max_price']) && $params['max_price']) { - $where[] = ['net_price', '<=', $params['max_price']]; + /** + * 商品详情(详细信息) + * @param int $goodsId + * @return Json + * @throws BaseException + * @throws \think\db\exception\DataNotFoundException + * @throws DbException + * @throws \think\db\exception\ModelNotFoundException + */ + public function detail(int $goodsId): Json + { + // 获取商品详情 + $model = new GoodsModel; + $goodsInfo = $model->getDetail($goodsId); + return $this->renderSuccess(compact('goodsInfo')); + } + + /** + * 商品详情(基础信息) + * @param int $goodsId + * @return Json + * @throws BaseException + * @throws \think\db\exception\DataNotFoundException + * @throws DbException + * @throws \think\db\exception\ModelNotFoundException + */ + public function basic(int $goodsId): Json + { + // 获取商品详情 + $model = new GoodsModel; + $detail = $model->getBasic($goodsId); + return $this->renderSuccess(compact('detail')); + } + + /** + * 添加商品 + * @return Json + * @throws BaseException + * @throws \think\db\exception\DataNotFoundException + * @throws DbException + * @throws \think\db\exception\ModelNotFoundException + */ + public function add(): Json + { + $model = new GoodsModel; + if ($model->add($this->postForm())) { + return $this->renderSuccess('添加成功'); } - if (!empty($params['catalog_name'])) { - $where[] = ['catalog_name', 'like', "%{$params["catalog_name"]}%"]; + return $this->renderError($model->getError() ?: '添加失败'); + } + + /** + * 商品编辑 + * @param int $goodsId + * @return Json + * @throws BaseException + * @throws \think\db\exception\DataNotFoundException + * @throws DbException + * @throws \think\db\exception\ModelNotFoundException + */ + public function edit(int $goodsId): Json + { + // 商品详情 + $model = GoodsModel::detail($goodsId); + // 更新记录 + if ($model->edit($this->postForm())) { + return $this->renderSuccess('更新成功'); } - $list = Db::connect("dataCenterMysql")->table('goods_sku') - ->where($where) - ->order($sort, $order) - ->paginate($this->request->param('per_page', 15))->each(function ($item, $key) { - $item['create_time'] = date("Y-m-d H:i:s", $item['create_time']); - $item['update_time'] = date("Y-m-d H:i:s", $item['update_time']); - $item['channel'] = config('app.platformList')[$item['channel']] ?? ""; - return $item; - }); - return $this->renderSuccess(compact('list')); + return $this->renderError($model->getError() ?: '更新失败'); } + /** + * 修改商品状态(上下架) + * @param array $goodsIds 商品id集 + * @param bool $state 为true表示上架 + * @return Json + */ + public function state(array $goodsIds, bool $state): Json + { + $model = new GoodsModel; + if (!$model->setStatus($goodsIds, $state)) { + return $this->renderError($model->getError() ?: '操作失败'); + } + return $this->renderSuccess('操作成功'); + } + /** + * 删除商品 + * @param array $goodsIds + * @return Json + */ + public function delete(array $goodsIds): Json + { + $model = new GoodsModel; + if (!$model->setDelete($goodsIds)) { + return $this->renderError($model->getError() ?: '删除失败'); + } + return $this->renderSuccess('删除成功'); + } } diff --git a/app/admin/controller/Goods1.php b/app/admin/controller/Goods1.php new file mode 100644 index 00000000..47bd25e3 --- /dev/null +++ b/app/admin/controller/Goods1.php @@ -0,0 +1,80 @@ + +// +---------------------------------------------------------------------- +declare (strict_types=1); + +namespace app\admin\controller; + +use think\facade\Db; +use think\response\Json; + +/** + * 商城管理 + * Class Store + * @package app\admin\controller + */ +class Goods extends Controller +{ + /** + * 强制验证当前访问的控制器方法method + * @var array + */ + protected array $methodRules = [ + 'index' => 'GET', + 'recycle' => 'GET', + 'add' => 'POST', + 'move' => 'POST', + 'delete' => 'POST', + ]; + + /** + * 商城列表 + * @return Json + * @throws \think\db\exception\DbException + */ + public function index(): Json + { + + $params = $this->request->param(); + $sort = $this->request->param('sort', 'id'); + $order = $this->request->param('order', 'desc'); + $where = []; + if (isset($params['channel']) && $params['channel']) { + $where[] = ['channel', '=', $params['channel']]; + } + if (isset($params['min_profit_rate']) && $params['min_profit_rate']) { + $where[] = ['profit_rate', '>=', $params['min_profit_rate']]; + } + if (isset($params['max_profit_rate']) && $params['max_profit_rate']) { + $where[] = ['profit_rate', '<=', $params['max_profit_rate']]; + } + if (isset($params['min_price']) && $params['min_price']) { + $where[] = ['net_price', '>=', $params['min_price']]; + } + if (isset($params['max_price']) && $params['max_price']) { + $where[] = ['net_price', '<=', $params['max_price']]; + } + if (!empty($params['catalog_name'])) { + $where[] = ['catalog_name', 'like', "%{$params["catalog_name"]}%"]; + } + $list = Db::connect("dataCenterMysql")->table('goods_sku') + ->where($where) + ->order($sort, $order) + ->paginate($this->request->param('per_page', 15))->each(function ($item, $key) { + $item['create_time'] = date("Y-m-d H:i:s", $item['create_time']); + $item['update_time'] = date("Y-m-d H:i:s", $item['update_time']); + $item['channel'] = config('app.platformList')[$item['channel']] ?? ""; + return $item; + }); + return $this->renderSuccess(compact('list')); + } + + +} diff --git a/app/admin/controller/files/Group.php b/app/admin/controller/files/Group.php new file mode 100644 index 00000000..5384529e --- /dev/null +++ b/app/admin/controller/files/Group.php @@ -0,0 +1,86 @@ + +// +---------------------------------------------------------------------- +declare (strict_types=1); + +namespace app\admin\controller\files; + +use think\response\Json; +use app\admin\controller\Controller; +use app\store\model\UploadGroup as GroupModel; + +/** + * 文件分组 + * Class Group + * @package app\store\controller\content + */ +class Group extends Controller +{ + /** + * 文件分组列表 + * @return Json + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\DbException + * @throws \think\db\exception\ModelNotFoundException + */ + public function list(): Json + { + $model = new GroupModel; + $list = $model->getList(); + return $this->renderSuccess(compact('list')); + } + + /** + * 添加文件分组 + * @return Json + */ + public function add(): Json + { + // 新增记录 + $model = new GroupModel; + if ($model->add($this->postForm())) { + return $this->renderSuccess('添加成功'); + } + return $this->renderError($model->getError() ?: '添加失败'); + } + + /** + * 编辑文件分组 + * @param int $groupId + * @return Json + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\DbException + * @throws \think\db\exception\ModelNotFoundException + */ + public function edit(int $groupId): Json + { + // 分组详情 + $model = GroupModel::detail($groupId); + // 更新记录 + if ($model->edit($this->postForm())) { + return $this->renderSuccess('更新成功'); + } + return $this->renderError($model->getError() ?: '更新失败'); + } + + /** + * 删除文件分组 + * @param int $groupId + * @return Json + */ + public function delete(int $groupId): Json + { + $model = GroupModel::detail($groupId); + if (!$model->remove()) { + return $this->renderError($model->getError() ?: '删除失败'); + } + return $this->renderSuccess('删除成功'); + } +} diff --git a/app/admin/controller/goods/Import.php b/app/admin/controller/goods/Import.php new file mode 100644 index 00000000..c338b42f --- /dev/null +++ b/app/admin/controller/goods/Import.php @@ -0,0 +1,71 @@ + +// +---------------------------------------------------------------------- +declare (strict_types=1); + +namespace app\admin\controller\goods; + +use think\response\Json; +use app\admin\controller\Controller; +use app\store\model\goods\Import as ImportModel; +use cores\exception\BaseException; + +/** + * 商品批量导入管理 + * Class Import + * @package app\store\controller\goods + */ +class Import extends Controller +{ + /** + * 获取列表记录 + * @return Json + * @throws \think\db\exception\DbException + */ + public function list(): Json + { + $params = $this->request->param(); + $params['store_id'] = 0; + $model = new ImportModel; + $list = $model->getList($params); + return $this->renderSuccess(compact('list')); + } + + /** + * 执行批量导入 + * @return Json + * @throws BaseException + * @throws \PhpOffice\PhpSpreadsheet\Exception + * @throws \PhpOffice\PhpSpreadsheet\Reader\Exception + */ + public function batch(): Json + { + // 新增记录 + $model = new ImportModel; + if ($model->adminBatch($this->postData())) { + return $this->renderSuccess('已添加到导入任务中,请在历史导入记录中查看结果'); + } + return $this->renderError($model->getError() ?: '操作失败'); + } + + /** + * 删除记录 + * @param int $id + * @return Json + */ + public function delete(int $id): Json + { + $model = ImportModel::detail($id); + if (!$model->setDelete()) { + return $this->renderError($model->getError() ?: '删除失败'); + } + return $this->renderSuccess('删除成功'); + } +} diff --git a/app/api/service/Goods.php b/app/api/service/Goods.php index 00753a70..8b5958cb 100644 --- a/app/api/service/Goods.php +++ b/app/api/service/Goods.php @@ -137,8 +137,10 @@ class Goods extends GoodsService return []; } $list = $goodsList->toArray(); - - $list['data'] = $this->formatGoodsList($goodsList); + // echo "
"; + // print_r($list); + // exit() + $list['data'] = $this->newFormatGoodsList($list['data']); return $list; } @@ -177,7 +179,7 @@ class Goods extends GoodsService } $list = $goodsList->toArray(); - $list['data'] = $this->formatGoodsList($goodsList); + $list['data'] = $this->newFormatGoodsList($list['data']); return $list; } @@ -203,7 +205,7 @@ class Goods extends GoodsService ], 3); $list2 = $goodsList->toArray()['data']; - $v['goods_list'] = $this->formatGoodsList($list2); + $v['goods_list'] = $this->newFormatGoodsList($list2); } return $list; @@ -317,6 +319,46 @@ class Goods extends GoodsService } return $price; } + /** + * 格式化商品列表 + * @param $goodsList + * @return array + */ + private function newFormatGoodsList($goodsList): array + { + + $data = []; + foreach ($goodsList as $goods) { + $temp = [ + 'goods_id' => $goods['goods_id'], + 'goods_name' => $goods['goods_name'], + 'selling_point' => $goods['selling_point'], + 'goods_image' => $goods['goods_image'], + 'goods_price_min' => $goods['goods_price_min'],//商品价格 + 'goods_price_max' => $goods['goods_price_max'], + 'line_price_min' => $goods['goods_price_min'],//划线价格等于市场价 + 'line_price_max' => $goods['line_price_max'], + 'goods_sales' => $goods['goods_sales'], + 'remaizhishu' => $goods['remaizhishu'], + ]; + + if (UserService::isLogin()) { + $catService = new \app\store\model\GoodsCategoryRel(); + $catIds = $catService->where(['goods_id' => $goods['goods_id']])->column('category_id'); + //价格判断 + if (UserService::isstore()) { + $temp['goods_price_min'] = $goods['cost_price_min']; + } elseif (UserService::isPlusMember()) { + $temp['goods_price_min'] = \app\common\model\PriceSet::membershipPrice($goods['goods_price_min'], $goods['cost_price_min'], $catIds); + } elseif (UserService::isDealerMember()) { + $priceArr = \app\common\model\PriceSet::distributionPrice($goods['goods_price_min'], $goods['cost_price_min'], $catIds); + $temp['goods_price_min'] = $priceArr['goods_price_min_dealer']; + } + } + $data[] = $temp; + } + return $data; + } /** * 格式化商品列表 * @param $goodsList diff --git a/app/common/model/Category.php b/app/common/model/Category.php index cefeeff2..bd49e997 100644 --- a/app/common/model/Category.php +++ b/app/common/model/Category.php @@ -94,6 +94,7 @@ class Category extends BaseModel if (isset($param['is_in_store']) && $param['is_in_store'] != "") { $filter[] = ['is_in_store', '=', $params['is_in_store'] ?? 0]; } + isset($params['store_id']) && $params['store_id'] > -1 && $filter[] = ['store_id', '=', (int)$params['store_id']]; // 查询列表数据 return $this->with(['image']) diff --git a/app/common/validate/goods/AdminImport.php b/app/common/validate/goods/AdminImport.php new file mode 100644 index 00000000..2413d4b3 --- /dev/null +++ b/app/common/validate/goods/AdminImport.php @@ -0,0 +1,58 @@ + ['goods_name', 'delivery_id', 'imagesIds', 'categoryIds', 'serviceIds'], + 'skuInfo' => ['goods_price', 'stock_num', 'goods_weight'], + ]; + + /** + * 验证规则 + * @var array + */ + protected $rule = [ + 'goods_name|商品名称' => ['require'], + 'delivery_id|运费模板ID' => ['require', 'number'], + 'imagesIds|商品图片ID集' => ['require', 'array', 'validateIds'], + 'categoryIds|商品分类ID集' => ['require', 'array', 'validateIds'], + 'serviceIds|服务与承诺' => ['array', 'validateIds'], + + 'goods_price|商品价格' => ['require', 'float', '>=:0.01'], + 'stock_num|库存数量' => ['require', 'integer', '>=:0'], + 'goods_weight|商品重量' => ['require', 'float', '>=:0'], + ]; + + /** + * 错误信息 + * @var string[] + */ + protected $message = [ + // 'delivery_id.number' => '运费模板ID必须是数字', + ]; + + /** + * 验证ID集格式是否正确 + * @param $value + * @return bool + */ + protected function validateIds($value): bool + { + foreach ($value as $val) { + if (!is_numeric($val)) { + return false; + } + } + return true; + } +} \ No newline at end of file diff --git a/app/job/controller/goods/AdminImport.php b/app/job/controller/goods/AdminImport.php new file mode 100644 index 00000000..044d0e67 --- /dev/null +++ b/app/job/controller/goods/AdminImport.php @@ -0,0 +1,49 @@ + +// +---------------------------------------------------------------------- +declare (strict_types=1); + +namespace app\job\controller\goods; + +use app\job\service\goods\AdminImport as GoodsAdminImportService; +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 AdminImport 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 GoodsAdminImportService; + return $service->batch($data['list'], $data['recordId'], $data['storeId']); + } +} \ No newline at end of file diff --git a/app/job/service/goods/AdminImport.php b/app/job/service/goods/AdminImport.php new file mode 100644 index 00000000..5069bb34 --- /dev/null +++ b/app/job/service/goods/AdminImport.php @@ -0,0 +1,318 @@ + +// +---------------------------------------------------------------------- +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; + +/** + * 服务类:商品批量导入 + * Class Import + * @package app\job\service\goods + */ +class AdminImport extends BaseService +{ + /** + * 错误日志记录 + * @var array + */ + private array $errorLog = []; + + /** + * 导入成功的数量 + * @var int + */ + private int $successCount = 0; + + /** + * 商品主信息字段映射 + * @var string[] + */ + private array $mapGoods = [ + 'goods_name' => 'B', + 'goods_no' => 'C', + 'categoryIds' => 'D', + 'imagesIds' => 'E', + 'delivery_id' => 'F', + 'status' => 'G', + 'sort' => 'N', + 'deduct_stock_type' => 'O', + 'sales_initial' => 'P', + 'serviceIds' => 'Q', + 'is_points_gift' => 'R', + 'is_points_discount' => 'S', + 'is_enable_grade' => 'T', + ]; + + /** + * 商品SKU字段映射 + * @var string[] + */ + private array $mapSku = [ + 'specText' => 'H', + 'goods_price' => 'I', + 'line_price' => 'J', + 'stock_num' => 'K', + 'goods_weight' => 'L', + 'goods_sku_no' => 'M', + ]; + + /** + * 批量导入商品 + * @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); + $service->single($item['D'], $data, $storeId); + + // 生成商品数据(用于写入数据库) + // $data = $this->createData($item, $storeId); + // Log::record("data".json_encode($data)); + // // 数据验证 + // if (!$this->validateGoodsData($data, $storeId)) { + // $this->errorLog[] = ['goodsSn' => $item['A'], 'message' => $this->getError()]; + // continue; + // } + // // 事务处理:添加商品 + // $model = new GoodsModel(); + // $model->transaction(function () use ($model, $data, $storeId) { + // // 添加商品 + // $model->save($data); + // // 新增商品与分类关联 + // GoodsCategoryRelModel::increased((int)$model['goods_id'], $data['categoryIds'], $storeId); + // // 新增商品与图片关联 + // GoodsImageModel::increased((int)$model['goods_id'], $data['imagesIds'], $storeId); + // // 新增商品与规格关联 + // GoodsSpecRelModel::increased((int)$model['goods_id'], $data['newSpecList'], $storeId); + // // 新增商品sku信息 + // GoodsSkuModel::add((int)$model['goods_id'], $data['spec_type'], $data['newSkuList'], $storeId); + // // 新增服务与承诺关联 + // GoodsServiceRelModel::increased((int)$model['goods_id'], $data['serviceIds'], $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 $data + * @param int $storeId + * @return bool + */ + private function validateGoodsData(array $data, int $storeId): bool + { + // 验证商品信息:商品名称、分类ID集、图片ID集、运费模板ID + $validate = new GoodsAdminImportValidate; + Log::record($data); + if (!$validate->scene('goods')->check($data)) { + $this->setError($validate->getError()); + return false; + } + // 验证SKU信息:商品价格、库存数量、商品重量 + $skuList = $data['spec_type'] == GoodsSpecTypeEnum::MULTI ? $data['newSkuList'] : [$data['newSkuList']]; + foreach ($skuList as $item) { + $validate = new GoodsAdminImportValidate; + if (!$validate->scene('skuInfo')->check($item)) { + $this->setError($validate->getError()); + return false; + } + } + // 验证运费模板ID是否存在 + if (!DeliveryModel::checkDeliveryId((int)$data['delivery_id'], $storeId)) { + $this->setError('运费模板ID不存在'); + return false; + } + if ($data['spec_type'] == GoodsSpecTypeEnum::MULTI) { + // 判断用户填写的SKU数量是否正确 + $shouldSkuTotal = SpecModel::calcSkuListTotal($data['specData']['specList']); + $originalSkuTotal = \count($data['specData']['skuList']); + if ($shouldSkuTotal !== $originalSkuTotal) { + $this->setError('商品SKU数量不正确'); + return false; + } + // 判断商品SKU是否存在重复规格 + $goodsSkuIdArr = helper::getArrayColumn($data['newSkuList'], 'goods_sku_id'); + if (count($data['newSkuList']) != count(array_unique($goodsSkuIdArr))) { + $this->setError('商品SKU存在重复的规格值'); + return false; + } + } + return true; + } + + /** + * 生成商品数据(用于写入数据库) + * @param array $original + * @param int $storeId + * @return array + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\DbException + * @throws \think\db\exception\ModelNotFoundException + */ + private function createData(array $original, int $storeId): array + { + // 整理商品数据 + $data = [ + 'cmmdty_model' => $original["A"], + 'channel' => "jd", + 'goods_no' => $original["C"], + 'categoryIds' => $this->ids2array($original["E"]), + 'imageStorage' => 10,//下载图片到本地 + 'goods_type' => 10,//实物 + 'goods_status' => 10,//上架 + 'store_id' => $storeId, + ]; + // 过滤不存在的ID集数据 + //$data['categoryIds'] = CategoryModel::filterCategoryIds($data['categoryIds'], $storeId); + 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/job/service/goods/Collector.php b/app/job/service/goods/Collector.php index 757d84b1..2ccfe71b 100644 --- a/app/job/service/goods/Collector.php +++ b/app/job/service/goods/Collector.php @@ -116,7 +116,40 @@ class Collector extends BaseService $this->updateRecord($recordId, \count($urls)); return true; } - + public function single(string $url, array $form, int $storeId): bool + { + + try { + // 采集第三方商品数据 + $original = $this->collector($url, $storeId); + // 下载远程商品图片 + $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; + } + // 生成商品数据(用于写入数据库) + $data = $this->createData($original, $form, $storeId); + // 事务处理:添加商品 + $model = new GoodsModel(); + $model->transaction(function () use ($model, $data, $storeId) { + // 添加商品 + $model->save($data); + // 新增商品与分类关联 + GoodsCategoryRelModel::increased((int)$model['goods_id'], $data['categoryIds'], $storeId); + // 新增商品与图片关联 + GoodsImageModel::increased((int)$model['goods_id'], $data['imagesIds'], $storeId); + // 新增商品与规格关联 + GoodsSpecRelModel::increased((int)$model['goods_id'], $data['newSpecList'], $storeId); + // 新增商品sku信息 + GoodsSkuModel::add((int)$model['goods_id'], $data['spec_type'], $data['newSkuList'], $storeId); + }); + + return true; + } /** * 检查采集记录状态是否异常 * @param int $recordId @@ -251,6 +284,9 @@ class Collector extends BaseService 'newSkuList' => [], 'store_id' => $storeId, ]; + if (isset($form['channel']) { + $data['channel'] = $form['channel']; + } // 整理商品的价格和库存总量 if ($data['spec_type'] === GoodsSpecTypeEnum::MULTI) { $data['stock_total'] = GoodsSkuModel::getStockTotal($original['specData']['skuList']); diff --git a/app/store/model/Category.php b/app/store/model/Category.php index 99ab22a2..908a4997 100644 --- a/app/store/model/Category.php +++ b/app/store/model/Category.php @@ -28,7 +28,7 @@ class Category extends CategoryModel */ public function add($data): bool { - $data['store_id'] = self::$storeId; + $data['store_id'] = $data['store_id'] ?? self::$storeId; return $this->save($data); } diff --git a/app/store/model/goods/Import.php b/app/store/model/goods/Import.php index e855c62d..ef553737 100644 --- a/app/store/model/goods/Import.php +++ b/app/store/model/goods/Import.php @@ -15,7 +15,7 @@ namespace app\store\model\goods; use think\Paginator; use think\db\exception\DbException; use PhpOffice\PhpSpreadsheet\Exception; -use app\job\controller\goods\Import as GoodsImportJob; +use app\job\controller\goods\AdminImport as GoodsAdminImportJob; use app\common\enum\goods\ImportStatus as GoodsImportStatusEnum; use app\common\library\FileLocal; use app\common\library\helper; @@ -56,9 +56,68 @@ class Import extends ImportModel // 检索查询条件 $filter = []; $params['status'] > -1 && $filter[] = ['status', '=', (int)$params['status']]; + isset($params['store_id']) && $params['store_id'] > -1 && $filter[] = ['store_id', '=', (int)$params['store_id']]; return $filter; } - + /** + * 执行批量导入 + * @param array $form + * @return bool + * @throws BaseException + * @throws Exception + * @throws \PhpOffice\PhpSpreadsheet\Reader\Exception + */ + public function adminBatch(array $form): bool + { + // 读取excel文件内容 + $execlData = $this->readExecl(); + // 验证导入的商品数量是否合法 + $this->checkLimit($execlData); + + self::$storeId = 0; + // $service = new \app\job\service\goods\Collector(); + // foreach ($execlData as $item) { + // $data = [ + // 'cmmdty_model' => $item["A"], + // 'channel' => "jd", + // 'goods_no' => $item["C"], + // 'categoryIds' => explode(",", $item["E"]), + // 'imageStorage' => 10,//下载图片到本地 + // 'goods_type' => 10,//实物 + // 'goods_status' => 10,//上架 + // 'store_id' => self::$storeId, + // ]; + // $service->single($item['D'], $data, self::$storeId); + // } + // 新增商品导入记录 + $recordId = $this->addRecord(\count($execlData)); + // 调度计划任务 + $this->adminDispatchJob($execlData, $recordId); + return true; + } + /** + * 调度队列服务执行商品导入 + * @param array $goodsList 商品列表 + * @param int $recordId 商品导入记录ID + * @return void + */ + private function adminDispatchJob(array $goodsList, int $recordId) + { + // 分批每次导入20条 + $limit = 20; + // 根据商品总数量计算需要的队列任务数量 + $jobCount = \count($goodsList) / $limit; + // 逐次发布队列任务 + for ($i = 0; $i < $jobCount; $i++) { + $data = array_slice($goodsList, $i * $limit, $limit); + GoodsAdminImportJob::dispatch([ + 'list' => $data, + 'recordId' => $recordId, + 'storeId' => self::$storeId, + ]); + } + } + /** * 执行批量导入 * @param array $form @@ -121,7 +180,7 @@ class Import extends ImportModel ]); return (int)$this['id']; } - + /** * 格式化导入的商品列表数据 * @param array $execlData