评论

【高校开发者】小程序,遇见你真好 ;

小程序,遇见你真好;我们的故事未完!微信开发大赛参与者的经验分享与学习经历,希望对大家有用。

前言
很荣幸自己能够在微信小程序开发社区上面发布一篇关于程序开发的经验分享文章,文章标题有点奇怪,不是我写错,而是是我故意的哈哈哈哈😄,“;”是为了表示我和小程序还有将来。

在这三年的大学生活以来,除了课程报告,本人几乎很少写技术性/经验分享的文章,博客编写也基本没接触过o(︶︿︶)o 。

本人也将近毕业了,学习的道路走得越远,越觉得自己缺乏的是知识的沉淀,因此自己也正准备搭建一个(高大上)的博客,用来记录我作为程序员的一生。

噢好像话题跑偏了嘿嘿😁, 我是2019年微信开发者大赛小程序组的一个不知名队伍,作品是一个答题小程序,名称叫 “不服来挑战我”。本来臆想是没有“我”字的,但因为被注册了,就没办法了(希望那个开发者看到这篇文章可以大方让给我(一脸正经调皮脸))。

1. 设计背景

在一个偶然的机会,我在大一暑假开始接触微信小程序/小游戏生态环境。我找到一份初创公司的小游戏实习工作(当时小游戏爆火,曾经我对此很感兴趣),负责业余功能的实现。

那时候我很高兴,毕竟那是我人生第一次工作啊!

但经过两个多月的996工作“折磨”,我对程序员的工作有点害怕了。

不过,那也是一瞬间的事,不然我为什么会在这里分享经验呢。

现在回想起那段时间,我的脑海总会冒出一句话:“纵你虐我千百遍,我仍待你如初恋”。咳咳虽然是百度的,但此话的确表明了我的心。因为我在那段时间,学到了很多,从人生到理想,从后端到前端,从基础到复杂,从设计到架构……我真的很感谢那段时间公司老板倾力授予我的各种知识。

在此,我首先感谢他,感谢他在暑假指导我,如果没有他,我估计也走不进程序之美的地方。

(哎又跑偏了,各位见谅!)回到正题,“不服来挑战我”答题小程序是我在2019年寒假就开始着手开发的小程序,其实当时我并不知道有微信开发者大赛的,模糊记得2018年举办过一次。但我心中还是只有一个念头:通过我在大学中学到的知识,把我的idea实现出来!所以比赛只是顺手参加的。

由于我自己英语水平比较低,在校期间想提升一下,但用了好多app都不是我的菜,咱又没女朋友一起学习,所以就想自己搞一个程序学习英语。偶然接触到“一站到底”之类的节目,我就想要不写个答题程序?

我想我好像政治也不咋地,要不再加个政治题目呢?背熟点大三考研。
然后想到我曾经可是做过小游戏的呀!而且小游戏挺火的,嗯那就做个答题小游戏吧。然后就对我的创意进行编织、架构,后面想起,小游戏
发布要软著,这要咋弄呢,我当初做小游戏的时候都是客户提供的,而且还挺麻烦的。心里面又打退堂鼓了。后来发现除了小游戏,还有个小程序呀,我做个小程序不就好了,程序的主题玩法应该不受前端架构的影响才对。然后就确定了做个答题小程序。

接着看了小程序的文档,发现入门很简单,非常简单,十分之简单(划重点),这使我信心十足。

然后想着我一个人做那不得累死,就找了一个也对小程序感兴趣的好朋友一起做。我们从想法、架构聊到UI设计。我主要负责后端代码实现,以及前端逻辑层对接;他负责前端UI设计。至于后来题库变多,是因为我打算做出给想要学习的人群使用的,可以学习一下专业知识。然后在开学后就找个同学负责测试和题库收集。

寒假那段时间,我们每天7点干到晚上1点到2点多,连过年都是在打代码,所以有借口一脸正经地对家人说我在学习!那是我最嗨皮的一段时间了。

后台架构花太多时间了,现在还有一些功能还没完善,时间也有限,这学期也挺忙的。

3. 小程序介绍

小程序第一版功能架构挺详细的,分享给大家看看:

但后面发现有些功能没法实现了,然后有一些又麻烦,就暂时还没实现出来。

现在的版本,主要玩法有匹配功能和好友对战功能,其他的功能有任务系统、排行榜每日签到等,然后最近加了个上传题库。目前打算把小程序发展到学习交流范围,日后整理好数据,会给用户提供题库下载的。

最终成型作品如下:


没有美术属性加成,界面有点丑…

4. 开发前准备

团队开发过程中,只有充足的准备才能达到合理分工,开发高效的效果。

首先,代码管理是一个很重要的问题,作为程序员,每天编码不会很少的(如果有那是大佬),如果发现了写错了,撤销或删除也可以,但是如果程序中增加了一个功能可能有100行代码,然后在程序的其他地方进行调用,那这个时候我们就不好找了;除此之外,每写一个功能都要备份一次,不备份万一弄丢就心疼了;最重要的一点就是对人操作同一个文件,最终需要整合一起。

所以代码管理工具就是为了解决类似问题而产生的。因此,团队开发中,我建议一定要学会使用代码管理工具,比如git或者svn,即使在学习过程中,也可以使用它来存储我们日常训练的代码的。然后可以装一些git的GUI工具,我使用的是SourceTree,方便快捷,不过萌新学习期间还是推荐用命令行,不然很难理解并且深入学习到git的原理的。

其次,作为后端开发者,编写规范的API文档是必不可少的,大家可以上网查找一些在线文档编辑工具,我用的是ShowDoc,把团队成员加在一起就可以共享文档项目了,上面还有数据库设计文档的模板。

接着的话,在设计数据库的时候,可以先用visio等绘图软件绘制ER图,然后再用数据库相关GUI设计数据库表。GUI我使用的是Navicat,在上面进行数据库操作的时候比较方便。

最后就是前后端语言开发工具了,前端的话首选微信开发工具,尽管目前工具还是有一些bug,但我相信经过我们开发者群体的使用并详细反馈,以及微信小程序开发工程师的维护后,它会变得更好更优秀的!

5. 后台架构
重点来了,在这里我会详细给大家说一说我的后台架构以及一些重要的功能实现。

首先,小程序后端使用的是node+express+mysql框架,微信登录使用官方的wafer-node-sdk。

由于我的小程序本质上属于一个游戏,因此在后端设计上需要重视一下性能与稳定性。参考了网上一些解释,我在此重复一下,文笔不好,望见谅。

(借用网上的解释)
游戏的运行是一个多进程协同工作的过程,因此开发的复杂度会大大提高。同时,我们也需要关注小程序对手机内存和CPU的使用程度,以求在特定业务代码下,能尽量满足承载量和响应延迟的需求。除此之外,网卡也是另外一个约束因素,网络带宽直接限制了服务器的处理能力,所以游戏服务器架构也必定要考虑这个因素。因此,解决以上的问题,最基本的做法就是“时空转换”,用各种缓存的方式来开发程序,以求在CPU时间和内存空间上取得合适的平衡。对于游戏服务器架构设计来说,最重要的是利用游戏产品的需求约束,从而优化出对此特定功能最合适的“时-空”架构。并且最小化对网络带宽的占用。

基于上述的分析模型,对于游戏服务端架构,最重要的三个部分就是,如何使用CPU、内存、网卡的设计:

  1. 内存架构:主要决定服务器如何使用内存,以保证尽量少的内存泄漏的可能,以及最大化利用服务器端内存来提高承载量,降低服务延迟。比如在我的小程序里面遇到一个空闲房间占有内存问题:玩家创建了一个房间,同时服务器建立一个监听方法,启动空闲计时器,监听房间是否空闲,如果房间的人数一直有变动,则监听方法则会重新更新时间,计时器结束, 解散房间,游戏开始,则清除房间的空闲计时器,从而提高服务器的内存使用效率,避免内存泄露。

  2. 调度架构:设计如何使用进程、线程这些对于CPU调度的方案。选择同步、异步操作等不同的编程模型,以提高服务器的稳定性和承载量。同时也要考虑对于开发带来的复杂度问题,例如开始游戏扣除房费、结算时更新玩家经验值、等级以及虚拟货币这些都需要异步处理。

  3. 通信模式:决定使用何种方式通讯。网络通讯包含有传输层的选择,如TCP/UDP,Socket通讯等;跟据表达层的选择,如定义协议;以及应用层的接口设计,如消息队列、事件分发、远程调用等;在我的服务器里面,账号服与大厅服使用TCP进行消息传输,游戏服使用Socket进行游戏逻辑通讯。

整个程序运行的服务器在设计上分为三层服务器:账号服务器、大厅服务器以及游戏运行服务器:

  1. 账号服务器:主要用于处理用户注册和登录游戏系统。用户是否重复登录游戏也将在账号服务器上处理。账号服务器由主备构成,任何时候只有主账号服务器负责和客户端及GATE服务器交互。

  2. 大厅服务器:响应账号服务器和游戏服务器的请求,将游戏列表和房间信息返回给它们。当用户登录成功后将和大厅服务器保持长连接以实时获取游戏系统的信息。当用户在大厅里创建房间时,大厅服务器会根据存储的游戏服务器信息,依靠负载均衡算法把用户分布到任何一台游戏服务器上,以此保证工作任务分摊到多个处理单元,即减轻了单个服务器的承载力,也提高了并发处理能力。

  3. 游戏服务器:服务器在启动时将自己注册进入大厅服务器,关闭时从大厅服务器里销毁自己,同时在线玩家进入房间时还会要求大厅服务器更新在线人数。当用户在大厅中点击进入某个游戏时,用户将登录相应的游戏服务器进行游戏。

6. 相关功能分析

6.1 快速匹配

快速匹配这一个模块比较复杂,主要的核心思想是使用了状态机思想来实现的,以及整个游戏包括房间内的一切操作都使用状态机的思想来实现的。首先用户在大厅里点击快速匹配,选择完房间类型后,把房间的配置信息发送给大厅服务器,大厅服务器再把信息转发给网关,网关服根据房间信息查找是否存在此类型的房间,如果存在,则会创建一间临时房间供玩家入座,玩家入座后与后台建立Socket连接,并且创建房间状态机,房间状态机控制一切玩家在房间内的操作。然后可手动点击快速匹配,然后服务器会触发匹配状态机,从而把玩家安排进匹配队列中,如果匹配队列的人数达不到房间要求,则触发机器人调度器,给玩家分配机器人陪玩。

相关算法如下:

let waitingPlayers = this.getOtherMatchingPlayers(player);
        if (waitingPlayers.length + 1 === this.descriptor.numberOfSeats) {
            if (player.socket) {
                //房间没换, 也一样让客户端发起到新房间的登录
                player.socket.emit(`startMatchingReply`, {
                    newState: `Matched`,
                    roomNumber: player.waitingRoom.roomNumber,
                    chuShiMultiple: player.waitingRoom.config.initialMultiple,
                });
            }
            for (let i = 0; i < waitingPlayers.length; i++) {
                if (waitingPlayers[i].socket) {
                    //房间换了, 客户端需要发起到新房间的登录
                    waitingPlayers[i].socket.emit(`matchingComplete`, {
                        newState: `Matched`,
                        roomNumber: player.waitingRoom.roomNumber,
                        chuShiMultiple: player.waitingRoom.config.initialMultiple,
                    });
                }
            }
            this.playerCompleteOneMatching(player, waitingPlayers);
       }

6.2 游戏运行时流程

由于休闲对战类游戏的性质,导致游戏前期需要机器人的支持才能渡过艰难的前期运营,所以我在快速匹配中实现了机器人陪玩的功能,当然由于游戏的难度,以及个人能力有限,机器人的正确率与答题时间目前只能用随机算法来决定。游戏状态机如下:

状态机相关算法如下:

6.3 游戏对局信息重建

在玩家对战类型的游戏里面,游戏场景重建是一个十分重要的技术,当玩家正在游戏的时候,由于网络原因或者有意与服务器断开,此时网络恢复畅通或者玩家重新回到游戏中,这是就需要把玩家离场期间发生的所有消息一一重建。

根据上述状态机我们可以知道,游戏期间有五个状态,对于这五个状态而言,服务器都会下发消息通知客户端执行了什么,此时服务器会把下发给某个用户的消息记录进用户分身里面(非微信唯一用户,用户分身是游戏中创建的一个用户对象,游戏开始则创建,游戏结束则销毁),然后当用户断线重连时,客户端会检查消息记录里面都有那些发生过的消息,然后再一一重新发送回给服务器。

但是这时候会有一个很严重的问题,当你场景重建的同时,实际上玩家仍然在继续答题,这样就会导致客户端在处理各个消息的时候发生冲突,于是我对每个消息设立一个timer以及消息发送时当前时间date,这个timer是服务器处理消息时使用的时长,也就是理论上timer后客户端会收到消息;那么,客户端在场景重建的时候,就会用date+timer得出客户端收到消息时的理论时间,理论时间与场景重建时的当前时间做比较,如果前者大于后者的话,服务器会在时间差值内处理过期消息,直至同步至最新消息,反之则不处理。

这样就能避免消息冲突的情况了。那么消息重建的过程就如下所示:

客户端相关算法如下:

reconstructFromPlayerMessages(roomData) {
    this.isReconstructing = false;
    if (roomData.playerMessages.length) {
      // this.beginRound();
      let RoundMessagePlayer = require(`./../../model/RoundMessagePlayer.js`).instance;
      RoundMessagePlayer.currentActor = this;
      this.isReconstructing = true;
      console.log(`重构消息内容包括:`);
      roomData.playerMessages.forEach(msg => {
        console.log(JSON.stringify(msg));
      });
      console.log(`重构消息全`);
      let playerMessages = roomData.playerMessages;

      if (playerMessages.length > 0) {
        for (let i = 0; i < playerMessages.length; i++) {
          let message = playerMessages[i];
          let eventName = message.name;
          let content = message.content;
          RoundMessagePlayer.addItem({ name: eventName, packet: content });
        }
        RoundMessagePlayer.reconstruct();
      }
      this.isReconstructing = false;
    }
  },

6.4. 匹配机器人模式

众所周知,多人联网对战游戏大部分都是经过单机游戏演化过来的,而作为单机游戏,最重要的一点就是机器人陪玩了,其实,无论站在开发角度或者玩家角度来说,机器人陪玩是必不可少的,对于开发者来说可以节省很多时间,一个人边开发边进行游戏测试即可,而联网对战游戏上的机器人更是解决了游戏初期玩家无人匹配的尴尬场面,因此我也实现了匹配模式机器人调度的功能。启动机器人调度服务器,会加载出机器人数据,然后登录并连接上游戏服务器,当玩家触发了匹配程序时,调度器就会安排机器人进入队列,从而实现陪玩功能。

后端相关算法如下:

gateWaySocket.on(`connected`, function () {
        console.log(`恭喜, 调度器连接成功`);
        gateWaySocket.emit(`iScheduler`, {credential: `dfoakuewqnvlhdojfd`});
    });
    gateWaySocket.on(`iScheduler`, function () {
        console.log(`服务器确认我可以调度了`);
    });
    gateWaySocket.on(`playerEnterGroup`, async function (desc) {
        if (idleRobots.length >= desc.numberOfSeats - 1) {
            for(let i = 1; i <= desc.numberOfSeats - 1; i++) {
                let first = randInt(0, idleRobots.length);
                let robot = idleRobots[first];
                idleRobots.splice(first, 1);
                await robot.login();
                if (!robot.loginData) {
                    robotBecomeAvailable(robot);
                    return;
                }
                let enterResult = await robot.enterGroup(desc.groupType, desc.gameType, desc.subjectType, desc.numberOfSeats);
                if (!enterResult.waitingRoomLoginData) {
                    console.log(`机器人进入场地失败了`);
                    robotBecomeAvailable(robot);
                    return;
                }
                robot.doMatching(enterResult.waitingRoomLoginData);
            }
        } else {
            console.log(`糟糕透顶, 没有空闲的机器人`);
        }
    });

6.5 运行内存清除

为了避免在游戏服务器上的内存泄露与溢出的情况,由于玩家是并发创建用户分身以及创建房间进行游戏的,如果不做内存清除的措施,服务器内存是会造成泄露或溢出的情况的。

后端相关算法如下:

/**
 * 恢复房间清除计时器
 */
function _resetRoomIdleTimeout(room) {
    clearTimeout(room[idleTimeout]);
    _setRoomIdleTimeout(room);
}

/**
 * 设置房间清除计时器
 */
function _setRoomIdleTimeout(room) {
    room[idleTimeout] = setTimeout(() => {
        roomMgr.removeRoom(room);
        delete room[idleTimeout];
        delete room.onPlayerDidEnter;
        delete room.onPlayerDidLeave;
    }, allowedIdleTime);
}

//用户成功创建房间时使用, 马上启动空闲计时器, 计时器结束, 解散房间
exports.startWatchingRoom = function (room) {
    room.onPlayerDidEnter = function () {
        clearTimeout(room[idleTimeout]);
    };
    room.onPlayerDidLeave = function () {
        if (room.numberOfPersons() === 0) {
            _resetRoomIdleTimeout(room);
        }
    };

    _setRoomIdleTimeout(room);
};

7. 未来展望

小程序我也使用过很多,也发现有一些通病,比如小程序反馈不友好,无法长久挽留用户等问题。为了增加产品的粘性,我应该在后期会陆续丰富我的小程序的功能,提供更多用户下载,当然需要小程序的虚拟货币,同时也可以通过虚拟货币换取礼物(比如一些学习的工具书、周边产品等)的一些形式来让大家有所收获等等。希望我的作品能一直走下去。

8. 看法

在这个项目开发过程中,我第一次体会到自己开发一个程序并且在平台上面提供给所有人使用的感受。在此过程中我学到了很多,从前端的UI设计与交互到后端的网络编程、性能优化,还有运维方面的知识,例如域名备案、配置SSL证书、把项目部署在服务器上等等,除此之外,还有团队合作的精神,队员的交流沟通等,我觉得这些基础知识是我们开发者必须要掌握的一些技术。最后,衷心感谢微信提供这样的一个平台给我们开发者去尝试和为之努力。

小程序/小游戏正在蓬勃发展,我相信,在众多开发者的努力下,总有一天,这个生态环境会变得更好!

小程序/小游戏,遇见你真好;我们的故事未完!

最后一次编辑于  2019-06-28  
点赞 8
收藏
评论

3 个评论

  • 申圳
    申圳
    2019-10-09

    您好,有源码吗,观摩一下

    2019-10-09
    赞同
    回复
  • 黑魔法的零度救赎
    黑魔法的零度救赎
    2019-07-17

    感觉说了半天需求文档,技术是一点没体现


    2019-07-17
    赞同
    回复
  • 黄昏
    黄昏
    2019-07-02

    前路漫漫 加油!

    2019-07-02
    赞同
    回复 1
    • 2019-07-02

      谢谢,一起进步!

      2019-07-02
      回复
登录 后发表内容