feat: checkpoint user state tracking service with PostgreSQL
- RESTful API: POST /heartbeat, POST /checkpoints, GET /status, GET /summaries - State-change-only checkpoint model with extensible StateType enum - PostgreSQL backend with sqlx, auto-migration on startup - pg_cron scheduled aggregation (state_summaries) and offline detection - Heartbeat-based liveness with 60s timeout auto-offline - LEAD() window function for state duration calculation - JSONB content field for extensible checkpoint metadata BREAKING CHANGE: Complete rewrite from Hello World to full API service.
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
-- 001_init.sql
|
||||
-- Checkpoint 服务初始建表
|
||||
|
||||
CREATE TABLE IF NOT EXISTS checkpoints (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
user_id VARCHAR(128) NOT NULL,
|
||||
state VARCHAR(64) NOT NULL,
|
||||
timestamp BIGINT NOT NULL,
|
||||
content JSONB,
|
||||
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- 核心查询索引:按用户 + 时间范围
|
||||
CREATE INDEX IF NOT EXISTS idx_checkpoints_user_ts
|
||||
ON checkpoints (user_id, timestamp);
|
||||
|
||||
-- 快速获取用户最新检查点
|
||||
CREATE INDEX IF NOT EXISTS idx_checkpoints_user_latest
|
||||
ON checkpoints (user_id, timestamp DESC);
|
||||
@@ -0,0 +1,84 @@
|
||||
-- 002_daily_summary.sql
|
||||
-- 状态时长定时快照表 + 聚合函数 + pg_cron 调度说明
|
||||
|
||||
-- ============================================================
|
||||
-- 1. 快照存储表
|
||||
-- ============================================================
|
||||
CREATE TABLE IF NOT EXISTS state_summaries (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
user_id VARCHAR(128) NOT NULL,
|
||||
state VARCHAR(64) NOT NULL,
|
||||
duration_secs BIGINT NOT NULL DEFAULT 0,
|
||||
period_start BIGINT NOT NULL,
|
||||
period_end BIGINT NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
|
||||
UNIQUE (user_id, state, period_start, period_end)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_summaries_user_period
|
||||
ON state_summaries (user_id, period_start DESC);
|
||||
|
||||
-- ============================================================
|
||||
-- 2. 聚合函数:滑动窗口计算指定时间范围内各状态时长
|
||||
-- - 调度器只需传入 period_start / period_end
|
||||
-- - 内部用 LEAD() 窗口函数求相邻检查点差值
|
||||
-- - ON CONFLICT 保证幂等(重复执行不产生重复行)
|
||||
-- ============================================================
|
||||
CREATE OR REPLACE FUNCTION aggregate_checkpoint_durations(
|
||||
p_start BIGINT,
|
||||
p_end BIGINT
|
||||
) RETURNS BIGINT AS $$
|
||||
DECLARE
|
||||
affected BIGINT;
|
||||
BEGIN
|
||||
INSERT INTO state_summaries (user_id, state, duration_secs, period_start, period_end)
|
||||
SELECT user_id, state, SUM(duration) AS duration_secs, p_start, p_end
|
||||
FROM (
|
||||
SELECT user_id, state,
|
||||
LEAD(timestamp) OVER (PARTITION BY user_id ORDER BY timestamp) - timestamp AS duration
|
||||
FROM checkpoints
|
||||
WHERE timestamp >= p_start AND timestamp <= p_end
|
||||
) sub
|
||||
WHERE duration > 0
|
||||
GROUP BY user_id, state
|
||||
ON CONFLICT (user_id, state, period_start, period_end) DO UPDATE
|
||||
SET duration_secs = EXCLUDED.duration_secs;
|
||||
|
||||
GET DIAGNOSTICS affected = ROW_COUNT;
|
||||
RETURN affected;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- ============================================================
|
||||
-- 3. pg_cron 调度(需要超级用户手动执行一次)
|
||||
-- ============================================================
|
||||
--
|
||||
-- -- 安装扩展(仅需一次)
|
||||
-- CREATE EXTENSION IF NOT EXISTS pg_cron;
|
||||
--
|
||||
-- -- 每 5 分钟执行一次聚合
|
||||
-- SELECT cron.schedule(
|
||||
-- 'aggregate-5min',
|
||||
-- '*/5 * * * *',
|
||||
-- $$ SELECT aggregate_checkpoint_durations(
|
||||
-- FLOOR(EXTRACT(EPOCH FROM now() - INTERVAL '5 minutes'))::BIGINT,
|
||||
-- FLOOR(EXTRACT(EPOCH FROM now()))::BIGINT
|
||||
-- ); $$
|
||||
-- );
|
||||
--
|
||||
-- -- 每小时整点执行一次(日报表用)
|
||||
-- SELECT cron.schedule(
|
||||
-- 'aggregate-hourly',
|
||||
-- '0 * * * *',
|
||||
-- $$ SELECT aggregate_checkpoint_durations(
|
||||
-- FLOOR(EXTRACT(EPOCH FROM now() - INTERVAL '1 hour'))::BIGINT,
|
||||
-- FLOOR(EXTRACT(EPOCH FROM now()))::BIGINT
|
||||
-- ); $$
|
||||
-- );
|
||||
--
|
||||
-- -- 查看 cron 任务状态
|
||||
-- SELECT * FROM cron.job;
|
||||
--
|
||||
-- -- 取消(如需)
|
||||
-- SELECT cron.unschedule('aggregate-5min');
|
||||
@@ -0,0 +1,65 @@
|
||||
-- 003_sessions.sql
|
||||
-- 用户会话表 + 心跳管理 + 离线检测 + pg_cron 调度
|
||||
|
||||
-- ============================================================
|
||||
-- 1. 用户会话表(跟踪当前状态和心跳)
|
||||
-- ============================================================
|
||||
CREATE TABLE IF NOT EXISTS user_sessions (
|
||||
user_id VARCHAR(128) PRIMARY KEY,
|
||||
current_state VARCHAR(64) NOT NULL DEFAULT 'Offline',
|
||||
last_heartbeat BIGINT NOT NULL,
|
||||
last_state_change BIGINT NOT NULL,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- ============================================================
|
||||
-- 2. 离线检测函数:心跳超时 60s 的用户自动补一条 Offline 检查点
|
||||
-- pg_cron 每 1 分钟执行一次
|
||||
-- ============================================================
|
||||
CREATE OR REPLACE FUNCTION detect_offline_users(
|
||||
timeout_secs BIGINT DEFAULT 60
|
||||
) RETURNS SETOF BIGINT AS $$
|
||||
DECLARE
|
||||
r RECORD;
|
||||
now_ts BIGINT;
|
||||
BEGIN
|
||||
now_ts := EXTRACT(EPOCH FROM now())::BIGINT;
|
||||
|
||||
FOR r IN
|
||||
SELECT user_id, last_heartbeat
|
||||
FROM user_sessions
|
||||
WHERE current_state != 'Offline'
|
||||
AND (now_ts - last_heartbeat) > timeout_secs
|
||||
LOOP
|
||||
-- 插入离线检查点(时间戳 = 最后心跳 + 超时时间)
|
||||
INSERT INTO checkpoints (user_id, state, timestamp)
|
||||
VALUES (r.user_id, 'Offline', r.last_heartbeat + timeout_secs);
|
||||
|
||||
-- 更新会话状态
|
||||
UPDATE user_sessions
|
||||
SET current_state = 'Offline',
|
||||
last_state_change = r.last_heartbeat + timeout_secs,
|
||||
updated_at = now()
|
||||
WHERE user_id = r.user_id;
|
||||
|
||||
RETURN NEXT 1;
|
||||
END LOOP;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- ============================================================
|
||||
-- 3. pg_cron 调度(需要超级用户手动执行一次)
|
||||
-- ============================================================
|
||||
--
|
||||
-- -- 安装扩展(仅需一次)
|
||||
-- CREATE EXTENSION IF NOT EXISTS pg_cron;
|
||||
--
|
||||
-- -- 每 1 分钟检测离线用户
|
||||
-- SELECT cron.schedule(
|
||||
-- 'detect-offline',
|
||||
-- '* * * * *',
|
||||
-- $$ SELECT detect_offline_users(60); $$
|
||||
-- );
|
||||
--
|
||||
-- -- 查看状态
|
||||
-- SELECT * FROM cron.job;
|
||||
Reference in New Issue
Block a user