分表
This commit is contained in:
@@ -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特效
|
||||
|
||||
255
application/common/library/GiftDataMigrator.php
Normal file
255
application/common/library/GiftDataMigrator.php
Normal file
@@ -0,0 +1,255 @@
|
||||
<?php
|
||||
// application/common/library/GiftDataMigrator.php
|
||||
|
||||
namespace app\common\library;
|
||||
|
||||
use think\Db;
|
||||
use think\Exception;
|
||||
use think\facade\Log;
|
||||
|
||||
class GiftDataMigrator
|
||||
{
|
||||
/**
|
||||
* 迁移单个月份的数据
|
||||
* @param string $yearMonth 年月 202401
|
||||
* @param int $batchSize 每批数量
|
||||
* @param int $startId 起始ID
|
||||
* @return array 迁移结果
|
||||
*/
|
||||
public static function migrateMonthData($yearMonth, $batchSize = 1000, $startId = 0)
|
||||
{
|
||||
// 确保目标表存在
|
||||
GiftTableManager::createMonthTable($yearMonth);
|
||||
|
||||
$targetTable = 'fa_vs_give_gift_' . $yearMonth;
|
||||
$startTime = strtotime($yearMonth . '01 00:00:00');
|
||||
$endTime = strtotime(date('Y-m-01', strtotime('+1 month', $startTime)));
|
||||
|
||||
$totalMigrated = 0;
|
||||
$lastId = $startId;
|
||||
$hasMore = true;
|
||||
|
||||
Log::info("开始迁移 {$yearMonth} 数据,起始ID: {$startId}");
|
||||
|
||||
while ($hasMore) {
|
||||
try {
|
||||
// 查询原始表数据
|
||||
$records = Db::name('vs_give_gift')
|
||||
->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
|
||||
];
|
||||
}
|
||||
}
|
||||
251
application/common/library/GiftQueue.php
Normal file
251
application/common/library/GiftQueue.php
Normal file
@@ -0,0 +1,251 @@
|
||||
<?php
|
||||
// application/common/library/GiftQueue.php
|
||||
|
||||
namespace app\common\library;
|
||||
|
||||
use think\Cache;
|
||||
use think\Db;
|
||||
use think\facade\Log;
|
||||
use app\common\model\GiveGiftBase;
|
||||
|
||||
class GiftQueue
|
||||
{
|
||||
const QUEUE_KEY = 'gift:queue';
|
||||
const QUEUE_FAILED_KEY = 'gift:queue:failed';
|
||||
const MAX_RETRY = 3;
|
||||
|
||||
/**
|
||||
* 添加送礼记录到队列
|
||||
* @param array $giftData 送礼数据
|
||||
* @return bool
|
||||
*/
|
||||
public static function push($giftData)
|
||||
{
|
||||
try {
|
||||
// 验证必要字段
|
||||
if (empty($giftData['user_id']) || empty($giftData['gift_user']) || empty($giftData['from_id'])) {
|
||||
Log::error('送礼队列数据不完整: ' . json_encode($giftData));
|
||||
return false;
|
||||
}
|
||||
|
||||
// 添加队列元数据
|
||||
$queueData = [
|
||||
'data' => $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;
|
||||
}
|
||||
}
|
||||
}
|
||||
221
application/common/library/GiftTableManager.php
Normal file
221
application/common/library/GiftTableManager.php
Normal file
@@ -0,0 +1,221 @@
|
||||
<?php
|
||||
// application/common/library/GiftTableManager.php
|
||||
|
||||
namespace app\common\library;
|
||||
|
||||
use think\Db;
|
||||
use think\Exception;
|
||||
use think\facade\Log;
|
||||
|
||||
class GiftTableManager
|
||||
{
|
||||
/**
|
||||
* 根据时间戳获取分表名
|
||||
* @param int $timestamp 时间戳
|
||||
* @return string 表名
|
||||
*/
|
||||
public static function getTableName($timestamp = null)
|
||||
{
|
||||
$timestamp = $timestamp ?: time();
|
||||
$month = date('Ym', $timestamp);
|
||||
return 'fa_vs_give_gift_' . $month;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建月份分表(纯PHP实现,不使用存储过程)
|
||||
* @param string $yearMonth 年月格式:202401
|
||||
* @return bool
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function createMonthTable($yearMonth)
|
||||
{
|
||||
if (!preg_match('/^\d{6}$/', $yearMonth)) {
|
||||
throw new Exception('年月格式错误');
|
||||
}
|
||||
|
||||
$tableName = 'fa_vs_give_gift_' . $yearMonth;
|
||||
|
||||
// 检查表是否已存在
|
||||
try {
|
||||
$checkSql = "SHOW TABLES LIKE '{$tableName}'";
|
||||
$exists = Db::query($checkSql);
|
||||
if ($exists) {
|
||||
return true;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// 表不存在,继续创建
|
||||
}
|
||||
|
||||
// 创建分表SQL
|
||||
$sql = "
|
||||
CREATE TABLE IF NOT EXISTS `{$tableName}` (
|
||||
`id` bigint NOT NULL COMMENT '雪花ID',
|
||||
`user_id` int NOT NULL DEFAULT '0' COMMENT '用户id 送礼物的人',
|
||||
`gift_id` int NOT NULL DEFAULT '0' COMMENT '礼物id',
|
||||
`gift_type` tinyint(1) NOT NULL DEFAULT '1' COMMENT '礼物类型,1-正常礼物,2-盲盒开出来的,3-签约金币',
|
||||
`number` int unsigned NOT NULL DEFAULT '0' COMMENT '礼物数量',
|
||||
`gift_user` int NOT NULL DEFAULT '0' COMMENT '收礼物的人',
|
||||
`from_id` int NOT NULL DEFAULT '0' COMMENT '来源ID,(房间ID)',
|
||||
`pit_number` int NOT NULL DEFAULT '0' COMMENT '麦位编号(废弃)',
|
||||
`total_price` decimal(16,2) NOT NULL COMMENT '礼物总价值',
|
||||
`type` int NOT NULL DEFAULT '1' COMMENT '1金币购买 2送背包礼物',
|
||||
`from` int NOT NULL DEFAULT '1' COMMENT '1私聊送礼物 2语聊房送礼 3视频直播房送礼(暂时不用) 4动态打赏 6私密房间送礼',
|
||||
`createtime` bigint NOT NULL COMMENT '创建时间(秒时间戳)',
|
||||
`updatetime` bigint DEFAULT NULL COMMENT '更新时间(秒时间戳)',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_createtime` (`createtime`) USING BTREE,
|
||||
KEY `idx_user_id` (`user_id`) USING BTREE,
|
||||
KEY `idx_gift_user` (`gift_user`) USING BTREE,
|
||||
KEY `idx_from_id` (`from_id`) USING BTREE,
|
||||
KEY `idx_room_user_time` (`from_id`, `user_id`, `createtime`) USING BTREE,
|
||||
KEY `idx_room_receiver_time` (`from_id`, `gift_user`, `createtime`) USING BTREE,
|
||||
KEY `idx_user_time` (`user_id`, `createtime`) USING BTREE,
|
||||
KEY `idx_receiver_time` (`gift_user`, `createtime`) USING BTREE,
|
||||
KEY `idx_combined_search` (`from_id`, `user_id`, `gift_user`, `createtime`) USING BTREE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户送礼表_{$yearMonth}'
|
||||
";
|
||||
|
||||
try {
|
||||
Db::execute($sql);
|
||||
|
||||
// 计算时间范围
|
||||
$startTime = strtotime($yearMonth . '01 00:00:00');
|
||||
$endTime = strtotime(date('Y-m-01', strtotime('+1 month', $startTime)));
|
||||
|
||||
// 记录分表信息
|
||||
$tableInfo = Db::name('vs_give_gift_tables')
|
||||
->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);
|
||||
}
|
||||
}
|
||||
@@ -1,110 +1,60 @@
|
||||
<?php
|
||||
// application/common/library/Snowflake.php
|
||||
|
||||
namespace app\common\library;
|
||||
|
||||
/**
|
||||
* 雪花算法ID生成器
|
||||
* 不依赖数据库,通过配置或自动生成workerId
|
||||
* 64位ID结构:0(1位符号位) + 41位时间戳 + 10位工作机器ID + 12位序列号
|
||||
*/
|
||||
class Snowflake
|
||||
{
|
||||
// 起始时间戳(2023-01-01 00:00:00),可使用到2089年
|
||||
const EPOCH = 1672531200000;
|
||||
// 起始时间戳(2023-01-01)
|
||||
const EPOCH = 1672502400000;
|
||||
|
||||
// 工作机器ID位数
|
||||
const WORKER_ID_BITS = 10;
|
||||
|
||||
// 序列号位数
|
||||
// 机器ID位数
|
||||
const WORKER_ID_BITS = 5;
|
||||
const DATACENTER_ID_BITS = 5;
|
||||
const SEQUENCE_BITS = 12;
|
||||
|
||||
// 最大工作机器ID
|
||||
const MAX_WORKER_ID = (1 << self::WORKER_ID_BITS) - 1;
|
||||
// 最大值
|
||||
const MAX_WORKER_ID = -1 ^ (-1 << self::WORKER_ID_BITS);
|
||||
const MAX_DATACENTER_ID = -1 ^ (-1 << self::DATACENTER_ID_BITS);
|
||||
const MAX_SEQUENCE = -1 ^ (-1 << self::SEQUENCE_BITS);
|
||||
|
||||
// 序列号掩码
|
||||
const SEQUENCE_MASK = (1 << self::SEQUENCE_BITS) - 1;
|
||||
|
||||
// 工作机器ID左移位数
|
||||
// 移位
|
||||
const WORKER_ID_SHIFT = self::SEQUENCE_BITS;
|
||||
const DATACENTER_ID_SHIFT = self::SEQUENCE_BITS + self::WORKER_ID_BITS;
|
||||
const TIMESTAMP_LEFT_SHIFT = self::SEQUENCE_BITS + self::WORKER_ID_BITS + self::DATACENTER_ID_BITS;
|
||||
|
||||
// 时间戳左移位数
|
||||
const TIMESTAMP_LEFT_SHIFT = self::SEQUENCE_BITS + self::WORKER_ID_BITS;
|
||||
|
||||
// 工作机器ID(通过服务器IP生成)
|
||||
protected $workerId;
|
||||
|
||||
// 序列号
|
||||
protected $datacenterId;
|
||||
protected $sequence = 0;
|
||||
|
||||
// 上次生成ID的时间戳
|
||||
protected $lastTimestamp = -1;
|
||||
|
||||
// 单例实例
|
||||
protected static $instance = null;
|
||||
|
||||
/**
|
||||
* 构造函数私有化,单例模式
|
||||
*/
|
||||
private function __construct()
|
||||
public function __construct($workerId = 1, $datacenterId = 1)
|
||||
{
|
||||
$this->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();
|
||||
}
|
||||
}
|
||||
462
application/common/model/GiveGiftBase.php
Normal file
462
application/common/model/GiveGiftBase.php
Normal file
@@ -0,0 +1,462 @@
|
||||
<?php
|
||||
// application/common/model/GiveGiftBase.php
|
||||
|
||||
namespace app\common\model;
|
||||
|
||||
use think\Model;
|
||||
use think\Db;
|
||||
use think\facade\Log;
|
||||
use app\common\library\GiftTableManager;
|
||||
use app\common\library\Snowflake;
|
||||
|
||||
class GiveGiftBase extends Model
|
||||
{
|
||||
// 关闭自动时间戳
|
||||
protected $autoWriteTimestamp = false;
|
||||
|
||||
// 错误信息
|
||||
protected $error = '';
|
||||
|
||||
/**
|
||||
* 获取错误信息
|
||||
* @return string
|
||||
*/
|
||||
public function getError()
|
||||
{
|
||||
return $this->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 [];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
];
|
||||
|
||||
5
application/extra/snowflake.php
Normal file
5
application/extra/snowflake.php
Normal file
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
return [
|
||||
'worker_id' => 1, // 工作ID(1-31)
|
||||
'datacenter_id' => 1, // 数据中心ID(1-31)
|
||||
];
|
||||
Reference in New Issue
Block a user