123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655 |
- <?php
- namespace app\common\Service\Lottery;
- use app\common\model\lottery\LotteryDrawRecord;
- use app\common\model\lottery\LotteryWinRecord;
- use app\common\model\lottery\LotteryPrize;
- use app\common\Enum\LotteryEnum;
- use think\Exception;
- use app\common\model\lottery\LotteryActivity;
- /**
- * 抽奖记录服务类
- * 专门处理抽奖记录和中奖记录的业务逻辑
- */
- class LotteryRecordService
- {
- // ============ 抽奖记录相关方法 ============
- /**
- * 创建抽奖记录
- */
- public static function createDrawRecord($activityId, $userId, $prizeId, $isWin, $triggerType, $triggerOrderId = null, $triggerAmount = null, $winInfo = [])
- {
- $data = [
- 'activity_id' => $activityId,
- 'user_id' => $userId,
- 'prize_id' => $prizeId,
- 'is_win' => $isWin ? 1 : 0,
- 'trigger_type' => $triggerType,
- 'trigger_order_id' => $triggerOrderId,
- 'trigger_amount' => $triggerAmount,
- 'win_info' => $winInfo ? json_encode($winInfo) : '',
- 'draw_ip' => request()->ip(),
- 'draw_time' => time(),
- 'device_info' => request()->header('user-agent', '')
- ];
-
- return LotteryDrawRecord::create($data);
- }
- /**
- * 获取用户抽奖次数
- */
- public static function getUserDrawCount($activityId, $userId, $timeRange = null)
- {
- $query = LotteryDrawRecord::where('activity_id', $activityId)
- ->where('user_id', $userId);
-
- if ($timeRange) {
- if (isset($timeRange['start'])) {
- $query->where('draw_time', '>=', $timeRange['start']);
- }
- if (isset($timeRange['end'])) {
- $query->where('draw_time', '<=', $timeRange['end']);
- }
- }
-
- return $query->count();
- }
- /**
- * 获取用户中奖次数
- */
- public static function getUserWinCount($activityId, $userId, $timeRange = null)
- {
- $query = LotteryDrawRecord::where('activity_id', $activityId)
- ->where('user_id', $userId)
- ->where('is_win', 1);
-
- if ($timeRange) {
- if (isset($timeRange['start'])) {
- $query->where('draw_time', '>=', $timeRange['start']);
- }
- if (isset($timeRange['end'])) {
- $query->where('draw_time', '<=', $timeRange['end']);
- }
- }
-
- return $query->count();
- }
- /**
- * 获取活动抽奖统计
- */
- public static function getActivityDrawStats($activityId, $timeRange = null)
- {
- $query = LotteryDrawRecord::where('activity_id', $activityId);
-
- if ($timeRange) {
- if (isset($timeRange['start'])) {
- $query->where('draw_time', '>=', $timeRange['start']);
- }
- if (isset($timeRange['end'])) {
- $query->where('draw_time', '<=', $timeRange['end']);
- }
- }
-
- return [
- 'total_draw' => $query->count(),
- 'total_win' => $query->where('is_win', 1)->count(),
- 'total_people' => $query->distinct('user_id')->count()
- ];
- }
- /**
- * 获取用户抽奖记录列表
- */
- public static function getUserDrawRecords($userId = 0, $activityId = null,$status = 0, $page = 1, $pageSize = 20)
- {
- $query = LotteryDrawRecord::where('user_id', $userId);
-
- if ($activityId) {
- $query->where('activity_id', $activityId);
- }
- if ($status) {
- $query->where('status', $status);
- }
-
- // 1. 先分页查询抽奖记录
- $records = $query->field('id,activity_id,user_id,prize_id,is_win,trigger_type,trigger_order_id,trigger_amount,
- draw_time,status')
- ->order('draw_time', 'desc')
- ->paginate($pageSize, false, ['page' => $page]);
-
- if (empty($records) || $records->isEmpty()) {
- return $records;
- }
-
- // 2. 收集关联ID
- $activityIds = [];
- $prizeIds = [];
- $drawRecordIds = [];
-
- foreach ($records as $record) {
- $activityIds[] = $record->activity_id;
- $prizeIds[] = $record->prize_id;
- if ($record->is_win) {
- $drawRecordIds[] = $record->id;
- }
- }
-
- // 去重
- $activityIds = array_unique($activityIds);
- $prizeIds = array_unique($prizeIds);
- $drawRecordIds = array_unique($drawRecordIds);
-
- // 3. 批量查询关联数据
- $activities = [];
- if (!empty($activityIds)) {
- $activityList = LotteryActivity::whereIn('id', $activityIds)
- ->field('id,name,status,lottery_type,lottery_time')
- ->select();
- foreach ($activityList as $activity) {
- $activities[$activity->id] = $activity;
- }
- }
-
- $prizes = [];
- if (!empty($prizeIds)) {
- $prizeList = LotteryPrize::whereIn('id', $prizeIds)
- ->field('id,name,image,activity_id,type')
- ->select();
- foreach ($prizeList as $prize) {
- $prizes[$prize->id] = $prize;
- }
- }
-
- $winRecords = [];
- if (!empty($drawRecordIds)) {
- $winRecordList = LotteryWinRecord::whereIn('draw_record_id', $drawRecordIds)
- ->field('id,draw_record_id,deliver_status,deliver_time,exchange_code,receiver_name,receiver_mobile,receiver_address,express_company,express_number,exchange_code')
- ->select();
- foreach ($winRecordList as $winRecord) {
- $winRecords[$winRecord->draw_record_id] = $winRecord;
- }
- }
-
- // 4. 将关联数据作为字段附加到记录中
- foreach ($records as $record) {
- // 附加活动信息字段
- $activity = $activities[$record->activity_id] ?? null;
- $record->activity_name = $activity ? $activity->name : '';
- $record->activity_status = $activity ? $activity->status : 0;
- $record->activity_lottery_type = $activity ? $activity->lottery_type : 0;
- $record->activity_lottery_time = $activity ? $activity->lottery_time : 0;
-
- // 附加奖品信息字段
- $prize = $prizes[$record->prize_id] ?? null;
- $record->prize_name = $prize ? $prize->name : '';
- $record->prize_image = $prize ? cdnurl($prize->image) : '';
- $record->prize_type = $prize ? $prize->type : 0;
-
- // 附加中奖记录信息字段(仅限中奖记录)
- if ($record->is_win) {
- $winRecord = $winRecords[$record->id] ?? null;
- $record->win_record_id = $winRecord ? $winRecord->id : 0;
- $record->deliver_status = $winRecord ? $winRecord->deliver_status : 0;
- $record->deliver_time = $winRecord ? $winRecord->deliver_time : 0;
- $record->exchange_code = $winRecord ? $winRecord->exchange_code : '';
- $record->receiver_name = $winRecord ? $winRecord->receiver_name : '';
- $record->receiver_mobile = $winRecord ? $winRecord->receiver_mobile : '';
- $record->receiver_address = $winRecord ? $winRecord->receiver_address : '';
- $record->express_company = $winRecord ? $winRecord->express_company : '';
- $record->express_number = $winRecord ? $winRecord->express_number : '';
- } else {
- $record->win_record_id = 0;
- $record->deliver_status = 0;
- $record->deliver_time = 0;
- $record->exchange_code = '';
- $record->receiver_name = '';
- $record->receiver_mobile = '';
- $record->receiver_address = '';
- $record->express_company = '';
- $record->express_number = '';
- }
-
- // 添加触发类型文本
- $record->trigger_type_text = LotteryEnum::getTriggerTypeMap($record->trigger_type);
- }
-
- return $records;
- }
-
- /**
- * 获取抽奖记录详情
- *
- * @param int $drawRecordId 抽奖记录ID
- * @param int $userId 用户ID(可选,用于验证权限)
- * @return LotteryDrawRecord|null 抽奖记录详情,包含关联信息
- */
- public static function getDrawRecordDetail($drawRecordId, $userId = 0)
- {
- // 1. 查询抽奖记录基础信息
- $query = LotteryDrawRecord::where('id', $drawRecordId);
-
- // 如果指定了用户ID,则验证权限
- if ($userId) {
- $query->where('user_id', $userId);
- }
-
- $record = $query->field('id,activity_id,user_id,prize_id,is_win,trigger_type,trigger_order_id,
- trigger_amount,draw_time,remark,status,createtime')
- ->find();
-
- if (!$record) {
- return null;
- }
-
- // 2. 查询关联的活动信息
- $activity = LotteryActivity::where('id', $record->activity_id)
- ->field('id,name,type,status,lottery_type,lottery_time')
- ->find();
-
- // 3. 查询关联的奖品信息
- $prize = null;
- if ($record->prize_id > 0) {
- $prize = LotteryPrize::where('id', $record->prize_id)
- ->field('id,name,image,type')
- ->find();
- }
-
- // 4. 查询关联的中奖记录信息(仅限中奖记录)
- $winRecord = null;
- if ($record->is_win) {
- $winRecord = LotteryWinRecord::where('draw_record_id', $record->id)
- ->field('id,draw_record_id,prize_name,prize_type,deliver_status,
- deliver_time,receiver_name,receiver_mobile,receiver_address,
- express_company,express_number,exchange_code,code_used_time')
- ->find();
- }
-
- // 5. 将关联数据作为字段附加到记录中
- // 附加活动信息字段
- $record->activity_name = $activity ? $activity->name : '';
- $record->activity_type = $activity ? $activity->type : 0;
- $record->activity_status = $activity ? $activity->status : 0;
- $record->activity_lottery_type = $activity ? $activity->lottery_type : 0;
- $record->activity_lottery_time = $activity ? $activity->lottery_time : 0;
-
- // 附加奖品信息字段
- $record->prize_name = $prize ? $prize->name : '';
- $record->prize_image = $prize ? cdnurl($prize->image) : '';
- $record->prize_type = $prize ? $prize->type : 0;
- // 附加中奖记录信息字段
- if ($winRecord) {
- $record->win_record_id = $winRecord->id;
- $record->win_prize_name = $winRecord->prize_name;
- $record->win_prize_type = $winRecord->prize_type;
- $record->deliver_status = $winRecord->deliver_status;
- $record->deliver_time = $winRecord->deliver_time;
- $record->receiver_name = $winRecord->receiver_name;
- $record->receiver_mobile = $winRecord->receiver_mobile;
- $record->receiver_address = $winRecord->receiver_address;
- $record->express_company = $winRecord->express_company;
- $record->express_number = $winRecord->express_number;
- $record->exchange_code = $winRecord->exchange_code;
- $record->code_used_time = $winRecord->code_used_time;
- } else {
- $record->win_record_id = 0;
- $record->win_prize_name = '';
- $record->win_prize_type = 0;
- $record->deliver_status = 0;
- $record->deliver_time = 0;
- $record->receiver_name = '';
- $record->receiver_mobile = '';
- $record->receiver_address = '';
- $record->express_company = '';
- $record->express_number = '';
- $record->exchange_code = '';
- $record->code_used_time = 0;
- }
-
- return $record;
- }
- /**
- * 检查用户是否已为指定订单抽奖
- */
- public static function hasUserDrawnForOrder($activityId, $userId, $orderId)
- {
- return LotteryDrawRecord::where('activity_id', $activityId)
- ->where('user_id', $userId)
- ->where('trigger_order_id', $orderId)
- ->count() > 0;
- }
- /**
- * 获取用户抽奖历史统计
- */
- public static function getUserDrawHistoryStats($userId)
- {
- $stats = LotteryDrawRecord::where('user_id', $userId)
- ->field([
- 'COUNT(*) as total_draws',
- 'SUM(is_win) as total_wins',
- 'COUNT(DISTINCT activity_id) as total_activities'
- ])
- ->find();
- return [
- 'total_draws' => $stats['total_draws'] ?? 0,
- 'total_wins' => $stats['total_wins'] ?? 0,
- 'total_activities' => $stats['total_activities'] ?? 0,
- 'win_rate' => $stats['total_draws'] > 0 ?
- round(($stats['total_wins'] / $stats['total_draws']) * 100, 2) : 0
- ];
- }
- /**
- * 获取活动抽奖排行榜
- */
- public static function getActivityDrawRanking($activityId, $limit = 50)
- {
- return LotteryDrawRecord::where('activity_id', $activityId)
- ->field('user_id, COUNT(*) as draw_count, SUM(is_win) as win_count')
- ->with('user')
- ->group('user_id')
- ->order('draw_count desc, win_count desc')
- ->limit($limit)
- ->select();
- }
- /**
- * 获取活动抽奖时间分布统计
- */
- public static function getActivityDrawTimeDistribution($activityId, $dateRange = null)
- {
- $query = LotteryDrawRecord::where('activity_id', $activityId);
-
- if ($dateRange) {
- if (isset($dateRange['start'])) {
- $query->where('draw_time', '>=', $dateRange['start']);
- }
- if (isset($dateRange['end'])) {
- $query->where('draw_time', '<=', $dateRange['end']);
- }
- }
-
- return $query->field('DATE(FROM_UNIXTIME(draw_time)) as date, COUNT(*) as draw_count, SUM(is_win) as win_count')
- ->group('date')
- ->order('date asc')
- ->select();
- }
- // ============ 中奖记录相关方法 ============
- /**
- * 获取中奖记录详情
- */
- public static function getWinRecordDetail($winRecordId, $userId = 0)
- {
- $query = LotteryWinRecord::where('id', $winRecordId);
- if ($userId) {
- $query->where('user_id', $userId);
- }
- return $query->find();
- }
- /**
- * 创建中奖记录
- */
- public static function createWinRecord($drawRecordId, $activityId, $userId, $prizeId, $prizeName, $prizeType, $prizeValue = [])
- {
- $data = [
- 'draw_record_id' => $drawRecordId,
- 'activity_id' => $activityId,
- 'user_id' => $userId,
- 'prize_id' => $prizeId,
- 'prize_name' => $prizeName,
- 'prize_type' => $prizeType,
- 'prize_value' => is_array($prizeValue) ? json_encode($prizeValue) : $prizeValue,
- 'deliver_status' => LotteryEnum::DELIVER_STATUS_PENDING
- ];
-
- return LotteryWinRecord::create($data);
- }
- /**
- * 标记中奖记录为发放成功
- */
- public static function markWinRecordAsDelivered(LotteryWinRecord $winRecord, $deliverInfo = [])
- {
- $winRecord->deliver_status = LotteryEnum::DELIVER_STATUS_SUCCESS;
- $winRecord->deliver_time = time();
- if ($deliverInfo) {
- $winRecord->deliver_info = json_encode($deliverInfo);
- }
- return $winRecord->save();
- }
- /**
- * 标记中奖记录为发放失败
- */
- public static function markWinRecordAsFailed(LotteryWinRecord $winRecord, $reason = '')
- {
- $winRecord->deliver_status = LotteryEnum::DELIVER_STATUS_FAILED;
- $winRecord->fail_reason = $reason;
- return $winRecord->save();
- }
- /**
- * 设置中奖记录收货地址
- */
- public static function setWinRecordDeliveryAddress(LotteryWinRecord $winRecord, $name, $mobile, $address)
- {
- $winRecord->receiver_name = $name;
- $winRecord->receiver_mobile = $mobile;
- $winRecord->receiver_address = $address;
- return $winRecord->save();
- }
- /**
- * 设置中奖记录快递信息
- */
- public static function setWinRecordExpressInfo(LotteryWinRecord $winRecord, $company, $number)
- {
- $winRecord->express_company = $company;
- $winRecord->express_number = $number;
- return $winRecord->save();
- }
- /**
- * 设置中奖记录兑换码
- */
- public static function setWinRecordExchangeCode(LotteryWinRecord $winRecord, $code)
- {
- $winRecord->exchange_code = $code;
- return $winRecord->save();
- }
- /**
- * 标记中奖记录兑换码已使用
- */
- public static function markWinRecordCodeUsed(LotteryWinRecord $winRecord)
- {
- $winRecord->code_used_time = time();
- return $winRecord->save();
- }
- /**
- * 获取待发放的中奖记录
- */
- public static function getPendingWinRecords($limit = 100)
- {
- return LotteryWinRecord::where('deliver_status', LotteryEnum::DELIVER_STATUS_PENDING)
- ->order('createtime', 'asc')
- ->limit($limit)
- ->select();
- }
- /**
- * 获取用户中奖记录
- */
- public static function getUserWinRecords($userId, $page = 1, $limit = 20)
- {
- return LotteryWinRecord::where('user_id', $userId)
- ->with(['activity', 'prize'])
- ->order('createtime', 'desc')
- ->page($page, $limit)
- ->select();
- }
- /**
- * 获取活动中奖统计
- */
- public static function getActivityWinStats($activityId)
- {
- return [
- 'total_win' => LotteryWinRecord::where('activity_id', $activityId)->count(),
- 'delivered' => LotteryWinRecord::where('activity_id', $activityId)
- ->where('deliver_status', LotteryEnum::DELIVER_STATUS_SUCCESS)
- ->count(),
- 'pending' => LotteryWinRecord::where('activity_id', $activityId)
- ->where('deliver_status', LotteryEnum::DELIVER_STATUS_PENDING)
- ->count(),
- 'failed' => LotteryWinRecord::where('activity_id', $activityId)
- ->where('deliver_status', LotteryEnum::DELIVER_STATUS_FAILED)
- ->count()
- ];
- }
- /**
- * 批量处理待发放的中奖记录
- */
- public static function batchProcessPendingWinRecords($limit = 50)
- {
- $pendingRecords = static::getPendingWinRecords($limit);
- $processedCount = 0;
- foreach ($pendingRecords as $winRecord) {
- try {
- $prize = LotteryPrize::find($winRecord->prize_id);
- if ($prize && $prize->deliver_type == LotteryEnum::DELIVER_TYPE_AUTO) {
- static::autoDeliverPrize($winRecord, $prize);
- $processedCount++;
- }
- } catch (Exception $e) {
- static::markWinRecordAsFailed($winRecord, $e->getMessage());
- trace('自动发放奖品失败: ' . $e->getMessage(), 'error');
- }
- }
- return $processedCount;
- }
- /**
- * 手动发放奖品(管理员操作)
- */
- public static function manualDeliverPrize($winRecordId, $deliverInfo = [], $adminId = 0)
- {
- $winRecord = LotteryWinRecord::find($winRecordId);
- if (!$winRecord) {
- throw new Exception('中奖记录不存在');
- }
- if ($winRecord->deliver_status != LotteryEnum::DELIVER_STATUS_PENDING) {
- throw new Exception('该记录已处理,无法重复发放');
- }
- $deliverInfo['admin_id'] = $adminId;
- $deliverInfo['manual_deliver_time'] = time();
- return static::markWinRecordAsDelivered($winRecord, $deliverInfo);
- }
- /**
- * 取消中奖记录
- */
- public static function cancelWinRecord($winRecordId, $reason = '', $adminId = 0)
- {
- $winRecord = LotteryWinRecord::find($winRecordId);
- if (!$winRecord) {
- throw new Exception('中奖记录不存在');
- }
- $winRecord->deliver_status = LotteryEnum::DELIVER_STATUS_CANCELLED;
- $winRecord->fail_reason = $reason;
- $winRecord->cancel_time = time();
- $winRecord->cancel_admin_id = $adminId;
- return $winRecord->save();
- }
- /**
- * 自动发放奖品
- */
- public static function autoDeliverPrize($winRecord, $prize)
- {
- try {
- switch ($prize->type) {
- case LotteryEnum::PRIZE_TYPE_COUPON:
- static::deliverCoupon($winRecord, $prize);
- break;
- case LotteryEnum::PRIZE_TYPE_REDPACK:
- static::deliverRedPacket($winRecord, $prize);
- break;
- case LotteryEnum::PRIZE_TYPE_CODE:
- static::deliverExchangeCode($winRecord, $prize);
- break;
- case LotteryEnum::PRIZE_TYPE_SHOP_GOODS:
- static::deliverGoods($winRecord, $prize);
- break;
- }
- } catch (Exception $e) {
- static::markWinRecordAsFailed($winRecord, $e->getMessage());
- }
- }
- /**
- * 发放优惠券
- */
- private static function deliverCoupon($winRecord, $prize)
- {
- // 这里调用优惠券发放接口
- // 示例代码,需要根据实际优惠券系统实现
- static::markWinRecordAsDelivered($winRecord, ['coupon_id' => $prize->coupon_id]);
- }
- /**
- * 发放红包
- */
- private static function deliverRedPacket($winRecord, $prize)
- {
- // 这里调用红包发放接口
- // 示例代码,需要根据实际红包系统实现
- static::markWinRecordAsDelivered($winRecord, ['amount' => $prize->amount]);
- }
- /**
- * 发放兑换码
- */
- private static function deliverExchangeCode($winRecord, $prize)
- {
- $code = LotteryService::getAvailableExchangeCode($prize);
- if ($code) {
- static::setWinRecordExchangeCode($winRecord, $code);
- LotteryService::markExchangeCodeUsed($prize, $code);
- static::markWinRecordAsDelivered($winRecord, ['exchange_code' => $code]);
- } else {
- throw new Exception('兑换码已用完');
- }
- }
- /**
- * 发放商城商品
- */
- private static function deliverGoods($winRecord, $prize)
- {
- // 这里可以自动加入购物车或创建订单
- // 示例代码,需要根据实际商城系统实现
- static::markWinRecordAsDelivered($winRecord, [
- 'goods_id' => $prize->goods_id,
- 'goods_sku_id' => $prize->goods_sku_id
- ]);
- }
- }
|