Ver código fonte

feat(command): 添加1688今日商品自动入库命令

- 新增 AlibabaTodayImport.php 命令类实现自动入库功能
- 支持按商户ID、指定日期、批量处理等参数进行商品导入
- 集成分润设置功能,默认配置15%分润比例
- 实现原价计算规则,根据SKU售价阶梯加价策略
- 添加断点续传支持,可通过skip参数跳过已处理商品
- 集成进度显示和错误处理机制
- 在console配置中注册alibaba:today-import命令
shichen 2 meses atrás
pai
commit
b50e06ca4e
2 arquivos alterados com 308 adições e 0 exclusões
  1. 307 0
      app/command/AlibabaTodayImport.php
  2. 1 0
      config/console.php

+ 307 - 0
app/command/AlibabaTodayImport.php

@@ -0,0 +1,307 @@
1
+<?php
2
+
3
+namespace app\command;
4
+
5
+use app\services\ThirdParty\AlibabaAgent\ProductService;
6
+use think\console\Command;
7
+use think\console\Input;
8
+use think\console\Output;
9
+use think\facade\Db;
10
+
11
+/**
12
+ * 今日1688商品自动入库命令
13
+ *
14
+ * 从 alibaba_import_goods 表中获取今日创建的商品,
15
+ * 调用 ProductService::importToStoreProduct() 进行正式入库到 store_product 表
16
+ *
17
+ * 用法:
18
+ *   php think alibaba:today-import                                          # 默认今天,mer_id=3447
19
+ *   php think alibaba:today-import --mer_id=3447                            # 指定商户ID
20
+ *   php think alibaba:today-import --date=2026-05-21                        # 指定日期
21
+ *   php think alibaba:today-import --mer_id=3447 --date=2026-05-21          # 指定商户+日期
22
+ *   php think alibaba:today-import --batch=100                              # 每批处理100条(默认50)
23
+ *   php think alibaba:today-import --skip=10                                # 跳过前10条(断点续传)
24
+ *
25
+ * 定时任务配置(每天23:55执行):
26
+ *   55 23 * * * php /www/wwwroot/shop/think alibaba:today-import --mer_id=3447 >> /tmp/alibaba_today_import.log 2>&1
27
+ *
28
+ * 分润设置(写死):
29
+ *   - extension_type: 1(开启分润)
30
+ *   - concession_pri: 售价 × 15%(商家让利金额)
31
+ *   - extension_one: 15(SKU级分润比例)
32
+ *   - plate_mer_profit: 0(平台商户利润)
33
+ *   - commission: 保持原样(不修改)
34
+ *
35
+ * 原价计算规则(根据SKU售价阶梯加价):
36
+ *   1-20元    => 固定加5元
37
+ *   21-50元   => 固定加20元
38
+ *   51-120元  => 固定加40元
39
+ *   121-200元 => 固定加45元
40
+ *   201-300元 => 固定加50元
41
+ */
42
+class AlibabaTodayImport extends Command
43
+{
44
+    protected function configure()
45
+    {
46
+        $this->setName('alibaba:today-import')
47
+            ->setDescription('从alibaba_import_goods获取今日创建的商品,调用importStore接口正式入库')
48
+            ->addOption('mer_id', null, \think\console\input\Option::VALUE_OPTIONAL, '商户ID', '3447')
49
+            ->addOption('date', null, \think\console\input\Option::VALUE_OPTIONAL, '指定日期 (Y-m-d),默认今天', '')
50
+            ->addOption('batch', null, \think\console\input\Option::VALUE_OPTIONAL, '每批处理数量', '50')
51
+            ->addOption('skip', null, \think\console\input\Option::VALUE_OPTIONAL, '跳过前N条(断点续传)', '0');
52
+    }
53
+
54
+    protected function execute(Input $input, Output $output)
55
+    {
56
+        // 解除时间限制
57
+        set_time_limit(0);
58
+
59
+        $output->writeln('========================================');
60
+        $output->writeln(' 1688今日商品自动入库 开始');
61
+        $output->writeln('========================================');
62
+
63
+        // 1. 解析参数
64
+        $merId = (int)$input->getOption('mer_id');
65
+        $date  = $input->getOption('date');
66
+        $batch = (int)$input->getOption('batch');
67
+        $skip  = (int)$input->getOption('skip');
68
+
69
+        if ($batch < 1) $batch = 50;
70
+        if ($skip < 0) $skip = 0;
71
+
72
+        if (empty($date)) {
73
+            $date = date('Y-m-d');
74
+        }
75
+
76
+        // ============================================================
77
+        // 分润配置(写死)
78
+        // ============================================================
79
+        $profitConfig = [
80
+            'extension_type'  => 1,      // 1=开启分润
81
+            'concession_rate' => 0.15,   // 商家让利比例15%(concession_pri = 售价 × 15%)
82
+            'extension_one'   => 15,     // SKU级分润比例15%
83
+        ];
84
+
85
+        $output->writeln("商户ID:         {$merId}");
86
+        $output->writeln("查询日期:       {$date}");
87
+        $output->writeln("每批数量:       {$batch}");
88
+        $output->writeln("跳过前N条:      {$skip}");
89
+        $output->writeln('分润设置:');
90
+        $output->writeln("  extension_type:  {$profitConfig['extension_type']} (开启分润)");
91
+        $output->writeln("  concession_rate: {$profitConfig['concession_rate']} (售价×{$profitConfig['concession_rate']})");
92
+        $output->writeln("  extension_one:   {$profitConfig['extension_one']}%");
93
+
94
+        // 2. 查询今日创建的 alibaba_import_goods 记录(只查ID,不查全字段,节省内存)
95
+        $todayStart = $date . ' 00:00:00';
96
+        $todayEnd   = $date . ' 23:59:59';
97
+
98
+        $output->write('正在查询今日待入库商品... ');
99
+        $allIds = Db::name('alibaba_import_goods')
100
+            ->where('status', 1)
101
+            ->where('create_time', '>=', $todayStart)
102
+            ->where('create_time', '<=', $todayEnd)
103
+            ->order('id', 'ASC')
104
+            ->column('id');
105
+
106
+        $total = count($allIds);
107
+        $output->writeln("共 {$total} 条");
108
+
109
+        if ($total === 0) {
110
+            $output->writeln("{$date} 没有需要入库的商品");
111
+            $output->writeln('========================================');
112
+            $output->writeln(' 1688今日商品自动入库 结束 (无商品)');
113
+            $output->writeln('========================================');
114
+            return;
115
+        }
116
+
117
+        // 3. 应用 skip 跳过已处理的
118
+        if ($skip > 0) {
119
+            $allIds = array_slice($allIds, $skip);
120
+            $output->writeln("跳过前 {$skip} 条,剩余 " . count($allIds) . " 条");
121
+        }
122
+
123
+        // 4. 分批处理
124
+        /** @var ProductService $productService */
125
+        $productService = app()->make(ProductService::class);
126
+
127
+        $chunks = array_chunk($allIds, $batch);
128
+        $totalChunks = count($chunks);
129
+
130
+        $output->writeln("共分 {$totalChunks} 批执行");
131
+        $output->writeln('');
132
+
133
+        $globalSuccess = 0;
134
+        $globalFail    = 0;
135
+        $globalProfitSet = 0;
136
+        $startTime     = time();
137
+
138
+        foreach ($chunks as $chunkIndex => $idBatch) {
139
+            $batchStartTime = time();
140
+            $batchNum = $chunkIndex + 1;
141
+            $processedCount = $skip + ($chunkIndex * $batch) + 1;
142
+
143
+            $output->writeln("--- 第 {$batchNum}/{$totalChunks} 批 (已处理 {$processedCount}/{$total}) ---");
144
+
145
+            foreach ($idBatch as $importId) {
146
+                $output->write("  [{$processedCount}/{$total}] import_goods_id={$importId} ... ");
147
+
148
+                try {
149
+                    // 4a. 调用原始入库方法
150
+                    $result = $productService->importToStoreProduct($importId, $merId);
151
+
152
+                    if ($result['code'] === 200) {
153
+                        $productId = $result['product_id'];
154
+                        $globalSuccess++;
155
+
156
+                        // 4b. 根据SKU售价阶梯计算原价(ot_price)
157
+                        try {
158
+                            // 获取所有SKU的售价(用 unique 字段唯一标识每个SKU)
159
+                            $skuValues = Db::name('store_product_attr_value')
160
+                                ->where('product_id', $productId)
161
+                                ->field('unique, price')
162
+                                ->select()
163
+                                ->toArray();
164
+
165
+                            $skuPrices = [];
166
+                            $skuOtPrices = [];
167
+                            foreach ($skuValues as $sku) {
168
+                                $salePrice = (float)$sku['price'];
169
+                                $skuPrices[] = $salePrice;
170
+
171
+                                // 根据售价区间计算原价
172
+                                $otPrice = self::calcOtPrice($salePrice);
173
+                                $skuOtPrices[] = $otPrice;
174
+
175
+                                // 通过 unique 字段更新当前SKU的 ot_price
176
+                                Db::name('store_product_attr_value')
177
+                                    ->where('unique', $sku['unique'])
178
+                                    ->update(['ot_price' => $otPrice]);
179
+                            }
180
+
181
+                            // 更新 store_product 表的 price(最低售价)和 ot_price(最高原价)
182
+                            $minPrice = !empty($skuPrices) ? min($skuPrices) : 0;
183
+                            $maxOtPrice = !empty($skuOtPrices) ? max($skuOtPrices) : 0;
184
+                            Db::name('store_product')
185
+                                ->where('product_id', $productId)
186
+                                ->update([
187
+                                    'price'    => $minPrice,
188
+                                    'ot_price' => $maxOtPrice,
189
+                                ]);
190
+
191
+                            $output->write("<info>[原价已计算]</info> ");
192
+                        } catch (\Throwable $priceE) {
193
+                            $output->write("<comment>[原价计算失败: {$priceE->getMessage()}]</comment> ");
194
+                        }
195
+
196
+                        // 4c. 设置分润(写死15%)
197
+                        try {
198
+                            // 获取商品最低售价
199
+                            $storeProduct = Db::name('store_product')
200
+                                ->where('product_id', $productId)
201
+                                ->field('price')
202
+                                ->find();
203
+
204
+                            $salePrice = $storeProduct ? (float)$storeProduct['price'] : 0;
205
+
206
+                            // 计算 concession_pri = 售价 × 15%,使用 bc 函数保留两位小数
207
+                            $concessionPri = '0.00';
208
+                            if ($salePrice > 0) {
209
+                                $concessionPri = bcmul((string)$salePrice, (string)$profitConfig['concession_rate'], 2);
210
+                            }
211
+
212
+                            // 更新 store_product 表的分润字段(commission保持原样不修改)
213
+                            Db::name('store_product')
214
+                                ->where('product_id', $productId)
215
+                                ->update([
216
+                                    'extension_type' => $profitConfig['extension_type'],
217
+                                    'concession_pri' => $concessionPri,
218
+                                ]);
219
+
220
+                            // 更新 store_product_attr_value 表的 SKU 分润比例
221
+                            Db::name('store_product_attr_value')
222
+                                ->where('product_id', $productId)
223
+                                ->update([
224
+                                    'extension_one' => $profitConfig['extension_one'],
225
+                                ]);
226
+
227
+                            $globalProfitSet++;
228
+                            $output->write("<info>[分润15%已设置]</info> ");
229
+                        } catch (\Throwable $profitE) {
230
+                            $output->write("<comment>[分润设置失败: {$profitE->getMessage()}]</comment> ");
231
+                        }
232
+
233
+                        $output->writeln("<info>成功 => product_id={$productId}</info>");
234
+                    } else {
235
+                        $globalFail++;
236
+                        $output->writeln("<comment>跳过: {$result['msg']}</comment>");
237
+                    }
238
+                } catch (\Throwable $e) {
239
+                    $globalFail++;
240
+                    $output->writeln("<error>异常: {$e->getMessage()}</error>");
241
+                }
242
+
243
+                $processedCount++;
244
+            }
245
+
246
+            // 每批结束后输出当前进度和耗时
247
+            $batchElapsed = time() - $batchStartTime;
248
+            $totalElapsed = time() - $startTime;
249
+            $output->writeln("  本批耗时: {$batchElapsed}s | 累计耗时: {$totalElapsed}s | 累计成功: {$globalSuccess} | 累计失败: {$globalFail} | 分润设置: {$globalProfitSet}");
250
+            $output->writeln('');
251
+
252
+            // 每批之间短暂休眠,避免数据库压力过大
253
+            if ($chunkIndex < $totalChunks - 1) {
254
+                $output->writeln("  等待 1 秒后继续下一批...");
255
+                sleep(1);
256
+            }
257
+        }
258
+
259
+        // 5. 输出汇总结果
260
+        $totalElapsed = time() - $startTime;
261
+        $output->writeln('');
262
+        $output->writeln('========================================');
263
+        $output->writeln(' 入库完成');
264
+        $output->writeln('========================================');
265
+        $output->writeln("总耗时:         {$totalElapsed}s");
266
+        $output->writeln("总处理:         {$total} 条");
267
+        $output->writeln("成功:           {$globalSuccess} 条");
268
+        $output->writeln("失败:           {$globalFail} 条");
269
+        $output->writeln("已设置分润15%:  {$globalProfitSet} 条");
270
+
271
+        if ($globalFail > 0) {
272
+            $output->writeln('');
273
+            $output->writeln('<comment>提示: 失败的商品可以查看 alibaba 日志通道获取详细信息</comment>');
274
+            $output->writeln("<comment>      重跑时可加 --skip={$skip} 跳过已处理的商品</comment>");
275
+        }
276
+
277
+        $output->writeln('========================================');
278
+        $output->writeln(' 1688今日商品自动入库 结束');
279
+        $output->writeln('========================================');
280
+    }
281
+
282
+    /**
283
+     * 根据售价计算原价(阶梯加价)
284
+     *
285
+     * @param float $salePrice SKU售价
286
+     * @return float 计算后的原价
287
+     */
288
+    protected static function calcOtPrice(float $salePrice): float
289
+    {
290
+        if ($salePrice <= 0) {
291
+            return 0;
292
+        }
293
+
294
+        if ($salePrice >= 1 && $salePrice <= 20) {
295
+            return $salePrice + 5;
296
+        } elseif ($salePrice >= 21 && $salePrice <= 50) {
297
+            return $salePrice + 20;
298
+        } elseif ($salePrice >= 51 && $salePrice <= 120) {
299
+            return $salePrice + 40;
300
+        } elseif ($salePrice >= 121 && $salePrice <= 200) {
301
+            return $salePrice + 45;
302
+        } else {
303
+            // 201元以上(含201-300及超过300),统一加50元
304
+            return $salePrice + 50;
305
+        }
306
+    }
307
+}

+ 1 - 0
config/console.php

@@ -38,5 +38,6 @@ return [
38 38
         'productPriceUpdate' => 'app\command\ProductPriceUpdate',
39 39
         'deleteApiLog' => 'app\command\DeleteApiLog',
40 40
         'sharedOfflineOrderProfitDelete' => 'app\command\SharedOfflineOrderProfitDelete',
41
+        'alibaba:today-import' => 'app\command\AlibabaTodayImport',
41 42
     ],
42 43
 ];