|
|
@@ -0,0 +1,94 @@
|
|
|
+package com.etotem.cfc.service;
|
|
|
+
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
+import com.etotem.cfc.dto.DeviceVerifyDTO;
|
|
|
+import com.etotem.cfc.dto.DeviceVerifyResultDTO;
|
|
|
+import com.etotem.cfc.entity.DeviceBind;
|
|
|
+import com.etotem.cfc.mapper.DeviceBindMapper;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+
|
|
|
+import javax.annotation.Resource;
|
|
|
+import java.util.Date;
|
|
|
+import java.util.List;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 设备/账号绑定校验服务(child.auth.login · SC-C-18 设备/账号绑定校验)。
|
|
|
+ *
|
|
|
+ * <p>登录准入预检:按 {@code (device_id[, user_id])} 查询 {@code device_bind}——
|
|
|
+ * 新设备 → 登记为 pending(正常放行,登录后链路可确认);已绑定 normal → 放行;
|
|
|
+ * 异常设备 abnormal → 返回 needVerify=true 触发二次验证(TC-C-18 边界)。</p>
|
|
|
+ */
|
|
|
+@Slf4j
|
|
|
+@Service
|
|
|
+public class DeviceBindService {
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private DeviceBindMapper deviceBindMapper;
|
|
|
+
|
|
|
+ /** 绑定状态常量 */
|
|
|
+ private static final String STATUS_NORMAL = "normal";
|
|
|
+ private static final String STATUS_ABNORMAL = "abnormal";
|
|
|
+ private static final String STATUS_PENDING = "pending";
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 设备/账号绑定校验入口(登录链路预检;匿名可触达,无登录后权限维度)。
|
|
|
+ *
|
|
|
+ * @param dto 登录携带的设备信息(deviceId 必填)
|
|
|
+ * @return 校验结果:正常设备放行 / 新设备登记 / 异常设备二次验证(needVerify=true)
|
|
|
+ */
|
|
|
+ public DeviceVerifyResultDTO verifyDevice(DeviceVerifyDTO dto) {
|
|
|
+ String deviceId = dto.getDeviceId();
|
|
|
+ Long userId = dto.getUserId();
|
|
|
+
|
|
|
+ DeviceBind existing = findByDevice(deviceId, userId);
|
|
|
+ if (existing == null) {
|
|
|
+ // 新设备:登记为 pending(待确认),登录正常放行
|
|
|
+ registerNewDevice(dto);
|
|
|
+ return buildResult(false, "新设备已登记", STATUS_PENDING);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 已绑定设备:异常设备 → 二次验证;正常/待确认 → 放行
|
|
|
+ if (STATUS_ABNORMAL.equals(existing.getStatus())) {
|
|
|
+ return buildResult(true, "设备存在异常登录记录,需二次验证", STATUS_ABNORMAL);
|
|
|
+ }
|
|
|
+ return buildResult(false, "正常设备", existing.getStatus());
|
|
|
+ }
|
|
|
+
|
|
|
+ private DeviceVerifyResultDTO buildResult(boolean needVerify, String message, String status) {
|
|
|
+ DeviceVerifyResultDTO r = new DeviceVerifyResultDTO();
|
|
|
+ r.setNeedVerify(needVerify);
|
|
|
+ r.setKnownDevice(STATUS_NORMAL.equals(status));
|
|
|
+ r.setMessage(message);
|
|
|
+ r.setStatus(status);
|
|
|
+ return r;
|
|
|
+ }
|
|
|
+
|
|
|
+ private DeviceBind findByDevice(String deviceId, Long userId) {
|
|
|
+ List<DeviceBind> rows = deviceBindMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<DeviceBind>()
|
|
|
+ .eq(DeviceBind::getDeviceId, deviceId)
|
|
|
+ .eq(userId != null, DeviceBind::getUserId, userId)
|
|
|
+ .last("LIMIT 1"));
|
|
|
+ return rows.isEmpty() ? null : rows.get(0);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void registerNewDevice(DeviceVerifyDTO dto) {
|
|
|
+ Date now = new Date();
|
|
|
+ DeviceBind bind = new DeviceBind();
|
|
|
+ bind.setUserId(dto.getUserId());
|
|
|
+ bind.setMemberId(dto.getMemberId());
|
|
|
+ bind.setDeviceId(dto.getDeviceId());
|
|
|
+ bind.setDeviceName(dto.getDeviceName());
|
|
|
+ bind.setDeviceModel(dto.getDeviceModel());
|
|
|
+ bind.setPlatform(dto.getPlatform());
|
|
|
+ bind.setStatus(STATUS_PENDING);
|
|
|
+ bind.setFirstSeenTime(now);
|
|
|
+ bind.setLastLoginTime(now);
|
|
|
+ bind.setLoginCount(1);
|
|
|
+ bind.setCreatedAt(now);
|
|
|
+ bind.setUpdatedAt(now);
|
|
|
+ deviceBindMapper.insert(bind);
|
|
|
+ log.info("DeviceBind: 新设备登记 deviceId={}, userId={}", dto.getDeviceId(), dto.getUserId());
|
|
|
+ }
|
|
|
+}
|