游戏礼包激活码案例分析
2014-11-11 14:30
609 查看
前言
最近我们游戏有一个通过激活码领取礼包的需求,需求大概是这样:服务器收到邀请码后,能判断激活码是否过期
同一个激活码只能激活一次
一个玩家只能对一个礼包的激活码进行激活
其中,负责该案的策划同学给了我一种方案,可是我觉得并不是十分合适,该方案大概是这样,由开放商(也就是我们)去随机生成一系列的激活码,每个激活码分别通过配置来绑定对应的礼包Id及有效期,然后再分发到运营渠道去进行活动推广。
当然这个方法不是说行不通,但是这个方案最大的问题就是维护复杂度过高,后期维护起来有一定的工作量,而且还要承担维护过程中出现问题所造成的风险。试想一下,假如一个渠道需要开放1000个激活码,每个激活码还需要自己去手工配礼包的Id与有效期,只有一个礼包倒还好,如果出现多个礼包,多个有效期的话,在庞大的数据面前,出错率明显会上升,再加上后续会持续添加新的激活码,还要清理已经过期的激活码,添加新激活码的时候还得避免本次生成的激活码在之前没有用过...光是这一系列的工作量,就已经需要单独安排一个人去负责这个激活码的后续维护工作,而且维护过程中所存在的风险是无法避免的。
如果要想避免上述中维护所带来的问题,最好的做法就是避免后续维护,从而降低工作量与维护风险。上述维护的内容主要是把激活码跟礼包Id与有效期绑定的一个过程,如果让激活码自身就带有这些信息的话,这个维护的过程就可以省下来了。也就是说,我们需要做的事情其实很简单,只需要设计一个合理的生成激活码算法就可以了。
可行性分析
由于激活码是需要玩家输入的,所以激活码有长度限制,不能太长。要是长度真的太长的话,我们可以考虑做其他的输入方式,比如扫条形码,不过这是后话。以目前的业务来看的话,我们激活码所包含的内容不需要太多,只需要一个对应的礼包Id与对应的有效期就可以了,当然这里的有效期可以跟礼包Id通过配置表关联起来,但是为了后期业务的灵活变更,目前还是把有效期放在了激活码里面。如果只把礼包Id与有效期作为数据源去生成激活码的话,每个激活码都会一样的,所以我们还需要加入一个随机数作为Key,来去对数据源进行编码,这样我们就可以生成不同的激活码了。最后为了保证激活码的合法性,我们还需要生成一个checksum放到激活码里面,这样我们的激活码所需要的数据基本上就已经齐全了。
接着就是对每项数据划分大小,让生成的激活码长度能保证在能接受的范围之内。目前我们的激活码,所需要的数据分别是
礼包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]); } }
相关文章推荐
- 案例分析:渡口网络选择“戴尔服务器+IP SAN 存储+EWC 服务”一体化方案建设高效游戏运营平台
- 游戏活动策划案例分析
- 典型案例分析:史玉柱做游戏“营销”的九点解读
- Android 游戏引擎libgdx 资源加载进度百分比显示案例分析
- day22 案例 发送邮箱激活码 & 购物车 分析
- 【读书笔记】《推荐系统(recommender systems An introduction)》第八章 案例分析:移动互联网个性化游戏推荐
- LBS在国内的三类案例分析:社交、团购和游戏
- [Mugeda HTML5技术教程之16]案例分析:制作跨屏互动游戏
- [Mugeda HTML5技术教程之16]案例分析:制作跨屏互动游戏
- 游戏程序设计--真实案例分析(一)
- 真实案例分析:游戏线程处理,在英特尔® 处理器上获得高性能
- Cocos文档案例游戏设计的梳理与分析
- 案例分析:培训合同与赔款事宜
- 餐饮服务案例分析(预订)
- read-Atleap-10-主业务分析-ContentField类-Hibernate多对一关系实施案例
- 案例分析1---Spestore System Design
- 编写优秀Bug报告的艺术及案例分析
- 一个基于工作流管理系统的需求分析案例
- 案例分析2 --- Design for Log module
- read-Atleap-11-主业务分析-NewsItem类-Hibernate继承关系实施案例