202 lines
12 KiB
SQL
202 lines
12 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 '用户邮箱';
|
||
|
||
|
||
-- ----------------------------------------------------------------------------
|
||
-- 补丁 4:为 bm_device 增加「设备级低电告警阈值」两列
|
||
-- (hood_battery_threshold / ring_battery_threshold)
|
||
-- ----------------------------------------------------------------------------
|
||
-- 背景:
|
||
-- 低电告警阈值原先是**全局配置**(application.yml 的 device.mqtt.low-battery-threshold=20),
|
||
-- 全平台共用一个数。但 PRD Q9「头套/脚环低电阈值」至今未决,且不同型号设备的电池容量与
|
||
-- 放电特性并不相同,单一全局值无法覆盖,故改为按设备配置。
|
||
--
|
||
-- ★ 口径说明(重要)
|
||
-- 1) 本列 = **触发阈值**;**恢复阈值不落库**,由应用层派生:
|
||
-- recover = trigger + (low-battery-recover-threshold - low-battery-threshold)
|
||
-- 工程默认即 20 / 25 ⇒ 回差 5。不把恢复阈值也暴露给用户,
|
||
-- 是为了从根上杜绝「恢复 ≤ 触发」这种会让设备卡死在告警态、再也恢复不了的配置。
|
||
-- 2) 取值语义:电量 ≤ 该值即触发低电告警。**0 表示不触发**(等同对该设备关闭低电保护),
|
||
-- 因为「电量 ≤ 0」实际不可能成立。同一取值**同时**作用于「低电拒绝遮盖指令」的拦截判定——
|
||
-- 两者刻意共用同一阈值,避免出现「已经告警了却还能遮盖」的口径撕裂。
|
||
-- 3) 头套与脚环**各自独立**取值,互不影响。
|
||
--
|
||
-- ★ 为什么是 NOT NULL DEFAULT 20,而不是可空(NULL = 未配置)
|
||
-- a) ALTER 时一次性把存量行回填为 20,与改造前的全局行为**逐字一致**,升级零行为变化;
|
||
-- b) 前端列表要按行判定「电量是否低于阈值并标红」(PRD §7.1),若列可空,
|
||
-- 前端还得再知道一次全局默认值 —— 又变成「两处真相」;后端也得额外输出 effective 字段
|
||
-- 并遍历列表回填。NOT NULL 直接把这个问题消灭在 DDL 层;
|
||
-- c) 与本表 status / hood_connected / signal_level 等列的既有风格一致(均 NOT NULL 带默认值)。
|
||
-- 注:新增设备时若请求体不带该字段,MP 全局 insertStrategy=NOT_NULL 会**省略该列**,
|
||
-- 此时生效的正是本处的列默认值 20,符合预期。
|
||
--
|
||
-- ★ 执行前必读 · 前置检查(确认列尚不存在,避免重复执行报 1060)
|
||
-- SELECT column_name, column_type, is_nullable, column_default
|
||
-- FROM information_schema.columns
|
||
-- WHERE table_schema = 'bird_monitoring' AND table_name = 'bm_device'
|
||
-- AND column_name IN ('hood_battery_threshold', 'ring_battery_threshold');
|
||
-- → 期望返回 0 行。
|
||
--
|
||
-- ★ 重复执行本文件时的预期报错(可忽略)
|
||
-- ERROR 1060 (42S21): Duplicate column name 'hood_battery_threshold'
|
||
--
|
||
-- ★ 影响面提示
|
||
-- 两列为 tinyint(有符号,范围 -128~127),取值域由应用层 @Min(0)/@Max(100) 校验;
|
||
-- 本补丁只做「加列 + 回填默认值」,不改动任何既有业务数据。
|
||
--
|
||
-- ★ 回滚
|
||
-- ALTER TABLE bm_device
|
||
-- DROP COLUMN hood_battery_threshold,
|
||
-- DROP COLUMN ring_battery_threshold;
|
||
-- ----------------------------------------------------------------------------
|
||
|
||
-- 步骤 1:头套低电告警阈值(存量行回填 20,与改造前的全局默认一致)
|
||
ALTER TABLE bm_device
|
||
ADD COLUMN hood_battery_threshold tinyint NOT NULL DEFAULT 20
|
||
COMMENT '头套低电告警阈值0-100(≤该值触发,0=不触发)';
|
||
|
||
-- 步骤 2:脚环低电告警阈值(存量行同样回填 20)
|
||
ALTER TABLE bm_device
|
||
ADD COLUMN ring_battery_threshold tinyint NOT NULL DEFAULT 20
|
||
COMMENT '脚环低电告警阈值0-100(≤该值触发,0=不触发)';
|