This commit is contained in:
2025-12-30 11:37:21 +08:00
parent 6dc429ec36
commit f8e7ff1d52
8 changed files with 1286 additions and 131 deletions

View File

@@ -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特效

View 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
];
}
}

View 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;
}
}
}

View 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);
}
}

View File

@@ -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();
}
}

View 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 [];
}
}
}

View File

@@ -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,
];

View File

@@ -0,0 +1,5 @@
<?php
return [
'worker_id' => 1, // 工作ID1-31
'datacenter_id' => 1, // 数据中心ID1-31
];