/** * AIoT 设备管理 API * * 所有请求通过 wvpRequestClient 发送到 WVP 视频平台后端, * 由 Vite 代理按路径分发并 rewrite: * /aiot/device/proxy/* → WVP /api/proxy/* * /aiot/device/user/* → WVP /api/user/* * /aiot/device/server/* → WVP /api/server/media_server/* * /aiot/device/* → WVP /api/ai/* */ import { useAppConfig } from '@vben/hooks'; import { getWvpToken, wvpRequestClient } from '#/api/aiot/request'; const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD); // ==================== 类型定义 ==================== export namespace AiotDeviceApi { /** 分页响应结构 */ export interface PageResult { list: T[]; total: number; } /** 摄像头(拉流代理) */ export interface Camera { id?: number; type?: string; // 'default' | 'ffmpeg' app?: string; stream?: string; cameraCode?: string; // 摄像头唯一编码 cameraName?: string; // 摄像头名称(用户自定义) edgeDeviceId?: string; // 绑定的边缘设备ID srcUrl?: string; timeout?: number; rtspType?: string; // '0'=TCP, '1'=UDP, '2'=Multicast enable?: boolean; enableAudio?: boolean; enableMp4?: boolean; enableDisableNoneReader?: boolean; relatesMediaServerId?: string; ffmpegCmdKey?: string; pulling?: boolean; mediaServerId?: string; streamKey?: string; createTime?: string; } /** ROI 区域 */ export interface Roi { id?: number; roiId?: string; cameraId?: string; deviceId?: string; name?: string; roiType?: string; // 'rectangle' | 'polygon' coordinates?: string; color?: string; priority?: number; enabled?: number; // 0 | 1 description?: string; algorithms?: RoiAlgoBinding[]; } /** ROI 算法绑定(详情嵌套结构) */ export interface RoiAlgoBinding { bind: AlgoBind; algorithm?: Algorithm; } /** 算法绑定记录 */ export interface AlgoBind { bindId?: string; roiId?: string; algoCode?: string; enabled?: number; // 0 | 1 params?: string; } /** 算法定义 */ export interface Algorithm { id?: number; algoCode?: string; algoName?: string; description?: string; isActive?: boolean; paramSchema?: string; } /** 媒体服务器 */ export interface MediaServer { id: string; ip: string; } } // ==================== 摄像头管理 ==================== /** 摄像头列表(分页) */ export function getCameraList(params: { page: number; count: number; query?: string; pulling?: boolean; }) { return wvpRequestClient.get>( '/aiot/device/proxy/list', { params }, ); } /** 新增摄像头 */ export function addCamera(data: Partial) { return wvpRequestClient.post('/aiot/device/proxy/add', data); } /** 编辑摄像头 */ export function updateCamera(data: Partial) { return wvpRequestClient.post('/aiot/device/proxy/update', data); } /** 保存摄像头(新增/编辑,有 id 为编辑) */ export function saveCamera(data: Partial) { return data.id ? updateCamera(data) : addCamera(data); } /** 删除摄像头 */ export function deleteCamera(id: number) { return wvpRequestClient.delete('/aiot/device/proxy/delete', { params: { id }, }); } /** 开始拉流 */ export function startCamera(id: number) { return wvpRequestClient.get('/aiot/device/proxy/start', { params: { id } }); } /** 停止拉流 */ export function stopCamera(id: number) { return wvpRequestClient.get('/aiot/device/proxy/stop', { params: { id } }); } /** 在线媒体服务器列表 */ export function getMediaServerList() { return wvpRequestClient.get( '/aiot/device/server/online/list', ); } /** 摄像头选项列表(用于下拉搜索选择) */ export function getCameraOptions() { return wvpRequestClient.get< { cameraCode: string; cameraName: string }[] >('/aiot/device/camera/options'); } // ==================== ROI 区域管理 ==================== /** ROI 列表(分页) */ export function getRoiList(params: { page: number; count: number; cameraId?: string; deviceId?: string; query?: string; }) { return wvpRequestClient.get>( '/aiot/device/roi/list', { params }, ); } /** ROI 详情(含算法绑定) */ export function getRoiDetail(id: number) { return wvpRequestClient.get(`/aiot/device/roi/${id}`); } /** 某摄像头的所有 ROI */ export function getRoiByCameraId(cameraId: string) { return wvpRequestClient.get( '/aiot/device/roi/channel', { params: { cameraId } }, ); } /** 保存 ROI(新增/编辑) */ export function saveRoi(data: Partial) { return wvpRequestClient.post('/aiot/device/roi/save', data); } /** 删除 ROI */ export function deleteRoi(roiId: string) { return wvpRequestClient.delete(`/aiot/device/roi/delete/${roiId}`); } /** * 获取摄像头截图 URL * * 非 force 模式:直接返回 /snap/image 代理 URL(无时间戳,浏览器自动缓存) * force 模式:先触发边缘端截图,再返回带时间戳的代理 URL 破缓存 */ export async function getSnapUrl(cameraCode: string, force = false): Promise { const token = await getWvpToken(); if (force) { // 强制刷新:先触发边缘端截图(等待完成) try { await wvpRequestClient.get('/aiot/device/roi/snap', { params: { cameraCode, force: true }, }); } catch { // 截图请求可能超时,但 COS 上可能已有图片,继续返回代理 URL } // 加时间戳破浏览器缓存 return ( `${apiURL}/aiot/device/roi/snap/image` + `?cameraCode=${encodeURIComponent(cameraCode)}` + `&access-token=${encodeURIComponent(token)}` + `&t=${Date.now()}` ); } // 非 force:使用代理端点,不加时间戳,浏览器自动缓存 return ( `${apiURL}/aiot/device/roi/snap/image` + `?cameraCode=${encodeURIComponent(cameraCode)}` + `&access-token=${encodeURIComponent(token)}` ); } // ==================== 算法管理 ==================== /** 算法列表 */ export function getAlgorithmList() { return wvpRequestClient.get( '/aiot/device/algorithm/list', ); } // ==================== 算法绑定 ==================== /** 绑定算法到 ROI */ export function bindAlgo(data: { roiId: string; algoCode: string }) { return wvpRequestClient.post('/aiot/device/roi/bindAlgo', data); } /** 解绑算法 */ export function unbindAlgo(bindId: string) { return wvpRequestClient.delete('/aiot/device/roi/unbindAlgo', { params: { bindId }, }); } /** 更新算法绑定参数 */ export function updateAlgoParams(data: { bindId: string; params?: string; enabled?: number; }) { return wvpRequestClient.post('/aiot/device/roi/updateAlgoParams', data); } // ==================== 配置推送 ==================== /** 推送配置到边缘端 */ export function pushConfig(cameraId: string) { return wvpRequestClient.post('/aiot/device/config/push', null, { params: { cameraId }, }); } /** 一次性推送全部配置到本地Edge */ export function pushAllConfig() { return wvpRequestClient.post>( '/aiot/device/config/push-all', ); } /** 导出摄像头配置 JSON */ export function exportConfig(cameraId: string) { return wvpRequestClient.get>( '/aiot/device/config/export', { params: { cameraId } }, ); } // ==================== 告警图片代理 ==================== /** * 构造告警图片代理 URL(通过 WVP 下载 COS 图片后返回字节流) * @param imagePath COS 对象路径或完整 URL */ export async function getAlertImageUrl(imagePath: string): Promise { if (!imagePath) return ''; const token = await getWvpToken(); return ( `${apiURL}/aiot/device/alert/image` + `?imagePath=${encodeURIComponent(imagePath)}` + `&access-token=${encodeURIComponent(token)}` ); }