Feature: Automatically start frp client for subordinate #2
@@ -55,9 +55,13 @@ static bool FreeFrpPortAllocation(int port, const std::string& expectedOwner);
|
|||||||
// 获取所有授权信息
|
// 获取所有授权信息
|
||||||
std::vector<LicenseInfo> GetAllLicenses()
|
std::vector<LicenseInfo> GetAllLicenses()
|
||||||
{
|
{
|
||||||
|
std::lock_guard<std::recursive_mutex> _lock(LicensesIniMutex());
|
||||||
std::vector<LicenseInfo> licenses;
|
std::vector<LicenseInfo> licenses;
|
||||||
std::string iniPath = GetLicensesPath();
|
std::string iniPath = GetLicensesPath();
|
||||||
|
|
||||||
|
// 注意:CIniParser 走 ifstream 读取整文件,与 WritePrivateProfileString 的内核锁
|
||||||
|
// 不在同一域。必须靠这里的 g_licensesIniMutex 阻止与其它写入交错,否则可能读到
|
||||||
|
// 写入到一半的中间态。
|
||||||
CIniParser parser;
|
CIniParser parser;
|
||||||
if (!parser.LoadFile(iniPath.c_str()))
|
if (!parser.LoadFile(iniPath.c_str()))
|
||||||
return licenses;
|
return licenses;
|
||||||
@@ -306,6 +310,7 @@ void CLicenseDlg::OnSize(UINT nType, int cx, int cy)
|
|||||||
// 更新授权状态
|
// 更新授权状态
|
||||||
bool SetLicenseStatus(const std::string& deviceID, const std::string& status)
|
bool SetLicenseStatus(const std::string& deviceID, const std::string& status)
|
||||||
{
|
{
|
||||||
|
std::lock_guard<std::recursive_mutex> _lock(LicensesIniMutex());
|
||||||
std::string iniPath = GetLicensesPath();
|
std::string iniPath = GetLicensesPath();
|
||||||
config cfg(iniPath);
|
config cfg(iniPath);
|
||||||
|
|
||||||
@@ -457,6 +462,7 @@ int ParseHostNumFromPasscode(const std::string& passcode)
|
|||||||
// 设置待续期信息
|
// 设置待续期信息
|
||||||
bool SetPendingRenewal(const std::string& deviceID, const std::string& expireDate, int hostNum, int quota)
|
bool SetPendingRenewal(const std::string& deviceID, const std::string& expireDate, int hostNum, int quota)
|
||||||
{
|
{
|
||||||
|
std::lock_guard<std::recursive_mutex> _lock(LicensesIniMutex());
|
||||||
std::string iniPath = GetLicensesPath();
|
std::string iniPath = GetLicensesPath();
|
||||||
config cfg(iniPath);
|
config cfg(iniPath);
|
||||||
|
|
||||||
@@ -475,6 +481,7 @@ bool SetPendingRenewal(const std::string& deviceID, const std::string& expireDat
|
|||||||
// 获取待续期信息
|
// 获取待续期信息
|
||||||
RenewalInfo GetPendingRenewal(const std::string& deviceID)
|
RenewalInfo GetPendingRenewal(const std::string& deviceID)
|
||||||
{
|
{
|
||||||
|
std::lock_guard<std::recursive_mutex> _lock(LicensesIniMutex());
|
||||||
RenewalInfo info;
|
RenewalInfo info;
|
||||||
std::string iniPath = GetLicensesPath();
|
std::string iniPath = GetLicensesPath();
|
||||||
config cfg(iniPath);
|
config cfg(iniPath);
|
||||||
@@ -488,6 +495,7 @@ RenewalInfo GetPendingRenewal(const std::string& deviceID)
|
|||||||
// 清除待续期信息
|
// 清除待续期信息
|
||||||
bool ClearPendingRenewal(const std::string& deviceID)
|
bool ClearPendingRenewal(const std::string& deviceID)
|
||||||
{
|
{
|
||||||
|
std::lock_guard<std::recursive_mutex> _lock(LicensesIniMutex());
|
||||||
std::string iniPath = GetLicensesPath();
|
std::string iniPath = GetLicensesPath();
|
||||||
config cfg(iniPath);
|
config cfg(iniPath);
|
||||||
|
|
||||||
@@ -498,8 +506,11 @@ bool ClearPendingRenewal(const std::string& deviceID)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 配额递减,返回是否还有剩余配额
|
// 配额递减,返回是否还有剩余配额
|
||||||
|
// 关键:read-modify-write 的 PendingQuota 必须在锁内完成,否则与 SetPendingRenewal
|
||||||
|
// 并发会丢失用户刚设置的预设续期(旧 bug:用户报告"预设续期消失"的根因)。
|
||||||
bool DecrementPendingQuota(const std::string& deviceID)
|
bool DecrementPendingQuota(const std::string& deviceID)
|
||||||
{
|
{
|
||||||
|
std::lock_guard<std::recursive_mutex> _lock(LicensesIniMutex());
|
||||||
std::string iniPath = GetLicensesPath();
|
std::string iniPath = GetLicensesPath();
|
||||||
config cfg(iniPath);
|
config cfg(iniPath);
|
||||||
|
|
||||||
@@ -512,7 +523,7 @@ bool DecrementPendingQuota(const std::string& deviceID)
|
|||||||
cfg.SetInt(deviceID, "PendingQuota", quota);
|
cfg.SetInt(deviceID, "PendingQuota", quota);
|
||||||
|
|
||||||
if (quota <= 0) {
|
if (quota <= 0) {
|
||||||
// 配额用完,清除待续期信息
|
// 配额用完,清除待续期信息(嵌套加锁,recursive_mutex 安全)
|
||||||
ClearPendingRenewal(deviceID);
|
ClearPendingRenewal(deviceID);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -616,6 +627,7 @@ void CLicenseDlg::OnLicenseRenewal()
|
|||||||
// 设置授权备注
|
// 设置授权备注
|
||||||
bool SetLicenseRemark(const std::string& deviceID, const std::string& remark)
|
bool SetLicenseRemark(const std::string& deviceID, const std::string& remark)
|
||||||
{
|
{
|
||||||
|
std::lock_guard<std::recursive_mutex> _lock(LicensesIniMutex());
|
||||||
std::string iniPath = GetLicensesPath();
|
std::string iniPath = GetLicensesPath();
|
||||||
config cfg(iniPath);
|
config cfg(iniPath);
|
||||||
|
|
||||||
@@ -659,6 +671,7 @@ void CLicenseDlg::OnLicenseEditRemark()
|
|||||||
// 删除授权
|
// 删除授权
|
||||||
bool DeleteLicense(const std::string& deviceID)
|
bool DeleteLicense(const std::string& deviceID)
|
||||||
{
|
{
|
||||||
|
std::lock_guard<std::recursive_mutex> _lock(LicensesIniMutex());
|
||||||
std::string iniPath = GetLicensesPath();
|
std::string iniPath = GetLicensesPath();
|
||||||
config cfg(iniPath);
|
config cfg(iniPath);
|
||||||
|
|
||||||
@@ -679,6 +692,10 @@ bool DeleteLicense(const std::string& deviceID)
|
|||||||
// 删除该 section (通过写入 NULL 删除整个 section)
|
// 删除该 section (通过写入 NULL 删除整个 section)
|
||||||
BOOL ret = ::WritePrivateProfileStringA(deviceID.c_str(), NULL, NULL, iniPath.c_str());
|
BOOL ret = ::WritePrivateProfileStringA(deviceID.c_str(), NULL, NULL, iniPath.c_str());
|
||||||
::WritePrivateProfileStringA(NULL, NULL, NULL, iniPath.c_str()); // 刷新缓存
|
::WritePrivateProfileStringA(NULL, NULL, NULL, iniPath.c_str()); // 刷新缓存
|
||||||
|
|
||||||
|
// 关键:清掉 UpdateLicenseActivity 的内存缓存。否则若同 SN 客户端再次连上来,
|
||||||
|
// cache 命中会跳过落盘 → disk 永远不会重建被删的 section。
|
||||||
|
InvalidateLicenseActivityCache(deviceID);
|
||||||
return ret != FALSE;
|
return ret != FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -860,12 +877,17 @@ void CLicenseDlg::OnLicenseViewIPs()
|
|||||||
|
|
||||||
// 如果有记录被删除,保存更新后的 IP 列表
|
// 如果有记录被删除,保存更新后的 IP 列表
|
||||||
if (removedCount > 0) {
|
if (removedCount > 0) {
|
||||||
std::string iniPath = GetLicensesPath();
|
// 锁内只做 I/O —— UI 控件更新(SetItemText)放锁外,避免锁内触发
|
||||||
config cfg(iniPath);
|
// 任何可能的消息循环回调,保持锁占用时间最短
|
||||||
cfg.SetStr(lic.SerialNumber, "IP", newIPList);
|
{
|
||||||
lic.IP = newIPList; // 更新内存中的数据
|
std::lock_guard<std::recursive_mutex> _lock(LicensesIniMutex());
|
||||||
|
std::string iniPath = GetLicensesPath();
|
||||||
|
config cfg(iniPath);
|
||||||
|
cfg.SetStr(lic.SerialNumber, "IP", newIPList);
|
||||||
|
}
|
||||||
|
lic.IP = newIPList; // 更新内存中的数据(与 m_Licenses 同步,不需要锁)
|
||||||
|
|
||||||
// 更新列表显示
|
// 更新列表显示(UI 线程操作,必须在锁外)
|
||||||
CString strIPDisplay = FormatIPDisplay(newIPList).c_str();
|
CString strIPDisplay = FormatIPDisplay(newIPList).c_str();
|
||||||
m_ListLicense.SetItemText(nItem, LIC_COL_IP, strIPDisplay);
|
m_ListLicense.SetItemText(nItem, LIC_COL_IP, strIPDisplay);
|
||||||
}
|
}
|
||||||
@@ -985,6 +1007,9 @@ bool FindLicenseByIPAndMachine(const std::string& ip, const std::string& machine
|
|||||||
{
|
{
|
||||||
if (ip.empty()) return false;
|
if (ip.empty()) return false;
|
||||||
|
|
||||||
|
// 加锁保护整个 list 遍历,避免与并发的 SetStr(IP, ...) 交错读到中间态。
|
||||||
|
// GetAllLicenses 内部也加锁,recursive_mutex 允许嵌套。
|
||||||
|
std::lock_guard<std::recursive_mutex> _lock(LicensesIniMutex());
|
||||||
auto licenses = GetAllLicenses();
|
auto licenses = GetAllLicenses();
|
||||||
for (const auto& lic : licenses) {
|
for (const auto& lic : licenses) {
|
||||||
if (lic.IP.empty()) continue;
|
if (lic.IP.empty()) continue;
|
||||||
@@ -1167,6 +1192,7 @@ void CLicenseDlg::OnLicenseAutoFrp()
|
|||||||
FreeFrpPortAllocation(existingPort, lic.SerialNumber); // 仅当旧端口确实归属本 SN 时才释放
|
FreeFrpPortAllocation(existingPort, lic.SerialNumber); // 仅当旧端口确实归属本 SN 时才释放
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
|
std::lock_guard<std::recursive_mutex> _lock(LicensesIniMutex());
|
||||||
std::string iniPath = GetLicensesPath();
|
std::string iniPath = GetLicensesPath();
|
||||||
config cfg(iniPath);
|
config cfg(iniPath);
|
||||||
cfg.SetStr(lic.SerialNumber, "FrpConfig", frpConfig);
|
cfg.SetStr(lic.SerialNumber, "FrpConfig", frpConfig);
|
||||||
@@ -1215,6 +1241,7 @@ void CLicenseDlg::OnLicenseRevokeFrp()
|
|||||||
|
|
||||||
// 清除 licenses.ini 中该授权的 FrpConfig 字段
|
// 清除 licenses.ini 中该授权的 FrpConfig 字段
|
||||||
{
|
{
|
||||||
|
std::lock_guard<std::recursive_mutex> _lock(LicensesIniMutex());
|
||||||
std::string iniPath = GetLicensesPath();
|
std::string iniPath = GetLicensesPath();
|
||||||
config cfg(iniPath);
|
config cfg(iniPath);
|
||||||
cfg.SetStr(lic.SerialNumber, "FrpConfig", "");
|
cfg.SetStr(lic.SerialNumber, "FrpConfig", "");
|
||||||
|
|||||||
@@ -14,11 +14,65 @@
|
|||||||
#include "InputDlg.h"
|
#include "InputDlg.h"
|
||||||
#include "FrpsForSubDlg.h"
|
#include "FrpsForSubDlg.h"
|
||||||
#include "UIBranding.h"
|
#include "UIBranding.h"
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <ctime>
|
||||||
|
|
||||||
// 外部函数声明
|
// 外部函数声明
|
||||||
extern std::vector<std::string> splitString(const std::string& str, char delimiter);
|
extern std::vector<std::string> splitString(const std::string& str, char delimiter);
|
||||||
extern std::string GetFirstMasterIP(const std::string& master);
|
extern std::string GetFirstMasterIP(const std::string& master);
|
||||||
|
|
||||||
|
// ---- licenses.ini 并发与写抑制基础设施 (P1) ----
|
||||||
|
// 见 CPasswordDlg.h 中 LicensesIniMutex() 注释。这里给出实例。
|
||||||
|
std::recursive_mutex& LicensesIniMutex()
|
||||||
|
{
|
||||||
|
static std::recursive_mutex m;
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
// UpdateLicenseActivity 的写抑制缓存:仅当字段实际变化或节流过期时才落盘。
|
||||||
|
//
|
||||||
|
// ⚠️ Cache key 是 "SN|IP|machine" 三元组而非单 SN,因为同一 SN 可能被多个客户端
|
||||||
|
// 共用(团购授权场景:上百台机器共用一个序列号)。若按 SN 索引,多客户端的
|
||||||
|
// (IP, machine) 会在 cache 里反复互相覆盖 → ipChanged 几乎每次都为 true →
|
||||||
|
// 写抑制完全失效(实测从 0.6 次/秒只降到 0.7 次/秒)。
|
||||||
|
//
|
||||||
|
// 5s 心跳 × 100 客户端,每客户端独立 30s 节流后 → 100/30 ≈ 3.3 次落盘/秒。
|
||||||
|
// Passcode/HMAC 是 per-SN 的,按本结构会在每个客户端的 entry 里冗余存一份,
|
||||||
|
// 续期换码时所有客户端会各自触发一次重写(写入同一新值),冗余但无害。
|
||||||
|
struct LicenseActivityCache {
|
||||||
|
time_t lastFlushTime = 0; // 上次实际落盘的 epoch 秒
|
||||||
|
std::string lastPasscode; // 上次写入 ini 的 Passcode
|
||||||
|
std::string lastHMAC; // 上次写入 ini 的 HMAC
|
||||||
|
std::string lastLocation; // 上次写入 ini 的 Location
|
||||||
|
std::string lastIPWriteDate; // 上次写 IP 列表时的日期 yyMMdd
|
||||||
|
};
|
||||||
|
// Key 格式:"SN|IP|machine"。IP/machine 可能为空(ctx == null 路径),
|
||||||
|
// 此时 key 形如 "SN||" —— 该路径自成一类节流域,互不干扰。
|
||||||
|
std::unordered_map<std::string, LicenseActivityCache> g_activityCache;
|
||||||
|
|
||||||
|
// 30 秒节流窗口:LastActiveTime 最多 30 秒落盘一次(即便其它字段未变)。
|
||||||
|
// UI 显示的"最后活跃"最多延迟 30 秒,业务可接受。
|
||||||
|
constexpr int LAST_ACTIVE_THROTTLE_SECONDS = 30;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 由 DeleteLicense 等"在 cache 视野外修改了 disk"的路径调用,清掉某 SN 名下所有
|
||||||
|
// (IP, machine) entry,强制下次 UpdateLicenseActivity 走 firstTime 路径重建 section。
|
||||||
|
// Cache key 形如 "SN|IP|machine",同一 SN 可能对应多个 entry(多客户端共用授权),
|
||||||
|
// 必须按前缀遍历清除。
|
||||||
|
void InvalidateLicenseActivityCache(const std::string& deviceID)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::recursive_mutex> _lock(LicensesIniMutex());
|
||||||
|
const std::string prefix = deviceID + "|";
|
||||||
|
for (auto it = g_activityCache.begin(); it != g_activityCache.end(); ) {
|
||||||
|
if (it->first.compare(0, prefix.size(), prefix) == 0) {
|
||||||
|
it = g_activityCache.erase(it);
|
||||||
|
} else {
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// CPasswordDlg 对话框
|
// CPasswordDlg 对话框
|
||||||
|
|
||||||
IMPLEMENT_DYNAMIC(CPasswordDlg, CDialogEx)
|
IMPLEMENT_DYNAMIC(CPasswordDlg, CDialogEx)
|
||||||
@@ -105,6 +159,7 @@ bool SaveLicenseInfo(const std::string& deviceID, const std::string& passcode,
|
|||||||
const std::string& authorization,
|
const std::string& authorization,
|
||||||
const std::string& frpConfig)
|
const std::string& frpConfig)
|
||||||
{
|
{
|
||||||
|
std::lock_guard<std::recursive_mutex> _lock(LicensesIniMutex());
|
||||||
std::string iniPath = GetLicensesPath();
|
std::string iniPath = GetLicensesPath();
|
||||||
config cfg(iniPath);
|
config cfg(iniPath);
|
||||||
|
|
||||||
@@ -146,6 +201,7 @@ bool SaveLicenseInfo(const std::string& deviceID, const std::string& passcode,
|
|||||||
bool LoadLicenseInfo(const std::string& deviceID, std::string& passcode,
|
bool LoadLicenseInfo(const std::string& deviceID, std::string& passcode,
|
||||||
std::string& hmac, std::string& remark)
|
std::string& hmac, std::string& remark)
|
||||||
{
|
{
|
||||||
|
std::lock_guard<std::recursive_mutex> _lock(LicensesIniMutex());
|
||||||
std::string iniPath = GetLicensesPath();
|
std::string iniPath = GetLicensesPath();
|
||||||
config cfg(iniPath);
|
config cfg(iniPath);
|
||||||
|
|
||||||
@@ -161,6 +217,7 @@ bool LoadLicenseInfo(const std::string& deviceID, std::string& passcode,
|
|||||||
// 加载授权的 FRP 配置
|
// 加载授权的 FRP 配置
|
||||||
std::string LoadLicenseFrpConfig(const std::string& deviceID)
|
std::string LoadLicenseFrpConfig(const std::string& deviceID)
|
||||||
{
|
{
|
||||||
|
std::lock_guard<std::recursive_mutex> _lock(LicensesIniMutex());
|
||||||
std::string iniPath = GetLicensesPath();
|
std::string iniPath = GetLicensesPath();
|
||||||
config cfg(iniPath);
|
config cfg(iniPath);
|
||||||
return cfg.GetStr(deviceID, "FrpConfig", "");
|
return cfg.GetStr(deviceID, "FrpConfig", "");
|
||||||
@@ -169,6 +226,7 @@ std::string LoadLicenseFrpConfig(const std::string& deviceID)
|
|||||||
// 加载授权的 Authorization(用于 V2 授权返回给第一层)
|
// 加载授权的 Authorization(用于 V2 授权返回给第一层)
|
||||||
std::string LoadLicenseAuthorization(const std::string& deviceID)
|
std::string LoadLicenseAuthorization(const std::string& deviceID)
|
||||||
{
|
{
|
||||||
|
std::lock_guard<std::recursive_mutex> _lock(LicensesIniMutex());
|
||||||
std::string iniPath = GetLicensesPath();
|
std::string iniPath = GetLicensesPath();
|
||||||
config cfg(iniPath);
|
config cfg(iniPath);
|
||||||
return cfg.GetStr(deviceID, "Authorization", "");
|
return cfg.GetStr(deviceID, "Authorization", "");
|
||||||
@@ -177,6 +235,7 @@ std::string LoadLicenseAuthorization(const std::string& deviceID)
|
|||||||
// 更新授权的 Authorization(V2 续期时更新)
|
// 更新授权的 Authorization(V2 续期时更新)
|
||||||
bool UpdateLicenseAuthorization(const std::string& deviceID, const std::string& authorization)
|
bool UpdateLicenseAuthorization(const std::string& deviceID, const std::string& authorization)
|
||||||
{
|
{
|
||||||
|
std::lock_guard<std::recursive_mutex> _lock(LicensesIniMutex());
|
||||||
std::string iniPath = GetLicensesPath();
|
std::string iniPath = GetLicensesPath();
|
||||||
config cfg(iniPath);
|
config cfg(iniPath);
|
||||||
|
|
||||||
@@ -313,59 +372,115 @@ static int GetIPCount(const std::string& ipListStr)
|
|||||||
return (int)ipList.size();
|
return (int)ipList.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新授权活跃信息
|
// 更新授权活跃信息(带写抑制)
|
||||||
|
//
|
||||||
|
// 设计要点:心跳每 5 秒触发一次本函数;同样的 SN 在稳态下绝大多数字段不会变化。
|
||||||
|
// 朴素实现每次心跳都做 6-8 次 SetStr (整文件重写),5 秒就是一轮全文件 I/O 风暴,
|
||||||
|
// 100 在线时会饱和。本实现引入 in-memory 缓存 g_activityCache:
|
||||||
|
// - 字段未变化 + LastActiveTime 节流窗口(30 秒)内 → 直接 return,零 I/O
|
||||||
|
// - 字段变化(passcode/HMAC/IP/Location 任一)→ 仅写变化字段
|
||||||
|
// - 节流过期 → 只写 LastActiveTime(轻量刷新)
|
||||||
|
// - IP 列表中的时间戳是日级精度(yyMMdd),跨天必须重写一次以刷新日期
|
||||||
|
//
|
||||||
|
// 注意:仅在落盘成功后才更新 cache,保证 cache 永远反映"磁盘上当前值"。
|
||||||
bool UpdateLicenseActivity(const std::string& deviceID, const std::string& passcode,
|
bool UpdateLicenseActivity(const std::string& deviceID, const std::string& passcode,
|
||||||
const std::string& hmac, const std::string& ip,
|
const std::string& hmac, const std::string& ip,
|
||||||
const std::string& location, const std::string& machineName)
|
const std::string& location, const std::string& machineName)
|
||||||
{
|
{
|
||||||
|
std::lock_guard<std::recursive_mutex> _lock(LicensesIniMutex());
|
||||||
|
|
||||||
|
// Cache key 是 (SN, IP, machine) 三元组 —— 同 SN 多客户端共用授权时各自独立节流。
|
||||||
|
// 若同 SN 不同 (ip, machine) 共用一个 cache entry,IP 字段会在不同客户端间反复
|
||||||
|
// 翻转,每次心跳都判定为 ipChanged → 写抑制完全失效。
|
||||||
|
const std::string cacheKey = deviceID + "|" + ip + "|" + machineName;
|
||||||
|
auto& cache = g_activityCache[cacheKey];
|
||||||
|
time_t now = time(nullptr);
|
||||||
|
|
||||||
|
// 计算今日日期串(yyMMdd),用于和 IP 列表时间戳比对
|
||||||
|
SYSTEMTIME st;
|
||||||
|
GetLocalTime(&st);
|
||||||
|
char today[8];
|
||||||
|
sprintf_s(today, "%02d%02d%02d", st.wYear % 100, st.wMonth, st.wDay);
|
||||||
|
|
||||||
|
// —— 决策阶段:判断本次心跳是否真的需要落盘 ——
|
||||||
|
// 注意:因 cacheKey 已经包含 (IP, machine),不同的客户端会落到不同 entry,
|
||||||
|
// 所以不再需要在字段比对中处理 IP/machine 变化 —— 那种"变化"其实是 cache miss。
|
||||||
|
const bool firstTime = (cache.lastFlushTime == 0);
|
||||||
|
const bool passcodeChanged = (passcode != cache.lastPasscode);
|
||||||
|
const bool hmacChanged = (hmac != cache.lastHMAC);
|
||||||
|
const bool ipDayChanged = !ip.empty() && !cache.lastIPWriteDate.empty() &&
|
||||||
|
std::string(today) != cache.lastIPWriteDate;
|
||||||
|
const bool locationChanged = !location.empty() && (location != cache.lastLocation);
|
||||||
|
const bool throttleExpired = (now - cache.lastFlushTime >= LAST_ACTIVE_THROTTLE_SECONDS);
|
||||||
|
|
||||||
|
if (!firstTime && !passcodeChanged && !hmacChanged
|
||||||
|
&& !ipDayChanged && !locationChanged && !throttleExpired) {
|
||||||
|
// 100% cache 命中:本客户端的所有字段都与上次落盘一致且节流未过期
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// —— 落盘阶段:仅写真正需要写的字段 ——
|
||||||
std::string iniPath = GetLicensesPath();
|
std::string iniPath = GetLicensesPath();
|
||||||
config cfg(iniPath);
|
config cfg(iniPath);
|
||||||
|
|
||||||
// 检查该授权是否存在
|
// 检查该授权是否存在(注意:此处仍需读磁盘,因为我们不缓存"是否存在"的事实)
|
||||||
std::string existingPasscode = cfg.GetStr(deviceID, "Passcode", "");
|
std::string existingPasscode = cfg.GetStr(deviceID, "Passcode", "");
|
||||||
if (existingPasscode.empty()) {
|
const bool isNewRecord = existingPasscode.empty();
|
||||||
// 授权不存在,但验证成功了,说明是既往授权,自动添加记录
|
|
||||||
|
if (isNewRecord) {
|
||||||
|
// 授权不存在但验证成功 —— 既往授权自动加入
|
||||||
cfg.SetStr(deviceID, "SerialNumber", deviceID);
|
cfg.SetStr(deviceID, "SerialNumber", deviceID);
|
||||||
cfg.SetStr(deviceID, "Passcode", passcode);
|
cfg.SetStr(deviceID, "Passcode", passcode);
|
||||||
cfg.SetStr(deviceID, "HMAC", hmac);
|
cfg.SetStr(deviceID, "HMAC", hmac);
|
||||||
cfg.SetStr(deviceID, "Remark", "既往授权自动加入");
|
cfg.SetStr(deviceID, "Remark", "既往授权自动加入");
|
||||||
cfg.SetStr(deviceID, "Status", LICENSE_STATUS_ACTIVE); // 新记录默认为有效
|
cfg.SetStr(deviceID, "Status", LICENSE_STATUS_ACTIVE);
|
||||||
} else {
|
} else {
|
||||||
// 授权已存在,更新 passcode(续期后 passcode 会变化)
|
// 已存在:只在 passcode/hmac 实际变化时才写(续期场景才会变)
|
||||||
cfg.SetStr(deviceID, "Passcode", passcode);
|
if (firstTime || passcodeChanged) {
|
||||||
cfg.SetStr(deviceID, "HMAC", hmac);
|
cfg.SetStr(deviceID, "Passcode", passcode);
|
||||||
|
}
|
||||||
|
if (firstTime || hmacChanged) {
|
||||||
|
cfg.SetStr(deviceID, "HMAC", hmac);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新最后活跃时间
|
// LastActiveTime:走到这里就更新(节流过期或字段变化都需要刷新)
|
||||||
SYSTEMTIME st;
|
|
||||||
GetLocalTime(&st);
|
|
||||||
char timeStr[32];
|
char timeStr[32];
|
||||||
sprintf_s(timeStr, "%04d-%02d-%02d %02d:%02d:%02d",
|
sprintf_s(timeStr, "%04d-%02d-%02d %02d:%02d:%02d",
|
||||||
st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond);
|
st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond);
|
||||||
cfg.SetStr(deviceID, "LastActiveTime", timeStr);
|
cfg.SetStr(deviceID, "LastActiveTime", timeStr);
|
||||||
|
|
||||||
// 如果是新添加的记录,设置创建时间
|
if (isNewRecord) {
|
||||||
if (existingPasscode.empty()) {
|
|
||||||
cfg.SetStr(deviceID, "CreateTime", timeStr);
|
cfg.SetStr(deviceID, "CreateTime", timeStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新 IP 列表(追加新 IP 或更新已有 IP 的时间戳)
|
// IP 列表:本客户端首次 或 同客户端跨天 才重写(UpdateIPList 会在 disk 上合并)
|
||||||
// 格式: IP(机器名)|yyMMdd
|
if (!ip.empty() && (firstTime || ipDayChanged)) {
|
||||||
if (!ip.empty()) {
|
|
||||||
std::string existingIPList = cfg.GetStr(deviceID, "IP", "");
|
std::string existingIPList = cfg.GetStr(deviceID, "IP", "");
|
||||||
std::string newIPList = UpdateIPList(existingIPList, ip, machineName);
|
std::string newIPList = UpdateIPList(existingIPList, ip, machineName);
|
||||||
cfg.SetStr(deviceID, "IP", newIPList);
|
cfg.SetStr(deviceID, "IP", newIPList);
|
||||||
|
cache.lastIPWriteDate = today;
|
||||||
}
|
}
|
||||||
if (!location.empty()) {
|
|
||||||
|
if (!location.empty() && (firstTime || locationChanged)) {
|
||||||
cfg.SetStr(deviceID, "Location", location);
|
cfg.SetStr(deviceID, "Location", location);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// —— 同步缓存(必须在落盘成功后)——
|
||||||
|
cache.lastFlushTime = now;
|
||||||
|
cache.lastPasscode = passcode;
|
||||||
|
cache.lastHMAC = hmac;
|
||||||
|
if (!location.empty()) {
|
||||||
|
cache.lastLocation = location;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查授权是否已被撤销
|
// 检查授权是否已被撤销
|
||||||
bool IsLicenseRevoked(const std::string& deviceID)
|
bool IsLicenseRevoked(const std::string& deviceID)
|
||||||
{
|
{
|
||||||
|
std::lock_guard<std::recursive_mutex> _lock(LicensesIniMutex());
|
||||||
std::string iniPath = GetLicensesPath();
|
std::string iniPath = GetLicensesPath();
|
||||||
config cfg(iniPath);
|
config cfg(iniPath);
|
||||||
std::string status = cfg.GetStr(deviceID, "Status", LICENSE_STATUS_ACTIVE);
|
std::string status = cfg.GetStr(deviceID, "Status", LICENSE_STATUS_ACTIVE);
|
||||||
|
|||||||
@@ -2,10 +2,23 @@
|
|||||||
|
|
||||||
#include <afx.h>
|
#include <afx.h>
|
||||||
#include <afxwin.h>
|
#include <afxwin.h>
|
||||||
|
#include <mutex>
|
||||||
#include "Resource.h"
|
#include "Resource.h"
|
||||||
#include "common/commands.h"
|
#include "common/commands.h"
|
||||||
#include "LangManager.h"
|
#include "LangManager.h"
|
||||||
|
|
||||||
|
// 全局 licenses.ini 互斥锁(Meyers singleton,跨翻译单元共享)。
|
||||||
|
// 所有读写 licenses.ini 的函数入口必须加锁,否则在心跳并发下会出现
|
||||||
|
// read-modify-write 丢更新(典型受害者:PendingQuota / IP 列表)。
|
||||||
|
// 使用 recursive_mutex 是因为部分函数会嵌套调用(如 DecrementPendingQuota → ClearPendingRenewal)。
|
||||||
|
std::recursive_mutex& LicensesIniMutex();
|
||||||
|
|
||||||
|
// 让 UpdateLicenseActivity 内部缓存里某个 SN 的 entry 失效。
|
||||||
|
// 必须在外部修改了授权(删除 / 重新创建 section)后调用,否则 cache 命中策略
|
||||||
|
// 会跳过本应触发的"既往授权自动加入"路径,导致 disk 上的 section 不会重建。
|
||||||
|
// 实现在 CPasswordDlg.cpp,需持 LicensesIniMutex(内部会自行加锁,可在已加锁线程嵌套调用)。
|
||||||
|
void InvalidateLicenseActivityCache(const std::string& deviceID);
|
||||||
|
|
||||||
// CPasswordDlg 对话框
|
// CPasswordDlg 对话框
|
||||||
namespace TcpClient {
|
namespace TcpClient {
|
||||||
std::string ObfuscateAuthorization(const std::string& auth);
|
std::string ObfuscateAuthorization(const std::string& auth);
|
||||||
|
|||||||
Reference in New Issue
Block a user