-- ============================================================================ -- 智能飞禽物联平台 · 项目自有补丁脚本(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);