# 设备与在线时长接口 ## 概述 - 基础地址:`http://localhost:8000` - 版本前缀:`/api/v1` - 说明:同一账号仅允许一个设备同时在线(依据 `device_id`);后端会记录登录登出并可统计在线时长。 ## 登录(含单设备限制) - 方法与路径:`POST /api/v1/auth/login` - 请求体: ```json { "username": "alice", "password": "secret123", "device_id": "devA" } ``` - 成功响应: ```json { "access_token": "", "token_type": "bearer", "username": "alice" } ``` - 失败响应: - 401:`用户名或密码错误` - 403:`该账号已在其他设备在线` - 说明: - 必须传入稳定的 `device_id`(前端生成并持久化),用于限制并发登录与统计时长 - 登录成功会记录会话的 `login_at`,并将设备会话标记为活跃 ## 登出(记录在线时长) - 方法与路径:`POST /api/v1/auth/logout` - 请求体: ```json { "username": "alice", "device_id": "devA" } ``` - 成功响应: ```json { "detail": "已退出登录" } ``` - 行为: - 将对应设备的会话置为非活跃,并记录 `logout_at` - 若存在 `login_at`,会计算本次会话的在线时长 `duration_seconds`(单位:秒) ## 在线时长统计 - 方法与路径:`GET /api/v1/auth/online-time/{username}` - 成功响应: ```json { "username": "alice", "total_seconds": 1234, "active_seconds": 56 } ``` - 字段说明: - `total_seconds`:历史所有已登出会话的累计在线时长(秒) - `active_seconds`:当前活跃会话的实时在线时长(秒),若无活跃会话则为 0;实时时长基于最近心跳时间 `last_seen_at` 与 `login_at` 的差值 - 统计逻辑: - 登录时写入 `login_at`(UTC) - 登出时写入 `logout_at` 并计算 `duration_seconds` - 查询时将已登出会话的 `duration_seconds` 累加为 `total_seconds`,并以当前时间与活跃会话的 `login_at` 计算 `active_seconds` ## 心跳接口(保持在线时长统计) - 方法与路径:`POST /api/v1/auth/heartbeat` - 请求体: ```json { "username": "alice", "device_id": "devA" } ``` - 成功响应: ```json { "detail": "心跳已更新" } ``` - 说明: - 前端应在用户在线期间定期调用心跳(例如每 30~60 秒),以更新会话的 `last_seen_at` - 若未调用登出而直接关闭应用,在线时长将以最近一次心跳为准,不会无限累计 ## 错误信息(统一中文) - 401:`用户名或密码错误` - 403:`该账号已在其他设备在线` - 404:`用户不存在` ## 前端调用示例(Axios) ```ts 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)}`); } ```