纳入范围: - backend/ Spring Boot 多模块后端(iot-common/device/rule/data/alarm/auth) - frontend/admin/ Vue3 + Element Plus 管理后台 - Android/ 涂鸦原生 APP (com.xhai.iot) - backend/sql/ Flyway 风格迁移脚本(V2~V21) - docs/ 对接文档与设计文档 排除范围: - 构建产物: node_modules/ target/ build/ dist/ .gradle/ - 签名密钥: *.jks *.keystore (严禁入库) - 本机配置: Android/local.properties - 运行日志: logs/ *.log - AI 协作本地数据 .codebuddy/ 与第三方 demo tuya-demo/ .gitattributes 统一以 LF 存储, Windows 批处理保留 CRLF
824 lines
90 KiB
HTML
824 lines
90 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||
<title>MumIot API · 涂鸦云集成</title>
|
||
<style>
|
||
|
||
:root{
|
||
--bg:#eef2f7; --contentbg:#ffffff; --sidebar:#0f172a; --sidebar2:#15233f;
|
||
--text:#1e293b; --muted:#64748b; --line:#e2e8f0; --line2:#cbd5e1;
|
||
--accent:#2563eb; --accent2:#1d4ed8;
|
||
--get:#16a34a; --post:#2563eb; --put:#d97706; --del:#dc2626;
|
||
--req:#dc2626; --opt:#0891b2; --none:#64748b;
|
||
--codebg:#0f172a; --codefg:#cbd5e1;
|
||
}
|
||
*{box-sizing:border-box}
|
||
html,body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif;color:var(--text);}
|
||
body{background:var(--bg);}
|
||
a{color:var(--accent);text-decoration:none}
|
||
a:hover{text-decoration:underline}
|
||
|
||
.layout{display:flex;min-height:100vh}
|
||
/* ===== 左侧导航 ===== */
|
||
aside#sidebar{width:264px;flex:0 0 264px;background:var(--sidebar);color:#cbd5e1;position:sticky;top:0;height:100vh;overflow-y:auto;padding:0 0 30px}
|
||
#sidebar .brand{padding:20px 22px 16px;border-bottom:1px solid #1e293b;margin-bottom:8px}
|
||
#sidebar .brand h2{margin:0;font-size:16px;color:#fff;letter-spacing:.02em}
|
||
#sidebar .brand p{margin:5px 0 0;font-size:12px;color:#94a3b8}
|
||
#sidebar .grp{font-size:11px;text-transform:uppercase;letter-spacing:.07em;color:#5b6b85;padding:16px 22px 5px}
|
||
#sidebar a.nav{display:block;padding:8px 22px;font-size:13.5px;color:#cbd5e1;text-decoration:none;border-left:3px solid transparent}
|
||
#sidebar a.nav:hover{background:var(--sidebar2);color:#fff}
|
||
#sidebar a.nav.active{background:var(--sidebar2);color:#fff;border-left-color:var(--accent)}
|
||
#sidebar .ft{padding:18px 22px 0;font-size:11px;color:#5b6b85;border-top:1px solid #1e293b;margin-top:18px}
|
||
|
||
/* ===== 主区域 ===== */
|
||
main{flex:1;min-width:0;display:flex;flex-direction:column}
|
||
#topbar{position:sticky;top:0;z-index:5;background:rgba(255,255,255,.92);backdrop-filter:blur(8px);border-bottom:1px solid var(--line);padding:13px 30px;display:flex;gap:14px;align-items:center;flex-wrap:wrap}
|
||
#topbar .ptitle{font-size:18px;font-weight:700;margin-right:auto;white-space:nowrap}
|
||
#topbar input.search{flex:1;min-width:170px;max-width:300px;padding:9px 12px;border:1px solid var(--line);border-radius:9px;font-size:13px;outline:none;background:#fff}
|
||
#topbar input.search:focus{border-color:var(--accent);box-shadow:0 0 0 3px rgba(37,99,235,.12)}
|
||
.basebox{display:flex;align-items:center;gap:8px;font-size:12.5px;color:var(--muted);white-space:nowrap}
|
||
.basebox input{padding:8px 10px;border:1px solid var(--line);border-radius:9px;font-size:12.5px;width:280px;max-width:46vw;background:#fff}
|
||
#content{padding:26px 30px 90px;max-width:1100px;width:100%}
|
||
|
||
/* ===== 概览页 ===== */
|
||
.ov-hero{background:linear-gradient(120deg,#0ea5e9,#6366f1);color:#fff;border-radius:14px;padding:26px 28px;margin-bottom:22px}
|
||
.ov-hero h1{margin:0 0 8px;font-size:24px}
|
||
.ov-hero p{margin:0;opacity:.92;font-size:14px;line-height:1.7}
|
||
.ov-cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(230px,1fr));gap:14px;margin-bottom:26px}
|
||
.ov-card{background:var(--contentbg);border:1px solid var(--line);border-radius:12px;padding:16px 18px;transition:.15s}
|
||
.ov-card:hover{border-color:var(--accent);box-shadow:0 4px 16px rgba(37,99,235,.1)}
|
||
.ov-card h3{margin:0 0 6px;font-size:15px}
|
||
.ov-card p{margin:0 0 10px;font-size:12.5px;color:var(--muted);line-height:1.6}
|
||
.ov-card .cnt{font-size:12px;color:var(--accent);font-weight:600}
|
||
.ov-note{background:#fff7ed;border:1px solid #fed7aa;border-radius:12px;padding:16px 18px;font-size:13px;line-height:1.8;margin-bottom:22px}
|
||
.ov-note b{color:#c2410c}
|
||
.ov-note code{background:#fff;border:1px solid var(--line);border-radius:5px;padding:1px 6px;font-size:12px}
|
||
|
||
/* ===== 分组 / 章节 ===== */
|
||
.sec{margin:0 0 30px}
|
||
.sec>.h2{font-size:19px;border-left:4px solid var(--accent);padding-left:11px;margin:0 0 4px}
|
||
.sec>.sub{color:var(--muted);font-size:13px;margin:0 0 14px}
|
||
.toc{background:var(--contentbg);border:1px solid var(--line);border-radius:10px;padding:10px 14px;margin:0 0 20px;font-size:13px;display:flex;flex-wrap:wrap;gap:8px 14px}
|
||
.toc a{padding:2px 10px;border:1px solid var(--line);border-radius:20px;color:var(--muted)}
|
||
.toc a:hover{border-color:var(--accent);color:var(--accent);text-decoration:none}
|
||
|
||
/* ===== 接口卡片 ===== */
|
||
.ep{background:var(--contentbg);border:1px solid var(--line);border-radius:12px;padding:15px 17px;margin:0 0 13px;scroll-margin-top:80px}
|
||
.ep.hidden{display:none}
|
||
.ep .row{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
|
||
.badge{font-weight:700;font-size:11.5px;padding:3px 9px;border-radius:6px;color:#fff}
|
||
.m-GET{background:var(--get)} .m-POST{background:var(--post)} .m-PUT{background:var(--put)} .m-DELETE{background:var(--del)}
|
||
.path{font-family:"SFMono-Regular",Consolas,monospace;font-size:13.5px;color:#0f172a;word-break:break-all;font-weight:600}
|
||
.tag{font-size:11px;padding:2px 9px;border-radius:20px;border:1px solid var(--line)}
|
||
.t-req{color:var(--req);border-color:var(--req)} .t-opt{color:var(--opt);border-color:var(--opt)} .t-none{color:var(--none);border-color:var(--none)}
|
||
.ep .desc{margin:9px 0 6px;font-size:14px}
|
||
.ep .params{font-size:12.5px;color:var(--muted)}
|
||
.ep .params b{color:var(--text)}
|
||
.ep .mlink{font-size:12.5px;margin-top:8px}
|
||
.ep .mlink a{color:var(--accent)}
|
||
.ex{margin-top:8px}
|
||
.ex summary{cursor:pointer;font-size:12.5px;color:var(--accent);user-select:none;outline:none}
|
||
.ex pre{background:var(--codebg);border:1px solid #1e293b;border-radius:9px;padding:12px 14px;overflow:auto;
|
||
font-family:"SFMono-Regular",Consolas,monospace;font-size:12px;color:var(--codefg);margin:8px 0 0;white-space:pre;line-height:1.55}
|
||
.ex pre .c{color:#64748b}
|
||
.empty{color:var(--muted);text-align:center;padding:40px;display:none}
|
||
|
||
/* ===== 模型 ===== */
|
||
.model{background:var(--contentbg);border:1px solid var(--line);border-radius:12px;padding:16px 18px;margin:0 0 16px;scroll-margin-top:80px}
|
||
.model h3{margin:0 0 4px;font-size:16px;color:#0f172a}
|
||
.model .mtag{font-size:11px;color:var(--muted);margin:0 0 10px}
|
||
.model .mtag .q{color:var(--accent);background:#eff6ff;border:1px solid #bfdbfe;border-radius:5px;padding:1px 6px;margin-right:6px}
|
||
.model .exjson{margin-top:8px}
|
||
table.ft{width:100%;border-collapse:collapse;font-size:12.5px;margin:6px 0 10px}
|
||
table.ft th{background:#f1f5f9;text-align:left;color:var(--muted);font-weight:600;padding:7px 9px;border-bottom:2px solid var(--line)}
|
||
table.ft td{padding:7px 9px;border-bottom:1px solid #eef2f7;vertical-align:top}
|
||
table.ft td .ty{color:#0369a1;font-family:Consolas,monospace;font-size:12px}
|
||
table.ft td .rq{color:var(--req);font-weight:700}
|
||
table.ft td .op{color:var(--muted)}
|
||
.model .exjson summary{cursor:pointer;font-size:12.5px;color:var(--accent);outline:none}
|
||
.model .exjson pre{background:var(--codebg);border-radius:9px;padding:12px 14px;overflow:auto;font-family:Consolas,monospace;font-size:12px;color:var(--codefg);margin:8px 0 0;white-space:pre}
|
||
.baseblk{background:#f8fafc;border:1px solid var(--line);border-radius:8px;padding:10px 12px;font-size:12px;margin:6px 0 10px}
|
||
.baseblk b{color:#0f172a}
|
||
.resp-note{font-size:12.5px;color:var(--muted);margin-top:6px}
|
||
code.inl{background:#f1f5f9;border:1px solid var(--line);border-radius:5px;padding:1px 6px;font-size:12px;font-family:Consolas,monospace;color:#0f172a}
|
||
footer{color:var(--muted);font-size:12px;text-align:center;padding:24px;border-top:1px solid var(--line);margin-top:20px}
|
||
|
||
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="layout">
|
||
<aside id="sidebar"></aside>
|
||
<main>
|
||
<div id="topbar"></div>
|
||
<div id="content"></div>
|
||
</main>
|
||
</div>
|
||
<script>
|
||
|
||
/* ===== MumIot API 文档 · 共享脚本 ===== */
|
||
/* 👇 只需在这里改一次默认 Base URL(生产用反向代理域名) */
|
||
const DEFAULT_BASE="http://mum.tonzou.cn/api/v1";
|
||
/* 👇 产品名(用于标题/品牌/首页),只改这一处 */
|
||
const PRODUCT_NAME="MumIot";
|
||
const AUTH={req:"强制",opt:"可选",none:"无"};
|
||
function F(n,t,r,d){return [n,t,r,d];}
|
||
const BASE_FIELDS=[
|
||
F("id","Long","-","主键(雪花ID, @TableId ASSIGN_ID)"),
|
||
F("createTime","LocalDateTime","-","创建时间(自动填充)"),
|
||
F("updateTime","LocalDateTime","-","更新时间(自动填充)"),
|
||
F("deleted","Integer","-","逻辑删除(0未删/1已删, @TableLogic)"),
|
||
F("tenantId","Long","-","租户ID(自动填充)"),
|
||
F("createBy","Long","-","创建人ID(自动填充)"),
|
||
F("updateBy","Long","-","更新人ID(自动填充)")
|
||
];
|
||
|
||
/* ===== 数据模型(字段 + JSON 示例) ===== */
|
||
const MODELS={
|
||
LoginRequest:{title:"LoginRequest 登录请求体",fields:[F("username","String","是","用户名"),F("password","String","是","密码")],example:{username:"admin",password:"admin123"}},
|
||
RefreshRequest:{title:"RefreshRequest 刷新Token",fields:[F("refreshToken","String","是","刷新令牌")],example:{refreshToken:"eyJhbGciOiJIUzI1Ni...refresh"}},
|
||
RegisterRequest:{title:"RegisterRequest 普通用户注册",fields:[F("username","String","是","用户名(3-32,字母数字下划线)"),F("password","String","是","密码(6-20)"),F("nickname","String","是","昵称"),F("phone","String","否","手机号(1[3-9]\\d{9})")],example:{username:"alice",password:"abc123",nickname:"Alice",phone:"13800138000"}},
|
||
MemberRegisterRequest:{title:"MemberRegisterRequest 会员注册",fields:[F("username","String","是","用户名(3-50)"),F("password","String","是","密码(6-32)"),F("nickname","String","否","昵称(≤50)"),F("phone","String","否","手机号(≤20)")],example:{username:"member01",password:"abc123",nickname:"小明",phone:"13800138000"}},
|
||
PasswordChange:{title:"PasswordChange 修改密码",fields:[F("oldPassword","String","是","原密码"),F("newPassword","String","是","新密码")],example:{oldPassword:"abc123",newPassword:"newPass456"}},
|
||
OauthCallback:{title:"OauthCallback 涂鸦OAuth回调",fields:[F("code","String","是","授权码code")],example:{code:"a1b2c3d4"}},
|
||
BindTokenRequest:{title:"BindTokenRequest Token绑定",fields:[F("accessToken","String","是","涂鸦access_token")],example:{accessToken:"tok_xxxx"}},
|
||
SmsSendRequest:{title:"SmsSendRequest 发送验证码",fields:[F("phone","String","是","手机号"),F("countryCode","String","否","国家码(默认86)")],example:{phone:"13800138000",countryCode:"86"}},
|
||
SmsBindRequest:{title:"SmsBindRequest 验证码绑定",fields:[F("phone","String","是","手机号"),F("code","String","是","验证码"),F("countryCode","String","否","国家码(默认86)")],example:{phone:"13800138000",code:"123456",countryCode:"86"}},
|
||
UserRequest:{title:"UserRequest 后台用户(增/改)",fields:[F("username","String","是","用户名(3-64)"),F("password","String","否","密码(6-64,创建时必填)"),F("realName","String","否","真实姓名"),F("phone","String","否","手机号"),F("email","String","否","邮箱"),F("status","Integer","否","状态(0禁用/1启用,默认1)"),F("roleIds","List<Long>","否","角色ID列表")],example:{username:"operator",password:"abc123",realName:"运维",phone:"13800138000",email:"op@example.com",status:1,roleIds:[2,3]}},
|
||
RoleRequest:{title:"RoleRequest 后台角色(增/改)",fields:[F("roleName","String","是","角色名称(≤64)"),F("roleCode","String","是","角色编码(≤64)"),F("description","String","否","描述"),F("status","Integer","否","状态(0禁用/1启用,默认1)"),F("sort","Integer","否","排序(默认0)"),F("permissions","Set<String>","否","权限编码集合")],example:{roleName:"设备管理员",roleCode:"DEVICE_ADMIN",description:"管理设备",status:1,sort:0,permissions:["device:view","device:edit"]}},
|
||
MemberRequest:{title:"MemberRequest 会员(增/改)",fields:[F("username","String","是","用户名(3-64)"),F("password","String","否","密码(6-64,创建时必填)"),F("nickname","String","否","昵称"),F("phone","String","否","手机号"),F("email","String","否","邮箱"),F("gender","Integer","否","性别(0未知/1男/2女)"),F("status","Integer","否","状态(0禁用/1正常,默认1)")],example:{username:"member01",password:"abc123",nickname:"小明",phone:"13800138000",email:"m@example.com",gender:1,status:1}},
|
||
PlatformConfigRequest:{title:"PlatformConfigRequest 第三方平台配置",fields:[F("platformCode","String","是","平台编码(tuya/google/alexa/ali...)"),F("platformName","String","否","平台名称"),F("enabled","Integer","否","是否启用(0/1)"),F("accessId","String","否","涂鸦Access ID"),F("accessSecret","String","否","涂鸦Access Secret"),F("apiKey","String","否","大模型API Key"),F("modelName","String","否","大模型(qwen-turbo/plus/max)"),F("baseUrl","String","否","API基础地址(OpenAI兼容)"),F("timeoutMs","Integer","否","超时(毫秒)"),F("apiRegion","String","否","API区域(cn/us/eu/in)"),F("projectId","String","否","Google Home Project ID"),F("clientId","String","否","Client ID"),F("clientSecret","String","否","Client Secret"),F("appIdentifier","String","否","App授权签名标识符"),F("alexaClientId","String","否","Alexa Client ID"),F("alexaSecret","String","否","Alexa Secret"),F("region","String","否","服务区域"),F("remark","String","否","备注")],example:{platformCode:"tuya",platformName:"涂鸦开放平台",enabled:1,accessId:"your_access_id",accessSecret:"your_access_secret",apiKey:"sk-xxxx",modelName:"qwen-turbo",baseUrl:"https://dashscope.aliyuncs.com",timeoutMs:30000}},
|
||
Device:{title:"Device 设备实体",entity:true,fields:[F("deviceId","String","否","设备唯一标识"),F("deviceName","String","否","设备名称"),F("platform","String","否","平台(tuya/miot/dueros/local/matter)"),F("deviceType","String","否","设备类型"),F("platformProductId","String","否","平台原始产品ID(如涂鸦productId)"),F("firmwareVersion","String","否","固件版本"),F("status","String","否","状态(online/offline/error)"),F("capabilities","JsonNode","否","设备能力描述(JSON)"),F("location","String","否","设备位置"),F("groupId","Long","否","分组ID"),F("parentDeviceId","String","否","父网关设备ID"),F("isGateway","Integer","否","是否网关(1=网关)"),F("ownerId","Long","否","归属用户ID(member.id)"),F("productId","Long","否","所属产品ID"),F("homeId","Long","否","所属家庭ID"),F("roomId","Long","否","所属房间ID"),F("icon","String","否","图标URL"),F("remark","String","否","备注")],example:{deviceId:"DEV_001",deviceName:"客厅灯",platform:"tuya",deviceType:"light",platformProductId:"abc123",firmwareVersion:"1.0.0",status:"online",capabilities:{"switch":true,"brightness":80},location:"客厅",groupId:1,parentDeviceId:null,isGateway:0,ownerId:1001,productId:10,homeId:1,roomId:2,icon:"https://cdn/x/light.png",remark:""}},
|
||
DeviceCommand:{title:"DeviceCommand 控制指令",fields:[F("deviceId","String","否","设备ID"),F("commandType","String","否","指令类型(switch/set/query...)"),F("params","Map<String,Object>","否","指令参数(如{\"power\":\"on\"}/{\"bright_value\":80})"),F("timeout","Long","否","超时毫秒(默认30000)")],example:{deviceId:"DEV_001",commandType:"switch",params:{power:"on"},timeout:30000}},
|
||
DeviceConnectRequest:{title:"DeviceConnectRequest 设备连接",fields:[F("deviceId","String","否","设备ID"),F("platform","String","否","平台(tuya/miot/...)"),F("credentials","Credentials","否","认证凭证(见下)")],nested:[["credentials.accessToken","String","否","访问令牌(如Tuya accessToken)"],["credentials.username","String","否","用户名"],["credentials.password","String","否","密码"],["credentials.clientId","String","否","客户端ID"],["credentials.extra","Map<String,Object>","否","其它扩展属性"]],example:{deviceId:"DEV_001",platform:"tuya",credentials:{accessToken:"tok_xxxx",username:"",password:"",clientId:"",extra:{}}}},
|
||
DeviceActivityReport:{title:"DeviceActivityReport 控制动态上报",fields:[F("action","String","否","动作(switch_on/switch_off/adjust/status_change)"),F("actionDesc","String","否","人类可读描述(可空,后端生成)"),F("detail","Map<String,Object>","否","明细(下发的DP,如{\"1\":true})"),F("commandId","String","否","控制回执配对ID(可空)")],example:{action:"switch_on",actionDesc:"开启客厅灯",detail:{"1":true},commandId:"cmd_1700000000"}},
|
||
DeviceGroup:{title:"DeviceGroup 设备分组实体",entity:true,fields:[F("groupName","String","否","分组名称"),F("groupCode","String","否","分组编码"),F("parentId","Long","否","父分组ID(0=根节点)"),F("description","String","否","描述"),F("sort","Integer","否","排序"),F("status","Integer","否","状态(0停用/1正常)")],example:{groupName:"卧室",groupCode:"bedroom",parentId:0,description:"",sort:0,status:1}},
|
||
Product:{title:"Product 产品实体",entity:true,fields:[F("productKey","String","否","产品业务编码"),F("name","String","否","产品名称"),F("category","String","否","品类(light/switch/ac/security...)"),F("manufacturer","String","否","厂商"),F("model","String","否","型号"),F("platform","String","否","接入平台(tuya/local/matter)"),F("platformProductId","String","否","平台产品ID(涂鸦productId)"),F("connectType","String","否","联网方式(wifi/ble/zigbee/matter)"),F("netConfigType","String","否","配网方式"),F("icon","String","否","图标"),F("description","String","否","说明"),F("status","String","否","状态(draft/published)"),F("specVersion","Integer","否","物模型版本")],example:{productKey:"PK_LIGHT_001",name:"智能灯",category:"light",manufacturer:"Tuya",model:"L1",platform:"tuya",platformProductId:"abc123",connectType:"wifi",netConfigType:"ap",icon:"https://cdn/x/light.png",description:"支持开关/亮度/色温",status:"published",specVersion:1}},
|
||
ProductSpec:{title:"ProductSpec 物模型(TSL)",fields:[F("productId","Long","否","产品ID"),F("properties","List<ProductProperty>","否","属性列表"),F("events","List<ProductEvent>","否","事件列表"),F("actions","List<ProductAction>","否","服务列表")],example:{productId:10,properties:[{code:"switch_1",name:"开关",access:"write",type:"bool",dpId:"1"}],events:[],actions:[]}},
|
||
ProductProperty:{title:"ProductProperty 物模型-属性",entity:true,fields:[F("productId","Long","否","产品ID"),F("code","String","否","功能点编码(如switch_1)"),F("name","String","否","名称"),F("access","String","否","访问权限(read/write/notify)"),F("type","String","否","数据类型(bool/int/enum/float/string/json)"),F("min","String","否","最小值"),F("max","String","否","最大值"),F("step","String","否","步长"),F("unit","String","否","单位"),F("enumList","String","否","枚举值(JSON数组)"),F("dpId","String","否","平台DP点映射(涂鸦DP)"),F("defaultValue","String","否","默认值"),F("sort","Integer","否","排序")],example:{productId:10,code:"switch_1",name:"开关",access:"write",type:"bool",min:"0",max:"1",step:"1",unit:"",enumList:"",dpId:"1",defaultValue:"false",sort:1}},
|
||
ProductEvent:{title:"ProductEvent 物模型-事件",entity:true,fields:[F("productId","Long","否","产品ID"),F("code","String","否","事件编码"),F("name","String","否","名称"),F("type","String","否","类型(info/alert/fault)"),F("params","String","否","事件参数(JSON)")],example:{productId:10,code:"power_report",name:"电量上报",type:"info",params:"{}"}},
|
||
ProductAction:{title:"ProductAction 物模型-服务",entity:true,fields:[F("productId","Long","否","产品ID"),F("code","String","否","服务编码"),F("name","String","否","名称"),F("input","String","否","输入参数(JSON)"),F("output","String","否","输出参数(JSON)")],example:{productId:10,code:"restart",name:"重启",input:"{}",output:"{}"}},
|
||
Firmware:{title:"Firmware 固件实体",entity:true,fields:[F("productId","Long","否","产品ID"),F("version","String","否","固件版本"),F("fileUrl","String","否","升级包地址"),F("fileMd5","String","否","校验MD5"),F("size","Long","否","大小(字节)"),F("releaseNote","String","否","更新说明"),F("isLatest","Integer","否","是否最新(0/1)"),F("status","String","否","状态(draft/published)")],example:{productId:10,version:"1.2.0",fileUrl:"https://cdn/x/fw_1.2.0.bin",fileMd5:"a1b2c3d4",size:123456,releaseNote:"修复已知问题",isLatest:0,status:"draft"}},
|
||
OtaTaskCreateRequest:{title:"OtaTaskCreateRequest 创建OTA任务",fields:[F("firmwareId","Long","是","目标固件ID"),F("scope","String","否","范围(all/designated/gray)"),F("deviceIds","List<Long>","否","指定设备ID(scope=designated必填)"),F("grayPercent","Integer","否","灰度百分比(scope=gray时1-99)")],example:{firmwareId:5,scope:"designated",deviceIds:[101,102],grayPercent:10}},
|
||
OtaReportRequest:{title:"OtaReportRequest OTA结果上报",fields:[F("deviceId","String","是","设备ID"),F("success","Boolean","是","是否升级成功"),F("version","String","否","升级后版本(成功时上报)")],example:{deviceId:"DEV_001",success:true,version:"1.2.0"}},
|
||
HomeRequest:{title:"HomeRequest 家庭(增/改)",fields:[F("name","String","是","家庭名称(≤30)"),F("coverImage","String","否","封面图URL"),F("address","String","否","地址(≤100)")],example:{name:"我的家",coverImage:"https://cdn/x/home.png",address:"上海市浦东新区"}},
|
||
JoinHomeRequest:{title:"JoinHomeRequest 加入家庭",fields:[F("shareCode","String","是","分享码(6-12)")],example:{shareCode:"ABC123"}},
|
||
MemberRoleRequest:{title:"MemberRoleRequest 修改成员角色",fields:[F("role","String","是","目标角色(admin/member)")],example:{role:"admin"}},
|
||
RoomRequest:{title:"RoomRequest 房间(增/改)",fields:[F("name","String","是","房间名称")],example:{name:"主卧"}},
|
||
TuyaBindRequest:{title:"TuyaBindRequest 绑定涂鸦设备",fields:[F("homeId","Long","是","我们的家庭ID"),F("roomId","Long","否","房间ID"),F("deviceIds","List<String>","否","涂鸦设备ID列表(旧版兼容)"),F("devices","List<DeviceInfo>","否","设备详情(推荐)"),F("fullSync","Boolean","否","全量同步(默认false)")],nested:[["devices.deviceId","String","否","涂鸦设备ID"],["devices.deviceName","String","否","设备名称"],["devices.category","String","否","设备分类"],["devices.online","Boolean","否","是否在线"],["devices.productId","String","否","产品ID"],["devices.icon","String","否","图标URL"]],example:{homeId:1,roomId:2,deviceIds:["tdev1"],devices:[{deviceId:"tdev1",deviceName:"灯",category:"light",online:true,productId:"pid",icon:""}],fullSync:false}},
|
||
TuyaNativeBindRequest:{title:"TuyaNativeBindRequest 原生SDK绑定",fields:[F("tuyaUid","String","是","涂鸦用户UID"),F("account","String","是","登录账号(手机号/邮箱)"),F("loginMethod","String","否","登录方式(phone/email,默认phone)"),F("platform","String","否","平台(android/ios/h5,默认android)")],example:{tuyaUid:"uid123",account:"13800138000",loginMethod:"phone",platform:"android"}},
|
||
TuyaRename:{title:"TuyaRename 重命名涂鸦设备",fields:[F("deviceName","String","是","新设备名称")],example:{deviceName:"客厅主灯"}},
|
||
TuyaCommand:{title:"TuyaCommand 涂鸦指令数组",fields:[F("code","String","是","指令码(如switch_1/bright_value)"),F("value","Object","是","指令值(true/80/\"white\"等)")],example:[{code:"switch_1",value:true},{code:"bright_value",value:80}]},
|
||
DeviceStatusSyncRequest:{title:"DeviceStatusSyncRequest 状态同步",fields:[F("deviceId","String","否","涂鸦设备ID"),F("online","Boolean","否","是否在线"),F("dps","Map<String,Object>","否","DP状态(key=dpId,value=dp值)"),F("source","String","否","上报来源(panel/app)")],example:{deviceId:"tdev1",online:true,dps:{"1":true,"2":80},source:"app"}},
|
||
DeviceDataPoint:{title:"DeviceDataPoint 数据点(数组元素)",fields:[F("key","String","是","数据点key"),F("type","String","否","类型(number/string/boolean/...)"),F("value","Object","是","数据值"),F("timestamp","Long","否","时间戳(毫秒)"),F("quality","String","否","数据质量(good/bad)")],example:{key:"temperature",type:"number",value:26.5,timestamp:1697000000000,quality:"good"}},
|
||
RuleEntity:{title:"RuleEntity 规则实体",entity:true,fields:[F("sceneId","Long","否","所属场景ID(空=独立规则)"),F("ruleName","String","否","规则名称"),F("ruleCode","String","否","规则编码"),F("description","String","否","规则描述"),F("triggerDeviceId","String","否","触发设备ID"),F("conditionExpression","String","否","条件表达式"),F("actions","JsonNode","否","执行动作(JSON)"),F("enabled","Boolean","否","是否启用"),F("priority","Integer","否","优先级"),F("ownerId","Long","否","归属用户ID(member.id)")],example:{sceneId:null,ruleName:"温度告警",ruleCode:"R001",description:"",triggerDeviceId:"DEV_001",conditionExpression:"temp>30",actions:{type:"notify",content:"高温"},enabled:true,priority:1,ownerId:1001}},
|
||
SceneEntity:{title:"SceneEntity 场景实体",entity:true,fields:[F("sceneName","String","否","场景名称"),F("sceneCode","String","否","场景编码"),F("sceneType","String","否","类型(one_click/automation)"),F("cronExpression","String","否","定时cron(六段式,可空)"),F("conditions","String","否","触发条件(JSON数组)"),F("actions","String","否","执行动作(JSON数组)"),F("conditionLogic","String","否","多条件逻辑(AND/OR)"),F("icon","String","否","图标URL"),F("description","String","否","描述"),F("enabled","Boolean","否","是否启用"),F("sortOrder","Integer","否","排序"),F("ownerId","Long","否","归属用户ID(member.id)")],example:{sceneName:"回家模式",sceneCode:"S001",sceneType:"one_click",cronExpression:"",conditions:"[]",actions:"[{\"type\":\"set_property\"}]",conditionLogic:"AND",icon:"",description:"",enabled:true,sortOrder:0,ownerId:1001}},
|
||
SceneCreateRequest:{title:"SceneCreateRequest 结构化场景(增/改)",fields:[F("sceneName","String","否","场景名称"),F("ownerId","Long","否","归属用户ID(网关注入)"),F("sceneType","String","否","类型(one_click/automation,可空)"),F("icon","String","否","图标"),F("description","String","否","描述"),F("conditionLogic","String","否","多条件逻辑(AND/OR,默认AND)"),F("conditions","List<SceneConditionDTO>","否","触发条件列表"),F("actions","List<SceneActionDTO>","否","执行动作列表")],example:{sceneName:"回家模式",ownerId:1001,sceneType:"one_click",icon:"",description:"",conditionLogic:"AND",conditions:[{type:"device_state",deviceId:"DEV_001",propertyCode:"switch_1",operator:">",value:true}],actions:[{type:"set_property",deviceId:"DEV_002",propertyCode:"switch_1",value:true},{type:"delay",seconds:3},{type:"notify",channel:"push",content:"已开启"}]}},
|
||
SceneActionDTO:{title:"SceneActionDTO 场景动作",fields:[F("type","String","是","动作类型(set_property/delay/execute_scene/notify)"),F("deviceId","String","否","目标设备ID(set_property用)"),F("propertyCode","String","否","属性code(set_property用)"),F("value","Object","否","属性值(set_property用)"),F("seconds","Integer","否","延时秒数(delay用)"),F("sceneId","Long","否","目标场景ID(execute_scene用)"),F("channel","String","否","通知渠道(notify用:app/sms/push)"),F("content","String","否","通知内容(notify用)")],example:{type:"set_property",deviceId:"DEV_002",propertyCode:"switch_1",value:true,seconds:3,sceneId:1,channel:"push",content:"已开启"}},
|
||
SceneConditionDTO:{title:"SceneConditionDTO 场景条件",fields:[F("type","String","是","条件类型(device_state/timer/manual)"),F("deviceId","String","否","设备ID(device_state必填)"),F("propertyCode","String","否","属性code(平台无关)"),F("dpId","String","否","平台DP标识(兼容dp-id)"),F("operator","String","否","运算符(==,!=,>,<,>=,<=)"),F("value","Object","否","比较值(boolean/number/string)"),F("time","String","否","定时时间HH:mm(timer用)"),F("weekDays","List<Integer>","否","定时重复星期1-7(timer用)")],example:{type:"device_state",deviceId:"DEV_001",propertyCode:"switch_1",dpId:"1",operator:">",value:true,time:"08:00",weekDays:[1,2,3]}},
|
||
AlarmRuleEntity:{title:"AlarmRuleEntity 告警规则实体",entity:true,fields:[F("ruleName","String","否","规则名称"),F("ruleCode","String","否","规则编码"),F("deviceId","String","否","设备ID(空=所有设备)"),F("dataPoint","String","否","数据点"),F("conditionExpression","String","否","条件表达式"),F("alarmLevel","String","否","告警级别"),F("alarmTemplate","String","否","告警消息模板"),F("enabled","Boolean","否","是否启用"),F("deduplicationWindow","Integer","否","去重窗口(分钟)"),F("notificationMethods","String","否","通知方式(email/sms/websocket/push)"),F("ownerId","Long","否","归属用户ID(member.id)")],example:{ruleName:"高温告警",ruleCode:"A001",deviceId:"DEV_001",dataPoint:"temperature",conditionExpression:"value>30",alarmLevel:"high",alarmTemplate:"设备{deviceName}温度过高",enabled:true,deduplicationWindow:30,notificationMethods:"push,email",ownerId:1001}},
|
||
AiCommandRequest:{title:"AiCommandRequest 自然语言指令",fields:[F("text","String","是","指令文本(如:打开客厅的灯)"),F("targetType","String","否","二次确认目标类型(device/scene)"),F("targetId","String","否","二次确认目标ID"),F("learn","Boolean","否","是否学习为别名(默认true)")],example:{text:"打开客厅的灯",targetType:"device",targetId:"DEV_001",learn:true}},
|
||
AiAliasRequest:{title:"AiAliasRequest 别名维护",fields:[F("alias","String","是","用户说法(如:客厅开关)"),F("targetType","String","是","目标类型(device/scene/room)"),F("targetId","String","是","目标ID(device.device_id/scene.id/room.id)"),F("homeId","Long","否","归属家庭ID(可选)")],example:{alias:"客厅灯",targetType:"device",targetId:"DEV_001",homeId:1}},
|
||
AiDeviceProfileRequest:{title:"AiDeviceProfileRequest 指令码校准",fields:[F("powerCode","String","否","开/关指令码(如switch_1/switch_led/control)"),F("powerOnValue","String","否","开启值(true/1)"),F("powerOffValue","String","否","关闭值(false/0)"),F("brightnessCode","String","否","亮度指令码(如bright_value)"),F("colorTempCode","String","否","色温指令码(如temp_value)"),F("temperatureCode","String","否","温度设定码(如temp_set)"),F("modeCode","String","否","模式指令码(如mode)")],example:{powerCode:"switch_1",powerOnValue:"true",powerOffValue:"false",brightnessCode:"bright_value",colorTempCode:"temp_value",temperatureCode:"temp_set",modeCode:"mode"}},
|
||
UserQuery:{title:"UserQuery 用户查询参数",query:true,fields:[F("username","String","否","用户名"),F("realName","String","否","真实姓名"),F("roleId","Long","否","角色ID"),F("status","Integer","否","状态(0/1)"),F("pageNum","Integer","否","页码(默认1)"),F("pageSize","Integer","否","每页条数(默认20)")],sample:"pageNum=1&pageSize=20&status=1"},
|
||
RoleQuery:{title:"RoleQuery 角色查询参数",query:true,fields:[F("roleName","String","否","角色名称(模糊)"),F("roleCode","String","否","角色编码"),F("status","Integer","否","状态(0/1)"),F("pageNum","Integer","否","页码(默认1)"),F("pageSize","Integer","否","每页条数(默认20)")],sample:"pageNum=1&pageSize=20"},
|
||
MemberQuery:{title:"MemberQuery 会员查询参数",query:true,fields:[F("keyword","String","否","用户名/昵称(模糊)"),F("phone","String","否","手机号"),F("status","Integer","否","状态(0/1)"),F("pageNum","Integer","否","页码(默认1)"),F("pageSize","Integer","否","每页条数(默认20)")],sample:"pageNum=1&pageSize=20&keyword=小"},
|
||
LogQuery:{title:"LogQuery 日志查询参数",query:true,fields:[F("username","String","否","操作人(模糊)"),F("module","String","否","模块"),F("operation","String","否","操作类型"),F("status","Integer","否","状态(0失败/1成功)"),F("startTime","LocalDateTime","否","开始时间"),F("endTime","LocalDateTime","否","结束时间"),F("pageNum","Integer","否","页码(默认1)"),F("pageSize","Integer","否","每页条数(默认20)")],sample:"pageNum=1&pageSize=20&module=device"},
|
||
DeviceQuery:{title:"DeviceQuery 设备查询参数",query:true,fields:[F("deviceName","String","否","设备名称(模糊)"),F("platform","String","否","平台(tuya/miot/...)"),F("deviceType","String","否","设备类型"),F("status","String","否","状态(online/offline/error)"),F("ownerId","Long","否","归属用户ID"),F("homeId","Long","否","家庭ID"),F("roomId","Long","否","房间ID"),F("groupId","Long","否","分组ID"),F("deviceId","String","否","设备ID(模糊)"),F("parentDeviceId","String","否","父网关ID"),F("isGateway","Integer","否","是否网关(1)"),F("pageNum","Integer","否","页码(默认1)"),F("pageSize","Integer","否","每页条数(默认20)")],sample:"pageNum=1&pageSize=20&platform=tuya"},
|
||
ProductQuery:{title:"ProductQuery 产品查询参数",query:true,fields:[F("page","Integer","否","页码(默认1)"),F("size","Integer","否","每页条数(默认20)"),F("keyword","String","否","名称/编码(模糊)"),F("category","String","否","品类"),F("platform","String","否","平台")],sample:"page=1&size=20&category=light"},
|
||
SceneQuery:{title:"SceneQuery 场景查询参数",query:true,fields:[F("pageNum","Integer","否","页码(默认1)"),F("pageSize","Integer","否","每页条数(默认20)")],sample:"pageNum=1&pageSize=20"},
|
||
GroupQuery:{title:"GroupQuery 分组查询参数",query:true,fields:[F("groupName","String","否","分组名称(模糊)"),F("status","Integer","否","状态(0/1)"),F("pageNum","Integer","否","页码(默认1)"),F("pageSize","Integer","否","每页条数(默认20)")],sample:"pageNum=1&pageSize=20"},
|
||
AlarmQuery:{title:"AlarmQuery 告警查询参数",query:true,fields:[F("deviceId","String","否","设备ID"),F("alarmLevel","String","否","告警级别"),F("status","String","否","状态(pending/confirmed/resolved)"),F("ownerId","Long","否","归属用户ID"),F("startTime","LocalDateTime","否","开始时间"),F("endTime","LocalDateTime","否","结束时间"),F("pageNum","Integer","否","页码(默认1)"),F("pageSize","Integer","否","每页条数(默认20)")],sample:"pageNum=1&pageSize=20&status=pending"},
|
||
RuleQuery:{title:"RuleQuery 规则查询参数",query:true,fields:[F("ruleName","String","否","规则名称(模糊)"),F("enabled","Boolean","否","是否启用"),F("triggerDeviceId","String","否","触发设备ID"),F("ownerId","Long","否","归属用户ID"),F("sceneId","Long","否","所属场景ID"),F("pageNum","Integer","否","页码(默认1)"),F("pageSize","Integer","否","每页条数(默认20)")],sample:"pageNum=1&pageSize=20"},
|
||
DataHistoryQuery:{title:"DataHistoryQuery 历史数据查询参数",query:true,fields:[F("pointKey","String","否","数据点key(不填查全部)"),F("startTime","Long","否","开始时间戳(毫秒)"),F("endTime","Long","否","结束时间戳(毫秒)"),F("pageNum","Integer","否","页码(默认1)"),F("pageSize","Integer","否","每页条数(默认20)")],sample:"pointKey=temperature&startTime=1696900000000&endTime=1697000000000"},
|
||
DataStatsQuery:{title:"DataStatsQuery 聚合统计参数",query:true,fields:[F("field","String","是","统计字段(数据点key)"),F("aggregation","String","是","聚合方式(mean/max/min/sum/count)"),F("startTime","Long","否","开始时间戳(毫秒)"),F("endTime","Long","否","结束时间(毫秒)")],sample:"field=temperature&aggregation=max&startTime=1696900000000&endTime=1697000000000"},
|
||
LoginResponse:{title:"LoginResponse 登录响应 data",resp:true,fields:[F("token","String","-","JWT令牌"),F("username","String","-","用户名"),F("roles","List<String>","-","角色编码列表"),F("expiresIn","Long","-","有效期(秒)")],example:{token:"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",username:"admin",roles:["ADMIN"],expiresIn:86400}},
|
||
PageResult:{title:"PageResult 分页响应 data",resp:true,fields:[F("records","List","-","当前页数据列表(元素为对应entity)"),F("total","Long","-","总条数"),F("pages","Long","-","总页数"),F("current","Long","-","当前页"),F("size","Long","-","每页条数")],example:{records:[{id:1,deviceId:"DEV_001",deviceName:"客厅灯",platform:"tuya",status:"online"}],total:9,pages:1,current:1,size:20}},
|
||
|
||
/* ===== 中控屏(Panel) 数据模型 ===== */
|
||
PanelSession:{title:"PanelSession 屏会话(响应)",resp:true,fields:[F("panelId","Long","-","屏ID"),F("homeId","Long","否","归属家庭(后台预置即绑定场景)"),F("role","String","-","角色(GATEWAY/DISPLAY)"),F("accessToken","String","-","屏访问令牌"),F("refreshToken","String","-","刷新令牌(轮换用)"),F("expiresIn","Long","-","有效期(秒)")],example:{panelId:1,homeId:1,role:"GATEWAY",accessToken:"eyJ...",refreshToken:"r-...",expiresIn:86400}},
|
||
PanelSessionReq:{title:"PanelSessionReq 建会话请求",fields:[F("bootstrapSecret","String","是","管理端预置的一次性初始化凭证")],example:{bootstrapSecret:"bs-xxxx"}},
|
||
PanelSessionRefreshReq:{title:"PanelSessionRefreshReq 刷新请求",fields:[F("refreshToken","String","是","刷新令牌")],example:{refreshToken:"r-xxxx"}},
|
||
PanelSessionRefreshResp:{title:"PanelSessionRefreshResp 刷新响应",resp:true,fields:[F("accessToken","String","-","新访问令牌"),F("refreshToken","String","-","新刷新令牌"),F("expiresIn","Long","-","有效期(秒)")],example:{accessToken:"eyJ...",refreshToken:"r-...",expiresIn:86400}},
|
||
PanelHeartbeat:{title:"PanelHeartbeat 心跳体",fields:[F("online","Boolean","否","是否在线(默认true)"),F("uptime","Long","否","运行时长(秒)"),F("freeMem","Long","否","空闲内存(字节)"),F("msg","String","否","附加信息")],example:{online:true,uptime:3600,freeMem:524288000}},
|
||
PanelHeartbeatResp:{title:"PanelHeartbeatResp 心跳响应",resp:true,fields:[F("ok","Boolean","-","续约成功")],example:{ok:true}},
|
||
PanelInfo:{title:"PanelInfo 屏信息",resp:true,fields:[F("panelId","Long","-","屏ID"),F("homeId","Long","否","归属家庭"),F("role","String","-","角色"),F("enabled","Boolean","-","是否启用"),F("online","Boolean","-","是否在线"),F("lastSeenAt","Long","否","最后心跳时间"),F("features","List<String>","否","能力列表")],example:{panelId:1,homeId:1,role:"GATEWAY",enabled:true,online:true,lastSeenAt:1697000000000,features:["device-control","scene"]}},
|
||
PanelSnapshotQuery:{title:"PanelSnapshotQuery 快照分页",query:true,fields:[F("pageToken","String","否","游标(首屏为空)"),F("limit","Integer","否","每页条数(默认200)")],sample:"pageToken=&limit=200"},
|
||
PanelSnapshot:{title:"PanelSnapshot 家庭快照",resp:true,fields:[F("home","Object","-","家庭对象{homeId,name}"),F("rooms","List","-","房间列表"),F("devices","List","-","设备列表(首屏全量)"),F("stateEpoch","Long","-","一致性游标"),F("pageToken","String","否","下一页游标"),F("hasMore","Boolean","-","是否还有更多")],example:{home:{homeId:1,name:"我的家"},rooms:[{roomId:1,name:"客厅"}],devices:[{deviceId:"DEV_001",deviceName:"客厅灯"}],stateEpoch:1700000000000,pageToken:"",hasMore:false}},
|
||
PanelConfig:{title:"PanelConfig 屏配置",resp:true,fields:[F("configJson","String","-","运营配置(JSON)"),F("version","Integer","-","配置版本"),F("checksum","String","-","校验和(屏端比对是否变更)")],example:{configJson:"{...}",version:3,checksum:"a1b2c3"}},
|
||
PanelDeviceRegister:{title:"PanelDeviceRegister 设备登记",fields:[F("deviceId","String","是","设备唯一标识"),F("deviceName","String","否","设备名称"),F("platform","String","否","平台(tuya/miot/local/matter)"),F("productId","Long","否","产品ID"),F("category","String","否","品类"),F("schema","Object","否","标准控制模型")],example:{deviceId:"DEV_001",deviceName:"客厅灯",platform:"tuya",productId:10,category:"light"}},
|
||
PanelDeviceRegisterResult:{title:"PanelDeviceRegisterResult 登记响应",resp:true,fields:[F("deviceId","String","-","设备ID"),F("registered","Boolean","-","是否新登记")],example:{deviceId:"DEV_001",registered:true}},
|
||
PanelSchemaUpdate:{title:"PanelSchemaUpdate 控制模型更新",fields:[F("schema","Object","是","标准控制模型(属性/事件/动作)")],example:{schema:{switch_1:{type:"bool"}}}},
|
||
PanelSchemaUpdateResult:{title:"PanelSchemaUpdateResult",resp:true,fields:[F("accepted","Boolean","-","接受")],example:{accepted:true}},
|
||
PanelReportBatch:{title:"PanelReportBatch 状态上报",fields:[F("deviceId","String","是","设备ID"),F("items","List","是","状态项[{key,value,ts}]")],example:{deviceId:"DEV_001",items:[{key:"switch_1",value:true,ts:1697000000000}]}},
|
||
PanelReportResult:{title:"PanelReportResult 上报响应",resp:true,fields:[F("accepted","Integer","-","已接受条数"),F("rejected","Integer","-","被拒条数")],example:{accepted:1,rejected:0}},
|
||
PanelCmdPullReq:{title:"PanelCmdPullReq 命令拉取",fields:[F("cursor","Long","否","已处理到的最大命令ID"),F("limit","Integer","否","拉取条数(默认20)")],example:{cursor:100,limit:20}},
|
||
PanelCmdPullResp:{title:"PanelCmdPullResp 命令拉取响应",resp:true,fields:[F("commands","List","-","待执行命令[{commandId,type,params}]")],example:{commands:[{commandId:101,type:"set_property",params:{switch_1:true}}]}},
|
||
PanelCmdStartReq:{title:"PanelCmdStartReq 申请执行",fields:[F("executorRevision","Long","否","执行端版本号")],example:{executorRevision:5}},
|
||
PanelCmdStartResp:{title:"PanelCmdStartResp",resp:true,fields:[F("accepted","Boolean","-","接受")],example:{accepted:true}},
|
||
PanelCmdReportReq:{title:"PanelCmdReportReq 命令回报",fields:[F("status","String","是","执行状态(SUCCESS/FAIL)"),F("result","Object","否","结果明细")],example:{status:"SUCCESS",result:{switch_1:true}}},
|
||
PanelCmdResult:{title:"PanelCmdResult 命令回报响应",resp:true,fields:[F("recorded","Boolean","-","已记录")],example:{recorded:true}},
|
||
PanelDiagnostic:{title:"PanelDiagnostic 诊断上报",fields:[F("type","String","是","类型(network/mqtt/ota/command/config)"),F("detail","Object","否","明细"),F("ts","Long","否","时间戳")],example:{type:"network",detail:{rssi:-50},ts:1697000000000}},
|
||
PanelEventQuery:{title:"PanelEventQuery 事件查询",query:true,fields:[F("cursor","Long","否","上次事件ID(默认0)"),F("timeout","Integer","否","最长阻塞毫秒(默认20000)"),F("limit","Integer","否","返回条数(默认50)")],sample:"cursor=0&timeout=20000&limit=50"},
|
||
PanelEventsResp:{title:"PanelEventsResp 增量事件",resp:true,fields:[F("events","List","-","事件列表[{id,type,payload}]"),F("nextCursor","Long","-","下一游标")],example:{events:[{id:11,type:"device_state",payload:{deviceId:"DEV_001"}}],nextCursor:11}},
|
||
PanelOtaNext:{title:"PanelOtaNext 待升级任务",resp:true,fields:[F("taskId","Long","否","任务ID(无则为null)"),F("version","String","否","目标版本"),F("fileUrl","String","否","升级包地址"),F("size","Long","否","大小(字节)"),F("md5","String","否","校验MD5")],example:{taskId:5,version:"1.2.0",fileUrl:"https://cdn/x.bin",size:123456,md5:"a1b2"}},
|
||
PanelSceneRunReport:{title:"PanelSceneRunReport 场景执行结果",fields:[F("sceneId","Long","是","场景ID"),F("runId","String","否","执行批次ID"),F("status","String","是","状态(SUCCESS/FAIL)"),F("detail","Object","否","明细")],example:{sceneId:1,runId:"r-1",status:"SUCCESS"}},
|
||
PanelSceneRunView:{title:"PanelSceneRunView",resp:true,fields:[F("runId","String","-","执行批次ID"),F("status","String","-","状态")],example:{runId:"r-1",status:"SUCCESS"}},
|
||
PanelBindReq:{title:"PanelBindReq 会员绑定请求",fields:[F("serialNumber","String","是","中控屏序列号(SN)"),F("homeId","Long","是","绑定到的家庭ID"),F("pairingCode","String","否","配对码(可选)")],example:{serialNumber:"SN-2024-0001",homeId:1}},
|
||
PanelBindResult:{title:"PanelBindResult 绑定结果",resp:true,fields:[F("panelId","Long","-","屏ID"),F("serialNumber","String","-","序列号"),F("homeId","Long","-","家庭ID"),F("memberId","Long","-","会员ID"),F("role","String","-","角色"),F("boundAt","Long","-","绑定时间")],example:{panelId:1,serialNumber:"SN-2024-0001",homeId:1,memberId:1001,role:"GATEWAY",boundAt:1697000000000}},
|
||
PanelBindable:{title:"PanelBindable 可绑定性预检",resp:true,fields:[F("exists","Boolean","-","是否已预置"),F("enabled","Boolean","-","是否启用"),F("bound","Boolean","-","是否已绑定家庭"),F("panelId","Long","否","屏ID(已预置时)")],example:{exists:true,enabled:true,bound:false,panelId:1}},
|
||
PanelSummary:{title:"PanelSummary 我的屏",resp:true,fields:[F("panelId","Long","-","屏ID"),F("serialNumber","String","-","序列号"),F("homeId","Long","-","家庭ID"),F("role","String","-","角色"),F("enabled","Boolean","-","是否启用"),F("online","Boolean","-","是否在线"),F("lastSeenAt","Long","否","最后心跳")],example:{panelId:1,serialNumber:"SN-2024-0001",homeId:1,role:"GATEWAY",enabled:true,online:true,lastSeenAt:1697000000000}},
|
||
PanelAdminCreate:{title:"PanelAdminCreate 预置屏终端",fields:[F("serialNumber","String","是","序列号(SN)"),F("homeId","Long","否","家庭ID(留空=待绑定库存)"),F("memberId","Long","否","预绑会员(可选)"),F("role","String","否","角色(GATEWAY/DISPLAY,默认GATEWAY)"),F("model","String","否","型号"),F("features","List<String>","否","能力列表")],example:{serialNumber:"SN-2024-0001",homeId:null,role:"GATEWAY"}},
|
||
PanelAdminCreateResult:{title:"PanelAdminCreateResult 预置响应",resp:true,fields:[F("panelId","Long","-","屏ID"),F("serialNumber","String","-","序列号"),F("bootstrapSecret","String","-","一次性初始化凭证(仅返回一次)")],example:{panelId:1,serialNumber:"SN-2024-0001",bootstrapSecret:"bs-xxxx"}},
|
||
PanelAdminItem:{title:"PanelAdminItem 屏终端列表项",resp:true,fields:[F("panelId","Long","-","屏ID"),F("serialNumber","String","-","序列号"),F("homeId","Long","否","家庭ID"),F("role","String","-","角色"),F("enabled","Boolean","-","是否启用"),F("bound","Boolean","-","是否已绑定"),F("createdAt","Long","-","创建时间")],example:{panelId:1,serialNumber:"SN-2024-0001",homeId:null,role:"GATEWAY",enabled:true,bound:false,createdAt:1697000000000}},
|
||
PanelAdminRotateResult:{title:"PanelAdminRotateResult 轮换响应",resp:true,fields:[F("panelId","Long","-","屏ID"),F("bootstrapSecret","String","-","新初始化凭证")],example:{panelId:1,bootstrapSecret:"bs-new"}},
|
||
PanelConfigSave:{title:"PanelConfigSave 保存屏配置",fields:[F("configJson","String","是","配置JSON"),F("version","Integer","是","版本(需等于当前版本)")],example:{configJson:"{...}",version:3}},
|
||
PanelReassignReq:{title:"PanelReassignReq 多屏归属交接",fields:[F("fromPanelId","Long","是","源屏ID"),F("toPanelId","Long","是","目标屏ID"),F("routeRevision","Long","是","路由版本(需一致)")],example:{fromPanelId:1,toPanelId:2,routeRevision:7}},
|
||
PanelReassignResult:{title:"PanelReassignResult 交接响应",resp:true,fields:[F("routeRevision","Long","-","新路由版本"),F("reassigned","Integer","-","交接设备数")],example:{routeRevision:8,reassigned:3}}
|
||
};
|
||
|
||
/* ===== curl 示例模板 ===== */
|
||
const EXAMPLES={
|
||
login:(b)=>`<span class="c"># 1) 登录拿 Token</span>
|
||
curl -X POST ${b}/auth/login \\
|
||
-H 'Content-Type: application/json' \\
|
||
-d '{"username":"admin","password":"admin123"}'
|
||
<span class="c"># 返回 data.token,后续请求带 Header: Authorization: Bearer <TOKEN></span>`,
|
||
devlist:(b)=>`<span class="c"># 2) 设备列表(带 Token)</span>
|
||
curl ${b}/devices \\
|
||
-H 'Authorization: Bearer <TOKEN>'`,
|
||
cmd:(b)=>`<span class="c"># 3) 发送设备控制指令(开关 / 设置)</span>
|
||
curl -X POST ${b}/devices/{deviceId}/commands \\
|
||
-H 'Authorization: Bearer <TOKEN>' \\
|
||
-H 'Content-Type: application/json' \\
|
||
-d '{"deviceId":"{deviceId}","commandType":"switch","params":{"power":"on"}}'`,
|
||
ingest:(b)=>`<span class="c"># 4) 设备上报时序数据(iot-data :9082)</span>
|
||
curl -X POST ${b}/data/devices/{deviceId}/data \\
|
||
-H 'Content-Type: application/json' \\
|
||
-d '[{"key":"temperature","type":"number","value":26.5,"timestamp":1697000000000,"quality":"good"}]'`,
|
||
scene:(b)=>`<span class="c"># 5) 创建结构化场景(一键执行 + 延时 + 通知)</span>
|
||
curl -X POST ${b}/scenes/v2 \\
|
||
-H 'Authorization: Bearer <TOKEN>' \\
|
||
-H 'Content-Type: application/json' \\
|
||
-d '{
|
||
"sceneName":"回家模式",
|
||
"sceneType":"one_click",
|
||
"conditionLogic":"AND",
|
||
"conditions":[],
|
||
"actions":[
|
||
{"type":"set_property","deviceId":"DEV001","propertyCode":"switch_1","value":true},
|
||
{"type":"delay","seconds":3},
|
||
{"type":"notify","channel":"push","content":"已开启回家模式"}
|
||
]
|
||
}'`,
|
||
rulelist:(b)=>`<span class="c"># 6) 规则列表(按会员隔离,传 Token 看自己的)</span>
|
||
curl ${b}/rules \\
|
||
-H 'Authorization: Bearer <TOKEN>'`
|
||
};
|
||
|
||
/* ===== 接口 → 模型 映射 ===== */
|
||
const EPMAP={
|
||
"POST /api/v1/auth/login":{body:"LoginRequest",resp:"LoginResponse"},
|
||
"POST /api/v1/auth/register":{body:"RegisterRequest"},
|
||
"POST /api/v1/auth/member/login":{body:"LoginRequest"},
|
||
"POST /api/v1/auth/member/refresh":{body:"RefreshRequest"},
|
||
"POST /api/v1/auth/member/register":{body:"MemberRegisterRequest"},
|
||
"PUT /api/v1/auth/member/password":{body:"PasswordChange"},
|
||
"GET /api/v1/users":{query:"UserQuery"},
|
||
"POST /api/v1/users":{body:"UserRequest"},
|
||
"PUT /api/v1/users/{id}":{body:"UserRequest"},
|
||
"PUT /api/v1/users/{id}/password":{body:"PasswordChange"},
|
||
"GET /api/v1/roles":{query:"RoleQuery"},
|
||
"POST /api/v1/roles":{body:"RoleRequest"},
|
||
"PUT /api/v1/roles/{id}":{body:"RoleRequest"},
|
||
"GET /api/v1/members":{query:"MemberQuery"},
|
||
"POST /api/v1/members":{body:"MemberRequest"},
|
||
"PUT /api/v1/members/{id}":{body:"MemberRequest"},
|
||
"GET /api/v1/logs":{query:"LogQuery"},
|
||
"PUT /api/v1/admin/platforms/{platformCode}":{body:"PlatformConfigRequest"},
|
||
"GET /api/v1/devices":{query:"DeviceQuery"},
|
||
"POST /api/v1/devices":{body:"Device",entity:true},
|
||
"PUT /api/v1/devices/{id}":{body:"Device",entity:true},
|
||
"POST /api/v1/devices/{deviceId}/connect":{body:"DeviceConnectRequest"},
|
||
"POST /api/v1/devices/{deviceId}/commands":{body:"DeviceCommand"},
|
||
"POST /api/v1/devices/activities/{deviceId}/activity":{body:"DeviceActivityReport"},
|
||
"GET /api/v1/products":{query:"ProductQuery"},
|
||
"POST /api/v1/products":{body:"Product",entity:true},
|
||
"PUT /api/v1/products/{id}":{body:"Product",entity:true},
|
||
"PUT /api/v1/products/{id}/spec":{body:"ProductSpec"},
|
||
"POST /api/v1/products/{id}/properties":{body:"ProductProperty",entity:true},
|
||
"POST /api/v1/products/{id}/events":{body:"ProductEvent",entity:true},
|
||
"POST /api/v1/products/{id}/actions":{body:"ProductAction",entity:true},
|
||
"POST /api/v1/products/{id}/firmware":{body:"Firmware",entity:true},
|
||
"POST /api/v1/products/{id}/ota":{body:"OtaTaskCreateRequest"},
|
||
"POST /api/v1/ota-tasks/{id}/report":{body:"OtaReportRequest"},
|
||
"GET /api/v1/device-groups":{query:"GroupQuery"},
|
||
"POST /api/v1/device-groups":{body:"DeviceGroup",entity:true},
|
||
"PUT /api/v1/device-groups/{id}":{body:"DeviceGroup",entity:true},
|
||
"POST /api/v1/homes":{body:"HomeRequest"},
|
||
"PUT /api/v1/homes/{id}":{body:"HomeRequest"},
|
||
"POST /api/v1/homes/join":{body:"JoinHomeRequest"},
|
||
"PUT /api/v1/homes/{id}/members/{memberId}/role":{body:"MemberRoleRequest"},
|
||
"POST /api/v1/homes/{homeId}/rooms":{body:"RoomRequest"},
|
||
"PUT /api/v1/homes/{homeId}/rooms/{roomId}":{body:"RoomRequest"},
|
||
"POST /api/v1/tuya/devices/bind":{body:"TuyaBindRequest"},
|
||
"POST /api/v1/tuya/devices/status":{body:"DeviceStatusSyncRequest"},
|
||
"POST /api/v1/tuya/auth/bind-native":{body:"TuyaNativeBindRequest"},
|
||
"POST /api/v1/tuya/auth/sms/send":{body:"SmsSendRequest"},
|
||
"POST /api/v1/tuya/auth/sms/bind":{body:"SmsBindRequest"},
|
||
"POST /api/v1/tuya/auth/bind-token":{body:"BindTokenRequest"},
|
||
"POST /api/v1/tuya/auth/callback":{body:"OauthCallback"},
|
||
"POST /api/v1/tuya/devices/{deviceId}/commands":{body:"TuyaCommand"},
|
||
"POST /api/v1/tuya/devices/{deviceId}/rename":{body:"TuyaRename"},
|
||
"POST /api/v1/data/devices/{deviceId}/data":{body:"DeviceDataPoint",isArray:true},
|
||
"GET /api/v1/data/devices/{deviceId}/history":{query:"DataHistoryQuery"},
|
||
"GET /api/v1/data/devices/{deviceId}/stats":{query:"DataStatsQuery"},
|
||
"POST /api/v1/rules":{body:"RuleEntity",entity:true},
|
||
"PUT /api/v1/rules/{ruleId}":{body:"RuleEntity",entity:true},
|
||
"GET /api/v1/rules":{query:"RuleQuery"},
|
||
"GET /api/v1/scenes":{query:"SceneQuery"},
|
||
"POST /api/v1/scenes":{body:"SceneEntity",entity:true},
|
||
"PUT /api/v1/scenes/{sceneId}":{body:"SceneEntity",entity:true},
|
||
"POST /api/v1/scenes/v2":{body:"SceneCreateRequest"},
|
||
"PUT /api/v1/scenes/{sceneId}/v2":{body:"SceneCreateRequest"},
|
||
"POST /api/v1/alarm-rules":{body:"AlarmRuleEntity",entity:true},
|
||
"PUT /api/v1/alarm-rules/{ruleId}":{body:"AlarmRuleEntity",entity:true},
|
||
"GET /api/v1/alarms":{query:"AlarmQuery"},
|
||
"POST /api/v1/ai/command":{body:"AiCommandRequest"},
|
||
"POST /api/v1/ai/aliases":{body:"AiAliasRequest"},
|
||
"PUT /api/v1/ai/devices/{deviceId}/profile":{body:"AiDeviceProfileRequest"},
|
||
|
||
/* —— 中控屏(Panel) —— */
|
||
"POST /api/v1/panels/session":{body:"PanelSessionReq",resp:"PanelSession"},
|
||
"POST /api/v1/panels/session/refresh":{body:"PanelSessionRefreshReq",resp:"PanelSessionRefreshResp"},
|
||
"POST /api/v1/panels/{panelId}/heartbeat":{body:"PanelHeartbeat",resp:"PanelHeartbeatResp"},
|
||
"GET /api/v1/panels/{panelId}":{resp:"PanelInfo"},
|
||
"GET /api/v1/panels/{panelId}/snapshot":{query:"PanelSnapshotQuery",resp:"PanelSnapshot"},
|
||
"GET /api/v1/panels/{panelId}/configuration":{resp:"PanelConfig"},
|
||
"POST /api/v1/panels/{panelId}/devices/register":{body:"PanelDeviceRegister",resp:"PanelDeviceRegisterResult"},
|
||
"PUT /api/v1/panels/{panelId}/devices/{deviceId}/schema":{body:"PanelSchemaUpdate",resp:"PanelSchemaUpdateResult"},
|
||
"POST /api/v1/panels/{panelId}/reports":{body:"PanelReportBatch",resp:"PanelReportResult"},
|
||
"POST /api/v1/panels/{panelId}/commands/pull":{body:"PanelCmdPullReq",resp:"PanelCmdPullResp"},
|
||
"POST /api/v1/panels/{panelId}/commands/{commandId}/start":{body:"PanelCmdStartReq",resp:"PanelCmdStartResp"},
|
||
"POST /api/v1/panels/{panelId}/commands/{commandId}/report":{body:"PanelCmdReportReq",resp:"PanelCmdResult"},
|
||
"POST /api/v1/panels/{panelId}/diagnostics":{body:"PanelDiagnostic"},
|
||
"GET /api/v1/panels/{panelId}/events":{query:"PanelEventQuery",resp:"PanelEventsResp"},
|
||
"GET /api/v1/panels/{panelId}/ota-tasks/next":{resp:"PanelOtaNext"},
|
||
"POST /api/v1/panels/{panelId}/scene-runs/report":{body:"PanelSceneRunReport",resp:"PanelSceneRunView"},
|
||
"POST /api/v1/member/panels/bind":{body:"PanelBindReq",resp:"PanelBindResult"},
|
||
"GET /api/v1/member/panels":{resp:"PanelSummary"},
|
||
"GET /api/v1/member/panels/bindable":{resp:"PanelBindable"},
|
||
"POST /api/v1/admin/panels":{body:"PanelAdminCreate",resp:"PanelAdminCreateResult"},
|
||
"GET /api/v1/admin/panels":{resp:"PanelAdminItem"},
|
||
"PUT /api/v1/admin/panels/{panelId}/configuration":{body:"PanelConfigSave",resp:"PanelConfig"},
|
||
"GET /api/v1/admin/panels/{panelId}/configuration":{resp:"PanelConfig"},
|
||
"POST /api/v1/admin/panels/{panelId}/rotate":{resp:"PanelAdminRotateResult"},
|
||
"POST /api/v1/admin/panels/executor-reassign":{body:"PanelReassignReq",resp:"PanelReassignResult"}
|
||
};
|
||
|
||
/* ===== 页面与导航 ===== */
|
||
const PAGES=[
|
||
{id:"index",label:"概览首页",group:"入门"},
|
||
{id:"auth",label:"认证与账号",group:"账号体系"},
|
||
{id:"member",label:"会员与家庭",group:"账号体系"},
|
||
{id:"device",label:"设备管理",group:"设备接入"},
|
||
{id:"product",label:"产品与物模型",group:"设备接入"},
|
||
{id:"tuya",label:"涂鸦云集成",group:"设备接入"},
|
||
{id:"data",label:"数据采集",group:"数据服务"},
|
||
{id:"rule",label:"规则与场景",group:"智能联动"},
|
||
{id:"ai",label:"AI 智能",group:"智能联动"},
|
||
{id:"alarm",label:"告警服务",group:"智能联动"},
|
||
{id:"models",label:"数据模型",group:"参考"},
|
||
{id:"partner",label:"授权对接",group:"参考"},
|
||
{id:"panel",label:"中控屏接口",group:"设备接入"}
|
||
];
|
||
|
||
/* ===== 接口分组(按页面) ===== */
|
||
const SECTIONS={};
|
||
let _cur=null;
|
||
function sec(page,title,sub){_cur={title,sub,items:[]};(SECTIONS[page]=SECTIONS[page]||[]).push(_cur);}
|
||
function ep(m,p,d,a,params,ex){_cur.items.push({m,p,d,a,params,ex:ex||""});}
|
||
|
||
/* —— 认证与账号 auth —— */
|
||
sec("auth","认证与登录","后台 / H5 账号登录注册、获取当前用户信息");
|
||
ep("POST","/api/v1/auth/login","用户登录,返回 JWT",AUTH.none,"body: LoginRequest","login");
|
||
ep("POST","/api/v1/auth/register","注册普通(H5)用户",AUTH.none,"body: RegisterRequest");
|
||
ep("GET","/api/v1/auth/info","获取当前登录用户信息",AUTH.req,"Header: Authorization: Bearer <token>");
|
||
sec("auth","后台用户管理","用户 CRUD、启用禁用、重置密码、角色分配");
|
||
ep("GET","/api/v1/users","后台用户列表(分页)",AUTH.none,"query: UserQuery");
|
||
ep("GET","/api/v1/users/{id}","用户详情",AUTH.none,"path: id");
|
||
ep("POST","/api/v1/users","创建用户",AUTH.none,"body: UserRequest");
|
||
ep("PUT","/api/v1/users/{id}","更新用户",AUTH.none,"path: id; body: UserRequest");
|
||
ep("DELETE","/api/v1/users/{id}","删除用户",AUTH.none,"path: id");
|
||
ep("PUT","/api/v1/users/{id}/status","启用/禁用用户",AUTH.none,"path: id; query: status(0/1)");
|
||
ep("PUT","/api/v1/users/{id}/password","重置用户密码",AUTH.none,"path: id; body: PasswordChange");
|
||
ep("GET","/api/v1/users/{id}/roles","用户角色ID列表",AUTH.none,"path: id");
|
||
sec("auth","角色与权限","角色 CRUD、角色下拉");
|
||
ep("GET","/api/v1/roles","角色列表(分页)",AUTH.none,"query: RoleQuery");
|
||
ep("GET","/api/v1/roles/all","全部角色(下拉)",AUTH.none,"-");
|
||
ep("GET","/api/v1/roles/{id}","角色详情",AUTH.none,"path: id");
|
||
ep("POST","/api/v1/roles","创建角色",AUTH.none,"body: RoleRequest");
|
||
ep("PUT","/api/v1/roles/{id}","更新角色",AUTH.none,"path: id; body: RoleRequest");
|
||
ep("DELETE","/api/v1/roles/{id}","删除角色",AUTH.none,"path: id");
|
||
sec("auth","操作日志","后台操作审计分页");
|
||
ep("GET","/api/v1/logs","操作日志分页",AUTH.none,"query: LogQuery");
|
||
sec("auth","第三方平台配置","涂鸦 / 大模型等平台凭证配置");
|
||
ep("GET","/api/v1/admin/platforms/{platformCode}","获取第三方平台配置",AUTH.none,"path: platformCode(tuya/...)");
|
||
ep("PUT","/api/v1/admin/platforms/{platformCode}","更新平台配置",AUTH.none,"path: platformCode; body: PlatformConfigRequest");
|
||
ep("POST","/api/v1/admin/platforms/{platformCode}/toggle","启用/禁用平台",AUTH.none,"path: platformCode; query: enabled(bool)");
|
||
ep("POST","/api/v1/admin/platforms/{platformCode}/test","测试平台连接",AUTH.none,"path: platformCode");
|
||
|
||
/* —— 会员与家庭 member —— */
|
||
sec("member","会员端账号","移动端会员登录、注册、资料、改密");
|
||
ep("POST","/api/v1/auth/member/login","会员(移动端)登录",AUTH.none,"body: LoginRequest");
|
||
ep("POST","/api/v1/auth/member/refresh","刷新会员 Token",AUTH.none,"body: RefreshRequest");
|
||
ep("POST","/api/v1/auth/member/register","会员注册",AUTH.none,"body: MemberRegisterRequest");
|
||
ep("GET","/api/v1/auth/member/info","当前会员信息",AUTH.none,"query: memberId");
|
||
ep("PUT","/api/v1/auth/member/profile","更新会员资料",AUTH.none,"query: memberId; form: nickname/avatar/email(可空)");
|
||
ep("PUT","/api/v1/auth/member/password","修改会员密码",AUTH.none,"query: memberId; body: PasswordChange");
|
||
sec("member","会员管理(后台)","会员 CRUD、启用禁用、重置密码");
|
||
ep("GET","/api/v1/members","会员列表(分页)",AUTH.none,"query: MemberQuery");
|
||
ep("GET","/api/v1/members/{id}","会员详情",AUTH.none,"path: id");
|
||
ep("POST","/api/v1/members","创建会员",AUTH.none,"body: MemberRequest");
|
||
ep("PUT","/api/v1/members/{id}","更新会员",AUTH.none,"path: id; body: MemberRequest");
|
||
ep("DELETE","/api/v1/members/{id}","删除会员",AUTH.none,"path: id");
|
||
ep("PUT","/api/v1/members/{id}/status","启用/禁用会员",AUTH.none,"path: id; query: status(0/1)");
|
||
ep("PUT","/api/v1/members/{id}/password","重置会员密码",AUTH.none,"path: id; body: PasswordChange");
|
||
sec("member","家庭管理","家庭的创建、成员、分享、加入、离开");
|
||
ep("GET","/api/v1/homes","我的家庭列表",AUTH.none,"query: memberId");
|
||
ep("GET","/api/v1/homes/{id}","家庭详情",AUTH.none,"path: id; query: memberId");
|
||
ep("POST","/api/v1/homes","创建家庭",AUTH.none,"query: memberId; body: HomeRequest");
|
||
ep("PUT","/api/v1/homes/{id}","更新家庭",AUTH.none,"path: id; query: memberId; body: HomeRequest");
|
||
ep("DELETE","/api/v1/homes/{id}","删除家庭",AUTH.none,"path: id; query: memberId");
|
||
ep("POST","/api/v1/homes/{id}/share","生成家庭分享码",AUTH.none,"path: id; query: memberId");
|
||
ep("POST","/api/v1/homes/join","加入家庭",AUTH.none,"query: memberId; body: JoinHomeRequest");
|
||
ep("GET","/api/v1/homes/{id}/members","家庭成员列表",AUTH.none,"path: id; query: memberId");
|
||
ep("PUT","/api/v1/homes/{id}/members/{memberId}/role","修改成员角色",AUTH.none,"path: id,memberId; query: operatorId; body: MemberRoleRequest");
|
||
ep("DELETE","/api/v1/homes/{id}/members/{memberId}","移除成员",AUTH.none,"path: id,memberId; query: operatorId");
|
||
ep("POST","/api/v1/homes/{id}/leave","离开家庭",AUTH.none,"path: id; query: memberId");
|
||
sec("member","房间管理","家庭下房间增删改");
|
||
ep("GET","/api/v1/homes/{homeId}/rooms","房间列表",AUTH.none,"path: homeId; query: memberId");
|
||
ep("POST","/api/v1/homes/{homeId}/rooms","添加房间",AUTH.none,"path: homeId; query: memberId; body: RoomRequest");
|
||
ep("DELETE","/api/v1/homes/{homeId}/rooms/{roomId}","删除房间",AUTH.none,"path: homeId,roomId; query: memberId");
|
||
ep("PUT","/api/v1/homes/{homeId}/rooms/{roomId}","重命名房间",AUTH.none,"path: homeId,roomId; query: memberId; body: RoomRequest");
|
||
|
||
/* —— 设备管理 device —— */
|
||
sec("device","设备管理","设备 CRUD、连接/断开、控制指令、控制模型、父网关、操控动态");
|
||
ep("GET","/api/v1/devices","获取设备列表",AUTH.opt,"query: DeviceQuery","devlist");
|
||
ep("GET","/api/v1/devices/{deviceId}","设备详情",AUTH.none,"path: deviceId");
|
||
ep("POST","/api/v1/devices","创建设备",AUTH.none,"body: Device");
|
||
ep("PUT","/api/v1/devices/{id}","更新设备",AUTH.none,"path: id; body: Device");
|
||
ep("DELETE","/api/v1/devices/{id}","删除设备",AUTH.none,"path: id");
|
||
ep("GET","/api/v1/devices/{deviceId}/control-schema","设备标准控制模型",AUTH.none,"path: deviceId");
|
||
ep("GET","/api/v1/devices/{deviceId}/data","读取设备数据",AUTH.none,"path: deviceId");
|
||
ep("POST","/api/v1/devices/{deviceId}/connect","连接设备",AUTH.none,"path: deviceId; body: DeviceConnectRequest");
|
||
ep("POST","/api/v1/devices/{deviceId}/disconnect","断开设备",AUTH.none,"path: deviceId");
|
||
ep("POST","/api/v1/devices/{deviceId}/commands","发送控制指令",AUTH.none,"path: deviceId; body: DeviceCommand","cmd");
|
||
ep("POST","/api/v1/devices/{deviceId}/parent","绑定/解绑父网关",AUTH.none,"path: deviceId; body: {parentDeviceId} (空=解绑)");
|
||
ep("GET","/api/v1/devices/activities/recent","近期操控动态(首页)",AUTH.opt,"query: homeId, limit(默认5)");
|
||
ep("GET","/api/v1/devices/activities","动态分页查询",AUTH.opt,"query: homeId, deviceId, pageNum, pageSize");
|
||
ep("POST","/api/v1/devices/activities/{deviceId}/activity","上报设备控制动态",AUTH.none,"path: deviceId; body: DeviceActivityReport");
|
||
sec("device","设备分组","分组树 / 分页 / CRUD / 设备计数");
|
||
ep("GET","/api/v1/device-groups/tree","分组树形列表",AUTH.none,"-");
|
||
ep("GET","/api/v1/device-groups","分组分页",AUTH.none,"query: GroupQuery");
|
||
ep("POST","/api/v1/device-groups","创建分组",AUTH.none,"body: DeviceGroup");
|
||
ep("PUT","/api/v1/device-groups/{id}","更新分组",AUTH.none,"path: id; body: DeviceGroup");
|
||
ep("DELETE","/api/v1/device-groups/{id}","删除分组",AUTH.none,"path: id");
|
||
ep("GET","/api/v1/device-groups/{id}/devices/count","分组下设备数量",AUTH.none,"path: id");
|
||
|
||
/* —— 产品与物模型 product —— */
|
||
sec("product","产品管理","产品 CRUD、按平台解析、存量归类");
|
||
ep("GET","/api/v1/products","产品列表",AUTH.none,"query: ProductQuery");
|
||
ep("GET","/api/v1/products/{id}","产品详情",AUTH.none,"path: id");
|
||
ep("POST","/api/v1/products","创建产品",AUTH.none,"body: Product");
|
||
ep("PUT","/api/v1/products/{id}","更新产品",AUTH.none,"path: id; body: Product");
|
||
ep("DELETE","/api/v1/products/{id}","删除产品",AUTH.none,"path: id");
|
||
ep("GET","/api/v1/products/resolve","按平台信息解析产品",AUTH.none,"query: platform,platformProductId,model,manufacturer,category,name");
|
||
ep("POST","/api/v1/products/reconcile","存量设备按型号归类",AUTH.none,"-");
|
||
ep("POST","/api/v1/products/reconcile-spec","补全省物模型",AUTH.none,"query: memberId");
|
||
sec("product","物模型(TSL)","产品标准控制模型、属性/事件/服务维护");
|
||
ep("GET","/api/v1/products/{id}/spec","物模型(属性/事件/服务)",AUTH.none,"path: id");
|
||
ep("GET","/api/v1/products/{id}/control-schema","产品标准控制模型",AUTH.none,"path: id");
|
||
ep("PUT","/api/v1/products/{id}/spec","整体保存物模型",AUTH.none,"path: id; body: ProductSpec");
|
||
ep("POST","/api/v1/products/{id}/properties","新增/更新属性(DP)",AUTH.none,"path: id; body: ProductProperty");
|
||
ep("POST","/api/v1/products/{id}/events","新增/更新事件",AUTH.none,"path: id; body: ProductEvent");
|
||
ep("POST","/api/v1/products/{id}/actions","新增/更新服务",AUTH.none,"path: id; body: ProductAction");
|
||
ep("DELETE","/api/v1/spec/{type}/{id}","删除物模型项",AUTH.none,"path: type(property/event/action), id");
|
||
sec("product","固件与 OTA","固件注册/发布、OTA 任务、回滚、统计");
|
||
ep("POST","/api/v1/products/{id}/firmware","注册固件(草稿)",AUTH.none,"path: id; body: Firmware");
|
||
ep("GET","/api/v1/products/{id}/firmwares","产品固件分页",AUTH.none,"path: id; query: page,size");
|
||
ep("GET","/api/v1/products/{id}/firmware/latest","最新已发布固件",AUTH.none,"path: id");
|
||
ep("PUT","/api/v1/firmwares/{id}/publish","发布固件",AUTH.none,"path: id");
|
||
ep("POST","/api/v1/products/{id}/ota","创建OTA任务",AUTH.none,"path: id; body: OtaTaskCreateRequest");
|
||
ep("GET","/api/v1/ota-tasks","OTA任务分页",AUTH.none,"query: page,size,productId");
|
||
ep("GET","/api/v1/ota-tasks/{id}","OTA任务详情",AUTH.none,"path: id");
|
||
ep("POST","/api/v1/ota-tasks/{id}/report","设备上报升级结果",AUTH.none,"path: id; body: OtaReportRequest");
|
||
ep("POST","/api/v1/ota-tasks/{id}/cancel","暂停/取消任务",AUTH.none,"path: id");
|
||
ep("POST","/api/v1/products/{id}/ota/rollback","版本回滚",AUTH.none,"path: id; query: targetVersion(可空)");
|
||
ep("GET","/api/v1/ota-tasks/stats","OTA统计看板",AUTH.none,"query: productId(可空)");
|
||
|
||
/* —— 涂鸦云集成 tuya —— */
|
||
sec("tuya","账号绑定","OAuth 授权、Token/原生/短信绑定、解绑");
|
||
ep("GET","/api/v1/tuya/auth/url","获取涂鸦 OAuth 授权URL",AUTH.req,"Header: Bearer <token>");
|
||
ep("GET","/api/v1/tuya/auth/status","涂鸦绑定状态",AUTH.req,"Header: Bearer <token>");
|
||
ep("POST","/api/v1/tuya/auth/callback","处理OAuth回调",AUTH.req,"body: OauthCallback");
|
||
ep("GET","/api/v1/tuya/auth/callback-page","OAuth回调页(返回HTML)",AUTH.none,"query: code, state");
|
||
ep("POST","/api/v1/tuya/auth/bind-token","Token绑定涂鸦账号",AUTH.req,"body: BindTokenRequest");
|
||
ep("POST","/api/v1/tuya/auth/auto-bind","自动绑定涂鸦账号",AUTH.req,"Header: Bearer <token>");
|
||
ep("POST","/api/v1/tuya/auth/bind-native","APP原生SDK绑定",AUTH.req,"body: TuyaNativeBindRequest");
|
||
ep("POST","/api/v1/tuya/auth/unbind","解绑涂鸦账号",AUTH.req,"Header: Bearer <token>");
|
||
ep("POST","/api/v1/tuya/auth/sms/send","发送绑定验证码",AUTH.none,"body: SmsSendRequest");
|
||
ep("POST","/api/v1/tuya/auth/sms/bind","验证码绑定涂鸦账号",AUTH.req,"body: SmsBindRequest");
|
||
sec("tuya","设备与场景","涂鸦家庭/设备/场景同步与控制");
|
||
ep("GET","/api/v1/tuya/homes","涂鸦家庭列表",AUTH.req,"Header: Bearer <token>");
|
||
ep("GET","/api/v1/tuya/devices","涂鸦设备列表",AUTH.req,"query: homeId(可空)");
|
||
ep("POST","/api/v1/tuya/devices/bind","绑定涂鸦设备到平台",AUTH.req,"body: TuyaBindRequest");
|
||
ep("POST","/api/v1/tuya/devices/status","设备状态实时同步",AUTH.none,"body: DeviceStatusSyncRequest");
|
||
ep("POST","/api/v1/tuya/devices/{deviceId}/commands","发送涂鸦控制指令",AUTH.req,"path: deviceId; body: 涂鸦指令数组 List<{code,value}>");
|
||
ep("GET","/api/v1/tuya/scenes","涂鸦场景列表",AUTH.req,"query: homeId(可空)");
|
||
ep("POST","/api/v1/tuya/scenes/{sceneId}/trigger","执行涂鸦场景",AUTH.req,"path: sceneId");
|
||
ep("GET","/api/v1/tuya/devices/{deviceId}/status","涂鸦设备状态",AUTH.req,"path: deviceId");
|
||
ep("GET","/api/v1/tuya/devices/{deviceId}/cloud-status","涂鸦云端在线状态",AUTH.req,"path: deviceId");
|
||
ep("GET","/api/v1/tuya/subscribe/status","消息订阅状态",AUTH.none,"-");
|
||
sec("tuya","AI 指令码","设备 AI 指令码档案探测");
|
||
ep("GET","/api/v1/tuya/devices/{deviceId}/ai-profile","设备AI指令码档案",AUTH.req,"path: deviceId");
|
||
ep("POST","/api/v1/tuya/devices/{deviceId}/ai-profile/detect","探测AI指令码档案",AUTH.req,"path: deviceId; query: force");
|
||
ep("POST","/api/v1/tuya/ai-profile/detect-all","批量探测AI指令码",AUTH.req,"Header: Bearer <token>");
|
||
sec("tuya","设备与配网","重命名、Wi-Fi/Matter/Zigbee 配网 Token");
|
||
ep("POST","/api/v1/tuya/devices/{deviceId}/rename","重命名涂鸦设备",AUTH.req,"path: deviceId; body: TuyaRename");
|
||
ep("POST","/api/v1/tuya/config/wifi-token","Wi-Fi配网Token",AUTH.req,"query: homeId(默认0)");
|
||
ep("POST","/api/v1/tuya/config/matter-token","Matter配网Token",AUTH.req,"query: homeId(默认0)");
|
||
ep("POST","/api/v1/tuya/config/zigbee-token","Zigbee配网Token",AUTH.req,"query: homeId(默认0)");
|
||
sec("tuya","天气","当前天气查询");
|
||
ep("GET","/api/v1/weather","获取当前天气",AUTH.none,"query: lat(默认31.23), lon(默认121.47)");
|
||
|
||
/* —— 数据采集 data —— */
|
||
sec("data","时序数据","设备数据点上报、历史查询、聚合统计、最新值");
|
||
ep("POST","/api/v1/data/devices/{deviceId}/data","接收设备上报数据",AUTH.none,"path: deviceId; body: List<DeviceDataPoint>","ingest");
|
||
ep("GET","/api/v1/data/devices/{deviceId}/history","查询历史数据",AUTH.none,"path: deviceId; query: DataHistoryQuery");
|
||
ep("GET","/api/v1/data/devices/{deviceId}/stats","数据聚合统计",AUTH.none,"path: deviceId; query: DataStatsQuery");
|
||
ep("GET","/api/v1/data/devices/{deviceId}/latest","获取最新数据",AUTH.none,"path: deviceId");
|
||
|
||
/* —— 规则与场景 rule —— */
|
||
sec("rule","规则引擎","规则 CRUD、启停、手动执行、设备触发");
|
||
ep("GET","/api/v1/rules","规则列表",AUTH.opt,"query: RuleQuery","rulelist");
|
||
ep("GET","/api/v1/rules/{ruleId}","规则详情",AUTH.none,"path: ruleId");
|
||
ep("POST","/api/v1/rules","创建规则",AUTH.opt,"body: RuleEntity");
|
||
ep("PUT","/api/v1/rules/{ruleId}","更新规则",AUTH.none,"path: ruleId; body: RuleEntity");
|
||
ep("DELETE","/api/v1/rules/{ruleId}","删除规则",AUTH.none,"path: ruleId");
|
||
ep("POST","/api/v1/rules/{ruleId}/enable","启用规则",AUTH.none,"path: ruleId");
|
||
ep("POST","/api/v1/rules/{ruleId}/disable","禁用规则",AUTH.none,"path: ruleId");
|
||
ep("POST","/api/v1/rules/{ruleId}/execute","手动执行规则",AUTH.none,"path: ruleId; body: Object(触发数据)");
|
||
ep("POST","/api/v1/rules/trigger/{deviceId}","触发设备数据变化",AUTH.none,"path: deviceId; body: Object");
|
||
sec("rule","场景联动","场景 CRUD、启停、一键执行、定时(cron)、结构化场景");
|
||
ep("GET","/api/v1/scenes","场景分页列表",AUTH.opt,"query: SceneQuery");
|
||
ep("GET","/api/v1/scenes/{sceneId}","场景详情",AUTH.none,"path: sceneId");
|
||
ep("POST","/api/v1/scenes","创建场景",AUTH.opt,"body: SceneEntity");
|
||
ep("PUT","/api/v1/scenes/{sceneId}","更新场景",AUTH.none,"path: sceneId; body: SceneEntity");
|
||
ep("DELETE","/api/v1/scenes/{sceneId}","删除场景",AUTH.none,"path: sceneId");
|
||
ep("POST","/api/v1/scenes/{sceneId}/enable","启用场景",AUTH.none,"path: sceneId");
|
||
ep("POST","/api/v1/scenes/{sceneId}/disable","禁用场景",AUTH.none,"path: sceneId");
|
||
ep("POST","/api/v1/scenes/{sceneId}/execute","一键执行场景",AUTH.none,"path: sceneId");
|
||
ep("PUT","/api/v1/scenes/{sceneId}/cron","设置定时执行(cron)",AUTH.none,"path: sceneId; body: {cron} (六段式 0 0 * * * ?)");
|
||
ep("POST","/api/v1/scenes/v2","创建结构化场景",AUTH.opt,"body: SceneCreateRequest","scene");
|
||
ep("PUT","/api/v1/scenes/{sceneId}/v2","更新结构化场景",AUTH.opt,"path: sceneId; body: SceneCreateRequest");
|
||
ep("GET","/api/v1/scenes/{sceneId}/v2","结构化场景详情",AUTH.none,"path: sceneId");
|
||
|
||
/* —— AI 智能 ai —— */
|
||
sec("ai","语音控制","自然语言指令、设备指令码、AI 日志");
|
||
ep("POST","/api/v1/ai/command","执行自然语言控制指令",AUTH.req,"body: AiCommandRequest");
|
||
ep("GET","/api/v1/ai/devices","可AI控制设备清单",AUTH.req,"Header: Bearer <token>");
|
||
ep("GET","/api/v1/ai/devices/{deviceId}/profile","查询设备控制指令码",AUTH.req,"path: deviceId");
|
||
ep("PUT","/api/v1/ai/devices/{deviceId}/profile","校准设备控制指令码",AUTH.req,"path: deviceId; body: AiDeviceProfileRequest");
|
||
ep("GET","/api/v1/ai/logs","最近AI指令日志",AUTH.req,"query: limit(默认20)");
|
||
sec("ai","别名管理","设备/场景/房间别名的增删查、语音鉴权");
|
||
ep("GET","/api/v1/ai/aliases","查询设备/场景/房间别名",AUTH.req,"query: targetType(device/scene/room)");
|
||
ep("POST","/api/v1/ai/aliases","新增/更新别名",AUTH.req,"body: AiAliasRequest");
|
||
ep("DELETE","/api/v1/ai/aliases/{aliasId}","删除别名",AUTH.req,"path: aliasId");
|
||
ep("GET","/api/v1/ai/speech/auth","讯飞语音听写鉴权地址",AUTH.req,"Header: Bearer <token>");
|
||
|
||
/* —— 告警服务 alarm —— */
|
||
sec("alarm","告警","告警列表/详情/确认/解决/统计");
|
||
ep("GET","/api/v1/alarms","告警列表",AUTH.opt,"query: AlarmQuery");
|
||
ep("GET","/api/v1/alarms/{alarmId}","告警详情",AUTH.none,"path: alarmId");
|
||
ep("POST","/api/v1/alarms/{alarmId}/confirm","确认告警",AUTH.none,"path: alarmId; body: {userId}");
|
||
ep("POST","/api/v1/alarms/{alarmId}/resolve","解决告警",AUTH.none,"path: alarmId; body: {userId}");
|
||
ep("GET","/api/v1/alarms/stats","告警统计",AUTH.none,"-");
|
||
sec("alarm","告警规则","告警规则 CRUD");
|
||
ep("GET","/api/v1/alarm-rules","告警规则列表",AUTH.none,"query: pageNum,pageSize");
|
||
ep("POST","/api/v1/alarm-rules","创建告警规则",AUTH.none,"body: AlarmRuleEntity");
|
||
ep("PUT","/api/v1/alarm-rules/{ruleId}","更新告警规则",AUTH.none,"path: ruleId; body: AlarmRuleEntity");
|
||
ep("DELETE","/api/v1/alarm-rules/{ruleId}","删除告警规则",AUTH.none,"path: ruleId");
|
||
|
||
/* —— 中控屏接口 panel —— */
|
||
sec("panel","屏端 · 身份与会话","中控屏(面板)专用身份层,与会员/后台隔离;凭证建会话后持 panel JWT 调用其余接口");
|
||
ep("POST","/api/v1/panels/session","建立会话:终端初始化凭证换受限身份",AUTH.none,"Header: Authorization: PanelBootstrap <bootstrapSecret>; body: PanelSessionReq");
|
||
ep("POST","/api/v1/panels/session/refresh","刷新会话:轮换刷新凭证",AUTH.none,"body: PanelSessionRefreshReq");
|
||
ep("POST","/api/v1/panels/{panelId}/heartbeat","屏心跳:续在线租期",AUTH.req,"body: PanelHeartbeat");
|
||
ep("GET","/api/v1/panels/{panelId}","屏信息",AUTH.req,"path: panelId");
|
||
sec("panel","屏端 · 家庭快照","首屏拉取家庭/房间/设备完整视图(设备支持游标分页)");
|
||
ep("GET","/api/v1/panels/{panelId}/snapshot","家庭快照(含 rooms/devices/stateEpoch)",AUTH.req,"query: PanelSnapshotQuery");
|
||
sec("panel","屏端 · 配置","运营配置拉取(带 checksum,屏端比对是否变更)");
|
||
ep("GET","/api/v1/panels/{panelId}/configuration","拉取屏运营配置",AUTH.req,"path: panelId");
|
||
sec("panel","屏端 · 设备登记与物模型","本地设备登记、上报标准控制模型");
|
||
ep("POST","/api/v1/panels/{panelId}/devices/register","登记本地设备",AUTH.req,"body: PanelDeviceRegister");
|
||
ep("PUT","/api/v1/panels/{panelId}/devices/{deviceId}/schema","更新设备标准控制模型",AUTH.req,"path: panelId,deviceId; body: PanelSchemaUpdate");
|
||
sec("panel","屏端 · 状态上报","批量设备状态上报(按 epoch/sequence 去旧)");
|
||
ep("POST","/api/v1/panels/{panelId}/reports","批量设备状态上报",AUTH.req,"body: PanelReportBatch");
|
||
sec("panel","屏端 · 命令执行","拉取 → 申请 → 回报 三段式命令流");
|
||
ep("POST","/api/v1/panels/{panelId}/commands/pull","拉取待执行命令(QUEUED→DELIVERED)",AUTH.req,"body: PanelCmdPullReq");
|
||
ep("POST","/api/v1/panels/{panelId}/commands/{commandId}/start","申请执行(DELIVERED→EXECUTING)",AUTH.req,"path: panelId,commandId; body: PanelCmdStartReq");
|
||
ep("POST","/api/v1/panels/{panelId}/commands/{commandId}/report","命令执行回报",AUTH.req,"path: panelId,commandId; body: PanelCmdReportReq");
|
||
sec("panel","屏端 · 诊断与 OTA","诊断上报、屏端 OTA 升级任务拉取");
|
||
ep("POST","/api/v1/panels/{panelId}/diagnostics","上报诊断事件(network/mqtt/ota/command/config)",AUTH.req,"body: PanelDiagnostic");
|
||
ep("GET","/api/v1/panels/{panelId}/ota-tasks/next","拉取下一个待升级任务",AUTH.req,"path: panelId");
|
||
ep("POST","/api/v1/panels/{panelId}/scene-runs/report","屏上报本地设备场景执行结果",AUTH.req,"body: PanelSceneRunReport");
|
||
sec("panel","屏端 · 实时事件推送","长轮询 + SSE 双通道(非 WebSocket);屏端二选一,~20s 心跳");
|
||
ep("GET","/api/v1/panels/{panelId}/events","长轮询家庭增量事件(阻塞~20s)",AUTH.req,"query: PanelEventQuery");
|
||
ep("GET","/api/v1/panels/{panelId}/events/stream","SSE 增量事件流(text/event-stream,非 Result 包装)",AUTH.req,"query: cursor");
|
||
sec("panel","会员侧 · 自助绑定(APP 扫码)","与屏身份层隔离;memberId 取自会员 JWT 不信任参数");
|
||
ep("GET","/api/v1/member/panels/bindable","可绑定性预检(是否已预置/启用/已绑)",AUTH.req,"query: serialNumber");
|
||
ep("POST","/api/v1/member/panels/bind","扫码绑定:预置屏绑到自己的家庭",AUTH.req,"body: PanelBindReq");
|
||
ep("GET","/api/v1/member/panels","我的屏列表",AUTH.req,"-");
|
||
ep("POST","/api/v1/member/panels/{panelId}/unbind","解绑(仅绑定者本人,释放家庭)",AUTH.req,"path: panelId");
|
||
sec("panel","后台 · 屏终端管理","ROLE_ADMIN;预置屏终端、启用禁用、轮换凭证、配置、归属交接");
|
||
ep("POST","/api/v1/admin/panels","预置屏终端(一次性初始化凭证)",AUTH.req,"body: PanelAdminCreate");
|
||
ep("GET","/api/v1/admin/panels","屏终端列表",AUTH.req,"-");
|
||
ep("PUT","/api/v1/admin/panels/{panelId}/status","启用/禁用屏终端",AUTH.req,"path: panelId; query: status(0/1)");
|
||
ep("POST","/api/v1/admin/panels/{panelId}/rotate","轮换终端初始化凭证",AUTH.req,"path: panelId");
|
||
ep("GET","/api/v1/admin/panels/{panelId}/configuration","查看屏配置",AUTH.req,"path: panelId");
|
||
ep("PUT","/api/v1/admin/panels/{panelId}/configuration","保存屏配置",AUTH.req,"path: panelId; body: PanelConfigSave");
|
||
ep("POST","/api/v1/admin/panels/executor-reassign","多屏归属交接(routeRevision 递增)",AUTH.req,"body: PanelReassignReq");
|
||
|
||
/* ===== 渲染工具 ===== */
|
||
function esc(s){return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');}
|
||
function jsonHTML(obj){return esc(JSON.stringify(obj,null,2));}
|
||
function fieldTable(fields){
|
||
let t='<table class="ft"><thead><tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr></thead><tbody>';
|
||
fields.forEach(f=>{
|
||
const rc=f[2]==='是'?'rq':(f[2]==='否'?'op':'');
|
||
t+=`<tr><td><code>${esc(f[0])}</code></td><td><span class="ty">${esc(f[1])}</span></td><td class="${rc}">${esc(f[2])}</td><td>${esc(f[3])}</td></tr>`;
|
||
});
|
||
return t+'</tbody></table>';
|
||
}
|
||
function modelCard(key){
|
||
const m=MODELS[key]; if(!m) return '';
|
||
let h=`<div class="model" id="model-${key}"><h3>${esc(m.title)}</h3>`;
|
||
const tags=[];
|
||
if(m.entity) tags.push('<span class="q">实体(entity)</span>');
|
||
if(m.query) tags.push('<span class="q">查询参数</span>');
|
||
if(m.resp) tags.push('<span class="q">响应结构</span>');
|
||
h+=`<div class="mtag">${tags.join(' ')}</div>`;
|
||
h+=fieldTable(m.fields);
|
||
if(m.nested&&m.nested.length){h+='<div class="resp-note">嵌套对象字段:</div>'+fieldTable(m.nested);}
|
||
if(m.entity){h+=`<div class="baseblk"><b>+ 公共基类字段(所有 entity 响应均含)</b>${fieldTable(BASE_FIELDS)}</div>`;}
|
||
if(m.example!==undefined){h+=`<details class="exjson"><summary>JSON 示例</summary><pre>${jsonHTML(m.example)}</pre></details>`;}
|
||
if(m.sample){h+=`<div class="resp-note">示例查询串:<code class="inl">?${esc(m.sample)}</code></div>`;}
|
||
return h+'</div>';
|
||
}
|
||
function epHTML(e){
|
||
const map=EPMAP[e.m+' '+e.p]||{};
|
||
let extra='';
|
||
if(map.body){
|
||
const mk=map.body;
|
||
extra+=`<div class="mlink">📦 请求体字段 → <a href="models.html#model-${mk}">${esc(mk)}</a>${map.entity?'(实体,响应含公共基类字段)':''}</div>`;
|
||
let j=MODELS[mk]?jsonHTML(MODELS[mk].example):'';
|
||
if(map.isArray) j='[\n '+j.replace(/\n/g,'\n ')+'\n]';
|
||
extra+=`<details class="ex"><summary>查看请求体 JSON 示例${map.isArray?'(数组)':''}</summary><pre>${j}</pre></details>`;
|
||
}
|
||
if(map.query){
|
||
const qk=map.query;
|
||
extra+=`<div class="mlink">🔍 查询参数 → <a href="models.html#model-${qk}">${esc(qk)}</a></div>`;
|
||
extra+=`<details class="ex"><summary>查看查询参数表</summary>${fieldTable(MODELS[qk].fields)}${MODELS[qk].sample?('<div class="resp-note">示例:<code class="inl">?'+esc(MODELS[qk].sample)+'</code></div>'):''}</details>`;
|
||
}
|
||
if(map.resp){extra+=`<div class="mlink">📤 响应结构 → <a href="models.html#model-${map.resp}">${esc(map.resp)}</a></div>`;}
|
||
const exBlock=e.ex?`<details class="ex"><summary>查看请求示例(curl)</summary><pre data-ex="${e.ex}"></pre></details>`:'';
|
||
const st=(e.m+' '+e.p+' '+e.d+' '+e.params+' '+e.a+' '+(map.body||'')+' '+(map.query||'')).toLowerCase();
|
||
const tc=e.a===AUTH.req?'t-req':e.a===AUTH.opt?'t-opt':'t-none';
|
||
return `<div class="ep" data-text="${esc(st)}"><div class="row"><span class="badge m-${e.m}">${e.m}</span><span class="path">${esc(e.p)}</span><span class="tag ${tc}">${e.a}</span></div><div class="desc">${esc(e.d)}</div><div class="params"><b>参数:</b>${esc(e.params)}</div>${extra}${exBlock}</div>`;
|
||
}
|
||
function renderSections(pageId){
|
||
const secs=SECTIONS[pageId]||[];
|
||
let h='';
|
||
if(secs.length>1){h+='<div class="toc">';secs.forEach((s,i)=>{h+=`<a href="#g-${pageId}-${i}">${esc(s.title)}</a>`;});h+='</div>';}
|
||
secs.forEach((s,i)=>{
|
||
h+=`<section class="sec" id="g-${pageId}-${i}"><h2 class="h2">${esc(s.title)}</h2><div class="sub">${esc(s.sub)}</div>`;
|
||
s.items.forEach(e=>{h+=epHTML(e);});
|
||
h+='</section>';
|
||
});
|
||
return h;
|
||
}
|
||
function renderModels(){
|
||
let h='<div class="toc">';Object.keys(MODELS).forEach(k=>{h+=`<a href="#model-${k}">${esc(k)}</a>`;});h+='</div>';
|
||
Object.keys(MODELS).forEach(k=>{h+=modelCard(k);});
|
||
h+=`<div class="baseblk" style="margin-top:10px"><b>统一响应包装 Result<T></b>:<code class="inl">{ "code": 0, "message": "success", "data": T }</code>。分页接口 data 即 <a href="#model-PageResult">PageResult</a>;单条增改查 data 为对应 entity(如 <a href="#model-Device">Device</a>)。</div>`;
|
||
return h;
|
||
}
|
||
function totalEndpoints(){let n=0;for(const k in SECTIONS)n+=SECTIONS[k].reduce((a,s)=>a+s.items.length,0);return n;}
|
||
function renderOverview(){
|
||
let h=`<div class="ov-hero"><h1>${PRODUCT_NAME} 平台 · REST API 文档</h1><p>统一入口 <code class="inl">/api/v1</code> · 前后端分离 · JWT Bearer 鉴权 · 含字段级类型与 JSON 示例<br>共收录 ${totalEndpoints()} 个接口 / ${Object.keys(MODELS).length} 个数据模型,按业务域拆分多页。</p></div>`;
|
||
h+='<div class="ov-cards">';
|
||
PAGES.forEach(p=>{
|
||
if(p.id==='index')return;
|
||
const cnt=p.id==='models'?Object.keys(MODELS).length:(SECTIONS[p.id]?SECTIONS[p.id].reduce((a,s)=>a+s.items.length,0):0);
|
||
const descMap={auth:'后台登录注册、用户/角色/日志/平台配置',member:'会员账号、家庭与房间',device:'设备 CRUD、连接、控制指令、分组',product:'产品、物模型 TSL、固件与 OTA',tuya:'涂鸦 OAuth 绑定、设备/场景控制、配网',data:'时序数据上报、历史、聚合',rule:'规则引擎与场景联动(含 cron)',ai:'自然语言控制与语音',alarm:'告警与告警规则',models:'全部请求/响应字段与 JSON 示例',partner:'开放平台授权对接:应用凭证 ak/sk 换取 JWT,后台应用管理、接入指南与接口说明',panel:'中控屏(面板)身份、会话、快照、命令、实时事件推送、OTA 与会员自助绑定'};
|
||
h+=`<a class="ov-card" href="${p.id}.html"><h3>${esc(p.label)}</h3><p>${descMap[p.id]||''}</p><span class="cnt">${cnt} 项 →</span></a>`;
|
||
});
|
||
h+='</div>';
|
||
h+=`<div class="ov-note"><b>鉴权说明:</b>需登录的接口在请求头携带 <code class="inl">Authorization: Bearer <token></code>。后台管理类代码层不强制校验,由网关/反代保证;标注 <b style="color:var(--req)">强制</b> 的接口需会员 Token(无效即报错);<b style="color:var(--opt)">可选</b> 仅用于数据隔离(会员看自己的、admin 看全部);<b style="color:var(--none)">无</b> 不校验。<br><b>统一响应:</b><code class="inl">{"code":0,"message":"success","data":{...}}</code>;分页 data 为 <code class="inl">{records,total,pages,current,size}</code>。所有 <code class="inl">entity</code> 响应额外含公共基类字段(见「数据模型」章末)。</div>`;
|
||
return h;
|
||
}
|
||
function getBase(){try{return localStorage.getItem('apiBase')||DEFAULT_BASE;}catch(e){return DEFAULT_BASE;}}
|
||
function setBase(v){try{localStorage.setItem('apiBase',v);}catch(e){}renderExamples(v);}
|
||
function renderExamples(b){document.querySelectorAll('#content .ex pre[data-ex]').forEach(pre=>{const k=pre.dataset.ex;if(EXAMPLES[k])pre.innerHTML=EXAMPLES[k](b);});}
|
||
function filterPage(){const q=(document.getElementById('q').value||'').trim().toLowerCase();let vis=0;document.querySelectorAll('#content .ep').forEach(el=>{const ok=!q||el.dataset.text.indexOf(q)>=0;el.classList.toggle('hidden',!ok);if(ok)vis++;});const em=document.getElementById('empty');if(em)em.style.display=vis?'none':'block';}
|
||
function renderSidebar(cur){
|
||
let h=`<div class="brand"><h2>${PRODUCT_NAME} · API</h2><p>REST 接口文档 v1</p></div>`;
|
||
let last=null;
|
||
PAGES.forEach(p=>{if(p.group!==last){h+=`<div class="grp">${esc(p.group)}</div>`;last=p.group;}h+=`<a class="nav${p.id===cur?' active':''}" href="${p.id}.html">${esc(p.label)}</a>`;});
|
||
h+='<div class="ft">由代码扫描自动汇总<br>若有出入以服务端代码为准</div>';
|
||
document.getElementById('sidebar').innerHTML=h;
|
||
}
|
||
function renderTopbar(pageId){
|
||
const p=PAGES.find(x=>x.id===pageId);
|
||
let h=`<div class="ptitle">${esc(p.label)}</div>`;
|
||
h+=`<input class="search" id="q" placeholder="🔍 搜索本页接口 / 模型" oninput="filterPage()">`;
|
||
h+=`<div class="basebox">Base URL <input id="base" value="${esc(getBase())}" oninput="setBase(this.value)"></div>`;
|
||
document.getElementById('topbar').innerHTML=h;
|
||
}
|
||
function renderPartner(){
|
||
const B=getBase();
|
||
return `
|
||
<h1>开放平台 · 授权对接(接入指南)</h1>
|
||
<p class="ov-note">本平台采用 <b>应用凭证(ak/sk) + 换取 JWT</b> 的授权模式,与会员 JWT 同源校验。合作伙伴先在后台创建应用拿到 <code class="inl">AppKey / AppSecret</code>,再用 Secret 换取短期 <code class="inl">Access Token</code>,最后以 <code class="inl">Bearer</code> 方式调用业务接口。</p>
|
||
|
||
<h2>一、名词说明</h2>
|
||
<ul>
|
||
<li><b>AppKey</b>:公开标识,形如 <code class="inl">ak-xxxx</code>,可安全出现在前端/配置中。</li>
|
||
<li><b>AppSecret</b>:密钥,形如 <code class="inl">sk-xxxx</code>,<b>必须保密</b>;服务端仅存储其 BCrypt 哈希,创建/轮换时明文仅展示一次。</li>
|
||
<li><b>Access Token</b>:换取得到的 JWT(默认有效期 7 天),用于调用业务接口。</li>
|
||
</ul>
|
||
|
||
<h2>二、对接流程</h2>
|
||
<ol>
|
||
<li>后台 <b>系统管理 → 应用管理 → 新建应用</b>,生成 ak/sk(Secret 仅弹窗展示一次,请立即保存)。</li>
|
||
<li>调用 <code class="inl">POST /api/v1/auth/token</code>,用 ak/sk 换取 Access Token。</li>
|
||
<li>业务接口请求头带上 <code class="inl">Authorization: Bearer <accessToken></code>。</li>
|
||
<li>密钥泄露或人员变动时,在后台 <b>轮换密钥</b>(旧 Secret 立即失效)或 <b>禁用</b> 应用。</li>
|
||
</ol>
|
||
|
||
<h2>三、换取 Token</h2>
|
||
<p>支持两种传参方式(JSON 或 OAuth2 client_credentials 表单):</p>
|
||
<details class="ex"><summary>查看 curl 示例(JSON)</summary><pre>curl -X POST '${B}/api/v1/auth/token' \\
|
||
-H 'Content-Type: application/json' \\
|
||
-d '{"appKey":"ak-xxxxxxxxxxxx","appSecret":"sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}'</pre></details>
|
||
<details class="ex"><summary>查看 curl 示例(client_credentials 表单)</summary><pre>curl -X POST '${B}/api/v1/auth/token' \\
|
||
-H 'Content-Type: application/x-www-form-urlencoded' \\
|
||
-d 'grant_type=client_credentials&client_id=ak-xxxxxxxxxxxx&client_secret=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'</pre></details>
|
||
<p>返回:<code class="inl">{"accessToken":"eyJ...","tokenType":"Bearer","expiresIn":604800,"appKey":"ak-xxx"}</code></p>
|
||
|
||
<h2>四、调用业务接口</h2>
|
||
<details class="ex"><summary>查看 curl 示例(携带 Token)</summary><pre>curl '${B}/api/v1/devices?pageNum=1&pageSize=10' \\
|
||
-H 'Authorization: Bearer <accessToken>'</pre></details>
|
||
<p class="ov-note">应用 Token 拥有 <code class="inl">ROLE_APP</code> 权限,可访问业务接口;建议结合后台配置的 <b>授权范围(scopes)</b>、<b>IP 白名单</b>、<b>限流</b> 与 <b>过期时间</b> 做最小权限控制。</p>
|
||
|
||
<h2>五、后台管理接口(管理员)</h2>
|
||
<ul>
|
||
<li><code class="inl">POST /api/v1/admin/apps</code> 创建应用(返回 ak/sk)</li>
|
||
<li><code class="inl">GET /api/v1/admin/apps</code> 应用列表</li>
|
||
<li><code class="inl">GET /api/v1/admin/apps/{id}</code> 应用详情</li>
|
||
<li><code class="inl">PUT /api/v1/admin/apps/{id}/status?status=0|1</code> 启用/禁用</li>
|
||
<li><code class="inl">POST /api/v1/admin/apps/{id}/rotate</code> 轮换密钥(返回新 sk)</li>
|
||
</ul>
|
||
|
||
<h2>六、安全建议</h2>
|
||
<ul>
|
||
<li><b>全链路 HTTPS</b>:Token 与 Secret 均为明文承载,务必通过 HTTPS 传输。</li>
|
||
<li><b>Secret 不出服务端</b>:服务端仅存 BCrypt 哈希,前端/日志不落明文。</li>
|
||
<li><b>最小权限</b>:为每个对接方配置 scopes / IP 白名单 / 限流 / 有效期。</li>
|
||
<li><b>可吊销</b>:发生泄露立即「禁用」或「轮换密钥」,无需改代码。</li>
|
||
</ul>
|
||
`;
|
||
}
|
||
|
||
function initDoc(pageId){
|
||
renderSidebar(pageId);
|
||
renderTopbar(pageId);
|
||
let c='';
|
||
if(pageId==='index')c=renderOverview();
|
||
else if(pageId==='models')c=renderModels();
|
||
else if(pageId==='partner')c=renderPartner();
|
||
else c=renderSections(pageId);
|
||
c+='<div class="empty" id="empty">本页没有匹配的接口</div>';
|
||
c+=`<footer>文档由代码扫描自动汇总 · 共 ${totalEndpoints()} 个接口 / ${Object.keys(MODELS).length} 个数据模型 · 若有出入以服务端代码为准</footer>`;
|
||
document.getElementById('content').innerHTML=c;
|
||
renderExamples(getBase());
|
||
}
|
||
|
||
|
||
</script>
|
||
<script>initDoc('tuya');</script>
|
||
</body>
|
||
</html> |