Clients.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. import path from 'path';
  2. import { fileURLToPath } from 'url';
  3. import Cache from '../libs/cache.js';
  4. const __filename = fileURLToPath(import.meta.url);
  5. const __dirname = path.dirname(__filename);
  6. const ClientsCacheFile = path.join(__dirname, '../data/clients.cache');
  7. const CLIENT_HEADER_FIELDS = [
  8. 'X-Device',
  9. 'X-Version',
  10. 'X-Data-Type',
  11. 'X-Market-Type',
  12. 'X-Group-Sequence',
  13. 'X-Platform',
  14. ];
  15. const CLIENT_KEY_FIELDS = [
  16. 'X-Data-Type',
  17. 'X-Market-Type',
  18. 'X-Group-Sequence',
  19. 'X-Platform',
  20. ];
  21. const CLIENT_FIELD_NAMES = {
  22. 'X-Data-Type': 'dataType',
  23. 'X-Device': 'device',
  24. 'X-Group-Sequence': 'groupSequence',
  25. 'X-Market-Type': 'marketType',
  26. 'X-Platform': 'platform',
  27. 'X-Version': 'version',
  28. };
  29. const CLIENTS = {
  30. Items: {},
  31. };
  32. let saveTimer = null;
  33. const normalizeHeaderValue = (value) => {
  34. if (Array.isArray(value)) {
  35. return value.join(',');
  36. }
  37. return value == null ? '' : String(value).trim();
  38. }
  39. const getRequestHeader = (req, field) => {
  40. return normalizeHeaderValue(req.get?.(field));
  41. }
  42. const getClientHeaders = (req) => {
  43. return CLIENT_HEADER_FIELDS.reduce((headers, field) => {
  44. headers[field] = getRequestHeader(req, field);
  45. return headers;
  46. }, {});
  47. }
  48. const formatClientFields = (headers) => {
  49. return CLIENT_HEADER_FIELDS.reduce((clientFields, field) => {
  50. clientFields[CLIENT_FIELD_NAMES[field]] = headers[field];
  51. return clientFields;
  52. }, {});
  53. }
  54. const getClientKey = (headers) => {
  55. return CLIENT_KEY_FIELDS
  56. .map(field => `${field}:${headers[field] ?? ''}`)
  57. .join('|');
  58. }
  59. const getClientIp = (req) => {
  60. const ip = getRequestHeader(req, 'X-Real-IP') ||
  61. normalizeHeaderValue(req.ip) ||
  62. normalizeHeaderValue(req.socket?.remoteAddress) ||
  63. normalizeHeaderValue(req.connection?.remoteAddress);
  64. return ip.replace(/^::ffff:/, '');
  65. }
  66. const scheduleSaveClientsToCache = () => {
  67. if (saveTimer) {
  68. clearTimeout(saveTimer);
  69. }
  70. saveTimer = setTimeout(saveClientsToCache, 1000);
  71. }
  72. const recordRequest = (req) => {
  73. const route = req.path;
  74. const headers = getClientHeaders(req);
  75. const clientFields = formatClientFields(headers);
  76. const key = getClientKey(headers);
  77. const now = Date.now();
  78. const current = CLIENTS.Items[key] ?? {};
  79. CLIENTS.Items[key] = {
  80. key,
  81. ...clientFields,
  82. deviceId: current.deviceId,
  83. ip: getClientIp(req),
  84. route,
  85. firstRequestTime: current.firstRequestTime ?? now,
  86. lastRequestTime: now,
  87. requestCount: (current.requestCount ?? 0) + 1,
  88. };
  89. scheduleSaveClientsToCache();
  90. return CLIENTS.Items[key];
  91. }
  92. const getClients = () => {
  93. return Object.values(CLIENTS.Items)
  94. .sort((a, b) => (b.lastRequestTime ?? 0) - (a.lastRequestTime ?? 0));
  95. }
  96. const updateClient = ({ key, deviceId } = {}) => {
  97. if (!key || !CLIENTS.Items[key]) {
  98. return Promise.reject(new Error('CLIENT_NOT_FOUND'));
  99. }
  100. if (deviceId === undefined || deviceId === null || deviceId === '') {
  101. delete CLIENTS.Items[key].deviceId;
  102. saveClientsToCache();
  103. return Promise.resolve(CLIENTS.Items[key]);
  104. }
  105. const parsedDeviceId = Number(deviceId);
  106. if (!Number.isInteger(parsedDeviceId)) {
  107. return Promise.reject(new Error('DEVICE_ID_INVALID'));
  108. }
  109. CLIENTS.Items[key] = {
  110. ...CLIENTS.Items[key],
  111. deviceId: parsedDeviceId,
  112. };
  113. saveClientsToCache();
  114. return Promise.resolve(CLIENTS.Items[key]);
  115. }
  116. const deleteClient = (key) => {
  117. if (!key || !CLIENTS.Items[key]) {
  118. return Promise.reject(new Error('CLIENT_NOT_FOUND'));
  119. }
  120. delete CLIENTS.Items[key];
  121. saveClientsToCache();
  122. return Promise.resolve();
  123. }
  124. function saveClientsToCache() {
  125. if (saveTimer) {
  126. clearTimeout(saveTimer);
  127. saveTimer = null;
  128. }
  129. Cache.setData(ClientsCacheFile, CLIENTS);
  130. }
  131. function loadClientsFromCache() {
  132. const cachedClients = Cache.getData(ClientsCacheFile, true);
  133. if (!cachedClients?.Items) {
  134. return;
  135. }
  136. CLIENTS.Items = cachedClients.Items;
  137. }
  138. loadClientsFromCache();
  139. process.on('exit', saveClientsToCache);
  140. process.on('SIGINT', () => {
  141. process.exit(0);
  142. });
  143. process.on('SIGTERM', () => {
  144. process.exit(0);
  145. });
  146. process.on('SIGUSR2', () => {
  147. process.exit(0);
  148. });
  149. const Clients = {
  150. recordRequest,
  151. getClients,
  152. updateClient,
  153. deleteClient,
  154. };
  155. export { recordRequest, getClients, updateClient, deleteClient };
  156. export default Clients;