游戏礼包激活码案例分析

前言


        最近我们游戏有一个通过激活码领取礼包的需求,需求大概是这样:

  • 服务器收到邀请码后,能判断激活码是否过期
  • 同一个激活码只能激活一次
  • 一个玩家只能对一个礼包的激活码进行激活

        其中,负责该案的策划同学给了我一种方案,可是我觉得并不是十分合适,该方案大概是这样,由开放商(也就是我们)去随机生成一系列的激活码,每个激活码分别通过配置来绑定对应的礼包Id及有效期,然后再分发到运营渠道去进行活动推广。

        当然这个方法不是说行不通,但是这个方案最大的问题就是维护复杂度过高,后期维护起来有一定的工作量,而且还要承担维护过程中出现问题所造成的风险。试想一下,假如一个渠道需要开放1000个激活码,每个激活码还需要自己去手工配礼包的Id与有效期,只有一个礼包倒还好,如果出现多个礼包,多个有效期的话,在庞大的数据面前,出错率明显会上升,再加上后续会持续添加新的激活码,还要清理已经过期的激活码,添加新激活码的时候还得避免本次生成的激活码在之前没有用过...光是这一系列的工作量,就已经需要单独安排一个人去负责这个激活码的后续维护工作,而且维护过程中所存在的风险是无法避免的。

        如果要想避免上述中维护所带来的问题,最好的做法就是避免后续维护,从而降低工作量与维护风险。上述维护的内容主要是把激活码跟礼包Id与有效期绑定的一个过程,如果让激活码自身就带有这些信息的话,这个维护的过程就可以省下来了。也就是说,我们需要做的事情其实很简单,只需要设计一个合理的生成激活码算法就可以了。

可行性分析


        由于激活码是需要玩家输入的,所以激活码有长度限制,不能太长。要是长度真的太长的话,我们可以考虑做其他的输入方式,比如扫条形码,不过这是后话。以目前的业务来看的话,我们激活码所包含的内容不需要太多,只需要一个对应的礼包Id与对应的有效期就可以了,当然这里的有效期可以跟礼包Id通过配置表关联起来,但是为了后期业务的灵活变更,目前还是把有效期放在了激活码里面。如果只把礼包Id与有效期作为数据源去生成激活码的话,每个激活码都会一样的,所以我们还需要加入一个随机数作为Key,来去对数据源进行编码,这样我们就可以生成不同的激活码了。最后为了保证激活码的合法性,我们还需要生成一个check sum放到激活码里面,这样我们的激活码所需要的数据基本上就已经齐全了。

        接着就是对每项数据划分大小,让生成的激活码长度能保证在能接受的范围之内。目前我们的激活码,所需要的数据分别是

  1. 礼包Id
  2. 启始日期
  3. 失效日期
  4. 随机秘钥
  5. 校验码

        在这里,有两项数据的大小是有决定性作用的,礼包Id的大小会直接影响到后续的礼包数扩展数量限制,随机秘钥的大小范围决定了同样礼包Id与同样有效时间所能生成的最大激活码数量。至于其他的数据项可以有更多的优化空间,有效期的单位有必要的话可以适当地扩大。为了后续的灵活控制,在这我还是选择使用时间戳。校验码的大小可以适当按需求调整控制。目前划分的数据所生成的激活码长度为32个字符,我认为还是能在接受范围内的,所以这个思路可以执行下去。


案例代码

#include <string>
#include <random>
#include <memory>
#include <vector>
#include <iostream>
#include <sstream>

typedef std::shared_ptr<std::vector<std::string> > StringVectorPtr;

struct SFormatParam {
  uint16_t randomNumber;
  uint16_t giftId;
  uint32_t beginTime;
  uint32_t endTime;
  uint32_t checkSum;
};

char g_hexTable[] = "0123456789ABCDEF";
char g_keyBuffer[32];

void printUsage(const char* appName) {
  std::cout << "Usage:" << std::endl << std::endl;
  std::cout << "If you want to make gift code, you must like this:" << std::endl;
  std::cout << appName << " <gift-id> <begin-time-stamp> <end-time-stamp> <make-amount>" << std::endl << std::endl;
  std::cout << "And then, if you want to check gift code, you must like this:" << std::endl;
  std::cout << appName << " <gift-code>" << std::endl;
}

uint8_t hexToNumber(char hexChar) {
  if (hexChar >= '0' && hexChar <= '9') {
    return hexChar - '0';
  } else if (hexChar >= 'a' && hexChar <= 'f') {
    return hexChar - 'a' + 10;
  } else if (hexChar >= 'A' && hexChar <= 'F') {
    return hexChar - 'A' + 10;
  } else {
    printf("[Error]hexToNumber hexChar\n");
    return 0;
  }
}

bool hexToData(const char hex[], uint8_t data[], size_t len) {
  for (size_t i = 0; i < len; ++i) {
    size_t j = i << 1;
    int count = 2;

    for (int k = 0; k < count; ++k) {
      uint8_t byte = hex[j + k];
      if ((byte < '0' || byte > '9') && (byte < 'a' || byte > 'f') && (byte < 'A' || byte > 'F')) {
        return false;
      }
    }

    data[i] = static_cast<uint8_t>((hexToNumber(hex[j]) << 4) | hexToNumber(hex[j + 1]));
  }

  return true;
}

void dataToHex(const uint8_t data[], char outStr[], size_t len) {
  for (size_t i = 0; i < len; ++i) {
    uint8_t byte = data[i];
    size_t j = i << 1;

    outStr[j] = g_hexTable[byte >> 4];
    outStr[j + 1] = g_hexTable[byte & 0x0F];
  }
}

uint32_t getCheckSum(SFormatParam& outParam) {
  return ((outParam.randomNumber << 16 )+ outParam.giftId) ^ (outParam.beginTime + outParam.endTime);
}

bool getCodeInfo(const char hexStr[], SFormatParam& outParam) {
  if (!hexToData(hexStr, reinterpret_cast<uint8_t*>(&outParam), sizeof(outParam))) {
    return false;
  }

  uint32_t checkSum = getCheckSum(outParam);
  if (outParam.checkSum != checkSum) {
    printf("[Error]check sum error! check sum should be %x, but now is %x\n", checkSum, outParam.checkSum);
    return false;
  }

  uint16_t key = ~outParam.randomNumber;
  outParam.giftId ^= key;
  outParam.beginTime ^= key;
  outParam.beginTime ^= key << 16;
  outParam.endTime ^= key;
  outParam.endTime ^= key << 16;

  return true;
}

StringVectorPtr getGiftCode(uint32_t giftId, uint32_t beginTime, uint32_t endTime, uint32_t amount) {
  StringVectorPtr resultPtr(new std::vector<std::string>);
  srand(time(nullptr));

  while (amount--) {
    SFormatParam param = {
      .randomNumber = 0,
      .giftId = static_cast<uint16_t>(giftId),
      .beginTime = beginTime,
      .endTime = endTime,
    };

    param.randomNumber = static_cast<uint16_t>(rand() & ((1 << 16) - 1));

    uint16_t key = ~param.randomNumber;
    param.giftId ^= key;
    param.beginTime ^= key;
    param.beginTime ^= key << 16;
    param.endTime ^= key;
    param.endTime ^= key << 16;
    param.checkSum = getCheckSum(param),

    dataToHex(reinterpret_cast<const uint8_t*>(¶m), g_keyBuffer, sizeof(param));

    resultPtr->push_back(std::string(g_keyBuffer));
  }

  return resultPtr;
}

int main(int argc, char* args[]) {
  if (argc == 5) {
    StringVectorPtr ptr = getGiftCode(atoi(args[1]), atoi(args[2]), atoi(args[3]), atoi(args[4]));
    for (auto code : *ptr) {
      std::cout << code << std::endl;
    }
  } else if (argc == 2) {
    SFormatParam param;
    if (getCodeInfo(args[1], param)) {
      std::cout << "gift id:" << param.giftId << std::endl;
      std::cout << "begin time:" << param.beginTime << std::endl;
      std::cout << "end time:" << param.endTime << std::endl;
    }
  } else {
    printUsage(args[0]);
  }
}


  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值