From c8db3e170dfb0f19d128a0513debbacd2ea70d51 Mon Sep 17 00:00:00 2001 From: ctexthuang Date: Wed, 26 Feb 2025 18:01:02 +0800 Subject: [PATCH] feat : auto coupon --- .../Consumer/CouponAutoDispenseConsumer.php | 60 +++++ .../Producer/CouponAutoDispenseProducer.php | 26 +++ app/Constants/Common/CouponCode.php | 9 +- app/Model/CouponDispenseLog.php | 35 ++- app/Model/CouponDispenseUser.php | 71 ++++++ app/Model/UserCoupon.php | 42 ++++ .../Admin/Coupon/DispenseAddService.php | 207 +++++++++++++++++- .../Admin/Coupon/DispenseConfirmService.php | 73 +----- .../Amqp/Coupon/AutoDispenseService.php | 94 ++++++++ .../Admin/CouponDispenseTrait.php | 74 ++++++- 10 files changed, 605 insertions(+), 86 deletions(-) create mode 100644 app/Amqp/Consumer/CouponAutoDispenseConsumer.php create mode 100644 app/Amqp/Producer/CouponAutoDispenseProducer.php create mode 100644 app/Model/CouponDispenseUser.php create mode 100644 app/Model/UserCoupon.php create mode 100644 app/Service/Amqp/Coupon/AutoDispenseService.php diff --git a/app/Amqp/Consumer/CouponAutoDispenseConsumer.php b/app/Amqp/Consumer/CouponAutoDispenseConsumer.php new file mode 100644 index 0000000..badffba --- /dev/null +++ b/app/Amqp/Consumer/CouponAutoDispenseConsumer.php @@ -0,0 +1,60 @@ +log->error('CouponAutoDispenseConsumer:error:NoData:'.json_encode($data)); + return Result::ACK; + } + + try { + $service = new AutoDispenseService(); + + $service->couponDispenseId = $data['coupon_dispense_id']; + + $service->handle(); + + } catch (Exception $e) { + $this->log->error('CouponAutoDispenseConsumer:error:'.$e->getMessage().':data:'.json_encode($data)); + } + + return Result::ACK; + } +} diff --git a/app/Amqp/Producer/CouponAutoDispenseProducer.php b/app/Amqp/Producer/CouponAutoDispenseProducer.php new file mode 100644 index 0000000..bd29b47 --- /dev/null +++ b/app/Amqp/Producer/CouponAutoDispenseProducer.php @@ -0,0 +1,26 @@ + {"coupon_dispense_id":"dispenseId"} + */ + $this->payload = $data; + } +} diff --git a/app/Constants/Common/CouponCode.php b/app/Constants/Common/CouponCode.php index a01b93e..8ba22bf 100644 --- a/app/Constants/Common/CouponCode.php +++ b/app/Constants/Common/CouponCode.php @@ -32,9 +32,16 @@ class CouponCode CONST INT DISPENSE_APPOINT_GROUP_DESIGNATED_SITES_AND_GOODS = 5; /** - * @var int 优惠券发放规则 1 首页弹窗 2 下单账户 3 单页 + * @var int 优惠券发放规则 1 首页弹窗 2 直发账户 3 单页 */ CONST INT DISPENSE_CLAIM_RULE_HOME_POPUPS = 1; CONST INT DISPENSE_CLAIM_RULE_POST_ACCOUNT = 2; CONST INT DISPENSE_CLAIM_RULE_SINGLE_PAGE = 3; + + /** + * @var int 优惠券状态 1 未使用 2 已使用 3 未过期 + */ + CONST INT COUPON_STATUS_UNUSED = 1; + CONST INT COUPON_STATUS_USED = 2; + CONST INT COUPON_STATUS_NOT_EXPIRE = 3; } \ No newline at end of file diff --git a/app/Model/CouponDispenseLog.php b/app/Model/CouponDispenseLog.php index 215fe11..a2e9faa 100644 --- a/app/Model/CouponDispenseLog.php +++ b/app/Model/CouponDispenseLog.php @@ -11,16 +11,14 @@ use Hyperf\DbConnection\Model\Model; * @property string $title * @property string $coupon_name * @property int $coupon_template_id - * @property int $send_count - * @property int $receive_count - * @property int $left_count + * @property int $total_count + * @property int $receive_count * @property int $item_count * @property int $appoint_group * @property int $claim_rule - * @property string $claim_value - * @property int $appoint_city_id + * @property string $claim_value * @property string $appoint_value - * @property string $use_scen_ids + * @property string $use_scene_ids * @property int $validity_time_type * @property string $validity_time_value * @property string $remark @@ -38,9 +36,34 @@ class CouponDispenseLog extends Model * The attributes that are mass assignable. */ protected array $fillable = []; + protected array $guarded = []; /** * The attributes that should be cast to native types. */ protected array $casts = ['id' => 'integer', 'coupon_template_id' => 'integer', 'send_count' => 'integer', 'receive_count' => 'integer', 'left_count' => 'integer', 'item_count' => 'integer', 'appoint_group' => 'integer', 'claim_rule' => 'integer', 'appoint_city_id' => 'integer', 'validity_time_type' => 'integer']; + + const string CREATED_AT = 'create_time'; + const string UPDATED_AT = 'update_time'; + + /** + * @param int $id + * @return CouponDispenseLog|CouponDispenseLog[]|\Hyperf\Database\Model\Model|null + */ + public function getInfoById(int $id): \Hyperf\Database\Model\Model|CouponDispenseLog|array|null + { + return $this->find($id); + } + + /** + * @param int $id + * @param int $receiveCount + * @return int + */ + public function updateReceiveCountById(int $id,int $receiveCount): int + { + return $this->where('id', $id)->increment('receive_count' , $receiveCount); + } } + + diff --git a/app/Model/CouponDispenseUser.php b/app/Model/CouponDispenseUser.php new file mode 100644 index 0000000..75df3b1 --- /dev/null +++ b/app/Model/CouponDispenseUser.php @@ -0,0 +1,71 @@ + 'integer', 'coupon_dispense_id' => 'integer', 'user_id' => 'integer', 'total_count' => 'integer', 'receive_count' => 'integer']; + + const string CREATED_AT = 'create_time'; + const string UPDATED_AT = 'update_time'; + + /** + * @param int $dispenseId + * @return int + */ + public function getNoSendCountByDispenseId(int $dispenseId): int + { + return $this + ->where('dispense_id', $dispenseId) + ->where('receive_count',0) + ->count() ?? 0; + } + + public function getNoSendUserListByDispenseId(int $dispenseId): array + { + return $this + ->where('dispense_id', $dispenseId) + ->where('receive_count',0) + ->pluck('user_id') + ->toArray(); + } + + /** + * @param array $userIds + * @param int $count + * @return int + */ + public function updateReceiveCountByUserIds(array $userIds,int $count): int + { + return $this + ->whereIn('user_id', $userIds) + ->increment('receive_count', $count); + } +} diff --git a/app/Model/UserCoupon.php b/app/Model/UserCoupon.php new file mode 100644 index 0000000..ad598a2 --- /dev/null +++ b/app/Model/UserCoupon.php @@ -0,0 +1,42 @@ + 'integer', 'coupon_template_id' => 'integer', 'coupon_dispense_id' => 'integer', 'user_id' => 'integer', 'status' => 'integer']; + + const string CREATED_AT = 'created_time'; + const string UPDATED_AT = 'updated_time'; +} diff --git a/app/Service/Admin/Coupon/DispenseAddService.php b/app/Service/Admin/Coupon/DispenseAddService.php index 7009038..707d7bc 100644 --- a/app/Service/Admin/Coupon/DispenseAddService.php +++ b/app/Service/Admin/Coupon/DispenseAddService.php @@ -10,12 +10,215 @@ declare(strict_types=1); namespace App\Service\Admin\Coupon; +use App\Constants\Common\CouponCode; +use App\Exception\ErrException; +use App\Model\CouponDispenseLog; +use App\Model\CouponDispenseUser; +use App\Model\CouponTemplate; +use App\Model\Cycle; +use App\Model\Order; +use App\Model\OrderGood; +use App\Model\Site; +use App\Model\Sku; use App\Service\Admin\BaseService; +use App\Service\ServiceTrait\Admin\CouponDispenseTrait; +use Exception; +use Hyperf\DbConnection\Db; +use Hyperf\Di\Annotation\Inject; class DispenseAddService extends BaseService { - public function handle() + use CouponDispenseTrait; + + /** + * @var Order + */ + #[Inject] + protected Order $orderModel; + + /** + * @var Sku + */ + #[Inject] + protected Sku $skuModel; + + /** + * @var OrderGood + */ + #[Inject] + protected OrderGood $orderGoodModel; + + /** + * @var Site + */ + #[Inject] + protected Site $siteModel; + + /** + * @var Cycle + */ + #[Inject] + protected Cycle $cycleModel; + + /** + * @var CouponTemplate + */ + #[Inject] + protected CouponTemplate $couponTemplateModel; + + /** + * @var int + */ + private int $cycleId; + + /** + * @var array + */ + private array $appointValue; + + /** + * @var int + */ + private int $groupType; + + /** + * @var array + */ + private array $userIds; + + /** + * @var mixed + */ + private mixed $couponTemplateInfo; + + /** + * @var int + */ + private int $claimRule; + + /** + * @return array + */ + public function handle(): array { - //todo Write logic + try { + $this->userIds = $this->beneficiary(); + + $this->checkCouponTemplate(); + + $this->checkClaimRuleData(); + + Db::transaction(function () { + $dispenseId = $this->addMasterTableInformation(); + + $this->addSubTableInformation($dispenseId); + }); + + if ($this->claimRule == CouponCode::DISPENSE_CLAIM_RULE_POST_ACCOUNT) { + //todo mq发放优惠券 + echo 1; + } + + return $this->return->success(); + }catch (Exception $e) { + throw new ErrException($e->getMessage()); + } } + + /** + * @param int $dispenseId + * @return void + * @throws Exception + */ + private function addSubTableInformation(int $dispenseId): void + { + if ($this->groupType == CouponCode::DISPENSE_APPOINT_GROUP_ALL_PEOPLE) return; + + if ($this->claimRule == CouponCode::DISPENSE_CLAIM_RULE_SINGLE_PAGE) return; + + if (empty($this->userIds)) return; + + $insertData = []; + foreach ($this->userIds as $userId) { + $insertData[] = [ + 'coupon_dispense_id' => $dispenseId, + 'user_id' => $userId, + 'total_count' => $this->request->input('item_count'), + 'create_time' => date('Y-m-d H:i:s'), + ]; + } + + if (empty($insertData)) throw new Exception('添加失败'); + + if (!((new CouponDispenseUser)->insert($insertData))) throw new Exception('添加失败'); + } + + /** + * @return int + * @throws Exception + */ + private function addMasterTableInformation(): int + { + $insertModel = new CouponDispenseLog(); + + $insertModel->title = $this->request->input('title',date('Ymd').'分发优惠券'); + $insertModel->coupon_name = $this->request->input('coupon_name', $this->couponTemplateInfo->name); + $insertModel->coupon_template_id = $this->couponTemplateInfo->id; + $insertModel->total_count = 0; + $insertModel->receive_count = 0; + $insertModel->item_count = 0; + $insertModel->appoint_group = $this->groupType; + $insertModel->claim_rule = $this->claimRule; + $insertModel->claim_value = $this->request->input('claim_value'); + $insertModel->appoint_value = json_encode($this->appointValue); + $insertModel->validity_time_type = $this->request->input('validity_time_type'); + $insertModel->validity_time_value = json_encode($this->request->input('validity_time_value')); + $insertModel->remark = $this->request->input('remark'); + + if (!$insertModel->save()) throw new Exception('添加分发记录失败'); + + return $insertModel->id; + } + + /** + * @return void + * @throws Exception + */ + private function checkClaimRuleData(): void + { + $this->claimRule = (int)$this->request->input('claim_rule',CouponCode::DISPENSE_CLAIM_RULE_HOME_POPUPS); + if ($this->claimRule == CouponCode::DISPENSE_CLAIM_RULE_SINGLE_PAGE && empty($this->request->input('claim_value'))) { + throw new Exception('单页请输入路径'); + } + } + + /** + * @return void + * @throws Exception + */ + private function checkCouponTemplate(): void + { + $this->couponTemplateInfo = $this->couponTemplateModel->getInfoById((int)$this->request->input('coupon_template_id')); + + if (empty($this->couponTemplateInfo)) throw new Exception('未找到该优惠券模板'); + + if ($this->couponTemplateInfo->status == CouponCode::COUPON_TEMPLATE_STATUS_ENABLE) throw new Exception('该优惠券模板已被禁用'); + } + + /** + * @return array + * @throws Exception + */ + private function beneficiary(): array + { + return match ($this->groupType = (int)$this->request->input('appoint_group',CouponCode::DISPENSE_APPOINT_GROUP_ALL_PEOPLE)) + { + CouponCode::DISPENSE_APPOINT_GROUP_DESIGNATED_USERS => $this->handleDesignatedUsers(), + CouponCode::DISPENSE_APPOINT_GROUP_DESIGNATED_SITES => $this->handleDesignatedSites(), + CouponCode::DISPENSE_APPOINT_GROUP_DESIGNATED_GOODS => $this->handleDesignatedGoods(), + CouponCode::DISPENSE_APPOINT_GROUP_DESIGNATED_SITES_AND_GOODS => $this->handleDesignatedSitesAndGoods(), + default => [] + }; + } + } \ No newline at end of file diff --git a/app/Service/Admin/Coupon/DispenseConfirmService.php b/app/Service/Admin/Coupon/DispenseConfirmService.php index 821f818..2cd33f0 100644 --- a/app/Service/Admin/Coupon/DispenseConfirmService.php +++ b/app/Service/Admin/Coupon/DispenseConfirmService.php @@ -52,29 +52,12 @@ class DispenseConfirmService extends BaseService #[Inject] protected Site $siteModel; - /** - * @var User - */ - #[Inject] - protected User $userModel; - - /** - * @var CouponDispenseLog - */ - #[Inject] - protected CouponDispenseLog $couponDispenseLogModel; - /** * @var Cycle */ #[Inject] protected Cycle $cycleModel; - /** - * @var array - */ - private array $res; - /** * @var int */ @@ -97,11 +80,7 @@ class DispenseConfirmService extends BaseService public function handle(): array { try { - $this->res = []; - - if (empty($this->request->input('appoint_value'))) throw new Exception('请选择指定值'); - - match ($this->groupType = (int)$this->request->input('appoint_group',CouponCode::DISPENSE_APPOINT_GROUP_ALL_PEOPLE)) + $userIdList = match ($this->groupType = (int)$this->request->input('appoint_group',CouponCode::DISPENSE_APPOINT_GROUP_ALL_PEOPLE)) { CouponCode::DISPENSE_APPOINT_GROUP_DESIGNATED_USERS => $this->handleDesignatedUsers(), CouponCode::DISPENSE_APPOINT_GROUP_DESIGNATED_SITES => $this->handleDesignatedSites(), @@ -110,59 +89,11 @@ class DispenseConfirmService extends BaseService default => throw new Exception('不需要渲染用户数据') }; - return $this->return->success('success',['list' => $this->res]); + return $this->return->success('success',['list' => $userIdList]); }catch (Exception $e) { throw new ErrException($e->getMessage()); } } - /** - * @return void - */ - private function handleDesignatedUsers(): void - { - $this->res = explode(',',$this->request->input('appoint_value')); - } - - /** - * @return void - * @throws Exception - */ - private function handleDesignatedSitesAndGoods(): void - { - $this->getValueAndCheckDate(); - - $this->checkSite(); - - $this->checkSku(); - - $this->getUserData(); - } - - /** - * @return void - * @throws Exception - */ - private function handleDesignatedSites(): void - { - $this->getValueAndCheckDate(); - - $this->checkSite(); - - $this->getUserData(); - } - - /** - * @return void - * @throws Exception - */ - private function handleDesignatedGoods(): void - { - $this->getValueAndCheckDate(); - - $this->checkSku(); - - $this->getUserData(); - } } \ No newline at end of file diff --git a/app/Service/Amqp/Coupon/AutoDispenseService.php b/app/Service/Amqp/Coupon/AutoDispenseService.php new file mode 100644 index 0000000..989ff6c --- /dev/null +++ b/app/Service/Amqp/Coupon/AutoDispenseService.php @@ -0,0 +1,94 @@ +couponDispenseLogModel->getInfoById($this->couponDispenseId); + + if (empty($dispenseInfo)) throw new Exception('分发记录不存在'); + + $userIds = $this->couponDispenseUserModel->getNoSendUserListByDispenseId($this->couponDispenseId); + if (empty($userIds)) throw new Exception('该分发已发送完毕'); + + $partArr = array_chunk($userIds, self::SINGLE_MAX); + + foreach ($partArr as $onePart) { + $insertData = []; + foreach ($onePart as $oneUser) { + //todo 模拟数据 + $oneUserData = [ + 'user_id' => $oneUser, + 'coupon_template_id' => $dispenseInfo->coupon_template_id, + 'coupon_name' => $dispenseInfo->coupon_name, + 'coupon_dispense_id' => $dispenseInfo->id, + 'status' => CouponCode::COUPON_STATUS_UNUSED, + 'validity_start_time' => $dispenseInfo->validity_start_time, + 'validity_end_time' => $dispenseInfo->validity_end_time, + ]; + + $copies = array_map(function () use ($oneUserData) { + return $oneUserData; + }, array_fill(0, $dispenseInfo->item_count, null)); + + $insertData = array_merge($insertData, $copies); + } + + Db::transaction(function () use ($insertData,$dispenseInfo,$onePart) { + //添加优惠券信息 + if (!(new UserCoupon())->insert($insertData)) throw new Exception('写入数据失败'); + //修改分发用户信息 + if (!(new CouponDispenseUser())->updateReceiveCountByUserIds($onePart,$dispenseInfo->item_count)) throw new Exception('修改分发用户信息失败'); + + //修改分发 log 信息 (修改receive_count = origin_value + count($insertData) ps : 查询还没发的用户 * item_count / total_count = 完成百分比) + if (!(new CouponDispenseLog())->updateReceiveCountById($dispenseInfo->id,count($insertData))) throw new Exception('修改分发log信息失败'); + }); + + // 休息1秒 + Coroutine::sleep(1); + } + } +} \ No newline at end of file diff --git a/app/Service/ServiceTrait/Admin/CouponDispenseTrait.php b/app/Service/ServiceTrait/Admin/CouponDispenseTrait.php index ba86d25..a92e380 100644 --- a/app/Service/ServiceTrait/Admin/CouponDispenseTrait.php +++ b/app/Service/ServiceTrait/Admin/CouponDispenseTrait.php @@ -31,8 +31,10 @@ trait CouponDispenseTrait 'site' => explode(',', $appointValue['site']), 'sku' => explode(',', $appointValue['goods']) ]; + } else { + $this->appointValue = explode(',', $this->request->input('appoint_value')); } - $this->appointValue = explode(',', $this->request->input('appoint_value')); + unset($cycleInfo); } @@ -64,10 +66,10 @@ trait CouponDispenseTrait } /** - * @return void + * @return array * @throws Exception */ - protected function getUserData(): void + protected function getUserData(): array { $userIds = []; @@ -128,14 +130,74 @@ trait CouponDispenseTrait break; } - $this->res = $userIds; + return $userIds; + } + + + /** + * @return array + * @throws Exception + */ + protected function handleDesignatedUsers(): array + { + $this->checkAppointValue(); + + return explode(',',$this->request->input('appoint_value')); + } + + /** + * @return array + * @throws Exception + */ + protected function handleDesignatedSitesAndGoods(): array + { + $this->checkAppointValue(); + + $this->getValueAndCheckDate(); + + $this->checkSite(); + + $this->checkSku(); + + return $this->getUserData(); + } + + /** + * @return array + * @throws Exception + */ + protected function handleDesignatedSites(): array + { + $this->checkAppointValue(); + + $this->getValueAndCheckDate(); + + $this->checkSite(); + + return $this->getUserData(); + } + + /** + * @return array + * @throws Exception + */ + protected function handleDesignatedGoods(): array + { + $this->checkAppointValue(); + + $this->getValueAndCheckDate(); + + $this->checkSku(); + + return $this->getUserData(); } /** * @return void + * @throws Exception */ - protected function getUserInfoByUserIds(array $userIds) + protected function checkAppointValue(): void { - + if (empty($this->request->input('appoint_value'))) throw new Exception('请选择指定值'); } } \ No newline at end of file