Преглед изворни кода

feat(profit): 计算用户业绩 PV

- 新增 ProductPvDao 和 ProductPvModel 类,用于获取商品 PV 配置
- 重构 Pv 类,实现用户业绩 PV 的计算逻辑
- 修改 StoreOrderDao 类,增加 getDataForPv 方法获取订单数据- 更新 UserDao 类,增加 getTestUser 方法获取测试用户列表
- 调整 ProfitsRepository 类,优化日志记录和错误处理
shichen пре 10 месеци
родитељ
комит
383c3a1f50

+ 29 - 0
app/common/dao/profit/ProductPvDao.php

@@ -0,0 +1,29 @@
1
+<?php
2
+
3
+namespace app\common\dao\profit;
4
+
5
+use app\common\dao\BaseDao;
6
+use app\common\model\profit\ProductPvModel;
7
+
8
+class ProductPvDao extends BaseDao
9
+{
10
+    protected function getModel(): string
11
+    {
12
+        return ProductPvModel::class;
13
+    }
14
+
15
+    /**
16
+     * @param $productId
17
+     * @return mixed
18
+     * 获取商品业绩
19
+     */
20
+    public function getData($productId = 0)
21
+    {
22
+        $where = [];
23
+        if($productId != 0) {
24
+            $where['product_id'] = $productId;
25
+        }
26
+
27
+        return $this->getModel()::getDB()->where($where)->column('pv','product_id');
28
+    }
29
+}

+ 7 - 0
app/common/dao/store/order/StoreCartDao.php

@@ -218,4 +218,11 @@ class StoreCartDao extends BaseDao
218 218
             ->whereIn('cart_id', $cartIdList)
219 219
             ->update(['is_pay' => $isPay]);
220 220
     }
221
+
222
+    public function getDataByIds(array $cartIds)
223
+    {
224
+        /** @var StoreCart $model */
225
+        $model = $this->getModel();
226
+        return $model::getDB()->whereIn('cart_id', $cartIds)->select()->toArray();
227
+    }
221 228
 }

+ 42 - 12
app/common/dao/store/order/StoreOrderDao.php

@@ -637,19 +637,49 @@ class StoreOrderDao extends BaseDao
637 637
      * @return array
638 638
      * 获取pv
639 639
      */
640
-    public function getPv($userId, $price)
640
+    public function getDataForPv(array $userId = [], string $startTime = '', string $endTime = '', int $merId = 0, array $status = [], array $removeUserList = []): array
641 641
     {
642
+        // 创建where数组,依据默认值判断组成where条件
643
+        $where = [];
644
+
645
+        // mer_id条件:默认496表示特定商户,不等于496时添加条件
646
+        if ($merId != 0) {
647
+            $where[] = ['mer_id', '=', $merId];
648
+        }
649
+        
650
+        // uid条件:用户ID数组不为空时添加条件
651
+        if (!empty($userId)) {
652
+            $where[] = ['uid', 'in', $userId];
653
+        }
654
+        
655
+        // status条件:状态数组不为空时添加条件
656
+        if (!empty($status)) {
657
+            $where[] = ['status', 'in', $status];
658
+        }
659
+        
660
+        // removeUserList条件:排除用户列表不为空时添加条件
661
+        if (!empty($removeUserList)) {
662
+            $where[] = ['uid', 'not in', $removeUserList];
663
+        }
664
+        
665
+        // pay_time不为null条件:始终添加
666
+        $where[] = ['pay_time', 'not null'];
667
+        
668
+        // 时间范围条件:开始时间和结束时间都不为空时添加
669
+        if (!empty($startTime) && !empty($endTime)) {
670
+            $where[] = ['pay_time', 'between', [$startTime, $endTime]];
671
+        }
672
+
642 673
         $model = $this->getModel();
643
-        $oldPvData = $model::getDB()
644
-            ->where('mer_id', 496)
645
-            ->whereIn('uid', $userId)
646
-            ->whereIn('total_price', $price)
647
-            ->whereIn('status', [0, 1, 2, 3])
648
-            ->whereNotNull("pay_time")
649
-            ->group('total_price')
650
-            ->field('total_price,count(1) as count')
651
-            ->select()
652
-            ->toArray();
653
-        return array_column($oldPvData,'count','total_price');
674
+        $query = $model::getDB();
675
+        
676
+        // 应用where数组中的所有条件
677
+        foreach ($where as $condition) {
678
+            if (count($condition) === 3) {
679
+                $query->where($condition[0], $condition[1], $condition[2]);
680
+            }
681
+        }
682
+
683
+        return $query->select()->toArray();
654 684
     }
655 685
 }

+ 9 - 0
app/common/dao/user/UserDao.php

@@ -470,4 +470,13 @@ class UserDao extends BaseDao
470 470
     {
471 471
         return $this::getModel()::getDB()->whereIn('uid', $uids)->where(['user_group'=>$userGroup])->column('uid');
472 472
     }
473
+
474
+    /**
475
+     * @return array
476
+     * 获取测试用户
477
+     */
478
+    public function getTestUser(): array
479
+    {
480
+        return $this::getModel()::getDB()->where('test_user', '=', 1)->column('uid');
481
+    }
473 482
 }

+ 18 - 0
app/common/model/profit/ProductPvModel.php

@@ -0,0 +1,18 @@
1
+<?php
2
+
3
+namespace app\common\model\profit;
4
+
5
+use app\common\model\BaseModel;
6
+
7
+class ProductPvModel extends BaseModel
8
+{
9
+    public static function tablePk(): ?string
10
+    {
11
+        return 'product_id';
12
+    }
13
+
14
+    public static function tableName(): string
15
+    {
16
+        return 'product_pv';
17
+    }
18
+}

+ 9 - 17
app/common/repositories/profits/ProfitsRepository.php

@@ -4,12 +4,11 @@ namespace app\common\repositories\profits;
4 4
 
5 5
 
6 6
 use app\common\dao\profit\ZeroUserProfitDao;
7
-use app\common\dao\store\order\OldUserOfGoodsDao;
8
-use app\common\dao\store\order\StoreOrderDao;
9 7
 use app\common\dao\user\UserDao;
10 8
 use app\common\dao\user\ZeroUserLineDao;
11 9
 use app\common\enum\user\ZeroUserEnum;
12 10
 use app\common\model\profit\ZeroUserProfitsDetail;
11
+use app\common\utils\QueueLogger;
13 12
 use app\jobs\ZeroUserProfits;
14 13
 use think\db\exception\DataNotFoundException;
15 14
 use think\db\exception\DbException;
@@ -142,16 +141,18 @@ class ProfitsRepository
142 141
                 // 获取初,中,高用户
143 142
                 unset($groupData);
144 143
                 $groupData = $this->getUserByGroup($userIds, $key);
145
-                if (!empty($groupData)) {
146
-                    // 获取初中高用户直推和团队业绩
147
-                    $profitDetail = $this->getDirect($groupData, $amount * $value);
148
-                    $directUids[$key] = $profitDetail;
149
-                    $this->recordLog($param, $amount, $profitDetail);
144
+                if (empty($groupData)) {
145
+                    QueueLogger::info('0号线合伙人分红:0号线用户:'.$param['zeroUserId'].',暂无'.$key.'级别用户' );
146
+                    continue;
150 147
                 }
148
+                // 获取初中高用户直推和团队业绩
149
+                $profitDetail = $this->getDirect($groupData, $amount * $value);
150
+                $directUids[$key] = $profitDetail;
151
+                $this->recordLog($param, $amount, $profitDetail);
151 152
             }
152 153
             return true;
153 154
         } catch (\Exception $e) {
154
-            return false;
155
+            return $e->getMessage();
155 156
         }
156 157
     }
157 158
 
@@ -202,15 +203,6 @@ class ProfitsRepository
202 203
             return [];
203 204
         }
204 205
 
205
-        // // 循环获取0号线用户团队
206
-        // $resArr = [];
207
-        // foreach ($zeroUserIds as $zeroUserId) {
208
-        //     $zeroUid = $zeroUserId['team_uid'];
209
-        //     $sql = "select getUserLevelId($zeroUid) as teamUids";
210
-        //     $teamUids = Db::query($sql)[0]['teamUids'];
211
-        //     $resArr[$zeroUid] = $teamUids;
212
-        // }
213
-
214 206
         return $zeroUserIds;
215 207
     }
216 208
 

+ 167 - 25
app/common/utils/Pv.php

@@ -2,53 +2,195 @@
2 2
 
3 3
 namespace app\common\utils;
4 4
 
5
+use app\common\dao\profit\ProductPvDao;
6
+use app\common\dao\store\order\StoreCartDao;
5 7
 use app\common\dao\store\order\StoreOrderDao;
8
+use app\common\dao\user\UserDao;
6 9
 
7 10
 /**
8
- * 业绩计算
11
+ * PV业绩计算工具类
12
+ *
13
+ * 用于计算用户的业绩(PV)值,基于订单和购物车数据进行统计
14
+ * PV值根据商品配置的PV系数和购买数量计算得出
9 15
  */
10 16
 class Pv
11 17
 {
12
-    public $pv = [];
18
+    /**
19
+     * 商户ID,默认为496
20
+     * @var int
21
+     */
22
+    private $merId = 496;
23
+
24
+    /**
25
+     * 订单状态数组,包含有效的订单状态值
26
+     * 0,1,2,3 可能代表:待付款、已付款、已发货、已完成等状态
27
+     * @var array
28
+     */
29
+    private $status = [0, 1, 2, 3];
30
+
31
+    /**
32
+     * 需要排除的测试用户ID列表
33
+     * 在计算业绩时需要过滤掉的测试用户
34
+     * @var array
35
+     */
36
+    private $removeUserIdList;
37
+
38
+    /**
39
+     * 订单数据访问对象
40
+     * @var StoreOrderDao
41
+     */
13 42
     public $storeOrderDao;
43
+
44
+    /**
45
+     * 购物车数据访问对象
46
+     * @var StoreCartDao
47
+     */
48
+    public $cartDao;
49
+
50
+    /**
51
+     * 用户数据访问对象
52
+     * @var UserDao
53
+     */
54
+    public $userDao;
55
+
56
+    public $pvDao;
57
+
58
+    /**
59
+     * 构造函数
60
+     * 初始化数据访问对象和测试用户列表
61
+     */
14 62
     public function __construct()
15 63
     {
16
-        $this->pv = $this->getPvConfig();
17 64
         $this->storeOrderDao = new StoreOrderDao();
65
+        $this->cartDao = new StoreCartDao();
66
+        $this->userDao = new UserDao();
67
+        $this->pvDao = new ProductPvDao();
68
+        $this->removeUserIdList = $this->getRemoveUserIdList();
18 69
     }
19 70
 
20
-    public function getPvConfig()
71
+    /**
72
+     * 获取商品PV配置
73
+     *
74
+     * 返回商品ID到PV系数的映射数组
75
+     * PV系数表示每个商品对应的业绩值
76
+     *
77
+     * @return int[] 商品PV配置数组,格式:[商品ID => PV系数]
78
+     */
79
+    public function getPvConfig(): array
21 80
     {
22
-        return [
23
-            1 => 2,
24
-            2 => 3
25
-        ];
81
+        return  $this->pvDao->getData();
26 82
     }
27 83
 
28
-    // 计算业绩
29
-    public function countPv($userId = 0,$startTime = 0,$endTime = 0)
84
+    /**
85
+     * 计算用户业绩(PV)
86
+     *
87
+     * 根据用户ID和时间范围计算业绩值
88
+     * 流程:获取订单数据 → 获取购物车数据 → 统计商品数量 → 计算PV总值
89
+     *
90
+     * @param array $userId 用户ID数组,默认为空数组
91
+     * @param int $startTime 开始时间戳,0表示无限制
92
+     * @param int $endTime 结束时间戳,0表示无限制
93
+     * @return void 直接输出计算结果(使用dd函数)
94
+     */
95
+    public function countPv($userId = [], $startTime = 0, $endTime = 0): void
30 96
     {
31
-        $orderData = $this->storeOrderDao->getDataForPv($userId = 0,$startTime = 0,$endTime = 0);
97
+        // 测试用户id(硬编码,实际应该使用传入的参数)
98
+        // $userId = [1592];
99
+        
100
+        // 时间范围示例(注释掉的代码)
101
+        // $startTime = '2025-9-8';
102
+        // $endTime = '2025-9-11';
103
+        
104
+        // 获取订单数据(过滤掉测试用户)
105
+        $orderData = $this->storeOrderDao->getDataForPv(
106
+            $userId,
107
+            $startTime,
108
+            $endTime,
109
+            $this->merId,
110
+            $this->status,
111
+            $this->removeUserIdList
112
+        );
113
+        
114
+        // 提取所有购物车ID
115
+        $cartIds = array_column($orderData, 'cart_id');
32 116
 
33
-
34
-
35
-        $cartData = $this->getCartData();
117
+        // 根据购物车ID获取购物车数据
118
+        $cartData = $this->cartDao->getDataByIds($cartIds);
119
+        
120
+        // 统计每个商品的出现次数
121
+        $productCount = $this->countProductIds($cartData);
122
+        
123
+        // 根据商品数量和PV配置计算总PV值
124
+        $pv = $this->countResult($productCount);
125
+        
126
+        // 输出计算结果(调试用)
127
+        dd($pv);
36 128
     }
37 129
 
38
-    // 获取订单数据
39
-    public function getOrderData($userId,$startTime,$endTime){
130
+    /**
131
+     * 获取需要排除的测试用户ID列表
132
+     *
133
+     * 从用户DAO获取标记为测试用户的ID列表
134
+     * 这些用户在计算业绩时会被排除
135
+     *
136
+     * @return array 测试用户ID数组
137
+     */
138
+    private function getRemoveUserIdList(): array
139
+    {
140
+        return $this->userDao->getTestUser();
141
+    }
40 142
 
41
-        dd($this->storeOrderDao::getModel()::getDB());
143
+    /**
144
+     * 统计购物车数据中商品ID的出现次数
145
+     *
146
+     * 遍历购物车数据,统计每个商品ID的购买次数
147
+     *
148
+     * @param array $cartData 购物车数据数组,包含商品信息
149
+     * @return array 商品ID统计结果,格式:[商品ID => 购买次数]
150
+     */
151
+    private function countProductIds(array $cartData): array
152
+    {
153
+        $productIdCounts = [];
154
+        
155
+        foreach ($cartData as $cartItem) {
156
+            $productId = $cartItem['product_id'];
157
+            
158
+            if (isset($productIdCounts[$productId])) {
159
+                // 如果商品已存在,增加计数
160
+                $productIdCounts[$productId]++;
161
+            } else {
162
+                // 如果商品不存在,初始化计数为1
163
+                $productIdCounts[$productId] = 1;
164
+            }
165
+        }
42 166
 
43
-        return [
44
-            1=>[],
45
-            2=>[]
46
-        ];
167
+        return $productIdCounts;
47 168
     }
48 169
 
49
-    public function getCartData(){
50
-        return [
51
-
52
-        ];
170
+    /**
171
+     * 计算最终PV结果
172
+     *
173
+     * 根据商品购买次数和PV配置计算总业绩值
174
+     * 公式:总PV = Σ(商品购买次数 × 商品PV系数)
175
+     *
176
+     * @param array $productCount 商品购买次数数组,格式:[商品ID => 购买次数]
177
+     * @return float|int 总PV值
178
+     */
179
+    private function countResult(array $productCount)
180
+    {
181
+        // 获取PV配置
182
+        $pvConfig = $this->getPvConfig();
183
+        $totalPv = 0;
184
+        
185
+        // 遍历每个商品计算PV贡献
186
+        foreach ($productCount as $productId => $count) {
187
+            if (isset($pvConfig[$productId])) {
188
+                // 如果商品有配置PV系数,计算该商品的PV贡献
189
+                $totalPv += $count * $pvConfig[$productId];
190
+            }
191
+            // 如果没有配置PV系数的商品,不计入业绩
192
+        }
193
+        
194
+        return $totalPv;
53 195
     }
54 196
 }

+ 7 - 0
app/controller/admin/profits/ShareProfits.php

@@ -3,6 +3,7 @@
3 3
 namespace app\controller\admin\profits;
4 4
 
5 5
 use app\common\repositories\profits\ProfitsRepository;
6
+use app\common\utils\Pv;
6 7
 use app\controller\admin\BaseController;
7 8
 use app\entity\admin\request\profits\PartnerProfitsRequestEntity;
8 9
 use app\entity\admin\response\profits\ZeroUserProfitResponseEntity;
@@ -32,6 +33,12 @@ class ShareProfits extends BaseController
32 33
      */
33 34
     public function partnerProfits(PartnerProfitsValidate $validate)
34 35
     {
36
+
37
+        $a = new Pv();
38
+        $a->countPv();
39
+        die;
40
+
41
+
35 42
         $validationResult = $this->validateRequest($validate, PartnerProfitsRequestEntity::class);
36 43
         $res = $this->repository->getZeroUser($validationResult->toArray());
37 44
         // $res = $this->repository->profits($validationResult->toArray());

+ 2 - 2
app/jobs/ZeroUserProfits.php

@@ -25,13 +25,13 @@ class ZeroUserProfits implements JobInterface
25 25
             // 执行业务
26 26
             $res = $repository->profits($data);
27 27
             
28
-            if ($res) {
28
+            if (!is_string($res)) {
29 29
                 // 记录成功日志
30 30
                 QueueLogger::success('ZeroUserProfits', $job, $data, $res);
31 31
                 $job->delete();
32 32
             } else {
33 33
                 // 记录业务执行失败但未异常的情况
34
-                QueueLogger::warning('ZeroUserProfits', $job, $data, '业务执行返回false');
34
+                QueueLogger::warning('ZeroUserProfits', $job, $data, '业务执行返回false,错误信息:' . $res);
35 35
                 
36 36
                 if ($job->attempts() > 3) {
37 37
                     // 超过重试次数,调用failed方法记录异常状态