MeteorCat / 游戏发行和开发

Created Thu, 15 May 2025 22:33:51 +0800 Modified Wed, 29 Oct 2025 23:24:53 +0800
3285 Words

在商业游戏当中比较有以下基础角色:

  • 游戏研发(Content Provider, 简称CP): 最基础的游戏开发底层
  • 游戏发行(Publisher, 商业发行): 游戏推广和商业计划执行者

Steam|WeGame|Epic 之类平台的平台也能看到不少相关信息, 当然也有研发和发行一体的商业公司.

游戏发行 涉及以下工作( 以下内容不分单机和手游 ):

  • 海外|国内运营的本地文化和本地化处理, 需要按照不同地方规避文化民俗和联系语言本地化翻译(可能要联系其他海外发行合作)
  • 海外|国内授权和支付体系接入, 需要暴露外部Web接口联系 游戏研发 来接入处理
  • 海外|国内广告平台接入, 需要 游戏发行商务 确认平台广告量级从而才能进行广告推送(Facebook|抖音贴片广告位)
  • 海外|国内运营客服的处理, 游戏研发 不会直接和玩家对接, 而是玩家和客服反馈, 通过运营整合活动BUG联系策划出活动或者修复
  • 海外|国内需要按照定下的推广指标, 通过 财务 再按照商定的分账比例由 游戏发行 转账给具体的 游戏研发

并且 游戏发行 还有部分 第三方 来处理, 也就是 游戏研发 没有余力搭建维护整套发行架构的时候就会去寻求 第三方发行.

比较知名的就是直接接入腾讯体系发行, 只需要接入对应内部体系并且联系他们推广就行, 不过分成比例也是很高

这里主要说下 第三方游戏发行 的开发和设计, 最基础的就是必须实现接口:

  • 登陆请求
  • 请求支付
  • 支付回调

还有广告追踪转化SDK等, 这些附属的SDK比较高级和复杂所以不展开说明

注意: 如果作为 第三方游戏发行方, 你对接的是 游戏研发 的技术人员而非玩家用户.

# 常规登陆账号体系
[客户端] --> username|password --> [服务器] <---> 数据查询存在并生成授权token

# 第三方发行账号体系
[客户端] --> username|password --> [CP服务器]
                                    |
                                    |--> [appid, username, password] + 发行申请的 secretKey 哈希出 token
                                                    |
                                                    |--> [第三方发行服务器]
                                                                |
                                                                | --> 数据查询存在并生成授权token
                                                                            |
                                                                            |
                                                              如果存在账号返回信息, 不存在就创建账号入库
                                                                            |
                                  [CP服务器] <------------------------------ |
                                    |
                                    |
                (可选步骤,有的CP方需要记录入库来保存, 也有的CP方不想维护独立服务器)  
                验证发行返回的账号是否在 CP 自己维护的数据库用户表存在, 不存在就创建
                                    |
                                    |
[客户端] <------------- 将发行的验证的授权凭证数据返回客户端

这种架构可以有效把职责独立出来, 发行方面不需要和研发做太多交互, 直接单独维护自己 API 服务就行; 对于 游戏发行, 你只需要提供给 游戏研发 以下数据:

  • appKey: 登陆授权的参与签名哈希处理的密钥
  • appSecret: 创建订单的时候参与签名哈希处理的密钥

授权验证

这里提供我个人比较常用且基础的第三方授权机制, 基本上可以用于大部分发行情况:

  • 请求方法: POST(仅支持)
  • 参数方式: Form(表单形式)

为什么仅支持 POST 方式, 不采用 GET|POST 混合?

主要除了 GET 数据可传输数据比 POST 小之外, 还有个问题就是转发日志安全性; 一般为了防止 DDOS 攻击会采用高防服务器过滤请求之后转发到最终目标服务器, 有些默认第三方服务器都会做本地请求日志把 GET 相关参数写入到本地服务器日志, 如果该服务器被攻破的时候能够看到大量 GET 请求参数带有 username|password 相关参数.

这里是考虑到安全因素, 防止默认 Web 服务记录 GET 参数日志, 如果对这些不太敏感也可以默认支持处理 GET 提交

为什么参数必须要 FormData 提交?

主要是因为 FormData 这种方式更加灵活, 不仅仅支持 String 还支持 Blob 提交, 有时候支持奇葩需要 OpenSSL 密钥证书等文件提交认证的情况, 传统的 application/json 适用面仅仅作为字符串处理.

这里也是为了适用性考虑, 如果能够确认仅有 JSON 字符串提交的话可以采用传统 RESTAPI 方式

这里提供默认的提交参数 请求表单:

字段名 类型 默认值 说明
appid Long 必须 在发行创建的应用ID
channel Long 必须 不同第三方渠道标识,比如 快手/抖音/微信/应用宝等
time Int 必须 时间戳用于参与哈希防止碰撞, 使用秒级即可
extension JSON 必须 第三方渠道所需的参数,sid,token,sessionId等不同渠道所需参数不同
sign String 必须 数据签名串,按照除 sign 参数以 key 正序合并追加发行密钥来 md5 处理

这里加密方式提供个 PHP 示例可以直接查看 sign 哈希过程:

<?php

// 基础数据
$appid = 1001; // 应用ID
$appKey = "c53d93bafeb52c0be562613daef647de";// 参与哈希Key
$channel = 11; // 渠道标识
$time = 1747335102;// 秒级时间戳

// 渠道所需数据
$extension = json_encode([
	"username" => "meteorcat",
	"password" => "password"
]);
echo "extension: '{$extension}'".PHP_EOL;

// 合并成参数列表, appKey 不参与哈希
$params = [
	'appid' => $appid,
	'channel' => $channel,
	'time' => $time,
	'extension' => $extension
];


// 按照Key正序排序
ksort($params);
echo str_repeat("=",84).PHP_EOL;
var_dump($params);
echo str_repeat("=",84).PHP_EOL;


// 构建 Key1=Value1&Key2=Value2 形式
// 注意 http_build_query 会按照 url 方编码, 需要 urldecode 解码处理下
$encrypt = urldecode(http_build_query($params));
echo "encrypt: '{$encrypt}'".PHP_EOL;
echo str_repeat("=",84).PHP_EOL;

// 添加 appKey 
$encryptWithKey = "$encrypt$appKey";
echo "encryptWithKey: '{$encryptWithKey}'".PHP_EOL;
echo str_repeat("=",84).PHP_EOL;

// 最后处理成 sign
$sign = md5($encryptWithKey);
echo "sign: '{$sign}'".PHP_EOL;
echo str_repeat("=",84).PHP_EOL;

// 最后合并到提交 form 表单参数
$params['sign'] = $sign;
var_dump($params);
echo str_repeat("=",84).PHP_EOL;

# 最后展示打印内容数据如下
# extension: '{"username":"meteorcat","password":"password"}'
# ====================================================================================
# array(4) {
#   ["appid"]=>
#   int(1001)
#   ["channel"]=>
#   int(11)
#   ["extension"]=>
#   string(46) "{"username":"meteorcat","password":"password"}"
#   ["time"]=>
#   int(1747335102)
# }
# ====================================================================================
# encrypt: 'appid=1001&channel=11&extension={"username":"meteorcat","password":"password"}&time=1747335102'
# ====================================================================================
# encryptWithKey: 'appid=1001&channel=11&extension={"username":"meteorcat","password":"password"}&time=1747335102c53d93bafeb52c0be562613daef647de'
# ====================================================================================
# sign: '5ad78915353f59639729cb535dd95fdf'
# ====================================================================================
# array(5) {
#  ["appid"]=>
#  int(1001)
#  ["channel"]=>
#  int(11)
#  ["extension"]=>
#  string(46) "{"username":"meteorcat","password":"password"}"
#  ["time"]=>
#  int(1747335102)
#  ["sign"]=>
#  string(32) "5ad78915353f59639729cb535dd95fdf"
# }
# ====================================================================================

以上就是一步步的构建生成签名的流程, 之后就是 响应表单 的参数 JSON:

{
  // 其他都是不同对应类型失败, 400 为默认失败
  "state": 200,
  // 响应结构体, 如果非 200 响应就 null, 200 成功就是对象组
  "data": {
    // 作为发行生成的落地在数据库唯一id(long)
    "uid": 10001,
    // 作为发行生成的落地在数据库的用户名,比如 10001.wechat,10001.qq,10001.tiktok,....(格式:uid.渠道简称)
    "username": "meteorcat",
    // 第三方渠道验证成功返回的用户标识唯一ID(比如微信openid), 有的渠道需要第三方渠道这些信息
    "sdkUid": "oaKk343WOktAaT2ygsX138BGblrg",
    // 第三方渠道验证可能会返回在该平台的昵称, 用于设置默认的用户帐号名
    "sdkUsername": "MeteorCat",
    // 当前授权登陆的唯一凭证, 可以md5处理即可
    token: "",
    // 特殊的扩展字段, 有些平台可能后续会用到的参数可以放置其中用 JSON 返回
    extension: "{}",
  }
}

注意: 这里返回 token 就是在自己游戏发行平台在本次授权的凭证.

另外还需要提供 token 登陆接口, 用于客户端后续直接以 token 登陆方式保存本地不需要再次请求第三方渠道的情况:

字段名 类型 默认值 说明
uid Long 必须 在发行的用户唯一ID
token String 必须 授权返回token字段
time Int 必须 时间戳用于参与哈希防止碰撞, 使用秒级即可
sign String 必须 数据签名串,按照除 sign 参数以 key 正序合并追加发行密钥来 md5 处理

这样好处就是不需要每次再去第三方渠道再去确认授权, 有的第三方渠道确认授权过多会将其设为风险行为

订单预下单

订单的创建流程其实也和登陆验证一致, 请求表单 参数如下:

字段名 类型 默认值 说明
uid Long 必须 在发行的用户唯一ID
orderId String 必须 充值订单ID, 由CP方的自己服务器数据生成提供
orderItem String 必须 充值订单道具标识, 比如 ‘104’
orderName String 必须 充值订单道具名, 比如 ‘钻石’
orderDesc String 必须 充值订单详情, 比如 ‘钻石 * 100’
amount Long 必须 充值订单金额, 以分为单位
roleId String 必须 玩家角色ID, 注意单个UID是可以对应多个角色Id,也就是同个账号可能在多个区服都账号
roleName String 必须 玩家角色名称
roleLevel Long 必须 玩家角色等级, 没有等级机制可以为0
serverId String 必须 玩家角色所属服务器ID
serverName String 必须 玩家角色所属服务器名称
extension JSON 必须 自定义字段, 支付回调将其原封不动回调给 CP 的服务器用于通知游戏服务器发放
notifyUrl String 必须 支付订单到账的时候, 作为我们游戏发行需要回调的地址通知的地址, 可以不配置但需要在发行管理后台设置默认回调(优先采用参数而非后台定义)
time Int 必须 时间戳用于参与哈希防止碰撞, 使用秒级即可
sign String 必须 数据签名串,按照除 sign 参数以 key 正序合并追加发行密钥来 md5 处理

这里 sign 生成方式和登陆验证一样所以不做赘述.

游戏发行方订单不需要处理响应, 但是需要处理回调通知给游戏研发方订单已到账, 默认第三方支付回调之后需要标识好并按照 notifyUrl 通知以下 JSON 格式:

// 这里按照 notifyUrl 回调给 CP 方
{
  // 200 代表成功, 其他为失败
  "status": 200,
  // 200 该字段为对象组, 其他为 null
  "data": {
    // 作为游戏应用的ID
    "appid": 1001,
    // 作为我们发行方的唯一标识ID
    "uid": 10001,
    // 第三方渠道标识, 比如 微信 = 10, QQ = 12, ....
    "channel": 10,
    // 发行游戏订单id, 需要区分 1.发行订单ID 2.第三方订单ID 3.CP订单ID
    "sdkOrderId": 2025051603555501,
    // 玩家角色所属服务器ID
    "serverId": "1",
    // 玩家购买的道具标识
    "orderItem": "104",
    // 玩家支付金额, 单位为分
    "amount": 600,
    // CP方请求推送过来的扩展参数原样返回, 用于 CP 自己去提取对应参数
    extension: "{}",
    // 用于CP验证用 appSecret 参数哈希匹配
    sign: "1e20f28a33fcbb27ded35c9425e18aff",
  }
}

这里 CP 方回调处理方式有几种处理方案:

  • 发行支付回调的时候, 由 CP 地址打印显示 SUCCESS 或者 FAILED 确认 CP 方已经接收到回调不需要再发回调请求
  • 发行支付回调的时候, CP 接收到之后额外推送给发行另外验证接口, 告诉发行方已经接收到数据完成了

这两种方法按照自己需求选择就行了, 上面就是 作为开发 比较基础的 游戏发行 的处理流程, 高级还有广告投放的 归因统计, 后续有机会再单独说明.