Improve: client/server - stable client ID via MachineGuid+path (V2)
This commit is contained in:
@@ -1342,15 +1342,25 @@ VOID CMy2015RemoteDlg::AddList(CString strIP, CString strAddr, CString strPCName
|
||||
verDisplay, install, startTime, v[RES_CLIENT_TYPE].empty() ? "?" : v[RES_CLIENT_TYPE].c_str(), path,
|
||||
v[RES_CLIENT_PUBIP].empty() ? strIP : v[RES_CLIENT_PUBIP].c_str(), startTime, capStr,
|
||||
};
|
||||
auto id = CONTEXT_OBJECT::CalculateID(data);
|
||||
auto id_str = std::to_string(id);
|
||||
if (v[RES_CLIENT_ID].empty()) {
|
||||
v[RES_CLIENT_ID] = id_str;
|
||||
} else if (id_str != v[RES_CLIENT_ID]) {
|
||||
Mprintf("上线消息 - 主机ID错误: calc=%llu, recv=%s, IP=%s, Path=%s\n",
|
||||
id, v[RES_CLIENT_ID].c_str(), strIP.GetString(), path.GetString());
|
||||
// 优先采用客户端自报的 ID(新客户端用 V2 算法 = MachineGuid + 归一化路径,
|
||||
// 比服务端按老算法 IP+PC+OS+CPU+PATH 重算更稳定)。
|
||||
// 客户端未发或解析失败时,回退到服务端老算法重算(兼容老客户端)。
|
||||
auto computedId = CONTEXT_OBJECT::CalculateID(data);
|
||||
uint64_t id = 0;
|
||||
if (!v[RES_CLIENT_ID].empty()) {
|
||||
id = std::strtoull(v[RES_CLIENT_ID].c_str(), nullptr, 10);
|
||||
}
|
||||
if (id == 0) {
|
||||
id = computedId;
|
||||
v[RES_CLIENT_ID] = std::to_string(id);
|
||||
}
|
||||
|
||||
bool modify = false, needConvert = true;
|
||||
|
||||
// 新客户端 V2 ID 算法首次上线时,把老 ID 下的元数据迁过来。
|
||||
if (TryMigrateClientMetadata(id, strPCName, path)) {
|
||||
modify = true;
|
||||
}
|
||||
CString loc = m_ClientMap->GetClientMapData(id, MAP_LOCATION);
|
||||
if (loc.IsEmpty()) {
|
||||
loc = v[RES_CLIENT_LOC].c_str();
|
||||
@@ -5612,6 +5622,65 @@ LRESULT CMy2015RemoteDlg::OnUserToOnlineList(WPARAM wParam, LPARAM lParam)
|
||||
}
|
||||
|
||||
|
||||
// 启发式 ID 迁移:当新客户端 V2 算法生成的 ID 在 m_ClientMap 里没条目时,
|
||||
// 按 (ComputerName, ProgramPath) 扫老条目找唯一匹配,把元数据搬过去。
|
||||
// 设计取舍见相关讨论:
|
||||
// - 严格 ComputerName 相等 + 大小写不敏感 ProgramPath 匹配。
|
||||
// - 多候选保守跳过——避免局域网同 hostname+同路径多机互相串备注。
|
||||
// - 老条目不删,作为审计/回滚依据;dat 文件不会显著膨胀。
|
||||
//
|
||||
// 线程安全说明:
|
||||
// - AddList 由 SendMessage(WM_USERTOONLINELIST) 进入,跑在 UI 线程,串行。
|
||||
// - newId 在本函数返回前没进入 m_HostList,UI 上看不到这台机器,
|
||||
// 用户无法通过右键触发 OnOnlineHostnote 写 newId 的备注。
|
||||
// - IO 线程的 AUTH 写发生在登录认证流之后,针对的是已注册客户端,不会
|
||||
// 在 newId 首次出现的瞬间踩进来。
|
||||
// - GetAll() 返回快照副本,迭代期间老条目被并发修改不影响匹配(我们只
|
||||
// 看 ComputerName/ProgramPath,这两个字段不会被并发改写)。
|
||||
// - _ClientList 实现内部有同步(项目里其它路径同样不持 m_cs 调用它)。
|
||||
// 故无需额外加锁。
|
||||
bool CMy2015RemoteDlg::TryMigrateClientMetadata(uint64_t newId, const CString& pcName, const CString& exePath)
|
||||
{
|
||||
if (!m_ClientMap) return false;
|
||||
if (m_ClientMap->Exists(newId)) return false; // 已有条目,无需迁移
|
||||
|
||||
const std::string targetPC = pcName.GetString();
|
||||
const std::string targetPath = exePath.GetString();
|
||||
|
||||
// 扫描所有条目,找匹配的老 ID(多于一个就停,按歧义处理)
|
||||
std::vector<ClientKey> candidates;
|
||||
for (const auto& kv : m_ClientMap->GetAll()) {
|
||||
if (kv.first == newId) continue;
|
||||
if (strcmp(kv.second.ComputerName, targetPC.c_str()) == 0 &&
|
||||
_stricmp(kv.second.ProgramPath, targetPath.c_str()) == 0) {
|
||||
candidates.push_back(kv.first);
|
||||
if (candidates.size() > 1) break;
|
||||
}
|
||||
}
|
||||
|
||||
if (candidates.size() > 1) {
|
||||
Mprintf("ID 迁移歧义: PC=%s, Path=%s 命中 %zu+ 个候选,保守跳过——"
|
||||
"需要运维手动确认是哪台机器的元数据\n",
|
||||
targetPC.c_str(), targetPath.c_str(), candidates.size());
|
||||
return false;
|
||||
}
|
||||
if (candidates.empty()) return false; // 真正的新机器
|
||||
|
||||
// 唯一匹配:复制用户可设置的元数据到新 ID(其它字段下次心跳/上线会覆盖)
|
||||
ClientKey oldId = candidates[0];
|
||||
m_ClientMap->SetClientMapData(newId, MAP_NOTE,
|
||||
m_ClientMap->GetClientMapData(oldId, MAP_NOTE).GetString());
|
||||
m_ClientMap->SetClientMapData(newId, MAP_LOCATION,
|
||||
m_ClientMap->GetClientMapData(oldId, MAP_LOCATION).GetString());
|
||||
m_ClientMap->SetClientMapInteger(newId, MAP_LEVEL,
|
||||
m_ClientMap->GetClientMapInteger(oldId, MAP_LEVEL));
|
||||
m_ClientMap->SetClientMapInteger(newId, MAP_AUTH,
|
||||
m_ClientMap->GetClientMapInteger(oldId, MAP_AUTH));
|
||||
Mprintf("ID 迁移: %llu -> %llu (PC=%s, Path=%s)\n",
|
||||
oldId, newId, targetPC.c_str(), targetPath.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
// 根据列表显示索引获取 context(考虑分组过滤)
|
||||
context* CMy2015RemoteDlg::GetContextByListIndex(int iItem)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user