Files
water/docs/AppController-API.md
yuhaiming 70e2356f22 fix(app): 修复 AppController 安全与查询问题
- 加强异常处理、类型安全和图片上传校验
- 优化设备相关查询,避免重复访问数据源
- 补充并记录并发测试与审查修复实施计划
2026-07-17 08:20:44 +08:00

1821 lines
43 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Water IoT APP 接口文档
> **业务接口 Base URL**: `/app/v1`
> **认证接口前缀**: `/auth`、`/resource`
> **认证方式**: Sa-Token`/app/v1/**` 业务接口需携带 `Authorization: Bearer <token>`,登录/注册/普通验证码接口免登录
> **响应编码**: UTF-8
> **最后更新**: 2026-07-14
---
## 目录
- [通用说明](#通用说明)
- [统一响应结构 R\<T\>](#统一响应结构-rt)
- [分页响应结构 TableDataInfo\<T\>](#分页响应结构-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\<T\>
大多数接口返回统一的 `R<T>` 包装结构:
```json
{
"code": 200,
"msg": "操作成功",
"data": { }
}
```
| 字段 | 类型 | 说明 |
|------|------|------|
| `code` | `int` | `200` 表示成功,`500` 表示失败 |
| `msg` | `string` | 提示消息(支持 i18n 国际化) |
| `data` | `T` | 业务数据,类型视接口而定;无数据时为 `null` |
**判断逻辑**`code === 200` 为成功,其余均为失败。
---
### 分页响应结构 TableDataInfo\<T\>
列表查询接口返回 `TableDataInfo<T>`
```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<CaptchaVo>`
```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<Void>`
```json
{
"code": 200,
"msg": "123456",
"data": null
}
```
> 当前实现会把验证码同时放入响应 `msg`,生产环境存在验证码泄露风险,建议生产部署时改为固定提示文本。
---
### 0.3 获取短信验证码
用于短信验证码登录。
```
GET /resource/sms/code?phonenumber={phonenumber}
```
**认证**:免登录。相同手机号每 60 秒最多请求 1 次。
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `phonenumber` | `string` | **是** | 接收验证码的手机号 |
**响应**`R<Void>`
```json
{
"code": 200,
"msg": "操作成功",
"data": null
}
```
---
### 0.4 获取邮箱验证码
```
GET /resource/email/code?email={email}
```
**认证**:免登录。相同邮箱每 60 秒最多请求 1 次。
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `email` | `string` | **是** | 接收验证码的邮箱地址 |
**响应**`R<Void>`。未开启邮件功能时返回 `当前系统没有开启邮箱功能!`
---
### 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<LoginVo>`
```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<Void>`
```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<Void>`
> **当前实现限制**:邮箱分支查询结果未赋值给用户对象,因此邮箱找回密码当前会被判定为“账号未注册”;建议修复后再开放邮箱找回。
---
### 0.8 退出登录
```
POST /auth/logout
```
**认证**:需要 `Authorization: Bearer <token>`
**响应**`R<Void>`,成功消息为 `退出成功`
---
### 0.9 获取账号注销验证码
根据当前登录账号优先向已绑定手机号发送短信;没有有效手机号时发送到邮箱。
```
GET /resource/account/cancel/code
```
**认证**:需要 `Authorization: Bearer <token>`。每个用户每 60 秒最多请求 1 次。
**响应**`R<Void>`
> 验证码以当前登录用户名为缓存键。账号未绑定手机号或邮箱时返回失败。
---
### 0.11 注销账号
```
DELETE /auth/account
```
**认证**:需要 `Authorization: Bearer <token>`
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `code` | `string` | **是** | `/resource/account/cancel/code` 获取的验证码 |
```json
{
"code": "123456"
}
```
**响应**`R<Void>`,成功消息为 `注销成功`
> 注销成功后会解绑并初始化用户名下设备,删除排程、浇水记录及系统用户关联数据,并退出当前账号。该操作不可恢复,超级管理员账号不允许注销。
---
## 一、设备管理
### 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<AppDeviceVo>`
```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<Map<String, Object>>`
```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<AppDeviceVo>`
```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<AppDeviceVo>`
```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<Void>`
```json
{
"code": 200,
"msg": "操作成功",
"data": null
}
```
> **权限**:只能修改当前用户名下的设备。
---
### 1.6 删除设备(解绑)
解绑一个或多个设备。
```
DELETE /app/v1/deleteDevice/{deviceNos}
```
**路径参数**
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `deviceNos` | `string` | **是** | 设备编号,多个用逗号分隔。例:`D001,D002` |
**示例**
```
DELETE /app/v1/deleteDevice/D20260101001,D20260101002
```
**响应**`R<Void>`
```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<Void>`
```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<AppScheduleVo>`
```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`JSONAddGroup 校验)
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `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<AppScheduleVo>`
```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<AppScheduleVo>`
```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<Void>`
```json
{
"code": 200,
"msg": "操作成功",
"data": null
}
```
> **MQTT 通知**
> - 关闭(`status=0`):向绑定设备下发排程取消指令。
> - 开启(`status=1`):向绑定设备重新下发完整排程信息。
---
### 2.5 修改排程
修改排程内容,更新后通过 MQTT 向所有绑定设备重新下发排程。
```
PUT /app/v1/updataschedule
```
> `@RepeatSubmit` — 防重复提交
**请求头**
```
Content-Type: application/json
```
**请求体**`AppScheduleBo`JSONEditGroup 校验)
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `id` | `long` | **是** | 排程 IDEditGroup 校验) |
| `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<Object>`
```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<Void>`
```json
{
"code": 200,
"msg": "操作成功",
"data": null
}
```
> **MQTT 通知**:删除成功后,向每个排程关联的所有设备下发解绑指令。
---
## 三、排程-设备关联
### 3.1 查询排程可关联的设备
查询当前用户名下**未被其他排程关联**的设备列表。
```
GET /app/v1/scheduleDeviceList/{scheduleId}
```
**路径参数**
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `scheduleId` | `long` | **是** | 排程 ID |
**响应**`R<Map<String, Object>>`
```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<Void>`
```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<Void>`
```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<AppWateringLogVo>`
```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<Map<String, Object>>`
```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<Void>`
```json
{
"code": 200,
"msg": "操作成功",
"data": null
}
```
> **权限**:只能删除当前用户的浇水记录。
---
## 五、数据统计
### 5.1 数据统计
获取当前用户的浇水数据统计汇总。
```
GET /app/v1/dataStatistics
```
**请求参数**:无
**响应**`R<Map<String, Object>>`
```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<UserInfoVo>`
```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<Void>`
```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<Void>`
```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<Void>`
```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<SysOssUploadVo>`
```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&currentVersion=1.0.2
```
**响应**`R<AppVersionCheckVo>`
```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<Void>`(运行时实际将命令 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 <token>`;登录、注册、忘记密码、租户列表和普通验证码接口免登录。
> 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` 格式。