# Water IoT APP 接口文档 > **业务接口 Base URL**: `/app/v1` > **认证接口前缀**: `/auth`、`/resource` > **认证方式**: Sa-Token;`/app/v1/**` 业务接口需携带 `Authorization: Bearer `,登录/注册/普通验证码接口免登录 > **响应编码**: UTF-8 > **最后更新**: 2026-07-14 --- ## 目录 - [通用说明](#通用说明) - [统一响应结构 R\](#统一响应结构-rt) - [分页响应结构 TableDataInfo\](#分页响应结构-tabledatainfot) - [分页请求参数](#分页请求参数) - [特殊标注说明](#特殊标注说明) - [零、登录、注册与验证码](#零登录注册与验证码) - [0.1 获取租户列表](#01-获取租户列表) - [0.2 获取图形验证码](#02-获取图形验证码) - [0.3 获取通用手机号或邮箱验证码](#03-获取通用手机号或邮箱验证码) - [0.4 获取短信验证码](#04-获取短信验证码) - [0.5 获取邮箱验证码](#05-获取邮箱验证码) - [0.6 用户登录](#06-用户登录) - [0.7 用户注册](#07-用户注册) - [0.8 忘记密码](#08-忘记密码) - [0.9 退出登录](#09-退出登录) - [0.10 获取账号注销验证码](#010-获取账号注销验证码) - [0.11 注销账号](#011-注销账号) - [一、设备管理](#一设备管理) - [1.1 查询设备列表(分页)](#11-查询设备列表分页) - [1.2 绑定设备状态检查](#12-绑定设备状态检查) - [1.3 绑定已上线设备](#13-绑定已上线设备) - [1.4 获取设备详情](#14-获取设备详情) - [1.5 修改设备信息](#15-修改设备信息) - [1.6 删除设备(解绑)](#16-删除设备解绑) - [1.7 手动开关设备](#17-手动开关设备) - [二、排程管理](#二排程管理) - [2.1 查询排程列表(分页)](#21-查询排程列表分页) - [2.2 新增排程](#22-新增排程) - [2.3 排程详情](#23-排程详情) - [2.4 修改排程状态](#24-修改排程状态) - [2.5 修改排程](#25-修改排程) - [2.6 删除排程](#26-删除排程) - [三、排程-设备关联](#三排程-设备关联) - [3.1 查询排程可关联的设备](#31-查询排程可关联的设备) - [3.2 绑定排程与设备](#32-绑定排程与设备) - [3.3 删除排程与设备绑定](#33-删除排程与设备绑定) - [四、浇水记录](#四浇水记录) - [4.1 查询浇水记录列表(分页)](#41-查询浇水记录列表分页) - [4.2 查询设备最新进行中的浇水记录](#42-查询设备最新进行中的浇水记录) - [4.3 删除浇水记录](#43-删除浇水记录) - [五、数据统计](#五数据统计) - [5.1 数据统计](#51-数据统计) - [六、用户管理](#六用户管理) - [6.1 获取用户信息](#61-获取用户信息) - [6.2 修改用户信息](#62-修改用户信息) - [6.3 修改登录密码](#63-修改登录密码) - [6.4 找回密码](#64-找回密码) - [七、文件上传](#七文件上传) - [7.1 上传图片](#71-上传图片) - [八、版本检查](#八版本检查) - [8.1 检查APP版本更新](#81-检查app版本更新) - [九、内部调试接口](#九内部调试接口) - [9.1 MQTT绑定命令测试](#91-mqtt绑定命令测试) - [附录:数据模型](#附录数据模型) --- ## 通用说明 ### 统一响应结构 R\ 大多数接口返回统一的 `R` 包装结构: ```json { "code": 200, "msg": "操作成功", "data": { } } ``` | 字段 | 类型 | 说明 | |------|------|------| | `code` | `int` | `200` 表示成功,`500` 表示失败 | | `msg` | `string` | 提示消息(支持 i18n 国际化) | | `data` | `T` | 业务数据,类型视接口而定;无数据时为 `null` | **判断逻辑**:`code === 200` 为成功,其余均为失败。 --- ### 分页响应结构 TableDataInfo\ 列表查询接口返回 `TableDataInfo`: ```json { "code": 200, "msg": "查询成功", "total": 100, "rows": [ ] } ``` | 字段 | 类型 | 说明 | |------|------|------| | `code` | `int` | `200` 表示成功 | | `msg` | `string` | 提示消息 | | `total` | `long` | 总记录数 | | `rows` | `T[]` | 当前页数据列表 | --- ### 分页请求参数 所有分页查询接口(GET 请求)支持以下 Query 参数: | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `pageNum` | `int` | 否 | 当前页码,默认 `1` | | `pageSize` | `int` | 否 | 每页条数,默认查全部 | > **注意**:设备列表、排程列表、浇水记录列表的排序方向固定为 `createTime desc`(服务端强制设置),前端无需传排序参数。 --- ### 特殊标注说明 | 标注 | 说明 | |------|------| | `@ApiEncrypt` | 启用 `api-decrypt` 后强制请求体加密;默认配置关闭时仍使用普通 JSON | | `@RepeatSubmit` | 防重复提交,相同请求在短时间内只能提交一次 | | `@Log` | 操作会被记录到系统日志 | > 当前默认配置 `api-decrypt.enabled=false`,因此 `@ApiEncrypt` 接口可直接发送普通 JSON。 > 当部署环境启用接口加密后,标注 `@ApiEncrypt` 的 POST/PUT 接口必须携带 `encrypt-key` 请求头并发送加密后的 body;该标注不代表免登录。 --- ## 零、登录、注册与验证码 本章节接口来自 `AuthController` 和 `CaptchaController`。两个控制器使用了 `@SaIgnore`,普通登录、注册和验证码接口免登录;退出登录、账号注销验证码和账号注销仍会在方法内部校验 Token。 默认 APP 客户端配置: | 参数 | 默认值 | 说明 | |------|------|------| | `clientId` | `428a8310cd442757ae699df5d894f051` | APP 客户端 ID,来源于 `sys_client` 初始化数据 | | `tenantId` | `000000` | 默认租户 ID;关闭多租户时仍可沿用该值 | | `userType` | `app_user` | APP 注册用户类型 | | `grantType` | `password` / `sms` | 默认 APP 客户端支持密码、短信和社交授权 | --- ### 0.1 获取图形验证码 ``` GET /auth/code ``` **认证**:免登录。验证码开启时按 IP 限流,每 60 秒最多 10 次。 **响应**:`R` ```json { "code": 200, "msg": "操作成功", "data": { "captchaEnabled": true, "uuid": "54ef90b79e384d29a44d59f863f83a52", "img": "data:image/png;base64,..." } } ``` | 字段 | 类型 | 说明 | |------|------|------| | `captchaEnabled` | `boolean` | 是否启用图形验证码 | | `uuid` | `string` | 验证码缓存标识 | | `img` | `string` | Base64 图片数据 | > 当前密码登录和注册服务中的图形验证码校验代码已注释,APP 注册实际使用手机号/邮箱验证码。 --- ### 0.2 获取通用手机号或邮箱验证码 根据 `username` 格式自动选择短信或邮件发送,主要用于注册和找回密码。 ``` GET /resource/code?username={username} ``` **认证**:免登录。相同 `username` 每 60 秒最多请求 1 次。 | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `username` | `string` | **是** | 手机号或邮箱;手机号发送短信,其他值按邮箱发送 | **响应**:`R` ```json { "code": 200, "msg": "123456", "data": null } ``` > 当前实现会把验证码同时放入响应 `msg`,生产环境存在验证码泄露风险,建议生产部署时改为固定提示文本。 --- ### 0.3 获取短信验证码 用于短信验证码登录。 ``` GET /resource/sms/code?phonenumber={phonenumber} ``` **认证**:免登录。相同手机号每 60 秒最多请求 1 次。 | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `phonenumber` | `string` | **是** | 接收验证码的手机号 | **响应**:`R` ```json { "code": 200, "msg": "操作成功", "data": null } ``` --- ### 0.4 获取邮箱验证码 ``` GET /resource/email/code?email={email} ``` **认证**:免登录。相同邮箱每 60 秒最多请求 1 次。 | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `email` | `string` | **是** | 接收验证码的邮箱地址 | **响应**:`R`。未开启邮件功能时返回 `当前系统没有开启邮箱功能!`。 --- ### 0.5 用户登录 ``` POST /auth/login ``` **认证**:免登录。接口标注 `@ApiEncrypt`,加密规则参见通用说明。 **密码登录请求体**(`grantType=password`) | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `clientId` | `string` | **是** | APP 客户端 ID | | `grantType` | `string` | **是** | 固定为 `password` | | `username` | `string` | **是** | 用户名、手机号或邮箱,长度 2~30 | | `password` | `string` | **是** | 密码,长度 5~30 | ```json { "clientId": "428a8310cd442757ae699df5d894f051", "grantType": "password", "username": "13800000000", "password": "123456" } ``` **短信登录请求体**(`grantType=sms`) | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `clientId` | `string` | **是** | APP 客户端 ID | | `grantType` | `string` | **是** | 固定为 `sms` | | `phonenumber` | `string` | **是** | 手机号 | | `smsCode` | `string` | **是** | `/resource/sms/code` 获取的验证码 | ```json { "clientId": "428a8310cd442757ae699df5d894f051", "grantType": "sms", "phonenumber": "13800000000", "smsCode": "123456" } ``` **响应**:`R` ```json { "code": 200, "msg": "操作成功", "data": { "access_token": "登录Token", "refresh_token": null, "expire_in": 1800, "refresh_expire_in": null, "client_id": "428a8310cd442757ae699df5d894f051", "scope": null, "openid": null } } ``` 后续业务请求头: ```http Authorization: Bearer 登录Token ``` --- ### 0.6 用户注册 ``` POST /auth/register ``` **认证**:免登录。接口标注 `@ApiEncrypt`;系统配置必须允许注册。 | 字段 | 类型 | 必填 | 说明 | |------|------|------|-----------------------------------------| | `clientId` | `string` | **是** | APP 客户端 ID | | `grantType` | `string` | **是** | 可传 `password、sms`,用于通过基础请求校验 | | `username` | `string` | **是** | 手机号、邮箱或用户名,长度 2~30 | | `password` | `string` | **是** | 密码,长度 5~30 | | `userType` | `string` | **是** | APP 用户固定传 `app_user` | | `code` | `string` | **是** | 调用 `/resource/code?username=...` 获取的验证码 | ```json { "clientId": "428a8310cd442757ae699df5d894f051", "grantType": "password", "username": "13800000000", "password": "123456", "userType": "app_user", "code": "123456" } ``` **响应**:`R` ```json { "code": 200, "msg": "操作成功", "data": null } ``` > 注册验证码以 `username` 为缓存键,因此获取验证码和注册时的 `username` 必须完全一致。 --- ### 0.7 忘记密码 ``` PUT /auth/forgot ``` **认证**:免登录。接口标注 `@ApiEncrypt`。 | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `username` | `string` | **是** | 已注册的用户名或手机号 | | `smsCode` | `string` | **是** | 以 `username` 为缓存键的验证码,可通过 `/resource/code` 获取 | | `password` | `string` | **是** | 新密码,长度 5~30 | ```json { "username": "13800000000", "smsCode": "123456", "password": "NewPass456" } ``` **响应**:`R` > **当前实现限制**:邮箱分支查询结果未赋值给用户对象,因此邮箱找回密码当前会被判定为“账号未注册”;建议修复后再开放邮箱找回。 --- ### 0.8 退出登录 ``` POST /auth/logout ``` **认证**:需要 `Authorization: Bearer `。 **响应**:`R`,成功消息为 `退出成功`。 --- ### 0.9 获取账号注销验证码 根据当前登录账号优先向已绑定手机号发送短信;没有有效手机号时发送到邮箱。 ``` GET /resource/account/cancel/code ``` **认证**:需要 `Authorization: Bearer `。每个用户每 60 秒最多请求 1 次。 **响应**:`R` > 验证码以当前登录用户名为缓存键。账号未绑定手机号或邮箱时返回失败。 --- ### 0.11 注销账号 ``` DELETE /auth/account ``` **认证**:需要 `Authorization: Bearer `。 | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `code` | `string` | **是** | `/resource/account/cancel/code` 获取的验证码 | ```json { "code": "123456" } ``` **响应**:`R`,成功消息为 `注销成功`。 > 注销成功后会解绑并初始化用户名下设备,删除排程、浇水记录及系统用户关联数据,并退出当前账号。该操作不可恢复,超级管理员账号不允许注销。 --- ## 一、设备管理 ### 1.1 查询设备列表(分页) 查询当前登录用户名下的设备列表。 ``` GET /app/v1/deviceList ``` **请求参数(Query)** | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `pageNum` | `int` | 否 | 页码,默认 1 | | `pageSize` | `int` | 否 | 每页条数 | | `deviceNo` | `string` | 否 | 设备编号(精确查询) | | `deviceName` | `string` | 否 | 设备名称(模糊查询) | | `status` | `string` | 否 | 设备状态:`1`-在线 `0`-离线 `2`-到期 `3`-故障 | | `wifiName` | `string` | 否 | WiFi 名称(模糊查询) | | `deviceEm` | `string` | 否 | 设备型号(精确查询) | | `deviceSn` | `string` | 否 | 设备序列号(精确查询) | | `fwVer` | `string` | 否 | 固件版本(精确查询) | | `macAddress` | `string` | 否 | MAC 地址(精确查询) | **响应**:`TableDataInfo` ```json { "code": 200, "msg": "查询成功", "total": 2, "rows": [ { "deviceNo": "D20260101001", "userId": 1, "deviceName": "前院浇灌器", "deviceInitName": "智能浇灌器Pro", "deviceImg": "https://oss.example.com/device/xxx.png", "qrcode": "D20260101001", "status": "1", "workStatus": "0", "powerLevel": "85", "powerLevelUpdatatime": "2026-07-14 08:30:00", "wifiName": "Home-WiFi", "wifiPassword": "12345678", "deviceEm": "WATER-PRO-1", "deviceSn": "SN20260101001", "fwVer": "1.0.3", "macAddress": "AA:BB:CC:DD:EE:FF", "nickName": "张三", "expirationTime": "2027-01-01 00:00:00" } ] } ``` --- ### 1.2 绑定设备状态检查 检查设备绑定状态(加密接口)。 ``` POST /app/v1/bindDeviceStatus ``` **请求头** ``` Content-Type: application/json ``` **请求体**(JSON;启用接口加密时发送加密后的 JSON 字符串) | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `deviceNo` | `string` | 否 | 设备编号 | | `macAddress` | `string` | 否 | MAC 地址 | | `deviceInitName` | `string` | 否 | 设备初始名称 | 以上三个设备标识至少填写一个。查询顺序为 `deviceNo`、`macAddress`、`deviceInitName`。 **响应**:`R>` ```json { "code": 200, "msg": "操作成功", "data": { "bindDeviceStatus": 304, "bindDeviceStatusName": "设备未绑定,请先绑定用户", "bindDevice": { "deviceNo": "D20260101001", "macAddress": "AA:BB:CC:DD:EE:FF", "userId": null, "status": "1", "workStatus": "2" } } } ``` `bindDeviceStatus` 业务状态码: | 状态码 | 说明 | |------|------| | `200` | 设备已绑定当前用户,可正常使用 | | `300` | 设备信息为空 | | `301` | 未提供设备编号、MAC 地址或设备初始名称 | | `302` | 设备未入库注册 | | `303` | 设备已被其他用户绑定 | | `304` | 设备存在但尚未绑定用户,可以继续调用 `/addDevice` | | `305` | 设备尚未完成配网 | > 这些是 `data.bindDeviceStatus` 的业务状态码;接口外层 `code` 在正常处理时仍为 `200`。 --- ### 1.3 绑定已上线设备 绑定通过 MQTT 注册上线的设备到当前用户(加密接口)。 ``` POST /app/v1/addDevice ``` **请求头** ``` Content-Type: application/json ``` **请求体**(JSON;启用接口加密时发送加密后的 JSON 字符串) | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `deviceNo` | `string` | **是** | MQTT 注册后服务端分配的设备编号 | | `deviceName` | `string` | 否 | 设备名称 | | `status` | `string` | 否 | 绑定后保留/设置的设备在线状态 | | `wifiName` | `string` | 否 | WiFi 名称 | | `wifiPassword` | `string` | 否 | WiFi 密码 | **响应**:`R` ```json { "code": 200, "msg": "操作成功", "data": { "deviceNo": "D20260101001", "userId": 1, "deviceName": "前院浇灌器", "deviceInitName": "智能浇灌器Pro", "deviceImg": "https://oss.example.com/device/xxx.png", "status": "1", "workStatus": "0", "macAddress": "AA:BB:CC:DD:EE:FF", "expirationTime": "2027-01-01 00:00:00" } } ``` --- ### 1.4 获取设备详情 ``` GET /app/v1/device/{deviceNo} ``` **路径参数** | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `deviceNo` | `string` | **是** | 设备编号 | **响应**:`R` ```json { "code": 200, "msg": "操作成功", "data": { "deviceNo": "D20260101001", "userId": 1, "deviceName": "前院浇灌器", "deviceInitName": "智能浇灌器Pro", "deviceImg": "https://oss.example.com/device/xxx.png", "qrcode": "D20260101001", "status": "1", "workStatus": "0", "powerLevel": "85", "powerLevelUpdatatime": "2026-07-14 08:30:00", "wifiName": "Home-WiFi", "wifiPassword": "12345678", "deviceEm": "WATER-PRO-1", "deviceSn": "SN20260101001", "fwVer": "1.0.3", "macAddress": "AA:BB:CC:DD:EE:FF", "nickName": "张三", "expirationTime": "2027-01-01 00:00:00" } } ``` > **权限**:只能查询当前用户名下的设备,否则返回错误。 --- ### 1.5 修改设备信息 ``` PUT /app/v1/updateDeviceInfo ``` > `@RepeatSubmit` — 防重复提交 **请求头** ``` Content-Type: application/json ``` **请求体**:`AppDeviceBo`(JSON) | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `deviceNo` | `string` | **是** | 设备编号(EditGroup 校验) | | `deviceName` | `string` | 否 | 设备名称 | | `deviceImg` | `string` | 否 | 设备图片 URL | | `wifiName` | `string` | 否 | WiFi 名称 | | `wifiPassword` | `string` | 否 | WiFi 密码 | ```json { "deviceNo": "D20260101001", "deviceName": "后院浇灌器", "deviceImg": "https://oss.example.com/device/new.png" } ``` **响应**:`R` ```json { "code": 200, "msg": "操作成功", "data": null } ``` > **权限**:只能修改当前用户名下的设备。 --- ### 1.6 删除设备(解绑) 解绑一个或多个设备。 ``` DELETE /app/v1/deleteDevice/{deviceNos} ``` **路径参数** | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `deviceNos` | `string` | **是** | 设备编号,多个用逗号分隔。例:`D001,D002` | **示例** ``` DELETE /app/v1/deleteDevice/D20260101001,D20260101002 ``` **响应**:`R` ```json { "code": 200, "msg": "操作成功", "data": null } ``` > **权限**:只能解绑当前用户名下的设备。 --- ### 1.7 手动开关设备 手动开启或关闭设备工作状态,并可通过 MQTT 下发指令到设备。 ``` PUT /app/v1/switchDevice ``` > `@RepeatSubmit` — 防重复提交 **请求头** ``` Content-Type: application/json ``` **请求体**(JSON) | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `deviceNo` | `string` | **是** | 设备编号 | | `workStatus` | `string` | **是** | 工作状态:`1`-开启 `0`-关闭 | | `durationMin` | `int` | 条件必填 | 持续时长(分钟);`workStatus=1` 时必须大于 `0`,关闭时可不传 | ```json { "deviceNo": "D20260101001", "workStatus": "1", "durationMin": 20 } ``` **响应**:`R` ```json { "code": 200, "msg": "操作成功", "data": null } ``` > **说明**:开启时 `startTime` 由服务端按当前时间生成,格式为 `yyyy-MM-dd HH:mm:ss`。 --- ## 二、排程管理 ### 2.1 查询排程列表(分页) ``` GET /app/v1/schedulelist ``` **请求参数(Query)** | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `pageNum` | `int` | 否 | 页码 | | `pageSize` | `int` | 否 | 每页条数 | | `name` | `string` | 否 | 排程名称(模糊查询) | | `status` | `string` | 否 | 状态:`0`-关闭 `1`-开启 | **响应**:`TableDataInfo` ```json { "code": 200, "msg": "查询成功", "total": 1, "rows": [ { "id": 1, "userId": 1, "name": "工作日早晨浇水", "status": "1", "details": null, "deviceNos": null } ] } ``` > **注意**:列表查询返回的 `details` 和 `deviceNos` 为 `null`,完整信息请通过 [2.3 排程详情](#23-排程详情) 获取。 --- ### 2.2 新增排程 ``` POST /app/v1/addschedule ``` > `@RepeatSubmit` — 防重复提交 **请求头** ``` Content-Type: application/json ``` **请求体**:`AppScheduleBo`(JSON,AddGroup 校验) | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `name` | `string` | 否 | 排程名称 | | `status` | `string` | 否 | 状态:`0`-关闭 `1`-开启 | | `details` | `array` | **是** | 排程详情列表(至少一天) | **`details` 数组元素结构(`AppScheduleDetail`)** | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `weekday` | `int` | **是** | 星期几:`1`-周一 ~ `7`-周日 | | `timeData` | `array` | **是** | 时间段列表 | | `zones` | `int` | 否 | 浇水区域(位掩码) | | `triggerType` | `string` | 否 | 触发类型:`0`-排程(默认) | | `status` | `string` | 否 | 状态:`0`-关闭(默认) | **`timeData` 数组元素结构(`TimeSlot`)** | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `startTime` | `string` | **是** | 开始时间,格式 `HH:mm` | | `durationMin` | `int` | 否 | 持续时长(分钟) | ```json { "name": "工作日早晨浇水", "status": "1", "details": [ { "weekday": 1, "timeData": [ { "startTime": "06:30", "durationMin": 15 }, { "startTime": "18:00", "durationMin": 20 } ], "triggerType": "0", "status": "1" }, { "weekday": 2, "timeData": [ { "startTime": "06:30", "durationMin": 15 } ], "triggerType": "0", "status": "1" } ] } ``` **响应**:`R` ```json { "code": 200, "msg": "操作成功", "data": { "id": 10, "userId": 1, "name": "工作日早晨浇水", "status": "1", "details": [ { "id": 101, "weekday": 1, "timeData": [ { "startTime": "06:30", "durationMin": 15 }, { "startTime": "18:00", "durationMin": 20 } ], "triggerType": "0", "status": "1" } ], "deviceNos": null } } ``` --- ### 2.3 排程详情 获取排程完整信息,包含排程详情列表和已绑定设备列表。 ``` GET /app/v1/schedule/{id} ``` **路径参数** | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `id` | `long` | **是** | 排程 ID | **响应**:`R` ```json { "code": 200, "msg": "操作成功", "data": { "id": 10, "userId": 1, "name": "工作日早晨浇水", "status": "1", "details": [ { "id": 1, "weekday": 1, "timeData": [ { "startTime": "06:30", "durationMin": 15 }, { "startTime": "18:00", "durationMin": 20 } ], "triggerType": "0", "status": "1" } ], "deviceNos": [ { "deviceNo": "D20260101001", "userId": 1, "deviceName": "前院浇灌器", "status": "1", "workStatus": "0" } ] } } ``` > **说明**: > - `details` 返回该排程当前查询到的全部明细,不在控制器中额外过滤状态。 > - `deviceNos` 为已绑定设备的完整 `AppDeviceVo` 列表。 > - `timeData` 已从 JSON 字符串解析为数组。 --- ### 2.4 修改排程状态 开启或关闭排程,状态变更后通过 MQTT 通知所有已绑定设备。 ``` PUT /app/v1/editScheduleStatus ``` **请求头** ``` Content-Type: application/json ``` **请求体**:`AppScheduleBo`(JSON) | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `id` | `long` | **是** | 排程 ID | | `status` | `string` | **是** | 目标状态:`0`-关闭 `1`-开启 | ```json { "id": 10, "status": "0" } ``` **响应**:`R` ```json { "code": 200, "msg": "操作成功", "data": null } ``` > **MQTT 通知**: > - 关闭(`status=0`):向绑定设备下发排程取消指令。 > - 开启(`status=1`):向绑定设备重新下发完整排程信息。 --- ### 2.5 修改排程 修改排程内容,更新后通过 MQTT 向所有绑定设备重新下发排程。 ``` PUT /app/v1/updataschedule ``` > `@RepeatSubmit` — 防重复提交 **请求头** ``` Content-Type: application/json ``` **请求体**:`AppScheduleBo`(JSON,EditGroup 校验) | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `id` | `long` | **是** | 排程 ID(EditGroup 校验) | | `name` | `string` | 否 | 排程名称 | | `status` | `string` | 否 | 状态:`0`-关闭 `1`-开启 | | `details` | `array` | 否 | 排程详情列表(同 [2.2 新增排程](#22-新增排程)) | ```json { "id": 10, "name": "周末浇水计划", "status": "1", "details": [ { "id": 101, "weekday": 6, "timeData": [ { "startTime": "08:00", "durationMin": 30 } ], "triggerType": "0", "status": "1" } ] } ``` **响应**:`R` ```json { "code": 200, "msg": "操作成功", "data": { "id": 10, "userId": 1, "name": "周末浇水计划", "status": "1", "details": [ ] } } ``` > **说明**:`data` 为更新后的 `AppScheduleVo` 对象。修改已有排程明细时应传明细 `id`,否则服务端无法按主键更新该明细。 --- ### 2.6 删除排程 删除一个或多个排程,并通知所有绑定设备解绑。 ``` DELETE /app/v1/deleteschedule/{ids} ``` **路径参数** | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `ids` | `long` | **是** | 排程 ID,多个用逗号分隔。例:`10,11` | **示例** ``` DELETE /app/v1/deleteschedule/10,11 ``` **响应**:`R` ```json { "code": 200, "msg": "操作成功", "data": null } ``` > **MQTT 通知**:删除成功后,向每个排程关联的所有设备下发解绑指令。 --- ## 三、排程-设备关联 ### 3.1 查询排程可关联的设备 查询当前用户名下**未被其他排程关联**的设备列表。 ``` GET /app/v1/scheduleDeviceList/{scheduleId} ``` **路径参数** | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `scheduleId` | `long` | **是** | 排程 ID | **响应**:`R>` ```json { "code": 200, "msg": "操作成功", "data": { "deviceList": [ { "deviceNo": "D20260101003", "userId": 1, "deviceName": "阳台浇灌器", "status": "1", "workStatus": "0" } ] } } ``` > **说明**:返回的设备列表为当前用户名下未与任何排程关联的设备。已关联其他排程的设备不会出现在列表中。 --- ### 3.2 绑定排程与设备 将一个或多个设备绑定到指定排程,绑定后通过 MQTT 向设备下发排程信息。 ``` POST /app/v1/addScheduleDevice ``` > `@RepeatSubmit` — 防重复提交 **请求头** ``` Content-Type: application/json ``` **请求体**(JSON) | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `scheduleId` | `long` | **是** | 排程 ID | | `deviceNos` | `array` | **是** | 设备编号列表。也可用 `deviceNo`(逗号分隔字符串)或 `deviceIds` | ```json { "scheduleId": 10, "deviceNos": ["D20260101001", "D20260101003"] } ``` **响应**:`R` ```json { "code": 200, "msg": "操作成功", "data": null } ``` > **说明**: > - `deviceNos` 支持数组或逗号分隔字符串格式。 > - 已绑定该排程的设备会自动跳过(幂等)。 > - 绑定后,服务端通过 MQTT 向设备下发排程详情。 --- ### 3.3 删除排程与设备绑定 解除排程与设备的绑定关系,并通知设备取消排程。 ``` DELETE /app/v1/deleteScheduleDevice ``` **请求参数(Query)** | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `scheduleId` | `string` | **是** | 排程 ID | | `deviceNo` | `string` | **是** | 设备编号 | **示例** ``` DELETE /app/v1/deleteScheduleDevice?scheduleId=10&deviceNo=D20260101001 ``` **响应**:`R` ```json { "code": 200, "msg": "操作成功", "data": null } ``` > **MQTT 通知**:解绑成功后,向设备下发排程解绑指令。若 MQTT 下发失败,不影响接口返回成功(仅记录日志警告)。 --- ## 四、浇水记录 ### 4.1 查询浇水记录列表(分页) ``` GET /app/v1/waterLogList ``` **请求参数(Query)** | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `pageNum` | `int` | 否 | 页码 | | `pageSize` | `int` | 否 | 每页条数 | | `deviceNo` | `string` | 否 | 设备编号 | | `commandId` | `string` | 否 | MQTT 命令 ID | | `scheduleId` | `long` | 否 | 关联排程 ID | | `startTime` | `date` | 否 | 开始时间(精确匹配) | | `endTime` | `date` | 否 | 结束时间(精确匹配) | | `durationMin` | `string` | 否 | 持续时间(精确匹配) | | `zones` | `string` | 否 | 浇水区域 | | `triggerType` | `string` | 否 | 触发类型:`0`-排程 `1`-手动 | | `remark` | `string` | 否 | 备注(模糊匹配) | | `params[beginTime]` | `date` | 否 | 创建时间范围开始;需与 `params[endTime]` 同时传入 | | `params[endTime]` | `date` | 否 | 创建时间范围结束;需与 `params[beginTime]` 同时传入 | **响应**:`TableDataInfo` ```json { "code": 200, "msg": "查询成功", "total": 50, "rows": [ { "id": 1, "userId": 1, "deviceNo": "D20260101001", "deviceName": "前院浇灌器", "commandId": "CMD_20260714001", "scheduleId": 10, "startTime": "2026-07-14 06:30:00", "endTime": "2026-07-14 06:45:00", "durationMin": "15", "zones": "1", "triggerType": "0", "remark": null, "status": "0", "createTime": "2026-07-14 06:30:00" } ] } ``` > **说明**:响应中 `deviceName` 由服务端关联查询后填充。 --- ### 4.2 查询设备最新进行中的浇水记录 ``` GET /app/v1/latestWaterLog ``` **请求参数(Query)** | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `deviceNo` | `string` | **是** | 设备编号 | **响应**:`R>` ```json { "code": 200, "msg": "操作成功", "data": { "startTime": "2026-07-14 08:00:00", "endTime": "2026-07-14 08:20:00" } } ``` > **说明**:控制器按当前用户、设备编号和状态 `1` 发起查询,并取 ID 最大的一条记录。若无进行中的记录,`data` 为 `null`。时间格式为 `yyyy-MM-dd HH:mm:ss`。 > > **当前实现限制**:`AppWateringLogServiceImpl.buildQueryWrapper` 尚未把 `status` 加入查询条件,因此现有代码实际可能取到该设备 ID 最大但已结束的记录。客户端依赖“进行中”语义前,应先修复该服务查询条件。 --- ### 4.3 删除浇水记录 ``` DELETE /app/v1/waterLog/{ids} ``` **路径参数** | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `ids` | `long` | **是** | 记录 ID,多个用逗号分隔。例:`1,2,3` | **示例** ``` DELETE /app/v1/waterLog/1,2,3 ``` **响应**:`R` ```json { "code": 200, "msg": "操作成功", "data": null } ``` > **权限**:只能删除当前用户的浇水记录。 --- ## 五、数据统计 ### 5.1 数据统计 获取当前用户的浇水数据统计汇总。 ``` GET /app/v1/dataStatistics ``` **请求参数**:无 **响应**:`R>` ```json { "code": 200, "msg": "操作成功", "data": { "count": 120, "monCount": 15, "weekCount": 4, "schedule": 80, "manualNum": 40, "allTime": 3600, "averageTime": 30 } } ``` | 字段 | 类型 | 说明 | |------|------|------| | `count` | `long` | 总浇水次数 | | `monCount` | `long` | 本月浇水次数 | | `weekCount` | `long` | 本周浇水次数 | | `schedule` | `long` | 排程触发浇水次数 | | `manualNum` | `long` | 手动触发浇水次数 | | `allTime` | `long` | 总浇水时间(分钟) | | `averageTime` | `long` | 平均每次浇水时间(分钟) | --- ## 六、用户管理 ### 6.1 获取用户信息 ``` GET /app/v1/getUserInfo ``` **请求参数**:无 **响应**:`R` ```json { "code": 200, "msg": "操作成功", "data": { "user": { "userId": 1, "deptId": 100, "userName": "admin", "nickName": "管理员", "email": "admin@example.com", "phonenumber": "138****8888", "sex": "0", "avatar": "https://oss.example.com/avatar/xxx.png", "status": "0", "loginIp": "192.168.1.1", "loginDate": "2026-07-14 09:00:00", "remark": null, "createTime": "2026-01-01 00:00:00" }, "permissions": ["*:*:*"], "roles": ["admin"] } } ``` > **说明**:`phonenumber` 和 `email` 字段做了脱敏处理(需 `system:user:edit` 权限才能看到完整内容)。 --- ### 6.2 修改用户信息 ``` PUT /app/v1/updataUser ``` > `@RepeatSubmit` — 防重复提交 **请求头** ``` Content-Type: application/json ``` **请求体**:`SysUserBo`(JSON) | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `userId` | `long` | **是** | 用户 ID | | `nickName` | `string` | 否 | 用户昵称(最长 30 字符) | | `email` | `string` | 否 | 邮箱(需符合邮箱格式,最长 50 字符) | | `phonenumber` | `string` | 否 | 手机号(需唯一) | | `code` | `string` | 条件必填 | 修改手机号时使用的短信验证码 | | `sex` | `string` | 否 | 性别:`0`-男 `1`-女 `2`-未知 | | `avatar` | `string` | 否 | 头像 URL | ```json { "userId": 1, "nickName": "新昵称", "phonenumber": "13900001111", "code": "123456", "email": "new@example.com", "sex": "0" } ``` **响应**:`R` ```json { "code": 200, "msg": "操作成功", "data": null } ``` > **校验**:手机号需全局唯一;请求中包含 `phonenumber` 时,服务层会校验 `code` 短信验证码。 --- ### 6.3 修改登录密码 ``` PUT /app/v1/updataPassword ``` > `@RepeatSubmit` — 防重复提交 **请求头** ``` Content-Type: application/json ``` **请求体**:`SysUserVo`(JSON) | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `oldPassword` | `string` | **是** | 旧密码(明文,服务端 BCrypt 校验) | | `password` | `string` | **是** | 新密码(明文) | ```json { "oldPassword": "OldPass123", "password": "NewPass456" } ``` **响应**:`R` ```json { "code": 200, "msg": "操作成功", "data": null } ``` > **说明**:旧密码使用 BCrypt 校验,不匹配则返回 `user.password.not.match` 错误。 --- ### 6.4 找回密码 通过手机号或邮箱找回密码(加密接口)。 ``` POST /app/v1/retrievePassword ``` **请求头** ``` Content-Type: application/json ``` **请求体**(JSON;启用接口加密时发送加密后的 JSON 字符串) | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `userName` | `string` | **是** | 用户名(手机号或邮箱) | | `password` | `string` | **是** | 新密码 | | `code` | `string` | 条件必填 | `userName` 为手机号时使用的短信验证码 | ```json { "userName": "13900001111", "password": "NewPass456", "code": "123456" } ``` **响应**:`R` ```json { "code": 200, "msg": "操作成功", "data": null } ``` > **校验逻辑**: > - 若 `userName` 为邮箱格式:校验邮箱是否已注册,未注册则报错 `user.email.not.username`。 > - 若 `userName` 为手机号格式:校验手机号是否已注册及短信验证码,未注册则报错 `user.mobile.phone.number.not.username`。 > - 当前实现仍从登录 Token 获取 `userId`,因此该接口不是匿名接口,并且控制器内未校验短信/邮箱验证码。 --- ## 七、文件上传 ### 7.1 上传图片 上传图片到 OSS 对象存储,仅支持图片类型。 ``` POST /app/v1/uploadImage ``` **请求头** ``` Content-Type: multipart/form-data ``` **请求参数(FormData)** | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `file` | `file` | **是** | 图片文件(Content-Type 必须以 `image/` 开头) | 当前 Spring Multipart 配置允许单文件最大 `100MB`、单次请求最大 `100MB`;实际部署还可能受网关和 OSS 配置限制。 **响应**:`R` ```json { "code": 200, "msg": "操作成功", "data": { "url": "https://oss.example.com/2026/07/14/xxx.png", "fileName": "screenshot.png", "ossId": "123456" } } ``` | 字段 | 类型 | 说明 | |------|------|------| | `url` | `string` | 文件访问 URL | | `fileName` | `string` | 原始文件名 | | `ossId` | `string` | 对象存储记录 ID | --- ## 八、版本检查 ### 8.1 检查APP版本更新 ``` GET /app/v1/checkVersion ``` **请求参数(Query)** | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `platform` | `string` | 否 | 平台类型:`android`(默认)或 `ios` | | `currentVersion` | `string` | **是** | APP 当前版本号 | **示例** ``` GET /app/v1/checkVersion?platform=android¤tVersion=1.0.2 ``` **响应**:`R` ```json { "code": 200, "msg": "操作成功", "data": { "platform": "android", "currentVersion": "1.0.2", "latestVersion": "1.0.3", "versionCode": 3, "updateAvailable": true, "forceUpdate": false, "downloadUrl": "https://oss.example.com/app/water-1.0.3.apk", "releaseNotes": "1. 修复设备绑定问题\n2. 优化排程功能" } } ``` | 字段 | 类型 | 说明 | |------|------|------| | `platform` | `string` | 平台类型 | | `currentVersion` | `string` | 客户端当前版本 | | `latestVersion` | `string` | 服务端最新版本 | | `versionCode` | `int` | 版本序号(用于排序) | | `updateAvailable` | `boolean` | 是否有可更新版本 | | `forceUpdate` | `boolean` | 是否强制更新 | | `downloadUrl` | `string` | 安装包下载地址 | | `releaseNotes` | `string` | 更新说明 | > 当前版本判断采用版本字符串是否完全相等:`currentVersion` 与 `latestVersion` 不相等时,`updateAvailable=true`,未进行语义化版本大小比较。 --- ## 九、内部调试接口 ### 9.1 MQTT绑定命令测试 ``` GET /app/v1/test ``` 该接口会直接调用 `sendBindDeviceCommand("01")`,向固定设备编号 `01` 下发绑定命令,并返回生成的命令 ID。 **响应**:`R`(运行时实际将命令 ID 作为消息文本返回) ```json { "code": 200, "msg": "生成的命令ID", "data": null } ``` > 该路由仅用于后端联调,不属于 APP 正式业务接口。生产环境建议删除、关闭或增加管理员权限控制。 --- ## 附录:数据模型 ### AppDeviceVo(设备信息) | 字段 | 类型 | 说明 | |------|------|------| | `deviceNo` | `string` | 设备编号 | | `userId` | `long` | 用户 ID | | `deviceName` | `string` | 设备名称 | | `deviceInitName` | `string` | 设备初始名称 | | `deviceImg` | `string` | 设备图片 URL | | `qrcode` | `string` | 二维码内容 | | `status` | `string` | 设备状态:`1`-在线 `0`-离线 `2`-到期 `3`-故障 | | `workStatus` | `string` | 工作状态:`0`-休息 `1`-工作 `2`-初始/未配网 | | `powerLevel` | `string` | 电量百分比 | | `powerLevelUpdatatime` | `date` | 电量更新时间 | | `wifiName` | `string` | WiFi 名称 | | `wifiPassword` | `string` | WiFi 密码 | | `deviceEm` | `string` | 设备型号 | | `deviceSn` | `string` | 设备序列号 | | `fwVer` | `string` | 固件版本 | | `macAddress` | `string` | MAC 地址 | | `nickName` | `string` | 用户昵称 | | `expirationTime` | `date` | 到期时间 | ### AppScheduleVo(排程信息) | 字段 | 类型 | 说明 | |------|------|------| | `id` | `long` | 排程 ID | | `userId` | `long` | 用户 ID | | `name` | `string` | 排程名称 | | `status` | `string` | 状态:`0`-关闭 `1`-开启 | | `details` | `array` | 排程详情列表;新增和详情接口会返回,列表接口通常为空 | | `deviceNos` | `array` | 已绑定设备列表(仅详情接口返回) | ### AppScheduleDetail(排程详情) | 字段 | 类型 | 说明 | |------|------|------| | `id` | `long` | 详情 ID | | `weekday` | `int` | 星期几:`1`-周一 ~ `7`-周日 | | `timeData` | `array` | 时间段列表 | | `triggerType` | `string` | 触发类型:`0`-排程 `1`-手动 | | `status` | `string` | 状态:`0`-关闭 `1`-开启 | ### TimeSlot(时间段) | 字段 | 类型 | 说明 | |------|------|------| | `startTime` | `string` | 开始时间(格式 `HH:mm`) | | `durationMin` | `int` | 持续时长(分钟) | ### AppWateringLogVo(浇水记录) | 字段 | 类型 | 说明 | |------|------|------| | `id` | `long` | 记录 ID | | `userId` | `long` | 用户 ID | | `deviceNo` | `string` | 设备编号 | | `deviceName` | `string` | 设备名称 | | `commandId` | `string` | 命令 ID | | `scheduleId` | `long` | 关联排程 ID | | `startTime` | `date` | 开始时间 | | `endTime` | `date` | 结束时间 | | `durationMin` | `string` | 持续时间(分钟) | | `zones` | `string` | 浇水区域 | | `triggerType` | `string` | 触发类型:`0`-排程 `1`-手动 | | `remark` | `string` | 备注 | | `status` | `string` | 状态:`0`-已结束 `1`-进行中 | | `createTime` | `date` | 创建时间 | ### 枚举值速查 | 枚举 | 值 | 说明 | |------|------|------| | **设备状态 `status`** | `1` | 在线 | | | `0` | 离线 | | | `2` | 到期 | | | `3` | 故障 | | **工作状态 `workStatus`** | `0` | 休息 | | | `1` | 工作 | | | `2` | 初始/未配网 | | **排程状态 `status`** | `0` | 关闭 | | | `1` | 开启 | | **触发类型 `triggerType`** | `0` | 排程触发 | | | `1` | 手动触发 | | **浇水记录状态 `status`** | `0` | 已结束 | | | `1` | 进行中 | | **星期 `weekday`** | `1`~`7` | 周一~周日 | | **用户性别 `sex`** | `0` | 男 | | | `1` | 女 | | | `2` | 未知 | | **强制更新 `forceUpdate`** | `0` | 否 | | | `1` | 是 | --- > **联调提示**: > 1. `/app/v1/**` 业务接口及明确标注“需要登录”的认证接口,请求头需携带 `Authorization: Bearer `;登录、注册、忘记密码、租户列表和普通验证码接口免登录。 > 2. 当前 `api-decrypt.enabled=false` 时发送普通 JSON;启用后,标注 `@ApiEncrypt` 的接口(绑定设备状态检查、绑定设备、找回密码)需按约定加密请求体并携带 `encrypt-key`。 > 3. 标注 `@RepeatSubmit` 的接口,短时间内重复提交会被拦截,前端应在收到响应前禁用提交按钮。 > 4. 删除类接口的路径参数支持逗号分隔多个 ID。 > 5. 分页接口排序固定为 `createTime desc`,前端无需传排序参数。 > 6. 时间字段统一使用 `yyyy-MM-dd HH:mm:ss` 格式,时间段 `startTime` 使用 `HH:mm` 格式。