feat(ops): 蓝牙位置校验服务及接口(Task 4)

This commit is contained in:
lzh
2026-03-05 17:07:48 +08:00
parent 99651be386
commit fa45d94247
6 changed files with 220 additions and 2 deletions

View File

@@ -26,12 +26,14 @@
- 逻辑: area_id → 查 ops_bus_area 获取 function_type → 查 ops_inspection_template 加载检查项
- 返回: 检查项列表
### 4. 蓝牙位置校验逻辑
- [ ] InspectionLocationService位置校验服务
### 4. 蓝牙位置校验逻辑
- [x] InspectionLocationService位置校验服务
- 入参: area_id + detected_beacons
- 查询 ops_area_device_relation 获取该区域绑定的信标列表
- 匹配算法: 至少1个绑定信标匹配且 RSSI > 阈值
- 返回: 校验通过/失败
- [x] InspectionController + verify-location 端点
- [x] DetectedBeaconVO, LocationVerifyResultVO
## Medium Priority
@@ -72,6 +74,7 @@
- [x] Task 1: 数据库表 + DO + Mapper3表3DO3Mapper
- [x] Task 2: 巡检模板 CRUDService + Impl + Controller + VOs
- [x] Task 3: 获取动态表单接口list-by-areaarea_id function_type template 查询链路
- [x] Task 4: 蓝牙位置校验InspectionLocationService + verify-location 端点
## Notes
- 巡检是保洁业务线内的子功能代码放 viewsh-module-environment-biz

View File

@@ -0,0 +1,20 @@
package com.viewsh.module.ops.environment.controller.admin.inspection.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Schema(description = "蓝牙信标检测数据")
@Data
public class DetectedBeaconVO {
@Schema(description = "信标MAC地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "F0:C8:60:1D:10:BB")
@NotBlank(message = "信标MAC地址不能为空")
private String mac;
@Schema(description = "信号强度(RSSI)", requiredMode = Schema.RequiredMode.REQUIRED, example = "-65")
@NotNull(message = "信号强度不能为空")
private Integer rssi;
}

View File

@@ -0,0 +1,28 @@
package com.viewsh.module.ops.environment.controller.admin.inspection.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Schema(description = "蓝牙位置校验结果")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LocationVerifyResultVO {
@Schema(description = "校验是否通过")
private Boolean passed;
@Schema(description = "校验消息")
private String message;
public static LocationVerifyResultVO success() {
return new LocationVerifyResultVO(true, "位置校验通过");
}
public static LocationVerifyResultVO fail(String message) {
return new LocationVerifyResultVO(false, message);
}
}

View File

@@ -0,0 +1,25 @@
package com.viewsh.module.ops.environment.service.inspection;
import com.viewsh.module.ops.environment.controller.admin.inspection.vo.DetectedBeaconVO;
import com.viewsh.module.ops.environment.controller.admin.inspection.vo.LocationVerifyResultVO;
import java.util.List;
/**
* 巡检蓝牙位置校验 Service
*/
public interface InspectionLocationService {
/**
* 校验巡检人员是否在指定区域
* <p>
* 逻辑:查询该区域绑定的 BEACON 类型设备,
* 匹配检测到的蓝牙信标列表至少1个绑定信标匹配且 RSSI 大于阈值即通过
*
* @param areaId 区域ID
* @param detectedBeacons 检测到的蓝牙信标列表
* @return 校验结果
*/
LocationVerifyResultVO verifyLocation(Long areaId, List<DetectedBeaconVO> detectedBeacons);
}

View File

@@ -0,0 +1,100 @@
package com.viewsh.module.ops.environment.service.inspection;
import com.viewsh.module.ops.dal.dataobject.area.OpsAreaDeviceRelationDO;
import com.viewsh.module.ops.dal.mysql.area.OpsAreaDeviceRelationMapper;
import com.viewsh.module.ops.environment.controller.admin.inspection.vo.DetectedBeaconVO;
import com.viewsh.module.ops.environment.controller.admin.inspection.vo.LocationVerifyResultVO;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.util.List;
import java.util.Map;
/**
* 巡检蓝牙位置校验 Service 实现
*/
@Service
@Validated
@Slf4j
public class InspectionLocationServiceImpl implements InspectionLocationService {
private static final String RELATION_TYPE_BEACON = "BEACON";
private static final int DEFAULT_RSSI_THRESHOLD = -70;
@Resource
private OpsAreaDeviceRelationMapper areaDeviceRelationMapper;
@Override
public LocationVerifyResultVO verifyLocation(Long areaId, List<DetectedBeaconVO> detectedBeacons) {
if (detectedBeacons == null || detectedBeacons.isEmpty()) {
return LocationVerifyResultVO.fail("未检测到蓝牙信标");
}
// 查询该区域绑定的 BEACON 设备
List<OpsAreaDeviceRelationDO> beaconRelations =
areaDeviceRelationMapper.selectListByAreaIdAndRelationType(areaId, RELATION_TYPE_BEACON);
if (beaconRelations.isEmpty()) {
// 区域未绑定信标,视为不需要位置校验,直接通过
log.info("[verifyLocation] 区域 {} 未绑定蓝牙信标,跳过位置校验", areaId);
return LocationVerifyResultVO.success();
}
// 匹配逻辑至少1个绑定信标匹配且 RSSI > 阈值
for (OpsAreaDeviceRelationDO relation : beaconRelations) {
String boundBeaconMac = extractBeaconMac(relation);
if (boundBeaconMac == null) {
continue;
}
int rssiThreshold = extractRssiThreshold(relation);
for (DetectedBeaconVO detected : detectedBeacons) {
if (boundBeaconMac.equalsIgnoreCase(detected.getMac())
&& detected.getRssi() >= rssiThreshold) {
log.info("[verifyLocation] 区域 {} 位置校验通过,匹配信标 {}RSSI={}",
areaId, detected.getMac(), detected.getRssi());
return LocationVerifyResultVO.success();
}
}
}
return LocationVerifyResultVO.fail("未匹配到该区域的蓝牙信标,请确认是否在正确位置");
}
/**
* 从设备关联的 configData 中提取信标 MAC 地址
*/
private String extractBeaconMac(OpsAreaDeviceRelationDO relation) {
Map<String, Object> configData = relation.getConfigData();
if (configData == null) {
// 退回到 deviceKey 作为 MAC 标识
return relation.getDeviceKey();
}
Object beaconMac = configData.get("beaconMac");
if (beaconMac != null) {
return beaconMac.toString();
}
return relation.getDeviceKey();
}
/**
* 从设备关联的 configData 中提取 RSSI 阈值
*/
@SuppressWarnings("unchecked")
private int extractRssiThreshold(OpsAreaDeviceRelationDO relation) {
Map<String, Object> configData = relation.getConfigData();
if (configData == null) {
return DEFAULT_RSSI_THRESHOLD;
}
Object enter = configData.get("enter");
if (enter instanceof Map) {
Object threshold = ((Map<String, Object>) enter).get("rssiThreshold");
if (threshold instanceof Number) {
return ((Number) threshold).intValue();
}
}
return DEFAULT_RSSI_THRESHOLD;
}
}

View File

@@ -0,0 +1,42 @@
package com.viewsh.module.ops.controller.admin.inspection;
import com.viewsh.framework.common.pojo.CommonResult;
import com.viewsh.module.ops.environment.controller.admin.inspection.vo.DetectedBeaconVO;
import com.viewsh.module.ops.environment.controller.admin.inspection.vo.LocationVerifyResultVO;
import com.viewsh.module.ops.environment.service.inspection.InspectionLocationService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import static com.viewsh.framework.common.pojo.CommonResult.success;
/**
* 管理后台 - 巡检 Controller
*/
@Tag(name = "管理后台 - 巡检")
@RestController
@RequestMapping("/ops/inspection")
@Validated
public class InspectionController {
@Resource
private InspectionLocationService inspectionLocationService;
@PostMapping("/verify-location")
@Operation(summary = "蓝牙位置校验")
@Parameter(name = "areaId", description = "区域ID", required = true)
@PreAuthorize("@ss.hasPermission('ops:inspection:create')")
public CommonResult<LocationVerifyResultVO> verifyLocation(
@RequestParam("areaId") Long areaId,
@Valid @RequestBody List<DetectedBeaconVO> detectedBeacons) {
return success(inspectionLocationService.verifyLocation(areaId, detectedBeacons));
}
}