Files
DP/tempdocs/设备与在线时长接口文档.md

3.1 KiB
Raw Blame History

设备与在线时长接口

概述

  • 基础地址:http://localhost:8000
  • 版本前缀:/api/v1
  • 说明:同一账号仅允许一个设备同时在线(依据 device_id);后端会记录登录登出并可统计在线时长。

登录(含单设备限制)

  • 方法与路径:POST /api/v1/auth/login
  • 请求体:
{
  "username": "alice",
  "password": "secret123",
  "device_id": "devA"
}
  • 成功响应:
{
  "access_token": "<JWT>",
  "token_type": "bearer",
  "username": "alice"
}
  • 失败响应:
    • 401用户名或密码错误
    • 403该账号已在其他设备在线
  • 说明:
    • 必须传入稳定的 device_id(前端生成并持久化),用于限制并发登录与统计时长
    • 登录成功会记录会话的 login_at,并将设备会话标记为活跃

登出(记录在线时长)

  • 方法与路径:POST /api/v1/auth/logout
  • 请求体:
{
  "username": "alice",
  "device_id": "devA"
}
  • 成功响应:
{ "detail": "已退出登录" }
  • 行为:
    • 将对应设备的会话置为非活跃,并记录 logout_at
    • 若存在 login_at,会计算本次会话的在线时长 duration_seconds(单位:秒)

在线时长统计

  • 方法与路径:GET /api/v1/auth/online-time/{username}
  • 成功响应:
{
  "username": "alice",
  "total_seconds": 1234,
  "active_seconds": 56
}
  • 字段说明:
    • total_seconds:历史所有已登出会话的累计在线时长(秒)
    • active_seconds:当前活跃会话的实时在线时长(秒),若无活跃会话则为 0实时时长基于最近心跳时间 last_seen_atlogin_at 的差值
  • 统计逻辑:
    • 登录时写入 login_atUTC
    • 登出时写入 logout_at 并计算 duration_seconds
    • 查询时将已登出会话的 duration_seconds 累加为 total_seconds,并以当前时间与活跃会话的 login_at 计算 active_seconds

心跳接口(保持在线时长统计)

  • 方法与路径:POST /api/v1/auth/heartbeat
  • 请求体:
{
  "username": "alice",
  "device_id": "devA"
}
  • 成功响应:
{ "detail": "心跳已更新" }
  • 说明:
    • 前端应在用户在线期间定期调用心跳(例如每 3060 秒),以更新会话的 last_seen_at
    • 若未调用登出而直接关闭应用,在线时长将以最近一次心跳为准,不会无限累计

错误信息(统一中文)

  • 401用户名或密码错误
  • 403该账号已在其他设备在线
  • 404用户不存在

前端调用示例Axios

import axios from 'axios';
const API = 'http://localhost:8000/api/v1/auth';

export async function login(username: string, password: string, deviceId: string) {
  return axios.post(`${API}/login`, { username, password, device_id: deviceId });
}

export async function logout(username: string, deviceId: string) {
  return axios.post(`${API}/logout`, { username, device_id: deviceId });
}

export async function getOnlineTime(username: string) {
  return axios.get(`${API}/online-time/${encodeURIComponent(username)}`);
}