您的位置:首页 > 其它

游戏礼包激活码案例分析

2014-11-11 14:30 609 查看

前言

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

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

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

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

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

可行性分析

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

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

礼包Id
启始日期
失效日期
随机秘钥
校验码

        在这里,有两项数据的大小是有决定性作用的,礼包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
4000
.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]);
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息