feat: add application API documentation and C++ SDK
- Add complete API documentation for application integration (docs/API_DOCUMENT.md) - Fix check-update API to use version ID comparison instead of string comparison - Add validation: client version must exist in server version list - Add support for patch/incremental updates with base_version check - Add C++ SDK with HTTP client, JSON parser, and crypto support - Add simple_test.cpp for standalone testing on Windows Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,447 @@
|
||||
// 独立测试程序 - 不依赖外部库
|
||||
// 使用Windows原生WinHTTP进行HTTP请求
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <winhttp.h>
|
||||
#pragma comment(lib, "winhttp.lib")
|
||||
#else
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netdb.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include "json.hpp"
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <sstream>
|
||||
#include <map>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
// ============================================================================
|
||||
// WinHTTP客户端
|
||||
// ============================================================================
|
||||
class HttpClient {
|
||||
public:
|
||||
HttpClient() {
|
||||
#ifdef _WIN32
|
||||
hSession_ = WinHttpOpen(L"VerifySDK/1.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
|
||||
WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
~HttpClient() {
|
||||
#ifdef _WIN32
|
||||
if (hConnect_) WinHttpCloseHandle(hConnect_);
|
||||
if (hSession_) WinHttpCloseHandle(hSession_);
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string get(const std::string& url) {
|
||||
return request(url, "GET", "");
|
||||
}
|
||||
|
||||
std::string post(const std::string& url, const std::string& body) {
|
||||
return request(url, "POST", body);
|
||||
}
|
||||
|
||||
private:
|
||||
#ifdef _WIN32
|
||||
HINTERNET hSession_ = nullptr;
|
||||
HINTERNET hConnect_ = nullptr;
|
||||
#endif
|
||||
|
||||
std::string request(const std::string& url, const std::string& method, const std::string& body) {
|
||||
std::string result;
|
||||
#ifdef _WIN32
|
||||
// 解析URL
|
||||
std::wstring wurl(url.begin(), url.end());
|
||||
URL_COMPONENTS uc = { sizeof(uc) };
|
||||
wchar_t host[256] = {0};
|
||||
wchar_t path[1024] = {0};
|
||||
uc.lpszHostName = host;
|
||||
uc.dwHostNameLength = 256;
|
||||
uc.lpszUrlPath = path;
|
||||
uc.dwUrlPathLength = 1024;
|
||||
|
||||
if (!WinHttpCrackUrl(wurl.c_str(), 0, 0, &uc)) {
|
||||
throw std::runtime_error("Failed to parse URL");
|
||||
}
|
||||
|
||||
// 连接服务器
|
||||
std::wstring whost(host);
|
||||
hConnect_ = WinHttpConnect(hSession_, whost.c_str(), uc.nPort, 0);
|
||||
if (!hConnect_) {
|
||||
throw std::runtime_error("Failed to connect to server");
|
||||
}
|
||||
|
||||
// 创建请求
|
||||
std::wstring wpath(path);
|
||||
std::wstring wmethod(method.begin(), method.end());
|
||||
DWORD flags = (uc.nScheme == INTERNET_SCHEME_HTTPS) ? WINHTTP_FLAG_SECURE : 0;
|
||||
HINTERNET hRequest = WinHttpOpenRequest(hConnect_, wmethod.c_str(), wpath.c_str(),
|
||||
nullptr, WINHTTP_NO_REFERER,
|
||||
WINHTTP_DEFAULT_ACCEPT_TYPES, flags);
|
||||
if (!hRequest) {
|
||||
throw std::runtime_error("Failed to create request");
|
||||
}
|
||||
|
||||
// 发送请求
|
||||
LPCWSTR headers = L"Content-Type: application/json\r\n";
|
||||
BOOL bResult = WinHttpSendRequest(hRequest, headers, -1,
|
||||
(LPVOID)body.c_str(), body.length(),
|
||||
body.length(), 0);
|
||||
if (!bResult) {
|
||||
WinHttpCloseHandle(hRequest);
|
||||
throw std::runtime_error("Failed to send request");
|
||||
}
|
||||
|
||||
// 接收响应
|
||||
bResult = WinHttpReceiveResponse(hRequest, nullptr);
|
||||
if (!bResult) {
|
||||
WinHttpCloseHandle(hRequest);
|
||||
throw std::runtime_error("Failed to receive response");
|
||||
}
|
||||
|
||||
// 读取数据
|
||||
DWORD dwSize = 0;
|
||||
DWORD dwDownloaded = 0;
|
||||
do {
|
||||
dwSize = 0;
|
||||
if (!WinHttpQueryDataAvailable(hRequest, &dwSize)) break;
|
||||
if (dwSize == 0) break;
|
||||
|
||||
std::vector<char> buffer(dwSize + 1);
|
||||
if (WinHttpReadData(hRequest, &buffer[0], dwSize, &dwDownloaded)) {
|
||||
result.append(buffer.data(), dwDownloaded);
|
||||
}
|
||||
} while (dwSize > 0);
|
||||
|
||||
WinHttpCloseHandle(hRequest);
|
||||
#else
|
||||
// Linux实现(简化版)
|
||||
result = curl_request(url, method, body);
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
std::string curl_request(const std::string& url, const std::string& method, const std::string& body) {
|
||||
// 简化的socket实现(仅用于演示)
|
||||
return "{\"code\": 200, \"message\": \"success\", \"data\": {}}";
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// 验证客户端
|
||||
// ============================================================================
|
||||
class VerifyClient {
|
||||
public:
|
||||
VerifyClient(const std::string& baseUrl, const std::string& appKey)
|
||||
: baseUrl_(baseUrl), appKey_(appKey) {
|
||||
// 移除末尾斜杠
|
||||
if (!baseUrl_.empty() && baseUrl_.back() == '/') {
|
||||
baseUrl_.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
void setToken(const std::string& token) {
|
||||
token_ = token;
|
||||
}
|
||||
|
||||
// 获取应用信息
|
||||
json getAppInfo() {
|
||||
std::string url = baseUrl_ + "/api/v1/app/" + appKey_ + "/info";
|
||||
std::string response = http_.get(url);
|
||||
return json::parse(response);
|
||||
}
|
||||
|
||||
// 检查更新
|
||||
json checkUpdate(const std::string& version = "") {
|
||||
std::string url = baseUrl_ + "/api/v1/app/" + appKey_ + "/check-update";
|
||||
if (!version.empty()) {
|
||||
url += "?version=" + version;
|
||||
}
|
||||
std::string response = http_.get(url);
|
||||
return json::parse(response);
|
||||
}
|
||||
|
||||
// 用户登录
|
||||
json login(const std::string& username, const std::string& password,
|
||||
const std::string& deviceId) {
|
||||
std::string url = baseUrl_ + "/api/v1/app/" + appKey_ + "/login";
|
||||
|
||||
json body;
|
||||
body["username"] = username;
|
||||
body["password"] = password;
|
||||
body["device_id"] = deviceId;
|
||||
|
||||
std::string response = http_.post(url, body.dump());
|
||||
json result = json::parse(response);
|
||||
|
||||
// 自动设置token
|
||||
if (result["code"].get_int() == 200 && result["data"].contains("token")) {
|
||||
token_ = result["data"]["token"].get_string();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// 用户注册
|
||||
json registerUser(const std::string& username, const std::string& password,
|
||||
const std::string& deviceId) {
|
||||
std::string url = baseUrl_ + "/api/v1/app/" + appKey_ + "/register";
|
||||
|
||||
json body;
|
||||
body["username"] = username;
|
||||
body["password"] = password;
|
||||
body["device_id"] = deviceId;
|
||||
|
||||
std::string response = http_.post(url, body.dump());
|
||||
return json::parse(response);
|
||||
}
|
||||
|
||||
// 心跳
|
||||
json heartbeat(uint64_t userId, const std::string& deviceId,
|
||||
const std::string& instanceId = "") {
|
||||
std::string url = baseUrl_ + "/api/v1/app/" + appKey_ + "/heartbeat";
|
||||
|
||||
json body;
|
||||
body["user_id"] = userId;
|
||||
body["device_id"] = deviceId;
|
||||
if (!instanceId.empty()) {
|
||||
body["instance_id"] = instanceId;
|
||||
}
|
||||
|
||||
std::string response = http_.post(url, body.dump());
|
||||
return json::parse(response);
|
||||
}
|
||||
|
||||
// 卡密充值
|
||||
json recharge(const std::string& username, const std::string& cardKey,
|
||||
const std::string& deviceId) {
|
||||
std::string url = baseUrl_ + "/api/v1/app/" + appKey_ + "/recharge";
|
||||
|
||||
json body;
|
||||
body["username"] = username;
|
||||
body["card_key"] = cardKey;
|
||||
body["device_id"] = deviceId;
|
||||
|
||||
std::string response = http_.post(url, body.dump());
|
||||
return json::parse(response);
|
||||
}
|
||||
|
||||
// 获取设备列表
|
||||
json getDevices() {
|
||||
std::string url = baseUrl_ + "/api/v1/app/" + appKey_ + "/devices";
|
||||
std::string response = http_.get(url);
|
||||
return json::parse(response);
|
||||
}
|
||||
|
||||
// 获取云端变量
|
||||
json getVariables() {
|
||||
std::string url = baseUrl_ + "/api/v1/app/" + appKey_ + "/variables";
|
||||
std::string response = http_.get(url);
|
||||
return json::parse(response);
|
||||
}
|
||||
|
||||
private:
|
||||
std::string baseUrl_;
|
||||
std::string appKey_;
|
||||
std::string token_;
|
||||
HttpClient http_;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// 测试函数
|
||||
// ============================================================================
|
||||
void printSeparator(const std::string& title) {
|
||||
std::cout << "\n";
|
||||
std::cout << "========================================\n";
|
||||
std::cout << title << "\n";
|
||||
std::cout << "========================================\n";
|
||||
}
|
||||
|
||||
void testJson() {
|
||||
printSeparator("JSON功能测试");
|
||||
|
||||
// 创建JSON对象
|
||||
json obj = json::object();
|
||||
obj["name"] = "测试应用";
|
||||
obj["version"] = "1.0.0";
|
||||
obj["active"] = true;
|
||||
obj["count"] = 100;
|
||||
|
||||
std::cout << "[创建对象] ";
|
||||
std::cout << obj.dump() << "\n";
|
||||
|
||||
// 创建数组
|
||||
json arr = json::array();
|
||||
arr.push_back("item1");
|
||||
arr.push_back("item2");
|
||||
arr.push_back(123);
|
||||
|
||||
std::cout << "[创建数组] ";
|
||||
std::cout << arr.dump() << "\n";
|
||||
|
||||
// 解析JSON
|
||||
std::string jsonStr = R"({"code":200,"message":"success","data":{"user_id":1,"token":"abc123"}})";
|
||||
json parsed = json::parse(jsonStr);
|
||||
|
||||
std::cout << "[解析结果]\n";
|
||||
std::cout << " code: " << parsed["code"].get_int() << "\n";
|
||||
std::cout << " message: " << parsed["message"].get_string() << "\n";
|
||||
std::cout << " user_id: " << parsed["data"]["user_id"].get_uint64() << "\n";
|
||||
|
||||
std::cout << "\nJSON测试通过!\n";
|
||||
}
|
||||
|
||||
void testApi(const std::string& baseUrl, const std::string& appKey) {
|
||||
printSeparator("API测试");
|
||||
|
||||
std::cout << "服务器: " << baseUrl << "\n";
|
||||
std::cout << "AppKey: " << appKey << "\n\n";
|
||||
|
||||
VerifyClient client(baseUrl, appKey);
|
||||
|
||||
// 测试获取应用信息
|
||||
std::cout << "[测试] 获取应用信息...\n";
|
||||
try {
|
||||
json info = client.getAppInfo();
|
||||
if (info["code"].get_int() == 200) {
|
||||
std::cout << " [成功] 应用名称: " << info["data"]["name"].get_string() << "\n";
|
||||
std::cout << " [成功] 状态: " << info["data"]["status"].get_string() << "\n";
|
||||
} else {
|
||||
std::cout << " [错误] " << info["message"].get_string() << "\n";
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
std::cout << " [异常] " << e.what() << "\n";
|
||||
}
|
||||
|
||||
// 测试检查更新
|
||||
std::cout << "\n[测试] 检查更新...\n";
|
||||
try {
|
||||
json update = client.checkUpdate("1.0.0");
|
||||
if (update["code"].get_int() == 200) {
|
||||
std::cout << " [成功] 最新版本: " << update["data"]["latest_version"].get_string() << "\n";
|
||||
std::cout << " [成功] 有更新: " << (update["data"]["has_update"].get_bool() ? "是" : "否") << "\n";
|
||||
} else {
|
||||
std::cout << " [错误] " << update["message"].get_string() << "\n";
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
std::cout << " [异常] " << e.what() << "\n";
|
||||
}
|
||||
|
||||
// 测试不存在的版本
|
||||
std::cout << "\n[测试] 检查不存在的版本...\n";
|
||||
try {
|
||||
json update = client.checkUpdate("99.99.99");
|
||||
if (update["code"].get_int() == 200) {
|
||||
std::cout << " [意外成功]\n";
|
||||
} else {
|
||||
std::cout << " [预期错误] " << update["message"].get_string() << "\n";
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
std::cout << " [预期异常] " << e.what() << "\n";
|
||||
}
|
||||
|
||||
// 测试登录
|
||||
std::cout << "\n[测试] 用户登录...\n";
|
||||
try {
|
||||
// 使用测试用户
|
||||
json loginResult = client.login("test_cpp_user", "test123456", "cpp_test_device");
|
||||
if (loginResult["code"].get_int() == 200) {
|
||||
std::cout << " [成功] 用户ID: " << loginResult["data"]["user_id"].get_uint64() << "\n";
|
||||
std::cout << " [成功] Token已获取\n";
|
||||
|
||||
uint64_t userId = loginResult["data"]["user_id"].get_uint64();
|
||||
|
||||
// 测试心跳
|
||||
std::cout << "\n[测试] 心跳上报...\n";
|
||||
json heartbeat = client.heartbeat(userId, "cpp_test_device", "instance_001");
|
||||
if (heartbeat["code"].get_int() == 200) {
|
||||
std::cout << " [成功] 心跳成功\n";
|
||||
if (heartbeat["data"].contains("balance")) {
|
||||
std::cout << " [信息] 余额: " << heartbeat["data"]["balance"].get_double() << "\n";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
std::cout << " [错误] " << loginResult["message"].get_string() << "\n";
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
std::cout << " [异常] " << e.what() << "\n";
|
||||
}
|
||||
|
||||
// 测试卡密充值(预期失败)
|
||||
std::cout << "\n[测试] 卡密充值(无效卡密)...\n";
|
||||
try {
|
||||
json recharge = client.recharge("admin", "invalid_card_key", "cpp_test_device");
|
||||
if (recharge["code"].get_int() == 200) {
|
||||
std::cout << " [意外成功] 充值成功\n";
|
||||
} else {
|
||||
std::cout << " [预期错误] " << recharge["message"].get_string() << "\n";
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
std::cout << " [预期异常] " << e.what() << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 主函数
|
||||
// ============================================================================
|
||||
int main(int argc, char* argv[]) {
|
||||
std::cout << R"(
|
||||
╔════════════════════════════════════════════════════════════╗
|
||||
║ 验证平台 SDK C++ 独立测试 ║
|
||||
║ Verify Platform SDK C++ Test ║
|
||||
╚════════════════════════════════════════════════════════════╝
|
||||
)" << "\n";
|
||||
|
||||
// 配置
|
||||
std::string baseUrl = "https://gendan.xyz";
|
||||
std::string appKey = "your_app_key"; // 替换为实际的appKey
|
||||
|
||||
// 解析命令行参数
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
std::string arg = argv[i];
|
||||
if (arg == "--help" || arg == "-h") {
|
||||
std::cout << "用法: " << argv[0] << " [选项]\n";
|
||||
std::cout << "选项:\n";
|
||||
std::cout << " --help, -h 显示帮助信息\n";
|
||||
std::cout << " --url <URL> 设置服务器URL\n";
|
||||
std::cout << " --key <APPKEY> 设置应用密钥\n";
|
||||
std::cout << " --json-only 仅测试JSON功能\n";
|
||||
return 0;
|
||||
} else if (arg == "--url" && i + 1 < argc) {
|
||||
baseUrl = argv[++i];
|
||||
} else if (arg == "--key" && i + 1 < argc) {
|
||||
appKey = argv[++i];
|
||||
} else if (arg == "--json-only") {
|
||||
testJson();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// JSON测试
|
||||
testJson();
|
||||
|
||||
// API测试
|
||||
testApi(baseUrl, appKey);
|
||||
|
||||
std::cout << "\n========================================\n";
|
||||
std::cout << "所有测试完成!\n";
|
||||
std::cout << "========================================\n";
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "\n错误: " << e.what() << "\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user