Commit 113b0263 authored by 刘小敏's avatar 刘小敏

计划任务脚本 用户标签脚本 uvpv日统计脚本 pvuv失败重试脚本

parent 4c2eca16
......@@ -41,11 +41,7 @@ class Track extends Common
if (!isset($requestData['list']) || !is_array($requestData['list'])) {
$this->error('参数错误');
}
//print_r([
// 'data' => $requestData['list'],
// 'ip' => $this->request->ip(),
// 'timestamp' => time()
//]);
// 推入队列
$jobId = Queue::push('addons\shopro\job\TrackProcessJob@fire', [
'data' => $requestData['list'],
......@@ -60,22 +56,4 @@ class Track extends Common
}
}
/**
* 获取统计数据
*/
public function stats() {
$startDate = $this->request->param('start', date('Y-m-d', strtotime('-7 days')));
$endDate = $this->request->param('end', date('Y-m-d'));
// 参数验证
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $startDate) || !preg_match('/^\d{4}-\d{2}-\d{2}$/', $endDate)) {
$this->error('日期格式错误');
}
$stats = \app\admin\model\shopro\MiniProgramStats::whereBetween('date', [$startDate, $endDate])
->order('date ASC')
->select();
$this->success('获取成功', $stats);
}
}
\ No newline at end of file
......@@ -22,13 +22,10 @@ class TrackProcessJob
return;
}
// 1. 批量插入埋点数据
// 批量插入埋点数据
$this->insertTracks($trackData, $clientIp);
// 2. 更新统计数据
$this->updateStats();
// 3. 删除任务
// 删除任务
$job->delete();
} catch (\Exception $e) {
......@@ -72,35 +69,6 @@ class TrackProcessJob
}
/**
* 更新统计数据
*/
protected function updateStats() {
$date = date('Y-m-d');
// 统计UV(按openid去重)
$uv = Db::name('shopro_miniprogram_track')
->whereDate('create_time', $date)
->distinct(true)
->count('openid');
// 统计PV(页面浏览量)
$pv = Db::name('shopro_miniprogram_track')
->whereDate('create_time', $date)
->where('event_type', 'page_view')
->count();
// 更新或插入统计记录
Db::name('shopro_miniprogram_stats')->updateOrCreate(
['date' => $date],
[
'uv' => $uv,
'pv' => $pv,
'update_time' => time()
]
);
}
/**
* 保存失败数据
*/
protected function saveFailed($data, $error) {
......@@ -113,31 +81,4 @@ class TrackProcessJob
}
// 恢复失败数据的脚本
public function retryFailed()
{
$failedList = Db::name('shopro_track_failed')
->where('status', 0)
->select();
foreach ($failedList as $item) {
try {
$data = json_decode($item['data'], true);
// 重新推入队列
Queue::push('\addons\shopro\job\TrackProcessJob@fire', $data, 'shopro-high');
// 更新状态为已处理
Db::name('shopro_track_failed')
->where('id', $item['id'])
->update(['status' => 1, 'update_time' => time()]);
} catch (\Exception $e) {
// 更新状态为已放弃
Db::name('shopro_track_failed')
->where('id', $item['id'])
->update(['status' => 2, 'error' => $e->getMessage(), 'update_time' => time()]);
}
}
}
}
\ No newline at end of file
......@@ -17,4 +17,8 @@ return [
'app\admin\command\Min',
'app\admin\command\Addon',
'app\admin\command\Api',
'app\command\StatMemberTag',
'app\command\StatsDaily',
'app\command\TrackFailedRetry',
];
<?php
namespace app\command;
use think\console\Command;
use think\console\Input;
use think\console\Output;
use think\Log;
use think\Db;
// 继承ThinkPHP命令行控制器基类,符合FastAdmin框架规范
class StatMemberTag extends Command
{
/**
* 标签配置
*/
const TAGS = [
'active' => ['name' => '活跃用户', 'desc' => '一周内登录的用户'],
'valuable' => ['name' => '高价值用户', 'desc' => '一周内有消费记录的用户'],
'silent' => ['name' => '沉默用户', 'desc' => '一周未登录的用户'],
];
/**
* 每次处理的用户数量
*/
const BATCH_SIZE = 1000;
protected $output = null;
// 配置任务名称、描述(自定义,便于后续调用)
protected function configure()
{
$this->setName('shopro:stat-member-tag')->setDescription('每天凌晨两点执行的定时任务');
}
/**
* 自定义日志方法
* @param string $level 日志级别: info/warning/error
* @param string $message 日志内容
*/
protected function customLog($level, $message) {
$this->output->writeln($message);
custom_log($message, 'stat_member_tag', $level);
}
protected function execute(Input $input, Output $output) {
$this->output = $output;
$this->customLog('info', '========== 开始执行每日定时任务 ==========');
try {
// 用户标签更新(带异常处理)
$this->updateUserTags();
$this->customLog('info', '[' . date('Y-m-d H:i:s') . ']每日定时任务执行完成');
} catch (\Exception $e) {
$this->customLog('error', '[' . date('Y-m-d H:i:s') . '] 任务执行失败: ' . $e->getMessage() . ' | 堆栈: ' . $e->getTraceAsString());
}
}
/**
* 更新用户标签(带异常处理和去重)
*/
protected function updateUserTags() {
$this->customLog('info', '[' . date('Y-m-d H:i:s') . '] 开始更新用户标签');
$weekAgo = strtotime('-7 days');
try {
// 1. 先清空所有标签
Db::name('shopro_user_tag')->where('id', '>', 0)->delete();
$this->customLog('info', '已清空旧标签');
// 2. 处理活跃用户标签
$activeCount = $this->processActiveUsers($weekAgo);
$this->customLog('info', '活跃用户标签完成: ' . $activeCount . ' 人');
// 3. 处理高价值用户标签(去重)
$valuableCount = $this->processValuableUsers($weekAgo);
$this->customLog('info', '高价值用户标签完成: ' . $valuableCount . ' 人');
// 4. 处理沉默用户标签(去重)
$silentCount = $this->processSilentUsers($weekAgo);
$this->customLog('info', '沉默用户标签完成: ' . $silentCount . ' 人');
$this->customLog('info', '用户标签更新完成');
} catch (\Exception $e) {
$this->customLog('error', '用户标签更新失败: ' . $e->getMessage());
throw $e; // 重新抛出异常,让外层处理
}
}
/**
* 分页处理活跃用户
*/
protected function processActiveUsers($weekAgo) {
$count = 0;
$page = 1;
while (true) {
try {
$users = Db::name('user')
->where('logintime', '>=', $weekAgo)
->field('id')
->page($page, self::BATCH_SIZE)
->select();
if (empty($users)) {
break;
}
$userIds = array_column($users, 'id');
$this->batchInsertTags($userIds, 'active', self::TAGS['active']['name']);
$count += count($userIds);
$page++;
if ($page % 10 == 0) {
$this->customLog('info', '活跃用户处理中: ' . $count . ' 人');
}
} catch (\Exception $e) {
$this->customLog('error', '处理活跃用户失败(第' . $page . '页): ' . $e->getMessage());
throw $e;
}
}
return $count;
}
/**
* 分页处理高价值用户(去重)
*/
protected function processValuableUsers($weekAgo) {
$count = 0;
$page = 1;
$processedUsers = []; // 记录已处理的用户ID,用于去重
while (true) {
try {
$orders = Db::name('shopro_order')
->where('paid_time', '>=', $weekAgo)
->field('user_id')
->distinct(true)
->page($page, self::BATCH_SIZE)
->select();
if (empty($orders)) {
break;
}
// 去重处理
$userIds = [];
foreach ($orders as $order) {
$userId = $order['user_id'];
if (!in_array($userId, $processedUsers)) {
$userIds[] = $userId;
$processedUsers[] = $userId;
}
}
if (!empty($userIds)) {
$this->batchInsertTags($userIds, 'valuable', self::TAGS['valuable']['name']);
$count += count($userIds);
}
$page++;
if ($page % 10 == 0) {
$this->customLog('info', '高价值用户处理中: ' . $count . ' 人');
}
} catch (\Exception $e) {
$this->customLog('error', '处理高价值用户失败(第' . $page . '页): ' . $e->getMessage());
throw $e;
}
}
return $count;
}
/**
* 分页处理沉默用户(去重)
*/
protected function processSilentUsers($weekAgo) {
$count = 0;
$page = 1;
$processedUsers = []; // 记录已处理的用户ID
while (true) {
try {
$users = Db::name('user')
->where('logintime', '<', $weekAgo)
->where('logintime', '>', 0)
->field('id')
->page($page, self::BATCH_SIZE)
->select();
if (empty($users)) {
break;
}
// 去重处理
$userIds = [];
foreach ($users as $user) {
$userId = $user['id'];
if (!in_array($userId, $processedUsers)) {
$userIds[] = $userId;
$processedUsers[] = $userId;
}
}
if (!empty($userIds)) {
$this->batchInsertTags($userIds, 'silent', self::TAGS['silent']['name']);
$count += count($userIds);
}
$page++;
if ($page % 10 == 0) {
$this->customLog('info', '沉默用户处理中: ' . $count . ' 人');
}
} catch (\Exception $e) {
$this->customLog('error', '处理沉默用户失败(第' . $page . '页): ' . $e->getMessage());
throw $e;
}
}
return $count;
}
/**
* 批量插入标签(带去重)
*/
protected function batchInsertTags($userIds, $tagCode, $tagName) {
if (empty($userIds)) {
return;
}
try {
$data = [];
$now = time();
foreach ($userIds as $userId) {
$data[] = [
'user_id' => $userId,
'tag_code' => $tagCode,
'tag_name' => $tagName,
'create_time' => $now,
'update_time' => $now,
];
}
// 分批插入(每500条)
$batches = array_chunk($data, 500);
foreach ($batches as $batch) {
// 使用 INSERT IGNORE 去重(依赖唯一索引)
Db::name('shopro_user_tag')->insertAll($batch, true);
}
} catch (\Exception $e) {
$this->customLog('error', '批量插入标签失败: ' . $e->getMessage());
throw $e;
}
}
}
?>
\ No newline at end of file
<?php
namespace app\command;
use think\console\Command;
use think\console\Input;
use think\console\Output;
use think\Log;
use think\Db;
use think\Queue;
/**
* 每日统计任务 PV UV
*/
class StatsDaily extends Command
{
protected $output = null;
// 配置任务名称、描述(自定义,便于后续调用)
protected function configure()
{
$this->setName('shopro:stats-daily')->setDescription('每天凌晨1点半执行一次');
}
/**
* 自定义日志方法
* @param string $level 日志级别: info/warning/error
* @param string $message 日志内容
*/
protected function customLog($level, $message) {
$this->output->writeln($message);
custom_log($message, 'stat_member_tag', $level);
}
protected function execute(Input $input, Output $output) {
// 清理过期数据
$this->cleanExpiredData();
// 统计数据汇总
$this->summaryStats();
}
/**
* 清理过期数据
*/
protected function cleanExpiredData() {
try {
$expireDays = 90;
$expireTime = strtotime("-$expireDays days");
Db::name('shopro_miniprogram_track')
->where('create_time', '<', $expireTime)
->delete();
Db::name('shopro_miniprogram_track_failed')
->where('create_time', '<', $expireTime)
->delete();
$this->customLog('info', '清理过期数据完成');
} catch (\Exception $e) {
$this->customLog('error', '清理过期数据失败: ' . $e->getMessage());
// 清理失败不中断其他任务
}
}
/**
* 统计数据汇总
*/
protected function summaryStats() {
try {
$date = date('Y-m-d', strtotime('-1 day'));
$yesterday_begin = strtotime($date . ' 00:00:00');
$yesterday_end = strtotime($date . ' 23:59:59');
$uv = Db::name('shopro_miniprogram_track')
->where('create_time', '>', $yesterday_begin)
->where('create_time', '<', $yesterday_end)
->distinct(true)
->count('ip');
$pv = Db::name('shopro_miniprogram_track')
->where('create_time', '>', $yesterday_begin)
->where('create_time', '<', $yesterday_end)
->where('event_type', 'page_view')
->count();
$row = Db::name('shopro_miniprogram_stats')->where('date', $date)->find();
if (!$row) {
// 新增记录
Db::name('shopro_miniprogram_stats')->insert(
[
'date' => $date,
'uv' => $uv,
'pv' => $pv,
'update_time' => time()
]
);
} else {
// 更新记录
Db::name('shopro_miniprogram_stats')->where('date', $date)->update(
[
'uv' => $uv,
'pv' => $pv,
'update_time' => time()
]
);
}
$this->customLog('info', '统计数据汇总完成,日期: ' . $date . ', UV: ' . $uv . ', PV: ' . $pv);
} catch (\Exception $e) {
$this->customLog('error', '统计数据汇总失败: ' . $e->getMessage());
// 统计失败不中断其他任务
}
}
}
?>
\ No newline at end of file
<?php
namespace app\command;
use think\console\Command;
use think\console\Input;
use think\console\Output;
use think\Log;
use think\Db;
use think\Queue;
/**
* 重试失败的埋点数据 PV UV
*/
class TrackFailedRetry extends Command
{
protected $output = null;
// 配置任务名称、描述(自定义,便于后续调用)
protected function configure()
{
$this->setName('shopro:track-failed-retry')->setDescription('每1个小时执行一次');
}
/**
* 自定义日志方法
* @param string $level 日志级别: info/warning/error
* @param string $message 日志内容
*/
protected function customLog($level, $message) {
$this->output->writeln($message);
custom_log($message, 'stat_member_tag', $level);
}
protected function execute(Input $input, Output $output) {
$this->retryFailed();
}
/**
* 分页批量重试失败数据
*/
public function retryFailed()
{
// 每次处理的数量
$batchSize = 500;
$page = 1;
$totalProcessed = 0;
$totalSuccess = 0;
$totalFailed = 0;
while (true) {
// 分页查询失败数据
$failedList = Db::name('shopro_miniprogram_track_failed')
->where('status', 0)
->page($page, $batchSize)
->select();
if (empty($failedList)) {
break;
}
foreach ($failedList as $item) {
try {
$data = json_decode($item['data'], true);
// 验证数据格式
if (empty($data) || !isset($data['data'])) {
throw new \Exception('数据格式错误');
}
// 重新推入队列
Queue::push('\addons\shopro\job\TrackProcessJob@fire', $data['data'], 'shopro-high');
// 更新状态为已处理
Db::name('shopro_miniprogram_track_failed')
->where('id', $item['id'])
->update(['status' => 1, 'update_time' => time()]);
$totalSuccess++;
} catch (\Exception $e) {
// 更新状态为已放弃
Db::name('shopro_miniprogram_track_failed')
->where('id', $item['id'])
->update([
'status' => 2,
'error' => $e->getMessage(),
'update_time' => time(),
'attempts' => Db::raw('attempts + 1')
]);
$totalFailed++;
}
$totalProcessed++;
}
// 输出进度信息
$this->customLog('info', "已处理: {$totalProcessed} | 成功: {$totalSuccess} | 失败: {$totalFailed}");;
$page++;
}
$this->customLog('info', "重试完成!总计处理: {$totalProcessed} | 成功: {$totalSuccess} | 失败: {$totalFailed}");
}
}
?>
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment