feat: 会话记录接入客户端 X-Device-Id(auth_sessions.device_id)
- Flutter 端每次请求已携带 X-Device-Id;auth 读取该头(截断 128)经 CreateSessionRequest 透传,user 落 auth_sessions.device_id,为多设备会话列表备数据 - 门禁:./mvnw clean test → BUILD SUCCESS,74 测试 0 失败(新增 registerForwardsDeviceIdHeaderToTheSessionRecord + 会话落库断言) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -53,14 +53,19 @@ public class AuthController {
|
||||
}
|
||||
|
||||
private AuthService.ClientInfo clientInfo(HttpServletRequest request) {
|
||||
String userAgent = request.getHeader(HttpHeaders.USER_AGENT);
|
||||
if (userAgent != null && userAgent.length() > 512) {
|
||||
userAgent = userAgent.substring(0, 512);
|
||||
}
|
||||
String deviceId = truncate(request.getHeader("X-Device-Id"), 128);
|
||||
String userAgent = truncate(request.getHeader(HttpHeaders.USER_AGENT), 512);
|
||||
String forwarded = request.getHeader("X-Forwarded-For");
|
||||
String ip = forwarded != null && !forwarded.isBlank()
|
||||
? forwarded.split(",")[0].trim()
|
||||
: request.getRemoteAddr();
|
||||
return new AuthService.ClientInfo(userAgent, ip);
|
||||
return new AuthService.ClientInfo(deviceId, userAgent, ip);
|
||||
}
|
||||
|
||||
private static String truncate(String value, int maxLength) {
|
||||
if (value == null || value.isBlank()) {
|
||||
return null;
|
||||
}
|
||||
return value.length() > maxLength ? value.substring(0, maxLength) : value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ import java.util.UUID;
|
||||
public class AuthService {
|
||||
|
||||
/** Device metadata forwarded to the session record (observability only). */
|
||||
public record ClientInfo(String userAgent, String ipAddress) {
|
||||
public record ClientInfo(String deviceId, String userAgent, String ipAddress) {
|
||||
}
|
||||
|
||||
private final UserClient userClient;
|
||||
@@ -88,7 +88,8 @@ public class AuthService {
|
||||
|
||||
private AuthTokenResponse openSession(UUID userId, ClientInfo clientInfo) {
|
||||
SessionTokens tokens = requireData(sessionClient.create(new CreateSessionRequest(
|
||||
userId, clientInfo.userAgent(), clientInfo.ipAddress())), "创建会话失败");
|
||||
userId, clientInfo.deviceId(), clientInfo.userAgent(), clientInfo.ipAddress())),
|
||||
"创建会话失败");
|
||||
return assemble(tokens);
|
||||
}
|
||||
|
||||
|
||||
+20
@@ -8,6 +8,7 @@ import com.patbond.patbond.auth.support.TestJwtKeys;
|
||||
import com.patbond.patbond.common.error.BusinessException;
|
||||
import com.patbond.patbond.common.error.ErrorCode;
|
||||
import com.patbond.patbond.common.response.ApiResponse;
|
||||
import com.patbond.patbond.common.session.CreateSessionRequest;
|
||||
import com.patbond.patbond.common.session.RevokeSessionRequest;
|
||||
import com.patbond.patbond.common.session.SessionTokens;
|
||||
import com.patbond.patbond.common.user.UserProfile;
|
||||
@@ -112,6 +113,25 @@ class AuthControllerTest {
|
||||
.andExpect(jsonPath("$.data.expiresAt").doesNotExist());
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerForwardsDeviceIdHeaderToTheSessionRecord() throws Exception {
|
||||
when(userClient.createUser(any())).thenReturn(ApiResponse.success(
|
||||
new UserProfile(USER_ID, "alice", null, "+8613800138000", OffsetDateTime.now())));
|
||||
when(sessionClient.create(any())).thenReturn(ApiResponse.success(sessionTokens()));
|
||||
|
||||
mockMvc.perform(post("/api/v1/auth/register")
|
||||
.header("X-Device-Id", "pixel-8-of-alice")
|
||||
.contentType(APPLICATION_JSON)
|
||||
.content(REGISTER_BODY))
|
||||
.andExpect(status().isOk());
|
||||
|
||||
ArgumentCaptor<CreateSessionRequest> captor =
|
||||
ArgumentCaptor.forClass(CreateSessionRequest.class);
|
||||
verify(sessionClient).create(captor.capture());
|
||||
assertThat(captor.getValue().getDeviceId()).isEqualTo("pixel-8-of-alice");
|
||||
assertThat(captor.getValue().getUserId()).isEqualTo(USER_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerPropagatesDuplicateUsernameAsConflict() throws Exception {
|
||||
when(userClient.createUser(any()))
|
||||
|
||||
Reference in New Issue
Block a user