diff --git a/backend/src/main/java/com/imeeting/controller/android/AndroidAuthController.java b/backend/src/main/java/com/imeeting/controller/android/AndroidAuthController.java index c1adfd4..018a05f 100644 --- a/backend/src/main/java/com/imeeting/controller/android/AndroidAuthController.java +++ b/backend/src/main/java/com/imeeting/controller/android/AndroidAuthController.java @@ -1,6 +1,7 @@ package com.imeeting.controller.android; import com.imeeting.service.android.AndroidDeviceBindingService; +import com.imeeting.service.android.AndroidDeviceRegistrationService; import com.imeeting.support.AndroidRequestLogHelper; import com.unisbase.auth.JwtTokenProvider; import com.unisbase.common.ApiResponse; @@ -32,6 +33,7 @@ import org.springframework.web.bind.annotation.RestController; public class AndroidAuthController { private final AuthService authService; private final AndroidDeviceBindingService androidDeviceBindingService; + private final AndroidDeviceRegistrationService androidDeviceRegistrationService; private final JwtTokenProvider jwtTokenProvider; @Operation(summary = "Android登录") @@ -52,6 +54,10 @@ public class AndroidAuthController { "deviceId", deviceId, "appVersion", appVersion, "platform", platform); + if (!StringUtils.hasText(deviceId)) { + throw new IllegalArgumentException("X-Android-Device-Id不能为空"); + } + androidDeviceRegistrationService.requireRegistered(deviceId.trim()); TokenResponse response = authService.login(request, true); if (response != null && response.getUser() != null && response.getCurrentTenantId() != null && StringUtils.hasText(deviceId)) { androidDeviceBindingService.bindPrivateDevice( diff --git a/backend/src/main/java/com/imeeting/controller/android/AndroidDeviceController.java b/backend/src/main/java/com/imeeting/controller/android/AndroidDeviceController.java new file mode 100644 index 0000000..12110b7 --- /dev/null +++ b/backend/src/main/java/com/imeeting/controller/android/AndroidDeviceController.java @@ -0,0 +1,53 @@ +package com.imeeting.controller.android; + +import com.imeeting.dto.android.AndroidAuthContext; +import com.imeeting.dto.android.AndroidDeviceRegisterRequest; +import com.imeeting.dto.android.AndroidDeviceRegisterResponse; +import com.imeeting.service.android.AndroidAuthService; +import com.imeeting.service.android.AndroidDeviceRegistrationService; +import com.imeeting.support.AndroidRequestLogHelper; +import com.unisbase.common.ApiResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@Tag(name = "Android设备接口") +@RestController +@RequestMapping("/api/android/devices") +@RequiredArgsConstructor +@Slf4j +public class AndroidDeviceController { + private final AndroidAuthService androidAuthService; + private final AndroidDeviceRegistrationService androidDeviceRegistrationService; + + @Operation(summary = "设备自注册") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse( + responseCode = "200", + description = "返回设备注册后的基础信息", + content = @Content(schema = @Schema(implementation = AndroidDeviceRegisterResponse.class)) + ) + }) + @PostMapping("/register") + public ApiResponse register(HttpServletRequest request, + @RequestBody(required = false) AndroidDeviceRegisterRequest command) { + AndroidRequestLogHelper.logRequest(log, "Android设备", "设备自注册", "request", command); + AndroidAuthContext authContext = androidAuthService.authenticateHttp(request, false); + AndroidDeviceRegisterResponse response = androidDeviceRegistrationService.register( + authContext.getDeviceId(), + command == null ? null : command.getDeviceName(), + command == null ? authContext.getPlatform() : command.getTerminalType(), + command == null ? authContext.getAppVersion() : command.getTerminalVersion() + ); + return ApiResponse.ok(response); + } +} diff --git a/backend/src/main/java/com/imeeting/controller/android/AndroidPublicMeetingController.java b/backend/src/main/java/com/imeeting/controller/android/AndroidPublicMeetingController.java index 6e89332..bdd646c 100644 --- a/backend/src/main/java/com/imeeting/controller/android/AndroidPublicMeetingController.java +++ b/backend/src/main/java/com/imeeting/controller/android/AndroidPublicMeetingController.java @@ -113,6 +113,9 @@ public class AndroidPublicMeetingController { throw new RuntimeException("设备ID不能为空"); } var device = deviceInfoMapper.selectByDeviceCodeIgnoreTenant(deviceId); + if (device == null) { + throw new RuntimeException("设备未注册,请先完成设备注册"); + } if (device != null && device.getUserId() != null) { throw new RuntimeException("当前设备为私有设备,请走私有设备发会流程"); } diff --git a/backend/src/main/java/com/imeeting/mapper/DeviceInfoMapper.java b/backend/src/main/java/com/imeeting/mapper/DeviceInfoMapper.java index 3825e52..c22b3f2 100644 --- a/backend/src/main/java/com/imeeting/mapper/DeviceInfoMapper.java +++ b/backend/src/main/java/com/imeeting/mapper/DeviceInfoMapper.java @@ -41,6 +41,20 @@ public interface DeviceInfoMapper extends BaseMapper { """) int updateConnectionInfoByIdIgnoreTenant(DeviceInfoEntity deviceInfoEntity); + @InterceptorIgnore(tenantLine = "true") + @Update(""" + + """) + int updateBaseInfoByIdIgnoreTenant(DeviceInfoEntity deviceInfoEntity); + @InterceptorIgnore(tenantLine = "true") @Update(""" UPDATE biz_device_info diff --git a/backend/src/main/java/com/imeeting/service/android/AndroidAuthService.java b/backend/src/main/java/com/imeeting/service/android/AndroidAuthService.java index 2849e7f..20a1db8 100644 --- a/backend/src/main/java/com/imeeting/service/android/AndroidAuthService.java +++ b/backend/src/main/java/com/imeeting/service/android/AndroidAuthService.java @@ -7,4 +7,6 @@ public interface AndroidAuthService { AndroidAuthContext authenticateGrpc(String deviceId, String appVersion, String platform); AndroidAuthContext authenticateHttp(HttpServletRequest request); + + AndroidAuthContext authenticateHttp(HttpServletRequest request, boolean requireRegistered); } diff --git a/backend/src/main/java/com/imeeting/service/android/impl/AndroidAuthServiceImpl.java b/backend/src/main/java/com/imeeting/service/android/impl/AndroidAuthServiceImpl.java index 063dd0f..6818664 100644 --- a/backend/src/main/java/com/imeeting/service/android/impl/AndroidAuthServiceImpl.java +++ b/backend/src/main/java/com/imeeting/service/android/impl/AndroidAuthServiceImpl.java @@ -40,18 +40,21 @@ public class AndroidAuthServiceImpl implements AndroidAuthService { if (properties.isEnabled() && !properties.isAllowAnonymous()) { throw new RuntimeException("Android gRPC push does not allow anonymous access"); } - assertDeviceEnabled(deviceId); + DeviceInfoEntity device = requireRegisteredDevice(deviceId); + assertDeviceEnabled(device); AndroidAuthContext context = buildContext("NONE", true, deviceId, null, appVersion, platform, null, null, null, null); - DeviceInfoEntity device = deviceInfoMapper.selectByDeviceCodeIgnoreTenant(deviceId.trim()); - if (device != null) { - context.setUserId(device.getUserId()); - context.setTenantId(device.getTenantId()); - } + context.setUserId(device.getUserId()); + context.setTenantId(device.getTenantId()); return context; } @Override public AndroidAuthContext authenticateHttp(HttpServletRequest request) { + return authenticateHttp(request, true); + } + + @Override + public AndroidAuthContext authenticateHttp(HttpServletRequest request, boolean requireRegistered) { LoginUser loginUser = currentLoginUser(); String resolvedToken = resolveHttpToken(request); String deviceId = firstHeader(request, HEADER_DEVICE_ID); @@ -61,7 +64,8 @@ public class AndroidAuthServiceImpl implements AndroidAuthService { requireAndroidHttpHeaders(deviceId, appVersion, platform); log.info("[安卓接口访问]X-Android-Device-Id={},X-Android-App-Version={},X-Android-Platform={}",deviceId,appVersion,platform); - assertDeviceEnabled(deviceId); + DeviceInfoEntity device = requireRegistered ? requireRegisteredDevice(deviceId) : findDevice(deviceId); + assertDeviceEnabled(device); if (loginUser != null) { androidDeviceBindingService.validatePrivateDeviceAccess(deviceId, loginUser.getTenantId(), loginUser.getUserId()); return buildContext("USER_JWT", false, @@ -192,16 +196,27 @@ public class AndroidAuthServiceImpl implements AndroidAuthService { } } - private void assertDeviceEnabled(String deviceId) { - if (!StringUtils.hasText(deviceId)) { - return; - } - DeviceInfoEntity device = deviceInfoMapper.selectByDeviceCodeIgnoreTenant(deviceId.trim()); + private void assertDeviceEnabled(DeviceInfoEntity device) { if (device != null && device.getStatus() != null && device.getStatus() == 0) { throw new BusinessException("403", "设备被禁用"); } } + private DeviceInfoEntity requireRegisteredDevice(String deviceId) { + DeviceInfoEntity device = findDevice(deviceId); + if (device == null) { + throw new RuntimeException("设备未注册,请先调用设备注册接口"); + } + return device; + } + + private DeviceInfoEntity findDevice(String deviceId) { + if (!StringUtils.hasText(deviceId)) { + return null; + } + return deviceInfoMapper.selectByDeviceCodeIgnoreTenant(deviceId.trim()); + } + private String normalizeToken(String token) { if (!StringUtils.hasText(token)) { return null; diff --git a/backend/src/main/java/com/imeeting/service/android/impl/AndroidDeviceBindingServiceImpl.java b/backend/src/main/java/com/imeeting/service/android/impl/AndroidDeviceBindingServiceImpl.java index a5b9e97..5ee7997 100644 --- a/backend/src/main/java/com/imeeting/service/android/impl/AndroidDeviceBindingServiceImpl.java +++ b/backend/src/main/java/com/imeeting/service/android/impl/AndroidDeviceBindingServiceImpl.java @@ -21,19 +21,10 @@ public class AndroidDeviceBindingServiceImpl implements AndroidDeviceBindingServ throw new RuntimeException("设备登录缺少绑定上下文"); } DeviceInfoEntity existing = deviceInfoMapper.selectByDeviceCodeIgnoreTenant(deviceCode.trim()); - LocalDateTime now = LocalDateTime.now(); if (existing == null) { - DeviceInfoEntity created = new DeviceInfoEntity(); - created.setTenantId(tenantId); - created.setUserId(userId); - created.setDeviceCode(deviceCode.trim()); - created.setTerminalType(normalize(platform)); - created.setTerminalVersion(normalize(appVersion)); - created.setLastOnlineAt(now); - created.setStatus(1); - deviceInfoMapper.insert(created); - return; + throw new RuntimeException("设备未注册,请先完成设备注册"); } + LocalDateTime now = LocalDateTime.now(); existing.setTenantId(tenantId); existing.setUserId(userId); existing.setTerminalType(normalize(platform)); diff --git a/backend/src/main/java/com/imeeting/service/biz/impl/DeviceOnlineManagementServiceImpl.java b/backend/src/main/java/com/imeeting/service/biz/impl/DeviceOnlineManagementServiceImpl.java index b16f230..56a0746 100644 --- a/backend/src/main/java/com/imeeting/service/biz/impl/DeviceOnlineManagementServiceImpl.java +++ b/backend/src/main/java/com/imeeting/service/biz/impl/DeviceOnlineManagementServiceImpl.java @@ -34,21 +34,10 @@ public class DeviceOnlineManagementServiceImpl implements DeviceOnlineManagement } String deviceCode = authContext.getDeviceId().trim(); DeviceInfoEntity existing = deviceInfoMapper.selectByDeviceCodeIgnoreTenant(deviceCode); - LocalDateTime now = LocalDateTime.now(); if (existing == null) { - DeviceInfoEntity created = new DeviceInfoEntity(); - created.setTenantId(authContext.getTenantId()); - created.setUserId(authContext.getUserId()); - created.setDeviceCode(deviceCode); - created.setDeviceName(null); - created.setTerminalType(normalizeTerminalType(authContext.getPlatform())); - created.setTerminalVersion(normalize(authContext.getAppVersion())); - created.setLastOnlineAt(now); - created.setStatus(1); - deviceInfoMapper.insert(created); return; } - + LocalDateTime now = LocalDateTime.now(); existing.setTerminalType(normalizeTerminalType(authContext.getPlatform())); existing.setTerminalVersion(normalize(authContext.getAppVersion())); existing.setLastOnlineAt(now);