这些天在看项目的登录功能,思考游戏如何实现所有玩家同区,服务器满人不能和朋友一起玩,开新区会减少老区玩家会流失,还有一区情节等,也有人喜欢去新服新生态重新开始,但总体来说我认为弊大于利。
有些每个服务器上都有web服,游戏服,每个区玩家绑定很难实现。
上家公司是一个web帐号服,一个世界服,对应一批游戏服,世界服是跨服活动方面,具体看:**跨服夺矿战实现
例如一个安卓帐号服+世界服,对应30个游戏服务器,ios帐号服对应10个游戏服,其它各渠道对应2个,现在想想应该是可以按安卓,ios,各渠道分区的。
帐号服务器主要用于玩家登录信息,还有支付订单,或者渠道特有的优惠礼包等活动奖励,公司项目其实已经完成。
参考了这篇文章:类似于QQ游戏百万人同时在线的服务器架构实现
游戏分区主要原因还是服务器方面无法承受大量玩家同时在线,公司单个服务器一般5000用户左右。我的想法是,分区改成分频道或者分线路,有些游戏就是这种做法,但玩家登录web验证后,推荐人少的频道服务器,在游戏中每次切换频道实质就是切换其他游戏服务器,用户数据传过去不需要重新登录。维护也简单,关服直接关帐号服即可。
服务器器分流产生的问题是数据不一致,以前世界服是用来管理跨服活动,现在可以用来管理不同服务器玩家交流。
如果是公主连接那种没有其它用户界面,最多就添加好友,加工会的时候记录下id,查看成员的通过世界服查看成员id请求对方数据库服信息。如果是大型MMORPG,组队消息推送当前服务器,切换服务器也影响不大,队长进副本,世在界服创建新副本,拉取队伍成员用户。帮派帮战同理创建新地图即可,如果世界服压力也大,分类世界副本服,世界帮战服,王者荣耀不同区玩家跨服战斗原理应该差不多,世界服创建新战场。LOL,DOTA2也是,MOBA实现起来应该很简单,大量的战斗数据都在战斗服务器上。
这个web帐号服放一起就行,加个字段区分安卓还是ios玩家。
服务器问题解决后来看看数据库,web服公用一个数据,储存帐号登录游戏验证密钥,支付订单等信息。
玩家没有固定服务器,可以把玩家以前绑定的服务器id变成数据库服务器id,实质绑定服务器变成了绑定数据库,登录时读取对应数据库信息,分流游戏服,世界服,数据库服都可以根据玩家数量变化而变化
@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();
}
根据渠道id对应不用的渠道解析类
@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);
}
@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,创建订单等玩家支付
@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;
}
玩家支付成功后订单回调
@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();
}
验证通过,订单状态改为成功发送奖励
@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 删除。