feat(ops): 蓝牙位置校验服务及接口(Task 4)
This commit is contained in:
@@ -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 + Mapper(3表、3DO、3Mapper)
|
||||
- [x] Task 2: 巡检模板 CRUD(Service + Impl + Controller + VOs)
|
||||
- [x] Task 3: 获取动态表单接口(list-by-area,area_id → function_type → template 查询链路)
|
||||
- [x] Task 4: 蓝牙位置校验(InspectionLocationService + verify-location 端点)
|
||||
|
||||
## Notes
|
||||
- 巡检是保洁业务线内的子功能,代码放 viewsh-module-environment-biz
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user