Kaynağa Gözat

feat(profits): 实现合伙人分红功能

- 新增合伙人分红相关的模型、DAO和控制器
- 实现了获取0号线用户、计算分红、记录日志等核心功能
- 添加了队列任务处理和日志记录机制
shichen 10 ay önce
ebeveyn
işleme
8d2940570c

+ 5 - 0
app/common/dao/BaseDao.php

@@ -170,6 +170,11 @@ abstract class BaseDao
170 170
         return ($this->getModel())::getInstance()->where($where)->field($field)->select();
171 171
     }
172 172
 
173
+    public function selectWhereIn(string $whereField, array $where, string $field = '*')
174
+    {
175
+        return ($this->getModel())::getInstance()->whereIn($whereField,$where)->field($field)->select();
176
+    }
177
+
173 178
     /**
174 179
      * @param int $id
175 180
      * @param array $with

+ 25 - 0
app/common/dao/profit/ZeroUserProfitDao.php

@@ -0,0 +1,25 @@
1
+<?php
2
+
3
+namespace app\common\dao\profit;
4
+
5
+use app\common\dao\BaseDao;
6
+use app\common\model\profit\ZeroUserProfits;
7
+
8
+class ZeroUserProfitDao extends BaseDao
9
+{
10
+
11
+    protected function getModel(): string
12
+    {
13
+        return ZeroUserProfits::class;
14
+    }
15
+
16
+    /**
17
+     * @param $data
18
+     * @return mixed
19
+     * 插入数据并获取id
20
+     */
21
+    public function insertGetId($data)
22
+    {
23
+        return $this->getModel()::getDB()->insertGetId($data);
24
+    }
25
+}

+ 15 - 0
app/common/dao/profit/ZeroUserProfitDetailDao.php

@@ -0,0 +1,15 @@
1
+<?php
2
+
3
+namespace app\common\dao\profit;
4
+
5
+use app\common\dao\BaseDao;
6
+use app\common\model\profit\ZeroUserProfitsDetail;
7
+
8
+class ZeroUserProfitDetailDao extends BaseDao
9
+{
10
+
11
+    protected function getModel(): string
12
+    {
13
+        return ZeroUserProfitsDetail::class;
14
+    }
15
+}

+ 35 - 0
app/common/dao/store/order/OldUserOfGoodsDao.php

@@ -0,0 +1,35 @@
1
+<?php
2
+
3
+namespace app\common\dao\store\order;
4
+
5
+use app\common\dao\BaseDao;
6
+use app\common\model\store\order\OldUserOfGoods;
7
+
8
+class OldUserOfGoodsDao extends BaseDao
9
+{
10
+
11
+    protected function getModel(): string
12
+    {
13
+        return OldUserOfGoods::class;
14
+    }
15
+
16
+    /**
17
+     * @param $userId
18
+     * @param $price
19
+     * @return array
20
+     * 获取业绩
21
+     */
22
+    public function getPv($userId, $price)
23
+    {
24
+        $model = $this->getModel();
25
+        $oldPvData = $model::getDB()
26
+            ->whereIn('user_id', $userId)
27
+            ->whereIn('money', $price)
28
+            ->where('status', 1)
29
+            ->group('money')
30
+            ->field('money,count(1) as count')
31
+            ->select()
32
+            ->toArray();
33
+        return array_column($oldPvData,'count','money');
34
+    }
35
+}

+ 22 - 0
app/common/dao/store/order/StoreOrderDao.php

@@ -630,4 +630,26 @@ class StoreOrderDao extends BaseDao
630 630
         }
631 631
         return $entityList;
632 632
     }
633
+
634
+    /**
635
+     * @param $userId
636
+     * @param $price
637
+     * @return array
638
+     * 获取pv
639
+     */
640
+    public function getPv($userId, $price)
641
+    {
642
+        $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');
654
+    }
633 655
 }

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

@@ -456,4 +456,18 @@ class UserDao extends BaseDao
456 456
     {
457 457
         return $this::getModel()::getDB()->where('level_id', '=', $userId)->column('uid');
458 458
     }
459
+
460
+    /**
461
+     * @param $uids
462
+     * @param $userGroup
463
+     * @return array
464
+     * @throws DataNotFoundException
465
+     * @throws DbException
466
+     * @throws ModelNotFoundException
467
+     * 通过user_group获取用户
468
+     */
469
+    public function getUsersByGroup($uids,$userGroup)
470
+    {
471
+        return $this::getModel()::getDB()->whereIn('uid', $uids)->where(['user_group'=>$userGroup])->column('uid');
472
+    }
459 473
 }

+ 19 - 0
app/common/model/profit/ZeroUserProfits.php

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

+ 21 - 0
app/common/model/profit/ZeroUserProfitsDetail.php

@@ -0,0 +1,21 @@
1
+<?php
2
+
3
+namespace app\common\model\profit;
4
+
5
+use app\common\model\BaseModel;
6
+
7
+class ZeroUserProfitsDetail extends BaseModel
8
+{
9
+    private static $tablePk = 'id';
10
+    private static $tableName = 'zero_user_profit_detail';
11
+
12
+    public static function tablePk(): ?string
13
+    {
14
+        return self::$tablePk;
15
+    }
16
+
17
+    public static function tableName(): string
18
+    {
19
+        return self::$tableName;
20
+    }
21
+}

+ 20 - 0
app/common/model/store/order/OldUserOfGoods.php

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

+ 374 - 0
app/common/repositories/profits/ProfitsRepository.php

@@ -0,0 +1,374 @@
1
+<?php
2
+
3
+namespace app\common\repositories\profits;
4
+
5
+
6
+use app\common\dao\profit\ZeroUserProfitDao;
7
+use app\common\dao\store\order\OldUserOfGoodsDao;
8
+use app\common\dao\store\order\StoreOrderDao;
9
+use app\common\dao\user\UserDao;
10
+use app\common\dao\user\ZeroUserLineDao;
11
+use app\common\enum\user\ZeroUserEnum;
12
+use app\common\model\profit\ZeroUserProfitsDetail;
13
+use app\jobs\ZeroUserProfits;
14
+use think\db\exception\DataNotFoundException;
15
+use think\db\exception\DbException;
16
+use think\db\exception\ModelNotFoundException;
17
+use think\facade\Db;
18
+
19
+class ProfitsRepository
20
+{
21
+    // 初级合伙人id
22
+    const BASIC = 4;
23
+    // 初级合伙人佣金比例
24
+    const BASIC_PERCENT = 0.15;
25
+
26
+    // 中级合伙人id
27
+    const MIDDLE = 5;
28
+    // 中级合伙人佣金比例
29
+    const MIDDLE_PERCENT = 0.15;
30
+
31
+    // 高级合伙人id
32
+    const HIGH = 6;
33
+    // 高级合伙人佣金比例
34
+    const HIGH_PERCENT = 0.15;
35
+
36
+    // 合伙人数据映射
37
+    private $parents = [
38
+        self::BASIC => self::BASIC_PERCENT,
39
+        self::MIDDLE => self::MIDDLE_PERCENT,
40
+        self::HIGH => self::HIGH_PERCENT,
41
+    ];
42
+
43
+    // 业绩数据映射(pv)
44
+    private $pv = [
45
+        1180 => 700,
46
+        11800 => 7000,
47
+        10620 => 6300,
48
+        9440 => 5600,
49
+        2360 => 1400
50
+    ];
51
+
52
+    // 用户
53
+    private $userDao;
54
+
55
+    // 订单(老)
56
+    // private $oldUserOfGoodsDao;
57
+
58
+    // 订单(新)
59
+    // private $storeOrder;
60
+
61
+    public function __construct()
62
+    {
63
+        $this->userDao = new UserDao();
64
+        // $this->oldUserOfGoodsDao = new OldUserOfGoodsDao();
65
+        // $this->storeOrder = new StoreOrderDao();
66
+    }
67
+
68
+    /**
69
+     * @param $param
70
+     * @return string|true
71
+     * @throws DataNotFoundException
72
+     * @throws DbException
73
+     * @throws ModelNotFoundException
74
+     * 获取需要分红的0号线用户并加入队列
75
+     */
76
+    public function getZeroUser($param)
77
+    {
78
+        $zeroUserId = $param['zeroUserId'];
79
+        // 0号线用户id是0的时候,获取全部0号线用户
80
+        if ($zeroUserId == 0) {
81
+            $zeroUserId = $this->getAllZeroUserIds();
82
+        }
83
+        if (is_string($zeroUserId)) {
84
+            $zeroUserId = explode(',', $zeroUserId);
85
+        }
86
+
87
+        // 拼接队列需要的数据
88
+        $queueData = [
89
+            'startTime' => $param['startTime'],
90
+            'endTime' => $param['endTime'],
91
+            'is_test' => $param['isTest'],
92
+            'amount' => $param['amount']
93
+        ];
94
+
95
+        // 记录日志
96
+        $insertData = [
97
+            'start_time' => $param['startTime'],
98
+            'end_time' => $param['endTime'],
99
+            'is_test' => $param['isTest'],
100
+            'amount' => $param['amount']
101
+        ];
102
+        $dao = new ZeroUserProfitDao();
103
+        // 加入队列
104
+        try {
105
+            foreach ($zeroUserId as $value) {
106
+                $insertData['user_id'] = $value;
107
+                $logId = $dao->insertGetId($insertData);
108
+
109
+                $queueData['zeroUserId'] = $value;
110
+                $queueData['logId'] = $logId;
111
+
112
+                queue(ZeroUserProfits::class, $queueData, 0, 'zeroProfit');
113
+            }
114
+            return true;
115
+        } catch (\Exception $e) {
116
+            return $e->getMessage();
117
+        }
118
+
119
+
120
+    }
121
+
122
+    /**
123
+     * @throws DataNotFoundException
124
+     * @throws ModelNotFoundException
125
+     * @throws DbException
126
+     * 执行分红
127
+     */
128
+    public function profits($param)
129
+    {
130
+        try {
131
+            // 获取0号线用户的所有下级用户
132
+            $userIds = $this->getAllUser($param['zeroUserId']);
133
+            // 获取分红总额(业绩)
134
+            $amount = $param['amount'];
135
+            if ($param['amount'] == 0) {
136
+                $amount = $this->getAmount($userIds);
137
+            }
138
+
139
+            // 计算分红
140
+            $directUids = [];
141
+            foreach ($this->parents as $key => $value) {
142
+                // 获取初,中,高用户
143
+                unset($groupData);
144
+                $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);
150
+                }
151
+            }
152
+            return true;
153
+        } catch (\Exception $e) {
154
+            return false;
155
+        }
156
+    }
157
+
158
+    /**
159
+     * @param $param
160
+     * @return array
161
+     * 获取要分红的用户ids
162
+     */
163
+    private function getUserIds($param): array
164
+    {
165
+
166
+        $res = $this->getAllZeroUser($param['zeroUserId']);
167
+        return $res;
168
+    }
169
+
170
+    /**
171
+     * @return array
172
+     * @throws \think\db\exception\DataNotFoundException
173
+     * @throws \think\db\exception\DbException
174
+     * @throws \think\db\exception\ModelNotFoundException
175
+     * 获取所有下级用户
176
+     */
177
+    private function getAllUser($zeroUid)
178
+    {
179
+
180
+        $sql = "select getUserLevelId($zeroUid) as teamUids";
181
+        $teamUids = Db::query($sql)[0]['teamUids'];
182
+        // $resArr[$zeroUid] = $teamUids;
183
+        return $teamUids;
184
+    }
185
+
186
+    /**
187
+     * @return array
188
+     * @throws DataNotFoundException
189
+     * @throws DbException
190
+     * @throws ModelNotFoundException
191
+     * 获取全部0号线用户
192
+     */
193
+    private function getAllZeroUserIds()
194
+    {
195
+        $zeroUserDao = new ZeroUserLineDao();
196
+        $zeroUserIds = $zeroUserDao->selectWhere([
197
+            'is_remove' => ZeroUserEnum::IS_REMOVE_MAP['YES']['code'],
198
+            'status' => ZeroUserEnum::STATUS_MAP['NORMAL']['code']
199
+        ])->column('team_uid');
200
+
201
+        if (empty($zeroUserIds)) {
202
+            return [];
203
+        }
204
+
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
+        return $zeroUserIds;
215
+    }
216
+
217
+    /**
218
+     * @param $userIds
219
+     * @return int
220
+     * 获取分红总额
221
+     */
222
+    private function getAmount($userIds): int
223
+    {
224
+        return 10000;
225
+    }
226
+
227
+    /**
228
+     * @param $uids
229
+     * @param $userGroup
230
+     * @return array
231
+     * @throws \think\db\exception\DataNotFoundException
232
+     * @throws \think\db\exception\DbException
233
+     * @throws \think\db\exception\ModelNotFoundException
234
+     * 获取符合合伙人等级的用户
235
+     */
236
+    private function getUserByGroup($uid, $userGroup): array
237
+    {
238
+        $userDao = new UserDao();
239
+        return $userDao->getUsersByGroup($uid, $userGroup);
240
+    }
241
+
242
+    /**
243
+     * @param $data
244
+     * @return array
245
+     * @throws DataNotFoundException
246
+     * @throws DbException
247
+     * @throws ModelNotFoundException
248
+     * 获取直推用户业绩
249
+     */
250
+    private function getDirect($data, $amount): array
251
+    {
252
+
253
+
254
+        $money = $amount / 2;
255
+        $res = [];
256
+        foreach ($data as $key => $value) {
257
+            if (empty($value)) {
258
+                unset($data[$key]);
259
+                continue;
260
+            }
261
+
262
+            // 获取直推
263
+            $directUids = $this->userDao->selectWhere(['level_id' => $value])->column('uid');
264
+
265
+
266
+            // 获取直推用户pv
267
+            $pvArr = $this->getPv($directUids);
268
+            $all = 0;
269
+            $all += array_sum($pvArr);
270
+            $max = max($pvArr);
271
+            $min = $all - $max;
272
+            $res[$value]['all'] = $all;
273
+            $res[$value]['min'] = $min;
274
+            $res[$value]['max'] = $max;
275
+        }
276
+        $res = $this->countPrice($res, $money);
277
+        return $res;
278
+    }
279
+
280
+    /**
281
+     * @param $directUids
282
+     * @param $pvConfig
283
+     * @return array
284
+     * 计算pv
285
+     */
286
+    private function getPv($directUids)
287
+    {
288
+        $res = [];
289
+        foreach ($directUids as $uid) {
290
+            // 获取直推用户的团队uid
291
+            $teamUids = $this->getTeamUids($uid);
292
+            // 去除自己
293
+            $teamUids = array_slice($teamUids, 1);
294
+            // 获取团队的pv
295
+            $pvArr = $this->userDao->selectWhereIn('uid', $teamUids)->column('pv', 'uid');
296
+            if (($pvArr)) {
297
+                $all = array_sum($pvArr);
298
+                $res[$uid] = $all;
299
+            }
300
+        }
301
+        return $res;
302
+    }
303
+
304
+    /**
305
+     * @param $uid
306
+     * @return mixed
307
+     * 获取团队所有人uids  [1,2,3]
308
+     */
309
+    private function getTeamUids($uid): array
310
+    {
311
+        $sql = "select getUserLevelId($uid) as uids";
312
+        return explode(',', Db::query($sql)[0]['uids']);
313
+    }
314
+
315
+    /**
316
+     * @param $pvArr
317
+     * @param $money
318
+     * @return mixed
319
+     * 计算分佣
320
+     */
321
+    private function countPrice($pvArr, $money)
322
+    {
323
+        $minAll = array_sum(array_column($pvArr, 'min'));
324
+        $maxAll = array_sum(array_column($pvArr, 'all'));
325
+        foreach ($pvArr as $k => $v) {
326
+            $pvArr[$k]['minCount'] = floor($money * ($v['min'] / $minAll) * 100) / 100;
327
+            $pvArr[$k]['maxCount'] = floor($money * ($v['all'] / $maxAll) * 100) / 100;
328
+        }
329
+
330
+        return $pvArr;
331
+    }
332
+
333
+    /**
334
+     * @param $param
335
+     * @param $amount
336
+     * @param $detail
337
+     * @return string|true
338
+     * 记录日志
339
+     */
340
+    private function recordLog($param, $amount, $detail)
341
+    {
342
+        $logDao = new ZeroUserProfitDao();
343
+        $detailDao = new ZeroUserProfitsDetail();
344
+        // 记录分红明细日志,修改主表数据
345
+        $updateData = [
346
+            'amount' => $amount,
347
+            'status' => 1
348
+        ];
349
+
350
+        foreach ($detail as $key => $value) {
351
+            $insertData = [
352
+                'zero_user_profit_id' => $param['logId'],
353
+                'zero_user_id' => $param['zeroUserId'],
354
+                'user_id' => $key,
355
+                'amount' => $amount,
356
+                'all' => $value['all'],
357
+                'min' => $value['min'],
358
+                'max' => $value['max'],
359
+                'min_count' => $value['minCount'],
360
+                'max_count' => $value['maxCount'],
361
+            ];
362
+            try {
363
+                Db::startTrans();
364
+                $logDao->update($param['logId'], $updateData);
365
+                $detailDao->create($insertData);
366
+                Db::commit();
367
+            } catch (\Exception $e) {
368
+                Db::rollback();
369
+                return $e->getMessage();
370
+            }
371
+        }
372
+        return true;
373
+    }
374
+}

+ 202 - 0
app/common/utils/QueueLogger.php

@@ -0,0 +1,202 @@
1
+<?php
2
+
3
+namespace app\common\utils;
4
+
5
+use think\facade\Log;
6
+use think\console\Output;
7
+
8
+/**
9
+ * 队列日志工具类
10
+ * 提供统一的队列任务日志记录功能
11
+ */
12
+class QueueLogger
13
+{
14
+    /**
15
+     * 记录队列任务开始日志
16
+     *
17
+     * @param string $jobName 任务名称
18
+     * @param mixed $job 任务对象
19
+     * @param array $data 任务数据
20
+     */
21
+    public static function start($jobName, $job, $data)
22
+    {
23
+        $logData = [
24
+            'job_id' => method_exists($job, 'getJobId') ? $job->getJobId() : 'unknown',
25
+            'data' => $data,
26
+            'attempts' => method_exists($job, 'attempts') ? $job->attempts() : 0,
27
+            'start_time' => date('Y-m-d H:i:s')
28
+        ];
29
+        
30
+        Log::channel('queue')->info("{$jobName}任务开始执行", $logData);
31
+        self::outputToCli("{$jobName}任务开始执行", $logData);
32
+    }
33
+
34
+    /**
35
+     * 记录队列任务成功日志
36
+     *
37
+     * @param string $jobName 任务名称
38
+     * @param mixed $job 任务对象
39
+     * @param array $data 任务数据
40
+     * @param mixed $result 执行结果
41
+     */
42
+    public static function success($jobName, $job, $data, $result = null)
43
+    {
44
+        $logData = [
45
+            'job_id' => method_exists($job, 'getJobId') ? $job->getJobId() : 'unknown',
46
+            'data' => $data,
47
+            'result' => $result,
48
+            'end_time' => date('Y-m-d H:i:s')
49
+        ];
50
+        
51
+        Log::channel('queue')->info("{$jobName}任务执行成功", $logData);
52
+        self::outputToCli("{$jobName}任务执行成功", $logData);
53
+    }
54
+
55
+    /**
56
+     * 记录队列任务警告日志
57
+     *
58
+     * @param string $jobName 任务名称
59
+     * @param mixed $job 任务对象
60
+     * @param array $data 任务数据
61
+     * @param string $message 警告信息
62
+     */
63
+    public static function warning($jobName, $job, $data, $message)
64
+    {
65
+        $logData = [
66
+            'job_id' => method_exists($job, 'getJobId') ? $job->getJobId() : 'unknown',
67
+            'data' => $data,
68
+            'attempts' => method_exists($job, 'attempts') ? $job->attempts() : 0
69
+        ];
70
+        
71
+        Log::channel('queue')->warning("{$jobName}任务警告: {$message}", $logData);
72
+        self::outputToCli("{$jobName}任务警告: {$message}", $logData, 'warning');
73
+    }
74
+
75
+    /**
76
+     * 记录队列任务异常日志
77
+     *
78
+     * @param string $jobName 任务名称
79
+     * @param mixed $job 任务对象
80
+     * @param array $data 任务数据
81
+     * @param \Exception $e 异常对象
82
+     */
83
+    public static function error($jobName, $job, $data, \Exception $e)
84
+    {
85
+        $logData = [
86
+            'data' => $data,
87
+            'error_message' => $e->getMessage(),
88
+            'error_file' => $e->getFile(),
89
+            'error_line' => $e->getLine(),
90
+            'error_trace' => $e->getTraceAsString()
91
+        ];
92
+        
93
+        // 安全地获取job信息,避免方法不存在导致的错误
94
+        if (is_object($job)) {
95
+            if (method_exists($job, 'getJobId')) {
96
+                $logData['job_id'] = $job->getJobId();
97
+            }
98
+            if (method_exists($job, 'attempts')) {
99
+                $logData['attempts'] = $job->attempts();
100
+            }
101
+        }
102
+        
103
+        Log::channel('queue')->error("{$jobName}任务执行异常", $logData);
104
+        self::outputToCli("{$jobName}任务执行异常: " . $e->getMessage(), $logData, 'error');
105
+    }
106
+
107
+    /**
108
+     * 记录队列任务失败日志
109
+     *
110
+     * @param string $jobName 任务名称
111
+     * @param array $data 任务数据
112
+     * @param string $message 失败信息
113
+     */
114
+    public static function failed($jobName, $data, $message = '任务进入失败状态,需要手动处理')
115
+    {
116
+        $logData = [
117
+            'data' => $data,
118
+            'failed_time' => date('Y-m-d H:i:s'),
119
+            'message' => $message
120
+        ];
121
+        
122
+        Log::channel('queue')->error("{$jobName}任务失败", $logData);
123
+        self::outputToCli("{$jobName}任务失败: {$message}", $logData, 'error');
124
+    }
125
+
126
+    /**
127
+     * 记录队列任务结束日志
128
+     *
129
+     * @param string $jobName 任务名称
130
+     * @param mixed $job 任务对象
131
+     * @param array $data 任务数据
132
+     */
133
+    public static function end($jobName, $job, $data)
134
+    {
135
+        $logData = [
136
+            'job_id' => method_exists($job, 'getJobId') ? $job->getJobId() : 'unknown',
137
+            'data' => $data,
138
+            'end_time' => date('Y-m-d H:i:s')
139
+        ];
140
+        
141
+        Log::channel('queue')->info("{$jobName}任务执行结束", $logData);
142
+        self::outputToCli("{$jobName}任务执行结束", $logData);
143
+    }
144
+
145
+    /**
146
+     * 记录业务执行开始日志
147
+     *
148
+     * @param string $businessName 业务名称
149
+     * @param array $data 业务数据
150
+     */
151
+    public static function businessStart($businessName, $data)
152
+    {
153
+        $logData = [
154
+            'data' => $data,
155
+            'start_time' => date('Y-m-d H:i:s')
156
+        ];
157
+        
158
+        Log::channel('queue')->info("开始执行{$businessName}业务", $logData);
159
+        self::outputToCli("开始执行{$businessName}业务", $logData);
160
+    }
161
+
162
+    /**
163
+     * 记录队列任务信息日志
164
+     *
165
+     * @param string $message 日志信息
166
+     * @param array $context 上下文数据
167
+     */
168
+    public static function info($message, array $context = [])
169
+    {
170
+        Log::channel('queue')->info($message, $context);
171
+        self::outputToCli($message, $context);
172
+    }
173
+
174
+    /**
175
+     * 输出到CLI命令行
176
+     *
177
+     * @param string $message 消息内容
178
+     * @param array $context 上下文数据
179
+     * @param string $level 日志级别
180
+     */
181
+    private static function outputToCli($message, $context = [], $level = 'info')
182
+    {
183
+        if (PHP_SAPI === 'cli') {
184
+            $output = new Output();
185
+            $timestamp = date('Y-m-d H:i:s');
186
+            
187
+            switch ($level) {
188
+                case 'error':
189
+                    $prefix = 'ERROR';
190
+                    $output->error("[{$timestamp}] [{$prefix}] {$message}");
191
+                    break;
192
+                case 'warning':
193
+                    $prefix = 'WARN';
194
+                    $output->warning("[{$timestamp}] [{$prefix}] {$message}");
195
+                    break;
196
+                default:
197
+                    $prefix = 'INFO';
198
+                    $output->info("[{$timestamp}] [{$prefix}] {$message}");
199
+            }
200
+        }
201
+    }
202
+}

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

@@ -0,0 +1,45 @@
1
+<?php
2
+
3
+namespace app\controller\admin\profits;
4
+
5
+use app\common\repositories\profits\ProfitsRepository;
6
+use app\controller\admin\BaseController;
7
+use app\entity\admin\request\profits\PartnerProfitsRequestEntity;
8
+use app\entity\admin\response\profits\ZeroUserProfitResponseEntity;
9
+use app\validate\admin\profits\PartnerProfitsValidate;
10
+
11
+class ShareProfits extends BaseController
12
+{
13
+    /**
14
+     * @var
15
+     */
16
+    private $repository;
17
+
18
+    public function __construct(ProfitsRepository $repository)
19
+    {
20
+        parent::__construct();
21
+        $this->repository = $repository;
22
+    }
23
+
24
+
25
+    /**
26
+     * @param  PartnerProfitsValidate  $validate
27
+     * @return mixed
28
+     * @throws \think\db\exception\DataNotFoundException
29
+     * @throws \think\db\exception\DbException
30
+     * @throws \think\db\exception\ModelNotFoundException
31
+     * 合伙人分红接口
32
+     */
33
+    public function partnerProfits(PartnerProfitsValidate $validate)
34
+    {
35
+        $validationResult = $this->validateRequest($validate, PartnerProfitsRequestEntity::class);
36
+        $res = $this->repository->getZeroUser($validationResult->toArray());
37
+        // $res = $this->repository->profits($validationResult->toArray());
38
+        if (is_string($res)) {
39
+            $response = ZeroUserProfitResponseEntity::error($res);
40
+            return app('json')->fail($response->toResponseArray());
41
+        }
42
+        $response = ZeroUserProfitResponseEntity::success();
43
+        return app('json')->success($response->toResponseArray());
44
+    }
45
+}

+ 118 - 0
app/entity/admin/request/profits/PartnerProfitsRequestEntity.php

@@ -0,0 +1,118 @@
1
+<?php
2
+
3
+namespace app\entity\admin\request\profits;
4
+
5
+use app\entity\request\RequestCommonEntity;
6
+
7
+/**
8
+ * 合伙人收益请求参数
9
+ */
10
+class PartnerProfitsRequestEntity extends RequestCommonEntity
11
+{
12
+
13
+    // 开始时间
14
+    public $startTime;
15
+
16
+    // 结束时间
17
+    public $endTime;
18
+
19
+    // 是否发放分红
20
+    public $isTest;
21
+
22
+    // 金额
23
+    public $amount;
24
+
25
+    public $isZero;
26
+
27
+    // 0号线用户ID
28
+    public $zeroUserId;
29
+
30
+    public $type;
31
+
32
+    // 获取开始时间
33
+    public function getStartTime()
34
+    {
35
+        return $this->startTime;
36
+    }
37
+
38
+    // 设置开始时间
39
+    public function setStartTime($startTime): PartnerProfitsRequestEntity
40
+    {
41
+        $this->startTime = $startTime;
42
+        return $this;
43
+    }
44
+
45
+    // 获取结束时间
46
+    public function getEndTime()
47
+    {
48
+        return $this->endTime;
49
+    }
50
+
51
+    // 设置结束时间
52
+    public function setEndTime($endTime): PartnerProfitsRequestEntity
53
+    {
54
+        $this->endTime = $endTime;
55
+        return $this;
56
+    }
57
+
58
+    // 获取是否测试
59
+    public function getIsTest()
60
+    {
61
+        return $this->isTest;
62
+    }
63
+
64
+    // 设置是否测试
65
+    public function setIsTest($isTest): PartnerProfitsRequestEntity
66
+    {
67
+        $this->isTest = $isTest;
68
+        return $this;
69
+    }
70
+
71
+    // 获取金额是否为零
72
+    public function getAmount()
73
+    {
74
+        return $this->amount;
75
+    }
76
+
77
+    // 设置金额是否为零
78
+    public function setAmount($amount): PartnerProfitsRequestEntity
79
+    {
80
+        $this->amount = $amount;
81
+        return $this;
82
+    }
83
+
84
+    public function getIsZero()
85
+    {
86
+        return $this->isZero;
87
+    }
88
+
89
+    public function setIsZero($isZero): PartnerProfitsRequestEntity
90
+    {
91
+        $this->isZero = $isZero;
92
+        return $this;
93
+    }
94
+
95
+    // 获取零用户ID
96
+    public function getZeroUserId()
97
+    {
98
+        return $this->zeroUserId;
99
+    }
100
+
101
+    // 设置零用户ID
102
+    public function setZeroUserId($zeroUserId): PartnerProfitsRequestEntity
103
+    {
104
+        $this->zeroUserId = $zeroUserId;
105
+        return $this;
106
+    }
107
+
108
+    public function getType()
109
+    {
110
+        return $this->type;
111
+    }
112
+
113
+    public function setType($type): PartnerProfitsRequestEntity
114
+    {
115
+        $this->type = $type;
116
+        return $this;
117
+    }
118
+}

+ 32 - 0
app/entity/admin/response/profits/ZeroUserProfitResponseEntity.php

@@ -0,0 +1,32 @@
1
+<?php
2
+
3
+namespace app\entity\admin\response\profits;
4
+
5
+use app\entity\admin\response\AdminCommonResponseEntity;
6
+
7
+class ZeroUserProfitResponseEntity extends AdminCommonResponseEntity
8
+{
9
+    /**
10
+     * 创建零号线用户操作成功响应
11
+     * @param  null  $data
12
+     * @param  string  $message
13
+     * @param  int  $code
14
+     * @return AdminCommonResponseEntity
15
+     */
16
+    public static function success(string $message = '操作成功', int $code = 200, $data = null): AdminCommonResponseEntity
17
+    {
18
+        return parent::success($message);
19
+    }
20
+
21
+    /**
22
+     * 创建零号线用户操作失败响应
23
+     * @param  string  $message
24
+     * @param  int  $code
25
+     * @param  null  $data
26
+     * @return AdminCommonResponseEntity
27
+     */
28
+    public static function error(string $message = '操作失败', int $code = 400, $data = null): AdminCommonResponseEntity
29
+    {
30
+        return parent::error($message, $code);
31
+    }
32
+}

+ 82 - 0
app/jobs/ZeroUserProfits.php

@@ -0,0 +1,82 @@
1
+<?php
2
+
3
+namespace app\jobs;
4
+
5
+
6
+
7
+use app\common\model\profit\ZeroUserProfits as ZeroUserProfitsModel;
8
+use app\common\repositories\profits\ProfitsRepository;
9
+use app\common\utils\QueueLogger;
10
+use crmeb\interfaces\JobInterface;
11
+
12
+class ZeroUserProfits implements JobInterface
13
+{
14
+    public function fire($job, $data)
15
+    {
16
+        // 记录任务开始日志
17
+        QueueLogger::start('ZeroUserProfits', $job, $data);
18
+
19
+        // 数据仓库
20
+        $repository = new ProfitsRepository();
21
+        try {
22
+            // 记录业务执行开始
23
+            QueueLogger::businessStart('用户收益计算', $data);
24
+            
25
+            // 执行业务
26
+            $res = $repository->profits($data);
27
+            
28
+            if ($res) {
29
+                // 记录成功日志
30
+                QueueLogger::success('ZeroUserProfits', $job, $data, $res);
31
+                $job->delete();
32
+            } else {
33
+                // 记录业务执行失败但未异常的情况
34
+                QueueLogger::warning('ZeroUserProfits', $job, $data, '业务执行返回false');
35
+                
36
+                if ($job->attempts() > 3) {
37
+                    // 超过重试次数,调用failed方法记录异常状态
38
+                    $this->failed($data);
39
+                    $job->delete();
40
+                }
41
+            }
42
+        } catch (\Exception $e) {
43
+            // 记录异常日志
44
+            QueueLogger::error('ZeroUserProfits', $job, $data, $e);
45
+            
46
+            if ($job->attempts() > 3) {
47
+                // 超过重试次数,调用failed方法记录异常状态
48
+                $this->failed($data);
49
+                $job->delete();
50
+            }
51
+        }
52
+        
53
+        // 记录任务结束日志
54
+        QueueLogger::end('ZeroUserProfits', $job, $data);
55
+    }
56
+
57
+    public function failed($data)
58
+    {
59
+        try {
60
+            // 记录任务失败日志
61
+            QueueLogger::failed('ZeroUserProfits', $data);
62
+            
63
+            // 在数据库中标记数据异常状态
64
+            if (isset($data['logId']) && !empty($data['logId'])) {
65
+                // 更新ZeroUserProfits表的status字段为异常状态
66
+                ZeroUserProfitsModel::where('id', $data['logId'])
67
+                    ->update([
68
+                        'status' => 3, // 假设2表示异常状态
69
+                        'update_time' => time()
70
+                    ]);
71
+                
72
+                QueueLogger::info('ZeroUserProfits任务失败状态已更新', [
73
+                    'data_id' => $data['logId'],
74
+                    'status' => 2
75
+                ]);
76
+            }
77
+        } catch (\Exception $e) {
78
+            // 记录数据库更新异常
79
+            QueueLogger::error('ZeroUserProfits任务失败状态更新异常', null, $data, $e);
80
+        }
81
+    }
82
+}

+ 27 - 0
app/validate/admin/profits/PartnerProfitsValidate.php

@@ -0,0 +1,27 @@
1
+<?php
2
+
3
+namespace app\validate\admin\profits;
4
+
5
+use app\validate\CommonValidate;
6
+
7
+class PartnerProfitsValidate extends CommonValidate
8
+{
9
+    protected $failException = true;
10
+
11
+    protected $rule = [
12
+        'start_time|开始时间' => 'date',
13
+        'end_time|结束时间' => 'date',
14
+        'is_test|是否发放' => 'in:0,1',
15
+        'amount|分红总金额' => 'number',
16
+        'is_zero|是否0号线分红' => 'in:0,1',
17
+    ];
18
+
19
+    protected $message = [
20
+        'start_time.date' => '请选择开始时间',
21
+        'end_time.date' => '请选择结束时间',
22
+        'is_test.required' => '请选择是否发放' ,
23
+        'amount.number' => '分红总金额必须为数字',
24
+        'is_zero.required' => '请选择是否0号线分红',
25
+        'is_zero.integer' => '非法0号线分红'
26
+    ];
27
+}

+ 11 - 0
config/log.php

@@ -47,6 +47,17 @@ return [
47 47
             'path' => app()->getRootPath().'runtime/gong',
48 48
             'time_format' => 'Y-m-d H:i:s',
49 49
             'format' => '[%s][%s]:%s'
50
+        ],
51
+        // 队列任务专用日志通道
52
+        'queue' => [
53
+            'type' => 'File',
54
+            'path' => app()->getRootPath().'runtime/queue',
55
+            'time_format' => 'Y-m-d H:i:s',
56
+            'format' => '[%s][%s] %s',
57
+            'single' => false,
58
+            'max_files' => 30,
59
+            'file_size' => 1024 * 1024 * 1024,
60
+            'realtime_write' => true,
50 61
         ]
51 62
     ],
52 63
 

+ 14 - 3
crmeb/services/SwooleTaskService.php

@@ -49,7 +49,12 @@ class SwooleTaskService
49 49
     public function __construct(string $type)
50 50
     {
51 51
         $this->type = $type;
52
-        $this->server = app('swoole.server');
52
+        // 只有在Swoole扩展已加载且swoole.server服务存在时才获取server实例
53
+        if (extension_loaded('swoole') && app()->has('swoole.server')) {
54
+            $this->server = app('swoole.server');
55
+        } else {
56
+            $this->server = null;
57
+        }
53 58
     }
54 59
 
55 60
     /**
@@ -74,8 +79,14 @@ class SwooleTaskService
74 79
     public function push(int $workId = -1)
75 80
     {
76 81
         try {
77
-
78
-            return $this->server->task(['type' => $this->type, 'data' => $this->data], $workId);
82
+            // 只有在有Swoole服务器实例时才发送任务
83
+            if ($this->server instanceof \Swoole\Server) {
84
+                return $this->server->task(['type' => $this->type, 'data' => $this->data], $workId);
85
+            } else {
86
+                // 非Swoole环境下记录日志但不抛出异常
87
+                Log::info('Swoole服务器不可用,任务未发送: ' . $this->type);
88
+                return false;
89
+            }
79 90
         } catch (\Exception $e) {
80 91
             Log::info('发送 Task 失败' . $e->getMessage());
81 92
         }