Files
bird_monitoring/script/sql/bm_patch.sql

436 lines
31 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 '用户邮箱';
-- ----------------------------------------------------------------------------
-- 补丁 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);