ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

ASP经典技术实战:GM推广系统v6.0部署与归因开发解析

2026/9/16 15:09:33 拓冰建站 浏览量
ASP经典技术实战:GM推广系统v6.0部署与归因开发解析 简介这是一套基于ASP技术构建的游戏推广系统源码面向游戏管理员提供用户管理、推广链接追踪、数据分析、广告投放与奖励发放等一体化后台功能适合熟悉服务器端脚本开发的运营人员或学习者参考和二次开发。压缩包共四百九十个文件以动图、服务端脚本、交互脚本、页面文件与样式文件为主整体约一点七七兆字节目录结构清晰便于定位界面、逻辑与样式代码。目前已有一百四十八人学习下载。通过源码可重点研究管理员权限控制、推广链接点击与转化追踪、奖励发放时的数据校验逻辑以及配置文件、设置文件的初始化方式同时可自行调整界面风格与多语言实践接口对接和基础安全防护是一套可运行的实战型后台参考。1. 拆完这个GM专用推广系统 v6.0聊点ASP的实话拿到ASP源码—GM专用游戏推广系统 v6.0.zip解压后不是一堆.aspx而是install.axphp、axphp.config、几个 CSS 和 ASP 脚本。第一反应是“安装文件怎么不是 .asp”其实这类老系统为了绕开服务器默认文档限制常用自定义扩展名本质还是 ASP 脚本。GM 推广的核心诉求不是做漂亮官网而是给不同渠道、不同推广员发带标识的链接来源、点击、注册、首充一条线记下来结算佣金时有据可查。对今天仍跑私服页游一批项目来说这个 v6.0 比改 PHP 重构成本低得多。适合需要快速上手的运营、做老系统维护的工程师也适合想找一个最小推广归因模型来参考的开发者。2. IIS环境准备install.axphp、axphp.config和Access数据库跑通2.1 先让IIS认识.axphp扩展名在 Windows 11 上配置 IIS 和 ASP 算是最常被问到的一步装完之后C:\inetpub\wwwroot下放一个最简单的hello.asp浏览器访问能看到 ASP 执行结果环境才算通。安装命令用管理员 PowerShell 执行Enable-WindowsOptionalFeature -Online -FeatureName IIS-WebServerRole,IIS-WebServer,IIS-ASP -All这条命令会把 IIS 核心功能和 ASP 模块一起装好不需要重启系统。但 v6.0 的安装入口是install.axphp默认处理程序映射不认这个扩展直接访问会 404。常见做法是给站点加一条脚本映射请求路径*.axphp可执行文件指向%windir%\system32\inetsrv\asp.dll请求限制里把“仅当请求映射到...”取消勾选否则 IIS 会因为没有物理文件而拒绝执行。映射完成还有一个老坑ASP 默认不允许父路径而 v6.0 这类老代码里到处都是Server.MapPath(../data/)不开父路径会报 500.100 或者“找不到文件”。解决办法是在站点级别打开父路径Set-WebConfigurationProperty -Filter /system.webServer/asp -Name enableParentPaths -Value True -PSPath IIS:\Sites\Default Web SiteenableParentPathsTrue对应 IIS 管理器里的“启用父路径”只改默认站点时这一条够用多个站点就按各自站点路径执行。注意这条命令不会影响已经创建的应用程序池但如果站点使用了自定义应用程序池32 位模式需要在池上单独开。2.2 install.axphp与axphp.config的关系安装脚本带着install.axphp后缀建议先读一遍脚本逻辑不要上来就点击。老 ASP 安装器通常做三件事检测数据库是否存在、检测目录可写、创建管理员账号。如果中途卡住问题多半在axphp.config里的连接串形态一般是这样% axphp.config - v6.0 全局配置 Dim DBProvider, DBPath, DBUserName, DBUserPass DBProvider Microsoft.ACE.OLEDB.12.0 DBPath /data/gm_v6.mdb DBUserName DBUserPass Function GetConnectionString() GetConnectionString Provider DBProvider ;Data Source Server.MapPath(DBPath) End Function %这里DBProvider用Microsoft.ACE.OLEDB.12.0对应 Access 2007 以后的 mdb/accdb 文件。如果本机只装了旧版 Jet 驱动需要改回Microsoft.Jet.OLEDB.4.0但 Jet 只支持 32 位IIS 应用程序池也要切到 32 位模式。v6.0 这种老项目我一般直接建议用 32 位模式因为 GM 后台里晚点上传组件时很多只有 32 位 DLL。切换路径是应用程序池 → 高级设置 → “启用 32 位应用程序”改为 True。install.axphp通过 include 引入axphp.config所以无论怎么改名、改路径include 这一行都不能漏。另外注意DBPath是带/data/前缀的绝对路径Server.MapPath会把 Web 根目录拼到前面如果你的站点挂在一个子路径下这个配置需要同步调整否则安装脚本会提示找不到数据库文件。2.3 安装跑通前先看这几个检测点实际安装中经常遇到的报错不只是配置项我把 v6.0 部署时最常踩的几个检测点列出来检测项配置要求常见报错现象ASP 执行模块IIS-ASP功能开启404.2 / 500.2自定义扩展映射*.axphp映射到 asp.dll404.3父路径enableParentPathsTrue500.100Access 数据库权限data 目录可写Microsoft JET Database Engine (0x80004005)32 位模式使用 ACE 驱动时按需切换Provider 未注册数据库目录权限是重灾区。安装器会尝试创建或修改 mdb 文件IIS 进程身份必须对data目录有修改权限。站点放在默认wwwroot下时执行这条命令给应用池身份授权icacls C:\inetpub\wwwroot\gm_v6\data /grant IIS_IUSRS:(OI)(CI)M /T(OI)(CI)M表示容器和对象继承修改权限/T递归到子目录。如果不加/T只改顶层目录安装器在子目录创建临时锁文件时照样没权限。对于存放 mdb 的目录我还习惯把脚本执行权限直接关掉做法是 IIS 里选中/data目录双击“处理程序映射”把通配符映射全部移除。这样就算数据库目录被拖了 webshell也没法直接执行 ASP 脚本。3. 推广链接归因从点击Cookie到注册写库再到奖励结算3.1 为什么GM系统不能用HTTP_REFERER做结算依据很多游戏推广后台一开始只统计HTTP_REFERER但玩家从 A 站跳到游戏主页又收藏了页面几天后再注册此时 Referer 早就变成了about:blank。v6.0 的做法是在落地页把渠道参数种到 Cookie 里注册、激活、首充每个环节都从 Cookie 取渠道号。推广入口页的核心逻辑是存 Cookie、跳首页不在 URL 上长期暴露参数。% splash.asp - 推广落地页只负责打渠道标识 Dim cid, uid cid Trim(Request.QueryString(cid) ) uid Trim(Request.QueryString(uid) ) 写入推广渠道 Cookie有效期 30 天 If cid Then Response.Cookies(gm_cid) cid Response.Cookies(gm_cid).Expires DateAdd(d, 30, Now()) End If If uid Then Response.Cookies(gm_uid) uid Response.Cookies(gm_uid).Expires DateAdd(d, 30, Now()) End If 跳转正式首页URL 上不带 cid Response.Redirect /index.asp Response.End() %这段代码里两个 Cookie 分别是渠道 ID 和推广员 ID有效期统一 30 天。注册后结算奖励时只从 Cookie 里取不走 URL 参数可以避免玩家手动改cid999给自己发奖励。Response.End()放在Redirect之后是防止后续 HTML 内容被输出到页面老代码里漏写会导致当前页面上半部分被替换成空白页。3.2 注册激活时把推广关系写进业务表玩家在推广页拿到 Cookie再去注册服务器端需要把 Cookie 里的推广关系落到gm_register_log表。这里我建议直接用 ADODB.Command 做参数化v6.0 原始代码里很多是字符串拼接修复成本也不高% Dim cid, uid, playerId, conn, cmd, sql cid Request.Cookies(gm_cid) uid Request.Cookies(gm_uid) playerId Request.Form(player_id) If cid And playerId Then Set conn Server.CreateObject(ADODB.Connection) conn.Open GetConnectionString() Set cmd Server.CreateObject(ADODB.Command) Set cmd.ActiveConnection conn sql INSERT INTO gm_register_log(player_id, channel_id, sales_id, event_time) VALUES (?, ?, ?, ?) cmd.CommandText sql cmd.Parameters.Append cmd.CreateParameter(p1, 200, 1, 50, playerId) cmd.Parameters.Append cmd.CreateParameter(p2, 3, 1, , CLng(cid)) cmd.Parameters.Append cmd.CreateParameter(p3, 3, 1, , CLng(uid)) cmd.Parameters.Append cmd.CreateParameter(p4, 7, 1, , Now()) cmd.Execute conn.Close Set conn Nothing End If %CreateParameter的第一个参数名可以任意起第二个参数是数据类型200是adVarChar3是adInteger7是adDate。第三个参数1表示输入参数第四个参数是长度第五个是值。这样插入避免把cid直接拼进 SQL能挡掉最基础的注入。playerId是文本型参数长度给 50如果游戏账号超过 50 位也要同步修改表字段和这里。3.3 结算奖励前先锁账号避免重复发放奖励机制最容易踩重复发放。玩家注册、激活、首充各触发一次脚本并发跑时两个请求同时读到没发过奖就会发两遍。v6.0 的奖励流水表至少要有这些字段字段类型说明id自增主键流水 IDplayer_id文本 50玩家唯一 IDreward_type整型1 注册 / 2 激活 / 3 首充amount数字奖励点数status整型0 待发 / 1 已发create_time日期时间发放时间最简单的方式是给(player_id, reward_type)加唯一约束第一次插入成功第二次重复插入直接抛错代码里捕获错误就知道已经发过。Access 里可以在表设计器把这两个字段一起设置为主键也可以用 SQLALTER TABLE gm_reward_log ALTER COLUMN player_id TEXT(50) NOT NULL; ALTER TABLE gm_reward_log ALTER COLUMN reward_type INTEGER NOT NULL; ALTER TABLE gm_reward_log ADD CONSTRAINT uk_player_reward UNIQUE (player_id, reward_type);注意 Access 对ALTER TABLE后加CONSTRAINT的支持版本较老高版本 ACE 驱动可以。更稳的做法是在设计视图里选中两列点主键。结算时用事务包起来保证流水和余额同步写入conn.BeginTrans On Error Resume Next sql INSERT INTO gm_reward_log(player_id, reward_type, amount, status, create_time) _ VALUES( playerId , 1, 50, 0, Now()) conn.Execute sql If Err.Number 0 Then conn.Execute UPDATE gm_player SET points points 50 WHERE player_id playerId Else Err.Clear End If conn.CommitTrans这里为了演示事务流程用了字符串拼接实际使用时要换成参数化命令。后台报表要按渠道看转化用一条分组查询SELECT channel_id, COUNT(DISTINCT player_id) AS reg_count, SUM(IIf(first_pay_time IS NULL, 0, 1)) AS pay_count FROM gm_register_log GROUP BY channel_id;Access 不支持CASE WHEN要用IIf函数替代。COUNT(DISTINCT ...)在老 ACE 驱动里也可以跑但数据量超过十万行后性能明显下降维护阶段可以改成定时汇总表。4. 扩展广告位和asp上传给v6.0加图片与JSON接口4.1 广告位管理的存储与输出GM 推广系统里广告位不只是首页一张 banner通常还有弹窗、活动浮标、渠道专属广告位。v6.0 的gm_ad表一般按位置区分广告字段包含position、start_time、end_time、sort_no、status。页面端最常见的做法是做个 include 组件按位置读取当前生效的广告代码% Dim rsAd, connAd Set connAd Server.CreateObject(ADODB.Connection) connAd.Open GetConnectionString() Set rsAd connAd.Execute(SELECT ad_code FROM gm_ad WHERE positiontop_banner AND status1 AND start_time Now() AND end_time Now() ORDER BY sort_no) If Not rsAd.EOF Then Response.Write rsAd(ad_code) Else Response.Write span暂无广告/span End If %广告代码直接存 HTML好处是灵活坏处是后台一旦被植入恶意脚本所有玩家都会被打到。GM 自己配置没问题千万别开放给普通推广员提交广告代码。时间条件start_time Now() AND end_time Now()会让今天生效和已经过期的广告同时排除但要注意 Access 对日期边界有精度问题建议把广告结束时间存成 23:59:59避免当天 0 点就下架。4.2 asp图片上传组件方案与无组件方案的边界后台加 banner 图片绕不开上传功能。经典 ASP 上传有两个方向装第三方组件ASPUpload或者用无组件二进制解析。装组件最快示例很短% upload.asp - 使用 Persits.Upload 组件接收图片 Set Upload Server.CreateObject(Persits.Upload) Upload.OverwriteFiles False Upload.Save Server.MapPath(/uploads) Set File Upload.Files(pic) If Not File Is Nothing Then File.SaveAs Server.MapPath(/uploads/ File.FileName) Response.Write File.FileName End If %Persits.Upload是老项目最常用的上传组件OverwriteFiles False防止同名覆盖但建议把文件名改成时间戳随机数不要直接保留客户端的File.FileName否则两个 GM 传同名的banner.jpg会互相覆盖。组件法的问题是需要服务器装 DLL如果服务器不允许安装第三方组件就只能退回无组件方案。无组件上传的核心是把Request.BinaryRead拿到的 multipart 字节流按边界拆开再用 ADODB.Stream 写盘。拆 flow 的过程很容易碰到编码问题因为Request.BinaryRead返回的是字节数组不能直接用字符串函数查找边界。常见做法是先把整个请求体写入一个 ADODB.Stream再按字节找boundary和\r\n\r\n分隔符。这里给一个文件头校验的片段配合上传逻辑使用% 检查文件头是否为 JPEG/PNG/GIF Function IsImageData(data) Dim hex hex If LenB(data) 8 Then IsImageData False : Exit Function 取出前8字节转十六进制判断魔数 For i 1 To 8 hex hex Right(0 Hex(AscB(MidB(data, i, 1))), 2) Next If Left(hex, 4) FFD8 Or Left(hex, 8) 89504E47 Or Left(hex, 6) 474946 Then IsImageData True Else IsImageData False End If End Function %AscB和MidB是二进制安全函数Hex转十六进制后可以准确判断文件魔数。这样即使把扩展名改成.jpg的木马文件在写盘前也会被拦掉。无组件解析完整实现比较长可以直接搜一个成熟的无组件上传类集成进 v6.0 的api/upload.asp。4.3 用JSON API给外部统计平台喂数据游戏服务器或第三方统计平台要取 GM 系统的推广数据时直接连 Access 库不可行最好给系统加一个只读 JSON API。经典 ASP 输出 JSON 不需要专门组件用内置的字典和循环拼接即可% api/get_today_stats.asp?channel_id1001date2025-06-18 Response.ContentType application/json Response.Charset utf-8 channelId Request.QueryString(channel_id) dateStr Request.QueryString(date) Set conn Server.CreateObject(ADODB.Connection) conn.Open GetConnectionString() Set cmd Server.CreateObject(ADODB.Command) Set cmd.ActiveConnection conn cmd.CommandText SELECT COUNT(*) AS reg_count FROM gm_register_log WHERE channel_id ? AND event_time ? AND event_time ? cmd.Parameters.Append cmd.CreateParameter(p1, 3, 1, , CLng(channelId)) cmd.Parameters.Append cmd.CreateParameter(p2, 7, 1, , CDate(dateStr)) cmd.Parameters.Append cmd.CreateParameter(p3, 7, 1, , DateAdd(d, 1, CDate(dateStr))) Set rs cmd.Execute Response.Write {code:0,reg_count: rs(reg_count) } %参数p1是整型渠道 IDp2和p3分别表示当天开始和次日开始用?占位符传入天然避免注入。调用方用 fetch 或普通 HTTP 客户端都能按接口格式拿到数据。如果外部统计系统是 PHP可以直接file_get_contents调这个 URL如果要求 HTTPS必须在 IIS 里给站点绑定证书ASP 脚本本身不用改。5. 上线后的ASP排错SQL注入过滤和500错误三板斧5.1 把SQL注入过滤写在公共Include里v6.0 的原始页面多逐个改参数化成本高折中方案是把所有数字型参数过一遍过滤函数。在公共的safe.asp里加Function SafeInt(v) If IsNumeric(v) And InStr(v , .) 0 Then SafeInt CLng(v) Else SafeInt -1 End If End Function页面取参数时用SafeInt(Request.QueryString(cid))替代直接拼接。字符串参数可以加单引号转义函数但只能兜底新写的模块一律用 ADODB.Command。另外后台登录接口要限制连续失败次数简单做法是在gm_admin_log表里按 IP 统计最近 10 分钟失败次数超过 5 次就封 10 分钟。5.2 500错误不再抓瞎ASP 遇到 500 错误时IIS 默认返回泛化页面排查前先打开详细错误。在站点web.config的system.webServer节点里加configuration system.webServer asp scriptErrorSentToBrowsertrue / /system.webServer /configuration这个开关会把 ASP 脚本的堆栈信息直接显示到浏览器。上线后记得关掉或者只对本地回环地址开放。常见 500 原因按概率排序是父路径未开、Access 数据库文件只读、Provider未注册、自定义扩展映射顺序冲突。如果改了web.config还是不显示用事件查看器里的 IIS-W3SVC 日志能看到 500 子状态码比如500.100是 ASP 内部错误500.0往往是模块加载失败。5.3 Access锁库处理和后台日常维护GM 后台并发不高但多个管理页面同时写同一个 mdb 时会报“不能更新。数据库或对象为只读”或者“文件正被另一进程使用”。排查顺序是先看data目录下是否有.ldb文件有说明有连接没释放再检查所有 ADODB.Connection 是否在页面末尾Set conn Nothing。老代码里最容易漏的是写库后只conn.Close没有置空对象连接池会一直占着。日常维护建议每周用 Access 工具做一次压缩修复替换 mdb 文件时一定要先停 IIS 或回收应用程序池。对 GM 来源的异常流量要单独盯在 IIS 日志里定时抓install.axphp的访问记录正常情况下上线后没人再访问安装页看见这个请求基本可判定为扫描直接做 IP 黑名单拦截。本文还有配套的精品资源点击获取