Organizations

20 results for 架构
  • “我想做个游戏” 这是我从业这么多年看到最多也是最麻烦的需求, 很多时候老板们头脑一热想入局游戏研发就会提出这样的需求, 但是真正入局之后就会发现问题重重直到后续项目组分崩离析. 首先大部分提出这种需求的语境情况, 就是要做出 能够持续运营的手机游戏; 现在游戏开发已经是高度细分化产业, 抛开 单机游戏 和 独立游戏 , 前者基本上需要有一定大厂背书和稳定投资, 后者只适合小规模个人所以排除在外. 商业化游戏研发立项基本需要做好以下心理准备: 抗风险能力: 需要接受首个游戏并不满足预期, 大部分初创首个游戏成绩不理想才是常态 团队磨合: 很多时候起步的时候肯定是磕磕碰碰, 很多时候创立团队就想一步登天做爆款只能在梦里出现 成本控制: 很多时候就是低估算游戏方面投入, 初创游戏团队需要比成熟团队更加舍得花钱才能获得更好人才和资源 盈利模式: 游戏必须在立项就要考虑到基本付费模式, 必须提前想好怎么才能让研发投入能够快速回收成本 同时我也见过不少这种想法: 买套游戏源代码换皮不就行了? 游戏研发哪有这么复杂, 一定是你水平不行 首先需要说明游戏源代码项目很多 不包括售后, 都是开源代码改改之后上架卖课被大量分发出来的产物, 这种代码基本上和从头开发没什么区别, 运气好的时候编译版本正好在游戏编辑器兼容范围可以打包出来; 运气不好的项目可能是N年前陈年代码, 新版本编辑器根本无法出游戏包, 甚至内部插件已经找不到支持进行正常开发. 更别说有些游戏是游戏公司离职人员自己 夹带 出来的, 根本无法确定美术素材等是否被注册过专利, 直接冒用这方面游戏美术素材到时候著作所有人告又是一堆麻烦事. 有的游戏项目其实立项十分久远(我曾经接触最早是 2017 年的 unity3d 项目, 甚至客户端都没办法正常运行), 那时候的开发语言和环境可能完全没办法跑的(基于 centos6 已经被官方淘汰的系统, 如果升级高版本直接报错无法运行), 这种老代码做兼容和二次开发极其痛苦, 甚至连官方镜像和第三方资源包都不在提供维护了, 说是运行在 bug 之上都不为过. 如果买套源码改改就能顺利跑通商业化游戏所有流程, 那为什么市场上还有这么多失败的游戏项目组 接下来就是相对比较细致的游戏立项问题, 在开始动工的时候尽可能考虑一下: 研发什么类型游戏? - 卡牌|回合制|MMORPG|二次元…., 需要确定类型才能按照这些游戏人群画像去 ‘借鉴学习’ 相关游戏公司竞品 游戏平台是哪些?
    架构 Created Fri, 22 Aug 2025 19:43:52 +0800
  • 在很多第三方支付渠道都是只采用状态码返回响应, 像是比较常见微信和支付宝方面: # https://pay.weixin.qq.com/doc/v3/merchant/4012081709 # 微信响应公共错误码, 以 HTTP 响应码做主题 200 - Success - 请求成功 400 - PARAM_ERROR - 参数错误 400 - INVALID_REQUEST - HTTP 请求不符合微信支付 APIv3 接口规则 400 - APPID_MCHID_NOT_MATCH - AppID和mch_id不匹配 400 - INVALID_REQUEST - 无效请求 400 - MCH_NOT_EXISTS - 商户号不存在 401 - SIGN_ERROR - Sign 验证不通过 403 - NO_AUTH - 商户无权限 403 - OUT_TRADE_NO_USED - 商户订单号重复 403 - RULE_LIMIT - 业务规则限制 429 - FREQUENCY_LIMITED - 频率超限 500 - SYSTEM_ERROR - 系统异常,请稍后重试 # https://opendocs.
    架构 Created Wed, 20 Aug 2025 21:07:30 +0800
  • 记录下海外第三方支付开发的一些关键点, 常见的海外第三方开发推荐按照以下方式处理. 时间戳记录 统一采用 UTC 的时间戳处理, 避免采用服务器地区时间戳导致的跨时区异常, 日常使用的时间戳获取: Java: System.currentTimeMillis() Python: round(time.time() * 1000) .Net: DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() PHP: round(microtime(true) * 1000) 并且推荐时间戳记录记录在数据库当中采用毫秒级, 可以精确到具体高精度时间. 唯一订单号 这里推荐数据库的订单号类型为 varchar(32) 或者 varchar(64) 且可以设置为数据库表主键, 具体生成时间相关格式订单如下: /** * PHP生成和时间关联尽可能防止碰撞的订单号 * @return string */ function order(): string { $microtime = microtime(true); $millisecond = round($microtime * 1000); // 获取毫秒 $microseconds = $microtime - intval($microtime); // 获取微秒段 $pos1 = sprintf("%04d", $microseconds * 10000); // 提取4位毫秒 $pos2 = sprintf("%04d", mt_rand(1, 9999)); // 随机提取 0001-9999 $pos3 = sprintf("%04d", mt_rand(1, 9999));// 随机提取 0001-9999 $pos4 = sprintf("%04d", mt_rand(1, 9999));// 随机提取 0001-9999 $pos5 = sprintf("%02d", mt_rand(1, 99));// 随机提取 01-99 return date('YmdHis', intval($microtime)) .
    架构 Created Sun, 10 Aug 2025 15:31:01 +0800
  • 最近需要填坑处理原来海外的天坑, 基本上处理掉前期的比较基础部分, 还缺少部分估计也是要消耗不少时间处理. 按照目前的节奏针对现有问题做好分类规划: 抽离单独的支付平台(已完成, 待优化) 构建海外游戏发行平台(已完成, 待优化) 游戏业务SDK中心(已完成, 待优化) 整合后台管理中心(未完成, 更新中) 因为引入现代 composer 依赖, 所以避免很多问题只能从头做起, 开发效率上面也比较快; 但是中间很多问题都没有做细致处理优化, 后续对于已完成的项目要调整优化下. 支付平台 支付平台最好是单独抽离出做个中心服务, 除非万不得已不要把服务放置整合在一起; 这部分带来的影响很大, 因为作为发行中心的流量本身很巨大, 有时候简单的 php 任务进程可能没办法即时响应. 并且支付系统是带有定时回调处理( notify ), 如果整合成单机可能后续回调 CPU 抢占影响到服务器执行效率 目前流量较少所以采用单机负载, 后续看情况已经预留好可能会拆分支付订单模块的准备 因为目前支付平台是不对外开放的, 都是内部发行的支付系统传递的唯一订单号( reference_id ), 对订单号没有做唯一判断处理, 只是因为默认内部使用就就没有什么大问题, 后续需要补充更新这方面限制. 海外发行平台 这部分实际上考核很久, 主要就是在于作为 H5 海外的游戏平台 ‘容器’ 页面功能, 让游戏渲染在本身 iframe 之中, 大部分还是前端设计界面的问题. 特别需要注意的是海外支付的 embedded 方式, 这种方式就是引入第三方支付的JS-SDK, 在创建订单之后需要把会话参数传递给 JS-SDK, 后续 JS-SDK 会在 body 内插个节点渲染支付窗口. 这样虽然看起来效果还是挺不错的, 但是内部有很多问题: 第三方 CDN 不稳定, 引入第三方的 JS-SDK 可能连通性有问题, 直接卡死在等待加载的资源之中 插入节点和 css 样式出现冲突, 有的插入之后的样式名和类名在项目有出现冲突的样式 引发事件绑定异常, 有的SDK库带有动态事件绑定, 可能会直接导致游戏事件混乱 但是不可否认直接在页面内部弹窗唤起的交互效果好很多, 不过最后弹出新窗口去第三方页面支付, 支付完成再跳转会发行对外开发的订单完成窗口去执行 window.
    架构 Created Thu, 31 Jul 2025 20:50:52 +0800
  • 对于H5游戏发行来说, 除了服务端接口对接之外, 还需要暴露给游戏客户端JS库来引入, 主要流程如下: H5 游戏客户端拿到 h5.js 引入到游戏项目的页面之中 客户端需要接入对应发行实现接口, 目前没办法直接调用到那些功能 需要要求CP搭建好测试网站, 用来渲染游戏H5游戏页面 需要在发行方把CP搭建好和接入好SDK的H5游戏地址填写到后台配置游戏地址字段里面 发行方会生成游戏容器地址, 内部采用 iframe 渲染CP的游戏界面 对于客户端的调用功能实际上采用 PostMessage 跨页面推送所有事件 这就是 H5 在发行当中的流程, 那么我们首先要准备我们自己的发行游戏容器入口网站, 还有自己编写 JS-SDK 库文件: container.html: 发行的宿主网站, 也就是提供 iframe 功能的页面 game.html: CP游戏研发搭建并且接入我们发行的H5游戏主站 h5-sdk.js: 我们自己发行的JS功能集成库 注意: container 内部涉及到很多 iframe 调用机制和超时监听机制 简单用页面来梳理的话, 大概内部的结构如下: <html> <body> <!-- 授权容器: 作为发行方首先需要渲染的容器, 提供常用的注册授权机制 --> <div id="authority-container"> <!-- 内部不采用 iframe, 直接渲染页面即可 --> </div> <!-- 页面浮动的角标工具栏容器, 登录完成提供负责账号切换和退出的边栏窗口功能 --> <div id="toolbar-container"> <!-- 内部不采用 iframe, 直接渲染页面即可 --> </div> <!
    架构 Created Sat, 19 Jul 2025 01:18:29 +0800
  • 最近准备重构和整理发行平台项目, 结合项目中的问题边思考和边总结开发当中的流程和问题, 便于后续查漏补缺. 首先对于发行平台核心总共分为以下部分: 后台管理 SDK对接 静态渲染 后台管理 对于发行后台一般提供和设置以下参数: appid: 生成的游戏应用ID appKey: 游戏接入的登录时候需要用到 appSecret: CP回调的时候验证回调参数完整性的参与签名 clientId: 客户端ID(H5应用不需要) clientKey: 客户端Key(H5应用不需要) notifyUrl: 支付完成需要通知CP的回调地址 SDK对接 SDK对接目前仅有 H5 相关内容, 服务端需要处理以下接口来对接: 登录认证(需要CP文档对接,必要) 支付唤起(需要CP文档对接,按照游戏性质区分, 有的免费游戏不需要) 订单状态(需要CP文档对接,用于CP方确认订单状态) SDK初始化(事件上报,必要) 选择服务器(事件上报,非必要) 创建角色(事件上报,必要) 进入游戏(事件上报,必要) 等级提升(事件上报,必要) 退出游戏(事件上报,按照游戏性质区分,H5没有退出相关功能可以忽略) 选择服务器的时候没有角色信息, 所以上报的涉及的玩家和角色相关信息可以留空或者默认值 实际上可以区分为两部分: 研发联调 和 事件上报 事件上报是很关键的接口, 用于追踪玩家行为的转化率和在线留存等信息 研发联调 研发(CP)联调基本上只涉及授权和支付而已, 结构上报为统一的 JSON|FORM 格式, 内部字段如下: // 为了简洁, 对于默认不存在账号会自动在系统帮助创建注册, 省下了手动切换注册页面输入的流程 // 以下就是涉及到需要上报的注册|登录信息 { // 其他必要的登录参数 // 有的第三方可能参数有所不同 // --------------------------- // 后台创建的游戏应用ID appid: 1001, // 对应的推广渠道ID, 该渠道ID就是后台推广员绑定的账号ID // 用于将玩家绑定专属的渠道, 也就是相同游戏会被分包让其玩家归属到指定后台运营管理员 channel: "100002", // 登录平台, 有时候采用跨平台(Android|iOS|H5)登录, 两者平台都是同个账号 platform: 0, // 设备唯一标识码, H5无法获取到会默认留空 imei: "", // 设备信息上报, 比如 AndroidOS | AppleOS 等 device: "", // 设备UA信息上报, 比如 Mozilla/5.
    架构 Created Mon, 14 Jul 2025 23:26:56 +0800
  • WebSocket设计指南 这里主要对常用 TCP 和 WebSocket 做对比, 确认两者的网络传输之间的细节从而在开发当中规避一些可能存在的问题. WebSocket 是基于 HTTP 协议, 而 HTTP 协议也是基于底层 TCP 架构开发, WebSocket 为了弥补 HTTP 的 无状态 特性采取的 双向通讯 协议. 为什么采用 WebSocket 而不是更加底层且效率更好的 TCP? 主要来源于 Web和HTML5 应用的飞速发展, 传统 C/S端 逐渐被 B/S端 赶上, 而 Web 界面上面开始对长链接有所需求, 从而需要在网页应用能够做到简单的 Socket 网络请求. 特别是对于网页游戏来说, 需要专门的网页长链接协议方便做数据交换, 这里还需要说明下几种协议差别: UDP: 基于 报文(Packed) 传输消息, 每个报文都是独立的数据内含 源地址|目的地址|端口号以及数据 等信息, 不保证消息有序性 TCP: 基于 流(Stream) 传输消息, 传输数据无边界的字节流需要对流进行消息分包, 已经内部确保消息有序性 WebSocket: 基于 消息(Message) 传输消息, 每个消息都有明确的边界无须在此消息分包 这三种协议性能上从高到底比较来说是: UDP > TCP > WebSocket.
    架构 Created Fri, 23 May 2025 13:46:32 +0800
  • 在商业游戏当中比较有以下基础角色: 游戏研发(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 服务就行; 对于 游戏发行, 你只需要提供给 游戏研发 以下数据:
    架构 Created Thu, 15 May 2025 22:33:51 +0800
  • 对于日常 Web 应用来说 Spring 全家桶已经足够用, 但是涉及到某些底层操作(TCP|UDP|WebSocket)的时候就感觉冗余且捉襟见肘. Spring 封装过头导致及其冗余, 很多组件其实没有那么必要引入, 所以需要重新选择技术栈; Spring 作为框架太复杂在网上检索到 VertX 来做工具集: Spring:作为 框架(Framework) 集成大量日常通用工具并且屏蔽底层封装高级接口 VertX: 内部采用 netty 做底层将对应方法简略封装, 所以更像是 工具集(Utilities) 如果需要编写网关层, 推荐采用该方案, 原生 netty 虽然也可以但是缺失很多关键部件 对于底层网络基础来说 VertX 就更加符合需求, 所以在考虑之后决定引入: <dependencies> <!-- 最基础的核心底层, 如果功能单一基本上只需要该第三方库就行了 --> <dependency> <groupId>io.vertx</groupId> <artifactId>vertx-core</artifactId> <version>4.5.14</version> </dependency> <!-- Web功能服务 --> <dependency> <groupId>io.vertx</groupId> <artifactId>vertx-web</artifactId> <version>4.5.14</version> </dependency> <!-- MYSQL数据库客户端,支持池处理 --> <dependency> <groupId>io.vertx</groupId> <artifactId>vertx-mysql-client</artifactId> <version>4.5.14</version> </dependency> <!-- JUnit 测试框架参照: https://vertx.io/docs/vertx-junit5/java/ --> </dependencies> 建议配合 JCommander 编写成命令行工具, 抛弃原来集成 Spring 臃肿的组件
    架构 Java Created Sat, 03 May 2025 22:41:05 +0800
  • 状态定义 这里其实主要对于数据库的状态字段定义, 基本上有以下几种定义: is_del(1|0): bit(1) 或者 tinyint(1) 方式做的 是|否 类似 bool 处理 delete_time(0|timestmap): 删除时间, 如果为0代表没删除, 非0代表删除时间 state(0|number): unsigned 的状态值支持多种状态递增变化 数据库表状态字段这几种方式各有各的好处, 但是这里首先不推荐 is_xxx 方式定义字段. 之所以不推荐是因为首先 is_xxx 这种定义单个状态作用太小且占用表字段列空间; 比如今天运营需要 is_del, 明天需要 is_check(审核删除), 后天需要 is_lock(锁定操作) 等字段, 这里字段随着业务不断扩展出来会导致表结构需要不停更改不断去 ADD Column. 在数据业务量少的情况每次变动的时候可能没什么, 但是后续单表数据量膨胀的时候需要追加动到表结构字段的情况就很恐怖. 并且对于 is_xxx 这种字段其实还有个问题就是 Kotlin 引入 JPA 的时候会出现解析错误, 需要自定义处理 @Column(name = "is_del") 重命名实体类字段名并让其不要以 is 开头的作为实体字段. 关于 kotlin 的 is_ 开头字段就是被坑过, 所以后续默认不采用这种方式 而 delete_time 其实主要问题就是状态类型单一已被定义成 delete, 只能表示单种状态处理没办法处理后续多种状态, 所以推荐的方式其实采用 state 单个字段处理: CREATE TABLE `test_1` ( `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID', `state` TINYINT(3) UNSIGNED NOT NULL DEFAULT '0' COMMENT '状态码, 采用 TINYINT3 方便后续扩展', PRIMARY KEY (`id`) USING BTREE ) COMMENT ='测试表1' COLLATE = 'utf8mb4_unicode_ci' UNSIGNED TINYINT(3) 取值 0~255 基本上已经足够日常使用, 一般状态数据不会超过这么大的值且占用也不用那么高.
    架构 Created Sat, 15 Feb 2025 18:24:42 +0800
Next