简介:这是一套基于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 Site"enableParentPaths=True对应 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.dll | 404.3 |
| 父路径 | enableParentPaths=True | 500.100 |
| Access 数据库权限 | 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 参数,可以避免玩家手动改cid=999给自己发奖励。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是adVarChar,3是adInteger,7是adDate。第三个参数1表示输入参数,第四个参数是长度,第五个是值。这样插入避免把cid直接拼进 SQL,能挡掉最基础的注入。playerId是文本型参数,长度给 50,如果游戏账号超过 50 位,也要同步修改表字段和这里。
3.3 结算奖励前先锁账号,避免重复发放
奖励机制最容易踩重复发放。玩家注册、激活、首充各触发一次,脚本并发跑时两个请求同时读到没发过奖,就会发两遍。v6.0 的奖励流水表至少要有这些字段:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | 自增主键 | 流水 ID |
| player_id | 文本 50 | 玩家唯一 ID |
| reward_type | 整型 | 1 注册 / 2 激活 / 3 首充 |
| amount | 数字 | 奖励点数 |
| status | 整型 | 0 待发 / 1 已发 |
| create_time | 日期时间 | 发放时间 |
最简单的方式是给(player_id, reward_type)加唯一约束,第一次插入成功,第二次重复插入直接抛错,代码里捕获错误就知道已经发过。Access 里可以在表设计器把这两个字段一起设置为主键,也可以用 SQL:
ALTER 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 position='top_banner' AND status=1 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_id=1001&date=2025-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是整型渠道 ID,p2和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 scriptErrorSentToBrowser="true" /> </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 黑名单拦截。
本文还有配套的精品资源,点击获取