diff --git a/Common/config/default.json b/Common/config/default.json index ef8cac8f..ffed7af6 100644 --- a/Common/config/default.json +++ b/Common/config/default.json @@ -188,7 +188,8 @@ "sessionclosecommand": "2m", "pemStdTTL": "1h", "pemCheckPeriod": "10m", - "updateVersionStatus": "5m" + "updateVersionStatus": "5m", + "monthUniqueUsers": "1y" }, "ipfilter": { "rules": [{"address": "*", "allowed": true}], diff --git a/Common/sources/constants.js b/Common/sources/constants.js index e5f07b4d..aac4b8ef 100644 --- a/Common/sources/constants.js +++ b/Common/sources/constants.js @@ -228,6 +228,7 @@ exports.REDIS_KEY_PRESENCE_HASH = 'presence:hash:'; exports.REDIS_KEY_PRESENCE_SET = 'presence:set:'; exports.REDIS_KEY_PRESENCE_UNIQUE_USERS = 'presence:unique:users'; exports.REDIS_KEY_PRESENCE_UNIQUE_USERS_HASH = 'presence:unique:users:hash'; +exports.REDIS_KEY_PRESENCE_MONTH_UNIQUE_USERS_HASH = 'presence:unique:users:month'; exports.REDIS_KEY_LOCKS = 'locks:'; exports.REDIS_KEY_LOCK_DOCUMENT = 'lockdocument:'; exports.REDIS_KEY_MESSAGE = 'message:'; diff --git a/Common/sources/license.js b/Common/sources/license.js index c651c605..3f84ab75 100644 --- a/Common/sources/license.js +++ b/Common/sources/license.js @@ -39,6 +39,8 @@ const oBuildDate = new Date(buildDate); exports.readLicense = function*() { const c_LR = constants.LICENSE_RESULT; + var now = new Date(); + var startDate = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1));//first day of current month return [{ count: 1, type: c_LR.Success, @@ -53,6 +55,7 @@ exports.readLicense = function*() { hasLicense: false, plugins: false, buildDate: oBuildDate, + startDate: startDate, endDate: null }, null]; }; diff --git a/Common/sources/utils.js b/Common/sources/utils.js index 182dc67d..b9da6a2d 100644 --- a/Common/sources/utils.js +++ b/Common/sources/utils.js @@ -930,6 +930,7 @@ exports.convertLicenseInfoToFileParams = function(licenseInfo) { // whiteLabel = false; // } let license = {}; + license.start_date = licenseInfo.startDate && licenseInfo.startDate.toJSON(); license.end_date = licenseInfo.endDate && licenseInfo.endDate.toJSON(); license.timelimited = 0 !== (constants.LICENSE_MODE.Limited & licenseInfo.mode); license.trial = 0 !== (constants.LICENSE_MODE.Trial & licenseInfo.mode); @@ -966,3 +967,43 @@ exports.checkBaseUrl = function(baseUrl) { exports.resolvePath = function(object, path, defaultValue) { return path.split('.').reduce((o, p) => o ? o[p] : defaultValue, object); } + +Date.isLeapYear = function (year) { + return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0)); +}; + +Date.getDaysInMonth = function (year, month) { + return [31, (Date.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month]; +}; + +Date.prototype.isLeapYear = function () { + return Date.isLeapYear(this.getUTCFullYear()); +}; + +Date.prototype.getDaysInMonth = function () { + return Date.getDaysInMonth(this.getUTCFullYear(), this.getUTCMonth()); +}; + +Date.prototype.addMonths = function (value) { + var n = this.getUTCDate(); + this.setUTCDate(1); + this.setUTCMonth(this.getUTCMonth() + value); + this.setUTCDate(Math.min(n, this.getDaysInMonth())); + return this; +}; +function getMonthDiff(d1, d2) { + var months; + months = (d2.getUTCFullYear() - d1.getUTCFullYear()) * 12; + months -= d1.getUTCMonth(); + months += d2.getUTCMonth(); + return months; +} + +exports.getLicensePeriod = function(startDate, now) { + startDate = new Date(startDate.getTime());//clone + startDate.addMonths(getMonthDiff(startDate, now)); + if (startDate > now) { + startDate.addMonths(-1); + } + return startDate.getTime(); +}; diff --git a/DocService/sources/DocsCoServer.js b/DocService/sources/DocsCoServer.js index d243ac97..b7118a85 100644 --- a/DocService/sources/DocsCoServer.js +++ b/DocService/sources/DocsCoServer.js @@ -556,6 +556,8 @@ function* updateEditUsers(userId, anonym) { const expireAt = (Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() + 1)) / 1000 + licenseInfo.usersExpire - 1; yield editorData.addPresenceUniqueUser(userId, expireAt, {anonym: anonym}); + let period = utils.getLicensePeriod(licenseInfo.startDate, now); + yield editorData.addPresenceUniqueUsersOfMonth(userId, period, {anonym: anonym, firstOpenDate: now.toISOString()}); } function* getEditorsCount(docId, opt_hvals) { var elem, editorsCount = 0; @@ -3313,7 +3315,7 @@ exports.licenseInfo = function(req, res) { return co(function*() { let isError = false; let output = { - connectionsStat: {}, licenseInfo: {}, serverInfo: { + connectionsStat: {}, uniqueUsersOfMonth: {}, licenseInfo: {}, serverInfo: { buildVersion: commonDefines.buildVersion, buildNumber: commonDefines.buildNumber, }, quota: { uniqueUserCount: 0, @@ -3323,10 +3325,12 @@ exports.licenseInfo = function(req, res) { Object.assign(output.licenseInfo, licenseInfo); try { logger.debug('licenseInfo start'); + logger.debug(`licenseInfo req.query:%j`, req.query); + let monthOffset = parseInt(req.query['monthoffset']) || 0; var precisionSum = {}; for (let i = 0; i < PRECISION.length; ++i) { precisionSum[PRECISION[i].name] = { - edit: {min: Number.MAX_VALUE, sum: 0, count: 0, max: 0, time: null, period: PRECISION[i].val}, + edit: {min: Number.MAX_VALUE, sum: 0, count: 0, max: 0}, view: {min: Number.MAX_VALUE, sum: 0, count: 0, max: 0} }; output.connectionsStat[PRECISION[i].name] = { @@ -3346,7 +3350,6 @@ exports.licenseInfo = function(req, res) { precision.edit.max = Math.max(precision.edit.max, elem.edit); precision.edit.sum += elem.edit; precision.edit.count++; - precision.edit.time = elem.time; precision.view.min = Math.min(precision.view.min, elem.view); precision.view.max = Math.max(precision.view.max, elem.view); precision.view.sum += elem.view; @@ -3359,15 +3362,13 @@ exports.licenseInfo = function(req, res) { for (let i in precisionSum) { let precision = precisionSum[i]; let precisionOut = output.connectionsStat[i]; - //scale compensates for the lack of points at server start - let scale = (now - precision.edit.time) / precision.edit.period; if (precision.edit.count > 0) { - precisionOut.edit.avr = Math.round((precision.edit.sum / precision.edit.count) * scale); + precisionOut.edit.avr = Math.round(precision.edit.sum / precision.edit.count); precisionOut.edit.min = precision.edit.min; precisionOut.edit.max = precision.edit.max; } if (precision.view.count > 0) { - precisionOut.view.avr = Math.round((precision.view.sum / precision.view.count) * scale); + precisionOut.view.avr = Math.round(precision.view.sum / precision.view.count); precisionOut.view.min = precision.view.min; precisionOut.view.max = precision.view.max; } @@ -3380,6 +3381,10 @@ exports.licenseInfo = function(req, res) { output.quota.anonymousUserCount++; } }); + let nowClone = new Date(now);//clone + nowClone.addMonths(monthOffset); + let period = utils.getLicensePeriod(licenseInfo.startDate, nowClone); + output.uniqueUsersOfMonth = yield editorData.getPresenceUniqueUsersOfMonth(period); logger.debug('licenseInfo end'); } catch (err) { isError = true; diff --git a/DocService/sources/editorDataMemory.js b/DocService/sources/editorDataMemory.js index 611f28bb..2d306322 100644 --- a/DocService/sources/editorDataMemory.js +++ b/DocService/sources/editorDataMemory.js @@ -31,13 +31,18 @@ */ 'use strict'; +const config = require('config'); +const ms = require('ms'); const utils = require('./../../Common/sources/utils'); const commonDefines = require('./../../Common/sources/commondefines'); +const cfgExpMonthUniqueUsers = ms(config.get('services.CoAuthoring.expire.monthUniqueUsers')); + function EditorData() { this.data = {}; this.forceSaveTimer = {}; this.uniqueUser = {}; + this.uniqueUsersOfMonth = {}; this.shutdown = {}; this.stat = []; } @@ -242,6 +247,26 @@ EditorData.prototype.getPresenceUniqueUser = function(nowUTC) { } return Promise.resolve(res); }; +EditorData.prototype.addPresenceUniqueUsersOfMonth = function(userId, period, userInfo) { + if(!this.uniqueUsersOfMonth[period]) { + let expireAt = Date.now() + cfgExpMonthUniqueUsers; + this.uniqueUsersOfMonth[period] = {expireAt: expireAt, data: {}}; + } + this.uniqueUsersOfMonth[period].data[userId] = userInfo; + return Promise.resolve(); +}; +EditorData.prototype.getPresenceUniqueUsersOfMonth = function(period) { + let nowUTC = Date.now(); + for (let periodId in this.uniqueUsersOfMonth) { + if (this.uniqueUsersOfMonth.hasOwnProperty(periodId)) { + if (this.uniqueUsersOfMonth[periodId].expireAt <= nowUTC) { + delete this.uniqueUsersOfMonth[periodId]; + } + } + } + let res = this.uniqueUsersOfMonth[period]; + return Promise.resolve(res && res.data || {}); +}; EditorData.prototype.setEditorConnections = function(countEdit, countView, now, precision) { this.stat.push({time: now, edit: countEdit, view: countView}); diff --git a/DocService/sources/server.js b/DocService/sources/server.js index a5f77a68..2453e328 100644 --- a/DocService/sources/server.js +++ b/DocService/sources/server.js @@ -204,7 +204,7 @@ docsCoServer.install(server, () => { app.post('/docbuilder', utils.checkClientIp, rawFileParser, (req, res) => { converterService.builder(req, res); }); - app.get('/info/info.json', utils.checkClientIp, docsCoServer.licenseInfo); + app.get('/info/info.json', utils.checkClientIp, utils.lowercaseQueryString, docsCoServer.licenseInfo); app.put('/internal/cluster/inactive', utils.checkClientIp, docsCoServer.shutdown); app.delete('/internal/cluster/inactive', utils.checkClientIp, docsCoServer.shutdown);