iotsaas/docs/tuya-app-development-plan.md
2995840376 0ce191c448 chore: 初始化 iotsaas 版本库基线
纳入范围:
- 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
2026-10-27 12:00:41 +08:00

504 lines
16 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.

# 涂鸦智能 APP 开发方案(B2C 场景)
## 一、项目背景与目标
### 现状问题
| 问题 | 说明 |
|------|------|
| OAuth 授权页无法访问 | `openauth.tuyacn.com` DNS 解析失败 |
| H5 模式限制 | 无法使用涂鸦原生 App SDK(需原生环境) |
| 简单模式不适用 | 项目级 token,所有用户共享,**不适合 B2C** |
### 目标
- 用户在 APP 内用**手机号+验证码**登录/注册涂鸦账号
- 登录后获取**用户级 token**,操作自己的设备
- 支持 **WiFi / 蓝牙 / Matter** 配网添加设备
- 兼容小米、华为、OPPO、vivo 等主流安卓手机
---
## 二、技术方案选型
### 方案对比
| 方案 | 技术栈 | 优点 | 缺点 | 适合度 |
|------|--------|------|------|:---:|
| **A: uni-app + 原生插件** | Vue3 + Tuya 插件 | 复用现有代码、跨平台 | 需购买插件(¥499)、依赖第三方 | ⭐⭐⭐⭐⭐ |
| B: 原生 Android | Kotlin/Java + Tuya SDK | 功能最完整、性能最好 | 需重写 UI、开发周期长 | ⭐⭐⭐ |
| C: 微信小程序 | 微信小程序 + Tuya 小程序SDK | 无需下载安装、微信生态 | 仅限微信内使用 | ⭐⭐⭐ |
### 推荐方案:A — uni-app + 涂鸦原生插件
```
┌─────────────────────────────────────────────────────────┐
│ 你的 APP 架构 │
├─────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌────────────────────────────┐ │
│ │ uni-app │ │ XM-Tuya 原生插件 │ │
│ │ (Vue3) │◄──►│ (封装涂鸦 App SDK) │ │
│ │ │ │ │ │
│ │ • 用户体系 │ │ • 手机号登录/注册 │ │
│ │ • 设备列表 │ │ • WiFi/蓝牙配网 │ │
│ │ • 规则引擎 │ │ • 设备控制 │ │
│ │ • 告警中心 │ │ • 家庭管理 │ │
│ └──────────────┘ └────────────────────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌────────────────────────────────────────┐ │
│ │ 后端服务 (Spring Boot) │ │
│ │ iot-device:9081 | iot-data:9082 │ │
│ └────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────┐ │
│ │ 涂鸦 IoT 云平台 │ │
│ │ openapi.tuyacn.com │ │
│ └────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
```
---
## 三、涂鸦插件能力清单
### 已有 DCloud 插件:XM-Tuya (ID: 9081)
| 模块 | 功能 | API 数量 |
|------|------|---------|
| **用户管理** | 手机号/邮箱登录注册、验证码、修改资料 | 18 个 |
| **家庭管理** | 创建/查询家庭、房间管理、成员管理 | 19 个 |
| **设备配网** | WiFi 快连/AP模式、BLE蓝牙配网、双模配网 | 12 个 |
| **设备控制** | DP 点读取/下发、设备重命名/移除/重置 | 8 个 |
| **固件升级** | WiFi/BLE OTA 升级检查与执行 | 5 个 |
| **通用接口** | 带鉴权/无鉴权通用 API 调用 | 2 个 |
### 支持的登录方式(解决你的核心问题!)
```javascript
// 方式1: 手机号 + 验证码登录 ⭐推荐
const tuyaUser = uni.requireNativePlugin('XM-TuyaUserModule')
tuyaUser.loginWithPhone({
countryCode: '86', // 中国大陆
phone: '13800138000',
code: '123456' // 短信验证码
}, result => {
console.log('涂鸦登录成功:', result)
})
// 方式2: 手机号 + 密码登录
tuyaUser.loginWithPhonePassword({
countryCode: '86',
phone: '13800138000',
password: 'your_password'
}, callback)
// 方式3: 邮箱登录
tuyaUser.loginWithEmail({ ... })
tuyaUser.loginWithEmailCode({ ... })
```
---
## 四、开发步骤详解
### Phase 1: 准备工作(预计 0.5 天)
#### 1.1 涂鸦 IoT 平台配置
1. 登录 [涂鸦 IoT 平台](https://iot.tuya.com)
2. 进入 **云开发 → App SDK**
3. 创建应用:
- 应用类型:Android/iOS
- 包名:`com.iot.saas.app`(自定义)
- SHA1/SHA256:打包后从 keystore 获取
4. 下载 **安全图片** (`t_s.bmp`)
5. 记录 `AppKey` 和 `AppSecret`
#### 1.2 购买/试用 DCloud 插件
- 插件地址:[ext.dcloud.net.cn/plugin?id=9081](https://ext.dcloud.net.cn/plugin?id=9081)
- 价格:¥499(支持免费试用)
- 操作:
1. 在 HBuilderX 中购买/试用该插件
2. 绑定到当前项目 AppID
#### 1.3 uni-app 项目配置
```json
// manifest.json → App原生插件配置
{
"app-plus": {
"nativePlugins": {
"XM-Tuya": {
"__plugin__": true,
"__plugin_name__": "涂鸦智能生活双端插件"
}
}
}
}
```
#### 1.4 放置安全图片
```
uniapp/
└── nativeplugins/
└── XM-Tuya/
└── android/
└── assets/
└── t_s.bmp ← 从涂鸦平台下载的安全图片
```
---
### Phase 2: 涂鸦集成开发(预计 3-4 天)
#### 2.1 封装涂鸦服务层
```typescript
// src/services/tuya.ts - 涂鸦 SDK 服务封装
declare const requireNativePlugin: any
class TuyaService {
private userModule: any
private homeModule: any
private deviceModule: any
private configModule: any
constructor() {
this.userModule = requireNativePlugin('XM-TuyaUserModule')
this.homeModule = requireNativePlugin('XM-TuyaHomeModule')
this.deviceModule = requireNativePlugin('XM-TuyaDeviceModule')
this.configModule = requireNativePlugin('XM-TuyaConfigModule')
// 初始化涂鸦 SDK
// #ifdef APP-PLUS
if (this.userModule) {
this.userModule.initWithAppKey({
appKey: 'YOUR_TUYA_APP_KEY',
schemaName: 'tuyasmart' // 自定义 scheme
})
}
// #endif
}
// ========== 用户认证 ==========
/** 发送验证码 */
sendVerifyCode(phone: string, countryCode = '86'): Promise<void> {
return new Promise((resolve, reject) => {
this.userModule.sendVerifyCodeWithUserName({
countryCode,
userName: phone,
region: '86',
codeType: 1 // 1=登录验证码, 2=注册验证码
}, (res: any) => {
res.success ? resolve() : reject(new Error(res.error))
})
})
}
/** 手机号+验证码登录 */
loginWithPhoneCode(phone: string, code: string): Promise<any> {
return new Promise((resolve, reject) => {
this.userModule.loginWithPhone({
countryCode: '86',
phone,
code
}, (res: any) => {
res.success ? resolve(res.data) : reject(new Error(res.error))
})
})
}
/** 注册新账号 */
register(phone: string, code: string, password: string): Promise<void> { ... }
/** 登出 */
logout(): void {
this.userModule.logout()
}
// ========== 家庭管理 ==========
/** 获取家庭列表 */
getHomeList(): Promise<any[]> { ... }
/** 获取家庭下的设备 */
getDeviceList(homeId: number): Promise<any[]> { ... }
// ========== 设备配网 ==========
/** 获取 WiFi 配网 Token */
getWifiToken(homeId: number): Promise<string> { ... }
/** 开始配网 */
startActivator(token: string, ssid: string, password: string, type: string): void { ... }
/** 停止配网 */
stopActivator(): void { ... }
// ========== 设备控制 ==========
/** 发送 DP 指令 */
publishDps(deviceId: string, dps: Record<string, any>): Promise<void> { ... }
/** 获取设备状态 */
getDp(deviceId: string): Promise<Record<string, any>> { ... }
}
export default new TuyaService()
```
#### 2.2 改造绑定页面
```vue
<!-- pages/profile/tuya-bind.vue -->
<template>
<view class="bind-page">
<view class="header">
<text class="title">绑定涂鸦账号</text>
<text class="desc">使用涂鸦账号登录后即可管理您的智能设备</text>
</view>
<!-- 手机号输入 -->
<view class="form-item">
<input v-model="phone" placeholder="请输入手机号"
type="number" maxlength="11" />
</view>
<!-- 验证码 -->
<view class="form-item">
<input v-model="code" placeholder="请输入验证码" type="number" maxlength="6" />
<button class="code-btn" :disabled="countdown > 0" @click="sendCode">
{{ countdown > 0 ? `${countdown}s` : '获取验证码' }}
</button>
</view>
<!-- 绑定按钮 -->
<button class="submit-btn" :loading="binding" @click="handleBind">
{{ tuyaBound ? '已绑定' : '立即绑定' }}
</button>
<!-- 已有账号?显示解绑 -->
<button v-if="tuyaBound" class="unbind-btn" @click="handleUnbind">
解绑涂鸦账号
</button>
</view>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import tuyaService from '@/services/tuya'
const phone = ref('')
const code = ref('')
const binding = ref(false)
const countdown = ref(0)
const tuyaBound = ref(false)
async function sendCode() {
if (!phone.value || phone.value.length !== 11) {
uni.showToast({ title: '请输入正确手机号', icon: 'none' })
return
}
try {
await tuyaService.sendVerifyCode(phone.value)
countdown.value = 60
const timer = setInterval(() => {
countdown.value--
if (countdown.value <= 0) clearInterval(timer)
}, 1000)
uni.showToast({ title: '验证码已发送', icon: 'success' })
} catch (e: any) {
uni.showToast({ title: e.message || '发送失败', icon: 'none' })
}
}
async function handleBind() {
if (!phone.value || !code.value) {
uni.showToast({ title: '请填写完整', icon: 'none' })
return
}
try {
binding.value = true
const result = await tuyaService.loginWithPhoneCode(phone.value, code.value)
// 登录成功,保存状态
uni.setStorageSync('tuya_user_info', JSON.stringify(result))
uni.setStorageSync('tuya_bound', true)
uni.showToast({ title: '绑定成功', icon: 'success' })
setTimeout(() => uni.navigateBack(), 1500)
} catch (e: any) {
uni.showToast({ title: e.message || '绑定失败', icon: 'none' })
} finally {
binding.value = false
}
}
</script>
```
#### 2.3 改造个人中心入口
```vue
<!-- profile.vue - bindTuya 方法改造 -->
async function bindTuya() {
if (tuyaBound.value) {
// 解绑逻辑...
} else {
// 跳转到涂鸦绑定页面
// #ifdef APP-PLUS
uni.navigateTo({ url: '/pages/profile/tuya-bind' })
// #endif
// #ifndef APP-PLUS
uni.showToast({ title: '请在APP中使用此功能', icon: 'none' })
// #endif
}
}
```
---
### Phase 3: 设备同步功能(预计 2-3 天)
#### 3.1 涂鸦设备同步到本地平台
```typescript
// services/deviceSync.ts
export async function syncTuyaDevices(homeId: number) {
// 1. 从涂鸦 SDK 获取设备列表
const tuyaDevices = await tuyaService.getDeviceList(homeId)
// 2. 同步到我们后端
const syncedDevices = []
for (const dev of tuyaDevices) {
try {
const result = await post('/api/v1/tuya/devices/sync', {
tuyaDeviceId: dev.devId,
name: dev.name,
model: dev.productName,
icon: dev.iconUrl,
online: dev.isOnline,
capabilities: dev.dpCodes || {}
})
syncedDevices.push(result)
} catch (e) {
console.warn(`设备 ${dev.devId} 同步失败`, e)
}
}
return syncedDevices
}
```
---
### Phase 4: 打包发布(预计 1 天)
#### 4.1 打包流程
```
HBuilderX 操作步骤:
1. 运行 → 运行到手机或模拟器 → 制作自定义调试基座
2. 测试通过后:
发行 → 原生App-云打包
选择:
- Android
- 使用公共测试证书 (正式版用自己的证书)
- 勾选 XM-Tuya 插件
```
#### 4.2 Android 兼容性保证
| 品牌 | 兼容性说明 | 特殊处理 |
|------|----------|---------|
| **小米** | ✅ 完全兼容 | MIUI 需授权「位置权限」才能扫描 WiFi |
| **华为** | ✅ 完全兼容 | HMS 可能需要额外配置 |
| **OPPO/vivo** | ✅ 完全兼容 | ColorOS/OriginOS 无特殊要求 |
| **三星** | ✅ 兼容 | OneUI 标准安卓行为 |
**关键权限配置**(manifest.json):
```xml
<!-- 必要权限 -->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
<!-- 蓝牙配网需要 -->
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<!-- Android 12+ 新增 -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
```
---
## 五、文件结构变更
```
uniapp/src/
├── services/
│ ├── tuya.ts ← 新建:涂鸦 SDK 封装服务
│ └── deviceSync.ts ← 新建:设备同步服务
├── pages/
│ ├── profile/
│ │ ├── profile.vue ← 修改:绑定入口改为跳转
│ │ ├── tuya-bind.vue ← 新建:涂鸦账号绑定页
│ │ └── tuya-callback.vue ← 删除或保留备用
│ └── device/
│ ├── add.vue ← 修改:增加涂鸦配网方式
│ └── list.vue ← 修改:支持涂鸦设备展示
├── nativeplugins/ ← 新建目录
│ └── XM-Tuya/
│ └── android/assets/t_s.bmp
└── manifest.json ← 修改:添加原生插件配置
```
---
## 六、开发时间线
| 阶段 | 内容 | 时间 |
|------|------|------|
| **Phase 1** | 平台配置、购买插件、基础搭建 | 0.5 天 |
| **Phase 2** | 涂鸦 SDK 集成、登录/绑定功能 | 2-3 天 |
| **Phase 3** | 设备同步、配网功能完善 | 2 天 |
| **Phase 4** | 测试、适配、打包发布 | 1-2 天 |
| **合计** | | **约 6-7 天** |
---
## 七、费用预算
| 项目 | 费用 | 备注 |
|------|------|------|
| DCloud 涂鸦插件 | ¥499 | 一次性买断 |
| 涂鸦开发者账号 | 免费 | 个人/企业均可 |
| Android 签名证书 | 免费 | 或 ¥99/年用自有签名 |
| 应用商店上架 | 免费 | 华为/小米/OPPO等免费 |
---
## 八、风险与对策
| 风险 | 影响 | 对策 |
|------|------|------|
| 插件版本滞后 | SDK 为 4.X,非最新 | 先试用评估,满足需求即可 |
| 云打包排队 | 发布等待时间长 | 使用自定义基座本地调试 |
| 权限被拒 | Android 10+ 后台限制 | 引导用户手动授权 |
| 涂鸦服务变动 | API 变更导致不兼容 | 关注官方更新通知 |
---
## 九、下一步行动
1. ✅ **确认方案** — 你确认是否采用此方案
2. 🔧 **我帮你实施** — 开始编码改造
3. 📦 **准备材料** — 你去涂鸦 IoT 平台:
- 创建 App SDK 应用
- 下载安全图片
- 记录 AppKey/AppSecret
4. 💰 **购买插件** — 在 HBuilderX 中购买/试用 XM-Tuya 插件