遊戲禮包啟用碼案例分析
前言
最近我們遊戲有一個通過啟用碼領取禮包的需求,需求大概是這樣:
- 伺服器收到邀請碼後,能判斷啟用碼是否過期
- 同一個啟用碼只能啟用一次
- 一個玩家只能對一個禮包的啟用碼進行啟用
其中,負責該案的策劃同學給了我一種方案,可是我覺得並不是十分合適,該方案大概是這樣,由開放商(也就是我們)去隨機生成一系列的啟用碼,每個啟用碼分別通過配置來繫結對應的禮包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.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]);
}
}
相關文章
- Steam修改遊戲啟用規則 啟用碼必須與賬號錢包區域一致遊戲
- Proxifier註冊碼「Proxifier啟用安裝包」
- EOS原始碼分析(3)案例分析原始碼
- Macos端火爆遊戲:博德之門 3 中文啟用安裝包Mac遊戲
- sort 包原始碼分析原始碼
- SecureCRT中文啟用安裝包+SecureCRT可用註冊碼Securecrt
- 遊戲中的卡片模態皮膚設計【1】—運用案例分析遊戲
- Golang閉包案例分析與普通函式對比Golang函式
- Java 集合包原始碼分析Java原始碼
- 精盡Spring Boot原始碼分析 - Jar 包的啟動實現Spring Boot原始碼JAR
- 以太坊原始碼分析(9)cmd包分析原始碼
- VMware Fusion Pro 12中文啟用安裝包+有效註冊碼
- EOS原始碼分析(4)錢包原始碼
- Power BI實用案例——存貨分析
- 案例分析之JavaScript程式碼優化JavaScript優化
- 二維陣列程式碼案例分析陣列
- Android應用程式啟動過程原始碼分析Android原始碼
- 最新BetterDisplay Pro 啟用安裝包
- mindmaster啟用碼|mindmaster啟用金鑰AST
- Excel 2019 正式版啟用包+啟用許可Excel
- android展訊平臺 重啟案例分析(二)Android
- GoLand 啟用碼GoLand
- PyCharm 啟用碼PyCharm
- 遊戲史記(一):上帝的禮物遊戲
- 啟用碼詐騙:一場遊戲業的信任危機遊戲
- CF指尖上的豪禮活動網址新手與老兵迴歸禮包領取地址
- JProfiler for Mac 14.0.0啟用碼(Java開發分析軟體)MacJava
- 案例分析
- 搶紅包案例分析以及程式碼實現
- 求職奶爸2024屆簡歷禮包求職
- Androd 系統原始碼-3:應用啟動過程的原始碼分析原始碼
- PackageManagerService啟動原始碼分析Package原始碼
- 中文最新Microsoft Remote Desktop 啟用包ROSREM
- 專業終端SSH工具 SecureCRT註冊碼啟用安裝包9.4.3Securecrt
- Word 2021啟用工具+最新啟用安裝包
- 一則資料庫無法重啟的案例分析資料庫
- 一個濫用程式碼的案例
- 故障分析 | MySQL 從機故障重啟後主從同步報錯案例分析MySql主從同步