diff --git a/rc-auth/src/main/java/com/red/circle/auth/endpoint/EndpointRestController.java b/rc-auth/src/main/java/com/red/circle/auth/endpoint/EndpointRestController.java index 0e179832..fe17bce9 100644 --- a/rc-auth/src/main/java/com/red/circle/auth/endpoint/EndpointRestController.java +++ b/rc-auth/src/main/java/com/red/circle/auth/endpoint/EndpointRestController.java @@ -224,10 +224,19 @@ public class EndpointRestController { */ @PostMapping("/account/login/channel") public TokenCredentialCO login(@RequestBody @Validated UserChannelCredentialCmd cmd, HttpServletRequest request) { - TokenCredentialCO tokenCredentialCO = processToken.createUserCredential(ResponseAssert.requiredSuccess( - appUserAccountClient.channelCredential(cmd))); - return submitLoginAccessValidation(tokenCredentialCO, cmd.requireReqSysOrigin(), cmd.getReqZoneId(), request); - } + try { + TokenCredentialCO tokenCredentialCO = processToken.createUserCredential(ResponseAssert.requiredSuccess( + 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()); } + 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( UserAccountDTO account, String reqSysOrigin, diff --git a/rc-auth/src/main/java/com/red/circle/framework/feign/decoder/FeignErrorDecoder.java b/rc-auth/src/main/java/com/red/circle/framework/feign/decoder/FeignErrorDecoder.java index ef3aa6ae..bf396c8f 100644 --- a/rc-auth/src/main/java/com/red/circle/framework/feign/decoder/FeignErrorDecoder.java +++ b/rc-auth/src/main/java/com/red/circle/framework/feign/decoder/FeignErrorDecoder.java @@ -12,6 +12,7 @@ import com.red.circle.tool.core.text.StringUtils; import feign.Request; import feign.Response; import feign.codec.ErrorDecoder; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; @@ -69,11 +70,14 @@ public class FeignErrorDecoder implements ErrorDecoder { String errorCodeName = Objects.equals(response.status(), 429) ? "REQ_LIMIT_INNER_DECODER" : "ERROR_INNER_DECODER"; - return new ResponseException(response.status(), errorCodeName, getErrorMsg(methodKey, response)); + Map 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) { - return StringUtils.isBlankOrElse(response.reason(), "") + reqMapping(response); + private String getErrorMsg(Response response) { + return StringUtils.isBlankOrElse(response.reason(), "Inner service error"); } private ResponseException parseResponseException(String methodKey, Response response) { @@ -103,15 +107,44 @@ public class FeignErrorDecoder implements ErrorDecoder { } try (InputStream inputStream = response.body().asInputStream()) { - String responseCompression = ApplicationContextUtils.getApplicationContext() - .getEnvironment() - .getProperty("spring.cloud.openfeign.compression.response.enabled"); - return JacksonUtils.readValue(inputStream, new TypeReference<>() { - }, Objects.equals(responseCompression, "true")); + byte[] bodyBytes = inputStream.readAllBytes(); + if (bodyBytes.length == 0) { + log.warn("Feign error response body is empty, status={}, reason={}, request={}", + response.status(), response.reason(), reqMapping(response)); + return null; + } + boolean gzipEncoded = isGzipEncoded(response); + ResultResponse responseBody = parseResponseBytes(response, bodyBytes, gzipEncoded); + if (Objects.nonNull(responseBody)) { + return responseBody; + } + return parseResponseBytes(response, bodyBytes, !gzipEncoded); } 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); return null; } } + + private ResultResponse 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)); + } } diff --git a/rc-service/rc-inner-api/other-inner/other-inner-model/src/main/java/com/red/circle/other/inner/model/cmd/user/UserChannelCredentialCmd.java b/rc-service/rc-inner-api/other-inner/other-inner-model/src/main/java/com/red/circle/other/inner/model/cmd/user/UserChannelCredentialCmd.java index 092d3116..ff55be46 100644 --- a/rc-service/rc-inner-api/other-inner/other-inner-model/src/main/java/com/red/circle/other/inner/model/cmd/user/UserChannelCredentialCmd.java +++ b/rc-service/rc-inner-api/other-inner/other-inner-model/src/main/java/com/red/circle/other/inner/model/cmd/user/UserChannelCredentialCmd.java @@ -1,11 +1,12 @@ package com.red.circle.other.inner.model.cmd.user; -import com.red.circle.common.business.dto.cmd.AppExtCommand; -import com.red.circle.other.inner.enums.user.AuthTypeEnum; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import lombok.Data; -import lombok.EqualsAndHashCode; +import com.red.circle.common.business.dto.cmd.AppExtCommand; +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.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; /** * 用户认证检测. @@ -31,12 +32,16 @@ public class UserChannelCredentialCmd extends AppExtCommand { */ @NotBlank(message = "openId required.") private String openId; - - public String openId() { - if (openId.contains("_") && !AuthTypeEnum.HUAWEI.name().equals(authType.name())) { - return openId; - } - return this.getOpenId().concat("_").concat(this.requireReqSysOrigin()); - } - -} + + public String openId() { + if (StringUtils.isBlank(openId)) { + return openId; + } + String suffix = "_".concat(requireReqSysOrigin()); + if (openId.endsWith(suffix)) { + return openId; + } + return openId.concat(suffix); + } + +} diff --git a/rc-service/rc-inner-api/other-inner/other-inner-model/src/main/java/com/red/circle/other/inner/model/cmd/user/account/CreateAccountCmd.java b/rc-service/rc-inner-api/other-inner/other-inner-model/src/main/java/com/red/circle/other/inner/model/cmd/user/account/CreateAccountCmd.java index 2445d64e..0cd8f66c 100644 --- a/rc-service/rc-inner-api/other-inner/other-inner-model/src/main/java/com/red/circle/other/inner/model/cmd/user/account/CreateAccountCmd.java +++ b/rc-service/rc-inner-api/other-inner/other-inner-model/src/main/java/com/red/circle/other/inner/model/cmd/user/account/CreateAccountCmd.java @@ -99,17 +99,25 @@ public class CreateAccountCmd extends AppExtCommand { && Objects.equals(getMobile().getPhonePrefix(), 86); } - public void changeOpenId() { - if (!this.checkTypeMobile()) { - ResponseAssert.isTrue(ResponseErrorCode.REQUEST_PARAMETER_ERROR, - StringUtils.isNotBlank(this.openId) - && !Objects.equals(this.openId, "null")); - } - setOpenId(this.openId.concat("_").concat(requireReqSysOrigin())); - } - - public String registerOpenId() { - return checkTypeMobile() ? this.mobile.getFullPhoneNumber() : this.openId; - } - -} + public void changeOpenId() { + if (!this.checkTypeMobile()) { + ResponseAssert.isTrue(ResponseErrorCode.REQUEST_PARAMETER_ERROR, + StringUtils.isNotBlank(this.openId) + && !Objects.equals(this.openId, "null")); + } + setOpenId(openIdWithReqSysOrigin(this.openId)); + } + + public String registerOpenId() { + 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); + } + +} diff --git a/rc-service/rc-inner-api/other-inner/other-inner-model/src/test/java/com/red/circle/other/inner/model/cmd/user/UserOpenIdNormalizeTest.java b/rc-service/rc-inner-api/other-inner/other-inner-model/src/test/java/com/red/circle/other/inner/model/cmd/user/UserOpenIdNormalizeTest.java new file mode 100644 index 00000000..eab6b545 --- /dev/null +++ b/rc-service/rc-inner-api/other-inner/other-inner-model/src/test/java/com/red/circle/other/inner/model/cmd/user/UserOpenIdNormalizeTest.java @@ -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()); + } +} diff --git a/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/cmd/user/user/BindUserAuthTypeCmd.java b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/cmd/user/user/BindUserAuthTypeCmd.java index 6cb90a00..4cf44251 100644 --- a/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/cmd/user/user/BindUserAuthTypeCmd.java +++ b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/cmd/user/user/BindUserAuthTypeCmd.java @@ -40,15 +40,23 @@ public class BindUserAuthTypeCmd extends AppExtCommand { return Objects.equals(type, AuthTypeEnum.MOBILE); } - public String getActualOpenId() { - - if (!isTypeMobile() && StringUtils.isBlank(openId)) { - return ""; + public String getActualOpenId() { + + if (!isTypeMobile() && StringUtils.isBlank(openId)) { + return ""; } - - return isTypeMobile() - ? mobile.getFullPhoneNumber() - : openId.concat("_").concat(requireReqSysOrigin()); - } - -} + + return isTypeMobile() + ? mobile.getFullPhoneNumber() + : openIdWithReqSysOrigin(); + } + + private String openIdWithReqSysOrigin() { + String suffix = "_".concat(requireReqSysOrigin()); + if (openId.endsWith(suffix)) { + return openId; + } + return openId.concat(suffix); + } + +} diff --git a/rc-service/rc-service-other/other-start/src/main/java/com/red/circle/framework/feign/decoder/FeignErrorDecoder.java b/rc-service/rc-service-other/other-start/src/main/java/com/red/circle/framework/feign/decoder/FeignErrorDecoder.java index ef3aa6ae..bf396c8f 100644 --- a/rc-service/rc-service-other/other-start/src/main/java/com/red/circle/framework/feign/decoder/FeignErrorDecoder.java +++ b/rc-service/rc-service-other/other-start/src/main/java/com/red/circle/framework/feign/decoder/FeignErrorDecoder.java @@ -12,6 +12,7 @@ import com.red.circle.tool.core.text.StringUtils; import feign.Request; import feign.Response; import feign.codec.ErrorDecoder; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; @@ -69,11 +70,14 @@ public class FeignErrorDecoder implements ErrorDecoder { String errorCodeName = Objects.equals(response.status(), 429) ? "REQ_LIMIT_INNER_DECODER" : "ERROR_INNER_DECODER"; - return new ResponseException(response.status(), errorCodeName, getErrorMsg(methodKey, response)); + Map 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) { - return StringUtils.isBlankOrElse(response.reason(), "") + reqMapping(response); + private String getErrorMsg(Response response) { + return StringUtils.isBlankOrElse(response.reason(), "Inner service error"); } private ResponseException parseResponseException(String methodKey, Response response) { @@ -103,15 +107,44 @@ public class FeignErrorDecoder implements ErrorDecoder { } try (InputStream inputStream = response.body().asInputStream()) { - String responseCompression = ApplicationContextUtils.getApplicationContext() - .getEnvironment() - .getProperty("spring.cloud.openfeign.compression.response.enabled"); - return JacksonUtils.readValue(inputStream, new TypeReference<>() { - }, Objects.equals(responseCompression, "true")); + byte[] bodyBytes = inputStream.readAllBytes(); + if (bodyBytes.length == 0) { + log.warn("Feign error response body is empty, status={}, reason={}, request={}", + response.status(), response.reason(), reqMapping(response)); + return null; + } + boolean gzipEncoded = isGzipEncoded(response); + ResultResponse responseBody = parseResponseBytes(response, bodyBytes, gzipEncoded); + if (Objects.nonNull(responseBody)) { + return responseBody; + } + return parseResponseBytes(response, bodyBytes, !gzipEncoded); } 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); return null; } } + + private ResultResponse 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)); + } }