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

16 KiB
Raw Blame History

涂鸦智能 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 个

支持的登录方式(解决你的核心问题!)

// 方式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 平台
  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
  • 价格:¥499(支持免费试用)
  • 操作:
    1. 在 HBuilderX 中购买/试用该插件
    2. 绑定到当前项目 AppID

1.3 uni-app 项目配置

// 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 封装涂鸦服务层

// 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 改造绑定页面

<!-- 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 改造个人中心入口

<!-- 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 涂鸦设备同步到本地平台

// 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):

<!-- 必要权限 -->
<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 插件