Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
L
loveisland
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
刘小敏
loveisland
Commits
113b0263
Commit
113b0263
authored
May 20, 2026
by
刘小敏
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
计划任务脚本 用户标签脚本 uvpv日统计脚本 pvuv失败重试脚本
parent
4c2eca16
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
516 additions
and
86 deletions
+516
-86
Track.php
addons/shopro/controller/api/Track.php
+1
-24
TrackProcessJob.php
addons/shopro/job/TrackProcessJob.php
+2
-62
command.php
application/command.php
+4
-0
StatMemberTag.php
application/command/StatMemberTag.php
+273
-0
StatsDaily.php
application/command/StatsDaily.php
+123
-0
TrackFailedRetry.php
application/command/TrackFailedRetry.php
+113
-0
No files found.
addons/shopro/controller/api/Track.php
View file @
113b0263
...
...
@@ -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
addons/shopro/job/TrackProcessJob.php
View file @
113b0263
...
...
@@ -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
application/command.php
View file @
113b0263
...
...
@@ -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'
,
];
application/command/StatMemberTag.php
0 → 100644
View file @
113b0263
<?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
application/command/StatsDaily.php
0 → 100644
View file @
113b0263
<?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
application/command/TrackFailedRetry.php
0 → 100644
View file @
113b0263
<?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
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment