-- ============================================= -- 用户系统设置(用户维度)+ 设备侧全局开关入库 -- 维度判据:读的时候拿不拿得到 userId -- · 拿得到(HTTP 请求,有 SecurityContext)→ sys_user_setting,per-user -- · 拿不到(设备上电 @Async 线程)→ sys_config,全局 -- 配套代码:SysUserSetting* / PowerOnTriggerTaskExecutor:93 -- 版本: 1.0 2026-10-05 -- ============================================= -- ------------------------------------------------------------ -- 一、用户维度设置表 -- 一行一用户;所有设置列 NOT NULL + DEFAULT,不设「未配置过」这种 NULL 态 -- 业务默认值由 Java 常量兜底(UserSettingConstants),不依赖 DB 兜底 -- -- pinned_tags:任务栏「固定标签到任务栏」的 JSON 数组,按固定顺序存 route path。 -- * 原实现存浏览器 localStorage(key: pinned-tags),换浏览器/换电脑就丢, -- 现改为落库,同一账号在任何设备登录都能恢复固定区。 -- * 只存用户右键固定的 pinned 集合;路由自身声明的 affix(首页,router/index.js:80) -- 由 constantRoutes 天然重建,不入此列。 -- * **写入语义:单条增量**。接口是 POST/DELETE 一个 path,服务端在事务里 -- 「SELECT ... FOR UPDATE → 读数组 → 改 → UPDATE 整列」。 -- 刻意**不提供提交整份列表的覆盖写接口**:前端手里那份列表可能已被 -- 另一个标签页/另一台设备改过,整份提交会把对方的改动静默抹掉, -- 这正是固定标签「用着用着就少了」的成因。 -- * 增删都在 Java 侧按列表处理,**未使用任何 MySQL JSON 函数**。 -- * DEFAULT ('[]') 是 MySQL 8.0.13+ 的表达式默认值。TEXT 列不能带字面量 DEFAULT, -- 不给默认值时 STRICT_TRANS_TABLES 下会直接报错,故必须显式写空数组。 -- 低版本数据库请改为 VARCHAR(2000) NOT NULL DEFAULT '[]'。 -- -- 已建过表的库用下面的 ALTER 补列(可重复执行前请先确认列不存在): -- ALTER TABLE `sys_user_setting` -- ADD COLUMN `pinned_tags` text NOT NULL DEFAULT ('[]') -- COMMENT '任务栏固定标签JSON数组,按序存 path;空数组表示未固定' -- AFTER `login_redirect`; -- ALTER TABLE `sys_user_setting` -- ADD COLUMN `query_gateway_child` tinyint(1) NOT NULL DEFAULT 0 -- COMMENT '按编号查询时是否查网关子设备(0否1是)' -- AFTER `pinned_tags`; -- -- query_gateway_child:开启后 /iot/device/shortList **按设备编号查询时**(serialNumber 非空): -- ① 命中的若是网关子设备编号,返回其所属的网关设备(子设备 gw_dev_code 存的就是网关 serial_number, -- 取 ifnull(gw_dev_code, serial_number) 换算); -- ② 放开产品类型为「网关子设备(4)」的过滤 —— 这类设备 gw_dev_code 为空、实为独立设备, -- 只是产品被标成 4,默认在列表中被隐藏(否则设备管理页会多出一大批),仅在按编号精确定位时可见。 -- 该开关**不改变列表范围** —— gw_dev_code is null 恒生效,不带编号时 device_type != 4 依然过滤, -- 所以短列表始终只出顶层设备(网关 + 直连),既不出现挂在网关下的真子设备, -- 默认列表也不会多出 device_type=4 的设备。 -- 默认 0。读该值的入口是 DeviceController#shortList,有 SecurityContext,故为 per-user 设置。 -- ------------------------------------------------------------ CREATE TABLE IF NOT EXISTS `sys_user_setting` ( `setting_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '设置主键', `user_id` bigint(20) NOT NULL COMMENT '用户ID(sys_user.user_id)', `login_redirect` varchar(128) NOT NULL DEFAULT '/bigscreen' COMMENT '登录后跳转路由path', `pinned_tags` text NOT NULL DEFAULT ('[]') COMMENT '任务栏固定标签JSON数组,按序存 path;空数组表示未固定', `query_gateway_child` tinyint(1) NOT NULL DEFAULT 0 COMMENT '按编号查询时是否查网关子设备(0否1是)', `create_by` varchar(64) DEFAULT NULL COMMENT '创建者', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `update_by` varchar(64) DEFAULT NULL COMMENT '更新者', `update_time` datetime DEFAULT NULL COMMENT '更新时间', `remark` varchar(500) DEFAULT NULL COMMENT '备注', PRIMARY KEY (`setting_id`) USING BTREE, UNIQUE KEY `uk_sys_user_setting_user` (`user_id`) USING BTREE COMMENT '每用户仅一行' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC COMMENT='用户系统设置表'; -- ------------------------------------------------------------ -- 二、设备侧全局开关 → 复用已有 sys_config 表 -- 原实现:PowerOnTriggerTaskExecutor 直读运维手动写入的裸 Redis key -- "get_imei_iccid"(经 BackendController /set 写入) -- 新实现:改读 sys_config,运维改用已有的 PUT /system/config -- config_type='Y' = 系统内置,deleteConfigByIds 会拒删 -- ------------------------------------------------------------ INSERT INTO `sys_config` (`config_name`, `config_key`, `config_value`, `config_type`, `create_by`, `create_time`, `remark`) SELECT '是否下发读取设备IMEI/ICCID指令', 'iot.power_on.read_imei_iccid', 'true', 'Y', 'system', NOW(), '设备上电后是否下发 AT+IMEI/AT+ICCID/AT+LBS 指令读取;读该值的线程无登录上下文,故为全局配置' WHERE NOT EXISTS ( SELECT 1 FROM `sys_config` WHERE `config_key` = 'iot.power_on.read_imei_iccid' ); -- ------------------------------------------------------------ -- 三、菜单:系统管理 → 个人设置 -- 不设 system:setting:list 权限点的原因: -- 接口读写的是「当前登录用户自己的」设置,无需菜单级授权; -- 若填了权限点,非管理员账号将看不到也用不了自己的设置页。 -- menu_type='C' 菜单,component 指向新建的 views/system/setting/index.vue -- ------------------------------------------------------------ INSERT INTO `sys_menu` (`menu_name`, `parent_id`, `order_num`, `path`, `component`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`) SELECT '系统设置', 1, 13, 'setting', 'system/setting/index', 1, 0, 'C', '0', '0', '', 'setting', 'system', NOW() WHERE NOT EXISTS ( SELECT 1 FROM `sys_menu` WHERE `parent_id` = 1 AND `path` = 'setting' ); -- ------------------------------------------------------------ -- 四、清理历史遗留的裸 Redis key -- 原开关 key 无前缀、不在任何 key 生成器中,与 RedisKeyBuilder 规范不符。 -- 确认线上设备已完成一轮上电、开关已由 sys_config 接管后再执行: -- redis-cli DEL get_imei_iccid -- 切勿在切换完成前执行,否则 IMEI/ICCID 指令会立即停止下发。 -- ------------------------------------------------------------