v1.12.30 긴 접속 시간 표시와 중단 후 새 방문 처리 개선
This commit is contained in:
@@ -8,6 +8,27 @@ export const formatAnalyticsCount = (value) => {
|
||||
return new Intl.NumberFormat('ko-KR').format(Number.isFinite(number) ? number : 0)
|
||||
}
|
||||
|
||||
/**
|
||||
* 현재 접속 시간을 최대 두 단위로 짧게 표시한다.
|
||||
* @param {number} seconds - 접속 시간(초)
|
||||
* @returns {string} 일·시간·분·초 표시
|
||||
*/
|
||||
export const formatRealtimeDuration = (seconds) => {
|
||||
const input = Number(seconds)
|
||||
const value = Number.isFinite(input) ? Math.max(0, Math.floor(input)) : 0
|
||||
if (value >= 86400) {
|
||||
const hours = Math.floor(value % 86400 / 3600)
|
||||
return `${formatAnalyticsCount(Math.floor(value / 86400))}일${hours ? ` ${hours}시간` : ''}`
|
||||
}
|
||||
if (value >= 3600) {
|
||||
const minutes = Math.floor(value % 3600 / 60)
|
||||
return `${formatAnalyticsCount(Math.floor(value / 3600))}시간${minutes ? ` ${minutes}분` : ''}`
|
||||
}
|
||||
const minutes = Math.floor(value / 60)
|
||||
const remainder = value % 60
|
||||
return minutes ? `${minutes}분${remainder ? ` ${remainder}초` : ''}` : `${remainder}초`
|
||||
}
|
||||
|
||||
/**
|
||||
* 접속 페이지의 표시 제목을 반환한다.
|
||||
* @param {Object} page - 활성 페이지
|
||||
|
||||
30
lib/analytics-duration.js
Normal file
30
lib/analytics-duration.js
Normal file
@@ -0,0 +1,30 @@
|
||||
import { ANALYTICS_ACTIVE_SESSION_TTL_SECONDS, ANALYTICS_MAX_REALTIME_DURATION_SECONDS, ANALYTICS_NEW_VISIT_IDLE_SECONDS } from './analytics-shared.js'
|
||||
|
||||
/**
|
||||
* heartbeat 실행이 끊긴 긴 구간을 제외하는 페이지별 시간 측정기를 만든다.
|
||||
* @param {number} [startedAt] - 측정 시작 시각(ms)
|
||||
* @returns {(now?: number) => { durationSeconds: number, restarted: boolean }} 접속 시간과 새 방문 여부를 반환하는 측정 함수
|
||||
*/
|
||||
export const createAnalyticsDurationTracker = (startedAt = Date.now()) => {
|
||||
let lastSampleAt = startedAt
|
||||
let durationMs = 0
|
||||
|
||||
/**
|
||||
* 직전 측정 이후 실행이 이어진 구간만 누적한다.
|
||||
* @param {number} [now] - 현재 시각(ms)
|
||||
* @returns {{ durationSeconds: number, restarted: boolean }} 중단 구간을 제외한 시간과 새 방문 여부
|
||||
*/
|
||||
const sample = (now = Date.now()) => {
|
||||
if (!Number.isFinite(now)) return { durationSeconds: Math.floor(durationMs / 1000), restarted: false }
|
||||
const gapMs = now - lastSampleAt
|
||||
lastSampleAt = now
|
||||
const restarted = gapMs >= ANALYTICS_NEW_VISIT_IDLE_SECONDS * 1000
|
||||
if (restarted) durationMs = 0
|
||||
else if (gapMs > 0 && gapMs < ANALYTICS_ACTIVE_SESSION_TTL_SECONDS * 1000) {
|
||||
durationMs = Math.min(durationMs + gapMs, ANALYTICS_MAX_REALTIME_DURATION_SECONDS * 1000)
|
||||
}
|
||||
return { durationSeconds: Math.floor(durationMs / 1000), restarted }
|
||||
}
|
||||
|
||||
return sample
|
||||
}
|
||||
@@ -72,15 +72,21 @@ export const normalizePostSlugForAnalytics = (slug) => (slug || '').trim()
|
||||
*/
|
||||
export const normalizePageSlugForAnalytics = (slug) => (slug || '').trim()
|
||||
|
||||
/** @type {number} heartbeat 체류시간 상한(초) */
|
||||
/** @type {number} 평균 체류 통계에 반영할 콘텐츠별 시간 상한(초) */
|
||||
export const ANALYTICS_MAX_DURATION_SECONDS = 1800
|
||||
|
||||
/** @type {number} 실시간 접속 시간의 PostgreSQL 정수 저장 상한(초) */
|
||||
export const ANALYTICS_MAX_REALTIME_DURATION_SECONDS = 2147483647
|
||||
|
||||
/** @type {number} engaged_views 집계 최소 체류(초) */
|
||||
export const ANALYTICS_ENGAGED_MIN_SECONDS = 10
|
||||
|
||||
/** @type {number} 현재 접속자 판정 TTL(초) */
|
||||
export const ANALYTICS_ACTIVE_SESSION_TTL_SECONDS = 90
|
||||
|
||||
/** @type {number} 실행 중단 후 새 콘텐츠 방문으로 시작하는 기준(초) */
|
||||
export const ANALYTICS_NEW_VISIT_IDLE_SECONDS = 1800
|
||||
|
||||
/** @type {number} 관리자 차트 최대 조회 기간(일) */
|
||||
export const ANALYTICS_CHART_MAX_DAYS = 365
|
||||
|
||||
@@ -122,6 +128,17 @@ export const clampAnalyticsDurationSeconds = (seconds) => {
|
||||
return Math.min(Math.floor(value), ANALYTICS_MAX_DURATION_SECONDS)
|
||||
}
|
||||
|
||||
/**
|
||||
* 실시간 접속 시간을 데이터베이스 정수 범위로 보정한다.
|
||||
* @param {number} seconds - 접속 시간
|
||||
* @returns {number} 보정된 초
|
||||
*/
|
||||
export const clampAnalyticsRealtimeDurationSeconds = (seconds) => {
|
||||
const value = Number(seconds)
|
||||
if (!Number.isFinite(value) || value < 0) return 0
|
||||
return Math.min(Math.floor(value), ANALYTICS_MAX_REALTIME_DURATION_SECONDS)
|
||||
}
|
||||
|
||||
/**
|
||||
* 스크롤 비율을 0~1로 보정한다.
|
||||
* @param {number} ratio - 스크롤 비율
|
||||
|
||||
@@ -6,11 +6,13 @@ export {
|
||||
ANALYTICS_COLLECTED_SINCE_DEFAULT,
|
||||
ANALYTICS_ENGAGED_MIN_SECONDS,
|
||||
ANALYTICS_MAX_DURATION_SECONDS,
|
||||
ANALYTICS_MAX_REALTIME_DURATION_SECONDS,
|
||||
ANALYTICS_RETENTION_PURGE_INTERVAL_MS,
|
||||
ANALYTICS_SCROLL_THRESHOLDS,
|
||||
ANALYTICS_VISITOR_HASH_RETENTION_DAYS,
|
||||
normalizeAnalyticsCollectedSinceDay,
|
||||
clampAnalyticsDurationSeconds,
|
||||
clampAnalyticsRealtimeDurationSeconds,
|
||||
clampAnalyticsScrollRatio,
|
||||
getAnalyticsDayBefore,
|
||||
getAnalyticsDayKey,
|
||||
|
||||
Reference in New Issue
Block a user