前言

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

  • 服务器收到邀请码后,能判断激活码是否过期
  • 同一个激活码只能激活一次
  • 一个玩家只能对一个礼包的激活码进行激活
        其中,负责该案的策划同学给了我一种方案,可是我觉得并不是十分合适,该方案大概是这样,由开放商(也就是我们)去随机生成一系列的激活码,每个激活码分别通过配置来绑定对应的礼包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]);}
}

游戏礼包激活码案例分析相关推荐

  1. 如何设计和生成游戏的激活码

    游戏的激活码,也叫作奖励码.兑换码,通常是由字符和数字组成的字符串,用于在游戏的推广阶段发放给玩家,玩家在下载登录游戏之后兑换获得相应的奖励. 首先设计我们激活码的规则 字符 + 数字 组成 长度待定 ...

  2. day22 案例 发送邮箱激活码 购物车 分析

    2019独角兽企业重金招聘Python工程师标准>>> 邮箱激活码: 购物车分析: 转载于:https://my.oschina.net/u/2356966/blog/650116

  3. JAVA23种设计模式学习,源码+案例分析+类图讲解

    本文对JAVA中23种设计模式进行了简单的讲解,幷加以实际案例进行辅助理解,每种模式都会举例说明,幷将源码开源至gitee和githbu上.JAVA目录下为源码,resources目录下的UML文件夹 ...

  4. 【大话设计模式-2】UML 类图的绘制(源码案例分析)

    文章目录 1 UML 基本介绍 2 UML 图 3 UML 类图 4 类图-依赖关系(Dependence) 5 类图-泛化关系(generalization) 6 类图-实现关系(Implement ...

  5. php游戏礼包源码,php 游戏新手卡领号程序管理系统 v2.5

    游戏新手卡领号程序管理系统是蓝色动力网络自主开发的一套用于游戏推广的领号程序,采用php+mysql,后台使用了ExtJs,让操作更简单,界面更漂亮!该程序适合做个人博客,实现了伪静态功能,更利于se ...

  6. spring事务不生效和事务不回滚的原因(源码案例分析)

    1.首先需要加事务的方法不能是私有的(如果方法私有,则事务不生效) spring源码如下 在AbstractFallbackTransactionAttributeSource.computeTran ...

  7. 设计模式 6 - 原型模式及spring源码案例分析

    目录 原型模式 1. 原型模式的引入 2. 原型模式的介绍和使用场景 3. 原理结构图UML图 4. 代码演示 5. 原型模式在spring中的使用 6.深拷贝的实现方式 7. 小结 原型模式 博主一 ...

  8. 如何制作流畅有力的游戏动画+Skullgirls案例分析

    游戏动画与影视动画制作虽然看似有共通之处,但其实差别很大. 游戏动画是为玩家交互体验服务,同时需要与程序合作使其在设备上流畅运行,因此也有各种制作上的限制与要求. 在旧金山每年一度举办的游戏开发者大会 ...

  9. 当局者迷旁观者清红警游戏反杀局案例分析

    粉丝问我:南天可不可以低吸?我没回复: 有朋友向我感叹:股市跌的时候心情不好,涨的时候心情也不好.我没有理会. 对于这波行情走势特别上周五的大涨,我的直观感受是因为 国脉科技和南天信息的超预期,引发踏 ...

最新文章

  1. Silverlight:SSL教程
  2. python字典一键多值_python字典中如何一键多值的写入?
  3. Python写各大聊天系统的屏蔽脏话功能原理
  4. 波士顿动力机器狗测评来了!售价堪比豪车,避障、导航、舞蹈样样都行,买不起还能租...
  5. cuda 核函数 for循环_【CUDA 基础】4.4 核函数可达到的带宽
  6. 在.net开发过程中遇到的问题种种
  7. C# 访问数据的时候报错 (拒绝了对对象 'XXXX' (数据库 'SHQY',架构 'dbo')的 SELECT 权限)...
  8. Codeforces Round #442 (Div. 2) 877E - Danil and a Part-time Job dfs序+线段树
  9. ollvm源码分析之控制流扁平化(3)
  10. java ee课程设计_javaee课程设计
  11. 第二章计算机应用基础试题答案,职中计算机应用基础第二章测试题及答案.doc...
  12. python统计套利_基于python的统计套利实战(二)之协整检验
  13. android微信打不开怎么办,微信打不开怎么回事 微信打不开怎么办
  14. 如何通过SEO思维收割各大平台的流量?
  15. iPad播放网页视频(h5 video)失败的处理方法(Django网站)
  16. 华为云k8s环境部署应用
  17. python定时更换mac 超美桌面背景
  18. 【FNN回归预测】基于matlab蝙蝠算法优化前馈神经网络数据回归预测【含Matlab源码 2070期】
  19. html十六进制和RGB颜色表
  20. Ubuntu - command checklist

热门文章

  1. flashback总结六之Flashback_Transaction_Query
  2. localtime、localtime_s、localtime_r的使用
  3. modbustcp测试工具怎么用_【转】年轻人不讲武德不仅白piao接口测试知识还白piao接口测试工具会员...
  4. JSP实用教程-JSP语法
  5. 【SSL 协议介绍】
  6. VRP和调度问题的主流精确算法和启发式算法
  7. MySQL性能剖析工具(pt-query-digest)
  8. Tekton Pipeline 教程
  9. Linux搭建集群、负载。
  10. python的request发请求报500原因