前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >登录系统——分布式系统实现游戏不分区思路

登录系统——分布式系统实现游戏不分区思路

原创
作者头像
深雾
修改2020-07-06 10:41:18
9691
修改2020-07-06 10:41:18
举报
文章被收录于专栏:工具类工具类

前言

这些天在看项目的登录功能,思考游戏如何实现所有玩家同区,服务器满人不能和朋友一起玩,开新区会减少老区玩家会流失,还有一区情节等,也有人喜欢去新服新生态重新开始,但总体来说我认为弊大于利。

服务器方面

共用帐号服务器

有些每个服务器上都有web服,游戏服,每个区玩家绑定很难实现。

上家公司是一个web帐号服,一个世界服,对应一批游戏服,世界服是跨服活动方面,具体看:**跨服夺矿战实现

例如一个安卓帐号服+世界服,对应30个游戏服务器,ios帐号服对应10个游戏服,其它各渠道对应2个,现在想想应该是可以按安卓,ios,各渠道分区的。

帐号服务器主要用于玩家登录信息,还有支付订单,或者渠道特有的优惠礼包等活动奖励,公司项目其实已经完成。

游戏服务器分流

参考了这篇文章:类似于QQ游戏百万人同时在线的服务器架构实现

游戏分区主要原因还是服务器方面无法承受大量玩家同时在线,公司单个服务器一般5000用户左右。我的想法是,分区改成分频道或者分线路,有些游戏就是这种做法,但玩家登录web验证后,推荐人少的频道服务器,在游戏中每次切换频道实质就是切换其他游戏服务器,用户数据传过去不需要重新登录。维护也简单,关服直接关帐号服即可。

其它频道玩家交互

服务器器分流产生的问题是数据不一致,以前世界服是用来管理跨服活动,现在可以用来管理不同服务器玩家交流。

如果是公主连接那种没有其它用户界面,最多就添加好友,加工会的时候记录下id,查看成员的通过世界服查看成员id请求对方数据库服信息。如果是大型MMORPG,组队消息推送当前服务器,切换服务器也影响不大,队长进副本,世在界服创建新副本,拉取队伍成员用户。帮派帮战同理创建新地图即可,如果世界服压力也大,分类世界副本服,世界帮战服,王者荣耀不同区玩家跨服战斗原理应该差不多,世界服创建新战场。LOL,DOTA2也是,MOBA实现起来应该很简单,大量的战斗数据都在战斗服务器上。

安卓IOS用户同区

这个web帐号服放一起就行,加个字段区分安卓还是ios玩家。

数据库分流

服务器问题解决后来看看数据库,web服公用一个数据,储存帐号登录游戏验证密钥,支付订单等信息。

玩家没有固定服务器,可以把玩家以前绑定的服务器id变成数据库服务器id,实质绑定服务器变成了绑定数据库,登录时读取对应数据库信息,分流游戏服,世界服,数据库服都可以根据玩家数量变化而变化

登录系统

UserController用户接口

代码语言:java
复制
@Controlle
@RequestMapping("/user")
public class UserController {
    private static final Logger LOGGER = LoggerFactory.getLogger(UserController.class);
    @Autowired
    private UserFacade userFacade;
    @Value("${server.secert}")
    private String serverSecert;
    @RequestMapping(value = "/authToken")
    @ResponseBody
    public TResult<AuthTokenResponse> authToken(@RequestBody JSONObject object, HttpServletRequest request) {
        LOGGER.debug("authToken request:{}", object.toJSONString());
        Integer platformId = object.getInteger("platformId");
        String channel = object.getString("channel");
        String token = object.getString("token");
        if (platformId == null || StringUtils.isBlank(channel) || StringUtils.isBlank(token)) {
            return TResult.valueOf(StatusCode.DATA_VALUE_ERROR);
        }
        TResult<User> result = userFacade.authToken(object);
        if (result.isFail()) {
            return TResult.valueOf(result.statusCode);
        }
        long uid = result.item.getUid();
        long loginTime = System.currentTimeMillis();
        token = SecurityUtils.hmacSHA1Encrypt(String.format(PlatformId.LOGIN_VALIDATE_KEY_FORMAT, platformId, uid, loginTime), serverSecert);
        AuthTokenResponse response = AuthTokenResponse.valueOf(uid, token, loginTime);
        return TResult.sucess(response);

    }
    /**
     * 记录游戏登录
     * 
     * @param object
     * @return
     */
    @RequestMapping(value = "/recordLogin")
    @ResponseBody
    public Result recordLogin(@RequestBody JSONObject object, HttpServletRequest request) {
        long uid = object.getLongValue("uid");
        int serverId = object.getIntValue("serverId");
        if (uid == 0 || serverId == 0) {
            return Result.valueOf(StatusCode.DATA_VALUE_ERROR);
        }
        userFacade.updateLoginServer(uid, serverId);
        return Result.valueOf();
    }

UserFacadeImpl实现类

根据渠道id对应不用的渠道解析类

代码语言:java
复制
    @Override
    public TResult<User> authToken(JSONObject object) {
        Integer platformId = object.getInteger("platformId");
        String channel = object.getString("channel");
        PlatformInvoke parser = platformContext.getParser(platformId);
        if (parser == null) {
            LOGGER.error("PlatformInvoke not found, platformId:{}", platformId);
            return TResult.valueOf(StatusCode.DATA_VALUE_ERROR);
        }
        TResult<String> result = parser.login(object);
        if (result.isFail()) {
            return TResult.valueOf(result.statusCode);
        }
        if (platformId == PlatformId.QQ_GAME) {
            platformId = PlatformId.WAN_BA;
        }
        if (platformId == PlatformId.QQ_GAME_IOS) {
            platformId = PlatformId.WAN_BA_IOS;
        }
        User user = userDao.getUser(platformId, result.item);
        long now = System.currentTimeMillis();
        if (user.isDisabled() && now > user.getBeginTime() && now < user.getEndTime()) {
            return TResult.valueOf(ACTOR_HAS_DISABLED);
        }
        if (StringUtils.isBlank(user.getChannel())) {
            user.setChannel(channel);
            dbQueue.updateQueue(user);
        }
        return TResult.sucess(user);
    }

QQ_GAME渠道解析类

代码语言:java
复制
    @Override
    public TResult<String> login(JSONObject params) {
        try {
            String jsCode = params.getString("token");
            if (jsCode == null) {
                return TResult.fail();
            }
            Object pfObject = params.get("pf");
            String pf = "qqqgame";
            if (pfObject != null) {
                pf = pfObject.toString();
            }
            Object viaObject = params.get("via");
            String via = "qqqgame";
            if (viaObject != null) {
                via = viaObject.toString();
            }
            String mobile = params.getString("mobile");
            String mobiletype = params.getString("mobiletype");
            // Integer os = params.getInteger("os");
            Map<String, Object> checkParams = Maps.newHashMap();
            checkParams.put("appid", APPID);
            checkParams.put("secret", SECRET);
            checkParams.put("js_code", jsCode);
            checkParams.put("grant_type", "authorization_code");
            String response = HttpUtils.sendGet(CHECK_LOGIN_URL, checkParams);
            LOGGER.debug("QQGAME checkToken url:{},params:{},response:{}", CHECK_LOGIN_URL, checkParams, response);
            JSONObject jsonObject = JSONObject.parseObject(response);
            if (jsonObject.containsKey("openid")) {
                String openId = jsonObject.getString("openid");
                reportRegaccount(pf, openId, via, mobile, mobiletype, 1);
                Integer platformId = params.getInteger("platformId");
                if (platformId == PlatformId.QQ_GAME) {
                    platformId = PlatformId.WAN_BA;
                }
                if (platformId == PlatformId.QQ_GAME_IOS) {
                    platformId = PlatformId.WAN_BA_IOS;
                }
                User user = userDao.getUser(platformId, openId);
                user.setPf(pf);
                user.setVia(via);
                user.setLastLoginTime(System.currentTimeMillis());
                OPENID_SESSION_KEY_MAP.put(openId, jsonObject.getString("session_key"));
                return TResult.sucess(openId);
            } else {
                LOGGER.error("qqgame login url:{},request:{},response:{}", CHECK_LOGIN_URL, checkParams, response);
            }
        } catch (Exception e) {
            LOGGER.error("{}", e);
        }
        return TResult.fail();
    }

充值系统

登录流程基本就是这些,充值系统差不多更为简单

充值接口

充值商品id,创建订单等玩家支付

代码语言:java
复制
    @RequestMapping(value = "/inquiry", method = RequestMethod.POST)
    public @ResponseBody TResult<Map<String, Object>> inquiry(@RequestBody JSONObject object) {
        Result result = this.tokenValidate(object);
        if (result.isFail()) {
            return TResult.valueOf(result.statusCode);
        }
        if (JsonValidateUtils.paramValidate(object, "serverType", "serverId", "actorId", "shopItem", "ext") == false) {
            return TResult.valueOf(GameModuleStatusCodeConstant.INVALID_PARAM);
        }
        long userId = object.getLongValue("uid");
        int serverType = object.getIntValue("serverType");
        int serverId = object.getIntValue("serverId");
        long actorId = object.getLongValue("actorId");
        Map<String, Object> ext = object.getJSONObject("ext");
        ShopItem shopItem;
        try {
            shopItem = object.getObject("shopItem", ShopItem.class);
        } catch (Exception e) {
            return TResult.valueOf(GameModuleStatusCodeConstant.INVALID_PARAM);
        }
        TResult<Map<String, Object>> inquiryResult = buyOrderFacade.doInquiry(userId, serverType, serverId, actorId, shopItem, ext);
        return inquiryResult;
    }

支付回调接口

玩家支付成功后订单回调

代码语言:java
复制
@Controlle
@RequestMapping(value = "/qqgame")
public class QQGameController {
    private static final Logger LOGGER = LoggerFactory.getLogger(QQGameController.class);
    @Autowired
    private QQGamePlatformImpl platformImpl;
    @Autowired
    private UserFacade userFacade;
    @Value("${server.secert}")
    private String serverSecert;
    @RequestMapping(value = "/deliver", method = RequestMethod.POST)
    public @ResponseBody TResult<Object> deliver(@RequestBody JSONObject params) throws IOException {
        LOGGER.error("deliver request  params:{}", params.toJSONString());
        try {
            return platformImpl.deliver(params);
        } catch (Exception e) {
            LOGGER.error(e.getMessage(), e);
        }
        return TResult.fail();
    }

支付校验

验证通过,订单状态改为成功发送奖励

代码语言:java
复制
    @Override
    public TResult<Object> deliver(JSONObject params) {
        try {
            String openid = params.getString("openid");
            if (openid == null) {
                return TResult.fail();
            }
            String selfSign = this.makePaySign(params);
            String originSign = (String) params.get(SIGN_KEY);
            if (originSign == null || !originSign.equalsIgnoreCase(selfSign)) {
                LOGGER.error("originSign:{},selfSign:{}", originSign, selfSign);
                return TResult.fail();
            }
            String ext = params.getString("app_remark");
            String[] strings = ext.split(",");
            if (strings.length != 6) {
                LOGGER.error("qqgame doDeliver ext error, ext :{}", ext);
            }
            // serverId,actorId,thirdId,orderId,chargeId,os
            int serverId = Integer.valueOf(strings[0]);
            long actorId = Long.valueOf(strings[1]);
            String thirdId = strings[2];
            String cpOrderId = strings[3];
            String chargeId = strings[4];
            String os = strings[5];
            LOGGER.debug(" qqgame serverId:{},actorId:{},thirdId:{},chargeId:{},cpOrderId:{},os:{}", serverId, actorId, thirdId, chargeId, cpOrderId,
                    os);
            BuyOrder buyOrder = buyOrderFacade.getBuyOrder(actorId, cpOrderId);
            Double amount = params.getDouble("amt");
            if (buyOrder == null || !amount.equals(buyOrder.getAmount() * 10)) {
                return TResult.fail();
            }
            this.reportPay(thirdId, buyOrder.getAmount(), buyOrder.getUserId(), cpOrderId, os);
            buyOrder.setChargeId(Integer.parseInt(chargeId));
            buyOrder.setSuccessTimestamp(new Timestamp(System.currentTimeMillis()));
            dbQueue.updateQueue(buyOrder);
            buyOrderFacade.doDeliver(buyOrder);
            return TResult.sucess("success");
        } catch (Exception e) {
            LOGGER.error(e.getMessage(), e);
            e.printStackTrace();
        }
        return TResult.fail();
    }

多记录多思考蛮重要的,这些天写登录充值功能,想到服务器不分区问题,实现也有头绪了

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 服务器方面
    • 共用帐号服务器
      • 游戏服务器分流
        • 其它频道玩家交互
          • 安卓IOS用户同区
          • 数据库分流
          • 登录系统
            • UserController用户接口
              • UserFacadeImpl实现类
            • QQ_GAME渠道解析类
            • 充值系统
              • 充值接口
                • 支付回调接口
                  • 支付校验
                  相关产品与服务
                  TDSQL MySQL 版
                  TDSQL MySQL 版(TDSQL for MySQL)是腾讯打造的一款分布式数据库产品,具备强一致高可用、全球部署架构、分布式水平扩展、高性能、企业级安全等特性,同时提供智能 DBA、自动化运营、监控告警等配套设施,为客户提供完整的分布式数据库解决方案。
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档