fix: normalize channel login open ids

This commit is contained in:
hy001 2026-05-18 14:04:17 +08:00
parent 2a7be2ca92
commit 916ce628be
7 changed files with 207 additions and 62 deletions

View File

@ -224,9 +224,18 @@ public class EndpointRestController {
*/ */
@PostMapping("/account/login/channel") @PostMapping("/account/login/channel")
public TokenCredentialCO login(@RequestBody @Validated UserChannelCredentialCmd cmd, HttpServletRequest request) { public TokenCredentialCO login(@RequestBody @Validated UserChannelCredentialCmd cmd, HttpServletRequest request) {
TokenCredentialCO tokenCredentialCO = processToken.createUserCredential(ResponseAssert.requiredSuccess( try {
appUserAccountClient.channelCredential(cmd))); TokenCredentialCO tokenCredentialCO = processToken.createUserCredential(ResponseAssert.requiredSuccess(
return submitLoginAccessValidation(tokenCredentialCO, cmd.requireReqSysOrigin(), cmd.getReqZoneId(), request); appUserAccountClient.channelCredential(cmd)));
return submitLoginAccessValidation(tokenCredentialCO, cmd.requireReqSysOrigin(), cmd.getReqZoneId(), request);
} catch (ResponseException ex) {
if (isChannelCredentialDecodeError(ex)) {
log.warn("channel login credential decode error, return USER_NOT_REGISTERED. authType={}",
cmd.getAuthType());
throw new ResponseException(UserErrorCode.USER_NOT_REGISTERED);
}
throw ex;
}
} }
/** /**
@ -287,6 +296,13 @@ public class EndpointRestController {
|| Objects.equals(ex.getErrorCode(), UserErrorCode.SOURCE_REGISTERED.getCode()); || Objects.equals(ex.getErrorCode(), UserErrorCode.SOURCE_REGISTERED.getCode());
} }
private boolean isChannelCredentialDecodeError(ResponseException ex) {
return Objects.equals(ex.getErrorCode(), ResponseErrorCode.NOT_ACCEPTABLE.getCode())
&& Objects.equals(ex.getErrorCodeName(), "ERROR_INNER_DECODER")
&& Objects.toString(ex.getExtValues(), "")
.contains("/app-user/account/channel/credential");
}
private TokenCredentialCO createTokenCredential( private TokenCredentialCO createTokenCredential(
UserAccountDTO account, UserAccountDTO account,
String reqSysOrigin, String reqSysOrigin,

View File

@ -12,6 +12,7 @@ import com.red.circle.tool.core.text.StringUtils;
import feign.Request; import feign.Request;
import feign.Response; import feign.Response;
import feign.codec.ErrorDecoder; import feign.codec.ErrorDecoder;
import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.HashMap; import java.util.HashMap;
@ -69,11 +70,14 @@ public class FeignErrorDecoder implements ErrorDecoder {
String errorCodeName = Objects.equals(response.status(), 429) String errorCodeName = Objects.equals(response.status(), 429)
? "REQ_LIMIT_INNER_DECODER" ? "REQ_LIMIT_INNER_DECODER"
: "ERROR_INNER_DECODER"; : "ERROR_INNER_DECODER";
return new ResponseException(response.status(), errorCodeName, getErrorMsg(methodKey, response)); Map<String, Object> extValues = new HashMap<>();
extValues.put("innerRequest", reqMapping(response));
extValues.put("innerMethodKey", methodKey);
return new ResponseException(response.status(), errorCodeName, getErrorMsg(response), extValues);
} }
private String getErrorMsg(String methodKey, Response response) { private String getErrorMsg(Response response) {
return StringUtils.isBlankOrElse(response.reason(), "") + reqMapping(response); return StringUtils.isBlankOrElse(response.reason(), "Inner service error");
} }
private ResponseException parseResponseException(String methodKey, Response response) { private ResponseException parseResponseException(String methodKey, Response response) {
@ -103,15 +107,44 @@ public class FeignErrorDecoder implements ErrorDecoder {
} }
try (InputStream inputStream = response.body().asInputStream()) { try (InputStream inputStream = response.body().asInputStream()) {
String responseCompression = ApplicationContextUtils.getApplicationContext() byte[] bodyBytes = inputStream.readAllBytes();
.getEnvironment() if (bodyBytes.length == 0) {
.getProperty("spring.cloud.openfeign.compression.response.enabled"); log.warn("Feign error response body is empty, status={}, reason={}, request={}",
return JacksonUtils.readValue(inputStream, new TypeReference<>() { response.status(), response.reason(), reqMapping(response));
}, Objects.equals(responseCompression, "true")); return null;
}
boolean gzipEncoded = isGzipEncoded(response);
ResultResponse<Void> responseBody = parseResponseBytes(response, bodyBytes, gzipEncoded);
if (Objects.nonNull(responseBody)) {
return responseBody;
}
return parseResponseBytes(response, bodyBytes, !gzipEncoded);
} catch (IOException | RuntimeException ex) { } catch (IOException | RuntimeException ex) {
log.warn("Parse feign error response body failed, status={}, reason={}, request={}", log.warn("Read feign error response body failed, status={}, reason={}, request={}",
response.status(), response.reason(), reqMapping(response), ex); response.status(), response.reason(), reqMapping(response), ex);
return null; return null;
} }
} }
private ResultResponse<Void> parseResponseBytes(
Response response,
byte[] bodyBytes,
boolean compression) {
try (ByteArrayInputStream inputStream = new ByteArrayInputStream(bodyBytes)) {
return JacksonUtils.readValue(inputStream, new TypeReference<>() {
}, compression);
} catch (IOException | RuntimeException ex) {
log.warn("Parse feign error response body failed, status={}, reason={}, request={}, compression={}",
response.status(), response.reason(), reqMapping(response), compression, ex);
return null;
}
}
private boolean isGzipEncoded(Response response) {
return response.headers().entrySet().stream()
.filter(entry -> Objects.nonNull(entry.getKey()))
.filter(entry -> "Content-Encoding".equalsIgnoreCase(entry.getKey()))
.flatMap(entry -> entry.getValue().stream())
.anyMatch(value -> "gzip".equalsIgnoreCase(value));
}
} }

View File

@ -2,6 +2,7 @@ package com.red.circle.other.inner.model.cmd.user;
import com.red.circle.common.business.dto.cmd.AppExtCommand; import com.red.circle.common.business.dto.cmd.AppExtCommand;
import com.red.circle.other.inner.enums.user.AuthTypeEnum; import com.red.circle.other.inner.enums.user.AuthTypeEnum;
import com.red.circle.tool.core.text.StringUtils;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import lombok.Data; import lombok.Data;
@ -33,10 +34,14 @@ public class UserChannelCredentialCmd extends AppExtCommand {
private String openId; private String openId;
public String openId() { public String openId() {
if (openId.contains("_") && !AuthTypeEnum.HUAWEI.name().equals(authType.name())) { if (StringUtils.isBlank(openId)) {
return openId; return openId;
} }
return this.getOpenId().concat("_").concat(this.requireReqSysOrigin()); String suffix = "_".concat(requireReqSysOrigin());
if (openId.endsWith(suffix)) {
return openId;
}
return openId.concat(suffix);
} }
} }

View File

@ -105,11 +105,19 @@ public class CreateAccountCmd extends AppExtCommand {
StringUtils.isNotBlank(this.openId) StringUtils.isNotBlank(this.openId)
&& !Objects.equals(this.openId, "null")); && !Objects.equals(this.openId, "null"));
} }
setOpenId(this.openId.concat("_").concat(requireReqSysOrigin())); setOpenId(openIdWithReqSysOrigin(this.openId));
} }
public String registerOpenId() { public String registerOpenId() {
return checkTypeMobile() ? this.mobile.getFullPhoneNumber() : this.openId; return checkTypeMobile() ? this.mobile.getFullPhoneNumber() : this.openId;
} }
private String openIdWithReqSysOrigin(String sourceOpenId) {
String suffix = "_".concat(requireReqSysOrigin());
if (sourceOpenId.endsWith(suffix)) {
return sourceOpenId;
}
return sourceOpenId.concat(suffix);
}
} }

View File

@ -0,0 +1,42 @@
package com.red.circle.other.inner.model.cmd.user;
import static org.junit.Assert.assertEquals;
import com.red.circle.framework.core.dto.ReqSysOrigin;
import com.red.circle.other.inner.enums.user.AuthTypeEnum;
import com.red.circle.other.inner.model.cmd.user.account.CreateAccountCmd;
import org.junit.Test;
public class UserOpenIdNormalizeTest {
@Test
public void channelLoginAppendsOriginWhenGoogleOpenIdContainsUnderscore() {
UserChannelCredentialCmd cmd = new UserChannelCredentialCmd();
cmd.setAuthType(AuthTypeEnum.GOOGLE);
cmd.setOpenId("google_open_id");
cmd.setReqSysOrigin(ReqSysOrigin.of("LIKEI"));
assertEquals("google_open_id_LIKEI", cmd.openId());
}
@Test
public void channelLoginDoesNotAppendOriginTwice() {
UserChannelCredentialCmd cmd = new UserChannelCredentialCmd();
cmd.setAuthType(AuthTypeEnum.GOOGLE);
cmd.setOpenId("google_open_id_LIKEI");
cmd.setReqSysOrigin(ReqSysOrigin.of("LIKEI"));
assertEquals("google_open_id_LIKEI", cmd.openId());
}
@Test
public void accountCreateDoesNotAppendOriginTwice() {
CreateAccountCmd cmd = new CreateAccountCmd();
cmd.setType(AuthTypeEnum.GOOGLE.name());
cmd.setOpenId("google_open_id_LIKEI");
cmd.setReqSysOrigin(ReqSysOrigin.of("LIKEI"));
cmd.changeOpenId();
assertEquals("google_open_id_LIKEI", cmd.getOpenId());
}
}

View File

@ -48,7 +48,15 @@ public class BindUserAuthTypeCmd extends AppExtCommand {
return isTypeMobile() return isTypeMobile()
? mobile.getFullPhoneNumber() ? mobile.getFullPhoneNumber()
: openId.concat("_").concat(requireReqSysOrigin()); : openIdWithReqSysOrigin();
}
private String openIdWithReqSysOrigin() {
String suffix = "_".concat(requireReqSysOrigin());
if (openId.endsWith(suffix)) {
return openId;
}
return openId.concat(suffix);
} }
} }

View File

@ -12,6 +12,7 @@ import com.red.circle.tool.core.text.StringUtils;
import feign.Request; import feign.Request;
import feign.Response; import feign.Response;
import feign.codec.ErrorDecoder; import feign.codec.ErrorDecoder;
import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.HashMap; import java.util.HashMap;
@ -69,11 +70,14 @@ public class FeignErrorDecoder implements ErrorDecoder {
String errorCodeName = Objects.equals(response.status(), 429) String errorCodeName = Objects.equals(response.status(), 429)
? "REQ_LIMIT_INNER_DECODER" ? "REQ_LIMIT_INNER_DECODER"
: "ERROR_INNER_DECODER"; : "ERROR_INNER_DECODER";
return new ResponseException(response.status(), errorCodeName, getErrorMsg(methodKey, response)); Map<String, Object> extValues = new HashMap<>();
extValues.put("innerRequest", reqMapping(response));
extValues.put("innerMethodKey", methodKey);
return new ResponseException(response.status(), errorCodeName, getErrorMsg(response), extValues);
} }
private String getErrorMsg(String methodKey, Response response) { private String getErrorMsg(Response response) {
return StringUtils.isBlankOrElse(response.reason(), "") + reqMapping(response); return StringUtils.isBlankOrElse(response.reason(), "Inner service error");
} }
private ResponseException parseResponseException(String methodKey, Response response) { private ResponseException parseResponseException(String methodKey, Response response) {
@ -103,15 +107,44 @@ public class FeignErrorDecoder implements ErrorDecoder {
} }
try (InputStream inputStream = response.body().asInputStream()) { try (InputStream inputStream = response.body().asInputStream()) {
String responseCompression = ApplicationContextUtils.getApplicationContext() byte[] bodyBytes = inputStream.readAllBytes();
.getEnvironment() if (bodyBytes.length == 0) {
.getProperty("spring.cloud.openfeign.compression.response.enabled"); log.warn("Feign error response body is empty, status={}, reason={}, request={}",
return JacksonUtils.readValue(inputStream, new TypeReference<>() { response.status(), response.reason(), reqMapping(response));
}, Objects.equals(responseCompression, "true")); return null;
}
boolean gzipEncoded = isGzipEncoded(response);
ResultResponse<Void> responseBody = parseResponseBytes(response, bodyBytes, gzipEncoded);
if (Objects.nonNull(responseBody)) {
return responseBody;
}
return parseResponseBytes(response, bodyBytes, !gzipEncoded);
} catch (IOException | RuntimeException ex) { } catch (IOException | RuntimeException ex) {
log.warn("Parse feign error response body failed, status={}, reason={}, request={}", log.warn("Read feign error response body failed, status={}, reason={}, request={}",
response.status(), response.reason(), reqMapping(response), ex); response.status(), response.reason(), reqMapping(response), ex);
return null; return null;
} }
} }
private ResultResponse<Void> parseResponseBytes(
Response response,
byte[] bodyBytes,
boolean compression) {
try (ByteArrayInputStream inputStream = new ByteArrayInputStream(bodyBytes)) {
return JacksonUtils.readValue(inputStream, new TypeReference<>() {
}, compression);
} catch (IOException | RuntimeException ex) {
log.warn("Parse feign error response body failed, status={}, reason={}, request={}, compression={}",
response.status(), response.reason(), reqMapping(response), compression, ex);
return null;
}
}
private boolean isGzipEncoded(Response response) {
return response.headers().entrySet().stream()
.filter(entry -> Objects.nonNull(entry.getKey()))
.filter(entry -> "Content-Encoding".equalsIgnoreCase(entry.getKey()))
.flatMap(entry -> entry.getValue().stream())
.anyMatch(value -> "gzip".equalsIgnoreCase(value));
}
} }