ASP+JSON实现视频试看:播放路径分层与token签名防盗链实战
简介这是一份用于构建「免费试看付费订阅」模式视频网站的完整源码包面向PHP开发者、前端工程师及需要搭建视频内容付费预览功能的站长群体。压缩包总计2806个文件主要包含PHP、HTML、JS、CSS等前后端代码另有GIF/PNG/JPG图片素材、SQL数据库脚本、字体及配置文件压缩包整体约36.95MB。目前已有1096人学习下载说明其在视频试看开发场景中具备不错的参考价值。源码覆盖视频试看时长控制、用户角色权限区分、第三方支付接口对接、HLS/DASH视频流播放等核心环节同时内置文件管理、上传接口、演示页面等示例便于开发者拆分模块学习并快速理解试看收费逻辑。还包括性能优化、SEO、版权防护等实现思路目录结构清晰是一套适合从入门到进阶的视频站点开发与内容变现参考方案。1. 试看的核心不是剪文件而是把播放路径拆成两段做视频网站的人最容易踩的坑是以为“免费试看”等于把视频裁掉前几秒。真正能上线、不被人抓包撬走的试看收费源码核心在播放路径的权限分层未付费用户拿到的是带时间锁、带签名、只包含前 N 秒分片的播放地址付费用户拿到的是完整播放地址两者物理上指向同一个视频文件但请求链路完全分开。这种设计的直接收益是试看逻辑不动视频文件就不会破坏 CDN 缓存也不会让“试看部分”通过拖拽播放器进度条被直接跳过。本文拆解的是这套以 ASP JSON 接口层为骨架的试看视频网站源码适合正在做视频付费业务、需要给现有站点嵌入试看能力或者想把自己手上的播放器代码改造成“前端播放器 后端地址鉴权”结构的开发者。zip 里没有完整的会员系统它给的是文件管理、上传和 JSON 交互这层地基怎么把试看做上去、做不穿正是下面的重点。2. 先看懂这套源码的骨架上传类与 JSON 接口才是核心2.1 从 zip 内的文件名判断技术栈和结构解压后看到UpLoad_Class.asp、file_manager_json.asp、upload_json.asp、JSON_2.0.4.asp、demo.asp、demo.aspx这组文件第一判断不是“这是一个完整视频网站”而是一个典型的内容管理后端ASP 处理上传逻辑JSON 类做数据交互ASPX 文件负责演示同一个上传接口在不同宿主下的表现。这套源码的边界要认清。文件职能协议/格式在试看场景中的角色UpLoad_Class.asp处理上传请求、保存文件multipart/form-data视频文件入库的入口file_manager_json.asp / .ashx遍历目录、返回文件列表JSON后台管理端选择视频文件upload_json.asp / .ashx接收上传并返回文件路径JSON试看片段/封面图写入JSON_2.0.4.aspJSON 序列化与解析JSON前端请求和后台接口的数据契约demo.asp / demo.aspx演示上传与文件管理效果HTML/JS可用来快速确认上传接口是否可用这里的.asp和.ashx同时出现说明服务器是 IIS并且同时启用了 ASP 和 ASP.NET 处理能力。file_manager_json这一类接口的返回结构一般是{ total: 12, files: [ { filename: trial_demo.mp4, is_dir: false, size: 20488301, datetime: 2024-11-03 10:22:01 } ] }返回值里的total是文件总数files是当前目录下的文件数组is_dir用于区分目录和文件前端拿到这个结果后渲染文件列表。上传接口返回的则是文件保存后的相对路径例如/upload/video/202411/xxxx.mp4。这个路径就是后续播放器要用的视频地址但它不能直接被当成播放地址暴露原因在下面一节。2.2 视频试看的落点文件管理器不能直接暴露原文件路径file_manager_json这类接口的定位是管理端工具不是播放鉴权网关。问题在于很多二次开发会直接把接口里返回的文件路径拼到video标签的src上结果就是完整视频可以被任何人拿到地址后下载。试看视频网站源码里文件管理器和播放鉴权必须分开文件管理器负责视频上传、目录浏览、封面管理、存储路径维护。播放鉴权负责当前用户是否有权看完整视频、试看时长是多少、请求的播放地址有没有过期。视频文件本身不放在upload/这种静态目录下而是放在带校验逻辑的/vod/路径下由服务端脚本先验签再决定是否输出内容。这个分层在 ASP 里落地时常见做法是给视频信息加一张业务表而不是直接用文件系统的目录名来区分试看资源。表设计我一般这样建CREATE TABLE video_info ( video_id INT PRIMARY KEY IDENTITY(1,1), file_path NVARCHAR(255) NOT NULL, -- 存储服务器上的物理路径 play_seconds INT DEFAULT 0, -- 视频总时长单位秒 trial_seconds INT DEFAULT 30, -- 试看时长单位秒 cover_path NVARCHAR(255), -- 封面图路径 price_month DECIMAL(10,2) DEFAULT 0, -- 月度订阅价 status TINYINT DEFAULT 1 -- 1上架 0下架 );字段trial_seconds存的是管理员设定的试看秒数后端的试看切片数量、播放器端的倒计时都以它为准。file_path不直接返回给前端前端拿到的永远是播放代理地址例如/vod/play.asp?vid1001tokenxxx。这样做的直接好处是即使播放地址泄露token 过期后请求就失效不会因为文件真实路径被扒出来而彻底不可控。2.3 为什么上传接口和播放授权要一起改只看源码包不改造upload_json.asp会把文件原封不动存进磁盘。对于视频试看场景这个逻辑要加一道转码钩子上传完成后触发 FFmpeg 切片任务生成 HLS 分片同时把/vod/下的访问权限交给鉴权脚本。这一层不加上后面所有 token、签名、防盗链都无从谈起。从这个源码包出发合理的改造顺序是先确认上传接口能正常工作再给它配置视频目录的访问规则最后才是写播放鉴权脚本。实际开发里很多团队把顺序倒过来先写播放器代码再回头做上传结果接口返回的地址始终在静态目录下鉴权脚本形同虚设。这套源码最值得借鉴的点是上传层和文件管理层已经完整你只需要在它上面再做一层播放网关就能把一个内容管理工具变成试看收费源。3. 试看 token 的生成、流向与校验边界3.1 试看不是截断文件把播放地址变成一段有时间锁的入口很多初版实现把试看做成“后端返回视频的 30 秒切片”这样做的第一问题是转码成本高第二问题是用户拿到切片地址后绕过播放器直接下载切片并拼起来照样能拼出完整内容。正确的思路是物理文件不切但播放地址带上试看边界。具体做法是把播放地址拆成两层控制URL 层签名/vod/play.asp后面带expires时间戳和sign签名服务端校验签名有效后才开始输出视频流。流内容层限制对于 HLS 协议服务端动态生成的 m3u8 文件里只列出前 N 个 ts 分片对于 HTTP 伪流播放后端针对 Range 请求做截断只允许输出试看时长内的字节段。对 5 年以上经验的开发来说更值得关注的是第二层。m3u8 文件本质是文本任何人都能直接打开看里面有哪几个分片。如果服务端只是把完整 m3u8 返回给前端再靠播放器currentTime做弹窗那用户自己抓包拿到的就是完整分片列表试看机制当场失效。所以流内容层的限制必须服务端做前端弹窗只是提升付费转化体验的辅助手段。3.2 生成签名播放地址的 ASP 实现先定义签名规则。签名的目的是让播放地址在有效期内可被服务端验证为用户本人请求并且防止用户篡改试看时长。签名串我用vid uid expires secret做 MD5原因是 ASP 环境下 MD5 实现简单不需要额外引入加密服务Secret 只保存在服务端前端拿不到。 build_watch_url.asp 参数: videoId 视频ID, userId 用户ID, expires 过期时间戳, secret 服务端密钥 Function BuildWatchUrl(videoId, userId, expires, secret) raw vid videoId uid userId expires expires sign MD5(raw key secret) BuildWatchUrl /vod/play.asp? raw sign sign End Function逻辑说明raw是把参与鉴权的明文参数按固定顺序拼接顺序一旦变化签名结果完全不同这可以防止有人增删参数sign是拼接上服务端密钥后的 MD5 摘要返回的播放地址带expires时间戳服务端每次校验时重新计算签名做比对。这个函数本身不涉及用户认证它只负责生成 URL。用户是否已登录、是免费用户还是订阅用户在调用BuildWatchUrl之前就要判断完成。通常我会再包一层GetPlayableUrl(videoId, userId)先查用户状态再根据用户等级决定expires的长度免费用户给试看时长订阅用户给 24 小时的完整访问时长。服务端校验脚本是另一段逻辑核心流程如下 vod/play.asp Dim expires, sign, vid, uid expires Request.QueryString(expires) sign Request.QueryString(sign) vid Request.QueryString(vid) uid Request.QueryString(uid) If expires Or sign Then Response.Status 403 Forbidden Response.End End If 当前时间超过 expires直接拒绝 If CLng(expires) CLng(DateDiff(s, 1970-01-01 00:00:00, Now())) Then Response.Status 410 Gone Response.End End If 重新计算签名并比对 Dim secret, raw, expectSign secret your_site_secret_key 实际应放在 include 文件或配置里 raw vid vid uid uid expires expires expectSign MD5(raw key secret) If StrComp(expectSign, sign, vbTextCompare) 0 Then Response.Status 403 Forbidden Response.End End If 通过校验后输出视频流或 m3u8 内容参数说明expires是 Unix 时间戳校验时先和服务器当前时间比较超时直接返回 410这样抓包拿到的旧地址会自动失效sign比对的重点是要用vbTextCompare避免因为大小写问题误伤客户端Response.Status使用标准 HTTP 状态码让前端播放器能直接感知失败而不是收到一段 HTML 错误页。这段代码看起来简单实际生产最容易漏的是“只校验 sign 不校验 expires”或者“只校验 expires 不重算签名”。前者导致地址永久有效后者导致用户手动改 expires 就能延长试看时间。两者必须同时成立试看边界才算封住。3.3 试看 m3u8 与分片授权的组合播放页面通过鉴权后接着要按用户类型输出不同的播放列表。我在这里用动态 m3u8 的方式控制试看分片数量% vod/m3u8.asp 参数: vid 视频ID, uid 用户ID, token 由 play.asp 下发的临时令牌 Dim trialSeconds, segDur, segCount, i trialSeconds 30 试看秒数从 video_info 表读取 segDur 6 每个 ts 分片时长与 FFmpeg 切分参数一致 segCount Int(trialSeconds / segDur) Response.ContentType application/vnd.apple.mpegurl Response.Write(#EXTM3U vbCrLf) Response.Write(#EXT-X-VERSION:3 vbCrLf) Response.Write(#EXT-X-TARGETDURATION: segDur vbCrLf) For i 0 To segCount - 1 Response.Write(#EXTINF: segDur , vbCrLf) Response.Write(seg_ Request.QueryString(vid) _ i .ts?token Request.QueryString(token) vbCrLf) Next Response.Write(#EXT-X-ENDLIST vbCrLf) %逻辑说明trialSeconds除以segDur得到试看分片数量segCount决定播放器最多能拉几个 ts 文件#EXT-X-ENDLIST必须输出否则播放器会认为直播流还在持续一直请求后续分片。这里的参数联动是这套逻辑的关键FFmpeg 切片时如果用-hls_time 6那么segDur必须写 6不能凭感觉写 10。分片时长和 m3u8 里的#EXTINF不一致时播放器会出现进度条跳变、最后几秒黑屏这类难排查的问题。ts 分片本身的 token 可以比 m3u8 的 token 有效期更短比如 m3u8 有效期 60 秒分片 token 有效期 30 秒因为分片地址是播放器在播放中动态拉取的前端感知不到那个极短的过期时间但爬虫想批量抓取 ts 地址就会频繁碰壁。3.4 试看记录与重复试看控制签名机制只是保护了地址管不住“同一个用户反复试看不同视频”或者“退出登录换个账号再试看”这种行为。做试看收费源码时需要一张试看记录表来兜底CREATE TABLE trial_auth_log ( id INT PRIMARY KEY IDENTITY(1,1), session_key NVARCHAR(64) NOT NULL, video_id INT NOT NULL, trial_started_at DATETIME DEFAULT GETDATE(), token NVARCHAR(64), expired_at DATETIME );session_key建议直接用 ASP 的 SessionID 加 UserID 拼接不要只依赖 SessionID因为 IIS 回收后 SessionID 会变用户重新打开页面还能再试看一次expired_at写入GETDATE() trial_seconds 秒。查询时先看这张表里是否已经有未过期的同视频记录有就直接复用 token没有再生成新的。业务上比较合理的限制是同一浏览器 24 小时内只能试看同一视频一次同一账号对同一视频的试看次数全局唯一。这样做既避免了体验被过度消耗也减少了盗录者反复刷新获取完整分片的机会。4. 播放器侧试看衔接与 HLS 分发防盗设计4.1 前端播放器倒计时和蒙层只是体验层后端权限做完之后前端要做的事反而是轻的。以 HLS 播放为例我使用hls.js播放动态 m3u8 地址并通过timeupdate事件驱动付费引导层的出现// player.js const video document.getElementById(video-main) const payLayer document.getElementById(pay-layer) if (Hls.isSupported()) { const hls new Hls() hls.loadSource(/vod/m3u8.asp?vid1001uiduser_01tokenxxx) hls.attachMedia(video) video.addEventListener(timeupdate, () { const trialSeconds 30 const remain trialSeconds - video.currentTime if (remain 5 remain 0) { payLayer.style.display block // 提前 5 秒展示付费引导 } if (remain 0) { video.pause() // 本地暂停但真正的拦截靠服务端 } }) }逻辑说明hls.loadSource加载的是动态 m3u8 地址不是完整视频地址timeupdate每 250ms 触发一次用剩余 5 秒作为支付弹层的提前量是为了让用户看到剧情关键帧时产生付费冲动播放器pause()只是体验兜底一旦服务端分片列表已经结束播放器无论如何都拉不到下一个分片。前端有两点不要做过度不要在 JS 里写死试看秒数去截断视频因为currentTime是可以被修改的也不要把完整 m3u8 地址在页面源码里注释出来Chrome 开发者工具看源码就能翻到。服务端对分片数量和签名的校验才是最终裁决。4.2 nginx 层的防盗链与 token 校验只靠 ASP 脚本校验签名会有一个漏洞如果 ts 分片直接放在/vod/ts/静态目录下用户绕过 m3u8直接按文件名规律请求分片一样能拼出完整视频。所以分片目录要配 nginx 的 secure_link 模块再做一层校验location ^~ /vod/ts/ { secure_link $arg_st,$arg_et; secure_link_md5 $secure_link_expires$uri your_secret_key; if ($secure_link ) { return 403; } if ($secure_link 0) { return 410; } } location ^~ /vod/pub/ { valid_referers none blocked server_names *.example.com; if ($invalid_referer) { return 403; } }参数说明$arg_st和$arg_et分别对应 URL 上的 md5 签名和过期时间参数secure_link_md5的拼接规则必须和 ASP 端生成签名时的规则完全一致否则会出现前端播放正常、但分片请求全部 403expires值超时后返回 410告诉播放器这个分片已经失效不要缓存。而/vod/pub/目录用来放封面图这种不敏感静态资源用valid_referers做 Referer 防盗链日常成本最低。三种策略适用的场景差别很大放一张表方便对照策略校验位置适用场景边界与缺陷signexpires业务脚本播放页、m3u8、试看入口密钥泄露会全盘失效需定期换secure_linknginxts 分片、mp4 直链签名规则和业务侧强耦合valid_referersnginx封面图、CSS、JS空 Referer 会被绕过不适用视频流我之前在项目里遇到过的典型事故是上线前只测了播放页没测分片直接访问结果抓包拿到首个 ts 地址后手动访问居然返回 200。这就是典型的“入口鉴权、出口裸奔”m3u8 校验过了但分片目录没接 secure_link。把视频源放到受保护路径下并让所有分片都带签名访问才算闭环。4.3 上传接口和转码任务的衔接upload_json.asp目前只负责把文件传上来但视频网站要的不只是文件保存还要切片。FFmpeg 转码命令可以固定挂在上传成功后的回调流程里ffmpeg -i /upload/video/202411/demo.mp4 \ -c:v libx264 -profile:v main -crf 23 \ -c:a aac -b:a 128k \ -hls_time 6 -hls_list_size 0 \ -hls_segment_filename /vod/ts/seg_1001_%d.ts \ /vod/m3u8/1001.m3u8参数说明-hls_time 6控制每个分片长度为 6 秒和前面 m3u8 动态生成脚本里的segDur对应-hls_list_size 0表示生成全量分片列表不做滚动清理否则直播模式下旧的 ts 会被删除录播视频会播到一半断流-profile:v main是兼容性较好的 H.264 等级旧设备也能硬解。转码完成后再把分片目录交给 nginx secure_link 保护。整个过程不应该由 IIS 进程去执行 FFmpeg建议用独立的任务队列调度避免上传接口阻塞超时。5. 用压测与日志验证试看没有烧带宽再谈落地方案5.1 验证签名边界和试看有效性的脚本代码改完后不能只靠播放器点一下“看起来能播”就交付。我会用脚本模拟四类请求有效期内播放、过期后播放、伪造签名播放、直接请求不存在的分片。下面这个 Python 脚本可以快速验证签名的边界import requests import time import hashlib vid 1001 uid user_001 secret your_site_secret_key expires int(time.time()) 30 raw fvid{vid}uid{uid}expires{expires} sign hashlib.md5((raw key secret).encode()).hexdigest() play_url fhttp://127.0.0.1/vod/play.asp?{raw}sign{sign} # 有效期内请求预期 200 r1 requests.get(play_url, timeout5) print(valid request:, r1.status_code, len(r1.content)) # 等待 31 秒后同地址请求预期 410 或 403 time.sleep(31) r2 requests.get(play_url, timeout5) print(expired request:, r2.status_code) # 篡改 expires 为更长时间预期校验失败 fake_expires str(int(time.time()) 3600 * 24) fake_raw fvid{vid}uid{uid}expires{fake_expires} r3 requests.get( fhttp://127.0.0.1/vod/play.asp?{fake_raw}sign{sign}, timeout5, ) print(tampered expires:, r3.status_code)逻辑说明r1验证正常签名和有效期r2验证过期后的状态码r3验证签名和参数绑定关系。r3是核心如果返回了 200 而不是 403说明验签时不带expires参与签名计算用户自己改过期时间即可无限延长试看这个漏洞必须当场修。带宽侧的验证统计口径是每千次播放消耗的流媒体字节数。如果某条视频的试看请求里ts 分片请求量超过trial_seconds / segDur 2说明播放器在试看结束后还在继续拉流日志里要能定位到具体 IP 和 session。审计日志建议至少记录video_id、token、ts_index、http_status、user_agent五个字段排查时先按video_id聚合再按ts_index排序很快能看出分片请求是不是超过了试看上限。5.2 把 ASP 上传类和 JSON 接口迁移到现代技术栈这套源码的上传与 JSON 交互结构放到现在仍然可以当作接口设计参考。UpLoad_Class.asp对应的是对象存储的预签名上传file_manager_json.asp对应 OSS 的文件列表接口JSON_2.0.4.asp对应框架内置的序列化组件。迁移时建议逐步替换不要让播放器同时依赖新旧两个域名UpLoad_Class.asp迁移为 OSS 预签名上传客户端直传对象存储回调服务端刷新video_info。file_manager_json.asp迁移为后端管理接口查询对象存储目录并返回带时效的预览地址。upload_json.asp迁移为转码任务触发器上传完成后投递消息队列由独立 Worker 调用 FFmpeg。播放鉴权维持签名思想但把 ASP 的 MD5 替换为 HMAC-SHA256并在 Redis 里对同一用户同一视频的试看请求做原子计数限流。token 的有效期建议设置成试看时长加 30 秒缓冲nginx 层对同一个用户对同一个视频的并发 ts 拉流做limit_req限制两件事叠加后免费试看基本不存在被批量抓取的可乘之机。本文还有配套的精品资源点击获取