Files
bird_monitoring/script/sql/bm_patch.sql
yuhaiming 79b739fe02 feat(device): 一期设备域 I1-I5 全量实现(APP 接口/后台管理/OTA/MQTT/直播)
模块接入:
- 新增 ruoyi-device(org.dromara.device)并注册到根/ruoyi-modules/ruoyi-admin pom

APP 侧(/app/**,需登录,仅本人绑定设备):
- 账号闭环:短信/邮箱验证码注册登录、密码重置(不跨界返回散列)
- 设备:绑定/列表/详情/重命名/解绑(越权统一"设备不存在",防 IDOR)
- 眼罩开关(/app/device/{deviceNo}/hood,低电不对称拦截)
- 定位(/app/device/{deviceNo}/location)
- 直播(/app/stream 6 端点 + /app/device/{deviceNo}/live,自建 ZLMediaKit,设备主动推流)

后台(/device/**):
- 设备 CRUD/统计/删除/导出(ExcelBuilder)
- OTA(6 端点:升级包管理 + 批量下发,total==created+skipped+failed)

MQTT(T05):
- 遥测/事件/ack 监听、指令下发+回执(防串设备)、离线扫描、低电告警
- 报文身份仅来自 Topic,反查 device_id 防跨设备伪造

数据与脚本:
- script/sql/ry_device.sql(6 表/字典/权限字/菜单)、bm_patch.sql
- script/mqtt 设备模拟器(mqtt-simulate.js)

安全:
- MQTT broker 凭据外置为环境变量 MQTT_USERNAME/MQTT_PASSWORD,不落明文
- ZLM webhook 密钥外置为环境变量 ZLM_SECRET(未配置时 /zlm/hook/** 失败关闭),不落明文
2026-09-17 10:21:27 +08:00

143 lines
8.4 KiB
SQL
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.
-- ============================================================================
-- 智能飞禽物联平台 · 项目自有补丁脚本(PROJECT PATCH,非框架基线)
-- 文件:script/sql/bm_patch.sql
-- 说明:本文件承载「项目自有的库表变更」,与框架基线 ry_vue.sql 分离维护,
-- 以便将来框架(RuoYi-Vue-Plus)升级时按补丁重放、避免与基线冲突。
-- 【请勿修改 ry_vue.sql 以保持与上游基线一致】。
-- 执行方式:连接业务库(bird_monitoring)后整体执行本文件。
-- ============================================================================
-- ----------------------------------------------------------------------------
-- 补丁 1:为 sys_user.phone_number 增加唯一索引(uk_sys_user_phone_number)
-- ----------------------------------------------------------------------------
-- 背景:
-- 框架基线 ry_vue.sql 中 sys_user.phone_number 仅有普通索引
-- key idx_sys_user_phone (phone_number) -- 非唯一
-- 无法在 DB 层阻止「同一手机号注册多个账号」。
-- APP 账号闭环(迭代 I2)的注册为「先查后插」,存在并发同号双写(TOCTOU)风险;
-- 应用层已用「按手机号加 Redisson 锁 + 判重」将其降到接近零(见 AppAuthServiceImpl#register),
-- 但 **DB 唯一键才是最终防线**,故以本补丁补充该约束;应用层锁保留作为双保险。
--
-- ★ 执行前必读 · 前置检查(务必先跑 SELECT,确认无重复再执行 ALTER)
-- 1) 检查是否存在重复的「非空」手机号:
-- SELECT phone_number, COUNT(*) AS cnt
-- FROM sys_user
-- WHERE phone_number IS NOT NULL AND phone_number <> ''
-- GROUP BY phone_number
-- HAVING COUNT(*) > 1;
-- → 期望返回 0 行。若返回了行,须先人工清理重复数据(每个手机号仅保留一条),
-- 再执行本补丁;否则第 2 步 ALTER 会因重复键失败(1062)。
--
-- 2) 空字符串归一化说明:
-- phone_number 列默认值为 ''(可存在多行同为 ''),而 MySQL 唯一索引
-- **不允许多个 ''(空串)**、但**允许多个 NULL**(NULL 不参与唯一性判定)。
-- 因此本补丁先将 '' 归一化为 NULL,使唯一约束只作用于「非空手机号」。
--
-- ★ 执行前已核验(2026-09-16 实测线上库 bird_monitoring.sys_user)
-- - 总行数:3;
-- - 非空 phone_number:仅 1 行(属 sys_user);
-- - 重复手机号:无(前置检查 SELECT 返回 0 行);
-- - app_user 记录:0 条。
-- => 满足前置条件,可安全执行。执行后如需复核:
-- SHOW INDEX FROM sys_user WHERE Key_name = 'uk_sys_user_phone_number';
--
-- ★ MySQL 限制说明(不要用存储过程硬凑 IF NOT EXISTS)
-- MySQL 不支持 "ALTER TABLE ... ADD UNIQUE KEY IF NOT EXISTS"。
-- 本补丁**未**用存储过程模拟幂等;若重复执行本文件,将出现如下**预期报错**(可忽略):
-- ERROR 1061 (42000): Duplicate key name 'uk_sys_user_phone_number'
-- 看到该报错即表示索引已存在,无需再次创建。
--
-- ★ 影响面提示(重要)
-- 该唯一索引作用于整张 sys_user 表(含 sys_user 与 app_user 两类 user_type),
-- 即执行后**手机号在全表范围内唯一**(非空手机号不可在「后台用户」与「APP 用户」间重复)。
-- 这与迭代 I2 早期「两类用户手机号互不冲突」的表述不同:现在由 DB 强制全局唯一。
-- 若后台新增用户时用到已被 APP 占用的手机号,将因该唯一键报 1062(属预期约束)。
--
-- ★ 回滚(如需撤销)
-- ALTER TABLE sys_user DROP INDEX uk_sys_user_phone_number;
-- ----------------------------------------------------------------------------
-- 步骤 1:将空字符串手机号归一化为 NULL(唯一索引允许多个 NULL)
UPDATE sys_user SET phone_number = NULL WHERE phone_number = '';
-- 步骤 2:新增唯一索引
ALTER TABLE sys_user ADD UNIQUE KEY uk_sys_user_phone_number (phone_number);
-- ----------------------------------------------------------------------------
-- 补丁 2:为 sys_user.email 增加唯一索引(uk_sys_user_email)
-- ----------------------------------------------------------------------------
-- 背景:
-- 迭代「邮箱注册」新增邮箱账号入口后,email 成为与 phone_number 并列的账号标识,
-- 同样需要「先查后插」之外的第二道防线(DB 唯一键)。
-- 框架基线 ry_vue.sql 中 sys_user.email 无任何索引。
--
-- ★ 执行前必读 · 前置检查
-- 1) 重复非空邮箱检查:
-- SELECT email, COUNT(*) AS cnt FROM sys_user
-- WHERE email IS NOT NULL AND email <> ''
-- GROUP BY email HAVING COUNT(*) > 1;
-- → 期望返回 0 行。
-- 2) 空串归一化:email 列默认值为 ''(实测线上 9 行中 8 行为 ''),
-- 而 MySQL 唯一索引允许多个 NULL 但**不允许多个 ''**,故必须先归一化。
--
-- ★ 执行前已核验(2026-09-17 实测线上库 bird_monitoring.sys_user)
-- - 总行数 9;非空 email 仅 1 行(admin 的 crazyLionLi@163.com);
-- - 重复邮箱:无(前置检查 SELECT 返回 0 行);
-- - app_user 记录 8 条,email 全为 ''。
-- => 满足前置条件,可安全执行。
--
-- ★ 影响面提示
-- 与手机号同一口径:该唯一索引作用于整张 sys_user 表(含 sys_user 与 app_user),
-- 即邮箱在全表范围内唯一。若后台新增用户填写了已被 APP 占用的邮箱,将报 1062(属预期约束)。
--
-- ★ 重复执行本文件时的预期报错(可忽略)
-- ERROR 1061 (42000): Duplicate key name 'uk_sys_user_email'
--
-- ★ 回滚
-- ALTER TABLE sys_user DROP INDEX uk_sys_user_email;
-- ----------------------------------------------------------------------------
-- 步骤 1:将空字符串邮箱归一化为 NULL
UPDATE sys_user SET email = NULL WHERE email = '';
-- 步骤 2:新增唯一索引
ALTER TABLE sys_user ADD UNIQUE KEY uk_sys_user_email (email);
-- ----------------------------------------------------------------------------
-- 补丁 3:将 sys_user.phone_number / email 的列默认值 '' 改为 NULL
-- ----------------------------------------------------------------------------
-- ★ 本补丁是「邮箱注册」能否落地的关键前提,不可省略。
--
-- 背景(两个坑叠加才会暴露):
-- 1) 本项目 MyBatis-Plus 全局字段策略为 insertStrategy: NOT_NULL(见
-- ruoyi-common-mybatis/src/main/resources/common-mybatis.yml)——
-- 实体字段为 null 时,该列会被**整个从 INSERT 语句中省略**,而不是写入 NULL;
-- 2) 此时生效的是**列默认值**,而 phone_number / email 的基线默认值都是 ''。
--
-- 于是:邮箱注册创建的账号不填手机号 ⇒ INSERT 省略 phone_number ⇒ 落库为 ''
-- ⇒ 第二个邮箱注册账号再落 '' ⇒ 撞 uk_sys_user_phone_number 报 1062。
-- 同一问题对称存在于 email:手机号注册的账号不填邮箱 ⇒ 落 '' ⇒ 第二个撞 uk_sys_user_email。
--
-- 修法:把两列的默认值改为 NULL(NULL 不参与唯一性判定,可多行共存)。
-- 这样「字段为 null ⇒ INSERT 省略该列 ⇒ 落库 NULL」成为一致且唯一的行为,
-- 无需修改框架实体 SysUser(不改框架文件的铁律得以保持)。
--
-- ★ 影响面提示(有意为之的正向变化)
-- 后台「新增用户」不填邮箱/手机号时,落库由 '' 变为 NULL。
-- 应用层所有比较均已用 StringUtils.isBlank 处理('' 与 null 等价),
-- 实测已确认无逻辑依赖 '' 这一具体取值。
--
-- ★ 回滚(恢复到基线默认值,回滚前须先处理已落 NULL 的行)
-- ALTER TABLE sys_user MODIFY COLUMN phone_number varchar(11) DEFAULT '' COMMENT '手机号码';
-- ALTER TABLE sys_user MODIFY COLUMN email varchar(50) DEFAULT '' COMMENT '用户邮箱';
-- ----------------------------------------------------------------------------
-- 步骤 1:手机号列默认值改为 NULL
ALTER TABLE sys_user MODIFY COLUMN phone_number varchar(11) DEFAULT NULL COMMENT '手机号码';
-- 步骤 2:邮箱列默认值改为 NULL
ALTER TABLE sys_user MODIFY COLUMN email varchar(50) DEFAULT NULL COMMENT '用户邮箱';