苹果cms10的原本的重名视频只能删除。所以就做了个可以合并播放链接的功能。本功能只适用于苹果cms10.
| 苹果cms10程序版本 | 2023.1000.3051 |
✨ 新增文件(1个)
application/admin/view/vod/merge_repeat.html
✏️ 修改文件(2个)
application/admin/common/auth.php
application/admin/controller/Vod.php
步骤1.在“application/admin/view/vod”新建文件,名称为“merge_repeat.html”。
文件内容;
{include file="../../../application/admin/view/public/head" /}
<style>
.stats-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #fff;
padding: 25px;
border-radius: 8px;
margin-bottom: 20px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}
.stats-card h2 {
margin: 0 0 15px 0;
font-size: 24px;
font-weight: normal;
}
.stats-row {
display: flex;
gap: 30px;
margin-top: 15px;
}
.stat-item {
flex: 1;
}
.stat-value {
font-size: 36px;
font-weight: bold;
margin-bottom: 5px;
}
.stat-label {
font-size: 14px;
opacity: 0.9;
}
.tips-box {
background: #fff3cd;
border-left: 4px solid #ffc107;
padding: 15px 20px;
margin-bottom: 20px;
border-radius: 4px;
}
.tips-box h4 {
margin: 0 0 10px 0;
color: #856404;
}
.tips-box p {
margin: 5px 0;
color: #856404;
font-size: 13px;
}
.strategy-box {
background: #fff;
padding: 20px;
border-radius: 8px;
margin-bottom: 20px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.progress-box {
background: #fff;
padding: 20px;
border-radius: 8px;
display: none;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.progress-bar-container {
width: 100%;
height: 30px;
background: #f0f0f0;
border-radius: 15px;
overflow: hidden;
margin: 15px 0;
}
.progress-bar {
height: 100%;
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
width: 0%;
transition: width 0.3s;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-weight: bold;
}
.result-box {
background: #f8f9fa;
padding: 15px;
border-radius: 4px;
margin-top: 15px;
max-height: 300px;
overflow-y: auto;
}
.result-item {
padding: 8px 12px;
background: #fff;
margin-bottom: 8px;
border-radius: 4px;
border-left: 3px solid #67c23a;
}
.strategy-card {
border: 2px solid #e0e0e0;
padding: 15px;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s;
margin-bottom: 15px;
}
.strategy-card:hover {
border-color: #667eea;
background: #f8f9fa;
}
.strategy-card.active {
border-color: #667eea;
background: #ecf5ff;
}
.strategy-card h4 {
margin: 0 0 8px 0;
color: #333;
}
.strategy-card p {
margin: 0;
color: #666;
font-size: 13px;
}
</style>
<div class="page-container p15">
<div class="stats-card">
<h2><i class="layui-icon layui-icon-template-1"></i> 重名视频统计(zzzypro.com制作)</h2>
<div class="stats-row">
<div class="stat-item">
<div class="stat-value">{$repeat_names_count}</div>
<div class="stat-label">重名视频组数</div>
</div>
<div class="stat-item">
<div class="stat-value">{$total_repeat_count}</div>
<div class="stat-label">重名视频总数</div>
</div>
<div class="stat-item">
<div class="stat-value">{$total_repeat_count - $repeat_names_count}</div>
<div class="stat-label">可清理数量</div>
</div>
</div>
</div>
<div class="tips-box">
<h4>⚠️ 重要提示</h4>
<p>1. 本功能会将重名视频合并为一条数据,并将播放链接合并到保留的视频中</p>
<p>2. 采用分批异步处理方式,每批处理5组重名视频,防止数据库卡死</p>
<p>3. 合并后会累加点击量,不会丢失播放链接数据</p>
<p>4. <strong style="color: #d32f2f;">操作不可逆,建议先备份数据库!</strong></p>
</div>
{if $repeat_names_count == 0}
<div style="text-align: center; padding: 80px 0; background: #fff; border-radius: 8px;">
<i class="layui-icon layui-icon-ok-circle" style="font-size: 80px; color: #67c23a;"></i>
<h3 style="margin-top: 20px; color: #666;">太棒了!没有发现重名视频</h3>
<p style="color: #999;">您的视频库非常整洁</p>
</div>
{else/}
<div style="text-align: right; margin-bottom: 10px;">
<button type="button" class="layui-btn layui-btn-sm layui-btn-danger" onclick="manualClearCache()">
<i class="layui-icon layui-icon-delete"></i> 清除缓存释放内存
</button>
</div>
<div class="strategy-box">
<h3 style="margin: 0 0 15px 0;">📋 选择合并策略</h3>
<p style="color: #666; margin-bottom: 15px;">选择保留哪条重名视频的策略:</p>
<div class="strategy-card active" data-strategy="newest">
<h4><i class="layui-icon layui-icon-time" style="color: #409eff;"></i> 保留最新的视频(推荐)</h4>
<p>根据更新时间(vod_time),保留最新的视频数据</p>
</div>
<div class="strategy-card" data-strategy="oldest">
<h4><i class="layui-icon layui-icon-date" style="color: #e6a23c;"></i> 保留最旧的视频</h4>
<p>根据添加时间(vod_time_add),保留最早添加的视频数据</p>
</div>
<div class="strategy-card" data-strategy="most_hits">
<h4><i class="layui-icon layui-icon-fire" style="color: #f56c6c;"></i> 保留点击量最高的视频</h4>
<p>根据点击量(vod_hits),保留最受欢迎的视频数据</p>
</div>
<div style="margin-top: 20px; padding: 15px; background: #f8f9fa; border-radius: 4px;">
<h4 style="margin: 0 0 15px 0; font-size: 14px;">⚙️ 合并选项</h4>
<div class="layui-form">
<div class="layui-form-item" style="margin-bottom: 15px;">
<input type="checkbox" id="deduplicate" name="deduplicate" lay-skin="switch" lay-text="去重|保留" lay-filter="deduplicate" checked>
<label for="deduplicate" style="margin-left: 10px; color: #666;">
播放源去重
<span style="font-size: 12px; color: #999;">(开启后相同播放源只保留一个)</span>
</label>
</div>
<div class="layui-form-item" style="margin-bottom: 0;">
<label style="color: #666; margin-bottom: 8px; display: block;">
<i class="layui-icon layui-icon-senior" style="color:#409eff;"></i>
缓存策略:
</label>
<input type="radio" name="cache_mode" value="high" title="内存充足模式" lay-filter="cache_mode">
<input type="radio" name="cache_mode" value="balance" title="平衡模式(推荐)" lay-filter="cache_mode" checked>
<input type="radio" name="cache_mode" value="low" title="内存紧张模式" lay-filter="cache_mode">
</div>
</div>
<div style="margin-top: 15px; padding: 12px; background: #fff; border-radius: 4px; border-left: 3px solid #409eff;">
<table style="width: 100%; font-size: 12px; color: #666;">
<tr style="background: #f5f5f5;">
<th style="padding: 8px; text-align: left;">策略</th>
<th style="padding: 8px;">缓存分段</th>
<th style="padding: 8px;">每批处理</th>
<th style="padding: 8px;">内存占用</th>
<th style="padding: 8px;">2万组耗时</th>
</tr>
<tr id="mode-high">
<td style="padding: 8px;"><strong>内存充足</strong></td>
<td style="padding: 8px; text-align: center;">10000组/次</td>
<td style="padding: 8px; text-align: center;">30组/批</td>
<td style="padding: 8px; text-align: center;">~30MB</td>
<td style="padding: 8px; text-align: center;"><span style="color:#67c23a;font-weight:bold;">5分钟</span></td>
</tr>
<tr id="mode-balance" style="background: #e8f5e9;">
<td style="padding: 8px;"><strong>平衡模式(推荐)</strong></td>
<td style="padding: 8px; text-align: center;">5000组/次</td>
<td style="padding: 8px; text-align: center;">20组/批</td>
<td style="padding: 8px; text-align: center;">~15MB</td>
<td style="padding: 8px; text-align: center;"><span style="color:#e6a23c;font-weight:bold;">6分钟</span></td>
</tr>
<tr id="mode-low">
<td style="padding: 8px;"><strong>内存紧张</strong></td>
<td style="padding: 8px; text-align: center;">2000组/次</td>
<td style="padding: 8px; text-align: center;">10组/批</td>
<td style="padding: 8px; text-align: center;">~6MB</td>
<td style="padding: 8px; text-align: center;"><span style="color:#f56c6c;font-weight:bold;">10分钟</span></td>
</tr>
</table>
</div>
<p style="margin: 10px 0 0 0; font-size: 12px; color: #999;">
💡 采用<strong>分段缓存</strong>技术:先查询数据库缓存到内存,再分批处理,极大减少数据库查询次数
</p>
</div>
<div style="margin-top: 20px; text-align: center;">
<button type="button" id="startMergeBtn" class="layui-btn layui-btn-lg layui-btn-normal">
<i class="layui-icon layui-icon-play"></i> 开始合并处理
</button>
</div>
</div>
<div class="progress-box" id="progressBox">
<h3 style="margin: 0 0 15px 0;"><i class="layui-icon layui-icon-loading layui-anim layui-anim-rotate layui-anim-loop"></i> 正在处理中...</h3>
<div class="progress-bar-container">
<div class="progress-bar" id="progressBar">0%</div>
</div>
<div style="display: flex; justify-content: space-between; color: #666; font-size: 13px;">
<span>已处理:<strong id="processedCount">0</strong> 组</span>
<span>已删除:<strong id="deletedCount">0</strong> 条</span>
<span>剩余:<strong id="remainingCount">{$repeat_names_count}</strong> 组</span>
<span>内存占用:<strong id="memoryUsed" style="color:#409eff;">0 MB</strong></span>
</div>
<div class="result-box" id="resultBox"></div>
<div style="margin-top: 15px; text-align: center;">
<button type="button" id="stopBtn" class="layui-btn layui-btn-danger">
<i class="layui-icon layui-icon-pause"></i> 停止处理
</button>
</div>
</div>
{/if}
{if $repeat_names_count > 0}
<div style="background: #fff; padding: 20px; border-radius: 8px; margin-top: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.1);">
<h3 style="margin: 0 0 15px 0; padding-bottom: 10px; border-bottom: 2px solid #f0f0f0;">
<i class="layui-icon layui-icon-template-1"></i> 重名视频列表
<span style="float: right; font-size: 14px; color: #999;">共 {$total} 条数据</span>
</h3>
<form class="layui-form" action="" method="post" id="form">
<table class="layui-table" lay-skin="line">
<thead>
<tr>
<th width="25"><input type="checkbox" lay-skin="primary" lay-filter="allChoose"></th>
<th width="40">ID</th>
<th>视频名称</th>
<th width="50">点击</th>
<th width="50">周点击</th>
<th width="40">评分</th>
<th width="30">等级</th>
<th width="80">播放器</th>
<th width="120">更新时间</th>
<th width="150">操作</th>
</tr>
</thead>
<tbody>
{volist name="list" id="vo"}
<tr>
<td><input type="checkbox" name="ids[]" value="{$vo.vod_id}" class="layui-checkbox checkbox-ids" lay-skin="primary"></td>
<td>{$vo.vod_id}</td>
<td style="text-align: left;">
[{$vo.type.type_name}]
<a target="_blank" class="layui-badge-rim" href="{:mac_url_vod_detail($vo)}" style="color: #409eff;">
{$vo.vod_name|mac_filter_xss|mac_restore_htmlfilter}
</a>
{if condition="$vo.vod_status eq 0"}
<span class="layui-badge">未审核</span>
{/if}
{if condition="$vo.vod_lock eq 1"}
<span class="layui-badge layui-bg-orange">锁定</span>
{/if}
{if condition="$vo.vod_remarks neq ''"}
<span class="layui-badge layui-bg-cyan">{$vo.vod_remarks}</span>
{/if}
</td>
<td>{$vo.vod_hits}</td>
<td>{$vo.vod_hits_week}</td>
<td>{$vo.vod_score}</td>
<td>
<span class="layui-badge layui-bg-orange">{$vo.vod_level}</span>
</td>
<td>
<span title="{$vo['vod_play_from']|str_replace='$$$',',',###}" style="font-size: 12px;">
{$vo['vod_play_from']|str_replace='$$$',',',###|mb_substr=0,10,'utf-8'}
</span>
</td>
<td>
{$vo.vod_time|date='Y-m-d H:i',###}
</td>
<td>
<a class="layui-btn layui-btn-xs layui-btn-normal" target="_blank" href="{:url('info?id='.$vo['vod_id'])}">
<i class="layui-icon layui-icon-edit"></i> 编辑
</a>
<a class="layui-btn layui-btn-xs layui-btn-danger j-tr-del" data-href="{:url('del?ids='.$vo['vod_id'])}" href="javascript:;">
<i class="layui-icon layui-icon-delete"></i> 删除
</a>
</td>
</tr>
{/volist}
</tbody>
</table>
<div id="pages" class="center"></div>
</form>
</div>
{/if}
</div>
{include file="../../../application/admin/view/public/foot" /}
<script type="text/javascript">
var curUrl="{:url('vod/mergeRepeat',$param)}";
layui.use(['layer', 'laypage', 'form'], function(){
var layer = layui.layer,
laypage = layui.laypage,
form = layui.form,
$ = layui.jquery;
var selectedStrategy = 'newest'; // 默认策略
var deduplicate = true; // 默认开启去重
var cacheMode = 'balance'; // 默认平衡模式(更稳定)
var chunkSize = 5000; // 每次缓存多少组
var batchSize = 20; // 每批处理数量
var batchDelay = 50; // 批次延迟(ms)
var isProcessing = false;
var shouldStop = false;
var totalGroups = parseInt('{$repeat_names_count}');
var processedGroups = 0;
var totalDeleted = 0;
var currentRound = 0; // 当前缓存轮次
$('.strategy-card').click(function(){
if(isProcessing) return;
$('.strategy-card').removeClass('active');
$(this).addClass('active');
selectedStrategy = $(this).data('strategy');
});
form.on('switch(deduplicate)', function(data){
deduplicate = data.elem.checked;
console.log('去重开关:', deduplicate ? '开启' : '关闭');
});
form.on('radio(cache_mode)', function(data){
cacheMode = data.value;
$('tr[id^="mode-"]').css('background', '');
$('#mode-' + cacheMode).css('background', '#e8f5e9');
switch(cacheMode){
case 'high':
chunkSize = 10000;
batchSize = 30; // 降低到30,防止单批数据过多
batchDelay = 50;
break;
case 'balance':
chunkSize = 5000;
batchSize = 20; // 降低到20,更稳定
batchDelay = 50;
break;
case 'low':
chunkSize = 2000;
batchSize = 10; // 降低到10,最安全
batchDelay = 100;
break;
}
console.log('缓存策略:', cacheMode, '分段:', chunkSize, '批量:', batchSize, '延迟:', batchDelay + 'ms');
});
$('#startMergeBtn').click(function(){
if(isProcessing) return;
var deduplicateText = deduplicate ? '<span style="color:#67c23a;">开启</span>' : '<span style="color:#f56c6c;">关闭</span>';
var cacheText = cacheMode == 'high' ? '<span style="color:#67c23a;">内存充足</span>' :
(cacheMode == 'balance' ? '<span style="color:#e6a23c;">平衡模式</span>' : '<span style="color:#f56c6c;">内存紧张</span>');
var roundCount = Math.ceil(totalGroups / chunkSize); // 需要几轮
var estimateMinutes = Math.ceil(roundCount * 30 / 60 + totalGroups / batchSize * (batchDelay + 20) / 60000);
layer.confirm('确定要开始合并重名视频吗?<br><br><span style="color:#d32f2f;">此操作不可逆,请确保已备份数据!</span><br><br>当前设置:<br>• 保留策略:<strong>' + getStrategyName(selectedStrategy) + '</strong><br>• 播放源去重:' + deduplicateText + '<br>• 缓存策略:' + cacheText + '<br>• 预计分 <strong>' + roundCount + '</strong> 轮处理<br>• 预估时间:约 <strong>' + estimateMinutes + '</strong> 分钟', {
icon: 3,
title: '确认操作',
btn: ['确定开始', '取消']
}, function(index){
layer.close(index);
startMerge();
});
});
$('#stopBtn').click(function(){
shouldStop = true;
$(this).prop('disabled', true).html('<i class="layui-icon layui-icon-loading layui-anim layui-anim-rotate layui-anim-loop"></i> 正在停止...');
});
function startMerge(){
isProcessing = true;
shouldStop = false;
processedGroups = 0;
totalDeleted = 0;
currentRound = 0;
$('.strategy-box').hide();
$('#progressBox').show();
$('#resultBox').html('');
loadCacheRound();
}
function loadCacheRound(){
if(shouldStop){
finishProcess('已停止处理');
return;
}
currentRound++;
addResultLog('🔄 第 ' + currentRound + ' 轮:正在预加载 ' + chunkSize + ' 组数据到内存...');
var loadIndex = layer.load(2, {content: '正在查询数据库,请稍候...', shade: [0.3, '#fff']});
$.ajax({
url: "{:url('vod/loadMergeCache')}",
type: 'POST',
data: {
chunk_size: chunkSize
},
success: function(res){
layer.close(loadIndex);
console.log('预加载结果:', res);
if(res.code == 1){
if(res.data.has_data){
var memoryMB = Math.ceil(res.data.total_vods * 1 / 1024); // 1KB/条
$('#memoryUsed').text(memoryMB + ' MB');
addResultLog('✅ 第 ' + currentRound + ' 轮预加载完成:缓存了 <span style="color:#409eff;font-weight:bold;">' + res.data.count + '</span> 组数据(共 ' + res.data.total_vods + ' 条视频,占用约 <span style="color:#e6a23c;">' + memoryMB + ' MB</span> 内存)');
processCacheBatch(0);
} else {
finishProcess('全部处理完成!共合并 ' + processedGroups + ' 组,删除 ' + totalDeleted + ' 条重复数据');
}
} else {
console.error('❌ 预加载失败:', res);
console.error('❌ 完整响应:', JSON.stringify(res, null, 2));
var errorMsg = res.msg || res.info || '未知错误';
addResultLog('❌ 预加载失败:' + errorMsg);
layer.msg(errorMsg, {icon: 2, time: 5000});
finishProcess('预加载失败:' + errorMsg);
}
},
error: function(xhr, status, error){
layer.close(loadIndex);
console.error('❌ 预加载AJAX错误:', {xhr: xhr, status: status, error: error});
var errorMsg = '预加载请求失败:' + (xhr.status ? xhr.status + ' ' : '') + (error || status);
addResultLog('❌ ' + errorMsg);
layer.msg(errorMsg, {icon: 2, time: 5000});
finishProcess(errorMsg);
}
});
}
function processCacheBatch(batchInRound, retryCount){
if(shouldStop){
finishProcess('已停止处理');
return;
}
retryCount = retryCount || 0; // 重试次数
$.ajax({
url: "{:url('vod/processCacheBatch')}",
type: 'POST',
timeout: 60000, // 60秒超时
data: {
strategy: selectedStrategy,
deduplicate: deduplicate ? 1 : 0,
batch_size: batchSize
},
success: function(res){
console.log('✅ 批次 ' + (batchInRound + 1) + ' 处理成功:', res);
if(res.code == 1){
processedGroups += res.data.merged_count;
totalDeleted += res.data.deleted_count;
updateProgress();
var batchTime = res.data.batch_time || 0;
var timeColor = batchTime > 1000 ? '#f56c6c' : (batchTime > 500 ? '#e6a23c' : '#67c23a');
var logMsg = '第 ' + currentRound + ' 轮 - 批次 ' + (batchInRound + 1) + ':';
if(res.data.merged_count > 0){
logMsg += '合并 <span style="color:#409eff;font-weight:bold;">' + res.data.merged_count + '</span> 组';
logMsg += ',删除 <span style="color:#f56c6c;">' + res.data.deleted_count + '</span> 条';
} else {
logMsg += '<span style="color:#909399;">无需合并</span>';
}
if(res.data.skipped_count > 0){
logMsg += ',跳过 <span style="color:#909399;">' + res.data.skipped_count + '</span> 组';
}
logMsg += ' <span style="color:' + timeColor + ';">(' + batchTime + 'ms)</span>';
if(res.data.details && res.data.details.length > 0){
var hasSlowItems = false;
var hasSkipped = false;
res.data.details.forEach(function(detail){
if(detail.skip){
hasSkipped = true;
} else if(detail.item_time > 500){
hasSlowItems = true;
}
});
if(hasSlowItems || hasSkipped || batchTime > 500){
logMsg += '<br> 📋 本批详情:';
res.data.details.forEach(function(detail){
if(detail.skip){
logMsg += '<br> ⚠️ <span style="color:#f56c6c;">' + detail.name + '</span> (跳过:' + detail.error + ')';
} else if(detail.merge_info && detail.merge_info.success){
var info = detail.merge_info;
var itemTimeColor = detail.item_time > 1000 ? '#f56c6c' : (detail.item_time > 500 ? '#e6a23c' : '#909399');
logMsg += '<br> • ' + detail.name +
' <span style="color:#909399;">(重复<strong>' + detail.repeat_count + '</strong>条</span>' +
', 播放器:' + info.original_play_count + '→<strong>' + info.total_play_sources + '</strong>' +
', 耗时:<span style="color:' + itemTimeColor + ';">' + detail.item_time + 'ms</span>)';
if(detail.performance && Object.keys(detail.performance).length > 0){
var perf = detail.performance;
logMsg += '<br> 🔍 <span style="color:#909399;">性能分析:' +
'合并逻辑=' + perf.merge_logic_time + 'ms, ' +
'DB更新=' + perf.db_update_time + 'ms, ' +
'DB删除=' + perf.db_delete_time + 'ms, ' +
'单组总耗时=' + perf.total_item_time + 'ms</span>';
}
if(info.performance && info.performance.total_time > 0){
var mperf = info.performance;
logMsg += '<br> ⚙️ <span style="color:#67c23a;">合并细节:</span>' +
'<span style="color:#909399;">' +
'解析=' + mperf.parse_time + 'ms, ' +
'循环=' + mperf.merge_loop_time + 'ms, ' +
'拼接=' + mperf.join_time + 'ms, ' +
'DB写入=' + mperf.db_update_time + 'ms' +
'</span>';
}
}
});
}
}
addResultLog(logMsg);
if(res.data.cache_finished || res.data.need_reload){
addResultLog('📊 第 ' + currentRound + ' 轮处理完成!累计合并 ' + processedGroups + ' 组,<span style="color:#67c23a;">释放内存...</span>');
$('#memoryUsed').text('0 MB');
setTimeout(function(){
loadCacheRound();
}, 200);
} else {
setTimeout(function(){
processCacheBatch(batchInRound + 1);
}, batchDelay);
}
} else {
console.error('❌ 处理失败:', res);
console.error('❌ 完整响应:', JSON.stringify(res, null, 2));
var errorMsg = res.msg || res.info || '未知错误';
var errorDetail = '';
if(res.data && typeof res.data === 'object'){
errorDetail = JSON.stringify(res.data);
console.error('❌ 错误详情:', errorDetail);
}
addResultLog('❌ 处理失败:' + errorMsg + (errorDetail ? '<br> 详情:' + errorDetail : ''));
layer.msg(errorMsg, {icon: 2, time: 5000});
finishProcess('处理失败:' + errorMsg);
}
},
error: function(xhr, status, error){
console.error('❌ AJAX错误:', {xhr: xhr, status: status, error: error, retry: retryCount});
if((xhr.status == 502 || xhr.status == 504 || status == 'timeout') && retryCount < 3){
addResultLog('⚠️ 第 ' + currentRound + ' 轮 - 批次 ' + (batchInRound + 1) + ':请求超时/502错误,3秒后重试(第' + (retryCount + 1) + '次)...');
setTimeout(function(){
processCacheBatch(batchInRound, retryCount + 1); // 重试
}, 3000);
} else {
var errorMsg = '请求失败:' + (xhr.status ? xhr.status + ' ' : '') + (error || status);
addResultLog('❌ ' + errorMsg);
layer.msg(errorMsg, {icon: 2, time: 5000});
finishProcess('处理中断:' + errorMsg + '。已合并 ' + processedGroups + ' 组');
}
}
});
}
function updateProgress(){
var percent = Math.floor((processedGroups / totalGroups) * 100);
if(percent > 100) percent = 100;
$('#progressBar').css('width', percent + '%').text(percent + '%');
$('#processedCount').text(processedGroups);
$('#deletedCount').text(totalDeleted);
$('#remainingCount').text(totalGroups - processedGroups);
}
function addResultLog(text){
var time = new Date().toLocaleTimeString();
var html = '<div class="result-item">' +
'<i class="layui-icon layui-icon-ok" style="color:#67c23a;"></i> ' +
'[' + time + '] ' + text +
'</div>';
$('#resultBox').prepend(html);
var resultBox = document.getElementById('resultBox');
resultBox.scrollTop = 0;
}
function finishProcess(message){
isProcessing = false;
addResultLog(message);
$('#stopBtn').prop('disabled', true).html('<i class="layui-icon layui-icon-ok"></i> 已完成');
clearSessionCache();
layer.msg(message, {icon: 1, time: 2000}, function(){
setTimeout(function(){
location.reload();
}, 1000);
});
}
function clearSessionCache(){
$.post("{:url('vod/clearMergeCache')}", {}, function(res){
console.log('✅ 缓存已清除,内存已释放:', res);
});
}
function manualClearCache(){
layer.confirm('确定要清除缓存吗?<br><br>如果有正在进行的合并任务会被中断!', {
icon: 3,
title: '清除缓存'
}, function(index){
clearSessionCache();
layer.close(index);
layer.msg('缓存已清除!', {icon: 1});
});
}
window.addEventListener('beforeunload', function(e){
if(isProcessing){
e.preventDefault();
e.returnValue = '正在处理中,确定要离开吗?缓存数据将被保留。';
} else {
clearSessionCache();
}
});
$(document).ready(function(){
if(!isProcessing){
$.post("{:url('vod/clearMergeCache')}", {}, function(res){
console.log('页面加载时清除旧缓存:', res);
});
}
});
function getStrategyName(strategy){
switch(strategy){
case 'newest': return '保留最新的视频';
case 'oldest': return '保留最旧的视频';
case 'most_hits': return '保留点击量最高的视频';
default: return '未知策略';
}
}
{if $total > 0}
laypage.render({
elem: 'pages',
count: {$total},
limit: {$limit},
curr: {$page},
layout: ['count', 'prev', 'page', 'next', 'limit', 'skip'],
jump: function(obj, first){
if(!first){
location.href = curUrl.replace('%7Bpage%7D', obj.curr).replace('%7Blimit%7D', obj.limit);
}
}
});
{/if}
form.on('checkbox(allChoose)', function(data){
var checked = data.elem.checked;
$('.checkbox-ids').each(function(){
this.checked = checked;
});
form.render('checkbox');
});
$('.j-tr-del').click(function(){
var url = $(this).data('href');
layer.confirm('确定要删除这条视频吗?', {
icon: 3,
title: '删除确认'
}, function(index){
$.post(url, {}, function(res){
if(res.code == 1){
layer.msg(res.msg, {icon: 1}, function(){
location.reload();
});
} else {
layer.msg(res.msg, {icon: 2});
}
});
layer.close(index);
});
});
});
</script>
</body>
</html>
步骤2.找到“application/admin/common/auth.php”找到:
'49' => array("show"=>1,'name' => lang('menu/vod_batch'), 'controller' => 'vod', 'action' => 'batch'),
'491' => array("show"=>1,'name' => lang('menu/vod_repeat'), 'controller' => 'vod', 'action' => 'data', 'param'=>'repeat=1'),
在这两行代码(约145行处)的下面增加代码;
'492' => array("show"=>1,'name' => '重名视频合并(优化)', 'controller' => 'vod', 'action' => 'mergeRepeat'),
'4923' => array("show"=>0,'name' => '--预加载缓存', 'controller' => 'vod', 'action' => 'loadMergeCache'),
'4924' => array("show"=>0,'name' => '--处理缓存批次', 'controller' => 'vod', 'action' => 'processCacheBatch'),
'4925' => array("show"=>0,'name' => '--清除合并缓存', 'controller' => 'vod', 'action' => 'clearMergeCache'),
步骤3.找到文件“application/admin/controller/Vod.php”在文件的最下方添加以下代码,注意粘贴前先删除文件中最后一个大括号“}”然后再粘贴下面的代码,不然会报错
/**
* 重名视频合并(优化版)- 支持大批量数据处理(https://www.zzzypro.com制作)
*/
public function mergeRepeat()
{
if($this->request->isPost()){
return $this->error('请使用异步接口');
}
$param = input();
$param['page'] = intval($param['page']) < 1 ? 1 : $param['page'];
$param['limit'] = intval($param['limit']) < 1 ? $this->_pagesize : $param['limit'];
// 检查是否有重名数据
$prefix = config('database.prefix');
$count_sql = "SELECT COUNT(DISTINCT vod_name) as total FROM {$prefix}vod
WHERE vod_name IN (
SELECT vod_name FROM {$prefix}vod
GROUP BY vod_name HAVING COUNT(vod_id) > 1
)";
$result = Db::query($count_sql);
$repeat_names_count = $result[0]['total'] ?? 0;
// 统计重名视频总数
$total_sql = "SELECT COUNT(*) as total FROM {$prefix}vod
WHERE vod_name IN (
SELECT vod_name FROM {$prefix}vod
GROUP BY vod_name HAVING COUNT(vod_id) > 1
)";
$total_result = Db::query($total_sql);
$total_repeat_count = $total_result[0]['total'] ?? 0;
// 获取重名视频列表(分页显示)
if($param['page'] == 1){
Db::execute('DROP TABLE IF EXISTS '.$prefix.'tmpvod');
Db::execute('CREATE TABLE `'.$prefix.'tmpvod` (`id1` int unsigned DEFAULT NULL, `name1` varchar(1024) NOT NULL DEFAULT \'\') ENGINE=MyISAM');
Db::execute('INSERT INTO `'.$prefix.'tmpvod` (SELECT min(vod_id)as id1,vod_name as name1 FROM '.$prefix.'vod GROUP BY name1 HAVING COUNT(name1)>1)');
}
$where = [];
$order = 'vod_name asc';
$res = model('Vod')->listRepeatData($where, $order, $param['page'], $param['limit']);
// 分类
$type_list = model('Type')->getCache('type_list');
foreach($res['list'] as $k => &$v){
$v['ismake'] = 1;
if($GLOBALS['config']['view']['vod_detail'] > 0 && $v['vod_time_make'] < $v['vod_time']){
$v['ismake'] = 0;
}
}
$this->assign('list', $res['list']);
$this->assign('total', $res['total']);
$this->assign('page', $res['page']);
$this->assign('limit', $res['limit']);
$param['page'] = '{page}';
$param['limit'] = '{limit}';
$this->assign('param', $param);
$this->assign('repeat_names_count', $repeat_names_count);
$this->assign('total_repeat_count', $total_repeat_count);
$this->assign('title', '重名视频合并(优化版)');
return $this->fetch('admin@vod/merge_repeat');
}
/**
* 获取重名视频分组数据(分批)
*/
public function getMergeRepeatData()
{
$param = input();
$page = isset($param['page']) ? intval($param['page']) : 1;
$limit = isset($param['limit']) ? intval($param['limit']) : 10; // 每批处理10组
$prefix = config('database.prefix');
// 获取重名的视频名称(分批)
$offset = ($page - 1) * $limit;
$names_sql = "SELECT vod_name, COUNT(*) as count FROM {$prefix}vod
GROUP BY vod_name HAVING COUNT(vod_id) > 1
LIMIT {$offset}, {$limit}";
$names = Db::query($names_sql);
$groups = [];
foreach($names as $name_info){
// 获取每个重名组的所有视频
$vods = Db::name('vod')
->field('vod_id,vod_name,vod_time,vod_time_add,vod_hits,vod_play_url,vod_pic')
->where('vod_name', $name_info['vod_name'])
->order('vod_time desc')
->select();
if(count($vods) > 1){
$groups[] = [
'name' => $name_info['vod_name'],
'count' => count($vods),
'items' => $vods
];
}
}
// 统计总数
$total_sql = "SELECT COUNT(DISTINCT vod_name) as total FROM {$prefix}vod
WHERE vod_name IN (
SELECT vod_name FROM {$prefix}vod
GROUP BY vod_name HAVING COUNT(vod_id) > 1
)";
$total_result = Db::query($total_sql);
$total = $total_result[0]['total'] ?? 0;
return json([
'code' => 1,
'msg' => '获取成功',
'data' => $groups,
'total' => $total,
'page' => $page,
'limit' => $limit
]);
}
/**
* 清除合并缓存
*/
public function clearMergeCache()
{
session('merge_vod_cache', null);
session('merge_vod_names', null);
session('merge_cache_index', null);
return json([
'code' => 1,
'msg' => '缓存已清除'
]);
}
/**
* 预加载重名数据到缓存(分段加载)
*/
public function loadMergeCache()
{
$param = input();
$chunk_size = isset($param['chunk_size']) ? intval($param['chunk_size']) : 5000; // 每次缓存多少组
$prefix = config('database.prefix');
try {
// 先清除旧缓存,释放内存
session('merge_vod_cache', null);
session('merge_vod_names', null);
session('merge_cache_index', null);
// 查询指定数量的重名组
$names_sql = "SELECT vod_name, COUNT(*) as count FROM {$prefix}vod
GROUP BY vod_name HAVING COUNT(vod_id) > 1
LIMIT {$chunk_size}";
$names = Db::query($names_sql);
if(empty($names)){
// 没有重名数据了
return json([
'code' => 1,
'msg' => '无重名数据',
'data' => [
'has_data' => false,
'count' => 0
]
]);
}
// 提取所有重名视频名称
$vod_names = array_column($names, 'vod_name');
// 一次性查询这些重名视频的所有数据
$all_vods = Db::name('vod')
->field('vod_id,vod_name,vod_time,vod_time_add,vod_hits,vod_play_from,vod_play_url,vod_play_server,vod_play_note')
->where('vod_name', 'in', $vod_names)
->order('vod_name asc, vod_time desc')
->select();
// 按vod_name分组
$grouped_data = [];
foreach($all_vods as $vod){
$grouped_data[$vod['vod_name']][] = $vod;
}
// 过滤出真正重名的组(count>1)
$final_groups = [];
foreach($grouped_data as $name => $vods){
if(count($vods) > 1){
$final_groups[$name] = $vods;
}
}
// 缓存到Session
session('merge_vod_cache', $final_groups);
session('merge_vod_names', array_keys($final_groups));
session('merge_cache_index', 0);
return json([
'code' => 1,
'msg' => '预加载成功',
'data' => [
'has_data' => true,
'count' => count($final_groups),
'total_vods' => count($all_vods)
]
]);
} catch (\Exception $e) {
// 记录详细错误日志
$error_log = [
'time' => date('Y-m-d H:i:s'),
'error' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'trace' => $e->getTraceAsString()
];
\think\Log::error('预加载失败: ' . json_encode($error_log, JSON_UNESCAPED_UNICODE));
return json([
'code' => 0,
'msg' => '预加载失败:' . $e->getMessage(),
'data' => [
'error_file' => basename($e->getFile()),
'error_line' => $e->getLine()
]
]);
}
}
/**
* 从缓存处理一批数据(新的处理方法)
*/
public function processCacheBatch()
{
// 设置超时保护
@set_time_limit(300); // 5分钟超时
@ini_set('memory_limit', '512M'); // 增加内存限制
$param = input();
$strategy = $param['strategy'] ?? 'newest';
$deduplicate = isset($param['deduplicate']) ? intval($param['deduplicate']) : 1;
$batch_size = isset($param['batch_size']) ? intval($param['batch_size']) : 30;
try {
$cache = session('merge_vod_cache');
$names = session('merge_vod_names');
$index = session('merge_cache_index');
if(empty($cache) || empty($names)){
return json([
'code' => 0,
'msg' => '缓存为空,请先预加载'
]);
}
if($index >= count($names)){
// 当前缓存处理完
return json([
'code' => 1,
'msg' => '当前缓存段处理完成',
'data' => [
'merged_count' => 0,
'deleted_count' => 0,
'cache_finished' => true,
'need_reload' => true
]
]);
}
// 获取本批要处理的视频组
$batch_names = array_slice($names, $index, $batch_size);
$merged_count = 0;
$deleted_count = 0;
$skipped_count = 0; // 跳过的组数(非重名或已处理)
$merge_details = [];
$batch_start_time = microtime(true); // 记录批次开始时间
$max_delete_per_batch = 500; // 单批最多删除500条,防止超时
foreach($batch_names as $vod_name){
// 超时保护:如果单批处理超过20秒,提前结束
if((microtime(true) - $batch_start_time) > 20){
$merge_details[] = [
'warning' => '单批处理时间过长,提前结束本批次',
'skip' => true
];
break;
}
// 删除数量保护:单批删除数量过多时提前结束
if($deleted_count >= $max_delete_per_batch){
$merge_details[] = [
'warning' => '单批删除数量达到限制(' . $max_delete_per_batch . '条),提前结束',
'skip' => true
];
break;
}
// 检查缓存中是否存在该名称的数据
if(!isset($cache[$vod_name])){
// 缓存中不存在,记录异常
$skipped_count++;
$merge_details[] = [
'name' => $vod_name,
'error' => '缓存中不存在',
'skip' => true
];
continue;
}
$vods = $cache[$vod_name];
$vod_count = count($vods);
if($vod_count < 2){
// 不是重名(理论上不应该出现)
$skipped_count++;
$merge_details[] = [
'name' => $vod_name,
'error' => '只有' . $vod_count . '条数据',
'skip' => true
];
continue;
}
// 根据策略排序选择保留哪条
usort($vods, function($a, $b) use ($strategy){
switch($strategy){
case 'oldest':
return $a['vod_time_add'] - $b['vod_time_add'];
case 'most_hits':
return $b['vod_hits'] - $a['vod_hits'];
case 'newest':
default:
return $b['vod_time'] - $a['vod_time'];
}
});
// 保留第一条
$keep_vod = $vods[0];
$merge_vods = array_slice($vods, 1);
$merge_ids = array_column($merge_vods, 'vod_id');
if(!empty($merge_ids)){
$item_start_time = microtime(true);
// 步骤1:合并数据(内部包含数据库更新)
$merge_start = microtime(true);
$merge_result = $this->mergeVodData($keep_vod, $merge_vods, $deduplicate);
$merge_time = round((microtime(true) - $merge_start) * 1000);
// 从 mergeVodData 的返回值中提取数据库更新耗时
$update_time = $merge_result['performance']['db_update_time'] ?? 0;
// 步骤2:删除重复视频
$delete_start = microtime(true);
Db::name('vod')->where('vod_id', 'in', $merge_ids)->delete();
$delete_time = round((microtime(true) - $delete_start) * 1000);
$item_time = round((microtime(true) - $item_start_time) * 1000);
$merged_count++;
$deleted_count += count($merge_ids);
// 性能分析:只有慢速项目才记录详细步骤(>100ms)
$performance = [];
if($item_time > 100){ // 超过100ms才记录详细步骤
$performance = [
'merge_logic_time' => $merge_time, // 合并数据逻辑耗时
'db_update_time' => $update_time, // 数据库更新耗时(来自mergeVodData)
'db_delete_time' => $delete_time, // 数据库删除耗时
'total_item_time' => $item_time // 单组总耗时
];
}
$merge_details[] = [
'name' => $vod_name,
'keep_id' => $keep_vod['vod_id'],
'deleted_ids' => $merge_ids,
'repeat_count' => count($vods), // 重复条数
'item_time' => $item_time, // 单组耗时
'merge_info' => $merge_result,
'performance' => $performance, // 性能分析
'skip' => false
];
}
}
$batch_time = round((microtime(true) - $batch_start_time) * 1000); // 批次耗时(ms)
// 更新索引
session('merge_cache_index', $index + $batch_size);
return json([
'code' => 1,
'msg' => '处理成功',
'data' => [
'merged_count' => $merged_count,
'deleted_count' => $deleted_count,
'skipped_count' => $skipped_count, // 跳过的数量
'batch_time' => $batch_time, // 批次耗时
'cache_finished' => false,
'need_reload' => false,
'cache_progress' => round(($index + $batch_size) / count($names) * 100),
'details' => $merge_details
]
]);
} catch (\Exception $e) {
// 记录详细错误日志
$error_log = [
'time' => date('Y-m-d H:i:s'),
'error' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'trace' => $e->getTraceAsString()
];
\think\Log::error('合并处理失败: ' . json_encode($error_log, JSON_UNESCAPED_UNICODE));
return json([
'code' => 0,
'msg' => '处理失败:' . $e->getMessage(),
'data' => [
'error_file' => basename($e->getFile()),
'error_line' => $e->getLine()
]
]);
}
}
/**
* 执行合并操作(异步处理)- 保留旧方法兼容
*/
public function doMergeRepeat()
{
$param = input();
$strategy = $param['strategy'] ?? 'newest'; // newest:保留最新, oldest:保留最旧, most_hits:保留点击最高
$deduplicate = isset($param['deduplicate']) ? intval($param['deduplicate']) : 1; // 是否去重,默认开启
$batch_size = isset($param['batch_size']) ? intval($param['batch_size']) : 5; // 每批处理5组
$current_batch = isset($param['current_batch']) ? intval($param['current_batch']) : 0;
$prefix = config('database.prefix');
try {
// 获取本批次的重名组(不使用OFFSET,而是每次重新查询)
// 这样可以确保每次都能查到最新的重名数据
$names_sql = "SELECT vod_name, COUNT(*) as count FROM {$prefix}vod
GROUP BY vod_name HAVING COUNT(vod_id) > 1
LIMIT {$batch_size}";
$names = Db::query($names_sql);
// 如果没有数据了,说明处理完成
if(empty($names)){
return json([
'code' => 1,
'msg' => '本批无数据',
'data' => [
'merged_count' => 0,
'deleted_count' => 0,
'remaining' => 0,
'current_batch' => $current_batch,
'finished' => true,
'has_data' => false
]
]);
}
$merged_count = 0;
$deleted_count = 0;
$merge_details = []; // 记录合并详情
foreach($names as $name_info){
$vod_name = $name_info['vod_name'];
// 根据策略选择保留哪条数据
switch($strategy){
case 'oldest':
$order = 'vod_time_add asc';
break;
case 'most_hits':
$order = 'vod_hits desc';
break;
case 'newest':
default:
$order = 'vod_time desc';
break;
}
// 获取该名称的所有视频(只需要播放器相关字段)
$vods = Db::name('vod')
->field('vod_id,vod_name,vod_time,vod_time_add,vod_hits,vod_play_from,vod_play_url,vod_play_server,vod_play_note')
->where('vod_name', $vod_name)
->order($order)
->select();
if(count($vods) > 1){
// 保留第一条(根据策略)
$keep_vod = $vods[0];
$merge_vods = [];
$merge_ids = [];
// 收集要合并和删除的数据
for($i = 1; $i < count($vods); $i++){
$merge_ids[] = $vods[$i]['vod_id'];
$merge_vods[] = $vods[$i];
}
if(!empty($merge_ids)){
// 先合并数据,再删除(传递去重开关)
$merge_result = $this->mergeVodData($keep_vod, $merge_vods, $deduplicate);
// 删除重复的视频
Db::name('vod')->where('vod_id', 'in', $merge_ids)->delete();
$merged_count++;
$deleted_count += count($merge_ids);
// 记录详情用于调试
$merge_details[] = [
'name' => $vod_name,
'keep_id' => $keep_vod['vod_id'],
'deleted_ids' => $merge_ids,
'merge_info' => $merge_result
];
}
}
}
// 【性能优化】快速检查是否还有重名数据
// 使用EXISTS代替LIMIT 1,性能更好
$check_sql = "SELECT EXISTS(
SELECT 1 FROM {$prefix}vod
GROUP BY vod_name
HAVING COUNT(vod_id) > 1
) as has_data";
$check_result = Db::query($check_sql);
$has_more_data = ($check_result[0]['has_data'] ?? 0) == 1;
// 统计剩余数量(用于显示,每3批才统计一次以减少查询)
$remaining = 0;
if($has_more_data && $current_batch % 3 == 0){
// 每3批才统计一次,减少数据库压力
$count_sql = "SELECT COUNT(*) as cnt FROM (
SELECT vod_name FROM {$prefix}vod
GROUP BY vod_name
HAVING COUNT(vod_id) > 1
) AS tmp";
$count_result = Db::query($count_sql);
$remaining = $count_result[0]['cnt'] ?? 0;
}
return json([
'code' => 1,
'msg' => '处理成功',
'data' => [
'merged_count' => $merged_count,
'deleted_count' => $deleted_count,
'remaining' => $remaining,
'has_more_data' => $has_more_data, // 关键标志
'current_batch' => $current_batch + 1,
'finished' => !$has_more_data, // 有数据就未完成
'details' => $merge_details // 返回详情用于调试
]
]);
} catch (\Exception $e) {
// 记录详细错误日志
$error_log = [
'time' => date('Y-m-d H:i:s'),
'error' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'trace' => $e->getTraceAsString()
];
\think\Log::error('旧版合并处理失败: ' . json_encode($error_log, JSON_UNESCAPED_UNICODE));
return json([
'code' => 0,
'msg' => '处理失败:' . $e->getMessage(),
'data' => [
'error_file' => basename($e->getFile()),
'error_line' => $e->getLine()
]
]);
}
}
/**
* 合并视频数据(只合并播放链接)
* @param array $keep_vod 保留的视频数据
* @param array $merge_vods 要合并的视频数组
* @param int $deduplicate 是否去重 1=去重 0=不去重
* @return array 返回合并信息
*/
private function mergeVodData($keep_vod, $merge_vods, $deduplicate = 1)
{
$perf_start = microtime(true); // 总计时开始
if(empty($merge_vods) || empty($keep_vod)){
return ['success' => false, 'msg' => '数据为空'];
}
// 步骤1:解析保留视频的播放数据
$parse_start = microtime(true);
$play_from_arr = !empty($keep_vod['vod_play_from']) ? explode('$$$', $keep_vod['vod_play_from']) : [];
$play_url_arr = !empty($keep_vod['vod_play_url']) ? explode('$$$', $keep_vod['vod_play_url']) : [];
$play_server_arr = !empty($keep_vod['vod_play_server']) ? explode('$$$', $keep_vod['vod_play_server']) : [];
$play_note_arr = !empty($keep_vod['vod_play_note']) ? explode('$$$', $keep_vod['vod_play_note']) : [];
$parse_time = round((microtime(true) - $parse_start) * 1000, 2);
// 初始化统计
$total_hits = intval($keep_vod['vod_hits']);
$added_play_sources = 0;
$duplicate_sources = 0; // 重复的播放源数量
$original_play_count = count($play_from_arr);
// 步骤2:遍历要合并的视频,只合并播放链接
$merge_start = microtime(true);
foreach($merge_vods as $merge_vod){
// 合并播放来源
if(!empty($merge_vod['vod_play_from'])){
$from_arr = explode('$$$', $merge_vod['vod_play_from']);
$url_arr = !empty($merge_vod['vod_play_url']) ? explode('$$$', $merge_vod['vod_play_url']) : [];
$server_arr = !empty($merge_vod['vod_play_server']) ? explode('$$$', $merge_vod['vod_play_server']) : [];
$note_arr = !empty($merge_vod['vod_play_note']) ? explode('$$$', $merge_vod['vod_play_note']) : [];
// 逐个添加播放源
foreach($from_arr as $idx => $from){
$from = trim($from);
if(!empty($from)){
// 根据去重开关决定是否检查重复
if($deduplicate == 1){
// 开启去重:检查是否已存在
if(!in_array($from, $play_from_arr)){
$play_from_arr[] = $from;
$play_url_arr[] = isset($url_arr[$idx]) ? trim($url_arr[$idx]) : '';
$play_server_arr[] = isset($server_arr[$idx]) ? trim($server_arr[$idx]) : 'no';
$play_note_arr[] = isset($note_arr[$idx]) ? trim($note_arr[$idx]) : '';
$added_play_sources++;
} else {
$duplicate_sources++;
}
} else {
// 关闭去重:直接添加所有播放源
$play_from_arr[] = $from;
$play_url_arr[] = isset($url_arr[$idx]) ? trim($url_arr[$idx]) : '';
$play_server_arr[] = isset($server_arr[$idx]) ? trim($server_arr[$idx]) : 'no';
$play_note_arr[] = isset($note_arr[$idx]) ? trim($note_arr[$idx]) : '';
$added_play_sources++;
}
}
}
}
// 累加点击量
$total_hits += intval($merge_vod['vod_hits']);
}
$merge_loop_time = round((microtime(true) - $merge_start) * 1000, 2);
// 步骤3:拼接数据
$join_start = microtime(true);
$update_data = [
'vod_play_from' => join('$$$', $play_from_arr),
'vod_play_url' => join('$$$', $play_url_arr),
'vod_play_server' => join('$$$', $play_server_arr),
'vod_play_note' => join('$$$', $play_note_arr),
'vod_hits' => $total_hits,
];
$join_time = round((microtime(true) - $join_start) * 1000, 2);
// 步骤4:更新数据库
$db_start = microtime(true);
$result = Db::name('vod')->where('vod_id', $keep_vod['vod_id'])->update($update_data);
$db_time = round((microtime(true) - $db_start) * 1000, 2);
$total_time = round((microtime(true) - $perf_start) * 1000, 2);
return [
'success' => $result !== false,
'keep_id' => $keep_vod['vod_id'],
'added_play_sources' => $added_play_sources,
'duplicate_sources' => $duplicate_sources,
'deduplicate' => $deduplicate,
'original_play_count' => $original_play_count,
'total_play_sources' => count($play_from_arr),
'total_hits' => $total_hits,
'original_hits' => $keep_vod['vod_hits'],
'play_from' => join(',', $play_from_arr), // 便于日志显示
// 性能分析数据(只在总时间>100ms时返回,避免数据过多)
'performance' => $total_time > 100 ? [
'parse_time' => $parse_time, // 解析数据耗时
'merge_loop_time' => $merge_loop_time, // 合并循环耗时
'join_time' => $join_time, // 拼接数据耗时
'db_update_time' => $db_time, // 数据库更新耗时
'total_time' => $total_time // 总耗时
] : null
];
}
}
最后再苹果cms10的后台的“视频-重名视频合并(优化)”就可以正常使用了,如果有问题可以联系我。
暂无评论...