436 lines
31 KiB
SQL
436 lines
31 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=不触发)';
|
||
|
||
|
||
-- ----------------------------------------------------------------------------
|
||
-- 补丁 5:sys_user.phone_number 加长以支持国际号码
|
||
-- ----------------------------------------------------------------------------
|
||
-- 背景:APP 国际短信按 E.164 落库为「+国家码+号码」(最长 16 位),原 varchar(11) 装不下。
|
||
-- 国内存量 11 位号码不受影响。
|
||
--
|
||
-- ★ 回滚
|
||
-- ALTER TABLE sys_user MODIFY COLUMN phone_number varchar(11) DEFAULT NULL COMMENT '手机号码';
|
||
-- ----------------------------------------------------------------------------
|
||
ALTER TABLE sys_user MODIFY COLUMN phone_number varchar(20) DEFAULT NULL COMMENT '手机号码';
|
||
|
||
|
||
-- ----------------------------------------------------------------------------
|
||
-- 补丁 6:告警记录「处理流转」+ APP 消息表(告警消息推送)
|
||
-- ----------------------------------------------------------------------------
|
||
-- 背景:
|
||
-- 原设计(一期初版)把告警定为「只读展示」:bm_device_alert 只有 status 一个状态列,
|
||
-- 没有处理人/处理时间,也没有逻辑删除;APP 侧不做消息中心(开发计划 D8)。
|
||
-- 本次范围变更为:
|
||
-- 1) 后台「告警记录」页支持「标记处理 / 批量处理 / 删除」——需要处理痕迹与可追溯删除;
|
||
-- 2) 告警发生时向**设备绑定的 APP 用户**推送一条消息(全部类型:离线 + 头套低电 + 脚环低电),
|
||
-- APP 需要未读角标 ⇒ 消息必须有「已读」状态。
|
||
--
|
||
-- ★ 为什么新建 bm_app_message 而不是复用框架的 sys_message
|
||
-- 1) sys_message **没有「已读」字段**(只有 sendUserIds 接收人串),无法支撑 APP 未读角标;
|
||
-- 2) 给 sys_message 加列 = 改框架表,违反项目铁律「ruoyi-* 框架不改,业务只在 ruoyi-device」;
|
||
-- 3) 告警消息需要携带 device_no / alert_id 以便 APP 点击跳设备详情,属业务字段。
|
||
-- 职责划分:bm_device_alert = 设备事实(含处理流程);bm_app_message = 推送副本(含收件人与已读)。
|
||
--
|
||
-- ★ del_flag 的语义与既有约束
|
||
-- 与 bm_device 一致:0 存在 / 1 删除(common-mybatis.yml: logicDeleteValue=1)。
|
||
-- 后台「删除」走 MyBatis-Plus 逻辑删除,**不物理抹除**告警审计痕迹。
|
||
--
|
||
-- ★ 执行前必读 · 前置检查(确认列尚不存在,避免重复执行报 1060)
|
||
-- SELECT column_name FROM information_schema.columns
|
||
-- WHERE table_schema = 'bird_monitoring' AND table_name = 'bm_device_alert'
|
||
-- AND column_name IN ('handle_by', 'handle_time', 'message_user_id', 'del_flag');
|
||
-- → 期望返回 0 行。
|
||
--
|
||
-- ★ 重复执行本文件时的预期报错(可忽略)
|
||
-- ERROR 1060 (42S21): Duplicate column name 'handle_by'
|
||
-- ERROR 1050 (42S01): Table 'bm_app_message' already exists
|
||
-- ERROR 1062 (23000): Duplicate entry '1771400000000000005' for key 'PRIMARY'(菜单重复插入)
|
||
--
|
||
-- ★ 回滚
|
||
-- ALTER TABLE bm_device_alert
|
||
-- DROP COLUMN handle_by, DROP COLUMN handle_time, DROP COLUMN message_user_id,
|
||
-- DROP COLUMN del_flag,
|
||
-- DROP INDEX idx_status_time;
|
||
-- DROP TABLE bm_app_message;
|
||
-- DELETE FROM sys_role_menu WHERE menu_id IN (1771400000000000005,1771400000000000019,
|
||
-- 1771400000000000020,1771400000000000021);
|
||
-- DELETE FROM sys_menu WHERE menu_id IN (1771400000000000005,1771400000000000019,
|
||
-- 1771400000000000020,1771400000000000021);
|
||
-- ----------------------------------------------------------------------------
|
||
|
||
-- 步骤 1:告警表补「处理人 / 处理时间 / 逻辑删除」
|
||
-- 注:status 列原注释写着「一期只读展示」,本次变更后已失效,一并纠正注释。
|
||
ALTER TABLE bm_device_alert
|
||
MODIFY COLUMN status char(1) NOT NULL COMMENT '处理状态(0未处理 1已处理)';
|
||
|
||
ALTER TABLE bm_device_alert
|
||
ADD COLUMN handle_by bigint(20) DEFAULT NULL COMMENT '处理人(sys_user.user_id,后台账号)';
|
||
|
||
ALTER TABLE bm_device_alert
|
||
ADD COLUMN handle_time datetime DEFAULT NULL COMMENT '处理时间';
|
||
|
||
-- 告警行同时承担消息 outbox:保存告警发生时的收件人快照,消息写入失败后可据此安全补偿。
|
||
ALTER TABLE bm_device_alert
|
||
ADD COLUMN message_user_id bigint(20) DEFAULT NULL COMMENT '告警发生时的APP收件人快照(用于消息补偿)';
|
||
|
||
-- del_flag 用 NOT NULL DEFAULT '0':与 bm_device / bm_ota_package / bm_ota_task 保持一致。
|
||
-- 必须 NOT NULL:MyBatis-Plus 逻辑删除生成 WHERE del_flag = '0',
|
||
-- 若某行是 NULL 会被静默排除在所有查询之外(数据「消失」而非报错)。
|
||
-- 存量行由 ADD COLUMN 的 DEFAULT 一次性回填 '0'(全部视为存在),升级零行为变化。
|
||
ALTER TABLE bm_device_alert
|
||
ADD COLUMN del_flag char(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除(0存在 1删除)';
|
||
|
||
-- 「未处理优先 + 时间倒序」是告警列表的默认排序,建复合索引支撑。
|
||
ALTER TABLE bm_device_alert ADD INDEX idx_status_time (status, alert_time);
|
||
|
||
-- 步骤 2:APP 消息表
|
||
CREATE TABLE IF NOT EXISTS bm_app_message (
|
||
message_id bigint(20) NOT NULL COMMENT '主键(雪花)',
|
||
user_id bigint(20) NOT NULL COMMENT '接收用户(sys_user.user_id,APP 用户)',
|
||
device_id bigint(20) DEFAULT NULL COMMENT '关联设备主键',
|
||
device_no varchar(32) DEFAULT NULL COMMENT '关联设备编号(冗余,供展示与跳转)',
|
||
alert_id bigint(20) DEFAULT NULL COMMENT '关联告警主键(bm_device_alert)',
|
||
alert_type varchar(32) DEFAULT NULL COMMENT '告警类型(offline/low_battery_hood/low_battery_ring)',
|
||
alert_level char(1) DEFAULT NULL COMMENT '告警级别(0提示 1警告 2严重)',
|
||
title varchar(128) NOT NULL COMMENT '消息标题',
|
||
content varchar(255) DEFAULT NULL COMMENT '消息内容',
|
||
read_flag char(1) NOT NULL DEFAULT '0' COMMENT '已读标记(0未读 1已读)',
|
||
read_time datetime DEFAULT NULL COMMENT '已读时间',
|
||
create_time datetime NOT NULL COMMENT '产生时间',
|
||
PRIMARY KEY (message_id),
|
||
UNIQUE KEY uk_alert_id (alert_id),
|
||
KEY idx_user_read (user_id, read_flag),
|
||
KEY idx_user_time (user_id, create_time, message_id)
|
||
) ENGINE=InnoDB COMMENT = 'APP 消息(一期仅承载设备告警通知)';
|
||
|
||
-- 步骤 3:菜单 + 权限字(与 ry_device.sql「十、菜单 + 权限字」保持一致)
|
||
INSERT INTO sys_menu VALUES(1771400000000000005, '告警记录', 1771400000000000001, 4, 'alert', 'device/alert/index', '', 'N', 'Y', 'C', '0', '0', 'device:alert:list', 'message', '', '', 1761000000000000103, 1761100000000000001, sysdate(), NULL, NULL, '告警记录菜单');
|
||
INSERT INTO sys_menu VALUES(1771400000000000019, '告警查询', 1771400000000000005, 1, '', '', '', 'N', 'Y', 'F', '0', '0', 'device:alert:query', '#', '', '', 1761000000000000103, 1761100000000000001, sysdate(), NULL, NULL, '');
|
||
INSERT INTO sys_menu VALUES(1771400000000000020, '告警处理', 1771400000000000005, 2, '', '', '', 'N', 'Y', 'F', '0', '0', 'device:alert:edit', '#', '', '', 1761000000000000103, 1761100000000000001, sysdate(), NULL, NULL, '');
|
||
INSERT INTO sys_menu VALUES(1771400000000000021, '告警删除', 1771400000000000005, 3, '', '', '', 'N', 'Y', 'F', '0', '0', 'device:alert:remove', '#', '', '', 1761000000000000103, 1761100000000000001, sysdate(), NULL, NULL, '');
|
||
|
||
-- 步骤 4:角色-菜单关联
|
||
-- 设备管理员:全部告警权限
|
||
INSERT INTO sys_role_menu VALUES (1771300000000000001, 1771400000000000005);
|
||
INSERT INTO sys_role_menu VALUES (1771300000000000001, 1771400000000000019);
|
||
INSERT INTO sys_role_menu VALUES (1771300000000000001, 1771400000000000020);
|
||
INSERT INTO sys_role_menu VALUES (1771300000000000001, 1771400000000000021);
|
||
-- 审计员:只读(菜单 + 查询,不含处理与删除)
|
||
INSERT INTO sys_role_menu VALUES (1771300000000000002, 1771400000000000005);
|
||
INSERT INTO sys_role_menu VALUES (1771300000000000002, 1771400000000000019);
|
||
|
||
|
||
-- ----------------------------------------------------------------------------
|
||
-- 补丁 7:APP 客户端版本升级(bm_app_version 表 + 2 字典 + 菜单/权限字 + 角色授权
|
||
-- + 设备管理员补授框架上传权限 system:oss:upload)
|
||
-- ----------------------------------------------------------------------------
|
||
-- 背景:
|
||
-- 平台此前无「APP 客户端版本」概念,APP 发新版后老用户无感知,且缺少「不升级不可用」手段。
|
||
-- 本补丁落地「APP 客户端版本升级」功能的数据基线:后台登记 APP 安装包(Android / iOS 口径一致)
|
||
-- 与可选的 App Store 引导地址,APP 冷启动调用 GET /app/version/latest 判定是否有新版。
|
||
-- 业务上**与设备 OTA 固件升级完全独立**
|
||
-- (不共表、不共权限字、不共页面),仅共用「上传包到 OSS」这一技术手段。
|
||
--
|
||
-- ★ 关键口径(务必遵守)
|
||
-- 1) 版本比较**只用整数构建号 version_code**(int,32 位正整数,人工递增),version_name 仅展示。
|
||
-- ⚠️ 禁止用时间戳式构建号:int 上界 2147483647,时间戳(如 202609181200)会直接溢出。
|
||
-- 建议公式:主版本*1000000 + 次版本*1000 + 修订号(1.0.10 → 1000010)。
|
||
-- 2) 唯一键 uk_platform_version_code (platform, version_code) **不区分逻辑删除**(沿用项目既有约定,
|
||
-- 与 bm_device.device_no「逻辑删除后编号不可复用」同口径):记录被逻辑删除后,同平台同构建号
|
||
-- **不可再新增**。构建号填错的正确处理是**编辑(PUT)修正**,不是删除重建。
|
||
-- 3) file_size 用 bigint(对齐 OTA 先例);框架把 Long 序列化为**字符串**,APP 端需 Number() 后转换展示。
|
||
-- 数值由前端从本地文件对象 file.raw.size 取值一并提交(框架上传响应体不含 fileSize)。
|
||
-- 4) md5 本期**不采集**(框架上传响应只有 url/fileName/ossId):列保留但允许为空,不参与任何校验。
|
||
-- 5) 两平台(Android / iOS)均上传安装包、字段与校验口径一致;App Store 引导地址 store_url 为**选填**,
|
||
-- 填了就落库、就在检查更新接口下发,不填存 null。强更对 iOS 仍只能引导、无法真正拦截(系统限制)。
|
||
--
|
||
-- ★ 执行前必读 · 前置检查(确认表/菜单尚不存在,避免重复执行报错)
|
||
-- SELECT COUNT(*) FROM information_schema.tables
|
||
-- WHERE table_schema='bird_monitoring' AND table_name='bm_app_version'; -- 期望 0
|
||
-- SELECT COUNT(*) FROM sys_menu WHERE menu_id=1771400000000000006; -- 期望 0
|
||
--
|
||
-- ★ 重复执行本文件时的预期报错(可忽略)
|
||
-- ERROR 1050 (42S01): Table 'bm_app_version' already exists
|
||
-- ERROR 1062 (23000): Duplicate entry '1771500000000000009' for key 'PRIMARY'(字典类型重复)
|
||
-- ERROR 1062 (23000): Duplicate entry '1771600000000000024' for key 'PRIMARY'(字典数据重复)
|
||
-- ERROR 1062 (23000): Duplicate entry '1771400000000000006' for key 'PRIMARY'(菜单重复)
|
||
-- ERROR 1062 (23000): Duplicate entry '1771300000000000001-1761400000000001601' for key 'PRIMARY'(角色授权重复)
|
||
--
|
||
-- ★ 影响面提示
|
||
-- 仅新增表 / 字典 / 菜单 / 授权,不改动任何既有表结构与既有业务数据。
|
||
-- 设备管理员补授 system:oss:upload 后,该角色即可调用框架 POST /resource/oss/upload
|
||
-- (此前 403 的根因即缺此权限字,本修复同时惠及既有 OTA 上传页);**只补上传这一条**,
|
||
-- 读/删/改配置(system:oss:list/query/remove、system:ossConfig:*)仍属超管职责,不授。
|
||
--
|
||
-- ★ 回滚
|
||
-- DELETE FROM sys_role_menu WHERE menu_id IN (1771400000000000006,1771400000000000022,
|
||
-- 1771400000000000023,1771400000000000024,1771400000000000025)
|
||
-- OR (role_id=1771300000000000001 AND menu_id=1761400000000001601);
|
||
-- DELETE FROM sys_menu WHERE menu_id IN (1771400000000000006,1771400000000000022,
|
||
-- 1771400000000000023,1771400000000000024,1771400000000000025);
|
||
-- DELETE FROM sys_dict_data WHERE dict_type IN ('bm_app_platform','bm_app_version_status');
|
||
-- DELETE FROM sys_dict_type WHERE dict_type IN ('bm_app_platform','bm_app_version_status');
|
||
-- DROP TABLE bm_app_version;
|
||
-- ----------------------------------------------------------------------------
|
||
|
||
-- 步骤 1:APP 客户端版本表(第 8 张业务表)
|
||
CREATE TABLE IF NOT EXISTS bm_app_version (
|
||
version_id bigint(20) NOT NULL COMMENT '主键(雪花)',
|
||
platform varchar(16) NOT NULL COMMENT '平台(android/ios,字典 bm_app_platform)',
|
||
version_name varchar(32) NOT NULL COMMENT '展示版本号,如 1.0.10(仅展示,不参与比较)',
|
||
version_code int(11) NOT NULL COMMENT '构建号,32位正整数、单调递增、平台内唯一(唯一比较依据;禁止时间戳式构建号)',
|
||
force_update char(1) NOT NULL DEFAULT '0' COMMENT '强制更新(0否 1是;无字典,页面本地映射)',
|
||
oss_id bigint(20) DEFAULT NULL COMMENT '关联 sys_oss.oss_id(安装包,两平台均上传)',
|
||
file_url varchar(500) DEFAULT NULL COMMENT '安装包 URL(两平台均上传;冗余自 sys_oss,供 APP 直接下载)',
|
||
file_name varchar(255) DEFAULT NULL COMMENT '安装包文件名(两平台均上传)',
|
||
file_size bigint(20) DEFAULT NULL COMMENT '安装包字节数(两平台均上传;框架序列化为字符串,前端取 file.raw.size 提交)',
|
||
md5 varchar(64) DEFAULT NULL COMMENT '安装包校验值 MD5(预留,本期不填,不参与校验)',
|
||
store_url varchar(500) DEFAULT NULL COMMENT 'App Store 引导地址(选填)',
|
||
changelog varchar(500) DEFAULT NULL COMMENT '更新说明(纯文本)',
|
||
status char(1) NOT NULL DEFAULT '0' COMMENT '状态(0停用 1启用,字典 bm_app_version_status)',
|
||
publish_time datetime DEFAULT NULL COMMENT '发布时间(首次置为启用时写入)',
|
||
remark varchar(500) DEFAULT NULL COMMENT '备注',
|
||
create_dept bigint(20) DEFAULT NULL COMMENT '创建部门',
|
||
create_by bigint(20) DEFAULT NULL COMMENT '创建者',
|
||
create_time datetime COMMENT '创建时间',
|
||
update_by bigint(20) DEFAULT NULL COMMENT '更新者',
|
||
update_time datetime COMMENT '更新时间',
|
||
del_flag char(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除(0存在 1删除)',
|
||
PRIMARY KEY (version_id),
|
||
UNIQUE KEY uk_platform_version_code (platform, version_code),
|
||
UNIQUE KEY uk_platform_version_name (platform, version_name),
|
||
KEY idx_platform_status (platform, status),
|
||
KEY idx_create_time (create_time)
|
||
) ENGINE=InnoDB COMMENT = 'APP客户端版本(后台维护,APP冷启动检查更新)';
|
||
|
||
-- 步骤 2:字典类型(2 个)
|
||
INSERT INTO sys_dict_type VALUES(1771500000000000009, 'APP平台', 'bm_app_platform', 1761000000000000103, 1761100000000000001, sysdate(), NULL, NULL, 'APP平台列表');
|
||
INSERT INTO sys_dict_type VALUES(1771500000000000010, 'APP版本状态', 'bm_app_version_status', 1761000000000000103, 1761100000000000001, sysdate(), NULL, NULL, 'APP版本状态列表');
|
||
|
||
-- 步骤 3:字典数据(4 条)
|
||
INSERT INTO sys_dict_data VALUES(1771600000000000024, 1, '安卓', 'android', 'bm_app_platform', '', 'success', 'Y', 1761000000000000103, 1761100000000000001, sysdate(), NULL, NULL, '安卓');
|
||
INSERT INTO sys_dict_data VALUES(1771600000000000025, 2, 'iOS', 'ios', 'bm_app_platform', '', 'primary', 'N', 1761000000000000103, 1761100000000000001, sysdate(), NULL, NULL, 'iOS');
|
||
INSERT INTO sys_dict_data VALUES(1771600000000000026, 1, '停用', '0', 'bm_app_version_status', '', 'danger', 'N', 1761000000000000103, 1761100000000000001, sysdate(), NULL, NULL, '停用');
|
||
INSERT INTO sys_dict_data VALUES(1771600000000000027, 2, '启用', '1', 'bm_app_version_status', '', 'success', 'Y', 1761000000000000103, 1761100000000000001, sysdate(), NULL, NULL, '启用');
|
||
|
||
-- 步骤 4:菜单 + 权限字(挂「设备管理」目录 1771400000000000001;component 填 src/views/ 之后路径)
|
||
INSERT INTO sys_menu VALUES(1771400000000000006, 'APP版本管理', 1771400000000000001, 5, 'app-version', 'device/appVersion/index', '', 'N', 'Y', 'C', '0', '0', 'device:appversion:list', 'list', '', '', 1761000000000000103, 1761100000000000001, sysdate(), NULL, NULL, 'APP版本管理菜单');
|
||
INSERT INTO sys_menu VALUES(1771400000000000022, '版本查询', 1771400000000000006, 1, '', '', '', 'N', 'Y', 'F', '0', '0', 'device:appversion:query', '#', '', '', 1761000000000000103, 1761100000000000001, sysdate(), NULL, NULL, '');
|
||
INSERT INTO sys_menu VALUES(1771400000000000023, '版本新增', 1771400000000000006, 2, '', '', '', 'N', 'Y', 'F', '0', '0', 'device:appversion:add', '#', '', '', 1761000000000000103, 1761100000000000001, sysdate(), NULL, NULL, '');
|
||
INSERT INTO sys_menu VALUES(1771400000000000024, '版本修改', 1771400000000000006, 3, '', '', '', 'N', 'Y', 'F', '0', '0', 'device:appversion:edit', '#', '', '', 1761000000000000103, 1761100000000000001, sysdate(), NULL, NULL, '');
|
||
INSERT INTO sys_menu VALUES(1771400000000000025, '版本删除', 1771400000000000006, 4, '', '', '', 'N', 'Y', 'F', '0', '0', 'device:appversion:remove', '#', '', '', 1761000000000000103, 1761100000000000001, sysdate(), NULL, NULL, '');
|
||
|
||
-- 步骤 5:角色授权
|
||
-- 设备管理员:菜单 + 全部 4 个按钮
|
||
INSERT INTO sys_role_menu VALUES (1771300000000000001, 1771400000000000006);
|
||
INSERT INTO sys_role_menu VALUES (1771300000000000001, 1771400000000000022);
|
||
INSERT INTO sys_role_menu VALUES (1771300000000000001, 1771400000000000023);
|
||
INSERT INTO sys_role_menu VALUES (1771300000000000001, 1771400000000000024);
|
||
INSERT INTO sys_role_menu VALUES (1771300000000000001, 1771400000000000025);
|
||
-- 设备管理员:补授框架上传权限字 system:oss:upload
|
||
-- 该权限字挂在框架基线菜单 sys_menu.menu_id = 1761400000000001601('文件上传',见 ry_vue.sql:318)。
|
||
-- RuoYi 权限 = 角色所映射菜单的 perms 并集,故只需插这一行即可获得上传权,无需挂父菜单。
|
||
INSERT INTO sys_role_menu VALUES (1771300000000000001, 1761400000000001601);
|
||
-- 审计员:菜单 + 仅 query(不含新增/编辑/删除,符合只读审计定位)
|
||
INSERT INTO sys_role_menu VALUES (1771300000000000002, 1771400000000000006);
|
||
INSERT INTO sys_role_menu VALUES (1771300000000000002, 1771400000000000022);
|