feat(device): 添加设备策略信息管理功能

新增设备策略信息表 sys_sn_device_policy 及对应实体、Mapper、Converter、VO、Req 模型,并在设备端、家属端、管理端分别提供策略查询、全量更新和单独开关设置接口。同时修正设备菜单按钮权限命名(add/edit → create/update),并调整删除设备与解绑接口的权限和返回值。
This commit is contained in:
TongTongStudio
2026-08-27 11:48:45 +08:00
parent 76e239212a
commit af2b9846f1
16 changed files with 1081 additions and 10 deletions

View File

@@ -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`

42
sql/sn_device_policy.sql Normal file
View File

@@ -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 = '设备策略信息表';

View File

@@ -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<DevicePolicyVO> 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());
}
}

View File

@@ -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<DevicePolicyVO> 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<Void> 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<Void> 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());
}
}

View File

@@ -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<Void> deleteSn(@PathVariable String sn) {
deviceService.deleteSn(sn);
return Result.success();
@PreAuthorize("@ss.hasPerm('sys:device:delete')")
public Result<Boolean> 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<Boolean> unbindMobile(@PathVariable String sn) {
boolean result = deviceService.unbindMobile(sn);
return Result.judge(result);
}
@Operation(summary = "设备刷新")

View File

@@ -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<DevicePolicyVO> 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<Void> 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<Void> 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());
}
}

View File

@@ -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);
}

View File

@@ -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<SnDevicePolicy> {
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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<SnDevicePolicy> {
/**
* 校验设备 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
* <p>
* 推送透传 payload 携带全部策略开关状态,供设备端解析并应用。
*
* @param sn 设备序列号
* @return 是否推送成功
*/
boolean pushPolicyToDevice(String sn);
}

View File

@@ -145,6 +145,17 @@ public interface DeviceService extends IService<SnDeviceInfo> {
*/
boolean screenLock(String sn);
/**
* 解除设备绑定(管理员端发起)
* <p>
* 清除设备表 {@code sys_sn.snMobile} 的绑定手机号,并下发推送通知设备端
* 在本地标记解绑状态,避免设备下次上报手机号时重新建立绑定。
*
* @param sn 设备序列号
* @return 是否成功
*/
boolean unbindMobile(String sn);
/**
* 打开应用
*

View File

@@ -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<DevicePolicyMapper, SnDevicePolicy>
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<SnDeviceInfo>().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 = 19extras 承载各开关状态)
*/
private PushSendParam buildPolicyPushParam(String sn, SnDevicePolicy policy) {
PushSendParam pushSendParam = new PushSendParam();
pushSendParam.setPlatform("android");
Map<String, Set<String>> audience = new HashMap<>();
Set<String> 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<String, Object> 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());
}
}

View File

@@ -73,6 +73,7 @@ public class DeviceServiceImpl extends ServiceImpl<DeviceMapper, SnDeviceInfo> 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<DeviceMapper, SnDeviceInfo> 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) {