模块接入:
- 新增 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/** 失败关闭),不落明文
143 lines
8.4 KiB
SQL
143 lines
8.4 KiB
SQL
-- ============================================================================
|
||
-- 智能飞禽物联平台 · 项目自有补丁脚本(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 '用户邮箱';
|