diff --git a/application/api/model/Room.php b/application/api/model/Room.php index 11cac774..ce799781 100644 --- a/application/api/model/Room.php +++ b/application/api/model/Room.php @@ -1095,6 +1095,22 @@ class Room extends Model return ['code' => 0, 'msg' => '当前房间类型错误,请联系管理员', 'data' => '']; } + //记录用户进入房间 + $is_join = db::name('vs_room_visitor')->where(['room_id' => $room_id, 'user_id' => $user_id])->find(); + if (!$is_join) { + db::name('vs_room_visitor')->insert(['room_id' => $room_id, 'user_id' => $user_id, 'createtime' => time()]); + }else{ + db::name('vs_room_visitor')->where('id', $is_join['id'])->update(['createtime' => time(),'is_online' => 1]); + } + + //记录用户最后进入的是哪个房间 + db::name('user_data')->where('user_id', $user_id)->update(['room_id' => $room_id]); + + //记录用户访问记录 + if($user_id != $room['user_id']){ + model('api/User')->add_user_visit_log(2,$user_id, $room_id); + } + //给前端定义返回的数据 $room_owner = null;//房间拥有者信息 $cp_users = null;//CP用户信息 @@ -1178,22 +1194,6 @@ class Room extends Model break; } - //记录用户进入房间 - $is_join = db::name('vs_room_visitor')->where(['room_id' => $room_id, 'user_id' => $user_id])->find(); - if (!$is_join) { - db::name('vs_room_visitor')->insert(['room_id' => $room_id, 'user_id' => $user_id, 'createtime' => time()]); - }else{ - db::name('vs_room_visitor')->where('id', $is_join['id'])->update(['createtime' => time(),'is_online' => 1]); - } - - //记录用户最后进入的是哪个房间 - db::name('user_data')->where('user_id', $user_id)->update(['room_id' => $room_id]); - - //记录用户访问记录 - if($user_id != $room['user_id']){ - model('api/User')->add_user_visit_log(2,$user_id, $room_id); - } - //进入房间保持心跳 $is_xintiao = db::name('vs_room_heartbeat')->where(['user_id' => $user_id, 'room_id' => $room_id])->find(); if($is_xintiao){ @@ -1278,6 +1278,11 @@ class Room extends Model $is_hide = db::name('user')->where('id', $user_id)->value('hide_status'); if($is_hide != 1){//不是隐身 + $tet['text'] = '回到房间'; + $tet['user_id'] = $user_id; + $tet['type'] = 1; + model('api/Chat')->sendMsg(1058,$room_id,$tet); + model('api/Chat')->sendMsg(1001,$room_id,$text,$user_id); //当前用户不是隐身状态的时候触发CP特效 diff --git a/application/common/library/GiftDataMigrator.php b/application/common/library/GiftDataMigrator.php new file mode 100644 index 00000000..53604ec3 --- /dev/null +++ b/application/common/library/GiftDataMigrator.php @@ -0,0 +1,255 @@ +where('id', '>', $lastId) + ->where('createtime', '>=', $startTime) + ->where('createtime', '<', $endTime) + ->order('id', 'asc') + ->limit($batchSize) + ->select(); + + if (empty($records)) { + $hasMore = false; + break; + } + + // 转换数据格式,生成雪花ID + $dataToInsert = []; + foreach ($records as $record) { + $newRecord = [ + 'id' => Snowflake::generate(), + 'user_id' => $record['user_id'] ?? 0, + 'gift_id' => $record['gift_id'] ?? 0, + 'gift_type' => $record['gift_type'] ?? 1, + 'number' => $record['number'] ?? 0, + 'gift_user' => $record['gift_user'] ?? 0, + 'from_id' => $record['from_id'] ?? 0, + 'pit_number' => $record['pit_number'] ?? 0, + 'total_price' => $record['total_price'] ?? 0, + 'type' => $record['type'] ?? 1, + 'from' => $record['from'] ?? 1, + 'createtime' => $record['createtime'] ?? time(), + 'updatetime' => $record['updatetime'] ?? time(), + ]; + $dataToInsert[] = $newRecord; + $lastId = $record['id']; // 使用原始表的ID + } + + // 批量插入到分表 + if (!empty($dataToInsert)) { + Db::table($targetTable)->insertAll($dataToInsert); + $totalMigrated += count($dataToInsert); + + Log::info("迁移批次成功: {$yearMonth}, 本批: " . count($dataToInsert) . " 条,总计: {$totalMigrated} 条"); + } + + // 防止内存占用过高 + unset($records, $dataToInsert); + + // 小睡一下,防止CPU占用过高 + usleep(10000); + + } catch (\Exception $e) { + Log::error("迁移 {$yearMonth} 数据失败: " . $e->getMessage()); + return [ + 'success' => false, + 'message' => $e->getMessage(), + 'total' => $totalMigrated, + 'last_id' => $lastId + ]; + } + } + + Log::info("完成迁移 {$yearMonth} 数据,总计: {$totalMigrated} 条"); + + return [ + 'success' => true, + 'message' => '迁移完成', + 'total' => $totalMigrated, + 'last_id' => $lastId + ]; + } + + /** + * 迁移所有历史数据 + * @param int $batchSize 每批数量 + * @return array + */ + public static function migrateAllData($batchSize = 1000) + { + // 获取所有需要迁移的月份 + $firstRecord = Db::name('vs_give_gift') + ->order('createtime', 'asc') + ->find(); + $lastRecord = Db::name('vs_give_gift') + ->order('createtime', 'desc') + ->find(); + + if (!$firstRecord || !$lastRecord) { + return ['success' => true, 'message' => '没有数据需要迁移']; + } + + $startMonth = date('Ym', $firstRecord['createtime']); + $endMonth = date('Ym', $lastRecord['createtime']); + + // 生成月份列表 + $months = self::generateMonthRange($startMonth, $endMonth); + + Log::info("开始迁移数据,月份范围: {$startMonth} 到 {$endMonth},共 " . count($months) . " 个月"); + + $results = []; + foreach ($months as $month) { + Log::info("开始迁移 {$month} 数据"); + + $result = self::migrateMonthData($month, $batchSize); + $results[$month] = $result; + + if (!$result['success']) { + Log::error("迁移 {$month} 数据失败,停止迁移"); + break; + } + + Log::info("完成迁移 {$month} 数据,共 {$result['total']} 条"); + } + + return [ + 'success' => true, + 'message' => '所有数据迁移完成', + 'results' => $results + ]; + } + + /** + * 生成月份范围 + * @param string $startMonth 202401 + * @param string $endMonth 202412 + * @return array + */ + private static function generateMonthRange($startMonth, $endMonth) + { + $start = new \DateTime($startMonth . '01'); + $end = new \DateTime($endMonth . '01'); + + $months = []; + while ($start <= $end) { + $months[] = $start->format('Ym'); + $start->modify('+1 month'); + } + + return $months; + } + + /** + * 获取迁移进度 + * @return array + */ + public static function getMigrationProgress() + { + // 查询原始表数据量 + $totalCount = Db::name('vs_give_gift')->count(); + + // 查询已迁移的数据量 + $migratedCount = 0; + $tables = GiftTableManager::getAllTables(); + + foreach ($tables as $tableInfo) { + try { + $count = Db::table($tableInfo['table_name'])->count(); + $migratedCount += $count; + } catch (\Exception $e) { + // 表可能不存在,跳过 + } + } + + $progress = $totalCount > 0 ? round($migratedCount / $totalCount * 100, 2) : 0; + + return [ + 'total' => $totalCount, + 'migrated' => $migratedCount, + 'progress' => $progress, + 'remaining' => $totalCount - $migratedCount + ]; + } + + /** + * 验证迁移数据一致性 + * @param string $yearMonth 年月 + * @return array + */ + public static function verifyMigration($yearMonth) + { + $startTime = strtotime($yearMonth . '01 00:00:00'); + $endTime = strtotime(date('Y-m-01', strtotime('+1 month', $startTime))); + + // 原始表数据量 + $sourceCount = Db::name('vs_give_gift') + ->where('createtime', '>=', $startTime) + ->where('createtime', '<', $endTime) + ->count(); + + // 目标表数据量 + $targetTable = 'fa_vs_give_gift_' . $yearMonth; + $targetCount = 0; + try { + $targetCount = Db::table($targetTable)->count(); + } catch (\Exception $e) { + Log::error("验证迁移数据失败,目标表不存在: " . $targetTable); + } + + // 比较总价值 + $sourceTotalPrice = Db::name('vs_give_gift') + ->where('createtime', '>=', $startTime) + ->where('createtime', '<', $endTime) + ->sum('total_price'); + + $targetTotalPrice = 0; + try { + $targetTotalPrice = Db::table($targetTable)->sum('total_price'); + } catch (\Exception $e) { + // 表可能不存在 + } + + return [ + 'source_count' => $sourceCount, + 'target_count' => $targetCount, + 'count_match' => $sourceCount == $targetCount, + 'source_total_price' => $sourceTotalPrice, + 'target_total_price' => $targetTotalPrice, + 'price_match' => abs(floatval($sourceTotalPrice) - floatval($targetTotalPrice)) < 0.01 + ]; + } +} \ No newline at end of file diff --git a/application/common/library/GiftQueue.php b/application/common/library/GiftQueue.php new file mode 100644 index 00000000..8ed0cb16 --- /dev/null +++ b/application/common/library/GiftQueue.php @@ -0,0 +1,251 @@ + $giftData, + 'queue_time' => time(), + 'retry_count' => 0, + 'uuid' => uniqid('gift_', true) + ]; + + // 序列化数据 + $data = json_encode($queueData, JSON_UNESCAPED_UNICODE); + + // 使用Redis列表存储 + Cache::handler()->lpush(self::QUEUE_KEY, $data); + + Log::info("送礼记录已加入队列: {$queueData['uuid']}, 用户: {$giftData['user_id']}, 收礼人: {$giftData['gift_user']}"); + + return true; + } catch (\Exception $e) { + Log::error('送礼队列添加失败:' . $e->getMessage()); + return false; + } + } + + /** + * 批量添加送礼记录到队列 + * @param array $giftList 送礼数据列表 + * @return array 处理结果 + */ + public static function pushBatch($giftList) + { + $success = 0; + $failed = 0; + + foreach ($giftList as $giftData) { + if (self::push($giftData)) { + $success++; + } else { + $failed++; + } + } + + return [ + 'success' => $success, + 'failed' => $failed, + 'total' => count($giftList) + ]; + } + + /** + * 处理队列 + * @param int $batchSize 每次处理数量 + * @return array 处理结果 + */ + public static function process($batchSize = 100) + { + $processed = 0; + $success = 0; + $failed = 0; + + $model = new GiveGiftBase(); + + Log::info("开始处理送礼队列,批量大小: {$batchSize}"); + + for ($i = 0; $i < $batchSize; $i++) { + try { + // 从队列取数据 + $data = Cache::handler()->rpop(self::QUEUE_KEY); + if (!$data) { + break; + } + + $processed++; + + $queueData = json_decode($data, true); + if (!$queueData || !isset($queueData['data'])) { + $failed++; + Log::error('队列数据格式错误: ' . $data); + continue; + } + + $giftData = $queueData['data']; + $uuid = $queueData['uuid'] ?? 'unknown'; + + Log::info("处理送礼记录: {$uuid}, 用户: {$giftData['user_id']}"); + + // 验证数据完整性 + if (empty($giftData['createtime'])) { + $giftData['createtime'] = time(); + } + + // 插入数据库 + $result = $model->addGiftRecord($giftData); + + if ($result) { + $success++; + Log::info("送礼记录处理成功: {$uuid}, ID: {$result}"); + } else { + $failed++; + Log::error("送礼记录处理失败: {$uuid}, 错误: " . $model->getError()); + + // 重试逻辑 + self::retry($data); + } + + } catch (\Exception $e) { + $failed++; + Log::error('处理送礼队列失败:' . $e->getMessage()); + } + } + + $result = [ + 'processed' => $processed, + 'success' => $success, + 'failed' => $failed + ]; + + Log::info("送礼队列处理完成: " . json_encode($result)); + + return $result; + } + + /** + * 重试机制 + * @param string $data 队列数据 + */ + protected static function retry($data) + { + $queueData = json_decode($data, true); + if (!$queueData) { + return; + } + + $queueData['retry_count'] = ($queueData['retry_count'] ?? 0) + 1; + + if ($queueData['retry_count'] <= self::MAX_RETRY) { + // 重新加入队列 + $newData = json_encode($queueData, JSON_UNESCAPED_UNICODE); + Cache::handler()->lpush(self::QUEUE_KEY, $newData); + Log::info("重试送礼记录: {$queueData['uuid']}, 重试次数: {$queueData['retry_count']}"); + } else { + // 记录到失败队列 + $queueData['fail_time'] = time(); + $failedData = json_encode($queueData, JSON_UNESCAPED_UNICODE); + Cache::handler()->lpush(self::QUEUE_FAILED_KEY, $failedData); + Log::error("送礼记录重试超过最大次数: {$queueData['uuid']}, 数据: " . json_encode($queueData['data'])); + } + } + + /** + * 获取队列长度 + * @return int + */ + public static function size() + { + try { + return Cache::handler()->llen(self::QUEUE_KEY); + } catch (\Exception $e) { + Log::error('获取队列长度失败: ' . $e->getMessage()); + return 0; + } + } + + /** + * 获取失败队列长度 + * @return int + */ + public static function failedSize() + { + try { + return Cache::handler()->llen(self::QUEUE_FAILED_KEY); + } catch (\Exception $e) { + Log::error('获取失败队列长度失败: ' . $e->getMessage()); + return 0; + } + } + + /** + * 获取队列统计信息 + * @return array + */ + public static function stats() + { + return [ + 'queue_size' => self::size(), + 'failed_size' => self::failedSize(), + 'status' => self::size() > 1000 ? '繁忙' : (self::size() > 100 ? '正常' : '空闲') + ]; + } + + /** + * 清空队列 + * @return bool + */ + public static function clear() + { + try { + Cache::handler()->del(self::QUEUE_KEY); + Log::info('送礼队列已清空'); + return true; + } catch (\Exception $e) { + Log::error('清空队列失败: ' . $e->getMessage()); + return false; + } + } + + /** + * 清理失败队列 + * @return bool + */ + public static function clearFailed() + { + try { + Cache::handler()->del(self::QUEUE_FAILED_KEY); + Log::info('送礼失败队列已清空'); + return true; + } catch (\Exception $e) { + Log::error('清空失败队列失败: ' . $e->getMessage()); + return false; + } + } +} \ No newline at end of file diff --git a/application/common/library/GiftTableManager.php b/application/common/library/GiftTableManager.php new file mode 100644 index 00000000..497ceeb6 --- /dev/null +++ b/application/common/library/GiftTableManager.php @@ -0,0 +1,221 @@ +where('table_name', $tableName) + ->find(); + + if (!$tableInfo) { + Db::name('vs_give_gift_tables')->insert([ + 'table_name' => $tableName, + 'start_time' => $startTime, + 'end_time' => $endTime, + 'create_time' => time() + ]); + } + + Log::info("创建分表成功: {$tableName}"); + return true; + } catch (\Exception $e) { + Log::error("创建分表失败 {$tableName}: " . $e->getMessage()); + throw new Exception('创建分表失败:' . $e->getMessage()); + } + } + + /** + * 批量创建分表 + * @param int $months 创建未来几个月 + * @return array 创建结果 + */ + public static function batchCreateTables($months = 12) + { + $results = []; + + for ($i = 0; $i < $months; $i++) { + $yearMonth = date('Ym', strtotime("+{$i} month")); + + try { + $result = self::createMonthTable($yearMonth); + $results[$yearMonth] = [ + 'success' => $result, + 'message' => $result ? '创建成功' : '创建失败' + ]; + } catch (\Exception $e) { + $results[$yearMonth] = [ + 'success' => false, + 'message' => $e->getMessage() + ]; + } + } + + return $results; + } + + /** + * 获取所有分表 + * @return array + */ + public static function getAllTables() + { + try { + return Db::name('vs_give_gift_tables') + ->order('start_time', 'asc') + ->select(); + } catch (\Exception $e) { + Log::error("获取分表列表失败: " . $e->getMessage()); + return []; + } + } + + /** + * 根据时间范围获取需要查询的表 + * @param int $startTime 开始时间戳 + * @param int $endTime 结束时间戳 + * @return array 表名数组 + */ + public static function getTablesByTimeRange($startTime = null, $endTime = null) + { + try { + $query = Db::name('vs_give_gift_tables'); + + if ($startTime !== null) { + $query->where('end_time', '>=', $startTime); + } + + if ($endTime !== null) { + $query->where('start_time', '<=', $endTime); + } + + $tables = $query->order('start_time', 'asc') + ->column('table_name'); + + return $tables ?: []; + } catch (\Exception $e) { + Log::error("获取时间范围分表失败: " . $e->getMessage()); + return []; + } + } + + /** + * 初始化分表系统 + * @param string $startMonth 起始年月 202401 + * @param int $months 创建几个月 + * @return array + */ + public static function initTables($startMonth = null, $months = 24) + { + if (!$startMonth) { + $startMonth = date('Ym'); + } + + $results = []; + $startDate = new \DateTime($startMonth . '01'); + + for ($i = 0; $i < $months; $i++) { + $currentDate = clone $startDate; + $currentDate->modify("+{$i} month"); + $yearMonth = $currentDate->format('Ym'); + + try { + $result = self::createMonthTable($yearMonth); + $results[$yearMonth] = $result; + } catch (\Exception $e) { + $results[$yearMonth] = false; + } + } + + return $results; + } + + /** + * 检查并创建当前月份的表 + * @return bool + */ + public static function checkAndCreateCurrentMonthTable() + { + $currentMonth = date('Ym'); + return self::createMonthTable($currentMonth); + } +} \ No newline at end of file diff --git a/application/common/library/Snowflake.php b/application/common/library/Snowflake.php index 3bbe46a2..2e9e9280 100644 --- a/application/common/library/Snowflake.php +++ b/application/common/library/Snowflake.php @@ -1,110 +1,60 @@ workerId = $this->generateWorkerId(); - } - - /** - * 获取单例实例 - */ - public static function getInstance() - { - if (self::$instance === null) { - self::$instance = new self(); - } - return self::$instance; - } - - /** - * 生成工作机器ID(基于服务器IP) - */ - protected function generateWorkerId() - { - // 方法1:从配置文件中读取 - if (config('snowflake.worker_id')) { - return config('snowflake.worker_id') & self::MAX_WORKER_ID; + if ($workerId > self::MAX_WORKER_ID || $workerId < 0) { + throw new \Exception("worker Id can't be greater than " . self::MAX_WORKER_ID . " or less than 0"); } - // 方法2:基于服务器IP生成(推荐) - $serverIp = $_SERVER['SERVER_ADDR'] ?? '127.0.0.1'; - $ipParts = explode('.', $serverIp); - - // 使用IP后两段生成workerId - if (count($ipParts) >= 4) { - $workerId = ($ipParts[2] << 8) | $ipParts[3]; - } else { - // 如果是IPv6或特殊情况,使用随机数 - $workerId = mt_rand(0, self::MAX_WORKER_ID); + if ($datacenterId > self::MAX_DATACENTER_ID || $datacenterId < 0) { + throw new \Exception("datacenter Id can't be greater than " . self::MAX_DATACENTER_ID . " or less than 0"); } - return $workerId & self::MAX_WORKER_ID; + $this->workerId = $workerId; + $this->datacenterId = $datacenterId; } - /** - * 生成下一个ID - */ public function nextId() { - $timestamp = $this->getCurrentTimestamp(); + $timestamp = $this->timeGen(); - // 时钟回拨处理 if ($timestamp < $this->lastTimestamp) { $diff = $this->lastTimestamp - $timestamp; - throw new \Exception("时钟回拨了 {$diff} 毫秒"); + throw new \Exception("Clock moved backwards. Refusing to generate id for {$diff} milliseconds"); } - // 同一毫秒内生成多个ID if ($this->lastTimestamp == $timestamp) { - $this->sequence = ($this->sequence + 1) & self::SEQUENCE_MASK; + $this->sequence = ($this->sequence + 1) & self::MAX_SEQUENCE; if ($this->sequence == 0) { - // 序列号用尽,等待下一毫秒 - $timestamp = $this->waitNextMillis($this->lastTimestamp); + $timestamp = $this->tilNextMillis($this->lastTimestamp); } } else { $this->sequence = 0; @@ -112,58 +62,64 @@ class Snowflake $this->lastTimestamp = $timestamp; - // 组合生成ID - $id = (($timestamp - self::EPOCH) << self::TIMESTAMP_LEFT_SHIFT) - | ($this->workerId << self::WORKER_ID_SHIFT) - | $this->sequence; - - return (string)$id; + return (($timestamp - self::EPOCH) << self::TIMESTAMP_LEFT_SHIFT) | + ($this->datacenterId << self::DATACENTER_ID_SHIFT) | + ($this->workerId << self::WORKER_ID_SHIFT) | + $this->sequence; } - /** - * 批量生成ID - */ - public function nextIds($count) + protected function tilNextMillis($lastTimestamp) { - $ids = []; - for ($i = 0; $i < $count; $i++) { - $ids[] = $this->nextId(); - } - return $ids; - } - - /** - * 获取当前毫秒时间戳 - */ - protected function getCurrentTimestamp() - { - return (int)(microtime(true) * 1000); - } - - /** - * 等待到下一毫秒 - */ - protected function waitNextMillis($lastTimestamp) - { - $timestamp = $this->getCurrentTimestamp(); + $timestamp = $this->timeGen(); while ($timestamp <= $lastTimestamp) { - usleep(100); // 休眠100微秒 - $timestamp = $this->getCurrentTimestamp(); + $timestamp = $this->timeGen(); } return $timestamp; } + protected function timeGen() + { + return floor(microtime(true) * 1000); + } + /** * 解析雪花ID + * @param int $id + * @return array */ - public static function parse($id) + public static function parseId($id) { - $id = intval($id); + $binary = decbin($id); + $binary = str_pad($binary, 64, '0', STR_PAD_LEFT); + + $timestamp = bindec(substr($binary, 0, 42)); + $timestamp = $timestamp + self::EPOCH; + + $datacenterId = bindec(substr($binary, 42, 5)); + $workerId = bindec(substr($binary, 47, 5)); + $sequence = bindec(substr($binary, 52, 12)); + return [ - 'timestamp' => ($id >> self::TIMESTAMP_LEFT_SHIFT) + self::EPOCH, - 'worker_id' => ($id >> self::WORKER_ID_SHIFT) & self::MAX_WORKER_ID, - 'sequence' => $id & self::SEQUENCE_MASK, - 'generated_at' => date('Y-m-d H:i:s.v', (($id >> self::TIMESTAMP_LEFT_SHIFT) + self::EPOCH) / 1000) + 'timestamp' => $timestamp, + 'datacenterId' => $datacenterId, + 'workerId' => $workerId, + 'sequence' => $sequence ]; } + + /** + * 生成ID(单例模式) + * @return int + */ + public static function generate() + { + static $instance = null; + if (!$instance) { + // 从配置获取workerId和datacenterId + $workerId = config('snowflake.worker_id') ?: 1; + $datacenterId = config('snowflake.datacenter_id') ?: 1; + $instance = new self($workerId, $datacenterId); + } + return $instance->nextId(); + } } \ No newline at end of file diff --git a/application/common/model/GiveGiftBase.php b/application/common/model/GiveGiftBase.php new file mode 100644 index 00000000..8598f47d --- /dev/null +++ b/application/common/model/GiveGiftBase.php @@ -0,0 +1,462 @@ +error; + } + + /** + * 获取当前表名 + * @param int $timestamp 时间戳 + * @return string + */ + protected function getTableName($timestamp = null) + { + return GiftTableManager::getTableName($timestamp); + } + + /** + * 添加送礼记录(直接写入数据库) + * @param array $data 数据 + * @return int|false + */ + public function addGiftRecord($data) + { + try { + // 生成雪花ID + $data['id'] = Snowflake::generate(); + + // 设置时间 + if (empty($data['createtime'])) { + $data['createtime'] = time(); + } + $data['updatetime'] = time(); + + // 确保字段完整 + $defaults = [ + 'user_id' => 0, + 'gift_id' => 0, + 'gift_type' => 1, + 'number' => 0, + 'gift_user' => 0, + 'from_id' => 0, + 'pit_number' => 0, + 'total_price' => 0, + 'type' => 1, + 'from' => 1, + ]; + + $data = array_merge($defaults, $data); + + // 获取表名 + $tableName = $this->getTableName($data['createtime']); + + // 确保表存在 + $month = date('Ym', $data['createtime']); + GiftTableManager::createMonthTable($month); + + // 插入数据 + $result = Db::table($tableName)->insert($data); + + if ($result) { + Log::info("送礼记录写入成功,ID: {$data['id']}, 表: {$tableName}"); + return $data['id']; + } else { + $this->error = '写入数据库失败'; + Log::error("送礼记录写入失败: " . json_encode($data)); + return false; + } + } catch (\Exception $e) { + $this->error = $e->getMessage(); + Log::error("添加送礼记录异常: " . $e->getMessage()); + return false; + } + } + + /** + * 批量添加送礼记录 + * @param array $dataList 数据列表 + * @return int|false + */ + public function addGiftRecords($dataList) + { + try { + // 按月份分组 + $groupedData = []; + foreach ($dataList as $data) { + // 生成雪花ID + $data['id'] = Snowflake::generate(); + + // 设置时间 + if (empty($data['createtime'])) { + $data['createtime'] = time(); + } + $data['updatetime'] = time(); + + $month = date('Ym', $data['createtime']); + $tableName = 'fa_vs_give_gift_' . $month; + + // 确保表存在 + GiftTableManager::createMonthTable($month); + + if (!isset($groupedData[$tableName])) { + $groupedData[$tableName] = []; + } + + $groupedData[$tableName][] = $data; + } + + // 批量插入 + $total = 0; + foreach ($groupedData as $tableName => $data) { + $result = Db::table($tableName)->insertAll($data); + if ($result) { + $total += count($data); + } + } + + Log::info("批量送礼记录写入成功,总计: {$total} 条"); + return $total; + } catch (\Exception $e) { + $this->error = $e->getMessage(); + Log::error("批量添加送礼记录异常: " . $e->getMessage()); + return false; + } + } + + /** + * 查询送礼记录 + * @param array $where 查询条件 + * @param array $options 查询选项 + * @return array + */ + public function getGiftRecords($where = [], $options = []) + { + $defaultOptions = [ + 'fields' => '*', + 'order' => 'createtime desc', + 'page' => 1, + 'limit' => 20, + 'start_time'=> null, + 'end_time' => null, + ]; + + $options = array_merge($defaultOptions, $options); + + // 获取需要查询的表 + $tables = GiftTableManager::getTablesByTimeRange( + $options['start_time'], + $options['end_time'] + ); + + if (empty($tables)) { + return ['total' => 0, 'data' => [], 'page' => $options['page'], 'limit' => $options['limit']]; + } + + // 构建查询条件 + $queryConditions = $this->buildQueryConditions($where); + + // 多表查询 + return $this->unionQuery($tables, $queryConditions, $options); + } + + /** + * 构建查询条件 + * @param array $where + * @return array + */ + protected function buildQueryConditions($where) + { + $conditions = []; + + $fieldMap = [ + 'user_id' => 'user_id', + 'gift_user' => 'gift_user', + 'from_id' => 'from_id', + 'gift_id' => 'gift_id', + 'type' => 'type', + 'from' => 'from', + 'gift_type' => 'gift_type', + 'createtime' => 'createtime', + ]; + + foreach ($where as $key => $value) { + if (isset($fieldMap[$key])) { + if (is_array($value) && isset($value[0]) && in_array(strtolower($value[0]), ['>', '<', '>=', '<=', '<>', '!=', 'like', 'not like', 'between'])) { + $conditions[$fieldMap[$key]] = $value; + } else { + $conditions[$fieldMap[$key]] = ['=', $value]; + } + } + } + + return $conditions; + } + + /** + * 多表联合查询 + * @param array $tables + * @param array $conditions + * @param array $options + * @return array + */ + protected function unionQuery($tables, $conditions, $options) + { + $unionSql = ''; + $params = []; + + // 构建每个表的查询 + foreach ($tables as $table) { + $sql = "SELECT {$options['fields']} FROM `{$table}` WHERE 1=1"; + + foreach ($conditions as $field => $condition) { + if (is_array($condition)) { + if (count($condition) == 2) { + $operator = $condition[0]; + $value = $condition[1]; + $sql .= " AND `{$field}` {$operator} ?"; + $params[] = $value; + } elseif (count($condition) == 3 && strtolower($condition[0]) == 'between') { + $sql .= " AND `{$field}` BETWEEN ? AND ?"; + $params[] = $condition[1]; + $params[] = $condition[2]; + } + } + } + + // 时间范围 + if ($options['start_time']) { + $sql .= " AND createtime >= ?"; + $params[] = $options['start_time']; + } + + if ($options['end_time']) { + $sql .= " AND createtime <= ?"; + $params[] = $options['end_time']; + } + + if ($unionSql) { + $unionSql .= " UNION ALL "; + } + $unionSql .= "(" . $sql . ")"; + } + + // 计算总数 + $countSql = "SELECT COUNT(*) as total FROM ({$unionSql}) as tmp"; + try { + $totalResult = Db::query($countSql, $params); + $total = $totalResult[0]['total'] ?? 0; + } catch (\Exception $e) { + Log::error("查询送礼记录总数失败: " . $e->getMessage()); + $total = 0; + } + + // 分页查询 + $data = []; + if ($total > 0) { + $offset = ($options['page'] - 1) * $options['limit']; + $dataSql = "{$unionSql} ORDER BY {$options['order']} LIMIT {$offset}, {$options['limit']}"; + + try { + $data = Db::query($dataSql, $params); + } catch (\Exception $e) { + Log::error("查询送礼记录数据失败: " . $e->getMessage()); + $data = []; + } + } + + return [ + 'total' => $total, + 'data' => $data, + 'page' => $options['page'], + 'limit' => $options['limit'] + ]; + } + + /** + * 统计送礼数据 + * @param array $where 查询条件 + * @param array $options 统计选项 + * @return array + */ + public function getGiftStatistics($where = [], $options = []) + { + $defaultOptions = [ + 'group_by' => null, // 分组字段 + 'start_time' => null, + 'end_time' => null, + ]; + + $options = array_merge($defaultOptions, $options); + + // 获取需要查询的表 + $tables = GiftTableManager::getTablesByTimeRange( + $options['start_time'], + $options['end_time'] + ); + + if (empty($tables)) { + return []; + } + + // 构建查询条件 + $conditions = $this->buildQueryConditions($where); + + $unionSql = ''; + $params = []; + + // 构建统计SQL + $fields = 'SUM(number) as total_number, SUM(total_price) as total_price, COUNT(*) as total_count'; + if ($options['group_by']) { + $fields .= ", {$options['group_by']}"; + } + + foreach ($tables as $table) { + $sql = "SELECT {$fields} FROM `{$table}` WHERE 1=1"; + + foreach ($conditions as $field => $condition) { + if (is_array($condition) && count($condition) == 2) { + $operator = $condition[0]; + $value = $condition[1]; + $sql .= " AND `{$field}` {$operator} ?"; + $params[] = $value; + } + } + + // 时间范围 + if ($options['start_time']) { + $sql .= " AND createtime >= ?"; + $params[] = $options['start_time']; + } + + if ($options['end_time']) { + $sql .= " AND createtime <= ?"; + $params[] = $options['end_time']; + } + + if ($options['group_by']) { + $sql .= " GROUP BY {$options['group_by']}"; + } + + if ($unionSql) { + $unionSql .= " UNION ALL "; + } + $unionSql .= "(" . $sql . ")"; + } + + // 最终统计 + if ($options['group_by']) { + $finalSql = "SELECT {$options['group_by']}, + SUM(total_number) as total_number, + SUM(total_price) as total_price, + SUM(total_count) as total_count + FROM ({$unionSql}) as tmp + GROUP BY {$options['group_by']}"; + } else { + $finalSql = "SELECT SUM(total_number) as total_number, + SUM(total_price) as total_price, + SUM(total_count) as total_count + FROM ({$unionSql}) as tmp"; + } + + try { + $result = Db::query($finalSql, $params); + return $options['group_by'] ? $result : ($result[0] ?? []); + } catch (\Exception $e) { + Log::error("统计送礼数据失败: " . $e->getMessage()); + return []; + } + } + + /** + * 获取时间范围内的送礼趋势 + * @param int $startTime 开始时间 + * @param int $endTime 结束时间 + * @param string $interval 间隔 day, week, month + * @return array + */ + public function getTrendStatistics($startTime, $endTime, $interval = 'day') + { + $tables = GiftTableManager::getTablesByTimeRange($startTime, $endTime); + + if (empty($tables)) { + return []; + } + + $dateFormat = ''; + switch ($interval) { + case 'day': + $dateFormat = '%Y-%m-%d'; + break; + case 'week': + $dateFormat = '%Y-%u'; + break; + case 'month': + $dateFormat = '%Y-%m'; + break; + default: + $dateFormat = '%Y-%m-%d'; + } + + $unionSql = ''; + $params = []; + + foreach ($tables as $table) { + $sql = "SELECT + DATE_FORMAT(FROM_UNIXTIME(createtime), '{$dateFormat}') as date_group, + SUM(total_price) as total_price, + COUNT(*) as total_count, + COUNT(DISTINCT user_id) as user_count, + COUNT(DISTINCT gift_user) as receiver_count + FROM `{$table}` + WHERE createtime >= ? AND createtime <= ? + GROUP BY date_group"; + + if ($unionSql) { + $unionSql .= " UNION ALL "; + } + $unionSql .= "(" . $sql . ")"; + $params[] = $startTime; + $params[] = $endTime; + } + + $finalSql = "SELECT + date_group, + SUM(total_price) as total_price, + SUM(total_count) as total_count, + SUM(user_count) as user_count, + SUM(receiver_count) as receiver_count + FROM ({$unionSql}) as tmp + GROUP BY date_group + ORDER BY date_group"; + + try { + $result = Db::query($finalSql, $params); + return $result; + } catch (\Exception $e) { + Log::error("获取送礼趋势失败: " . $e->getMessage()); + return []; + } + } +} \ No newline at end of file diff --git a/application/extra/queue.php b/application/extra/queue.php index 19697035..bb10bd64 100644 --- a/application/extra/queue.php +++ b/application/extra/queue.php @@ -6,7 +6,7 @@ return [ 'host' => '127.0.0.1', // redis 主机ip 'port' => 6379, // redis 端口 'password' => '', // redis 密码 - 'select' => 0, // 使用哪一个 db,默认为 db0 + 'select' => 15, // 使用哪一个 db,默认为 db0 'timeout' => 0, // redis连接的超时时间 'persistent' => false, ]; diff --git a/application/extra/snowflake.php b/application/extra/snowflake.php new file mode 100644 index 00000000..1a3e94d9 --- /dev/null +++ b/application/extra/snowflake.php @@ -0,0 +1,5 @@ + 1, // 工作ID(1-31) + 'datacenter_id' => 1, // 数据中心ID(1-31) +]; \ No newline at end of file