diff --git a/sql/menu_device_sn.sql b/sql/menu_device_sn.sql index 7797c7ff..9bf9e29a 100644 --- a/sql/menu_device_sn.sql +++ b/sql/menu_device_sn.sql @@ -31,22 +31,29 @@ SET @menu_id = (SELECT `id` FROM `sys_menu` WHERE `component` = 'devices/sn/in -- 计算按钮的 tree_path = 菜单 tree_path + ',' + 菜单 id SET @tree_path = (SELECT CONCAT(`tree_path`, ',', `id`) FROM `sys_menu` WHERE `id` = @menu_id); --- 1. 插入按钮权限(不存在才插入);权限字符串用 CONCAT 拼接,避免 :token 被客户端误判 +-- 1. 清理旧命名按钮权限(add/edit → create/update),保证幂等可重跑 +-- 旧的 sys:device:add / sys:device:edit 若存在则删除,避免与标准命名并存 +DELETE FROM `sys_menu` +WHERE `perm` IN (CONCAT('sys', ':', 'device', ':', 'add'), CONCAT('sys', ':', 'device', ':', 'edit')) + AND `type` = 'B'; + +-- 2. 插入按钮权限(不存在才插入);权限字符串用 CONCAT 拼接,避免 :token 被客户端误判 +-- 命名遵循系统标准(前端 buttonConfig 与后端代码生成器统一):list / create / update / delete INSERT INTO `sys_menu` (`parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`) SELECT @menu_id, @tree_path, t.`name`, 'B', NULL, NULL, NULL, t.`perm`, NULL, NULL, 1, t.`sort`, NULL, NULL, now(), now(), NULL FROM ( - SELECT '查询' AS `name`, CONCAT('sys', ':', 'device', ':', 'list') AS `perm`, 1 AS `sort` UNION ALL - SELECT '新增', CONCAT('sys', ':', 'device', ':', 'add'), 2 UNION ALL - SELECT '编辑', CONCAT('sys', ':', 'device', ':', 'edit'), 3 UNION ALL - SELECT '删除', CONCAT('sys', ':', 'device', ':', 'delete'), 4 + SELECT '查询' AS `name`, CONCAT('sys', ':', 'device', ':', 'list') AS `perm`, 1 AS `sort` UNION ALL + SELECT '新增', CONCAT('sys', ':', 'device', ':', 'create'), 2 UNION ALL + SELECT '编辑', CONCAT('sys', ':', 'device', ':', 'update'), 3 UNION ALL + SELECT '删除', CONCAT('sys', ':', 'device', ':', 'delete'), 4 ) t WHERE @menu_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM `sys_menu` m WHERE m.`perm` = t.`perm`); --- 2. 授权给 ADMIN 角色(role_id = 2;如实际角色 id 不同请修改) +-- 3. 授权给 ADMIN 角色(role_id = 2;如实际角色 id 不同请修改) -- 直接授权该菜单下的全部按钮,避免 SQL 文本中出现带冒号的权限字符串 INSERT IGNORE INTO `sys_role_menu` (`role_id`, `menu_id`) SELECT 2, `id` diff --git a/sql/sn_device_policy.sql b/sql/sn_device_policy.sql new file mode 100644 index 00000000..1f9231b2 --- /dev/null +++ b/sql/sn_device_policy.sql @@ -0,0 +1,42 @@ +-- ============================================================================= +-- 设备策略信息表(sys_sn_device_policy) +-- 说明:存储设备的策略开关状态,与设备 SN 绑定,每个 SN 唯一一条。 +-- 所有开关字段默认值均为 1(开启)。 +-- ============================================================================= +CREATE TABLE IF NOT EXISTS `sys_sn_device_policy` +( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `serialno` VARCHAR(64) NOT NULL COMMENT '设备序列号(关联 sys_sn.serialno)', + + -- ===================== 策略开关 ===================== + `usb_data` TINYINT NOT NULL DEFAULT 1 COMMENT 'USB偏好设置(0关闭 1开启)', + `time_setting` TINYINT NOT NULL DEFAULT 1 COMMENT '时间设置管控(0关闭 1开启)', + `storage_card` TINYINT NOT NULL DEFAULT 1 COMMENT '存储卡(0关闭 1开启)', + `factory_reset` TINYINT NOT NULL DEFAULT 1 COMMENT '恢复出厂(0关闭 1开启)', + `wifi_hotspot` TINYINT NOT NULL DEFAULT 1 COMMENT 'WiFi热点开关(0关闭 1开启)', + `bluetooth_switch` TINYINT NOT NULL DEFAULT 1 COMMENT '蓝牙开关(0关闭 1开启)', + `wallpaper` TINYINT NOT NULL DEFAULT 1 COMMENT '壁纸功能(0关闭 1开启)', + `notification_bar` TINYINT NOT NULL DEFAULT 1 COMMENT '通知栏显示(0关闭 1开启)', + `status_bar_pull_down` TINYINT NOT NULL DEFAULT 1 COMMENT '状态栏下拉开关(0关闭 1开启)', + `system_nav_bar_show` TINYINT NOT NULL DEFAULT 1 COMMENT '系统导航条显示(0关闭 1开启)', + `system_nav_bar_setting` TINYINT NOT NULL DEFAULT 1 COMMENT '系统导航栏设置(0关闭 1开启)', + `nav_bar_options` TINYINT NOT NULL DEFAULT 1 COMMENT '导航条选项(0关闭 1开启)', + `bluetooth_function` TINYINT NOT NULL DEFAULT 1 COMMENT '蓝牙功能开关(0关闭 1开启)', + `ota_upgrade` TINYINT NOT NULL DEFAULT 1 COMMENT 'OTA升级管控(0关闭 1开启)', + `app_install` TINYINT NOT NULL DEFAULT 1 COMMENT '应用安装开关(0关闭 1开启)', + `auto_rotate` TINYINT NOT NULL DEFAULT 1 COMMENT '自动旋转(0关闭 1开启)', + `auto_brightness` TINYINT NOT NULL DEFAULT 1 COMMENT '自动亮度(0关闭 1开启)', + `eye_protection` TINYINT NOT NULL DEFAULT 1 COMMENT '护眼模式(0关闭 1开启)', + `dark_mode` TINYINT NOT NULL DEFAULT 1 COMMENT '深色模式(0关闭 1开启)', + + -- ===================== 通用字段 ===================== + `create_time` DATETIME DEFAULT NULL COMMENT '创建时间', + `update_time` DATETIME DEFAULT NULL COMMENT '更新时间', + `is_deleted` TINYINT DEFAULT 0 COMMENT '逻辑删除(0未删 1已删)', + + PRIMARY KEY (`id`), + UNIQUE KEY `uk_serialno` (`serialno`), + KEY `idx_is_deleted` (`is_deleted`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COMMENT = '设备策略信息表'; diff --git a/src/main/java/com/youlai/boot/client/controller/ClientController.java b/src/main/java/com/youlai/boot/client/controller/ClientController.java index f8df1020..ba95d17f 100644 --- a/src/main/java/com/youlai/boot/client/controller/ClientController.java +++ b/src/main/java/com/youlai/boot/client/controller/ClientController.java @@ -5,13 +5,19 @@ import com.youlai.boot.client.model.entity.ClientUser; import com.youlai.boot.client.service.ClientUserService; import com.youlai.boot.common.exception.BusinessException; import com.youlai.boot.common.result.Result; +import com.youlai.boot.device.converter.DevicePolicyConverter; import com.youlai.boot.device.model.entity.SnDeviceInfo; +import com.youlai.boot.device.model.entity.SnDevicePolicy; +import com.youlai.boot.device.model.req.DevicePolicyReq; +import com.youlai.boot.device.model.req.DevicePolicyToggleReq; import com.youlai.boot.device.model.vo.DeviceApkInfoVO; import com.youlai.boot.device.model.vo.DeviceBriefVO; import com.youlai.boot.device.model.vo.DeviceLocationVO; +import com.youlai.boot.device.model.vo.DevicePolicyVO; import com.youlai.boot.device.model.vo.DeviceSystemInfoVO; import com.youlai.boot.device.model.vo.ScreenshotVO; import com.youlai.boot.device.service.ApkInstallService; +import com.youlai.boot.device.service.DevicePolicyService; import com.youlai.boot.device.service.DeviceService; import com.youlai.boot.device.service.LocationService; import com.youlai.boot.device.service.ScreenshotService; @@ -26,6 +32,8 @@ import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @@ -53,6 +61,8 @@ public class ClientController { private final LocationService locationService; private final ApkInstallService apkInstallService; private final ScreenshotService screenshotService; + private final DevicePolicyService devicePolicyService; + private final DevicePolicyConverter devicePolicyConverter; /** * 校验当前登录的 client 用户是否绑定了传入的设备 SN。 @@ -286,4 +296,106 @@ public class ClientController { return Result.failed("解绑设备失败: " + e.getMessage()); } } + + // ===================== 设备策略信息(家属端) ===================== + + @Operation(summary = "获取已绑定设备策略信息", description = "SN 合法且无记录时自动新建默认策略(默认全开)") + @PreAuthorize("isAuthenticated()") + @GetMapping("/policy") + public Result getPolicy( + @Parameter(description = "设备序列号") @RequestParam("sn") String sn + ) { + try { + checkBound(sn); + SnDevicePolicy policy = devicePolicyService.getOrInitBySn(sn); + if (policy == null) { + return Result.failed("设备SN不合法或不存在"); + } + return Result.success(devicePolicyConverter.toVo(policy)); + } catch (BusinessException be) { + return Result.failed(be.getMessage()); + } catch (Exception e) { + log.error("getPolicy error, sn: {}", sn, e); + return Result.failed("获取策略信息失败: " + e.getMessage()); + } + } + + @Operation(summary = "全量设置已绑定设备策略信息", description = "SN 合法且无记录时新建,存在时更新(空字段保持原值)") + @PreAuthorize("isAuthenticated()") + @PutMapping("/policy") + public Result updatePolicy( + @Parameter(description = "设备序列号") @RequestParam("sn") String sn, + @RequestBody DevicePolicyReq req + ) { + try { + checkBound(sn); + if (req == null) { + return Result.failed("策略信息不能为空"); + } + SnDevicePolicy policy = new SnDevicePolicy(); + policy.setSerialno(sn); + copyPolicy(req, policy); + boolean success = devicePolicyService.saveOrUpdateBySn(sn, policy); + if (!success) { + return Result.failed("保存策略信息失败(设备SN不合法或参数异常)"); + } + // 全量保存后推送策略状态到对应设备 + devicePolicyService.pushPolicyToDevice(sn); + return Result.success(); + } catch (BusinessException be) { + return Result.failed(be.getMessage()); + } catch (Exception e) { + log.error("updatePolicy error, sn: {}", sn, e); + return Result.failed("保存策略信息失败: " + e.getMessage()); + } + } + + @Operation(summary = "单独设置已绑定设备策略开关", description = "通过 field 指定策略字段,value 设置 0/1") + @PreAuthorize("isAuthenticated()") + @PutMapping("/policy/toggle") + public Result togglePolicy( + @Parameter(description = "设备序列号") @RequestParam("sn") String sn, + @RequestBody DevicePolicyToggleReq req + ) { + try { + checkBound(sn); + if (req == null || req.getField() == null || req.getValue() == null) { + return Result.failed("field 与 value 不能为空"); + } + boolean success = devicePolicyService.updateSingleBySn(sn, req.getField(), req.getValue()); + if (!success) { + return Result.failed("设置策略开关失败(设备SN不合法或字段不支持)"); + } + // 单独保存后推送策略状态到对应设备 + devicePolicyService.pushPolicyToDevice(sn); + return Result.success(); + } catch (BusinessException be) { + return Result.failed(be.getMessage()); + } catch (Exception e) { + log.error("togglePolicy error, sn: {}", sn, e); + return Result.failed("设置策略开关失败: " + e.getMessage()); + } + } + + private void copyPolicy(DevicePolicyReq src, SnDevicePolicy target) { + target.setUsbData(src.getUsbData()); + target.setTimeSetting(src.getTimeSetting()); + target.setStorageCard(src.getStorageCard()); + target.setFactoryReset(src.getFactoryReset()); + target.setWifiHotspot(src.getWifiHotspot()); + target.setBluetoothSwitch(src.getBluetoothSwitch()); + target.setWallpaper(src.getWallpaper()); + target.setNotificationBar(src.getNotificationBar()); + target.setStatusBarPullDown(src.getStatusBarPullDown()); + target.setSystemNavBarShow(src.getSystemNavBarShow()); + target.setSystemNavBarSetting(src.getSystemNavBarSetting()); + target.setNavBarOptions(src.getNavBarOptions()); + target.setBluetoothFunction(src.getBluetoothFunction()); + target.setOtaUpgrade(src.getOtaUpgrade()); + target.setAppInstall(src.getAppInstall()); + target.setAutoRotate(src.getAutoRotate()); + target.setAutoBrightness(src.getAutoBrightness()); + target.setEyeProtection(src.getEyeProtection()); + target.setDarkMode(src.getDarkMode()); + } } diff --git a/src/main/java/com/youlai/boot/device/controller/DeviceController.java b/src/main/java/com/youlai/boot/device/controller/DeviceController.java index cb545bd4..a0f73055 100644 --- a/src/main/java/com/youlai/boot/device/controller/DeviceController.java +++ b/src/main/java/com/youlai/boot/device/controller/DeviceController.java @@ -6,16 +6,22 @@ import com.youlai.boot.common.enums.ActionTypeEnum; import com.youlai.boot.common.enums.LogModuleEnum; import com.youlai.boot.common.result.PageResult; import com.youlai.boot.common.result.Result; +import com.youlai.boot.device.converter.DevicePolicyConverter; import com.youlai.boot.device.model.entity.SnDeviceInfo; +import com.youlai.boot.device.model.entity.SnDevicePolicy; import com.youlai.boot.device.model.query.DeviceQuery; +import com.youlai.boot.device.model.req.DevicePolicyReq; +import com.youlai.boot.device.model.req.DevicePolicyToggleReq; import com.youlai.boot.device.model.vo.DeviceApkInfoVO; import com.youlai.boot.device.model.vo.DeviceHardwareInfoVO; import com.youlai.boot.device.model.vo.DeviceNetworkInfoVO; import com.youlai.boot.device.model.vo.DeviceOtherInfoVO; import com.youlai.boot.device.model.vo.DevicePageVO; +import com.youlai.boot.device.model.vo.DevicePolicyVO; import com.youlai.boot.device.model.vo.DeviceSecurityInfoVO; import com.youlai.boot.device.model.vo.DeviceSystemInfoVO; import com.youlai.boot.device.service.ApkInstallService; +import com.youlai.boot.device.service.DevicePolicyService; import com.youlai.boot.device.service.DeviceService; import com.youlai.boot.device.service.HardwareInfoService; import com.youlai.boot.device.service.NetworkInfoService; @@ -57,6 +63,8 @@ public class DeviceController { private final SecurityInfoService securityInfoService; private final OtherInfoService otherInfoService; private final ApkInstallService apkInstallService; + private final DevicePolicyService devicePolicyService; + private final DevicePolicyConverter devicePolicyConverter; @Operation(summary = "设备分页列表") @GetMapping("/page") @@ -145,4 +153,74 @@ public class DeviceController { return Result.success(apkInstallService.getDeviceApkInfo(sn)); } + // ===================== 设备策略信息 ===================== + + @Operation(summary = "获取设备策略信息", description = "SN 合法且无记录时自动新建默认策略(默认全开)") + @GetMapping("/{sn}/policy") + public Result getDevicePolicy( + @Parameter(description = "设备序列号") @PathVariable("sn") String sn) { + SnDevicePolicy policy = devicePolicyService.getOrInitBySn(sn); + if (policy == null) { + return Result.failed("设备SN不合法或不存在"); + } + return Result.success(devicePolicyConverter.toVo(policy)); + } + + @Operation(summary = "全量设置设备策略信息", description = "SN 合法且无记录时新建,存在时更新(空字段保持原值)") + @PutMapping("/{sn}/policy") + public Result updateDevicePolicy( + @Parameter(description = "设备序列号") @PathVariable("sn") String sn, + @RequestBody DevicePolicyReq req) { + if (req == null) { + return Result.failed("策略信息不能为空"); + } + SnDevicePolicy policy = new SnDevicePolicy(); + policy.setSerialno(sn); + copyPolicy(req, policy); + boolean success = devicePolicyService.saveOrUpdateBySn(sn, policy); + if (!success) { + return Result.failed("保存策略信息失败(设备SN不合法或参数异常)"); + } + // 全量保存后推送策略状态到对应设备 + devicePolicyService.pushPolicyToDevice(sn); + return Result.success(); + } + + @Operation(summary = "单独设置设备策略开关", description = "通过 field 指定策略字段,value 设置 0/1") + @PutMapping("/{sn}/policy/toggle") + public Result toggleDevicePolicy( + @Parameter(description = "设备序列号") @PathVariable("sn") String sn, + @RequestBody DevicePolicyToggleReq req) { + if (req == null || req.getField() == null || req.getValue() == null) { + return Result.failed("field 与 value 不能为空"); + } + boolean success = devicePolicyService.updateSingleBySn(sn, req.getField(), req.getValue()); + if (!success) { + return Result.failed("设置策略开关失败(设备SN不合法或字段不支持)"); + } + return Result.success(); + } + + private void copyPolicy(DevicePolicyReq src, SnDevicePolicy target) { + target.setUsbData(src.getUsbData()); + target.setTimeSetting(src.getTimeSetting()); + target.setStorageCard(src.getStorageCard()); + target.setFactoryReset(src.getFactoryReset()); + target.setWifiHotspot(src.getWifiHotspot()); + target.setBluetoothSwitch(src.getBluetoothSwitch()); + target.setWallpaper(src.getWallpaper()); + target.setNotificationBar(src.getNotificationBar()); + target.setStatusBarPullDown(src.getStatusBarPullDown()); + target.setSystemNavBarShow(src.getSystemNavBarShow()); + target.setSystemNavBarSetting(src.getSystemNavBarSetting()); + target.setNavBarOptions(src.getNavBarOptions()); + target.setBluetoothFunction(src.getBluetoothFunction()); + target.setOtaUpgrade(src.getOtaUpgrade()); + target.setAppInstall(src.getAppInstall()); + target.setAutoRotate(src.getAutoRotate()); + target.setAutoBrightness(src.getAutoBrightness()); + target.setEyeProtection(src.getEyeProtection()); + target.setDarkMode(src.getDarkMode()); + } + } diff --git a/src/main/java/com/youlai/boot/device/controller/DeviceOpsController.java b/src/main/java/com/youlai/boot/device/controller/DeviceOpsController.java index 993094d9..77df0614 100644 --- a/src/main/java/com/youlai/boot/device/controller/DeviceOpsController.java +++ b/src/main/java/com/youlai/boot/device/controller/DeviceOpsController.java @@ -59,10 +59,19 @@ public class DeviceOpsController { @Operation(summary = "删除SN") @DeleteMapping("/{sn}") @Log(module = LogModuleEnum.DEVICE, value = ActionTypeEnum.DELETE) - @PreAuthorize("@ss.hasPerm('sys:sn:delete')") - public Result deleteSn(@PathVariable String sn) { - deviceService.deleteSn(sn); - return Result.success(); + @PreAuthorize("@ss.hasPerm('sys:device:delete')") + public Result deleteSn(@PathVariable String sn) { + boolean deleted = deviceService.deleteSn(sn); + return Result.judge(deleted); + } + + @Operation(summary = "解除设备绑定", description = "清除设备绑定的手机号,并下发推送通知设备端标记解绑状态") + @PostMapping("/{sn}/unbind") + @Log(module = LogModuleEnum.DEVICE, value = ActionTypeEnum.UPDATE) + @PreAuthorize("@ss.hasPerm('sys:device:update')") + public Result unbindMobile(@PathVariable String sn) { + boolean result = deviceService.unbindMobile(sn); + return Result.judge(result); } @Operation(summary = "设备刷新") diff --git a/src/main/java/com/youlai/boot/device/controller/MobileController.java b/src/main/java/com/youlai/boot/device/controller/MobileController.java index 3eab0aed..1d789a5f 100644 --- a/src/main/java/com/youlai/boot/device/controller/MobileController.java +++ b/src/main/java/com/youlai/boot/device/controller/MobileController.java @@ -22,8 +22,13 @@ import com.youlai.boot.device.model.req.SnSecurityInfoReq; import com.youlai.boot.device.model.req.SnOtherInfoReq; import com.youlai.boot.device.model.req.SnLocationReq; import com.youlai.boot.device.model.entity.SnLocation; +import com.youlai.boot.device.model.entity.SnDevicePolicy; import com.youlai.boot.device.model.entity.SnScreenshot; +import com.youlai.boot.device.model.req.DevicePolicyReq; +import com.youlai.boot.device.model.req.DevicePolicyToggleReq; import com.youlai.boot.device.model.vo.DeveloperOptionsVO; +import com.youlai.boot.device.model.vo.DevicePolicyVO; +import com.youlai.boot.device.converter.DevicePolicyConverter; import com.youlai.boot.device.service.*; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -68,6 +73,8 @@ public class MobileController { private final NetworkInfoService networkInfoService; private final SecurityInfoService securityInfoService; private final OtherInfoService otherInfoService; + private final DevicePolicyService devicePolicyService; + private final DevicePolicyConverter devicePolicyConverter; private final Logger logger = LoggerFactory.getLogger(MobileController.class); @@ -565,4 +572,100 @@ public class MobileController { return Result.failed("上传应用图标失败: " + e.getMessage()); } } + + // ===================== 设备策略信息(设备端) ===================== + + @Operation(summary = "获取设备策略信息", description = "SN 合法且无记录时自动新建默认策略(默认全开)") + @GetMapping("/get_device_policy") + public Result getDevicePolicy( + @RequestHeader(value = "X-Device-SN") String sn + ) { + try { + if (sn == null || sn.trim().isEmpty()) { + return Result.failed("设备序列号不能为空"); + } + SnDevicePolicy policy = devicePolicyService.getOrInitBySn(sn); + if (policy == null) { + return Result.failed("设备SN不合法或不存在"); + } + return Result.success(devicePolicyConverter.toVo(policy)); + } catch (Exception e) { + logger.error("getDevicePolicy error, sn: {}", sn, e); + return Result.failed("获取设备策略失败: " + e.getMessage()); + } + } + + @Operation(summary = "上传/全量设置设备策略信息", description = "SN 合法且无记录时新建,存在时更新(空字段保持原值)") + @PostMapping("/update_device_policy") + public Result updateDevicePolicy( + @RequestHeader(value = "X-Device-SN") String sn, + @RequestBody DevicePolicyReq req + ) { + try { + if (sn == null || sn.trim().isEmpty()) { + return Result.failed("设备序列号不能为空"); + } + if (req == null) { + return Result.failed("策略信息不能为空"); + } + SnDevicePolicy policy = new SnDevicePolicy(); + policy.setSerialno(sn); + copyPolicy(req, policy); + boolean success = devicePolicyService.saveOrUpdateBySn(sn, policy); + if (!success) { + return Result.failed("保存设备策略失败(设备SN不合法或参数异常)"); + } + logger.info("设备策略上传成功, sn: {}", sn); + return Result.success(); + } catch (Exception e) { + logger.error("updateDevicePolicy error, sn: {}", sn, e); + return Result.failed("上传设备策略失败: " + e.getMessage()); + } + } + + @Operation(summary = "单独设置设备策略开关", description = "通过 field 指定策略字段,value 设置 0/1") + @PostMapping("/update_device_policy_toggle") + public Result toggleDevicePolicy( + @RequestHeader(value = "X-Device-SN") String sn, + @RequestBody DevicePolicyToggleReq req + ) { + try { + if (sn == null || sn.trim().isEmpty()) { + return Result.failed("设备序列号不能为空"); + } + if (req == null || req.getField() == null || req.getValue() == null) { + return Result.failed("field 与 value 不能为空"); + } + boolean success = devicePolicyService.updateSingleBySn(sn, req.getField(), req.getValue()); + if (!success) { + return Result.failed("设置设备策略失败(设备SN不合法或字段不支持)"); + } + return Result.success(); + } catch (Exception e) { + logger.error("toggleDevicePolicy error, sn: {}", sn, e); + return Result.failed("设置设备策略失败: " + e.getMessage()); + } + } + + private void copyPolicy(DevicePolicyReq src, SnDevicePolicy target) { + target.setUsbData(src.getUsbData()); + target.setTimeSetting(src.getTimeSetting()); + target.setStorageCard(src.getStorageCard()); + target.setFactoryReset(src.getFactoryReset()); + target.setWifiHotspot(src.getWifiHotspot()); + target.setBluetoothSwitch(src.getBluetoothSwitch()); + target.setWallpaper(src.getWallpaper()); + target.setNotificationBar(src.getNotificationBar()); + target.setStatusBarPullDown(src.getStatusBarPullDown()); + target.setSystemNavBarShow(src.getSystemNavBarShow()); + target.setSystemNavBarSetting(src.getSystemNavBarSetting()); + target.setNavBarOptions(src.getNavBarOptions()); + target.setBluetoothFunction(src.getBluetoothFunction()); + target.setOtaUpgrade(src.getOtaUpgrade()); + target.setAppInstall(src.getAppInstall()); + target.setAutoRotate(src.getAutoRotate()); + target.setAutoBrightness(src.getAutoBrightness()); + target.setEyeProtection(src.getEyeProtection()); + target.setDarkMode(src.getDarkMode()); + } } diff --git a/src/main/java/com/youlai/boot/device/converter/DevicePolicyConverter.java b/src/main/java/com/youlai/boot/device/converter/DevicePolicyConverter.java new file mode 100644 index 00000000..b941f45e --- /dev/null +++ b/src/main/java/com/youlai/boot/device/converter/DevicePolicyConverter.java @@ -0,0 +1,25 @@ +package com.youlai.boot.device.converter; + +import com.youlai.boot.device.model.entity.SnDevicePolicy; +import com.youlai.boot.device.model.vo.DevicePolicyVO; +import org.mapstruct.Mapper; + +/** + * 设备策略信息 实体与 VO 转换器 + * + * @author TTSTD + * @since 2026-08-27 + */ +@Mapper(componentModel = "spring") +public interface DevicePolicyConverter { + + /** + * 实体 → VO + */ + DevicePolicyVO toVo(SnDevicePolicy entity); + + /** + * 请求对象 → 实体 + */ + SnDevicePolicy toEntity(DevicePolicyVO vo); +} diff --git a/src/main/java/com/youlai/boot/device/mapper/DevicePolicyMapper.java b/src/main/java/com/youlai/boot/device/mapper/DevicePolicyMapper.java new file mode 100644 index 00000000..0a52e911 --- /dev/null +++ b/src/main/java/com/youlai/boot/device/mapper/DevicePolicyMapper.java @@ -0,0 +1,9 @@ +package com.youlai.boot.device.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.youlai.boot.device.model.entity.SnDevicePolicy; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface DevicePolicyMapper extends BaseMapper { +} diff --git a/src/main/java/com/youlai/boot/device/model/entity/SnDevicePolicy.java b/src/main/java/com/youlai/boot/device/model/entity/SnDevicePolicy.java new file mode 100644 index 00000000..7c710252 --- /dev/null +++ b/src/main/java/com/youlai/boot/device/model/entity/SnDevicePolicy.java @@ -0,0 +1,82 @@ +package com.youlai.boot.device.model.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.youlai.boot.common.base.BaseEntity; +import lombok.Getter; +import lombok.Setter; + +/** + * 设备策略信息实体(策略信息面板,与设备 SN 绑定,每个 SN 唯一一条) + * + * @author TTSTD + * @since 2026-08-27 + */ +@Getter +@Setter +@TableName("sys_sn_device_policy") +public class SnDevicePolicy extends BaseEntity { + + /** + * 设备序列号(关联 sys_sn.serialno) + */ + private String serialno; + + // ===================== 策略开关(0关闭 1开启) ===================== + + /** USB偏好设置 */ + private Integer usbData; + + /** 时间设置管控 */ + private Integer timeSetting; + + /** 存储卡 */ + private Integer storageCard; + + /** 恢复出厂 */ + private Integer factoryReset; + + /** WiFi热点开关 */ + private Integer wifiHotspot; + + /** 蓝牙开关 */ + private Integer bluetoothSwitch; + + /** 壁纸功能 */ + private Integer wallpaper; + + /** 通知栏显示 */ + private Integer notificationBar; + + /** 状态栏下拉开关 */ + private Integer statusBarPullDown; + + /** 系统导航条显示 */ + private Integer systemNavBarShow; + + /** 系统导航栏设置 */ + private Integer systemNavBarSetting; + + /** 导航条选项 */ + private Integer navBarOptions; + + /** 蓝牙功能开关 */ + private Integer bluetoothFunction; + + /** OTA升级管控 */ + private Integer otaUpgrade; + + /** 应用安装开关 */ + private Integer appInstall; + + /** 自动旋转 */ + private Integer autoRotate; + + /** 自动亮度 */ + private Integer autoBrightness; + + /** 护眼模式 */ + private Integer eyeProtection; + + /** 深色模式 */ + private Integer darkMode; +} diff --git a/src/main/java/com/youlai/boot/device/model/req/DevicePolicyReq.java b/src/main/java/com/youlai/boot/device/model/req/DevicePolicyReq.java new file mode 100644 index 00000000..c904f2db --- /dev/null +++ b/src/main/java/com/youlai/boot/device/model/req/DevicePolicyReq.java @@ -0,0 +1,77 @@ +package com.youlai.boot.device.model.req; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 设备策略信息请求对象(全量设置 / 设备端上报) + * + * @author TTSTD + * @since 2026-08-27 + */ +@Schema(description = "设备策略信息请求对象") +@Data +public class DevicePolicyReq { + + @Schema(description = "设备序列号") + private String serialno; + + // ===================== 策略开关(0关闭 1开启) ===================== + + @Schema(description = "USB偏好设置(0关闭 1开启)") + private Integer usbData; + + @Schema(description = "时间设置管控(0关闭 1开启)") + private Integer timeSetting; + + @Schema(description = "存储卡(0关闭 1开启)") + private Integer storageCard; + + @Schema(description = "恢复出厂(0关闭 1开启)") + private Integer factoryReset; + + @Schema(description = "WiFi热点开关(0关闭 1开启)") + private Integer wifiHotspot; + + @Schema(description = "蓝牙开关(0关闭 1开启)") + private Integer bluetoothSwitch; + + @Schema(description = "壁纸功能(0关闭 1开启)") + private Integer wallpaper; + + @Schema(description = "通知栏显示(0关闭 1开启)") + private Integer notificationBar; + + @Schema(description = "状态栏下拉开关(0关闭 1开启)") + private Integer statusBarPullDown; + + @Schema(description = "系统导航条显示(0关闭 1开启)") + private Integer systemNavBarShow; + + @Schema(description = "系统导航栏设置(0关闭 1开启)") + private Integer systemNavBarSetting; + + @Schema(description = "导航条选项(0关闭 1开启)") + private Integer navBarOptions; + + @Schema(description = "蓝牙功能开关(0关闭 1开启)") + private Integer bluetoothFunction; + + @Schema(description = "OTA升级管控(0关闭 1开启)") + private Integer otaUpgrade; + + @Schema(description = "应用安装开关(0关闭 1开启)") + private Integer appInstall; + + @Schema(description = "自动旋转(0关闭 1开启)") + private Integer autoRotate; + + @Schema(description = "自动亮度(0关闭 1开启)") + private Integer autoBrightness; + + @Schema(description = "护眼模式(0关闭 1开启)") + private Integer eyeProtection; + + @Schema(description = "深色模式(0关闭 1开启)") + private Integer darkMode; +} diff --git a/src/main/java/com/youlai/boot/device/model/req/DevicePolicyToggleReq.java b/src/main/java/com/youlai/boot/device/model/req/DevicePolicyToggleReq.java new file mode 100644 index 00000000..56f62688 --- /dev/null +++ b/src/main/java/com/youlai/boot/device/model/req/DevicePolicyToggleReq.java @@ -0,0 +1,21 @@ +package com.youlai.boot.device.model.req; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 设备策略单独设置请求对象 + * + * @author TTSTD + * @since 2026-08-27 + */ +@Schema(description = "设备策略单独设置请求对象") +@Data +public class DevicePolicyToggleReq { + + @Schema(description = "策略字段名(如 usbData、appInstall)") + private String field; + + @Schema(description = "开关值(0关闭 1开启)") + private Integer value; +} diff --git a/src/main/java/com/youlai/boot/device/model/vo/DevicePolicyVO.java b/src/main/java/com/youlai/boot/device/model/vo/DevicePolicyVO.java new file mode 100644 index 00000000..34e6de8e --- /dev/null +++ b/src/main/java/com/youlai/boot/device/model/vo/DevicePolicyVO.java @@ -0,0 +1,80 @@ +package com.youlai.boot.device.model.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 设备策略信息视图对象(策略信息面板) + * + * @author TTSTD + * @since 2026-08-27 + */ +@Schema(description = "设备策略信息视图对象") +@Data +public class DevicePolicyVO { + + @Schema(description = "ID") + private Long id; + + @Schema(description = "设备序列号") + private String serialno; + + // ===================== 策略开关(0关闭 1开启) ===================== + + @Schema(description = "USB偏好设置(0关闭 1开启)") + private Integer usbData; + + @Schema(description = "时间设置管控(0关闭 1开启)") + private Integer timeSetting; + + @Schema(description = "存储卡(0关闭 1开启)") + private Integer storageCard; + + @Schema(description = "恢复出厂(0关闭 1开启)") + private Integer factoryReset; + + @Schema(description = "WiFi热点开关(0关闭 1开启)") + private Integer wifiHotspot; + + @Schema(description = "蓝牙开关(0关闭 1开启)") + private Integer bluetoothSwitch; + + @Schema(description = "壁纸功能(0关闭 1开启)") + private Integer wallpaper; + + @Schema(description = "通知栏显示(0关闭 1开启)") + private Integer notificationBar; + + @Schema(description = "状态栏下拉开关(0关闭 1开启)") + private Integer statusBarPullDown; + + @Schema(description = "系统导航条显示(0关闭 1开启)") + private Integer systemNavBarShow; + + @Schema(description = "系统导航栏设置(0关闭 1开启)") + private Integer systemNavBarSetting; + + @Schema(description = "导航条选项(0关闭 1开启)") + private Integer navBarOptions; + + @Schema(description = "蓝牙功能开关(0关闭 1开启)") + private Integer bluetoothFunction; + + @Schema(description = "OTA升级管控(0关闭 1开启)") + private Integer otaUpgrade; + + @Schema(description = "应用安装开关(0关闭 1开启)") + private Integer appInstall; + + @Schema(description = "自动旋转(0关闭 1开启)") + private Integer autoRotate; + + @Schema(description = "自动亮度(0关闭 1开启)") + private Integer autoBrightness; + + @Schema(description = "护眼模式(0关闭 1开启)") + private Integer eyeProtection; + + @Schema(description = "深色模式(0关闭 1开启)") + private Integer darkMode; +} diff --git a/src/main/java/com/youlai/boot/device/service/DevicePolicyService.java b/src/main/java/com/youlai/boot/device/service/DevicePolicyService.java new file mode 100644 index 00000000..e47ccfdb --- /dev/null +++ b/src/main/java/com/youlai/boot/device/service/DevicePolicyService.java @@ -0,0 +1,66 @@ +package com.youlai.boot.device.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.youlai.boot.device.model.entity.SnDevicePolicy; + +/** + * 设备策略信息服务 + * + * @author TTSTD + * @since 2026-08-27 + */ +public interface DevicePolicyService extends IService { + + /** + * 校验设备 SN 是否合法(存在于 sys_sn) + * + * @param sn 设备序列号 + * @return 合法返回 true + */ + boolean isSnValid(String sn); + + /** + * 根据序列号查询设备策略信息(无记录则返回 null) + * + * @param sn 设备序列号 + * @return 策略信息实体(无则返回 null) + */ + SnDevicePolicy getBySn(String sn); + + /** + * 获取设备策略信息;若 SN 合法且数据库无记录,则新建一条(所有开关默认 1) + * + * @param sn 设备序列号 + * @return 策略信息实体;SN 不合法时返回 null + */ + SnDevicePolicy getOrInitBySn(String sn); + + /** + * 全量保存设备策略信息;SN 合法且无记录时新建,存在时更新 + * + * @param sn 设备序列号 + * @param policy 策略开关信息(空值字段保持原值) + * @return 是否成功 + */ + boolean saveOrUpdateBySn(String sn, SnDevicePolicy policy); + + /** + * 单独设置某个策略开关 + * + * @param sn 设备序列号 + * @param field 策略字段名(如 usbData) + * @param value 开关值(0关闭 1开启) + * @return 是否成功 + */ + boolean updateSingleBySn(String sn, String field, Integer value); + + /** + * 推送策略信息到对应设备(contentType = 19) + *

+ * 推送透传 payload 携带全部策略开关状态,供设备端解析并应用。 + * + * @param sn 设备序列号 + * @return 是否推送成功 + */ + boolean pushPolicyToDevice(String sn); +} diff --git a/src/main/java/com/youlai/boot/device/service/DeviceService.java b/src/main/java/com/youlai/boot/device/service/DeviceService.java index fe4e3bd7..a839d530 100644 --- a/src/main/java/com/youlai/boot/device/service/DeviceService.java +++ b/src/main/java/com/youlai/boot/device/service/DeviceService.java @@ -145,6 +145,17 @@ public interface DeviceService extends IService { */ boolean screenLock(String sn); + /** + * 解除设备绑定(管理员端发起) + *

+ * 清除设备表 {@code sys_sn.snMobile} 的绑定手机号,并下发推送通知设备端 + * 在本地标记解绑状态,避免设备下次上报手机号时重新建立绑定。 + * + * @param sn 设备序列号 + * @return 是否成功 + */ + boolean unbindMobile(String sn); + /** * 打开应用 * diff --git a/src/main/java/com/youlai/boot/device/service/impl/DevicePolicyServiceImpl.java b/src/main/java/com/youlai/boot/device/service/impl/DevicePolicyServiceImpl.java new file mode 100644 index 00000000..d7f1284a --- /dev/null +++ b/src/main/java/com/youlai/boot/device/service/impl/DevicePolicyServiceImpl.java @@ -0,0 +1,313 @@ +package com.youlai.boot.device.service.impl; + +import cn.jiguang.sdk.api.PushApi; +import cn.jiguang.sdk.bean.push.PushSendParam; +import cn.jiguang.sdk.bean.push.PushSendResult; +import cn.jiguang.sdk.bean.push.message.custom.CustomMessage; +import cn.jiguang.sdk.exception.ApiErrorException; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.youlai.boot.device.mapper.DevicePolicyMapper; +import com.youlai.boot.device.model.entity.SnDeviceInfo; +import com.youlai.boot.device.model.entity.SnDevicePolicy; +import com.youlai.boot.device.service.DevicePolicyService; +import com.youlai.boot.device.service.DeviceService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * 设备策略信息服务实现 + * + * @author TTSTD + * @since 2026-08-27 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class DevicePolicyServiceImpl extends ServiceImpl + implements DevicePolicyService { + + /** 策略推送业务码(与设备端 PushExecutor.DEVICE_POLICY 保持一致) */ + private static final String DEVICE_POLICY = "19"; + + private static final String PUSH_APP_KEY = "d779178d9900d4fb5d633678"; + private static final String PUSH_MASTER_SECRET = "be0e197d30fec7bec118a70d"; + + private final DeviceService deviceService; + + private final PushApi pushApi = new PushApi.Builder() + .setAppKey(PUSH_APP_KEY) + .setMasterSecret(PUSH_MASTER_SECRET) + .build(); + + @Override + public boolean isSnValid(String sn) { + if (StringUtils.isBlank(sn)) { + return false; + } + return deviceService.count( + new LambdaQueryWrapper().eq(SnDeviceInfo::getSerialno, sn) + ) > 0; + } + + @Override + public SnDevicePolicy getBySn(String sn) { + if (StringUtils.isBlank(sn)) { + return null; + } + return this.lambdaQuery() + .eq(SnDevicePolicy::getSerialno, sn) + .last("LIMIT 1") + .one(); + } + + @Override + public SnDevicePolicy getOrInitBySn(String sn) { + if (StringUtils.isBlank(sn)) { + return null; + } + SnDevicePolicy policy = getBySn(sn); + if (policy != null) { + return policy; + } + if (!isSnValid(sn)) { + log.warn("策略信息:设备 SN 不合法,不新建记录, sn: {}", sn); + return null; + } + // SN 合法但无记录,新建一条默认策略(默认全开 = 1) + policy = buildDefaultPolicy(sn); + boolean saved = this.save(policy); + if (saved) { + log.info("策略信息:新建默认策略成功, sn: {}", sn); + return policy; + } + return null; + } + + @Override + public boolean saveOrUpdateBySn(String sn, SnDevicePolicy policy) { + if (StringUtils.isBlank(sn)) { + return false; + } + if (!isSnValid(sn)) { + log.warn("策略信息:设备 SN 不合法,保存失败, sn: {}", sn); + return false; + } + SnDevicePolicy existing = getBySn(sn); + if (existing == null) { + SnDevicePolicy entity = new SnDevicePolicy(); + entity.setSerialno(sn); + fillNonNull(entity, policy); + boolean saved = this.save(entity); + log.info("策略信息:全量新增成功, sn: {}", sn); + return saved; + } + // 只更新非空字段,保留原值 + fillNonNull(existing, policy); + boolean updated = this.updateById(existing); + log.info("策略信息:全量更新成功, sn: {}", sn); + return updated; + } + + @Override + public boolean updateSingleBySn(String sn, String field, Integer value) { + if (StringUtils.isBlank(sn) || StringUtils.isBlank(field) || value == null) { + log.warn("策略信息:单独设置参数不合法, sn: {}, field: {}, value: {}", sn, field, value); + return false; + } + if (!isSnValid(sn)) { + log.warn("策略信息:设备 SN 不合法,单独设置失败, sn: {}", sn); + return false; + } + SnDevicePolicy existing = getOrInitBySn(sn); + if (existing == null) { + return false; + } + // 仅允许 0/1 + if (value != 0 && value != 1) { + log.warn("策略信息:开关值非法, field: {}, value: {}", field, value); + return false; + } + boolean updated; + switch (field) { + case "usbData" -> updated = this.lambdaUpdate().eq(SnDevicePolicy::getSerialno, sn) + .set(SnDevicePolicy::getUsbData, value).update(); + case "timeSetting" -> updated = this.lambdaUpdate().eq(SnDevicePolicy::getSerialno, sn) + .set(SnDevicePolicy::getTimeSetting, value).update(); + case "storageCard" -> updated = this.lambdaUpdate().eq(SnDevicePolicy::getSerialno, sn) + .set(SnDevicePolicy::getStorageCard, value).update(); + case "factoryReset" -> updated = this.lambdaUpdate().eq(SnDevicePolicy::getSerialno, sn) + .set(SnDevicePolicy::getFactoryReset, value).update(); + case "wifiHotspot" -> updated = this.lambdaUpdate().eq(SnDevicePolicy::getSerialno, sn) + .set(SnDevicePolicy::getWifiHotspot, value).update(); + case "bluetoothSwitch" -> updated = this.lambdaUpdate().eq(SnDevicePolicy::getSerialno, sn) + .set(SnDevicePolicy::getBluetoothSwitch, value).update(); + case "wallpaper" -> updated = this.lambdaUpdate().eq(SnDevicePolicy::getSerialno, sn) + .set(SnDevicePolicy::getWallpaper, value).update(); + case "notificationBar" -> updated = this.lambdaUpdate().eq(SnDevicePolicy::getSerialno, sn) + .set(SnDevicePolicy::getNotificationBar, value).update(); + case "statusBarPullDown" -> updated = this.lambdaUpdate().eq(SnDevicePolicy::getSerialno, sn) + .set(SnDevicePolicy::getStatusBarPullDown, value).update(); + case "systemNavBarShow" -> updated = this.lambdaUpdate().eq(SnDevicePolicy::getSerialno, sn) + .set(SnDevicePolicy::getSystemNavBarShow, value).update(); + case "systemNavBarSetting" -> updated = this.lambdaUpdate().eq(SnDevicePolicy::getSerialno, sn) + .set(SnDevicePolicy::getSystemNavBarSetting, value).update(); + case "navBarOptions" -> updated = this.lambdaUpdate().eq(SnDevicePolicy::getSerialno, sn) + .set(SnDevicePolicy::getNavBarOptions, value).update(); + case "bluetoothFunction" -> updated = this.lambdaUpdate().eq(SnDevicePolicy::getSerialno, sn) + .set(SnDevicePolicy::getBluetoothFunction, value).update(); + case "otaUpgrade" -> updated = this.lambdaUpdate().eq(SnDevicePolicy::getSerialno, sn) + .set(SnDevicePolicy::getOtaUpgrade, value).update(); + case "appInstall" -> updated = this.lambdaUpdate().eq(SnDevicePolicy::getSerialno, sn) + .set(SnDevicePolicy::getAppInstall, value).update(); + case "autoRotate" -> updated = this.lambdaUpdate().eq(SnDevicePolicy::getSerialno, sn) + .set(SnDevicePolicy::getAutoRotate, value).update(); + case "autoBrightness" -> updated = this.lambdaUpdate().eq(SnDevicePolicy::getSerialno, sn) + .set(SnDevicePolicy::getAutoBrightness, value).update(); + case "eyeProtection" -> updated = this.lambdaUpdate().eq(SnDevicePolicy::getSerialno, sn) + .set(SnDevicePolicy::getEyeProtection, value).update(); + case "darkMode" -> updated = this.lambdaUpdate().eq(SnDevicePolicy::getSerialno, sn) + .set(SnDevicePolicy::getDarkMode, value).update(); + default -> { + log.warn("策略信息:不支持的字段, field: {}", field); + return false; + } + } + log.info("策略信息:单独设置成功, sn: {}, field: {}, value: {}", sn, field, value); + return updated; + } + + @Override + public boolean pushPolicyToDevice(String sn) { + if (StringUtils.isBlank(sn)) { + log.warn("策略推送:设备序列号不能为空"); + return false; + } + SnDevicePolicy policy = getBySn(sn); + if (policy == null) { + log.warn("策略推送:设备策略不存在,sn: {}", sn); + return false; + } + try { + PushSendParam pushSendParam = buildPolicyPushParam(sn, policy); + PushSendResult result = pushApi.send(pushSendParam); + log.info("策略推送成功, sn: {}, result: {}", sn, result); + return true; + } catch (ApiErrorException e) { + int httpStatus = e.getStats(); + int errorCode = e.getApiError().getError().getCode(); + String errorMessage = e.getApiError().getError().getMessage(); + log.error("策略推送失败, sn: {}, httpStatus: {}, code: {}, message: {}", + sn, httpStatus, errorCode, errorMessage); + return false; + } catch (Exception e) { + log.error("策略推送异常, sn: {}", sn, e); + return false; + } + } + + /** + * 构建携带全部策略开关的推送参数(contentType = 19,extras 承载各开关状态) + */ + private PushSendParam buildPolicyPushParam(String sn, SnDevicePolicy policy) { + PushSendParam pushSendParam = new PushSendParam(); + pushSendParam.setPlatform("android"); + + Map> audience = new HashMap<>(); + Set aliasSet = new HashSet<>(); + aliasSet.add(sn); + audience.put("alias", aliasSet); + pushSendParam.setAudience(audience); + + CustomMessage customMessage = new CustomMessage(); + customMessage.setTitle("devicePolicy"); + customMessage.setContentType(DEVICE_POLICY); + customMessage.setContent("policy"); + + Map extras = new HashMap<>(); + extras.put("serialno", policy.getSerialno()); + extras.put("usbData", policy.getUsbData()); + extras.put("timeSetting", policy.getTimeSetting()); + extras.put("storageCard", policy.getStorageCard()); + extras.put("factoryReset", policy.getFactoryReset()); + extras.put("wifiHotspot", policy.getWifiHotspot()); + extras.put("bluetoothSwitch", policy.getBluetoothSwitch()); + extras.put("wallpaper", policy.getWallpaper()); + extras.put("notificationBar", policy.getNotificationBar()); + extras.put("statusBarPullDown", policy.getStatusBarPullDown()); + extras.put("systemNavBarShow", policy.getSystemNavBarShow()); + extras.put("systemNavBarSetting", policy.getSystemNavBarSetting()); + extras.put("navBarOptions", policy.getNavBarOptions()); + extras.put("bluetoothFunction", policy.getBluetoothFunction()); + extras.put("otaUpgrade", policy.getOtaUpgrade()); + extras.put("appInstall", policy.getAppInstall()); + extras.put("autoRotate", policy.getAutoRotate()); + extras.put("autoBrightness", policy.getAutoBrightness()); + extras.put("eyeProtection", policy.getEyeProtection()); + extras.put("darkMode", policy.getDarkMode()); + customMessage.setExtras(extras); + + pushSendParam.setCustom(customMessage); + return pushSendParam; + } + + /** + * 构建默认策略(所有开关默认开启 = 1) + */ + private SnDevicePolicy buildDefaultPolicy(String sn) { + SnDevicePolicy policy = new SnDevicePolicy(); + policy.setSerialno(sn); + policy.setUsbData(1); + policy.setTimeSetting(1); + policy.setStorageCard(1); + policy.setFactoryReset(1); + policy.setWifiHotspot(1); + policy.setBluetoothSwitch(1); + policy.setWallpaper(1); + policy.setNotificationBar(1); + policy.setStatusBarPullDown(1); + policy.setSystemNavBarShow(1); + policy.setSystemNavBarSetting(1); + policy.setNavBarOptions(1); + policy.setBluetoothFunction(1); + policy.setOtaUpgrade(1); + policy.setAppInstall(1); + policy.setAutoRotate(1); + policy.setAutoBrightness(1); + policy.setEyeProtection(1); + policy.setDarkMode(1); + return policy; + } + + /** + * 将 src 中的非空字段填充到 target(用于部分更新,保留原值) + */ + private void fillNonNull(SnDevicePolicy target, SnDevicePolicy src) { + if (src.getUsbData() != null) target.setUsbData(src.getUsbData()); + if (src.getTimeSetting() != null) target.setTimeSetting(src.getTimeSetting()); + if (src.getStorageCard() != null) target.setStorageCard(src.getStorageCard()); + if (src.getFactoryReset() != null) target.setFactoryReset(src.getFactoryReset()); + if (src.getWifiHotspot() != null) target.setWifiHotspot(src.getWifiHotspot()); + if (src.getBluetoothSwitch() != null) target.setBluetoothSwitch(src.getBluetoothSwitch()); + if (src.getWallpaper() != null) target.setWallpaper(src.getWallpaper()); + if (src.getNotificationBar() != null) target.setNotificationBar(src.getNotificationBar()); + if (src.getStatusBarPullDown() != null) target.setStatusBarPullDown(src.getStatusBarPullDown()); + if (src.getSystemNavBarShow() != null) target.setSystemNavBarShow(src.getSystemNavBarShow()); + if (src.getSystemNavBarSetting() != null) target.setSystemNavBarSetting(src.getSystemNavBarSetting()); + if (src.getNavBarOptions() != null) target.setNavBarOptions(src.getNavBarOptions()); + if (src.getBluetoothFunction() != null) target.setBluetoothFunction(src.getBluetoothFunction()); + if (src.getOtaUpgrade() != null) target.setOtaUpgrade(src.getOtaUpgrade()); + if (src.getAppInstall() != null) target.setAppInstall(src.getAppInstall()); + if (src.getAutoRotate() != null) target.setAutoRotate(src.getAutoRotate()); + if (src.getAutoBrightness() != null) target.setAutoBrightness(src.getAutoBrightness()); + if (src.getEyeProtection() != null) target.setEyeProtection(src.getEyeProtection()); + if (src.getDarkMode() != null) target.setDarkMode(src.getDarkMode()); + } +} diff --git a/src/main/java/com/youlai/boot/device/service/impl/DeviceServiceImpl.java b/src/main/java/com/youlai/boot/device/service/impl/DeviceServiceImpl.java index cbb78922..472883d3 100644 --- a/src/main/java/com/youlai/boot/device/service/impl/DeviceServiceImpl.java +++ b/src/main/java/com/youlai/boot/device/service/impl/DeviceServiceImpl.java @@ -73,6 +73,7 @@ public class DeviceServiceImpl extends ServiceImpl i private static final String DEVICE_CLEAN = "15"; private static final String DEVICE_MIRROR = "16"; private static final String DEVICE_SCREEN_LOCK = "17"; + private static final String DEVICE_UNBIND_MOBILE = "18"; PushApi pushApi = new PushApi.Builder() .setAppKey("d779178d9900d4fb5d633678") @@ -250,7 +251,42 @@ public class DeviceServiceImpl extends ServiceImpl i @Override public boolean deleteSn(String sn) { + if (sn == null || sn.isBlank()) { return false; + } + // 前端 deleteByIds 调用 DELETE /{ids},路径参数实际为主键 id + Long id = Long.valueOf(sn); + boolean deleted = this.removeById(id); + if (!deleted) { + log.warn("删除设备失败, id: {}", id); + } + return deleted; + } + + @Override + public boolean unbindMobile(String sn) { + if (sn == null || sn.isBlank()) { + return false; + } + // 1. 清除设备表绑定的手机号 + boolean updated = this.lambdaUpdate() + .set(SnDeviceInfo::getSnMobile, null) + .eq(SnDeviceInfo::getSerialno, sn) + .update(); + if (!updated) { + log.warn("解除绑定失败, sn: {}", sn); + return false; + } + // 2. 推送解绑命令到设备端,通知其在本地标记解绑状态, + // 避免设备下次上报 SIM 卡手机号时重新建立绑定 + try { + PushSendParam pushSendParam = getSinglePushSendParam(sn, DEVICE_UNBIND_MOBILE, "解除绑定", "设备已解除手机号绑定"); + PushSendResult result = pushApi.send(pushSendParam); + log.info("解除绑定推送成功, sn: {}, result: {}", sn, result); + } catch (Exception e) { + log.warn("解除绑定推送失败, sn: {}, error: {}", sn, e.getMessage()); + } + return true; } private PushSendParam getSinglePushSendParam(String sn, String type, String title, String content) {