Refactor: extract Linux/macOS client shared code into common

This commit is contained in:
yuanyuanxiang
2026-05-10 09:49:07 +02:00
parent 70354e244c
commit d46176f4ef
9 changed files with 443 additions and 424 deletions

View File

@@ -0,0 +1,99 @@
// posix_net_helpers.h
// Linux/macOS 客户端共用的网络/Shell 工具execCmd / httpGet / getPublicIP /
// jsonExtract / getGeoLocation。Windows 端已有等价实现,不应包含此头。
//
// 全部 inlineheader-only避免新增 .cpp / 改 CMakeLists。
//
// 设计说明:
// - httpGet 优先 curl备选 wgetLinux 默认自带macOS 默认无 wget缺失时
// wget 命令失败、execCmd 返空——无副作用,等价于"只用 curl"
// - getPublicIP 轮询多个公网 IP 查询源,按顺序尝试直到成功
// - jsonExtract 仅做最简单的 "key":"value" 提取,不依赖 jsoncpp
// - getGeoLocation 通过 ipinfo.io 反查地理位置,与 Windows IPConverter 同源
#pragma once
#include <cstdio>
#include <memory>
#include <string>
#include "common/logger.h"
namespace PosixNet {
// 执行 shell 命令,捕获其 stdout 输出trim 末尾空白后返回)
inline std::string execCmd(const std::string& cmd)
{
std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(cmd.c_str(), "r"), pclose);
if (!pipe) return "";
char buf[4096];
std::string result;
while (fgets(buf, sizeof(buf), pipe.get())) {
result += buf;
}
while (!result.empty() && (result.back() == '\n' || result.back() == '\r' || result.back() == ' '))
result.pop_back();
return result;
}
// HTTP GET 请求:优先 curl备选 wget
inline std::string httpGet(const std::string& url, int timeoutSec = 5)
{
std::string t = std::to_string(timeoutSec);
std::string r = execCmd("curl -s --max-time " + t + " \"" + url + "\" 2>/dev/null");
if (!r.empty()) return r;
r = execCmd("wget -qO- --timeout=" + t + " \"" + url + "\" 2>/dev/null");
return r;
}
// 获取公网 IP轮询多个查询源与 Windows 端 IPConverter 一致)
inline std::string getPublicIP()
{
static const char* urls[] = {
"https://checkip.amazonaws.com",
"https://api.ipify.org",
"https://ipinfo.io/ip",
"https://icanhazip.com",
"https://ifconfig.me/ip",
};
for (auto& url : urls) {
std::string ip = httpGet(url, 3);
if (!ip.empty() && ip.find('.') != std::string::npos && ip.size() <= 45) {
Mprintf("getPublicIP: %s (from %s)\n", ip.c_str(), url);
return ip;
}
}
Mprintf("getPublicIP: all sources failed\n");
return "";
}
// 从 JSON 字符串中提取指定 key 的 string 值(简易解析,不依赖 jsoncpp
// 仅支持 "key": "value" 或 "key":"value" 格式
inline std::string jsonExtract(const std::string& json, const std::string& key)
{
std::string needle = "\"" + key + "\"";
size_t pos = json.find(needle);
if (pos == std::string::npos) return "";
pos = json.find(':', pos + needle.size());
if (pos == std::string::npos) return "";
pos = json.find('"', pos + 1);
if (pos == std::string::npos) return "";
size_t end = json.find('"', pos + 1);
if (end == std::string::npos) return "";
return json.substr(pos + 1, end - pos - 1);
}
// 获取 IP 地理位置ipinfo.io与 Windows IPConverter 同源)
inline std::string getGeoLocation(const std::string& ip)
{
if (ip.empty()) return "";
std::string json = httpGet("https://ipinfo.io/" + ip + "/json", 5);
if (json.empty()) return "";
std::string country = jsonExtract(json, "country");
std::string city = jsonExtract(json, "city");
if (city.empty() && country.empty()) return "";
if (city.empty()) return country;
if (country.empty()) return city;
return city + ", " + country;
}
} // namespace PosixNet