客户端软件 — 用户统计方案

发布时间:2026/8/4 5:22:41
客户端软件 — 用户统计方案
概述本方案通过在客户端启动时上报计算机唯一标识至服务器实现用户数量的统计。整体设计简洁轻量客户端仅需发起一次 HTTP GET 请求服务端则使用 PHP SQLite3 存储数据。一、客户端1. 生成/读取计算机唯一 ID采用 Windows 注册表存储 GUID首次运行时生成并持久化后续启动时直接读取。CStringGetOrCreateComputerID(){CString id;HKEY hKey;// 尝试读取已存在的 IDif(RegOpenKeyEx(HKEY_CURRENT_USER,LSoftware\\GudianShuwu,0,KEY_READ,hKey)ERROR_SUCCESS){WCHAR buf[64]{0};DWORD lensizeof(buf);if(RegQueryValueEx(hKey,LComputerID,NULL,NULL,(LPBYTE)buf,len)ERROR_SUCCESS)idbuf;RegCloseKey(hKey);}// 若不存在则创建新的 GUIDif(id.IsEmpty()){GUID guid;if(SUCCEEDED(CoCreateGuid(guid))){WCHAR buf[64];StringFromGUID2(guid,buf,64);idbuf;if(RegCreateKeyEx(HKEY_CURRENT_USER,LSoftware\\GudianShuwu,0,NULL,0,KEY_WRITE,NULL,hKey,NULL)ERROR_SUCCESS){RegSetValueEx(hKey,LComputerID,0,REG_SZ,(LPBYTE)(LPCWSTR)id,(id.GetLength()1)*sizeof(WCHAR));RegCloseKey(hKey);}}}returnid;}2. 启动时上报程序启动时通过 WinINet 向服务器发送一次 HTTP GET 请求。注意需移除 GUID 中的花括号{}以避免 URL 编码问题。// 统计用户数上报计算机ID到服务器{CString computerIDGetOrCreateComputerID();if(!computerID.IsEmpty()){computerID.Remove(L{);computerID.Remove(L});CString pingUrl;pingUrl.Format(Lhttp://gudianxiaoshuo.com/api/ping/index.php?id%st%u,computerID,GetTickCount());HINTERNET hSessionInternetOpen(_T(GudianShuwu/1.0),INTERNET_OPEN_TYPE_PRECONFIG,NULL,NULL,0);if(hSession){HINTERNET hUrlInternetOpenUrl(hSession,pingUrl,NULL,0,INTERNET_FLAG_RELOAD|INTERNET_FLAG_NO_CACHE_WRITE,0);if(hUrl){charbuf[64];DWORD dwRead0;InternetReadFile(hUrl,buf,sizeof(buf),dwRead);InternetCloseHandle(hUrl);}InternetCloseHandle(hSession);}}}关键设计点设计说明注册表持久化GUID 存储在HKEY_CURRENT_USER\Software\GudianShuwu\ComputerID去掉花括号StringFromGUID2生成{xxx}格式URL 中需移除t随机数添加随机参数以绕过 WinINet 缓存确保每次请求都能真正发出INTERNET_FLAG_RELOAD强制重新加载不读取缓存INTERNET_FLAG_NO_CACHE_WRITE禁止将响应写入本地缓存3. 需要的头文件和库在MainFrm.cpp中添加以下内容#includewininet.h#pragmacomment(lib,wininet.lib)二、服务器端1. 环境阿里云 ECSNginx PHP 7.0文件路径/var/ftp/ttt/gudianxiaoshuo/api/ping/index.php数据库/var/ftp/ttt/gudianxiaoshuo/api/pings.db2. PHP 接口?php$idisset($_GET[id])?$_GET[id]:;if(!$id){echono id;exit;}$dbnewSQLite3(dirname(__FILE__)./../pings.db);$db-exec(CREATE TABLE IF NOT EXISTS pings ( id TEXT PRIMARY KEY, time INTEGER, ip TEXT ));$ipisset($_SERVER[REMOTE_ADDR])?$_SERVER[REMOTE_ADDR]:cli;$stmt$db-prepare(INSERT OR REPLACE INTO pings (id, time, ip) VALUES (:id, :time, :ip));$stmt-bindValue(:id,$id,SQLITE3_TEXT);$stmt-bindValue(:time,time(),SQLITE3_INTEGER);$stmt-bindValue(:ip,$ip,SQLITE3_TEXT);$stmt-execute();echook;数据库表结构字段类型说明idTEXT (主键)计算机 GUIDtimeINTEGER最近一次上报的 Unix 时间戳ipTEXT最近一次上报的 IP 地址3. 权限配置SQLite 写入操作会在目录中创建 journal 临时文件因此目录和数据库文件均需具备写权限chmod777/var/ftp/ttt/gudianxiaoshuo/api/chmod666/var/ftp/ttt/gudianxiaoshuo/api/pings.db三、数据库查询查看所有记录sqlite3-line/var/ftp/ttt/gudianxiaoshuo/api/pings.db\SELECT id, datetime(time, unixepoch, localtime), ip FROM pings;统计总安装数sqlite3 /var/ftp/ttt/gudianxiaoshuo/api/pings.db\SELECT count(*) FROM pings;统计当天活跃用户sqlite3 /var/ftp/ttt/gudianxiaoshuo/api/pings.db\SELECT count(*) FROM pings WHERE date(time, unixepoch, localtime) date(now, localtime);四、踩坑记录URLDownloadToFile 不适合 API 请求— 该函数主要用于下载文件对于纯 API GET 请求可能因缓存等原因导致请求无法到达服务器。改用InternetOpenInternetOpenUrl方案后问题得以解决。GUID 花括号问题—StringFromGUID2返回的 GUID 格式为{xxx}其中的花括号在 URL 中可能引发问题需通过Remove方法将其移除。WinINet 缓存机制— 相同的 URL 会被 WinINet 缓存导致后续请求无法发送至服务器。通过添加t随机参数并设置INTERNET_FLAG_RELOAD标志来解决此问题。SQLite 目录写权限— 仅数据库文件具备写权限是不够的SQLite 在执行写操作时会在同目录下创建-journal临时文件因此目录也需要设置为777权限。避免全局引入 wininet.h— 若将其放在stdafx.h中可能会影响其他已使用窄字符串调用 WinINet 的文件例如TimXmlRpc.cpp。建议仅在需要的.cpp文件中引入。五、数据流客户端启动 │ ▼ GetOrCreateComputerID() 读取注册表 GUID │ ▼ InternetOpenUrl() GET /api/ping/index.php?idxxxtrand │ ▼ Nginx → PHP → SQLite3 INSERT OR REPLACE INTO pings │ ▼ 返回 ok