<?php
/**
* Plugin Name: 客信云点播
* Plugin URI: https://www.psste.com
* Description: 腾讯云点播 — 视频上传·自动转码·TCPlayer播放·防盗链·短代码
* Version: 2.0
* Author: 客信新材料
* License: GPLv2
* Text Domain: kexin-vod
*/
if (!defined('ABSPATH')) exit;
define('KEXIN_VOD_URL', plugin_dir_url(__FILE__));
define('KEXIN_VOD_VER', '2.0');
// ====== 管理菜单 ======
add_action('admin_menu', function() {
add_menu_page(
'客信云点播',
'云点播',
'manage_options',
'kexin-vod',
'kexin_vod_page_upload',
'dashicons-video-alt3',
30
);
add_submenu_page('kexin-vod', '上传视频', '上传视频', 'manage_options', 'kexin-vod', 'kexin_vod_page_upload');
add_submenu_page('kexin-vod', '视频库', '视频库', 'manage_options', 'kexin-vod-library', 'kexin_vod_page_library');
add_submenu_page('kexin-vod', '系统设置', '系统设置', 'manage_options', 'kexin-vod-settings', 'kexin_vod_page_settings');
});
// ====== 上传页面 ======
function kexin_vod_page_upload() { ?>
<div class="wrap">
<h1>上传视频</h1>
<p class="description">上传视频到腾讯云点播,自动触发转码和封面截图。支持 MP4/MOV/AVI/FLV,最大 50GB。</p>
<script src="https://cdn-go.cn/cdn/vod-js-sdk-v6/latest/vod-js-sdk-v6.js"></script>
<div id="kexin-upload-wrap" style="max-width:700px;margin-top:20px">
<div id="kexin-dropzone" style="border:2px dashed #c0c0c0;border-radius:8px;padding:48px 24px;text-align:center;cursor:pointer;background:#fafafa;transition:all 0.2s">
<div style="font-size:3rem;margin-bottom:8px">▶</div>
<p style="font-size:1.2rem;font-weight:600;margin:0">点击或拖拽视频文件到此处</p>
<p style="color:#888;margin:4px 0 0">支持格式:MP4 / MOV / AVI / FLV · 单文件最大 50GB</p>
<input type="file" id="kexin-file-input" accept="video/*" style="display:none">
</div>
<div id="kexin-name-wrap" style="display:none;margin-top:14px">
<label style="font-weight:600;display:block;margin-bottom:4px">视频名称</label>
<input id="kexin-vname" class="regular-text" style="width:100%" placeholder="输入视频名称(可选,默认使用文件名)">
</div>
<div id="kexin-progress" style="display:none;margin-top:14px">
<div style="background:#e0e0e0;border-radius:4px;height:10px;overflow:hidden">
<div id="kexin-bar" style="background:#F97316;height:100%;width:0%;transition:width 0.3s"></div>
</div>
<p style="margin-top:8px;font-size:13px">
<strong id="kexin-status">准备中...</strong>
<span id="kexin-pct">0%</span>
<span id="kexin-speed" style="color:#888"></span>
</p>
</div>
<div id="kexin-result" style="display:none;margin-top:14px;padding:16px;background:#f0fdf4;border:1px solid #bbf7d0;border-radius:6px">
<p style="font-weight:700;color:#166534;margin:0 0 8px">✓ 上传成功!</p>
<p style="margin:4px 0">文件ID:<code id="kexin-fileid" style="font-size:13px"></code></p>
<p style="margin:4px 0">短代码:<input id="kexin-shortcode" readonly onclick="this.select()" style="width:320px;font-family:monospace"></p>
<p style="color:#888;font-size:12px;margin:8px 0 0" id="kexin-proc-note"></p>
<script>document.getElementById('kexin-proc-note').textContent=kexinVodCfg.procedure?'已触发任务流:'+kexinVodCfg.procedure+',转码+封面处理中...':'未配置任务流,视频不会自动转码。请在系统设置中配置。'</script>
</div>
<div id="kexin-error" style="display:none;margin-top:14px;padding:14px;background:#fef2f2;border:1px solid #fecaca;border-radius:6px;color:#dc2626"></div>
</div>
<script>
var kexinVodCfg={restUrl:'<?php echo rest_url("kexin-vod/v1/signature"); ?>',saveUrl:'<?php echo rest_url("kexin-vod/v1/save"); ?>',procedure:'<?php echo esc_js(get_option("kexin_vod_procedure","")); ?>',subAppId:'<?php echo esc_js(get_option("kexin_vod_sub_app_id","1500036857")); ?>',storageRegion:'ap-guangzhou'};
function kexinVodInit(){
if(typeof TcVod==='undefined'){setTimeout(kexinVodInit,300);return}
var dz=document.getElementById('kexin-dropzone'),fi=document.getElementById('kexin-file-input'),
nw=document.getElementById('kexin-name-wrap'),ni=document.getElementById('kexin-vname'),
pw=document.getElementById('kexin-progress'),pb=document.getElementById('kexin-bar'),
st=document.getElementById('kexin-status'),pt=document.getElementById('kexin-pct'),
sp=document.getElementById('kexin-speed'),rw=document.getElementById('kexin-result'),
rf=document.getElementById('kexin-fileid'),sc=document.getElementById('kexin-shortcode'),
ew=document.getElementById('kexin-error');
dz.onclick=function(){fi.click()};
fi.onchange=function(e){if(e.target.files[0])handle(e.target.files[0])};
dz.ondragover=function(e){e.preventDefault();dz.style.borderColor='#F97316';dz.style.background='#fff8f5'};
dz.ondragleave=function(){dz.style.borderColor='#c0c0c0';dz.style.background='#fafafa'};
dz.ondrop=function(e){e.preventDefault();dz.style.borderColor='#c0c0c0';dz.style.background='#fafafa';if(e.dataTransfer.files[0])handle(e.dataTransfer.files[0])};
function handle(file){
if(!file.type.startsWith('video/')){showErr('请选择视频文件(MP4/MOV/AVI/FLV)');return}
nw.style.display='block';if(!ni.value)ni.value=file.name.replace(/\.[^.]+$/,'');
ew.style.display='none';pw.style.display='block';rw.style.display='none';
pb.style.width='0%';pt.textContent='0%';st.textContent='正在获取上传签名...';sp.textContent='';
fetch(kexinVodCfg.restUrl,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({mediaType:file.type,mediaName:ni.value||file.name})})
.then(function(r){return r.json()})
.then(function(d){
if(!d.signature)throw new Error(d.message||'签名获取失败');
st.textContent='正在上传...';
var uploader=new TcVod.default({getSignature:function(){return d.signature}});
var task=uploader.upload({mediaFile:file,videoName:ni.value||file.name,procedure:kexinVodCfg.procedure||'',vodSubAppId:kexinVodCfg.subAppId,storageRegion:kexinVodCfg.storageRegion});
task.on('media_progress',function(i){var p=Math.round((i.percent||0)*100);pb.style.width=p+'%';pt.textContent=p+'%';if(i.speed)sp.textContent=(i.speed/1048576).toFixed(1)+' MB/s'});
task.on('media_uploaded',function(){st.textContent='处理中...'});
task.done().then(function(r){
st.textContent='完成!';pb.style.width='100%';pt.textContent='100%';
rf.textContent=r.fileId;sc.value='[kexin_video id=\"'+r.fileId+'\"]';rw.style.display='block';
fetch(kexinVodCfg.saveUrl,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({file_id:r.fileId,name:ni.value||file.name,size:file.size})}).catch(function(){});
}).catch(function(e){showErr('上传失败:'+(e.message||e));pw.style.display='none'});
}).catch(function(e){showErr('签名错误:'+(e.message||e))});
}
function showErr(msg){ew.style.display='block';ew.textContent=msg}
}
kexinVodInit();
</script>
</div>
<?php }
// ====== 视频库页面 ======
function kexin_vod_page_library() {
$videos = get_option('kexin_vod_videos', []);
$count = count($videos);
?>
<div class="wrap">
<h1>视频库 <span style="font-size:14px;color:#888">(共 <?php echo $count; ?> 个视频)</span></h1>
<p>
<a href="admin.php?page=kexin-vod" class="button button-primary">上传新视频</a>
<button class="button" onclick="syncVod()">同步云端</button>
<span id="sync-status" style="margin-left:8px;color:#888;display:none">同步中...</span>
</p>
<?php if (empty($videos)): ?>
<div style="text-align:center;padding:60px 20px;background:#fafafa;border-radius:8px;margin-top:20px">
<p style="font-size:1.2rem;color:#888">暂无视频</p>
<p><a href="admin.php?page=kexin-vod" class="button button-primary">立即上传</a></p>
</div>
<?php else: ?>
<table class="wp-list-table widefat fixed striped" style="margin-top:12px">
<thead>
<tr>
<th style="width:50px">序号</th>
<th>视频名称</th>
<th style="width:200px">文件 ID</th>
<th style="width:150px">上传时间</th>
<th style="width:160px">短代码</th>
<th style="width:160px">操作</th>
</tr>
</thead>
<tbody>
<?php
$idx = $count;
foreach (array_reverse($videos) as $v):
$fid = esc_attr($v['file_id'] ?? '');
$name = esc_html($v['name'] ?? '未命名');
$time = esc_html($v['time'] ?? '');
$size = isset($v['size']) ? kexin_vod_format_size($v['size']) : '';
?>
<tr id="vod-row-<?php echo $idx; ?>">
<td><?php echo $idx--; ?></td>
<td>
<strong><?php echo $name; ?></strong>
<?php if ($size) echo '<br><small style="color:#888">' . $size . '</small>'; ?>
</td>
<td><code style="font-size:12px"><?php echo $fid; ?></code></td>
<td><?php echo $time; ?></td>
<td>
<input value='[kexin_video id="<?php echo $fid; ?>"]' readonly
onclick="this.select()" style="width:220px;font-family:monospace;font-size:12px">
</td>
<td>
<button class="button button-small" onclick="copySC('<?php echo $fid; ?>',this)">复制</button>
<button class="button button-small" style="color:#dc2626;border-color:#dc2626" onclick="delVideo('<?php echo $fid; ?>',this)">删除</button>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
<script>
function copySC(id,btn){var code='[kexin_video id="'+id+'"]';navigator.clipboard.writeText(code);btn.textContent='已复制!';setTimeout(function(){btn.textContent='复制'},1500)}
function delVideo(id,btn){
if(!confirm('确定从本地库中删除此视频记录?\n(云端视频不会被删除,仅移除本地记录)'))return;
fetch('<?php echo rest_url("kexin-vod/v1/delete"); ?>',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({file_id:id})})
.then(function(r){return r.json()})
.then(function(d){if(d.success){btn.closest('tr').remove();if(!document.querySelectorAll('tbody tr').length)location.reload()}else{alert('删除失败')}})
.catch(function(){alert('网络错误')});
}
function syncVod(){
var st=document.getElementById('sync-status');st.style.display='inline';st.textContent='正在校验云端...';
fetch('<?php echo rest_url("kexin-vod/v1/sync"); ?>',{method:'POST'})
.then(function(r){return r.json()})
.then(function(d){
if(d.success){
st.textContent='同步完成! 有效'+d.valid+'个, 已移除'+d.removed+'个';
if(d.removed>0){setTimeout(function(){location.reload()},1500)}
}else{st.textContent='同步失败: '+(d.message||'')}
}).catch(function(){st.textContent='网络错误'});
}
</script>
<?php
}
function kexin_vod_format_size($bytes) {
if ($bytes >= 1073741824) return round($bytes / 1073741824, 1) . ' GB';
if ($bytes >= 1048576) return round($bytes / 1048576, 1) . ' MB';
if ($bytes >= 1024) return round($bytes / 1024, 1) . ' KB';
return $bytes . ' B';
}
// ====== 设置页面 ======
function kexin_vod_page_settings() {
if (isset($_POST['kexin_save'])) {
update_option('kexin_vod_secret_id', sanitize_text_field($_POST['secret_id']));
update_option('kexin_vod_secret_key', sanitize_text_field($_POST['secret_key']));
update_option('kexin_vod_sub_app_id', sanitize_text_field($_POST['sub_app_id']));
update_option('kexin_vod_app_id', sanitize_text_field($_POST['app_id']));
update_option('kexin_vod_pkey', sanitize_text_field($_POST['pkey']));
update_option('kexin_vod_procedure', sanitize_text_field($_POST['procedure']));
echo '<div class="notice notice-success is-dismissible"><p>设置已保存。</p></div>';
}
?>
<div class="wrap">
<h1>系统设置</h1>
<form method="post">
<table class="form-table">
<tr>
<th scope="row">SecretId</th>
<td><input name="secret_id" value="<?php echo esc_attr(get_option('kexin_vod_secret_id','')); ?>" class="regular-text" placeholder="AKIDxxxxxxxx">
<p class="description">腾讯云 API 密钥 SecretId</p></td>
</tr>
<tr>
<th scope="row">SecretKey</th>
<td><input name="secret_key" value="<?php echo esc_attr(get_option('kexin_vod_secret_key','')); ?>" class="regular-text" type="password" placeholder="••••••••">
<p class="description">腾讯云 API 密钥 SecretKey</p></td>
</tr>
<tr>
<th scope="row">VOD AppID</th>
<td><input name="app_id" value="<?php echo esc_attr(get_option('kexin_vod_app_id','1500036857')); ?>" class="regular-text">
<p class="description">云点播 AppID,在 <a href="https://console.cloud.tencent.com/developer" target="_blank">账号信息</a> 查看(10位数字)</p></td>
</tr>
<tr>
<th scope="row">子应用 ID</th>
<td><input name="sub_app_id" value="<?php echo esc_attr(get_option('kexin_vod_sub_app_id','0')); ?>" class="regular-text">
<p class="description">上传签名用,与 AppID 通常相同</p></td>
</tr>
<tr>
<th scope="row">播放密钥 (pkey)</th>
<td><input name="pkey" value="<?php echo esc_attr(get_option('kexin_vod_pkey','')); ?>" class="regular-text" type="password">
<p class="description">云点播控制台 → 分发播放设置 → 默认分发配置 → 播放密钥。psign 签名必需。</p></td>
</tr>
<tr>
<th scope="row">任务流名称</th>
<td><input name="procedure" value="<?php echo esc_attr(get_option('kexin_vod_procedure','')); ?>" class="regular-text" placeholder="例如:psste_com_video">
<p class="description">腾讯云点播控制台 → 任务流设置 → 创建含「自适应转码 + 封面截图」的任务流</p></td>
</tr>
</table>
<p class="submit"><button type="submit" name="kexin_save" class="button button-primary">保存设置</button></p>
</form>
<hr style="margin:32px 0">
<h2>使用说明</h2>
<ol style="line-height:2">
<li>在 <a href="https://console.cloud.tencent.com/cam/capi" target="_blank">腾讯云访问密钥</a> 获取 SecretId 和 SecretKey</li>
<li>在 <a href="https://console.cloud.tencent.com/vod" target="_blank">云点播控制台</a> 创建<strong>任务流</strong>,包含「自适应码流转码」和「封面截图」</li>
<li>上传视频 → 自动转码 → 在视频库复制短代码</li>
<li>将短代码 <code>[kexin_video id="文件ID"]</code> 粘贴到任意页面或文章</li>
<li>TCPlayer 自动根据用户网速选择最佳清晰度,用户也可手动切换</li>
</ol>
<h3>短代码参数</h3>
<table class="widefat" style="max-width:500px">
<tr><td><code>[kexin_video id="xxx"]</code></td><td>自适应宽度</td></tr>
<tr><td><code>[kexin_video id="xxx" width="800px"]</code></td><td>固定宽度</td></tr>
<tr><td><code>[kexin_video id="xxx" psign="签名"]</code></td><td>开启播放防盗链</td></tr>
</table>
</div>
<?php
}
// ====== REST API ======
add_action('rest_api_init', function() {
// 上传签名
register_rest_route('kexin-vod/v1', '/signature', [
'methods' => 'POST',
'callback' => function($r) {
$sid = get_option('kexin_vod_secret_id', '');
$skey = get_option('kexin_vod_secret_key', '');
$sub = get_option('kexin_vod_sub_app_id', '1500036857');
if (empty($sid) || empty($skey)) {
return new WP_Error('no_keys', '请先配置腾讯云密钥', ['status' => 500]);
}
$now = time();
$exp = $now + 7200;
$proc = get_option('kexin_vod_procedure', '');
$params = [
'secretId' => $sid,
'currentTimeStamp' => $now,
'expireTime' => $exp,
'random' => rand(0, 4294967295),
'classId' => 0,
'oneTimeValid' => 0,
'vodSubAppId' => $sub,
];
if (!empty($proc)) $params['procedure'] = $proc;
$original = http_build_query($params);
$hmacBin = hash_hmac('sha1', $original, $skey, true);
$signature = base64_encode($hmacBin . $original);
return ['success' => true, 'signature' => $signature, 'vodSubAppId' => $sub];
},
'permission_callback' => '__return_true',
]);
// 保存视频记录
register_rest_route('kexin-vod/v1', '/save', [
'methods' => 'POST',
'callback' => function($r) {
$videos = get_option('kexin_vod_videos', []);
$videos[] = [
'file_id' => sanitize_text_field($r->get_param('file_id')),
'name' => sanitize_text_field($r->get_param('name')),
'size' => intval($r->get_param('size')),
'time' => current_time('mysql'),
];
update_option('kexin_vod_videos', $videos);
return ['success' => true, 'count' => count($videos)];
},
'permission_callback' => function() { return current_user_can('edit_posts'); },
]);
// 列出视频
register_rest_route('kexin-vod/v1', '/list', [
'methods' => 'GET',
'callback' => function() {
return ['success' => true, 'videos' => get_option('kexin_vod_videos', [])];
},
'permission_callback' => '__return_true',
]);
// 同步云端视频(安全模式:只标记,不自动删除)
register_rest_route('kexin-vod/v1', '/sync', [
'methods' => 'POST',
'callback' => function() {
$sid = get_option('kexin_vod_secret_id', '');
$skey = get_option('kexin_vod_secret_key', '');
$sub = get_option('kexin_vod_sub_app_id', '1500036857');
$videos = get_option('kexin_vod_videos', []);
if (empty($sid) || empty($skey)) {
return ['success' => false, 'message' => '请先配置密钥'];
}
if (empty($videos)) return ['success' => true, 'valid' => 0, 'removed' => 0, 'message' => '本地无记录'];
// Collect all file IDs
$fileIds = array_map(function($v) { return $v['file_id']; }, $videos);
// Check each file ID against VOD API
$validIds = [];
foreach (array_chunk($fileIds, 20) as $chunk) {
$result = kexin_vod_api_call($sid, $skey, $sub, 'DescribeMediaInfos', ['FileIds' => $chunk]);
if ($result && isset($result['MediaInfoSet']) && !isset($result['Error'])) {
foreach ($result['MediaInfoSet'] as $media) {
$validIds[] = $media['FileId'];
}
}
// If API fails for a chunk, skip that chunk (don't delete its records)
}
// SAFETY: Only remove records that were CONFIRMED non-existent by a SUCCESSFUL API call
// If no API calls succeeded (validIds empty but we had files), don't touch anything
$checkedCount = count($validIds);
if ($checkedCount === 0 && empty($videos)) {
return ['success' => true, 'valid' => 0, 'removed' => 0, 'message' => '本地无记录'];
}
if ($checkedCount === 0) {
return ['success' => true, 'valid' => 0, 'removed' => 0, 'missing' => count($fileIds), 'message' => '校验完成:本地记录有效0个,云端文件可能已被删除'];
}
$removed = 0;
$newVideos = [];
foreach ($videos as $v) {
if (in_array($v['file_id'], $validIds) || !in_array($v['file_id'], $fileIds)) {
// Keep if: confirmed valid OR wasn't checked
$newVideos[] = $v;
} else {
// Only remove if: was checked AND was NOT found in valid set
// But wait - we can't distinguish "checked and not found" from "not checked"
// So we only keep records. Manual delete is safer.
$newVideos[] = $v;
}
}
// Actually: NEVER auto-delete. Just report status.
$missing = array_diff($fileIds, $validIds);
return [
'success' => true,
'valid' => $checkedCount,
'removed' => 0,
'missing' => count($missing),
'message' => '云端有效 ' . $checkedCount . ' 个,本地 ' . count($videos) . ' 个。用删除按钮手动清理。'
];
},
'permission_callback' => '__return_true',
]);
// 调试:返回生成的psign
register_rest_route('kexin-vod/v1', '/debug-psign', [
'methods' => 'GET',
'callback' => function() {
$pkey = get_option('kexin_vod_pkey', 'NOT SET');
$app = get_option('kexin_vod_app_id', '1500036857');
$fid = '5001834805555162521'; // official test file
$psign = kexin_vod_player_sign($fid, $app, $pkey);
return [
'pkey' => $pkey,
'pkey_len' => strlen($pkey),
'appId' => $app,
'fileId' => $fid,
'psign' => $psign,
'official_psign' => 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhcHBJZCI6MTUwMDAzNjg1NywiZmlsZUlkIjoiNTAwMTgzNDgwNTU1NTE2MjUyMSIsImN1cnJlbnRUaW1lU3RhbXAiOjE3ODAxNDYyMzMsImNvbnRlbnRJbmZvIjp7ImF1ZGlvVmlkZW9UeXBlIjoiT3JpZ2luYWwiLCJpbWFnZVNwcml0ZURlZmluaXRpb24iOjEwfSwidXJsQWNjZXNzSW5mbyI6eyJkb21haW4iOiJ2LnBzc3RlLmNvbSIsInNjaGVtZSI6IkhUVFBTIn19.a3ButwrP9s-O-uo6zuYyN1jVyimIxQjYRCML73-QZ2Y',
'header_match' => (explode('.', $psign)[0] ?? '') === 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9',
];
},
'permission_callback' => '__return_true',
]);
// 删除视频记录
register_rest_route('kexin-vod/v1', '/delete', [
'methods' => 'POST',
'callback' => function($r) {
$fid = $r->get_param('file_id');
$videos = get_option('kexin_vod_videos', []);
$videos = array_filter($videos, function($v) use ($fid) { return ($v['file_id'] ?? '') !== $fid; });
update_option('kexin_vod_videos', array_values($videos));
return ['success' => true];
},
'permission_callback' => function() { return current_user_can('edit_posts'); },
]);
});
// ====== 腾讯云 API 调用 (TC3 签名) ======
function kexin_vod_api_call($secretId, $secretKey, $subAppId, $action, $params) {
$service = 'vod';
$host = 'vod.tencentcloudapi.com';
$version = '2018-07-17';
$region = 'ap-guangzhou';
$timestamp = time();
$date = gmdate('Y-m-d', $timestamp);
$payload = json_encode($params);
// TC3-HMAC-SHA256 signing
$canonicalHeaders = "content-type:application/json\nhost:{$host}\n";
$signedHeaders = 'content-type;host';
$hashedPayload = hash('sha256', $payload);
$canonicalRequest = "POST\n/\n\n{$canonicalHeaders}\n{$signedHeaders}\n{$hashedPayload}";
$algorithm = 'TC3-HMAC-SHA256';
$credentialScope = "{$date}/{$service}/tc3_request";
$stringToSign = "{$algorithm}\n{$timestamp}\n{$credentialScope}\n" . hash('sha256', $canonicalRequest);
$secretDate = hash_hmac('sha256', $date, 'TC3' . $secretKey, true);
$secretService = hash_hmac('sha256', $service, $secretDate, true);
$secretSigning = hash_hmac('sha256', 'tc3_request', $secretService, true);
$signature = hash_hmac('sha256', $stringToSign, $secretSigning);
$authorization = "{$algorithm} Credential={$secretId}/{$credentialScope}, SignedHeaders={$signedHeaders}, Signature={$signature}";
$headers = [
'Authorization' => $authorization,
'Content-Type' => 'application/json',
'Host' => $host,
'X-TC-Action' => $action,
'X-TC-Version' => $version,
'X-TC-Timestamp' => (string)$timestamp,
'X-TC-Region' => $region,
];
if (!empty($subAppId) && $subAppId !== '0') {
$headers['X-TC-SubAppId'] = $subAppId;
}
$response = wp_remote_post("https://{$host}", [
'headers' => $headers,
'body' => $payload,
'timeout' => 20,
'sslverify' => true,
]);
if (is_wp_error($response)) {
return ['Error' => ['Message' => 'HTTP Error: ' . $response->get_error_message()]];
}
$body = wp_remote_retrieve_body($response);
$code = wp_remote_retrieve_response_code($response);
$data = json_decode($body, true);
if ($code !== 200 || !$data) {
return ['Error' => ['Message' => "HTTP {$code}: " . substr($body, 0, 200)]];
}
if (isset($data['Response']['Error'])) {
return ['Error' => $data['Response']['Error']];
}
return $data['Response'] ?? ['Error' => ['Message' => 'Empty response']];
}
// ====== 播放签名 psign ======
function kexin_vod_player_sign($fileId, $appId, $pkey) {
$now = time();
$header = ['alg' => 'HS256', 'typ' => 'JWT'];
$payload = [
'appId' => intval($appId),
'fileId' => (string)$fileId,
'currentTimeStamp' => $now,
'contentInfo' => ['audioVideoType' => 'Original', 'imageSpriteDefinition' => 10],
'urlAccessInfo' => ['domain' => 'v.psste.com', 'scheme' => 'HTTPS'],
];
$b64h = rtrim(strtr(base64_encode(json_encode($header)), '+/', '-_'), '=');
$b64p = rtrim(strtr(base64_encode(json_encode($payload, JSON_UNESCAPED_SLASHES)), '+/', '-_'), '=');
$sign = rtrim(strtr(base64_encode(hash_hmac('sha256', "$b64h.$b64p", $pkey, true)), '+/', '-_'), '=');
return "$b64h.$b64p.$sign";
}
// ====== 短代码:TCPlayer ======
add_shortcode('kexin_video', function($atts) {
$a = shortcode_atts(['id' => '', 'width' => '100%', 'psign' => ''], $atts);
if (empty($a['id'])) return '<p style="color:#dc2626">请指定视频 FileID</p>';
$fid = esc_attr($a['id']);
$app = esc_attr(get_option('kexin_vod_app_id', '1500036857'));
$pkey = get_option('kexin_vod_pkey', '');
// 自动生成播放签名
$psign_value = '';
if (!empty($a['psign'])) {
$psign_value = esc_attr($a['psign']);
} elseif (!empty($pkey)) {
$psign_value = kexin_vod_player_sign($fid, $app, $pkey);
}
$pid = 'tcplayer_' . uniqid();
ob_start();
?>
<video id="<?php echo $pid; ?>" preload="auto" playsinline webkit-playsinline x5-playsinline
style="width:100%;max-width:<?php echo esc_attr($a['width']); ?>;aspect-ratio:16/9;background:#000;border-radius:8px"></video>
<script>
(function() {
var pid = '<?php echo $pid; ?>';
var fid = '<?php echo $fid; ?>';
var app = '<?php echo $app; ?>';
function initPlayer() {
var opts = { fileID: fid, appID: app, autoplay: false };
var psignVal = '<?php echo $psign_value; ?>';
if (psignVal) opts.psign = psignVal;
if (typeof TCPlayer !== 'undefined') {
TCPlayer(pid, opts);
} else {
(window.TCPlayerInitQueue = window.TCPlayerInitQueue || []).push(function() {
TCPlayer(pid, opts);
});
var retryTimer = setInterval(function() {
if (typeof TCPlayer !== 'undefined') {
clearInterval(retryTimer);
var queue = window.TCPlayerInitQueue || [];
while (queue.length) { (queue.shift())(); }
window.TCPlayerInitQueue = [];
}
}, 300);
setTimeout(function() { clearInterval(retryTimer); }, 15000);
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initPlayer);
} else {
initPlayer();
}
})();
</script>
<?php
return ob_get_clean();
});
// ====== 前端加载 TCPlayer ======
add_action('wp_enqueue_scripts', function() {
wp_enqueue_style('tcplayer-css', 'https://tcsdk.com/player/tcplayer/release/v5.3.4/tcplayer.min.css', [], KEXIN_VOD_VER);
wp_enqueue_script('tcplayer-js', 'https://tcsdk.com/player/tcplayer/release/v5.3.4/tcplayer.v5.3.4.min.js', [], KEXIN_VOD_VER, false);
});
涂料工厂|油漆厂家 – 集研发、生产、销售为一体的专业涂料制造企业,直供工业漆、汽车漆、木器漆、建筑漆、纳米涂层,一站式涂料解决方案。
中国 · 广东佛山 · 涂料工厂直供
客信新材料
工业涂料解决方案提供商
专注工业漆、汽车漆、木器漆、建筑漆、纳米涂层研发与制造。涂料工厂直供,以卓越品质为全球客户提供高性能涂层解决方案。
Products
全品类涂料产品矩阵
覆盖工业制造、汽车交通、建筑装饰、家具家居、电镀合金、特种防护等领域,提供一站式涂料解决方案
工业漆系列
环氧富锌底漆、聚氨酯面漆、丙烯酸防腐漆、耐高温漆等,广泛用于钢结构、机械设备、管道储罐等重防腐领域。
防腐涂料耐高温漆环氧漆
了解详情 →
汽车漆系列
汽车原厂漆、汽车修补漆、轮毂专用漆、塑料件涂料,色彩精准匹配,耐候性能优异,满足OEM及后市场需求。
原厂漆修补漆轮毂漆
了解详情 →
木器漆系列
PU木器漆、水性木器漆、UV光固化漆、NC硝基漆,适用于家具制造、地板涂装、木艺工艺品等场景。
水性木器漆UV光固化家具漆
了解详情 →
建筑漆系列
内外墙乳胶漆、真石漆、多彩仿石涂料、质感艺术漆、防水涂料,为建筑提供美观耐用、绿色环保的涂装方案。
内外墙漆真石漆防水涂料
了解详情 →
纳米涂层系列
纳米防指纹油、纳米隔热涂料、超疏水自清洁涂层、耐磨损纳米涂层,应用于3C电子、光伏玻璃、医疗器械等高端领域。
纳米防指纹隔热涂层超疏水
了解详情 →
电镀漆系列
电镀银镜面漆、镀铬效果漆、仿电镀金漆、电镀光油,呈现金属电镀质感,广泛应用于汽车配件、卫浴五金、家电外壳等高装饰性领域。
电镀银镀铬效果镜面漆
了解详情 →
合金漆系列
铝合金专用漆、锌合金烤漆、镁合金防腐漆、不锈钢表面处理涂料,针对不同合金基材研发,附着力强、耐盐雾性能优异。
铝合金漆锌合金烤漆镁合金防腐
了解详情 →
塑料漆系列
PP专用漆、ABS塑料漆、PC透明件涂料、尼龙处理剂配套涂料,解决塑料基材附着力难题,广泛应用于汽车内饰件、家电外壳、玩具制品等。
PP漆ABS塑料漆尼龙涂料
了解详情 →
特殊油漆系列
耐高温漆、绝缘漆、防火涂料、反光漆、荧光漆、夜光漆、导电漆、防静电漆等特种功能涂料,满足极端工况和特殊性能需求的定制化解决方案。
耐高温漆防火涂料特种功能漆
了解详情 →
Factory
生产基地 · 制造实力
现代化生产车间与严格品控体系,确保每一批次产品稳定可靠
Why Choose Us
源头工厂 · 品质保障 · 服务领先
选择客信,即是选择稳定可靠的产品质量、专业及时的技术支持和长期共赢的合作关系
自主研发实力
拥有专业研发实验室和资深技术团队,持续投入配方研发与工艺改进,可根据客户需求定制专属涂料方案。
严苛质量管控
从原料采购到成品出厂,每一道工序均严格执行国际质量标准,批次色差控制精准,品质稳定可靠。
快速响应交付
现代化生产基地配备全自动生产线,产能充足,库存管理科学,确保订单快速响应、准时交付。
绿色环保理念
践行环保责任,持续提升水性涂料、高固含涂料占比,产品符合RoHS、REACH等国际环保法规要求。
专业技术服务
提供从涂装方案设计、现场试样指导到售后跟踪的全流程技术服务,让客户用得放心、省心。
全球市场覆盖
产品远销东南亚、中东、非洲、南美等30多个国家和地区,获得海内外客户广泛认可与信赖。
Certifications
资质认证
多项权威认证与专利技术,品质值得信赖
R&D Innovation
新材料研发实力
客信新材料设立专业研发实验室,拥有由资深工程师领衔的技术团队,与多所高校建立产学研合作关系。持续投入纳米材料、水性环保涂料等前沿领域研究,已获得50余项技术专利。
实验室配备盐雾试验箱、QUV老化仪、光谱分析仪等先进检测设备,确保从配方开发到产品验证的全流程科学管控。
Applications
覆盖全行业的涂装解决方案
从重工业防腐到精密电子涂层,从建筑外墙装饰到汽车车身涂装,客信涂料深入每一个应用场景
Join Us
招商加盟 · 共赢未来
诚邀各地区经销商合作伙伴,共享客信品牌价值与市场红利。品牌授权、区域保护、利润保障、全程扶持。
申请成为经销商 →
Cases & Partners
工程案例 · 合作伙伴
累计服务500+企业客户,覆盖汽车、建筑、工业制造等全行业领域
PARTNER 01
PARTNER 02
PARTNER 03
PARTNER 04
PARTNER 05
PARTNER 06
某大型汽车零部件企业涂装线
汽车漆 · 底盘防腐解决方案
华南某跨海大桥钢结构防腐项目
工业重防腐漆 · C5-M环境等级
知名家具品牌水性涂装产线升级
水性木器漆 · 零VOC环保方案
FAQ
常见问题
快速解答您的疑问,如未找到答案请直接联系我们
如何选择合适的工业涂料?+
选择工业涂料需要综合考虑四个维度:
1. 基材类型——钢材、铝材、不锈钢、塑料、木材等不同基材需匹配对应底漆体系,确保附着力达标。
2. 使用环境——室内/室外、酸碱腐蚀、高温高湿、紫外线强度等决定防腐等级和耐候年限。
3. 性能指标——盐雾小时数、耐温范围、硬度、柔韧性、光泽度等需根据实际工况确定。
4. 施工方式——喷涂、刷涂、浸涂、电泳等不同工艺匹配不同粘度和干燥速度。
不确定如何选择?致电 400-901-0757,技术团队免费提供选型建议和打样服务。
纳米涂层与传统涂料有什么区别?+
两者核心区别在于成膜机理和防护性能:
1. 涂层厚度——传统涂料通常需要50-200微米才能达到防护效果,纳米涂层仅需5-15微米即可实现同等甚至更优的防护。
2. 疏水自清洁——纳米涂层水接触角可达110°以上,水滴滚落时带走表面灰尘污垢,实现"自清洁"效果,传统涂料难以达到。
3. 耐高温性能——纳米陶瓷涂层可耐受800℃以上高温,远超传统有机涂料的200-300℃极限。
4. 硬度与耐磨——纳米涂层铅笔硬度可达6H-9H,抗划伤能力是传统涂料的3-5倍。
5. 环保性——纳米涂层多为无溶剂或水性体系,VOC排放极低,符合最新环保法规。
客信纳米涂层已批量应用于3C电子、光伏玻璃、医疗器械等高端领域。
最小起订量是多少?+
常规产品(有库存):工业漆/建筑漆最低20kg起订,汽车漆/木器漆最低10kg起订。
定制产品(配色/特殊配方):根据配方开发难度和原材料最低采购量确定,通常在50-200kg之间。
纳米涂层:高端功能涂层起订量灵活,可小至1kg试样,支持小批量定制验证。
样品申请:支持免费或付邮样品,常规样品48小时内安排寄出。
大批量订单价格从优,具体请联系销售顾问:13531350189 获取详细报价单。
可以申请免费样品吗?+
可以。客信支持样品申请服务,具体政策如下:
免费样品——常规标准化产品提供免费样品(100g-500g),客户承担快递费用,到付寄出。
付费样品——定制配方或特殊色号产品收取少量打样费(100-500元不等),正式大货订单可全额抵扣样品费。
样品周期——常规样品48小时内安排寄出;定制打样需3-7个工作日,急单可加急处理。
申请方式——致电 13531350189 或发送邮件至 service@pzsss.com,注明公司名称、需求产品类型和使用场景。
样品附带TDS(技术数据表)和MSDS(安全数据表),方便您进行测试评估。
交货周期是多久?+
交期根据产品类型和订单量有所不同:
常规现货产品——确认订单后3-7个工作日安排发货,全国物流覆盖,华南地区1-2天到货。
定制配色产品——配色打样确认后7-15个工作日完成生产,大批量订单(1吨以上)交期另议。
特殊配方开发——新配方从研发到量产通常需要15-30个工作日,含小试、中试、性能验证全流程。
加急订单——支持加急排产(收取加急费用),最快3个工作日内出货。
工厂位于广东佛山,毗邻广州港和深圳港,海运出口便捷,支持FOB/CIF贸易条款。
售后保障政策是什么?+
客信建立完善的售后服务体系,确保客户无后顾之忧:
1. 到货验收——每批次产品附出厂检测报告(COA),客户可对标验收,质量异议24小时内响应。
2. 施工指导——提供产品施工工艺说明书,可安排技术工程师免费上门进行施工培训和现场指导(珠三角地区)。
3. 质量承诺——产品在保质期内(通常12个月)如出现非施工原因的质量问题,经双方确认后可退换货处理。
4. 技术咨询——售后专线 400-901-0757(7×24小时),技术团队随时解答涂装工艺、产品适配等专业问题。
5. 定期回访——合作客户享受定期使用情况回访,主动发现并解决潜在问题。
客信的回头客率达85%以上,是对我们品质与服务的最好证明。
涂料工厂
油漆厂家
工业漆生产厂家
汽车漆厂家
木器漆厂家
建筑漆厂家
纳米涂层厂家
防腐涂料工厂
水性涂料厂家
佛山涂料厂
广东涂料生产厂家
环氧漆厂家
汽车修补漆
木器家具漆
内外墙涂料厂家