苹果cms视频批量合并

苹果cms 12小时前 图图
11 0

苹果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

苹果cms视频批量合并

步骤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>&nbsp;&nbsp;&nbsp;&nbsp;📋 本批详情:';
                                
                                res.data.details.forEach(function(detail){
                                    if(detail.skip){
                                       
                                        logMsg += '<br>&nbsp;&nbsp;&nbsp;&nbsp;⚠️ <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>&nbsp;&nbsp;&nbsp;&nbsp;• ' + 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>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;🔍 <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>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;⚙️ <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>&nbsp;&nbsp;&nbsp;&nbsp;详情:' + 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的后台的“视频-重名视频合并(优化)”就可以正常使用了,如果有问题可以联系我。

版权声明:图图 发表于 2025-10-31 23:44:36。
转载请注明:苹果cms视频批量合并 | 站长资源站Pro|站长资源|建站教程

暂无评论

暂无评论...