Compare commits
18 Commits
cd904de915
...
32a1094edc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
32a1094edc | ||
|
|
d9d690e53d | ||
|
|
b76e6e3a93 | ||
|
|
f26bbbc310 | ||
|
|
916ce628be | ||
|
|
2a7be2ca92 | ||
|
|
d49341204c | ||
|
|
9a66e58a93 | ||
|
|
7d1909858e | ||
|
|
b057119fd3 | ||
|
|
9deeef5004 | ||
|
|
d9ff507e5f | ||
|
|
94b9063dd6 | ||
|
|
b5b20b43a8 | ||
|
|
c0a6021982 | ||
|
|
6642ead223 | ||
|
|
3fc5d4a4ff | ||
|
|
d17f75e42d |
@ -108,6 +108,20 @@ spring:
|
||||
predicates:
|
||||
- Path=/play-server-notice/**
|
||||
|
||||
- id: console-go-service
|
||||
uri: http://10.2.1.16:2900
|
||||
order: -20
|
||||
predicates:
|
||||
- Path=/console/go/**
|
||||
filters:
|
||||
- StripPrefix=2
|
||||
|
||||
- id: service-console
|
||||
uri: http://10.2.1.16:2700
|
||||
order: -10
|
||||
predicates:
|
||||
- Path=/console,/console/**
|
||||
|
||||
- id: service-other
|
||||
uri: ${LIKEI_GATEWAY_ROUTE_OTHER_URI}
|
||||
predicates:
|
||||
@ -118,6 +132,11 @@ gateway:
|
||||
- /account/create
|
||||
- /account/login/**/*
|
||||
- /account/login
|
||||
- /console/go
|
||||
- /console/go/**
|
||||
- /console/account/login/**/*
|
||||
- /console/account/login
|
||||
- /console/**
|
||||
- /account/forget-pwd-reset
|
||||
- /user/user-profile/open-search
|
||||
- /party3rd/tencent/cloud/im/callback
|
||||
@ -174,8 +193,26 @@ gateway:
|
||||
- /go/resident-activity/room-turnover-reward/**
|
||||
- /resident-activity/room-turnover-reward/**
|
||||
- /go/resident-activity/register-reward/**
|
||||
- /resident-activity/register-reward/**
|
||||
- /go/resident-activity/specified-gift-weekly-rank/**
|
||||
- /resident-activity/specified-gift-weekly-rank/**
|
||||
- /go/resident-activity/week-star/**
|
||||
- /resident-activity/week-star/**
|
||||
- /resident-activity/sign-in-reward/**
|
||||
- /resident-activity/vip/**
|
||||
- /resident-activity/recharge-reward/**
|
||||
- /resident-activity/task-center/**
|
||||
- /resident-activity/voice-room-red-packet/**
|
||||
- /resident-activity/voice-room-rocket/**
|
||||
- /resident-activity/wheel/**
|
||||
- /resident-activity/smash-golden-egg/**
|
||||
- /operate/baishun-game/**
|
||||
- /operate/lingxian-game/**
|
||||
- /app-system/home-popups/**
|
||||
- /app-system/error-logs/**
|
||||
- /region/config/im-groups/**
|
||||
- /payment/binance/**
|
||||
- /props/store/**
|
||||
# leaderboard
|
||||
- /activity/leaderboard/gifts-send
|
||||
- /activity/leaderboard/gifts-received
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -0,0 +1,150 @@
|
||||
package com.red.circle.framework.feign.decoder;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.red.circle.framework.core.exception.ResponseException;
|
||||
import com.red.circle.framework.core.request.ResponseHeaderConstant;
|
||||
import com.red.circle.framework.core.response.ResponseErrorCode;
|
||||
import com.red.circle.framework.core.spring.ApplicationContextUtils;
|
||||
import com.red.circle.framework.dto.ResultResponse;
|
||||
import com.red.circle.tool.core.http.HttpConstant;
|
||||
import com.red.circle.tool.core.json.JacksonUtils;
|
||||
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;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class FeignErrorDecoder implements ErrorDecoder {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(FeignErrorDecoder.class);
|
||||
|
||||
private static final List<Integer> PROCESS_RESULT_RESPONSE = List.of(
|
||||
ResponseErrorCode.NOT_ACCEPTABLE.getCode(),
|
||||
ResponseErrorCode.REQUEST_PARAMETER_ERROR.getCode()
|
||||
);
|
||||
|
||||
@Override
|
||||
public Exception decode(String methodKey, Response response) {
|
||||
if (PROCESS_RESULT_RESPONSE.stream()
|
||||
.anyMatch(code -> Objects.equals(response.status(), code))) {
|
||||
ResponseException exception = parseResponseException(methodKey, response);
|
||||
if (Objects.nonNull(exception)) {
|
||||
return exception;
|
||||
}
|
||||
return createResponseException(methodKey, response);
|
||||
}
|
||||
|
||||
if (HttpConstant.is4xx(response.status())) {
|
||||
return createResponseException(methodKey, response);
|
||||
}
|
||||
|
||||
if (ResponseHeaderConstant.existsHeaderValue(response.headers(), "RC-No-Retry", "true")) {
|
||||
ResponseException exception = parseResponseException(methodKey, response);
|
||||
if (Objects.nonNull(exception)) {
|
||||
return exception;
|
||||
}
|
||||
return createResponseException(methodKey, response);
|
||||
}
|
||||
|
||||
return createResponseException(methodKey, response);
|
||||
}
|
||||
|
||||
private static String reqMapping(Response response) {
|
||||
Request request = response.request();
|
||||
if (Objects.isNull(request)) {
|
||||
return "";
|
||||
}
|
||||
return request.httpMethod() + " " + request.url();
|
||||
}
|
||||
|
||||
private ResponseException createResponseException(String methodKey, Response response) {
|
||||
String errorCodeName = Objects.equals(response.status(), 429)
|
||||
? "REQ_LIMIT_INNER_DECODER"
|
||||
: "ERROR_INNER_DECODER";
|
||||
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(Response response) {
|
||||
return StringUtils.isBlankOrElse(response.reason(), "Inner service error");
|
||||
}
|
||||
|
||||
private ResponseException parseResponseException(String methodKey, Response response) {
|
||||
ResultResponse<Void> responseBody = parseResponse(response);
|
||||
if (Objects.nonNull(responseBody)) {
|
||||
Map<String, Object> extValues = Optional.ofNullable(responseBody.getExtValues())
|
||||
.orElseGet(HashMap::new);
|
||||
|
||||
extValues.put("innerError",
|
||||
"[" + methodKey + "]" + StringUtils.isBlankOrElse(response.reason(), ""));
|
||||
|
||||
return new ResponseException(
|
||||
responseBody.getErrorCode(),
|
||||
responseBody.getErrorCodeName(),
|
||||
responseBody.getErrorMsg(),
|
||||
extValues
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private ResultResponse<Void> parseResponse(Response response) {
|
||||
if (Objects.isNull(response.body())) {
|
||||
log.warn("Feign error response body is null, status={}, reason={}, request={}",
|
||||
response.status(), response.reason(), reqMapping(response));
|
||||
return null;
|
||||
}
|
||||
|
||||
try (InputStream inputStream = response.body().asInputStream()) {
|
||||
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<Void> responseBody = parseResponseBytes(response, bodyBytes, gzipEncoded);
|
||||
if (Objects.nonNull(responseBody)) {
|
||||
return responseBody;
|
||||
}
|
||||
return parseResponseBytes(response, bodyBytes, !gzipEncoded);
|
||||
} catch (IOException | RuntimeException ex) {
|
||||
log.warn("Read feign error response body failed, status={}, reason={}, request={}",
|
||||
response.status(), response.reason(), reqMapping(response), ex);
|
||||
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));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,117 @@
|
||||
package com.red.circle.framework.web.feign;
|
||||
|
||||
import com.red.circle.framework.core.request.RequestHeaderConstant;
|
||||
import com.red.circle.framework.core.request.ServiceTraceId;
|
||||
import feign.RequestInterceptor;
|
||||
import feign.RequestTemplate;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
/**
|
||||
* Forward only business headers to Feign requests.
|
||||
*/
|
||||
@Component
|
||||
@Order(Ordered.LOWEST_PRECEDENCE)
|
||||
public class FeignRequestInterceptor implements RequestInterceptor {
|
||||
|
||||
private static final String INNER_SERVICE = "FEIGN";
|
||||
|
||||
private static final List<String> FORWARD_HEADERS = List.of(
|
||||
RequestHeaderConstant.AUTHORIZATION,
|
||||
RequestHeaderConstant.REQ_SYS_ORIGIN,
|
||||
RequestHeaderConstant.REQ_CLIENT,
|
||||
RequestHeaderConstant.REQ_ZONE,
|
||||
RequestHeaderConstant.REQ_IMEI,
|
||||
RequestHeaderConstant.REQ_APP_INTEL,
|
||||
RequestHeaderConstant.REQ_VERSION,
|
||||
RequestHeaderConstant.REQ_LANG,
|
||||
RequestHeaderConstant.REQ_INNER_INTERNAL,
|
||||
RequestHeaderConstant.REQ_INNER_TRACE_ID
|
||||
);
|
||||
|
||||
private static final Set<String> BLOCKED_HEADERS = Set.of(
|
||||
"host",
|
||||
"content-length",
|
||||
"transfer-encoding",
|
||||
"connection",
|
||||
"keep-alive",
|
||||
"upgrade",
|
||||
"proxy-authorization",
|
||||
"proxy-authenticate",
|
||||
"te",
|
||||
"trailer"
|
||||
);
|
||||
|
||||
@Override
|
||||
public void apply(RequestTemplate requestTemplate) {
|
||||
removeBlockedHeaders(requestTemplate);
|
||||
|
||||
if (RequestContextHolder.getRequestAttributes() instanceof ServletRequestAttributes attrs) {
|
||||
forwardHeaders(requestTemplate, attrs.getRequest());
|
||||
}
|
||||
|
||||
if (!hasHeader(requestTemplate, RequestHeaderConstant.REQ_INNER_SERVICE)) {
|
||||
requestTemplate.header(RequestHeaderConstant.REQ_INNER_SERVICE, INNER_SERVICE);
|
||||
}
|
||||
if (!hasHeader(requestTemplate, RequestHeaderConstant.REQ_INNER_TRACE_ID)) {
|
||||
requestTemplate.header(
|
||||
RequestHeaderConstant.REQ_INNER_TRACE_ID,
|
||||
ServiceTraceId.genTraceId(ServiceTraceId.Origin.INNER_REQ));
|
||||
}
|
||||
|
||||
removeBlockedHeaders(requestTemplate);
|
||||
}
|
||||
|
||||
private void forwardHeaders(RequestTemplate requestTemplate, HttpServletRequest request) {
|
||||
for (String header : FORWARD_HEADERS) {
|
||||
String value = request.getHeader(header);
|
||||
if (hasHeader(requestTemplate, header) || !isUsableHeaderValue(value)) {
|
||||
continue;
|
||||
}
|
||||
requestTemplate.header(header, value);
|
||||
}
|
||||
}
|
||||
|
||||
private void removeBlockedHeaders(RequestTemplate requestTemplate) {
|
||||
List<String> names = new ArrayList<>(requestTemplate.headers().keySet());
|
||||
for (String name : names) {
|
||||
if (isBlockedHeader(name)) {
|
||||
requestTemplate.removeHeader(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasHeader(RequestTemplate requestTemplate, String header) {
|
||||
return requestTemplate.headers().keySet().stream()
|
||||
.anyMatch(name -> name.equalsIgnoreCase(header));
|
||||
}
|
||||
|
||||
private boolean isBlockedHeader(String header) {
|
||||
if (header == null) {
|
||||
return true;
|
||||
}
|
||||
String normalized = header.toLowerCase(Locale.ROOT);
|
||||
return BLOCKED_HEADERS.contains(normalized) || normalized.startsWith("proxy-");
|
||||
}
|
||||
|
||||
private boolean isUsableHeaderValue(String value) {
|
||||
if (value == null || value.isBlank()) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < value.length(); i++) {
|
||||
char c = value.charAt(i);
|
||||
if (c < 32 || c == 127) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,117 @@
|
||||
package com.red.circle.framework.web.feign;
|
||||
|
||||
import com.red.circle.framework.core.request.RequestHeaderConstant;
|
||||
import com.red.circle.framework.core.request.ServiceTraceId;
|
||||
import feign.RequestInterceptor;
|
||||
import feign.RequestTemplate;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
/**
|
||||
* Forward only business headers to Feign requests.
|
||||
*/
|
||||
@Component
|
||||
@Order(Ordered.LOWEST_PRECEDENCE)
|
||||
public class FeignRequestInterceptor implements RequestInterceptor {
|
||||
|
||||
private static final String INNER_SERVICE = "FEIGN";
|
||||
|
||||
private static final List<String> FORWARD_HEADERS = List.of(
|
||||
RequestHeaderConstant.AUTHORIZATION,
|
||||
RequestHeaderConstant.REQ_SYS_ORIGIN,
|
||||
RequestHeaderConstant.REQ_CLIENT,
|
||||
RequestHeaderConstant.REQ_ZONE,
|
||||
RequestHeaderConstant.REQ_IMEI,
|
||||
RequestHeaderConstant.REQ_APP_INTEL,
|
||||
RequestHeaderConstant.REQ_VERSION,
|
||||
RequestHeaderConstant.REQ_LANG,
|
||||
RequestHeaderConstant.REQ_INNER_INTERNAL,
|
||||
RequestHeaderConstant.REQ_INNER_TRACE_ID
|
||||
);
|
||||
|
||||
private static final Set<String> BLOCKED_HEADERS = Set.of(
|
||||
"host",
|
||||
"content-length",
|
||||
"transfer-encoding",
|
||||
"connection",
|
||||
"keep-alive",
|
||||
"upgrade",
|
||||
"proxy-authorization",
|
||||
"proxy-authenticate",
|
||||
"te",
|
||||
"trailer"
|
||||
);
|
||||
|
||||
@Override
|
||||
public void apply(RequestTemplate requestTemplate) {
|
||||
removeBlockedHeaders(requestTemplate);
|
||||
|
||||
if (RequestContextHolder.getRequestAttributes() instanceof ServletRequestAttributes attrs) {
|
||||
forwardHeaders(requestTemplate, attrs.getRequest());
|
||||
}
|
||||
|
||||
if (!hasHeader(requestTemplate, RequestHeaderConstant.REQ_INNER_SERVICE)) {
|
||||
requestTemplate.header(RequestHeaderConstant.REQ_INNER_SERVICE, INNER_SERVICE);
|
||||
}
|
||||
if (!hasHeader(requestTemplate, RequestHeaderConstant.REQ_INNER_TRACE_ID)) {
|
||||
requestTemplate.header(
|
||||
RequestHeaderConstant.REQ_INNER_TRACE_ID,
|
||||
ServiceTraceId.genTraceId(ServiceTraceId.Origin.INNER_REQ));
|
||||
}
|
||||
|
||||
removeBlockedHeaders(requestTemplate);
|
||||
}
|
||||
|
||||
private void forwardHeaders(RequestTemplate requestTemplate, HttpServletRequest request) {
|
||||
for (String header : FORWARD_HEADERS) {
|
||||
String value = request.getHeader(header);
|
||||
if (hasHeader(requestTemplate, header) || !isUsableHeaderValue(value)) {
|
||||
continue;
|
||||
}
|
||||
requestTemplate.header(header, value);
|
||||
}
|
||||
}
|
||||
|
||||
private void removeBlockedHeaders(RequestTemplate requestTemplate) {
|
||||
List<String> names = new ArrayList<>(requestTemplate.headers().keySet());
|
||||
for (String name : names) {
|
||||
if (isBlockedHeader(name)) {
|
||||
requestTemplate.removeHeader(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasHeader(RequestTemplate requestTemplate, String header) {
|
||||
return requestTemplate.headers().keySet().stream()
|
||||
.anyMatch(name -> name.equalsIgnoreCase(header));
|
||||
}
|
||||
|
||||
private boolean isBlockedHeader(String header) {
|
||||
if (header == null) {
|
||||
return true;
|
||||
}
|
||||
String normalized = header.toLowerCase(Locale.ROOT);
|
||||
return BLOCKED_HEADERS.contains(normalized) || normalized.startsWith("proxy-");
|
||||
}
|
||||
|
||||
private boolean isUsableHeaderValue(String value) {
|
||||
if (value == null || value.isBlank()) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < value.length(); i++) {
|
||||
char c = value.charAt(i);
|
||||
if (c < 32 || c == 127) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -1,17 +1,13 @@
|
||||
package com.red.circle.other.inner.endpoint.activity.api;
|
||||
|
||||
import com.red.circle.common.business.core.enums.SysOriginPlatformEnum;
|
||||
import com.red.circle.framework.dto.PageResult;
|
||||
import com.red.circle.framework.dto.ResultResponse;
|
||||
import com.red.circle.other.inner.enums.activity.PropsActivityTypeEnum;
|
||||
import com.red.circle.other.inner.model.cmd.activity.SendActivityRewardCmd;
|
||||
import com.red.circle.other.inner.model.cmd.material.PropsActivityRuleConfigParamCmd;
|
||||
import com.red.circle.other.inner.model.cmd.material.PropsActivityRuleConfigQryCmd;
|
||||
import com.red.circle.other.inner.model.dto.activity.props.ActivityPropsGroup;
|
||||
import com.red.circle.other.inner.model.dto.activity.props.ActivityPropsRule;
|
||||
import com.red.circle.other.inner.model.dto.activity.props.ActivityPropsRuleDTO;
|
||||
import com.red.circle.other.inner.model.dto.activity.props.ActivityResource;
|
||||
import com.red.circle.other.inner.model.dto.material.PropsActivityRuleConfigDTO;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -178,39 +174,4 @@ public interface PropsActivityClientApi {
|
||||
@PostMapping("/mapRuleConfigByIds")
|
||||
ResultResponse<Map<Long, ActivityPropsRuleDTO>> mapRuleConfigByIds(
|
||||
@RequestBody Collection<Long> ids);
|
||||
|
||||
/**
|
||||
* 分页-查询活动道具规则配置.
|
||||
*/
|
||||
@PostMapping("/pagePropsActivityRuleConfig")
|
||||
ResultResponse<PageResult<PropsActivityRuleConfigDTO>> pagePropsActivityRuleConfig(
|
||||
@RequestBody @Validated PropsActivityRuleConfigQryCmd query);
|
||||
|
||||
|
||||
/**
|
||||
* 删除-查询活动道具规则配置.
|
||||
*/
|
||||
@PostMapping("/propsActivityRuleConfigDeleteById")
|
||||
ResultResponse<Void> propsActivityRuleConfigDeleteById(@RequestParam("id") Long id);
|
||||
|
||||
/**
|
||||
* 查询活动道具规则配置.
|
||||
*/
|
||||
@PostMapping("/getPropsActivityRuleConfig")
|
||||
ResultResponse<PropsActivityRuleConfigDTO> getPropsActivityRuleConfig(
|
||||
@RequestBody @Validated PropsActivityRuleConfigParamCmd param);
|
||||
|
||||
/**
|
||||
* 修改-查询活动道具规则配置.
|
||||
*/
|
||||
@PostMapping("/updatePropsActivityRuleConfigById")
|
||||
ResultResponse<Void> updatePropsActivityRuleConfigById(
|
||||
@RequestBody @Validated PropsActivityRuleConfigParamCmd param);
|
||||
|
||||
/**
|
||||
* 保存-查询活动道具规则配置.
|
||||
*/
|
||||
@PostMapping("/savePropsActivityRuleConfig")
|
||||
ResultResponse<Void> savePropsActivityRuleConfig(
|
||||
@RequestBody @Validated PropsActivityRuleConfigParamCmd param);
|
||||
}
|
||||
|
||||
@ -2,11 +2,13 @@ package com.red.circle.other.inner.endpoint.material.props.api;
|
||||
|
||||
import com.red.circle.framework.dto.PageResult;
|
||||
import com.red.circle.framework.dto.ResultResponse;
|
||||
import com.red.circle.other.inner.model.cmd.material.PropsCommodityStoreQryCmd;
|
||||
import com.red.circle.other.inner.model.dto.material.PropsCommodityStoreDTO;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import com.red.circle.other.inner.model.cmd.material.PropsCommodityStoreQryCmd;
|
||||
import com.red.circle.other.inner.model.dto.material.PropsCommodityStoreDTO;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
/**
|
||||
@ -23,13 +25,21 @@ public interface PropsCommodityStoreClientApi {
|
||||
* 获取平台指定资源.
|
||||
*/
|
||||
@GetMapping("/getPropsCommodityStore")
|
||||
ResultResponse<PropsCommodityStoreDTO> getPropsCommodityStore(
|
||||
@RequestParam("sysOrigin") String sysOrigin,
|
||||
@RequestParam("sourceId") Long sourceId
|
||||
);
|
||||
|
||||
/**
|
||||
* 商店分页列表.
|
||||
ResultResponse<PropsCommodityStoreDTO> getPropsCommodityStore(
|
||||
@RequestParam("sysOrigin") String sysOrigin,
|
||||
@RequestParam("sourceId") Long sourceId
|
||||
);
|
||||
|
||||
/**
|
||||
* 批量获取资源对应商品.
|
||||
*/
|
||||
@PostMapping("/mapBySourceIds")
|
||||
ResultResponse<Map<Long, PropsCommodityStoreDTO>> mapBySourceIds(
|
||||
@RequestParam("sysOrigin") String sysOrigin,
|
||||
@RequestBody Set<Long> sourceIds);
|
||||
|
||||
/**
|
||||
* 商店分页列表.
|
||||
*/
|
||||
@PostMapping("/pageOps")
|
||||
ResultResponse<PageResult<PropsCommodityStoreDTO>> pageOps(
|
||||
|
||||
@ -1,42 +0,0 @@
|
||||
package com.red.circle.other.inner.model.cmd.material;
|
||||
|
||||
import com.red.circle.framework.dto.Command;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 活动道具规则配置.
|
||||
* </p>
|
||||
*
|
||||
* @author pengshigang
|
||||
* @since 2021-06-18
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public class PropsActivityRuleConfigParamCmd extends Command {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Long id;
|
||||
|
||||
@NotNull(message = "请选择平台")
|
||||
private String sysOrigin;
|
||||
|
||||
@NotBlank(message = "请选择活动类型")
|
||||
private String activityType;
|
||||
|
||||
private Long resourceGroupId;
|
||||
|
||||
@NotBlank(message = "规则不能为空")
|
||||
private String jsonData;
|
||||
|
||||
private Integer sort;
|
||||
|
||||
private String ruleDescription;
|
||||
|
||||
}
|
||||
@ -1,37 +0,0 @@
|
||||
package com.red.circle.other.inner.model.cmd.material;
|
||||
|
||||
import com.red.circle.framework.core.dto.PageCommand;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.io.Serial;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 活动道具规则配置.
|
||||
* </p>
|
||||
*
|
||||
* @author pengshigang
|
||||
* @since 2021-06-18
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public class PropsActivityRuleConfigQryCmd extends PageCommand {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Long id;
|
||||
|
||||
@NotNull(message = "请选择平台")
|
||||
private String sysOrigin;
|
||||
|
||||
private String activityType;
|
||||
|
||||
private Long resourceGroupId;
|
||||
|
||||
private String ruleDescription;
|
||||
|
||||
}
|
||||
@ -1,9 +1,10 @@
|
||||
package com.red.circle.other.inner.model.cmd.material;
|
||||
|
||||
import com.red.circle.framework.core.dto.PageCommand;
|
||||
import java.io.Serial;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import com.red.circle.framework.core.dto.PageCommand;
|
||||
import java.io.Serial;
|
||||
import java.math.BigDecimal;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
@ -30,19 +31,39 @@ public class PropsSourceRecordQryCmd extends PageCommand {
|
||||
*/
|
||||
private String sysOrigin;
|
||||
|
||||
/**
|
||||
* 类型.
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 编号.
|
||||
*/
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 名称.
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 类型.
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 名称.
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 0.未删除 1.已删除.
|
||||
*/
|
||||
private Boolean del;
|
||||
|
||||
}
|
||||
* 0.未删除 1.已删除.
|
||||
*/
|
||||
private Boolean del;
|
||||
|
||||
/**
|
||||
* 经理赠送权限.
|
||||
*/
|
||||
private Boolean adminFree;
|
||||
|
||||
/**
|
||||
* 最小底价.
|
||||
*/
|
||||
private BigDecimal amountMin;
|
||||
|
||||
/**
|
||||
* 最大底价.
|
||||
*/
|
||||
private BigDecimal amountMax;
|
||||
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,34 +0,0 @@
|
||||
package com.red.circle.other.inner.model.dto.material;
|
||||
|
||||
import java.io.Serializable;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 活动 LuckyBox.
|
||||
* </p>
|
||||
*
|
||||
* @author pengshigang
|
||||
* @since 2021-07-13
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class ActivityLuckyBoxDTO implements Serializable {
|
||||
|
||||
/**
|
||||
* 标识.
|
||||
*/
|
||||
private Integer id;
|
||||
|
||||
/**
|
||||
* 消耗糖果.
|
||||
*/
|
||||
private Integer quantity;
|
||||
|
||||
/**
|
||||
* 抽奖次数.
|
||||
*/
|
||||
private Integer opportunityNumber;
|
||||
|
||||
}
|
||||
@ -1,80 +0,0 @@
|
||||
package com.red.circle.other.inner.model.dto.material;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 活动道具规则配置.
|
||||
* </p>
|
||||
*
|
||||
* @author pengshigang
|
||||
* @since 2021-06-18
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@Accessors(chain = true)
|
||||
public class PropsActivityRuleConfigDTO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键标识.
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 系统平台.
|
||||
*/
|
||||
private String sysOrigin;
|
||||
|
||||
/**
|
||||
* 活动类型.
|
||||
*/
|
||||
private String activityType;
|
||||
|
||||
/**
|
||||
* 活动类型.
|
||||
*/
|
||||
private String activityTypeName;
|
||||
|
||||
/**
|
||||
* 资源组ID.
|
||||
*/
|
||||
private Long resourceGroupId;
|
||||
|
||||
/**
|
||||
* 数据.
|
||||
*/
|
||||
private String jsonData;
|
||||
|
||||
/**
|
||||
* 排序.
|
||||
*/
|
||||
private Integer sort;
|
||||
|
||||
/**
|
||||
* 规则描述.
|
||||
*/
|
||||
private String ruleDescription;
|
||||
|
||||
/**
|
||||
* 奖励道具.
|
||||
*/
|
||||
private List<PropsActivityRewardConfigInfoDTO> activityRewards;
|
||||
|
||||
/**
|
||||
* 创建时间.
|
||||
*/
|
||||
private Timestamp createTime;
|
||||
|
||||
/**
|
||||
* 修改时间.
|
||||
*/
|
||||
private Timestamp updateTime;
|
||||
|
||||
}
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
@ -1,56 +0,0 @@
|
||||
package com.red.circle.console.adapter.app.props;
|
||||
|
||||
import com.red.circle.console.app.service.app.props.PropsActivityRuleConfigService;
|
||||
import com.red.circle.console.infra.annotations.OpsOperationReqLog;
|
||||
import com.red.circle.framework.dto.PageResult;
|
||||
import com.red.circle.framework.web.controller.BaseController;
|
||||
import com.red.circle.other.inner.model.cmd.material.PropsActivityRuleConfigParamCmd;
|
||||
import com.red.circle.other.inner.model.cmd.material.PropsActivityRuleConfigQryCmd;
|
||||
import com.red.circle.other.inner.model.dto.material.PropsActivityRuleConfigDTO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 活动道具规则配置.
|
||||
*
|
||||
* @author pengshigang
|
||||
* @since 2021-06-18
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/props/activity/rule/config")
|
||||
public class PropsActivityRuleConfigRestController extends BaseController {
|
||||
|
||||
private final PropsActivityRuleConfigService propsActivityRuleConfigService;
|
||||
|
||||
/**
|
||||
* 分页.
|
||||
*/
|
||||
@GetMapping("/page")
|
||||
public PageResult<PropsActivityRuleConfigDTO> pagePropsActivityRuleConfig(
|
||||
PropsActivityRuleConfigQryCmd propsActivityRuleConfigQuery) {
|
||||
return propsActivityRuleConfigService
|
||||
.pagePropsActivityRuleConfig(propsActivityRuleConfigQuery);
|
||||
}
|
||||
|
||||
@OpsOperationReqLog(value = "保存-活动道具规则配置")
|
||||
@PostMapping("/save-or-update")
|
||||
public void saveOrUpdate(
|
||||
@RequestBody @Validated PropsActivityRuleConfigParamCmd param) {
|
||||
propsActivityRuleConfigService.saveOrUpdate(param);
|
||||
}
|
||||
|
||||
@OpsOperationReqLog(value = "删除活动道具规则配置")
|
||||
@GetMapping("/del/{id}")
|
||||
public void deleteById(@PathVariable("id") Long id) {
|
||||
propsActivityRuleConfigService.deleteById(id);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -23,6 +23,7 @@ import java.time.ZoneId;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.time.temporal.WeekFields;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
@ -57,6 +58,10 @@ public class CountryDashboardServiceImpl implements CountryDashboardService {
|
||||
|
||||
private static final String COLLECTION_GIFT_GIVE_RUNNING_WATER = "gift_give_running_water";
|
||||
private static final String COLLECTION_USER_DAILY_ACTIVE_LOG = "user_daily_active_log";
|
||||
private static final String COLLECTION_USER_BANK_RUNNING_WATER = "user_bank_running_water";
|
||||
private static final String BANK_EXCHANGE_GOLD_EVENT = "EXCHANGE_GOLD_COINS";
|
||||
private static final String BANK_TRANSFER_GOLD_EVENT = "TRANSFER_GOLD";
|
||||
private static final int RECEIPT_TYPE_EXPENDITURE = 1;
|
||||
private static final String PERIOD_ALL = "ALL";
|
||||
private static final ZoneId STORAGE_ZONE_ID = ZoneId.of("Asia/Riyadh");
|
||||
private static final ZoneId DEFAULT_STAT_ZONE_ID = STORAGE_ZONE_ID;
|
||||
@ -73,18 +78,25 @@ public class CountryDashboardServiceImpl implements CountryDashboardService {
|
||||
QueryCondition condition = QueryCondition.of(cmd);
|
||||
Map<String, CountryDashboardMetricCO> rows = new LinkedHashMap<>();
|
||||
|
||||
if (shouldUsePrecomputedMetrics(condition)) {
|
||||
boolean usePrecomputedMetrics = shouldUsePrecomputedMetrics(condition);
|
||||
if (usePrecomputedMetrics) {
|
||||
mergeSql(rows, countryDashboardDAO.listPrecomputedPeriodMetrics(
|
||||
condition.periodType, condition.startTime, condition.endTime,
|
||||
condition.countryKeyword, condition.statTimezone, condition.sysOrigin));
|
||||
} else {
|
||||
mergeLiveSql(rows, condition);
|
||||
mergeBankSalaryExchange(rows, condition);
|
||||
}
|
||||
if (usePrecomputedMetrics && condition.shouldOverlayLiveSalaryExchange()) {
|
||||
overlayLiveSalaryExchange(rows, condition);
|
||||
}
|
||||
|
||||
mergeMongo(rows, condition, listGiftConsume(condition),
|
||||
(row, amount) -> row.setGiftConsume(add(row.getGiftConsume(), amount)));
|
||||
mergeMongo(rows, condition, listLuckyGiftAnchorShare(condition),
|
||||
(row, amount) -> row.setLuckyGiftAnchorShare(add(row.getLuckyGiftAnchorShare(), amount)));
|
||||
mergeMongo(rows, condition, listBankSalaryTransfer(condition),
|
||||
(row, amount) -> row.setSalaryTransfer(add(row.getSalaryTransfer(), amount)));
|
||||
mergeDailyActive(rows, condition, listDailyActive(condition));
|
||||
mergeRetention(rows, condition);
|
||||
|
||||
@ -157,6 +169,34 @@ public class CountryDashboardServiceImpl implements CountryDashboardService {
|
||||
condition.storageTimezoneOffset, condition.statTimezoneOffset));
|
||||
}
|
||||
|
||||
private void overlayLiveSalaryExchange(Map<String, CountryDashboardMetricCO> rows,
|
||||
QueryCondition condition) {
|
||||
rows.values().forEach(row -> row.setSalaryExchange(BigDecimal.ZERO));
|
||||
mergeSqlSalaryExchange(rows, countryDashboardDAO.listSalaryExchange(
|
||||
condition.periodType, condition.startTime, condition.endTime,
|
||||
condition.countryKeyword, condition.sysOrigin,
|
||||
condition.storageTimezoneOffset, condition.statTimezoneOffset));
|
||||
mergeBankSalaryExchange(rows, condition);
|
||||
}
|
||||
|
||||
private void mergeSqlSalaryExchange(Map<String, CountryDashboardMetricCO> rows,
|
||||
List<CountryDashboardMetricDTO> metrics) {
|
||||
if (metrics == null || metrics.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (CountryDashboardMetricDTO metric : metrics) {
|
||||
CountryDashboardMetricCO row = getRow(rows, metric.getCountryCode(), metric.getCountryName(),
|
||||
metric.getPeriodKey(), metric.getPeriodName());
|
||||
row.setSalaryExchange(add(row.getSalaryExchange(), metric.getSalaryExchange()));
|
||||
}
|
||||
}
|
||||
|
||||
private void mergeBankSalaryExchange(Map<String, CountryDashboardMetricCO> rows,
|
||||
QueryCondition condition) {
|
||||
mergeMongo(rows, condition, listBankSalaryExchange(condition),
|
||||
(row, amount) -> row.setSalaryExchange(add(row.getSalaryExchange(), amount)));
|
||||
}
|
||||
|
||||
private boolean shouldUsePrecomputedMetrics(QueryCondition condition) {
|
||||
if (!hasText(condition.sysOrigin)) {
|
||||
return false;
|
||||
@ -430,6 +470,22 @@ public class CountryDashboardServiceImpl implements CountryDashboardService {
|
||||
return aggregateGiftAmount(criteria, "$acceptUsers.targetAmount", true, condition.statTimezone);
|
||||
}
|
||||
|
||||
private List<MongoMetric> listBankSalaryExchange(QueryCondition condition) {
|
||||
List<Criteria> criteria = baseBankRunningWaterCriteria(condition);
|
||||
criteria.add(Criteria.where("event").is(BANK_EXCHANGE_GOLD_EVENT));
|
||||
criteria.add(Criteria.where("type").is(RECEIPT_TYPE_EXPENDITURE));
|
||||
return aggregateMongoAmount(criteria, "$amount", false, condition.statTimezone,
|
||||
COLLECTION_USER_BANK_RUNNING_WATER);
|
||||
}
|
||||
|
||||
private List<MongoMetric> listBankSalaryTransfer(QueryCondition condition) {
|
||||
List<Criteria> criteria = baseBankRunningWaterCriteria(condition);
|
||||
criteria.add(Criteria.where("event").is(BANK_TRANSFER_GOLD_EVENT));
|
||||
criteria.add(Criteria.where("type").is(RECEIPT_TYPE_EXPENDITURE));
|
||||
return aggregateMongoAmount(criteria, "$amount", false, condition.statTimezone,
|
||||
COLLECTION_USER_BANK_RUNNING_WATER);
|
||||
}
|
||||
|
||||
private List<MongoUserDayMetric> listDailyActive(QueryCondition condition) {
|
||||
List<Criteria> criteria = baseActiveCriteria(condition);
|
||||
List<AggregationOperation> operations = new ArrayList<>();
|
||||
@ -484,18 +540,39 @@ public class CountryDashboardServiceImpl implements CountryDashboardService {
|
||||
return criteria;
|
||||
}
|
||||
|
||||
private List<Criteria> baseBankRunningWaterCriteria(QueryCondition condition) {
|
||||
List<Criteria> criteria = new ArrayList<>();
|
||||
criteria.add(Criteria.where("userId").ne(null));
|
||||
if (condition.startInstant != null) {
|
||||
criteria.add(Criteria.where("createTime").gte(Timestamp.from(condition.startInstant)));
|
||||
}
|
||||
if (condition.endInstant != null) {
|
||||
criteria.add(Criteria.where("createTime").lt(Timestamp.from(condition.endInstant)));
|
||||
}
|
||||
if (hasText(condition.sysOrigin)) {
|
||||
criteria.add(Criteria.where("sysOrigin").is(condition.sysOrigin));
|
||||
}
|
||||
return criteria;
|
||||
}
|
||||
|
||||
private List<MongoMetric> aggregateGiftAmount(List<Criteria> criteria, String amountPath,
|
||||
boolean unwindAcceptUsers, String statTimezone) {
|
||||
return aggregateMongoAmount(criteria, amountPath, unwindAcceptUsers, statTimezone,
|
||||
COLLECTION_GIFT_GIVE_RUNNING_WATER);
|
||||
}
|
||||
|
||||
private List<MongoMetric> aggregateMongoAmount(List<Criteria> criteria, String amountPath,
|
||||
boolean unwindAcceptUsers, String statTimezone, String collectionName) {
|
||||
List<AggregationOperation> operations = new ArrayList<>();
|
||||
operations.add(Aggregation.match(new Criteria().andOperator(criteria.toArray(new Criteria[0]))));
|
||||
if (unwindAcceptUsers) {
|
||||
operations.add(Aggregation.unwind("acceptUsers"));
|
||||
}
|
||||
operations.add(projectGiftDayAmount(amountPath, statTimezone));
|
||||
operations.add(projectMongoDayAmount(amountPath, statTimezone));
|
||||
operations.add(Aggregation.group("userId", "day").sum("amount").as("amount"));
|
||||
|
||||
return mongoTemplate.aggregate(Aggregation.newAggregation(operations),
|
||||
COLLECTION_GIFT_GIVE_RUNNING_WATER, Document.class)
|
||||
collectionName, Document.class)
|
||||
.getMappedResults()
|
||||
.stream()
|
||||
.map(this::toMongoMetric)
|
||||
@ -503,7 +580,7 @@ public class CountryDashboardServiceImpl implements CountryDashboardService {
|
||||
.toList();
|
||||
}
|
||||
|
||||
private AggregationOperation projectGiftDayAmount(String amountPath, String statTimezone) {
|
||||
private AggregationOperation projectMongoDayAmount(String amountPath, String statTimezone) {
|
||||
return context -> new Document("$project",
|
||||
new Document("userId", "$userId")
|
||||
.append("amount", amountPath)
|
||||
@ -676,6 +753,7 @@ public class CountryDashboardServiceImpl implements CountryDashboardService {
|
||||
total.setGoogleRecharge(add(total.getGoogleRecharge(), row.getGoogleRecharge()));
|
||||
total.setDealerRecharge(add(total.getDealerRecharge(), row.getDealerRecharge()));
|
||||
total.setSalaryExchange(add(total.getSalaryExchange(), row.getSalaryExchange()));
|
||||
total.setSalaryTransfer(add(total.getSalaryTransfer(), row.getSalaryTransfer()));
|
||||
total.setGiftConsume(add(total.getGiftConsume(), row.getGiftConsume()));
|
||||
total.setLuckyGiftTotalFlow(add(total.getLuckyGiftTotalFlow(), row.getLuckyGiftTotalFlow()));
|
||||
total.setLuckyGiftUser(total.getLuckyGiftUser() + safeLong(row.getLuckyGiftUser()));
|
||||
@ -890,6 +968,14 @@ public class CountryDashboardServiceImpl implements CountryDashboardService {
|
||||
return new PeriodBucket(day, day);
|
||||
}
|
||||
|
||||
private boolean shouldOverlayLiveSalaryExchange() {
|
||||
if (PERIOD_ALL.equals(periodType) || startDateValue == null || endDateValue == null) {
|
||||
return false;
|
||||
}
|
||||
long days = ChronoUnit.DAYS.between(startDateValue, endDateValue.plusDays(1));
|
||||
return days <= 31;
|
||||
}
|
||||
|
||||
private Long startTimeMillis() {
|
||||
return startInstant == null ? null : startInstant.toEpochMilli();
|
||||
}
|
||||
|
||||
@ -1,102 +0,0 @@
|
||||
package com.red.circle.console.app.service.app.props;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.red.circle.console.app.common.sys.ActivitySourceGroupCommon;
|
||||
import com.red.circle.console.inner.error.ConsoleErrorCode;
|
||||
import com.red.circle.framework.core.asserts.ResponseAssert;
|
||||
import com.red.circle.framework.dto.PageResult;
|
||||
import com.red.circle.other.inner.endpoint.activity.PropsActivityClient;
|
||||
import com.red.circle.other.inner.enums.activity.PropsActivityTypeEnum;
|
||||
import com.red.circle.other.inner.model.cmd.material.PropsActivityRuleConfigParamCmd;
|
||||
import com.red.circle.other.inner.model.cmd.material.PropsActivityRuleConfigQryCmd;
|
||||
import com.red.circle.other.inner.model.dto.material.ActivityLuckyBoxDTO;
|
||||
import com.red.circle.other.inner.model.dto.material.PropsActivityRewardConfigInfoDTO;
|
||||
import com.red.circle.other.inner.model.dto.material.PropsActivityRuleConfigDTO;
|
||||
import com.red.circle.tool.core.collection.CollectionUtils;
|
||||
import com.red.circle.tool.core.json.JacksonUtils;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 活动道具规则配置
|
||||
*
|
||||
* @author zongpubin on 2023/10/24
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class PropsActivityRuleConfigServiceImpl implements PropsActivityRuleConfigService {
|
||||
|
||||
private final PropsActivityClient propsActivityClient;
|
||||
|
||||
private final ActivitySourceGroupCommon activitySourceGroupCommon;
|
||||
|
||||
@Override
|
||||
public PageResult<PropsActivityRuleConfigDTO> pagePropsActivityRuleConfig(
|
||||
PropsActivityRuleConfigQryCmd query) {
|
||||
PageResult<PropsActivityRuleConfigDTO> pageResult = ResponseAssert.requiredSuccess(
|
||||
propsActivityClient.pagePropsActivityRuleConfig(query));
|
||||
|
||||
if (CollectionUtils.isEmpty(pageResult.getRecords())) {
|
||||
return pageResult;
|
||||
}
|
||||
Map<Long, List<PropsActivityRewardConfigInfoDTO>> propsActivityRewardMap =
|
||||
activitySourceGroupCommon.mapSourceGroup(query.getSysOrigin(),
|
||||
pageResult.getRecords().stream()
|
||||
.map(PropsActivityRuleConfigDTO::getResourceGroupId).collect(Collectors.toSet()));
|
||||
|
||||
return pageResult.convert(pageItem -> {
|
||||
pageItem.setActivityRewards(propsActivityRewardMap.get(pageItem.getResourceGroupId()));
|
||||
return pageItem;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveOrUpdate(PropsActivityRuleConfigParamCmd param) {
|
||||
|
||||
checkActivityLuckyBox(param);
|
||||
|
||||
if (Objects.nonNull(param.getId())) {
|
||||
|
||||
PropsActivityRuleConfigDTO ruleConfigDTO = ResponseAssert.requiredSuccess(
|
||||
propsActivityClient.getPropsActivityRuleConfig(param));
|
||||
|
||||
ResponseAssert.isTrue(ConsoleErrorCode.BUSINESS_EXISTS,
|
||||
Objects.isNull(ruleConfigDTO) || Objects.equals(ruleConfigDTO.getId(), param.getId()));
|
||||
propsActivityClient.updatePropsActivityRuleConfigById(param);
|
||||
return;
|
||||
}
|
||||
propsActivityClient.savePropsActivityRuleConfig(param);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteById(Long id) {
|
||||
propsActivityClient.propsActivityRuleConfigDeleteById(id);
|
||||
}
|
||||
|
||||
private void checkActivityLuckyBox(PropsActivityRuleConfigParamCmd param) {
|
||||
if (!Objects.equals(PropsActivityTypeEnum.LUCKY_BOX.name(), param.getActivityType())) {
|
||||
return;
|
||||
}
|
||||
List<ActivityLuckyBoxDTO> boxList = JacksonUtils.readValue(param.getJsonData(),
|
||||
new TypeReference<>() {
|
||||
});
|
||||
|
||||
ResponseAssert.isFalse(ConsoleErrorCode.DATE_INCOMPLETE_ERROR,
|
||||
CollectionUtils.isEmpty(boxList));
|
||||
|
||||
for (ActivityLuckyBoxDTO boxDTO : boxList) {
|
||||
if (Objects.isNull(boxDTO.getQuantity()) || Objects.isNull(boxDTO.getOpportunityNumber())) {
|
||||
ResponseAssert.failure(ConsoleErrorCode.NUMBER_VIOLATION_ERROR);
|
||||
break;
|
||||
}
|
||||
if (boxDTO.getQuantity() <= 0 || boxDTO.getOpportunityNumber() <= 0) {
|
||||
ResponseAssert.failure(ConsoleErrorCode.NUMBER_VIOLATION_ERROR);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4,18 +4,21 @@ import com.red.circle.console.app.convertor.PropsAppConvertor;
|
||||
import com.red.circle.console.app.dto.clienobject.props.PropsResourcesOpsCO;
|
||||
import com.red.circle.console.app.dto.cmd.app.props.PropsSourceRecordSaveOrUpdateCmd;
|
||||
import com.red.circle.framework.core.asserts.ResponseAssert;
|
||||
import com.red.circle.framework.dto.PageResult;
|
||||
import com.red.circle.other.inner.endpoint.material.props.PropsRedPacketSkinClient;
|
||||
import com.red.circle.other.inner.endpoint.material.props.PropsSourceClient;
|
||||
import com.red.circle.other.inner.enums.material.ConsolePropsTypeEnum;
|
||||
import com.red.circle.other.inner.model.cmd.material.PropsSourceRecordQryCmd;
|
||||
import com.red.circle.other.inner.model.dto.material.PropsRedPacketSkinDTO;
|
||||
import com.red.circle.other.inner.model.dto.material.PropsResourcesDTO;
|
||||
import com.red.circle.framework.dto.PageResult;
|
||||
import com.red.circle.other.inner.endpoint.material.props.PropsCommodityStoreClient;
|
||||
import com.red.circle.other.inner.endpoint.material.props.PropsRedPacketSkinClient;
|
||||
import com.red.circle.other.inner.endpoint.material.props.PropsSourceClient;
|
||||
import com.red.circle.other.inner.enums.material.ConsolePropsTypeEnum;
|
||||
import com.red.circle.other.inner.model.cmd.material.PropsSourceRecordQryCmd;
|
||||
import com.red.circle.other.inner.model.dto.material.PropsCommodityStoreDTO;
|
||||
import com.red.circle.other.inner.model.dto.material.PropsRedPacketSkinDTO;
|
||||
import com.red.circle.other.inner.model.dto.material.PropsResourcesDTO;
|
||||
import com.red.circle.tool.core.collection.CollectionUtils;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.stereotype.Service;
|
||||
@ -33,9 +36,10 @@ import org.springframework.stereotype.Service;
|
||||
public class PropsSourceServiceImpl implements PropsSourceService {
|
||||
|
||||
@Qualifier("PropsAppConvertorNew")
|
||||
private final PropsAppConvertor propsAppConvertor;
|
||||
private final PropsSourceClient propsSourceClient;
|
||||
private final PropsRedPacketSkinClient propsRedPacketSkinClient;
|
||||
private final PropsAppConvertor propsAppConvertor;
|
||||
private final PropsSourceClient propsSourceClient;
|
||||
private final PropsRedPacketSkinClient propsRedPacketSkinClient;
|
||||
private final PropsCommodityStoreClient propsCommodityStoreClient;
|
||||
|
||||
@Override
|
||||
public List<PropsResourcesDTO> listSysOrigin(String sysOrigin, String type) {
|
||||
@ -62,14 +66,16 @@ public class PropsSourceServiceImpl implements PropsSourceService {
|
||||
if (CollectionUtils.isEmpty(pageResult.getRecords())) {
|
||||
return pageResult.emptyRecords();
|
||||
}
|
||||
Map<Long, PropsRedPacketSkinDTO> redPacketSkinMap = ResponseAssert.requiredSuccess(
|
||||
propsRedPacketSkinClient.mapRedPacketSkinByIds(
|
||||
pageResult.getRecords().stream().map(PropsResourcesDTO::getId).collect(
|
||||
Collectors.toSet())));
|
||||
|
||||
return pageResult.convert(propsResources -> {
|
||||
PropsResourcesOpsCO propsResourcesOpsCO = propsAppConvertor.toPropsResourcesOpsCO(propsResources);
|
||||
|
||||
Set<Long> sourceIds = pageResult.getRecords().stream().map(PropsResourcesDTO::getId).collect(
|
||||
Collectors.toSet());
|
||||
Map<Long, PropsRedPacketSkinDTO> redPacketSkinMap = ResponseAssert.requiredSuccess(
|
||||
propsRedPacketSkinClient.mapRedPacketSkinByIds(sourceIds));
|
||||
Map<Long, PropsCommodityStoreDTO> storeCommodityMap = ResponseAssert.requiredSuccess(
|
||||
propsCommodityStoreClient.mapBySourceIds(qryCmd.getSysOrigin(), sourceIds));
|
||||
|
||||
return pageResult.convert(propsResources -> {
|
||||
PropsResourcesOpsCO propsResourcesOpsCO = propsAppConvertor.toPropsResourcesOpsCO(propsResources);
|
||||
|
||||
PropsRedPacketSkinDTO redPacketSkin = redPacketSkinMap.get(propsResources.getId());
|
||||
if (Objects.nonNull(redPacketSkin)) {
|
||||
propsResourcesOpsCO.setImNotOpenedUrl(redPacketSkin.getImNotOpenedUrl());
|
||||
@ -77,12 +83,13 @@ public class PropsSourceServiceImpl implements PropsSourceService {
|
||||
propsResourcesOpsCO.setRoomNotOpenedUrl(redPacketSkin.getRoomNotOpenedUrl());
|
||||
propsResourcesOpsCO.setRoomOpenedUrl(redPacketSkin.getRoomOpenedUrl());
|
||||
propsResourcesOpsCO.setRoomSendCoverUrl(redPacketSkin.getRoomSendCoverUrl());
|
||||
propsResourcesOpsCO.setImSendCoverUrl(redPacketSkin.getImSendCoverUrl());
|
||||
propsResourcesOpsCO.setImOpenedUrlTwo(redPacketSkin.getImOpenedUrlTwo());
|
||||
}
|
||||
propsResourcesOpsCO.setTypeName(ConsolePropsTypeEnum.getDescValue(propsResources.getType()));
|
||||
return propsResourcesOpsCO;
|
||||
}
|
||||
propsResourcesOpsCO.setImSendCoverUrl(redPacketSkin.getImSendCoverUrl());
|
||||
propsResourcesOpsCO.setImOpenedUrlTwo(redPacketSkin.getImOpenedUrlTwo());
|
||||
}
|
||||
propsResourcesOpsCO.setStoreCommodity(storeCommodityMap.get(propsResources.getId()));
|
||||
propsResourcesOpsCO.setTypeName(ConsolePropsTypeEnum.getDescValue(propsResources.getType()));
|
||||
return propsResourcesOpsCO;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
package com.red.circle.console.app.dto.clienobject.props;
|
||||
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||
import com.red.circle.framework.dto.ClientObject;
|
||||
import com.red.circle.other.inner.model.dto.material.PropsResourcesDTO;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||
import com.red.circle.framework.dto.ClientObject;
|
||||
import com.red.circle.other.inner.model.dto.material.PropsCommodityStoreDTO;
|
||||
import com.red.circle.other.inner.model.dto.material.PropsResourcesDTO;
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.Timestamp;
|
||||
import lombok.Data;
|
||||
@ -63,13 +64,18 @@ public class PropsResourcesOpsCO extends ClientObject {
|
||||
*/
|
||||
private String expand;
|
||||
|
||||
/**
|
||||
* 底价.
|
||||
*/
|
||||
private BigDecimal amount;
|
||||
|
||||
/**
|
||||
* 0.未删除 1.已删除.
|
||||
/**
|
||||
* 底价.
|
||||
*/
|
||||
private BigDecimal amount;
|
||||
|
||||
/**
|
||||
* 对应道具商店商品.
|
||||
*/
|
||||
private PropsCommodityStoreDTO storeCommodity;
|
||||
|
||||
/**
|
||||
* 0.未删除 1.已删除.
|
||||
*/
|
||||
private Boolean del;
|
||||
|
||||
|
||||
@ -38,6 +38,8 @@ public class CountryDashboardMetricCO implements Serializable {
|
||||
|
||||
private BigDecimal salaryExchange = BigDecimal.ZERO;
|
||||
|
||||
private BigDecimal salaryTransfer = BigDecimal.ZERO;
|
||||
|
||||
private BigDecimal totalRecharge = BigDecimal.ZERO;
|
||||
|
||||
private BigDecimal giftConsume = BigDecimal.ZERO;
|
||||
|
||||
@ -1,26 +0,0 @@
|
||||
package com.red.circle.console.app.service.app.props;
|
||||
|
||||
|
||||
import com.red.circle.framework.dto.PageResult;
|
||||
import com.red.circle.other.inner.model.cmd.material.PropsActivityRuleConfigParamCmd;
|
||||
import com.red.circle.other.inner.model.cmd.material.PropsActivityRuleConfigQryCmd;
|
||||
import com.red.circle.other.inner.model.dto.material.PropsActivityRuleConfigDTO;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 活动道具规则配置.
|
||||
* </p>
|
||||
*
|
||||
* @author pengshigang
|
||||
* @since 2021-06-18
|
||||
*/
|
||||
public interface PropsActivityRuleConfigService {
|
||||
|
||||
|
||||
PageResult<PropsActivityRuleConfigDTO> pagePropsActivityRuleConfig(
|
||||
PropsActivityRuleConfigQryCmd query);
|
||||
|
||||
void saveOrUpdate(PropsActivityRuleConfigParamCmd param);
|
||||
|
||||
void deleteById(Long id);
|
||||
}
|
||||
@ -1474,7 +1474,12 @@
|
||||
UNION ALL
|
||||
SELECT user_id, sys_origin, amount, create_time
|
||||
FROM likei_wallet.user_salary_diamond_running_water
|
||||
WHERE type = 1 AND salary_event = 'SALARY_EXCHANGE_GOLD_COINS'
|
||||
WHERE type = 1
|
||||
AND salary_event IN (
|
||||
'SALARY_EXCHANGE_GOLD_COINS',
|
||||
'EXCHANGE_GOLD_COINS',
|
||||
'BILL_EXCHANGE_GOLD_COINS'
|
||||
)
|
||||
) t
|
||||
INNER JOIN user_base_info u ON u.id = t.user_id
|
||||
WHERE 1 = 1
|
||||
|
||||
@ -0,0 +1,117 @@
|
||||
package com.red.circle.framework.web.feign;
|
||||
|
||||
import com.red.circle.framework.core.request.RequestHeaderConstant;
|
||||
import com.red.circle.framework.core.request.ServiceTraceId;
|
||||
import feign.RequestInterceptor;
|
||||
import feign.RequestTemplate;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
/**
|
||||
* Forward only business headers to Feign requests.
|
||||
*/
|
||||
@Component
|
||||
@Order(Ordered.LOWEST_PRECEDENCE)
|
||||
public class FeignRequestInterceptor implements RequestInterceptor {
|
||||
|
||||
private static final String INNER_SERVICE = "FEIGN";
|
||||
|
||||
private static final List<String> FORWARD_HEADERS = List.of(
|
||||
RequestHeaderConstant.AUTHORIZATION,
|
||||
RequestHeaderConstant.REQ_SYS_ORIGIN,
|
||||
RequestHeaderConstant.REQ_CLIENT,
|
||||
RequestHeaderConstant.REQ_ZONE,
|
||||
RequestHeaderConstant.REQ_IMEI,
|
||||
RequestHeaderConstant.REQ_APP_INTEL,
|
||||
RequestHeaderConstant.REQ_VERSION,
|
||||
RequestHeaderConstant.REQ_LANG,
|
||||
RequestHeaderConstant.REQ_INNER_INTERNAL,
|
||||
RequestHeaderConstant.REQ_INNER_TRACE_ID
|
||||
);
|
||||
|
||||
private static final Set<String> BLOCKED_HEADERS = Set.of(
|
||||
"host",
|
||||
"content-length",
|
||||
"transfer-encoding",
|
||||
"connection",
|
||||
"keep-alive",
|
||||
"upgrade",
|
||||
"proxy-authorization",
|
||||
"proxy-authenticate",
|
||||
"te",
|
||||
"trailer"
|
||||
);
|
||||
|
||||
@Override
|
||||
public void apply(RequestTemplate requestTemplate) {
|
||||
removeBlockedHeaders(requestTemplate);
|
||||
|
||||
if (RequestContextHolder.getRequestAttributes() instanceof ServletRequestAttributes attrs) {
|
||||
forwardHeaders(requestTemplate, attrs.getRequest());
|
||||
}
|
||||
|
||||
if (!hasHeader(requestTemplate, RequestHeaderConstant.REQ_INNER_SERVICE)) {
|
||||
requestTemplate.header(RequestHeaderConstant.REQ_INNER_SERVICE, INNER_SERVICE);
|
||||
}
|
||||
if (!hasHeader(requestTemplate, RequestHeaderConstant.REQ_INNER_TRACE_ID)) {
|
||||
requestTemplate.header(
|
||||
RequestHeaderConstant.REQ_INNER_TRACE_ID,
|
||||
ServiceTraceId.genTraceId(ServiceTraceId.Origin.INNER_REQ));
|
||||
}
|
||||
|
||||
removeBlockedHeaders(requestTemplate);
|
||||
}
|
||||
|
||||
private void forwardHeaders(RequestTemplate requestTemplate, HttpServletRequest request) {
|
||||
for (String header : FORWARD_HEADERS) {
|
||||
String value = request.getHeader(header);
|
||||
if (hasHeader(requestTemplate, header) || !isUsableHeaderValue(value)) {
|
||||
continue;
|
||||
}
|
||||
requestTemplate.header(header, value);
|
||||
}
|
||||
}
|
||||
|
||||
private void removeBlockedHeaders(RequestTemplate requestTemplate) {
|
||||
List<String> names = new ArrayList<>(requestTemplate.headers().keySet());
|
||||
for (String name : names) {
|
||||
if (isBlockedHeader(name)) {
|
||||
requestTemplate.removeHeader(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasHeader(RequestTemplate requestTemplate, String header) {
|
||||
return requestTemplate.headers().keySet().stream()
|
||||
.anyMatch(name -> name.equalsIgnoreCase(header));
|
||||
}
|
||||
|
||||
private boolean isBlockedHeader(String header) {
|
||||
if (header == null) {
|
||||
return true;
|
||||
}
|
||||
String normalized = header.toLowerCase(Locale.ROOT);
|
||||
return BLOCKED_HEADERS.contains(normalized) || normalized.startsWith("proxy-");
|
||||
}
|
||||
|
||||
private boolean isUsableHeaderValue(String value) {
|
||||
if (value == null || value.isBlank()) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < value.length(); i++) {
|
||||
char c = value.charAt(i);
|
||||
if (c < 32 || c == 127) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,117 @@
|
||||
package com.red.circle.framework.web.feign;
|
||||
|
||||
import com.red.circle.framework.core.request.RequestHeaderConstant;
|
||||
import com.red.circle.framework.core.request.ServiceTraceId;
|
||||
import feign.RequestInterceptor;
|
||||
import feign.RequestTemplate;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
/**
|
||||
* Forward only business headers to Feign requests.
|
||||
*/
|
||||
@Component
|
||||
@Order(Ordered.LOWEST_PRECEDENCE)
|
||||
public class FeignRequestInterceptor implements RequestInterceptor {
|
||||
|
||||
private static final String INNER_SERVICE = "FEIGN";
|
||||
|
||||
private static final List<String> FORWARD_HEADERS = List.of(
|
||||
RequestHeaderConstant.AUTHORIZATION,
|
||||
RequestHeaderConstant.REQ_SYS_ORIGIN,
|
||||
RequestHeaderConstant.REQ_CLIENT,
|
||||
RequestHeaderConstant.REQ_ZONE,
|
||||
RequestHeaderConstant.REQ_IMEI,
|
||||
RequestHeaderConstant.REQ_APP_INTEL,
|
||||
RequestHeaderConstant.REQ_VERSION,
|
||||
RequestHeaderConstant.REQ_LANG,
|
||||
RequestHeaderConstant.REQ_INNER_INTERNAL,
|
||||
RequestHeaderConstant.REQ_INNER_TRACE_ID
|
||||
);
|
||||
|
||||
private static final Set<String> BLOCKED_HEADERS = Set.of(
|
||||
"host",
|
||||
"content-length",
|
||||
"transfer-encoding",
|
||||
"connection",
|
||||
"keep-alive",
|
||||
"upgrade",
|
||||
"proxy-authorization",
|
||||
"proxy-authenticate",
|
||||
"te",
|
||||
"trailer"
|
||||
);
|
||||
|
||||
@Override
|
||||
public void apply(RequestTemplate requestTemplate) {
|
||||
removeBlockedHeaders(requestTemplate);
|
||||
|
||||
if (RequestContextHolder.getRequestAttributes() instanceof ServletRequestAttributes attrs) {
|
||||
forwardHeaders(requestTemplate, attrs.getRequest());
|
||||
}
|
||||
|
||||
if (!hasHeader(requestTemplate, RequestHeaderConstant.REQ_INNER_SERVICE)) {
|
||||
requestTemplate.header(RequestHeaderConstant.REQ_INNER_SERVICE, INNER_SERVICE);
|
||||
}
|
||||
if (!hasHeader(requestTemplate, RequestHeaderConstant.REQ_INNER_TRACE_ID)) {
|
||||
requestTemplate.header(
|
||||
RequestHeaderConstant.REQ_INNER_TRACE_ID,
|
||||
ServiceTraceId.genTraceId(ServiceTraceId.Origin.INNER_REQ));
|
||||
}
|
||||
|
||||
removeBlockedHeaders(requestTemplate);
|
||||
}
|
||||
|
||||
private void forwardHeaders(RequestTemplate requestTemplate, HttpServletRequest request) {
|
||||
for (String header : FORWARD_HEADERS) {
|
||||
String value = request.getHeader(header);
|
||||
if (hasHeader(requestTemplate, header) || !isUsableHeaderValue(value)) {
|
||||
continue;
|
||||
}
|
||||
requestTemplate.header(header, value);
|
||||
}
|
||||
}
|
||||
|
||||
private void removeBlockedHeaders(RequestTemplate requestTemplate) {
|
||||
List<String> names = new ArrayList<>(requestTemplate.headers().keySet());
|
||||
for (String name : names) {
|
||||
if (isBlockedHeader(name)) {
|
||||
requestTemplate.removeHeader(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasHeader(RequestTemplate requestTemplate, String header) {
|
||||
return requestTemplate.headers().keySet().stream()
|
||||
.anyMatch(name -> name.equalsIgnoreCase(header));
|
||||
}
|
||||
|
||||
private boolean isBlockedHeader(String header) {
|
||||
if (header == null) {
|
||||
return true;
|
||||
}
|
||||
String normalized = header.toLowerCase(Locale.ROOT);
|
||||
return BLOCKED_HEADERS.contains(normalized) || normalized.startsWith("proxy-");
|
||||
}
|
||||
|
||||
private boolean isUsableHeaderValue(String value) {
|
||||
if (value == null || value.isBlank()) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < value.length(); i++) {
|
||||
char c = value.charAt(i);
|
||||
if (c < 32 || c == 127) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,117 @@
|
||||
package com.red.circle.framework.web.feign;
|
||||
|
||||
import com.red.circle.framework.core.request.RequestHeaderConstant;
|
||||
import com.red.circle.framework.core.request.ServiceTraceId;
|
||||
import feign.RequestInterceptor;
|
||||
import feign.RequestTemplate;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
/**
|
||||
* Forward only business headers to Feign requests.
|
||||
*/
|
||||
@Component
|
||||
@Order(Ordered.LOWEST_PRECEDENCE)
|
||||
public class FeignRequestInterceptor implements RequestInterceptor {
|
||||
|
||||
private static final String INNER_SERVICE = "FEIGN";
|
||||
|
||||
private static final List<String> FORWARD_HEADERS = List.of(
|
||||
RequestHeaderConstant.AUTHORIZATION,
|
||||
RequestHeaderConstant.REQ_SYS_ORIGIN,
|
||||
RequestHeaderConstant.REQ_CLIENT,
|
||||
RequestHeaderConstant.REQ_ZONE,
|
||||
RequestHeaderConstant.REQ_IMEI,
|
||||
RequestHeaderConstant.REQ_APP_INTEL,
|
||||
RequestHeaderConstant.REQ_VERSION,
|
||||
RequestHeaderConstant.REQ_LANG,
|
||||
RequestHeaderConstant.REQ_INNER_INTERNAL,
|
||||
RequestHeaderConstant.REQ_INNER_TRACE_ID
|
||||
);
|
||||
|
||||
private static final Set<String> BLOCKED_HEADERS = Set.of(
|
||||
"host",
|
||||
"content-length",
|
||||
"transfer-encoding",
|
||||
"connection",
|
||||
"keep-alive",
|
||||
"upgrade",
|
||||
"proxy-authorization",
|
||||
"proxy-authenticate",
|
||||
"te",
|
||||
"trailer"
|
||||
);
|
||||
|
||||
@Override
|
||||
public void apply(RequestTemplate requestTemplate) {
|
||||
removeBlockedHeaders(requestTemplate);
|
||||
|
||||
if (RequestContextHolder.getRequestAttributes() instanceof ServletRequestAttributes attrs) {
|
||||
forwardHeaders(requestTemplate, attrs.getRequest());
|
||||
}
|
||||
|
||||
if (!hasHeader(requestTemplate, RequestHeaderConstant.REQ_INNER_SERVICE)) {
|
||||
requestTemplate.header(RequestHeaderConstant.REQ_INNER_SERVICE, INNER_SERVICE);
|
||||
}
|
||||
if (!hasHeader(requestTemplate, RequestHeaderConstant.REQ_INNER_TRACE_ID)) {
|
||||
requestTemplate.header(
|
||||
RequestHeaderConstant.REQ_INNER_TRACE_ID,
|
||||
ServiceTraceId.genTraceId(ServiceTraceId.Origin.INNER_REQ));
|
||||
}
|
||||
|
||||
removeBlockedHeaders(requestTemplate);
|
||||
}
|
||||
|
||||
private void forwardHeaders(RequestTemplate requestTemplate, HttpServletRequest request) {
|
||||
for (String header : FORWARD_HEADERS) {
|
||||
String value = request.getHeader(header);
|
||||
if (hasHeader(requestTemplate, header) || !isUsableHeaderValue(value)) {
|
||||
continue;
|
||||
}
|
||||
requestTemplate.header(header, value);
|
||||
}
|
||||
}
|
||||
|
||||
private void removeBlockedHeaders(RequestTemplate requestTemplate) {
|
||||
List<String> names = new ArrayList<>(requestTemplate.headers().keySet());
|
||||
for (String name : names) {
|
||||
if (isBlockedHeader(name)) {
|
||||
requestTemplate.removeHeader(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasHeader(RequestTemplate requestTemplate, String header) {
|
||||
return requestTemplate.headers().keySet().stream()
|
||||
.anyMatch(name -> name.equalsIgnoreCase(header));
|
||||
}
|
||||
|
||||
private boolean isBlockedHeader(String header) {
|
||||
if (header == null) {
|
||||
return true;
|
||||
}
|
||||
String normalized = header.toLowerCase(Locale.ROOT);
|
||||
return BLOCKED_HEADERS.contains(normalized) || normalized.startsWith("proxy-");
|
||||
}
|
||||
|
||||
private boolean isUsableHeaderValue(String value) {
|
||||
if (value == null || value.isBlank()) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < value.length(); i++) {
|
||||
char c = value.charAt(i);
|
||||
if (c < 32 || c == 127) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,117 @@
|
||||
package com.red.circle.framework.web.feign;
|
||||
|
||||
import com.red.circle.framework.core.request.RequestHeaderConstant;
|
||||
import com.red.circle.framework.core.request.ServiceTraceId;
|
||||
import feign.RequestInterceptor;
|
||||
import feign.RequestTemplate;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
/**
|
||||
* Forward only business headers to Feign requests.
|
||||
*/
|
||||
@Component
|
||||
@Order(Ordered.LOWEST_PRECEDENCE)
|
||||
public class FeignRequestInterceptor implements RequestInterceptor {
|
||||
|
||||
private static final String INNER_SERVICE = "FEIGN";
|
||||
|
||||
private static final List<String> FORWARD_HEADERS = List.of(
|
||||
RequestHeaderConstant.AUTHORIZATION,
|
||||
RequestHeaderConstant.REQ_SYS_ORIGIN,
|
||||
RequestHeaderConstant.REQ_CLIENT,
|
||||
RequestHeaderConstant.REQ_ZONE,
|
||||
RequestHeaderConstant.REQ_IMEI,
|
||||
RequestHeaderConstant.REQ_APP_INTEL,
|
||||
RequestHeaderConstant.REQ_VERSION,
|
||||
RequestHeaderConstant.REQ_LANG,
|
||||
RequestHeaderConstant.REQ_INNER_INTERNAL,
|
||||
RequestHeaderConstant.REQ_INNER_TRACE_ID
|
||||
);
|
||||
|
||||
private static final Set<String> BLOCKED_HEADERS = Set.of(
|
||||
"host",
|
||||
"content-length",
|
||||
"transfer-encoding",
|
||||
"connection",
|
||||
"keep-alive",
|
||||
"upgrade",
|
||||
"proxy-authorization",
|
||||
"proxy-authenticate",
|
||||
"te",
|
||||
"trailer"
|
||||
);
|
||||
|
||||
@Override
|
||||
public void apply(RequestTemplate requestTemplate) {
|
||||
removeBlockedHeaders(requestTemplate);
|
||||
|
||||
if (RequestContextHolder.getRequestAttributes() instanceof ServletRequestAttributes attrs) {
|
||||
forwardHeaders(requestTemplate, attrs.getRequest());
|
||||
}
|
||||
|
||||
if (!hasHeader(requestTemplate, RequestHeaderConstant.REQ_INNER_SERVICE)) {
|
||||
requestTemplate.header(RequestHeaderConstant.REQ_INNER_SERVICE, INNER_SERVICE);
|
||||
}
|
||||
if (!hasHeader(requestTemplate, RequestHeaderConstant.REQ_INNER_TRACE_ID)) {
|
||||
requestTemplate.header(
|
||||
RequestHeaderConstant.REQ_INNER_TRACE_ID,
|
||||
ServiceTraceId.genTraceId(ServiceTraceId.Origin.INNER_REQ));
|
||||
}
|
||||
|
||||
removeBlockedHeaders(requestTemplate);
|
||||
}
|
||||
|
||||
private void forwardHeaders(RequestTemplate requestTemplate, HttpServletRequest request) {
|
||||
for (String header : FORWARD_HEADERS) {
|
||||
String value = request.getHeader(header);
|
||||
if (hasHeader(requestTemplate, header) || !isUsableHeaderValue(value)) {
|
||||
continue;
|
||||
}
|
||||
requestTemplate.header(header, value);
|
||||
}
|
||||
}
|
||||
|
||||
private void removeBlockedHeaders(RequestTemplate requestTemplate) {
|
||||
List<String> names = new ArrayList<>(requestTemplate.headers().keySet());
|
||||
for (String name : names) {
|
||||
if (isBlockedHeader(name)) {
|
||||
requestTemplate.removeHeader(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasHeader(RequestTemplate requestTemplate, String header) {
|
||||
return requestTemplate.headers().keySet().stream()
|
||||
.anyMatch(name -> name.equalsIgnoreCase(header));
|
||||
}
|
||||
|
||||
private boolean isBlockedHeader(String header) {
|
||||
if (header == null) {
|
||||
return true;
|
||||
}
|
||||
String normalized = header.toLowerCase(Locale.ROOT);
|
||||
return BLOCKED_HEADERS.contains(normalized) || normalized.startsWith("proxy-");
|
||||
}
|
||||
|
||||
private boolean isUsableHeaderValue(String value) {
|
||||
if (value == null || value.isBlank()) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < value.length(); i++) {
|
||||
char c = value.charAt(i);
|
||||
if (c < 32 || c == 127) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -6,13 +6,12 @@ import com.red.circle.other.domain.gateway.AdminTaskGateway;
|
||||
import com.red.circle.other.app.dto.cmd.AdminTaskQueryCmd;
|
||||
import com.red.circle.other.infra.database.mongo.service.team.TeamBillCycleUtils;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@ -27,10 +26,10 @@ public class AdminTaskQueryCmdExe {
|
||||
|
||||
private final AdminTaskGateway adminTaskGateway;
|
||||
|
||||
public List<AdminTaskRecord> execute(AdminTaskQueryCmd cmd) {
|
||||
LocalDate cycleStartDate = cmd.getCycleStartDate() != null ? cmd.getCycleStartDate() :
|
||||
LocalDate.parse(TeamBillCycleUtils.getCalcBillBelong().toString(), DateTimeFormatter.ofPattern("yyyyMMdd"));
|
||||
List<AdminTaskRecord> records = adminTaskGateway.findByUserIdAndCycle(cmd.getUserId(), cycleStartDate);
|
||||
public List<AdminTaskRecord> execute(AdminTaskQueryCmd cmd) {
|
||||
LocalDate cycleStartDate = cmd.getCycleStartDate() != null ? cmd.getCycleStartDate() :
|
||||
TeamBillCycleUtils.getBillBelongStartDate(TeamBillCycleUtils.getCalcBillBelong());
|
||||
List<AdminTaskRecord> records = adminTaskGateway.findByUserIdAndCycle(cmd.getUserId(), cycleStartDate);
|
||||
Map<String, AdminTaskRecord> taskRecordMap = records.stream().collect(Collectors.toMap(AdminTaskRecord::getTaskType, e -> e));
|
||||
|
||||
List<AdminTaskRecord> recordList = new ArrayList<>();
|
||||
@ -46,10 +45,10 @@ public class AdminTaskQueryCmdExe {
|
||||
/**
|
||||
* 查询单个任务
|
||||
*/
|
||||
public AdminTaskRecord findByUserIdAndTaskTypeAndCycle(Long userId, String taskType, LocalDate cycleStartDate) {
|
||||
LocalDate queryDate = cycleStartDate != null ? cycleStartDate :
|
||||
LocalDate.parse(TeamBillCycleUtils.getCalcBillBelong().toString(), DateTimeFormatter.ofPattern("yyyyMMdd"));
|
||||
return adminTaskGateway.findByUserIdAndTaskTypeAndCycle(userId, taskType, queryDate);
|
||||
}
|
||||
public AdminTaskRecord findByUserIdAndTaskTypeAndCycle(Long userId, String taskType, LocalDate cycleStartDate) {
|
||||
LocalDate queryDate = cycleStartDate != null ? cycleStartDate :
|
||||
TeamBillCycleUtils.getBillBelongStartDate(TeamBillCycleUtils.getCalcBillBelong());
|
||||
return adminTaskGateway.findByUserIdAndTaskTypeAndCycle(userId, taskType, queryDate);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -59,13 +59,15 @@ public class TeamBillQryExe {
|
||||
|
||||
public List<TeamBillCO> execute(AppIdLongCmd cmd) {
|
||||
|
||||
List<TeamBillCycle> teamBillCycles = teamBillCycleService.listLatestBill(cmd.getId(),
|
||||
PageConstant.MAX_LIMIT_SIZE);
|
||||
List<TeamBillCycle> teamBillCycles = teamBillCycleService.listByGeBillBelong(cmd.getId(),
|
||||
0, PageConstant.MAX_LIMIT_SIZE);
|
||||
|
||||
if (CollectionUtils.isEmpty(teamBillCycles)) {
|
||||
return Lists.newArrayList();
|
||||
}
|
||||
List<TeamBillCO> teamBill = teamBillCycles.stream().map(teamAppConvertor::toTeamBillCO).collect(Collectors.toList());
|
||||
List<TeamBillCO> teamBill = teamBillCycles.stream()
|
||||
.map(this::toMonthlyTeamBillCO)
|
||||
.collect(Collectors.toList());
|
||||
for (TeamBillCO teamBillCO : teamBill) {
|
||||
if (!Objects.isNull(teamBillCO.getSettleResult())) {
|
||||
BillDiamondBalance billDiamondBalance = billDiamondBalanceService
|
||||
@ -91,7 +93,7 @@ public class TeamBillQryExe {
|
||||
*/
|
||||
public List<TeamBillCO> executeAgent(AppIdLongCmd cmd) {
|
||||
|
||||
List<TeamBillCycle> teamBillCycles = teamBillCycleService.listLatestBill(cmd.getId(), 5);
|
||||
List<TeamBillCycle> teamBillCycles = teamBillCycleService.listByGeBillBelong(cmd.getId(), 0, 5);
|
||||
|
||||
if (CollectionUtils.isEmpty(teamBillCycles)) {
|
||||
return Lists.newArrayList();
|
||||
@ -110,15 +112,15 @@ public class TeamBillQryExe {
|
||||
}
|
||||
|
||||
// 查询该账单周期下的所有成员目标数据
|
||||
List<TeamMemberTarget> teamMemberTargets = teamMemberTargetService.listByBillBelong(
|
||||
teamBillCycle.getTeamId(), teamBillCycle.getBillBelong());
|
||||
List<TeamMemberTarget> teamMemberTargets = teamMemberTargetService.listByMonthlyBillBelong(
|
||||
teamBillCycle.getTeamId(), TeamBillCycleUtils.extractMonth(teamBillCycle.getBillBelong()));
|
||||
|
||||
if (CollectionUtils.isEmpty(teamMemberTargets)) {
|
||||
// 如果没有成员数据,仍然返回账单基本信息
|
||||
TeamBillCO teamBillCO = teamAppConvertor.toTeamBillCO(teamBillCycle);
|
||||
result.add(teamBillCO);
|
||||
continue;
|
||||
}
|
||||
TeamBillCO teamBillCO = toMonthlyTeamBillCO(teamBillCycle);
|
||||
result.add(teamBillCO);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 累计所有成员的数据
|
||||
List<TeamBillMemberTarget> memberTargetList = Lists.newArrayList();
|
||||
@ -134,8 +136,7 @@ public class TeamBillQryExe {
|
||||
}
|
||||
|
||||
// 转换为账单CO对象
|
||||
TeamBillCO teamBillCO = teamAppConvertor.toTeamBillCO(teamBillCycle);
|
||||
teamBillCO.setBillTitle(TeamBillCycleUtils.parseBillBelongToDateRangeStr(teamBillCO.getBillBelong()));
|
||||
TeamBillCO teamBillCO = toMonthlyTeamBillCO(teamBillCycle);
|
||||
|
||||
// 设置累计的成员结算结果
|
||||
if (!CollectionUtils.isEmpty(memberTargetList)) {
|
||||
@ -154,12 +155,20 @@ public class TeamBillQryExe {
|
||||
return result;
|
||||
}
|
||||
|
||||
private static List<TeamMemberTargetIndex> filter(List<TeamMemberTargetIndex> stringList, Integer dateNumBer) {
|
||||
private static List<TeamMemberTargetIndex> filter(List<TeamMemberTargetIndex> stringList, Integer dateNumBer) {
|
||||
stringList = stringList.stream().filter(item -> {
|
||||
return item.getDateNumber().equals(dateNumBer);
|
||||
}).collect(Collectors.toList());
|
||||
return stringList;
|
||||
}
|
||||
}
|
||||
|
||||
private TeamBillCO toMonthlyTeamBillCO(TeamBillCycle teamBillCycle) {
|
||||
TeamBillCO teamBillCO = teamAppConvertor.toTeamBillCO(teamBillCycle);
|
||||
Integer monthlyBillBelong = TeamBillCycleUtils.extractMonth(teamBillCycle.getBillBelong());
|
||||
teamBillCO.setBillBelong(monthlyBillBelong);
|
||||
teamBillCO.setBillTitle(TeamBillCycleUtils.parseBillBelongToDateRangeStr(monthlyBillBelong));
|
||||
return teamBillCO;
|
||||
}
|
||||
|
||||
private Map<String, String> getMetadata(SysRegionConfig regionConfig) {
|
||||
return Optional.ofNullable(regionConfig)
|
||||
|
||||
@ -57,12 +57,21 @@ public class TeamBillWorkMembersQryExe {
|
||||
return Lists.newArrayList();
|
||||
}
|
||||
|
||||
List<TeamMemberTarget> teamMemberTargets = teamMemberTargetService.listByBillBelong(
|
||||
teamBillCycle.getTeamId(), teamBillCycle.getBillBelong());
|
||||
|
||||
if (CollectionUtils.isEmpty(teamMemberTargets)) {
|
||||
return Lists.newArrayList();
|
||||
}
|
||||
Integer monthlyBillBelong = TeamBillCycleUtils.extractMonth(teamBillCycle.getBillBelong());
|
||||
List<TeamMemberTarget> teamMemberTargets = teamMemberTargetService
|
||||
.listByMonthlyBillBelong(teamBillCycle.getTeamId(), monthlyBillBelong);
|
||||
|
||||
if (CollectionUtils.isEmpty(teamMemberTargets)) {
|
||||
return Lists.newArrayList();
|
||||
}
|
||||
|
||||
teamMemberTargets = teamMemberTargets.stream()
|
||||
.map(TeamMemberTarget::getUserId)
|
||||
.distinct()
|
||||
.map(userId -> teamMemberTargetService.aggregateMonthlyTarget(
|
||||
teamBillCycle.getTeamId(), userId, monthlyBillBelong))
|
||||
.filter(Objects::nonNull)
|
||||
.toList();
|
||||
|
||||
Map<Long, UserProfileDTO> userProfileMap = userProfileAppConvertor.toMapUserProfileDTO(
|
||||
userProfileGateway.mapByUserIds(
|
||||
|
||||
@ -92,12 +92,13 @@ public class TeamMemberWorkQryExe {
|
||||
.setTargets(Lists.newArrayList());
|
||||
}
|
||||
|
||||
Map<Integer, TeamMemberTarget> teamMemberTargetMap = Optional.ofNullable(
|
||||
teamMemberTargetService.listByGeBillBelong(
|
||||
teamMember.getTeamId(), teamMember.getMemberId(), billBelong))
|
||||
.map(target -> target.stream()
|
||||
.collect(Collectors.toMap(TeamMemberTarget::getBillBelong, v -> v)))
|
||||
.orElseGet(Maps::newHashMap);
|
||||
Map<Integer, TeamMemberTarget> teamMemberTargetMap = Optional.ofNullable(
|
||||
teamMemberTargetService.listByGeBillBelong(
|
||||
teamMember.getTeamId(), teamMember.getMemberId(), billBelong))
|
||||
.map(target -> target.stream()
|
||||
.collect(Collectors.toMap(TeamMemberTarget::getBillBelong, v -> v, (v1, v2) -> v2)))
|
||||
.orElseGet(Maps::newHashMap);
|
||||
Map<Integer, TeamMemberTarget> monthlyTargetCache = Maps.newHashMap();
|
||||
|
||||
List<TeamBillCycle> unpaidBillCycles = teamBillCycles.stream()
|
||||
.filter(
|
||||
@ -114,18 +115,24 @@ public class TeamMemberWorkQryExe {
|
||||
|
||||
TeamMemberWorkCO teamMemberWorkCO = new TeamMemberWorkCO()
|
||||
.setMemberProfile(userProfile)
|
||||
.setTargets(teamBillCycles.stream()
|
||||
.map(bill -> {
|
||||
TeamMemberWorkBillCO co = new TeamMemberWorkBillCO()
|
||||
.setId(bill.getId())
|
||||
.setBillBelong(bill.getBillBelong())
|
||||
.setBillTitle(TeamBillCycleUtils.parseBillBelongToDateRangeStr(bill.getBillBelong()))
|
||||
.setStatus(bill.getStatus().name());
|
||||
|
||||
TeamMemberTarget target = teamMemberTargetMap.get(bill.getBillBelong());
|
||||
if (Objects.isNull(target)) {
|
||||
return co;
|
||||
}
|
||||
.setTargets(teamBillCycles.stream()
|
||||
.map(bill -> {
|
||||
Integer monthlyBillBelong = TeamBillCycleUtils.extractMonth(bill.getBillBelong());
|
||||
TeamMemberWorkBillCO co = new TeamMemberWorkBillCO()
|
||||
.setId(bill.getId())
|
||||
.setBillBelong(monthlyBillBelong)
|
||||
.setBillTitle(TeamBillCycleUtils.parseBillBelongToDateRangeStr(monthlyBillBelong))
|
||||
.setStatus(bill.getStatus().name());
|
||||
|
||||
TeamMemberTarget target = resolveMonthlyTarget(
|
||||
teamMember.getTeamId(),
|
||||
teamMember.getMemberId(),
|
||||
bill,
|
||||
teamMemberTargetMap,
|
||||
monthlyTargetCache);
|
||||
if (Objects.isNull(target)) {
|
||||
return co;
|
||||
}
|
||||
|
||||
co.setTarget(new TeamMemberWorkTargetCO()
|
||||
.setId(target.getTimeId())
|
||||
@ -182,13 +189,45 @@ public class TeamMemberWorkQryExe {
|
||||
return stringList;
|
||||
}
|
||||
|
||||
private Map<String, String> getMetadata(SysRegionConfig regionConfig) {
|
||||
return Optional.ofNullable(regionConfig)
|
||||
.map(SysRegionConfig::getMetadata).orElseGet(CollectionUtils::newHashMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否开启每日自动结算工资.
|
||||
private Map<String, String> getMetadata(SysRegionConfig regionConfig) {
|
||||
return Optional.ofNullable(regionConfig)
|
||||
.map(SysRegionConfig::getMetadata).orElseGet(CollectionUtils::newHashMap);
|
||||
}
|
||||
|
||||
private TeamMemberTarget resolveMonthlyTarget(Long teamId,
|
||||
Long memberId,
|
||||
TeamBillCycle bill,
|
||||
Map<Integer, TeamMemberTarget> targetMap,
|
||||
Map<Integer, TeamMemberTarget> monthlyTargetCache) {
|
||||
Integer billBelong = bill.getBillBelong();
|
||||
Integer monthlyBillBelong = TeamBillCycleUtils.extractMonth(billBelong);
|
||||
|
||||
TeamMemberTarget cachedMonthlyTarget = monthlyTargetCache.get(monthlyBillBelong);
|
||||
if (Objects.nonNull(cachedMonthlyTarget)) {
|
||||
return cachedMonthlyTarget;
|
||||
}
|
||||
|
||||
TeamMemberTarget aggregatedTarget = teamMemberTargetService.aggregateMonthlyTarget(
|
||||
teamId, memberId, monthlyBillBelong);
|
||||
if (Objects.nonNull(aggregatedTarget)) {
|
||||
monthlyTargetCache.put(monthlyBillBelong, aggregatedTarget);
|
||||
return aggregatedTarget;
|
||||
}
|
||||
|
||||
TeamMemberTarget monthlyTarget = targetMap.get(monthlyBillBelong);
|
||||
if (Objects.nonNull(monthlyTarget)) {
|
||||
return monthlyTarget;
|
||||
}
|
||||
|
||||
TeamMemberTarget exactTarget = targetMap.get(billBelong);
|
||||
if (Objects.equals(billBelong, monthlyBillBelong)) {
|
||||
return exactTarget;
|
||||
}
|
||||
return exactTarget;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否开启每日自动结算工资.
|
||||
*/
|
||||
private boolean isOpenDailyAutoSalary(Map<String, String> metadata) {
|
||||
return Objects.equals(metadata.get("openDailyAutoSalary"), "true");
|
||||
|
||||
@ -156,8 +156,8 @@ public class GiftAnchorCountStrategy implements GiftStrategy {
|
||||
teamIds.add(teamMember.getTeamId());
|
||||
|
||||
// 团队内 -> 代理主播活动(永久活动)
|
||||
// activityAgentAnchorCount(acceptAnchorUser.getTargetAmount().longValue(),
|
||||
// acceptAnchorUser.getAcceptUserId(), teamMember.getTeamId());
|
||||
activityAgentAnchorCount(acceptAnchorUser.getTargetAmount().longValue(),
|
||||
acceptAnchorUser.getAcceptUserId(), teamMember.getTeamId());
|
||||
// 代理活动(永久活动)
|
||||
// agentActivityCount(acceptAnchorUser, teamMember.getTeamId());
|
||||
}
|
||||
|
||||
@ -5,15 +5,12 @@ import com.red.circle.other.app.service.AdminSalarySettlementService;
|
||||
import com.red.circle.other.infra.database.mongo.service.team.TeamBillCycleUtils;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
/**
|
||||
* 管理员工资结算定时任务.
|
||||
* 每月1号和16号 2点执行半月结算(在BD和BD Leader结算之后)
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 管理员工资结算定时任务.
|
||||
* 每月1号2点执行整月结算(在BD和BD Leader结算之后).
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 2025-12-13
|
||||
@ -25,12 +22,12 @@ public class AdminSalarySettlementTask {
|
||||
|
||||
private final AdminSalarySettlementService adminSalarySettlementService;
|
||||
|
||||
/**
|
||||
* 每月1号和16号 2点执行半月结算.
|
||||
*/
|
||||
@Scheduled(cron = "0 0 2 1,16 * ?", zone = "Asia/Riyadh")
|
||||
@TaskCacheLock(key = "ADMIN_SALARY_SETTLEMENT", expireSecond = 86400)
|
||||
public void processAdminSalarySettlement() {
|
||||
/**
|
||||
* 每月1号2点执行上月整月结算.
|
||||
*/
|
||||
@Scheduled(cron = "0 0 2 1 * ?", zone = "Asia/Riyadh")
|
||||
@TaskCacheLock(key = "ADMIN_SALARY_SETTLEMENT", expireSecond = 86400)
|
||||
public void processAdminSalarySettlement() {
|
||||
long startTime = System.currentTimeMillis();
|
||||
log.warn("========== 管理员工资结算定时任务开始 ==========");
|
||||
|
||||
@ -54,30 +51,12 @@ public class AdminSalarySettlementTask {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算需要结算的账期.
|
||||
* 规则:
|
||||
* - 如果当前账期是本月1号(如20250101),则结算上个月16号的账期(如20241216)
|
||||
* - 如果当前账期是本月16号(如20250116),则结算本月1号的账期(如20250101)
|
||||
*/
|
||||
private Integer calculateSettlementBillBelong(Integer currentBillBelong) {
|
||||
String billStr = String.valueOf(currentBillBelong);
|
||||
int year = Integer.parseInt(billStr.substring(0, 4));
|
||||
int month = Integer.parseInt(billStr.substring(4, 6));
|
||||
int day = Integer.parseInt(billStr.substring(6, 8));
|
||||
|
||||
LocalDate settlementDate;
|
||||
|
||||
if (day == 1) {
|
||||
// 当前是本月1号,结算上个月16号
|
||||
settlementDate = LocalDate.of(year, month, 1).minusMonths(1).withDayOfMonth(16);
|
||||
} else {
|
||||
// 当前是本月16号,结算本月1号
|
||||
settlementDate = LocalDate.of(year, month, 1);
|
||||
}
|
||||
|
||||
return Integer.parseInt(settlementDate.format(DateTimeFormatter.ofPattern("yyyyMMdd")));
|
||||
}
|
||||
/**
|
||||
* 计算需要结算的上月整月账期.
|
||||
*/
|
||||
private Integer calculateSettlementBillBelong(Integer currentBillBelong) {
|
||||
return TeamBillCycleUtils.getPreviousMonthlyBillBelong(currentBillBelong);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试方法(手动触发).
|
||||
|
||||
@ -5,16 +5,12 @@ import com.red.circle.other.app.service.BdLeaderSalarySettlementService;
|
||||
import com.red.circle.other.infra.database.mongo.service.team.TeamBillCycleUtils;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
/**
|
||||
* BD Leader 工资结算定时任务.
|
||||
* 每月1号和16号 0点执行半月结算
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* BD Leader 工资结算定时任务.
|
||||
* 每月1号0点执行整月结算.
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 2025-10-29
|
||||
@ -26,12 +22,12 @@ public class BdLeaderSalarySettlementTask {
|
||||
|
||||
private final BdLeaderSalarySettlementService bdLeaderSalarySettlementService;
|
||||
|
||||
/**
|
||||
* 每月1号和16号 1点半执行半月结算.
|
||||
*/
|
||||
@Scheduled(cron = "0 0 1 1,16 * ?", zone = "Asia/Riyadh")
|
||||
@TaskCacheLock(key = "BD_LEADER_SALARY_SETTLEMENT", expireSecond = 86400)
|
||||
public void processBdLeaderSalarySettlement() {
|
||||
/**
|
||||
* 每月1号1点执行上月整月结算.
|
||||
*/
|
||||
@Scheduled(cron = "0 0 1 1 * ?", zone = "Asia/Riyadh")
|
||||
@TaskCacheLock(key = "BD_LEADER_SALARY_SETTLEMENT", expireSecond = 86400)
|
||||
public void processBdLeaderSalarySettlement() {
|
||||
long startTime = System.currentTimeMillis();
|
||||
log.warn("========== BD Leader工资结算定时任务开始 ==========");
|
||||
|
||||
@ -55,30 +51,12 @@ public class BdLeaderSalarySettlementTask {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算需要结算的账期
|
||||
* 规则:
|
||||
* - 如果当前账期是本月1号(如20250101),则结算上个月16号的账期(如20241216)
|
||||
* - 如果当前账期是本月16号(如20250116),则结算本月1号的账期(如20250101)
|
||||
*/
|
||||
private Integer calculateSettlementBillBelong(Integer currentBillBelong) {
|
||||
String billStr = String.valueOf(currentBillBelong);
|
||||
int year = Integer.parseInt(billStr.substring(0, 4));
|
||||
int month = Integer.parseInt(billStr.substring(4, 6));
|
||||
int day = Integer.parseInt(billStr.substring(6, 8));
|
||||
|
||||
LocalDate settlementDate;
|
||||
|
||||
if (day == 1) {
|
||||
// 当前是本月1号,结算上个月16号
|
||||
settlementDate = LocalDate.of(year, month, 1).minusMonths(1).withDayOfMonth(16);
|
||||
} else {
|
||||
// 当前是本月16号,结算本月1号
|
||||
settlementDate = LocalDate.of(year, month, 1);
|
||||
}
|
||||
|
||||
return Integer.parseInt(settlementDate.format(DateTimeFormatter.ofPattern("yyyyMMdd")));
|
||||
}
|
||||
/**
|
||||
* 计算需要结算的上月整月账期.
|
||||
*/
|
||||
private Integer calculateSettlementBillBelong(Integer currentBillBelong) {
|
||||
return TeamBillCycleUtils.getPreviousMonthlyBillBelong(currentBillBelong);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试方法(手动触发).
|
||||
|
||||
@ -5,16 +5,12 @@ import com.red.circle.other.app.service.BdSalarySettlementService;
|
||||
import com.red.circle.other.infra.database.mongo.service.team.TeamBillCycleUtils;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
/**
|
||||
* BD 工资结算定时任务.
|
||||
* 每月1号和16号 0点执行半月结算
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* BD 工资结算定时任务.
|
||||
* 每月1号0点执行整月结算.
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 2025-10-29
|
||||
@ -26,12 +22,12 @@ public class BdSalarySettlementTask {
|
||||
|
||||
private final BdSalarySettlementService bdSalarySettlementService;
|
||||
|
||||
/**
|
||||
* 每月1号和16号 1点执行半月结算.
|
||||
*/
|
||||
@Scheduled(cron = "0 30 0 1,16 * ?", zone = "Asia/Riyadh")
|
||||
@TaskCacheLock(key = "BD_SALARY_SETTLEMENT", expireSecond = 86400)
|
||||
public void processBdSalarySettlement() {
|
||||
/**
|
||||
* 每月1号0点30分执行上月整月结算.
|
||||
*/
|
||||
@Scheduled(cron = "0 30 0 1 * ?", zone = "Asia/Riyadh")
|
||||
@TaskCacheLock(key = "BD_SALARY_SETTLEMENT", expireSecond = 86400)
|
||||
public void processBdSalarySettlement() {
|
||||
long startTime = System.currentTimeMillis();
|
||||
log.warn("========== BD工资结算定时任务开始 ==========");
|
||||
|
||||
@ -55,30 +51,12 @@ public class BdSalarySettlementTask {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算需要结算的账期
|
||||
* 规则:
|
||||
* - 如果当前账期是本月1号(如20250101),则结算上个月16号的账期(如20241216)
|
||||
* - 如果当前账期是本月16号(如20250116),则结算本月1号的账期(如20250101)
|
||||
*/
|
||||
private Integer calculateSettlementBillBelong(Integer currentBillBelong) {
|
||||
String billStr = String.valueOf(currentBillBelong);
|
||||
int year = Integer.parseInt(billStr.substring(0, 4));
|
||||
int month = Integer.parseInt(billStr.substring(4, 6));
|
||||
int day = Integer.parseInt(billStr.substring(6, 8));
|
||||
|
||||
LocalDate settlementDate;
|
||||
|
||||
if (day == 1) {
|
||||
// 当前是本月1号,结算上个月16号
|
||||
settlementDate = LocalDate.of(year, month, 1).minusMonths(1).withDayOfMonth(16);
|
||||
} else {
|
||||
// 当前是本月16号,结算本月1号
|
||||
settlementDate = LocalDate.of(year, month, 1);
|
||||
}
|
||||
|
||||
return Integer.parseInt(settlementDate.format(DateTimeFormatter.ofPattern("yyyyMMdd")));
|
||||
}
|
||||
/**
|
||||
* 计算需要结算的上月整月账期.
|
||||
*/
|
||||
private Integer calculateSettlementBillBelong(Integer currentBillBelong) {
|
||||
return TeamBillCycleUtils.getPreviousMonthlyBillBelong(currentBillBelong);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试方法(手动触发).
|
||||
|
||||
@ -5,21 +5,16 @@ import com.red.circle.mq.business.model.event.team.TeamBillSettleEvent;
|
||||
import com.red.circle.mq.rocket.business.producer.TeamSalaryMqMessage;
|
||||
import com.red.circle.other.infra.database.mongo.entity.team.team.TeamProfile;
|
||||
import com.red.circle.other.infra.database.mongo.service.gift.GiftGiveRunningWaterService;
|
||||
import com.red.circle.other.infra.database.mongo.service.team.TeamBillCycleUtils;
|
||||
import com.red.circle.other.infra.database.mongo.service.team.team.TeamBillCycleService;
|
||||
import com.red.circle.other.infra.database.mongo.service.team.team.TeamProfileService;
|
||||
import com.red.circle.other.infra.utils.ZonedDateTimeUtils;
|
||||
import com.red.circle.other.inner.model.cmd.team.TeamBillCmd;
|
||||
import com.red.circle.tool.core.date.TimestampUtils;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.YearMonth;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import com.red.circle.other.infra.database.mongo.service.team.team.TeamBillCycleService;
|
||||
import com.red.circle.other.infra.database.mongo.service.team.team.TeamProfileService;
|
||||
import com.red.circle.other.infra.utils.ZonedDateTimeUtils;
|
||||
import com.red.circle.other.inner.model.cmd.team.TeamBillCmd;
|
||||
import com.red.circle.tool.core.date.TimestampUtils;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@ -39,25 +34,21 @@ public class TeamBillTask {
|
||||
private final TeamSalaryMqMessage otherMqMessage;
|
||||
private final TeamProfileService teamProfileService;
|
||||
private final TeamBillCycleService teamBillCycleService;
|
||||
private final GiftGiveRunningWaterService giftGiveRunningWaterService;
|
||||
|
||||
/**
|
||||
* 每月1号和16号 0点执行半月结算
|
||||
*/
|
||||
@Scheduled(cron = "0 0 0 1,16 * ?", zone = "Asia/Riyadh")
|
||||
@TaskCacheLock(key = "PROCESS_TEAM_BILL_HALF_MONTH", expireSecond = 86400)
|
||||
public void processTeamHalfMonthBill() {
|
||||
long startTime = System.currentTimeMillis();
|
||||
log.info("exec process_team_half_month_bill start");
|
||||
|
||||
// 判断是上半月还是下半月
|
||||
LocalDate now = LocalDate.now(ZoneId.of("Asia/Riyadh"));
|
||||
String billPeriod = now.getDayOfMonth() == 1 ? "first_half" : "second_half";
|
||||
|
||||
processTeamBill();
|
||||
log.info("exec process_team_half_month_bill end with {}",
|
||||
System.currentTimeMillis() - startTime);
|
||||
}
|
||||
private final GiftGiveRunningWaterService giftGiveRunningWaterService;
|
||||
|
||||
/**
|
||||
* 每月1号0点执行整月结算,并创建新月账期.
|
||||
*/
|
||||
@Scheduled(cron = "0 0 0 1 * ?", zone = "Asia/Riyadh")
|
||||
@TaskCacheLock(key = "PROCESS_TEAM_BILL_MONTH", expireSecond = 86400)
|
||||
public void processTeamMonthBill() {
|
||||
long startTime = System.currentTimeMillis();
|
||||
log.info("exec process_team_month_bill start");
|
||||
|
||||
processTeamBill();
|
||||
log.info("exec process_team_month_bill end with {}",
|
||||
System.currentTimeMillis() - startTime);
|
||||
}
|
||||
|
||||
public void processTeamMonthBillTest() {
|
||||
processTeamBill();
|
||||
|
||||
@ -15,12 +15,11 @@ import com.red.circle.other.infra.database.mongo.service.team.TeamBillCycleUtils
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 管理员任务服务实现
|
||||
@ -55,10 +54,10 @@ public class AdminTaskServiceImpl implements AdminTaskService {
|
||||
return adminTaskConvertor.toRecordCO(record);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AdminTaskSummaryCO queryTaskSummary(AdminTaskQueryCmd cmd) {
|
||||
LocalDate cycleStartDate = cmd.getCycleStartDate() != null ? cmd.getCycleStartDate() :
|
||||
LocalDate.parse(TeamBillCycleUtils.getCalcBillBelong().toString(), DateTimeFormatter.ofPattern("yyyyMMdd"));
|
||||
@Override
|
||||
public AdminTaskSummaryCO queryTaskSummary(AdminTaskQueryCmd cmd) {
|
||||
LocalDate cycleStartDate = cmd.getCycleStartDate() != null ? cmd.getCycleStartDate() :
|
||||
TeamBillCycleUtils.getBillBelongStartDate(TeamBillCycleUtils.getCalcBillBelong());
|
||||
|
||||
List<AdminTaskRecord> records = adminTaskQueryCmdExe.execute(cmd);
|
||||
|
||||
@ -107,20 +106,20 @@ public class AdminTaskServiceImpl implements AdminTaskService {
|
||||
return summary;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTaskCompleted(Long userId, String taskType, LocalDate cycleStartDate) {
|
||||
LocalDate queryDate = cycleStartDate != null ? cycleStartDate :
|
||||
LocalDate.parse(TeamBillCycleUtils.getCalcBillBelong().toString(), DateTimeFormatter.ofPattern("yyyyMMdd"));
|
||||
AdminTaskRecord record = adminTaskQueryCmdExe.findByUserIdAndTaskTypeAndCycle(userId, taskType, queryDate);
|
||||
return record != null && record.isCompleted();
|
||||
@Override
|
||||
public boolean isTaskCompleted(Long userId, String taskType, LocalDate cycleStartDate) {
|
||||
LocalDate queryDate = cycleStartDate != null ? cycleStartDate :
|
||||
TeamBillCycleUtils.getBillBelongStartDate(TeamBillCycleUtils.getCalcBillBelong());
|
||||
AdminTaskRecord record = adminTaskQueryCmdExe.findByUserIdAndTaskTypeAndCycle(userId, taskType, queryDate);
|
||||
return record != null && record.isCompleted();
|
||||
}
|
||||
|
||||
/**
|
||||
* 确保任务已初始化(兜底逻辑)
|
||||
*/
|
||||
private void ensureTasksInitialized(Long userId, LocalDate cycleStartDate) {
|
||||
LocalDate queryDate = cycleStartDate != null ? cycleStartDate :
|
||||
LocalDate.parse(TeamBillCycleUtils.getCalcBillBelong().toString(), DateTimeFormatter.ofPattern("yyyyMMdd"));
|
||||
private void ensureTasksInitialized(Long userId, LocalDate cycleStartDate) {
|
||||
LocalDate queryDate = cycleStartDate != null ? cycleStartDate :
|
||||
TeamBillCycleUtils.getBillBelongStartDate(TeamBillCycleUtils.getCalcBillBelong());
|
||||
|
||||
// 检查是否存在任务记录
|
||||
boolean exists = adminTaskGateway.existsByUserIdAndCycle(userId, queryDate);
|
||||
@ -142,18 +141,10 @@ public class AdminTaskServiceImpl implements AdminTaskService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算周期结束日期
|
||||
*/
|
||||
private LocalDate calculateCycleEndDate(LocalDate cycleStartDate) {
|
||||
int dayOfMonth = cycleStartDate.getDayOfMonth();
|
||||
|
||||
if (dayOfMonth == 1) {
|
||||
// 1号开始,结束于15号
|
||||
return LocalDate.of(cycleStartDate.getYear(), cycleStartDate.getMonth(), 15);
|
||||
} else {
|
||||
// 16号开始,结束于月末
|
||||
return cycleStartDate.withDayOfMonth(cycleStartDate.lengthOfMonth());
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 计算周期结束日期
|
||||
*/
|
||||
private LocalDate calculateCycleEndDate(LocalDate cycleStartDate) {
|
||||
return cycleStartDate.withDayOfMonth(cycleStartDate.lengthOfMonth());
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -12,18 +12,20 @@ import com.red.circle.tool.core.date.AgeUtils;
|
||||
import com.red.circle.tool.core.text.StringUtils;
|
||||
import com.red.circle.other.inner.model.cmd.user.UserProfileCmd;
|
||||
import com.red.circle.other.inner.model.cmd.user.account.CreateAccountCmd;
|
||||
import java.util.Objects;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
import java.util.Objects;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 检查注册资料信息,核对矫正等.
|
||||
*
|
||||
* @author pengliang on 2020/11/5
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class CheckRegisterUserProfileProcess {
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class CheckRegisterUserProfileProcess {
|
||||
|
||||
private final OssServiceClient ossServiceClient;
|
||||
private final SysCountryCodeService sysCountryCodeService;
|
||||
@ -32,7 +34,7 @@ public class CheckRegisterUserProfileProcess {
|
||||
UserProfileCmd profileCmd = cmd.getProfile();
|
||||
setUserCountryInfo(profileCmd);
|
||||
calculationAge(profileCmd);
|
||||
completeAvatarUrl(profileCmd);
|
||||
completeAvatarUrl(cmd, profileCmd);
|
||||
checkGenerateDefaultNickname(profileCmd);
|
||||
}
|
||||
|
||||
@ -46,14 +48,29 @@ public class CheckRegisterUserProfileProcess {
|
||||
cmd.setUserNickname(KeywordConstant.filter(cmd.getUserNickname()));
|
||||
}
|
||||
|
||||
private void completeAvatarUrl(UserProfileCmd cmd) {
|
||||
if (StringUtils.isNotBlank(cmd.getUserAvatar())) {
|
||||
cmd.setUserAvatar(ResponseAssert.requiredSuccess(
|
||||
ossServiceClient.processImgSaveAsCompressZoom(cmd.getUserAvatar(),
|
||||
ImageSizeConst.COVER_HEIGHT)
|
||||
));
|
||||
}
|
||||
}
|
||||
private void completeAvatarUrl(CreateAccountCmd accountCmd, UserProfileCmd cmd) {
|
||||
if (StringUtils.isNotBlank(cmd.getUserAvatar())) {
|
||||
String sourceAvatar = cmd.getUserAvatar();
|
||||
log.info(
|
||||
"Register avatar process start, userId={}, type={}, openId={}, nickname={}, countryCode={}, source={}",
|
||||
accountCmd.getReqUserId(), accountCmd.getType(), accountCmd.getOpenId(),
|
||||
cmd.getUserNickname(), cmd.getCountryCode(), sourceAvatar);
|
||||
try {
|
||||
String processedAvatar = ResponseAssert.requiredSuccess(
|
||||
ossServiceClient.processImgSaveAsCompressZoom(sourceAvatar, ImageSizeConst.COVER_HEIGHT)
|
||||
);
|
||||
cmd.setUserAvatar(processedAvatar);
|
||||
log.info("Register avatar process success, userId={}, source={}, target={}",
|
||||
accountCmd.getReqUserId(), sourceAvatar, processedAvatar);
|
||||
} catch (RuntimeException ex) {
|
||||
log.error(
|
||||
"Register avatar process failed, userId={}, type={}, openId={}, nickname={}, countryCode={}, source={}",
|
||||
accountCmd.getReqUserId(), accountCmd.getType(), accountCmd.getOpenId(),
|
||||
cmd.getUserNickname(), cmd.getCountryCode(), sourceAvatar, ex);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 计算年龄
|
||||
private void calculationAge(UserProfileCmd cmd) {
|
||||
|
||||
@ -154,9 +154,17 @@ public class UserAccountCommon {
|
||||
return inviteUserInfo;
|
||||
}
|
||||
|
||||
private String requestUserSign(Long userId) {
|
||||
return ResponseAssert.requiredSuccess(imAccountClient.createUserSig(userId));
|
||||
}
|
||||
private String requestUserSign(Long userId) {
|
||||
log.info("Register userSig create start, userId={}", userId);
|
||||
try {
|
||||
String userSig = ResponseAssert.requiredSuccess(imAccountClient.createUserSig(userId));
|
||||
log.info("Register userSig create success, userId={}", userId);
|
||||
return userSig;
|
||||
} catch (RuntimeException ex) {
|
||||
log.error("Register userSig create failed, userId={}", userId, ex);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
private BaseInfo toBaseInfo(CreateAccountCmd cmd) {
|
||||
BaseInfo baseInfo = userProfileInfraConvertor.toBaseInfo(cmd.getProfile());
|
||||
|
||||
@ -6,6 +6,7 @@ import com.red.circle.tool.core.collection.CollectionUtils;
|
||||
import com.red.circle.tool.core.date.TimestampUtils;
|
||||
import com.red.circle.tool.core.date.ZonedDateTimeAsiaRiyadhUtils;
|
||||
import com.red.circle.tool.core.sequence.IdWorkerUtils;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
@ -64,6 +65,10 @@ public class ActivityAgentAnchorCountServiceImpl implements ActivityAgentAnchorC
|
||||
@Override
|
||||
public void initZero(Long acceptUserId, Long teamId) {
|
||||
|
||||
if (!isMonthEndResetDay()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 主播月收礼,永久活动
|
||||
Long quantity1 = getCountQuantity(acceptUserId, "ACTIVE_ANCHOR_MONTH_TARGET_REWARD",
|
||||
getThisMonthDate());
|
||||
@ -87,6 +92,10 @@ public class ActivityAgentAnchorCountServiceImpl implements ActivityAgentAnchorC
|
||||
@Override
|
||||
public void initZeroTeam(Long teamId) {
|
||||
|
||||
if (!isMonthEndResetDay()) {
|
||||
return;
|
||||
}
|
||||
|
||||
mongoTemplate.updateFirst(Query.query(Criteria.where("businessId").is(teamId)
|
||||
.and("group").is("ACTIVE_AGENT_MONTH_TARGET_REWARD")
|
||||
.and("dateNumber").is(getThisMonthDate())),
|
||||
@ -97,6 +106,10 @@ public class ActivityAgentAnchorCountServiceImpl implements ActivityAgentAnchorC
|
||||
@Override
|
||||
public void initZeroMember(Set<Long> acceptUserIds) {
|
||||
|
||||
if (!isMonthEndResetDay()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (CollectionUtils.isEmpty(acceptUserIds)) {
|
||||
return;
|
||||
}
|
||||
@ -121,4 +134,10 @@ public class ActivityAgentAnchorCountServiceImpl implements ActivityAgentAnchorC
|
||||
return ZonedDateTimeAsiaRiyadhUtils.nowDateToInt();
|
||||
}
|
||||
|
||||
protected boolean isMonthEndResetDay() {
|
||||
ZonedDateTime now = ZonedDateTimeAsiaRiyadhUtils.now();
|
||||
return Objects.equals(ZonedDateTimeAsiaRiyadhUtils.nowDateToInt(),
|
||||
ZonedDateTimeAsiaRiyadhUtils.getLastDayOfMonthToInt(now));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -665,31 +665,16 @@ public final class TeamBillCycleUtils {
|
||||
}
|
||||
|
||||
|
||||
public static Integer getCalcBillBelong() {
|
||||
LocalDate now = ZonedDateTimeAsiaRiyadhUtils.now().toLocalDate();
|
||||
return calculateCurrentBillBelong(now);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算当前账期的 billBelong
|
||||
* 规则:
|
||||
* - 1-15号:当前账期是本月1号(20251001)
|
||||
* - 16-月末:当前账期是本月16号(20251016)
|
||||
*/
|
||||
private static Integer calculateCurrentBillBelong(LocalDate now) {
|
||||
int day = now.getDayOfMonth();
|
||||
LocalDate billDate;
|
||||
|
||||
if (day < 16) {
|
||||
// 当前是上半月,账期开始日期是本月1号
|
||||
billDate = now.withDayOfMonth(1);
|
||||
} else {
|
||||
// 当前是下半月,账期开始日期是本月16号
|
||||
billDate = now.withDayOfMonth(16);
|
||||
}
|
||||
|
||||
return Integer.parseInt(billDate.format(DateTimeFormatter.ofPattern("yyyyMMdd")));
|
||||
}
|
||||
public static Integer getCalcBillBelong() {
|
||||
return getMonthlyBillBelong();
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算当前整月账期的 billBelong,返回格式:202510.
|
||||
*/
|
||||
private static Integer calculateCurrentBillBelong(LocalDate now) {
|
||||
return Integer.parseInt(now.format(DateTimeFormatter.ofPattern("yyyyMM")));
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 billBelong 为日期范围.
|
||||
@ -758,10 +743,18 @@ public final class TeamBillCycleUtils {
|
||||
* 获取月度账期(用于批次模式)
|
||||
* 返回格式:202511
|
||||
*/
|
||||
public static Integer getMonthlyBillBelong() {
|
||||
LocalDate now = ZonedDateTimeAsiaRiyadhUtils.now().toLocalDate();
|
||||
return Integer.parseInt(now.format(DateTimeFormatter.ofPattern("yyyyMM")));
|
||||
}
|
||||
public static Integer getMonthlyBillBelong() {
|
||||
LocalDate now = ZonedDateTimeAsiaRiyadhUtils.now().toLocalDate();
|
||||
return calculateCurrentBillBelong(now);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取上一个整月账期,返回格式:202510.
|
||||
*/
|
||||
public static Integer getPreviousMonthlyBillBelong(Integer billBelong) {
|
||||
YearMonth yearMonth = parseYearMonth(billBelong);
|
||||
return Integer.parseInt(yearMonth.minusMonths(1).format(DateTimeFormatter.ofPattern("yyyyMM")));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前结算批次
|
||||
@ -775,11 +768,9 @@ public final class TeamBillCycleUtils {
|
||||
/**
|
||||
* 判断是否启用批次模式(通过区域配置)
|
||||
*/
|
||||
public static Boolean isBatchModeEnabled(String region) {
|
||||
// 从配置中读取,或者硬编码
|
||||
// 例如:return "SA".equals(region); // 沙特地区启用批次模式
|
||||
return true;
|
||||
}
|
||||
public static Boolean isBatchModeEnabled(String region) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 billBelong 提取月份(兼容新旧格式)
|
||||
@ -833,30 +824,56 @@ public final class TeamBillCycleUtils {
|
||||
return String.format("%d年%d月(%s)", year, month, batchDesc);
|
||||
}
|
||||
|
||||
public static LocalDateTime getBillBelongStartTime(Integer billBelong) {
|
||||
// billBelong 格式:20251101 或 20251116
|
||||
int year = billBelong / 10000; // 2025
|
||||
int month = (billBelong / 100) % 100; // 11
|
||||
int day = billBelong % 100; // 01 或 16
|
||||
|
||||
return LocalDateTime.of(year, month, day, 0, 0, 0);
|
||||
}
|
||||
|
||||
public static LocalDateTime getBillBelongEndTime(Integer billBelong) {
|
||||
// billBelong 格式:20251101 或 20251116
|
||||
int year = billBelong / 10000; // 2025
|
||||
int month = (billBelong / 100) % 100; // 11
|
||||
int day = billBelong % 100; // 01 或 16
|
||||
|
||||
LocalDateTime startTime = LocalDateTime.of(year, month, day, 0, 0, 0);
|
||||
|
||||
if (day == 1) {
|
||||
// 如果是1号,结束时间是当月16号0点
|
||||
return LocalDateTime.of(year, month, 16, 0, 0, 0);
|
||||
} else {
|
||||
// 如果是16号,结束时间是下个月1号0点
|
||||
return startTime.plusMonths(1).withDayOfMonth(1);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public static LocalDate getBillBelongStartDate(Integer billBelong) {
|
||||
String billStr = String.valueOf(billBelong);
|
||||
if (billStr.length() == 6) {
|
||||
return parseYearMonth(billBelong).atDay(1);
|
||||
}
|
||||
if (billStr.length() == 8) {
|
||||
return LocalDate.of(
|
||||
Integer.parseInt(billStr.substring(0, 4)),
|
||||
Integer.parseInt(billStr.substring(4, 6)),
|
||||
Integer.parseInt(billStr.substring(6, 8)));
|
||||
}
|
||||
throw new IllegalArgumentException("Unsupported billBelong: " + billBelong);
|
||||
}
|
||||
|
||||
public static LocalDate getBillBelongEndDate(Integer billBelong) {
|
||||
String billStr = String.valueOf(billBelong);
|
||||
if (billStr.length() == 6) {
|
||||
return parseYearMonth(billBelong).atEndOfMonth();
|
||||
}
|
||||
if (billStr.length() == 8) {
|
||||
LocalDate startDate = getBillBelongStartDate(billBelong);
|
||||
return startDate.getDayOfMonth() == 1
|
||||
? startDate.withDayOfMonth(15)
|
||||
: startDate.withDayOfMonth(startDate.lengthOfMonth());
|
||||
}
|
||||
throw new IllegalArgumentException("Unsupported billBelong: " + billBelong);
|
||||
}
|
||||
|
||||
public static LocalDateTime getBillBelongStartTime(Integer billBelong) {
|
||||
return getBillBelongStartDate(billBelong).atStartOfDay();
|
||||
}
|
||||
|
||||
public static LocalDateTime getBillBelongEndTime(Integer billBelong) {
|
||||
return getBillBelongEndDate(billBelong).plusDays(1).atStartOfDay();
|
||||
}
|
||||
|
||||
private static YearMonth parseYearMonth(Integer billBelong) {
|
||||
if (billBelong == null) {
|
||||
throw new IllegalArgumentException("billBelong is null");
|
||||
}
|
||||
String billStr = String.valueOf(billBelong);
|
||||
if (billStr.length() == 8) {
|
||||
billStr = billStr.substring(0, 6);
|
||||
}
|
||||
if (billStr.length() != 6) {
|
||||
throw new IllegalArgumentException("Unsupported billBelong: " + billBelong);
|
||||
}
|
||||
return YearMonth.of(
|
||||
Integer.parseInt(billStr.substring(0, 4)),
|
||||
Integer.parseInt(billStr.substring(4, 6)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -21,13 +21,15 @@ import com.red.circle.tool.core.date.ZonedDateTimeAsiaRiyadhUtils;
|
||||
import com.red.circle.tool.core.sequence.IdWorkerUtils;
|
||||
import com.red.circle.tool.core.text.StringUtils;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@ -244,12 +246,11 @@ public class TeamBillCycleServiceImpl implements TeamBillCycleService {
|
||||
/**
|
||||
* 创建账单(兼容批次模式)
|
||||
*/
|
||||
@Override
|
||||
public void createIfAbsent(TeamBillCmd cmd) {
|
||||
// 计算 billBelong(保持原有逻辑)
|
||||
Integer billBelong = TeamBillCycleUtils.getCalcBillBelong();
|
||||
|
||||
// 检查是否已存在
|
||||
@Override
|
||||
public void createIfAbsent(TeamBillCmd cmd) {
|
||||
Integer billBelong = TeamBillCycleUtils.getCalcBillBelong();
|
||||
|
||||
// 检查是否已存在
|
||||
boolean existing = existsPrimaryBillBelong(cmd.getTeamId(), billBelong);
|
||||
if (existing) {
|
||||
return;
|
||||
@ -260,33 +261,18 @@ public class TeamBillCycleServiceImpl implements TeamBillCycleService {
|
||||
.setId(IdWorkerUtils.getId())
|
||||
.setSysOrigin(cmd.getSysOrigin())
|
||||
.setTeamId(cmd.getTeamId())
|
||||
.setRegion(cmd.getRegion())
|
||||
.setBillBelong(billBelong)
|
||||
.setBillTitle(generateBillTitle(billBelong))
|
||||
.setStatus(TeamBillCycleStatus.UNPAID)
|
||||
.setCreateTime(cmd.getOperationTime())
|
||||
.setCreateUser(cmd.getOperationBackUser());
|
||||
|
||||
Boolean batchMode = TeamBillCycleUtils.isBatchModeEnabled(cmd.getRegion());
|
||||
Integer currentBatch = TeamBillCycleUtils.getCurrentSettlementBatch();
|
||||
Integer monthlyBillBelong = TeamBillCycleUtils.getMonthlyBillBelong();
|
||||
// ===== 新增:如果启用批次模式,添加批次信息 =====
|
||||
if (batchMode) {
|
||||
bill.setBatchMode(true);
|
||||
bill.setSettlementBatch(currentBatch);
|
||||
bill.setMonthlyBillBelong(monthlyBillBelong);
|
||||
|
||||
// 覆盖账单标题(增加批次说明)
|
||||
String batchTitle = TeamBillCycleUtils.generateBatchTitle(
|
||||
cmd.getMonthlyBillBelong(),
|
||||
cmd.getSettlementBatch()
|
||||
);
|
||||
bill.setBillTitle(batchTitle);
|
||||
}
|
||||
|
||||
mongoTemplate.save(bill);
|
||||
log.info("创建账单成功:{}", bill);
|
||||
}
|
||||
.setRegion(cmd.getRegion())
|
||||
.setBillBelong(billBelong)
|
||||
.setBillTitle(generateBillTitle(billBelong))
|
||||
.setBatchMode(false)
|
||||
.setMonthlyBillBelong(billBelong)
|
||||
.setStatus(TeamBillCycleStatus.UNPAID)
|
||||
.setCreateTime(cmd.getOperationTime())
|
||||
.setCreateUser(cmd.getOperationBackUser());
|
||||
|
||||
mongoTemplate.save(bill);
|
||||
log.info("创建账单成功:{}", bill);
|
||||
}
|
||||
|
||||
private String generateBillTitle(Integer billBelong) {
|
||||
String[] dateRange = TeamBillCycleUtils.parseBillBelongToDateRange(billBelong);
|
||||
@ -375,12 +361,22 @@ public class TeamBillCycleServiceImpl implements TeamBillCycleService {
|
||||
TeamBillCycle.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TeamBillCycle getTeamUnpaidBill(Long teamId) {
|
||||
return mongoTemplate.findOne(Query.query(Criteria.where("teamId").is(teamId)
|
||||
.and("status").is(TeamBillCycleStatus.UNPAID)),
|
||||
TeamBillCycle.class);
|
||||
}
|
||||
@Override
|
||||
public TeamBillCycle getTeamUnpaidBill(Long teamId) {
|
||||
TeamBillCycle currentMonthBill = mongoTemplate.findOne(
|
||||
Query.query(Criteria.where("teamId").is(teamId)
|
||||
.and("status").is(TeamBillCycleStatus.UNPAID)
|
||||
.and("billBelong").is(getCalcBillBelong()))
|
||||
.with(Sort.by(Sort.Order.desc("id"))),
|
||||
TeamBillCycle.class);
|
||||
if (Objects.nonNull(currentMonthBill)) {
|
||||
return currentMonthBill;
|
||||
}
|
||||
return mongoTemplate.findOne(Query.query(Criteria.where("teamId").is(teamId)
|
||||
.and("status").is(TeamBillCycleStatus.UNPAID))
|
||||
.with(Sort.by(Sort.Order.desc("id"))),
|
||||
TeamBillCycle.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getLatestBillIdByTeamId(Long teamId) {
|
||||
@ -394,11 +390,19 @@ public class TeamBillCycleServiceImpl implements TeamBillCycleService {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public TeamBillCycle getLatestByTeamId(Long teamId) {
|
||||
|
||||
List<TeamBillCycle> cycleList = listLatestBill(teamId, 1);
|
||||
if (CollectionUtils.isEmpty(cycleList)) {
|
||||
@Override
|
||||
public TeamBillCycle getLatestByTeamId(Long teamId) {
|
||||
TeamBillCycle currentMonthBill = mongoTemplate.findOne(
|
||||
Query.query(Criteria.where("teamId").is(teamId)
|
||||
.and("billBelong").is(getCalcBillBelong()))
|
||||
.with(Sort.by(Sort.Order.desc("id"))),
|
||||
TeamBillCycle.class);
|
||||
if (Objects.nonNull(currentMonthBill)) {
|
||||
return currentMonthBill;
|
||||
}
|
||||
|
||||
List<TeamBillCycle> cycleList = listLatestBill(teamId, 1);
|
||||
if (CollectionUtils.isEmpty(cycleList)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -435,6 +439,13 @@ public class TeamBillCycleServiceImpl implements TeamBillCycleService {
|
||||
Integer normalizedYearMonth = yearMonth > 999999
|
||||
? Integer.valueOf(yearMonth.toString().substring(0, 6))
|
||||
: yearMonth;
|
||||
TeamBillCycle monthlyBill = mongoTemplate.findOne(
|
||||
Query.query(Criteria.where("teamId").is(teamId).and("billBelong").is(normalizedYearMonth))
|
||||
.with(Sort.by(Sort.Order.desc("id"))),
|
||||
TeamBillCycle.class);
|
||||
if (Objects.nonNull(monthlyBill)) {
|
||||
return monthlyBill;
|
||||
}
|
||||
return mongoTemplate.findOne(
|
||||
Query.query(getBillBelongCriteria(normalizedYearMonth).and("teamId").is(teamId))
|
||||
.with(Sort.by(Sort.Order.desc("billBelong"), Sort.Order.desc("id"))),
|
||||
@ -490,14 +501,38 @@ public class TeamBillCycleServiceImpl implements TeamBillCycleService {
|
||||
return mongoTemplate.find(Query.query(new Criteria()), TeamBillCycle.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TeamBillCycle> listByGeBillBelong(Long teamId, Integer billBelong, Integer size) {
|
||||
Criteria criteria = Criteria.where("teamId").is(teamId).and("billBelong").gte(billBelong);
|
||||
return mongoTemplate.find(Query.query(criteria)
|
||||
.with(Sort.by(Sort.Order.desc("id")))
|
||||
.limit(size),
|
||||
TeamBillCycle.class);
|
||||
}
|
||||
@Override
|
||||
public List<TeamBillCycle> listByGeBillBelong(Long teamId, Integer billBelong, Integer size) {
|
||||
int limit = Objects.nonNull(size) ? size : PageConstant.DEFAULT_LIMIT_SIZE;
|
||||
Criteria criteria = Criteria.where("teamId").is(teamId).and("billBelong").gte(billBelong);
|
||||
List<TeamBillCycle> billCycles = mongoTemplate.find(Query.query(criteria)
|
||||
.with(Sort.by(Sort.Order.desc("id")))
|
||||
.limit(limit * 3),
|
||||
TeamBillCycle.class);
|
||||
if (CollectionUtils.isEmpty(billCycles)) {
|
||||
return billCycles;
|
||||
}
|
||||
|
||||
Map<Integer, TeamBillCycle> monthBillMap = new LinkedHashMap<>();
|
||||
billCycles.stream()
|
||||
.sorted(Comparator
|
||||
.comparing((TeamBillCycle bill) -> TeamBillCycleUtils.extractMonth(bill.getBillBelong()))
|
||||
.reversed()
|
||||
.thenComparing(this::monthlyBillPriority)
|
||||
.thenComparing(TeamBillCycle::getId, Comparator.reverseOrder()))
|
||||
.forEach(bill -> monthBillMap.putIfAbsent(
|
||||
TeamBillCycleUtils.extractMonth(bill.getBillBelong()), bill));
|
||||
|
||||
return new ArrayList<>(monthBillMap.values()).stream()
|
||||
.limit(limit)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private int monthlyBillPriority(TeamBillCycle bill) {
|
||||
return Objects.equals(bill.getBillBelong(), TeamBillCycleUtils.extractMonth(bill.getBillBelong()))
|
||||
? 0
|
||||
: 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TeamBillCycle> listLatestBill(Long teamId, Integer size) {
|
||||
@ -579,62 +614,39 @@ public class TeamBillCycleServiceImpl implements TeamBillCycleService {
|
||||
|
||||
|
||||
@Override
|
||||
public void billStatusPayOut() {
|
||||
// 计算当前账期的 billBelong
|
||||
Integer currentBillBelong = getCalcBillBelong();
|
||||
Integer billBelong = calculateSettlementBillBelong(currentBillBelong);
|
||||
|
||||
// 将小于当前账期的 UNPAID 状态改为 PAY_OUT
|
||||
mongoTemplate.updateMulti(
|
||||
Query.query(
|
||||
Criteria.where("billBelong").lte(billBelong)
|
||||
.and("status").is(TeamBillCycleStatus.UNPAID)),
|
||||
new Update()
|
||||
public void billStatusPayOut() {
|
||||
// 计算当前账期的 billBelong
|
||||
Integer currentBillBelong = getCalcBillBelong();
|
||||
Integer billBelong = TeamBillCycleUtils.getPreviousMonthlyBillBelong(currentBillBelong);
|
||||
|
||||
// 只出整月账单,历史半月账单不再自动进入结算,避免重复结算。
|
||||
mongoTemplate.updateMulti(
|
||||
Query.query(
|
||||
payableMonthlyBillBelongCriteria(billBelong)
|
||||
.and("status").is(TeamBillCycleStatus.UNPAID)),
|
||||
new Update()
|
||||
.set("updateTime", TimestampUtils.now())
|
||||
.set("status", TeamBillCycleStatus.PAY_OUT),
|
||||
TeamBillCycle.class);
|
||||
}
|
||||
|
||||
public void billStatusPayOutTest() {
|
||||
// 计算当前账期的 billBelong
|
||||
Integer currentBillBelong = 20251201;
|
||||
Integer billBelong = calculateSettlementBillBelong(currentBillBelong);
|
||||
|
||||
// 将小于当前账期的 UNPAID 状态改为 PAY_OUT
|
||||
List<TeamBillCycle> teamBillCycles = mongoTemplate.find(
|
||||
Query.query(
|
||||
Criteria.where("billBelong").lte(billBelong)
|
||||
.and("status").is(TeamBillCycleStatus.UNPAID)),
|
||||
TeamBillCycle.class);
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 计算需要结算的账期
|
||||
* 规则:
|
||||
* - 如果当前账期是本月1号(如20250101),则结算上个月16号的账期(如20241216)
|
||||
* - 如果当前账期是本月16号(如20250116),则结算本月1号的账期(如20250101)
|
||||
*/
|
||||
private Integer calculateSettlementBillBelong(Integer currentBillBelong) {
|
||||
String billStr = String.valueOf(currentBillBelong);
|
||||
int year = Integer.parseInt(billStr.substring(0, 4));
|
||||
int month = Integer.parseInt(billStr.substring(4, 6));
|
||||
int day = Integer.parseInt(billStr.substring(6, 8));
|
||||
|
||||
LocalDate settlementDate;
|
||||
|
||||
if (day == 1) {
|
||||
// 当前是本月1号,结算上个月16号
|
||||
settlementDate = LocalDate.of(year, month, 1).minusMonths(1).withDayOfMonth(16);
|
||||
} else {
|
||||
// 当前是本月16号,结算本月1号
|
||||
settlementDate = LocalDate.of(year, month, 1);
|
||||
}
|
||||
|
||||
return Integer.parseInt(settlementDate.format(DateTimeFormatter.ofPattern("yyyyMMdd")));
|
||||
}
|
||||
public void billStatusPayOutTest() {
|
||||
// 计算当前账期的 billBelong
|
||||
Integer currentBillBelong = 202512;
|
||||
Integer billBelong = TeamBillCycleUtils.getPreviousMonthlyBillBelong(currentBillBelong);
|
||||
|
||||
// 将小于当前账期的 UNPAID 状态改为 PAY_OUT
|
||||
List<TeamBillCycle> teamBillCycles = mongoTemplate.find(
|
||||
Query.query(
|
||||
payableMonthlyBillBelongCriteria(billBelong)
|
||||
.and("status").is(TeamBillCycleStatus.UNPAID)),
|
||||
TeamBillCycle.class);
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
private Criteria payableMonthlyBillBelongCriteria(Integer billBelong) {
|
||||
return Criteria.where("billBelong").gte(100000).lte(billBelong);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scanPayOut(Consumer<List<TeamBillCycle>> execute, Integer limit) {
|
||||
|
||||
@ -15,16 +15,18 @@ import com.red.circle.tool.core.collection.CollectionUtils;
|
||||
import com.red.circle.tool.core.date.TimestampUtils;
|
||||
import com.red.circle.tool.core.date.ZonedDateTimeAsiaRiyadhUtils;
|
||||
import com.red.circle.tool.core.date.ZonedId;
|
||||
import com.red.circle.tool.core.sequence.IdWorkerUtils;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.temporal.TemporalAdjusters;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import com.red.circle.tool.core.sequence.IdWorkerUtils;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.Timestamp;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.temporal.TemporalAdjusters;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.domain.Sort.Order;
|
||||
@ -656,15 +658,20 @@ public class TeamMemberTargetServiceImpl implements TeamMemberTargetService {
|
||||
return statistics;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TeamMemberTarget> listByMonthlyBillBelong(Long teamId, Integer monthlyBillBelong) {
|
||||
long prefix = monthlyBillBelong.longValue();
|
||||
long min = prefix * 100L + 1; // 202511 → 20251101
|
||||
long max = (prefix + 1) * 100L - 1; // 202511 → 20251199
|
||||
|
||||
Criteria criteria = Criteria.where("teamId").is(teamId)
|
||||
// .and("history").is(Boolean.FALSE)
|
||||
.and("billBelong").gte(min).lte(max);
|
||||
@Override
|
||||
public List<TeamMemberTarget> listByMonthlyBillBelong(Long teamId, Integer monthlyBillBelong) {
|
||||
long prefix = monthlyBillBelong.longValue();
|
||||
long min = prefix * 100L + 1; // 202511 → 20251101
|
||||
long max = (prefix + 1) * 100L - 1; // 202511 → 20251199
|
||||
|
||||
Criteria criteria = new Criteria().andOperator(
|
||||
Criteria.where("teamId").is(teamId),
|
||||
Criteria.where("history").is(Boolean.FALSE),
|
||||
new Criteria().orOperator(
|
||||
Criteria.where("billBelong").is(monthlyBillBelong),
|
||||
Criteria.where("billBelong").gte(min).lte(max)
|
||||
)
|
||||
);
|
||||
|
||||
return mongoTemplate.find(Query.query(criteria)
|
||||
.with(Sort.by(Order.asc("billBelong"))),
|
||||
@ -672,16 +679,21 @@ public class TeamMemberTargetServiceImpl implements TeamMemberTargetService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TeamMemberTarget> listMemberMonthlyTarget(Long teamId, Long userId, Integer monthlyBillBelong) {
|
||||
long prefix = monthlyBillBelong.longValue();
|
||||
long min = prefix * 100L + 1; // 202511 → 20251101
|
||||
long max = (prefix + 1) * 100L - 1; // 202511 → 20251199
|
||||
|
||||
// 查询指定用户在指定月份的所有目标记录
|
||||
Criteria criteria = Criteria.where("teamId").is(teamId)
|
||||
.and("userId").is(userId)
|
||||
// .and("history").is(Boolean.FALSE)
|
||||
.and("billBelong").gte(min).lte(max);
|
||||
public List<TeamMemberTarget> listMemberMonthlyTarget(Long teamId, Long userId, Integer monthlyBillBelong) {
|
||||
long prefix = monthlyBillBelong.longValue();
|
||||
long min = prefix * 100L + 1; // 202511 → 20251101
|
||||
long max = (prefix + 1) * 100L - 1; // 202511 → 20251199
|
||||
|
||||
// 查询指定用户在指定月份的所有目标记录
|
||||
Criteria criteria = new Criteria().andOperator(
|
||||
Criteria.where("teamId").is(teamId),
|
||||
Criteria.where("userId").is(userId),
|
||||
Criteria.where("history").is(Boolean.FALSE),
|
||||
new Criteria().orOperator(
|
||||
Criteria.where("billBelong").is(monthlyBillBelong),
|
||||
Criteria.where("billBelong").gte(min).lte(max)
|
||||
)
|
||||
);
|
||||
|
||||
return mongoTemplate.find(Query.query(criteria)
|
||||
.with(Sort.by(Order.asc("billBelong"))),
|
||||
@ -689,13 +701,20 @@ public class TeamMemberTargetServiceImpl implements TeamMemberTargetService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public TeamMemberTarget aggregateMonthlyTarget(Long teamId, Long userId, Integer monthlyBillBelong) {
|
||||
// 1. 查询该月的所有记录(可能1条或2条)
|
||||
List<TeamMemberTarget> monthlyTargets = listMemberMonthlyTarget(teamId, userId, monthlyBillBelong);
|
||||
|
||||
if (CollectionUtils.isEmpty(monthlyTargets)) {
|
||||
return null;
|
||||
}
|
||||
public TeamMemberTarget aggregateMonthlyTarget(Long teamId, Long userId, Integer monthlyBillBelong) {
|
||||
// 1. 查询该月的所有记录(可能1条或2条)
|
||||
List<TeamMemberTarget> monthlyTargets = listMemberMonthlyTarget(teamId, userId, monthlyBillBelong);
|
||||
|
||||
if (CollectionUtils.isEmpty(monthlyTargets)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<TeamMemberTarget> exactMonthlyTargets = monthlyTargets.stream()
|
||||
.filter(target -> Objects.equals(target.getBillBelong(), monthlyBillBelong))
|
||||
.collect(Collectors.toList());
|
||||
if (exactMonthlyTargets.size() == monthlyTargets.size()) {
|
||||
return exactMonthlyTargets.get(exactMonthlyTargets.size() - 1);
|
||||
}
|
||||
|
||||
// 2. 如果只有一条记录,直接返回
|
||||
if (monthlyTargets.size() == 1) {
|
||||
@ -720,11 +739,7 @@ public class TeamMemberTargetServiceImpl implements TeamMemberTargetService {
|
||||
aggregated.setUpdateTime(monthlyTargets.get(monthlyTargets.size() - 1).getUpdateTime());
|
||||
|
||||
// 4. 合并每日目标数据(dailyTargets)
|
||||
List<TeamMemberTargetIndex> allDailyTargets = monthlyTargets.stream()
|
||||
.flatMap(target -> target.getDailyTargets().stream())
|
||||
.sorted((d1, d2) -> d1.getDateNumber().compareTo(d2.getDateNumber()))
|
||||
.collect(Collectors.toList());
|
||||
aggregated.setDailyTargets(allDailyTargets);
|
||||
aggregated.setDailyTargets(mergeDailyTargets(monthlyTargets));
|
||||
|
||||
// 5. 聚合结算结果(如果有多条,取总和)
|
||||
List<TeamMemberTargetSettlementResult> settlementResults = monthlyTargets.stream()
|
||||
@ -829,15 +844,75 @@ public class TeamMemberTargetServiceImpl implements TeamMemberTargetService {
|
||||
* 生成月度账单标题
|
||||
* 202511 -> "2025年11月"
|
||||
*/
|
||||
private String generateMonthlyBillTitle(Integer monthlyBillBelong) {
|
||||
String billStr = String.valueOf(monthlyBillBelong);
|
||||
int year = Integer.parseInt(billStr.substring(0, 4));
|
||||
int month = Integer.parseInt(billStr.substring(4, 6));
|
||||
return String.format("%d年%d月", year, month);
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较状态优先级
|
||||
private String generateMonthlyBillTitle(Integer monthlyBillBelong) {
|
||||
String billStr = String.valueOf(monthlyBillBelong);
|
||||
int year = Integer.parseInt(billStr.substring(0, 4));
|
||||
int month = Integer.parseInt(billStr.substring(4, 6));
|
||||
return String.format("%d年%d月", year, month);
|
||||
}
|
||||
|
||||
private List<TeamMemberTargetIndex> mergeDailyTargets(List<TeamMemberTarget> monthlyTargets) {
|
||||
Map<Integer, TeamMemberTargetIndex> dailyTargetMap = new TreeMap<>();
|
||||
monthlyTargets.stream()
|
||||
.map(TeamMemberTarget::getDailyTargets)
|
||||
.filter(CollectionUtils::isNotEmpty)
|
||||
.flatMap(List::stream)
|
||||
.filter(Objects::nonNull)
|
||||
.filter(dailyTarget -> Objects.nonNull(dailyTarget.getDateNumber()))
|
||||
.forEach(dailyTarget -> {
|
||||
TeamMemberTargetIndex target = dailyTargetMap.computeIfAbsent(
|
||||
dailyTarget.getDateNumber(),
|
||||
dateNumber -> new TeamMemberTargetIndex()
|
||||
.setDateNumber(dateNumber)
|
||||
.setCreateTime(dailyTarget.getCreateTime())
|
||||
.setUpdateTime(dailyTarget.getUpdateTime()));
|
||||
|
||||
target.setOwnOnlineTime(sumLong(target.getOwnOnlineTime(), dailyTarget.getOwnOnlineTime()));
|
||||
target.setOtherOnlineTime(sumLong(target.getOtherOnlineTime(), dailyTarget.getOtherOnlineTime()));
|
||||
target.setAcceptGiftValue(sumLong(target.getAcceptGiftValue(), dailyTarget.getAcceptGiftValue()));
|
||||
target.setGiveGiftValue(sumLong(target.getGiveGiftValue(), dailyTarget.getGiveGiftValue()));
|
||||
target.setRoomValue(sumLong(target.getRoomValue(), dailyTarget.getRoomValue()));
|
||||
target.setOverFlowGiftValue(sumLong(target.getOverFlowGiftValue(), dailyTarget.getOverFlowGiftValue()));
|
||||
target.setEffectiveDay(maxInteger(target.getEffectiveDay(), dailyTarget.getEffectiveDay()));
|
||||
target.setCreateTime(minTimestamp(target.getCreateTime(), dailyTarget.getCreateTime()));
|
||||
target.setUpdateTime(maxTimestamp(target.getUpdateTime(), dailyTarget.getUpdateTime()));
|
||||
});
|
||||
return List.copyOf(dailyTargetMap.values());
|
||||
}
|
||||
|
||||
private Long sumLong(Long left, Long right) {
|
||||
return (Objects.nonNull(left) ? left : 0L) + (Objects.nonNull(right) ? right : 0L);
|
||||
}
|
||||
|
||||
private Integer maxInteger(Integer left, Integer right) {
|
||||
if (Objects.isNull(left) && Objects.isNull(right)) {
|
||||
return null;
|
||||
}
|
||||
return Math.max(Objects.nonNull(left) ? left : 0, Objects.nonNull(right) ? right : 0);
|
||||
}
|
||||
|
||||
private Timestamp minTimestamp(Timestamp left, Timestamp right) {
|
||||
if (Objects.isNull(left)) {
|
||||
return right;
|
||||
}
|
||||
if (Objects.isNull(right)) {
|
||||
return left;
|
||||
}
|
||||
return left.before(right) ? left : right;
|
||||
}
|
||||
|
||||
private Timestamp maxTimestamp(Timestamp left, Timestamp right) {
|
||||
if (Objects.isNull(left)) {
|
||||
return right;
|
||||
}
|
||||
if (Objects.isNull(right)) {
|
||||
return left;
|
||||
}
|
||||
return left.after(right) ? left : right;
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较状态优先级
|
||||
* 返回值: 1 表示 s1 优先级高, -1 表示 s2 优先级高, 0 表示相等
|
||||
*/
|
||||
private int compareStatus(TeamMemberTargetStatus s1, TeamMemberTargetStatus s2) {
|
||||
|
||||
@ -1,12 +1,9 @@
|
||||
package com.red.circle.other.infra.database.rds.service.activity;
|
||||
|
||||
import com.red.circle.common.business.core.enums.SysOriginPlatformEnum;
|
||||
import com.red.circle.framework.dto.PageResult;
|
||||
import com.red.circle.framework.mybatis.service.BaseService;
|
||||
import com.red.circle.other.infra.database.rds.entity.activity.PropsActivityRuleConfig;
|
||||
import com.red.circle.other.inner.enums.activity.PropsActivityTypeEnum;
|
||||
import com.red.circle.other.inner.model.cmd.material.PropsActivityRuleConfigParamCmd;
|
||||
import com.red.circle.other.inner.model.cmd.material.PropsActivityRuleConfigQryCmd;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -106,20 +103,4 @@ public interface PropsActivityRuleConfigService extends BaseService<PropsActivit
|
||||
* 查询活动道具规则配置
|
||||
*/
|
||||
Map<Long, PropsActivityRuleConfig> mapByIds(Collection<Long> ids);
|
||||
|
||||
/**
|
||||
* 删除活动道具规则配置
|
||||
*/
|
||||
void deleteById(Long id);
|
||||
|
||||
/**
|
||||
* 查询活动道具规则配置
|
||||
*/
|
||||
PropsActivityRuleConfig getPropsActivityRuleConfig(PropsActivityRuleConfigParamCmd param);
|
||||
|
||||
/**
|
||||
* 分页-查询活动道具规则配置
|
||||
*/
|
||||
PageResult<PropsActivityRuleConfig> pagePropsActivityRuleConfig(
|
||||
PropsActivityRuleConfigQryCmd query);
|
||||
}
|
||||
|
||||
@ -2,21 +2,16 @@ package com.red.circle.other.infra.database.rds.service.activity.impl;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import com.red.circle.common.business.core.enums.SysOriginPlatformEnum;
|
||||
import com.red.circle.framework.dto.PageResult;
|
||||
import com.red.circle.framework.mybatis.constant.PageConstant;
|
||||
import com.red.circle.framework.mybatis.service.impl.BaseServiceImpl;
|
||||
import com.red.circle.other.infra.database.rds.dao.activity.PropsActivityRuleConfigDAO;
|
||||
import com.red.circle.other.infra.database.rds.entity.activity.PropsActivityRuleConfig;
|
||||
import com.red.circle.other.infra.database.rds.service.activity.PropsActivityRuleConfigService;
|
||||
import com.red.circle.other.inner.enums.activity.PropsActivityTypeEnum;
|
||||
import com.red.circle.other.inner.model.cmd.material.PropsActivityRuleConfigParamCmd;
|
||||
import com.red.circle.other.inner.model.cmd.material.PropsActivityRuleConfigQryCmd;
|
||||
import com.red.circle.tool.core.collection.CollectionUtils;
|
||||
import com.red.circle.tool.core.text.StringUtils;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@ -156,44 +151,4 @@ public class PropsActivityRuleConfigServiceImpl extends
|
||||
.orElse(Maps.newHashMap());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteById(Long id) {
|
||||
update()
|
||||
.set(PropsActivityRuleConfig::getDel, Boolean.TRUE)
|
||||
.eq(PropsActivityRuleConfig::getId, id)
|
||||
.execute();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PropsActivityRuleConfig getPropsActivityRuleConfig(PropsActivityRuleConfigParamCmd param) {
|
||||
return Optional.ofNullable(query()
|
||||
.eq(PropsActivityRuleConfig::getActivityType, param.getActivityType())
|
||||
.eq(PropsActivityRuleConfig::getResourceGroupId, param.getResourceGroupId())
|
||||
.eq(PropsActivityRuleConfig::getSysOrigin, param.getSysOrigin())
|
||||
.eq(PropsActivityRuleConfig::getDel, Boolean.FALSE)
|
||||
.last(PageConstant.LIMIT_ONE)
|
||||
.getOne())
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<PropsActivityRuleConfig> pagePropsActivityRuleConfig(
|
||||
PropsActivityRuleConfigQryCmd query) {
|
||||
return query()
|
||||
.like(StringUtils.isNotBlank(query.getRuleDescription()),
|
||||
PropsActivityRuleConfig::getRuleDescription, query.getRuleDescription())
|
||||
.eq(Objects.nonNull(query.getId()), PropsActivityRuleConfig::getId, query.getId())
|
||||
.eq(StringUtils.isNotBlank(query.getActivityType()),
|
||||
PropsActivityRuleConfig::getActivityType,
|
||||
query.getActivityType())
|
||||
.eq(StringUtils.isNotBlank(query.getSysOrigin()), PropsActivityRuleConfig::getSysOrigin,
|
||||
query.getSysOrigin())
|
||||
.eq(Objects.nonNull(query.getResourceGroupId()),
|
||||
PropsActivityRuleConfig::getResourceGroupId,
|
||||
query.getResourceGroupId())
|
||||
.eq(PropsActivityRuleConfig::getDel, Boolean.FALSE)
|
||||
.orderByDesc(PropsActivityRuleConfig::getId)
|
||||
.page(query.getPageQuery());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,59 @@
|
||||
package com.red.circle.other.infra.database.mongo.service.activity.impl;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
|
||||
import java.util.Set;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
|
||||
class ActivityAgentAnchorCountServiceImplTest {
|
||||
|
||||
private MongoTemplate mongoTemplate;
|
||||
private ActivityAgentAnchorCountServiceImpl service;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
mongoTemplate = mock(MongoTemplate.class);
|
||||
service = new TestableActivityAgentAnchorCountService(mongoTemplate, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
void initZeroSkipsBeforeMonthEnd() {
|
||||
service.initZero(10001L, 20001L);
|
||||
|
||||
verifyNoInteractions(mongoTemplate);
|
||||
}
|
||||
|
||||
@Test
|
||||
void initZeroTeamSkipsBeforeMonthEnd() {
|
||||
service.initZeroTeam(20001L);
|
||||
|
||||
verifyNoInteractions(mongoTemplate);
|
||||
}
|
||||
|
||||
@Test
|
||||
void initZeroMemberSkipsBeforeMonthEnd() {
|
||||
service.initZeroMember(Set.of(10001L, 10002L));
|
||||
|
||||
verifyNoInteractions(mongoTemplate);
|
||||
}
|
||||
|
||||
private static class TestableActivityAgentAnchorCountService
|
||||
extends ActivityAgentAnchorCountServiceImpl {
|
||||
|
||||
private final boolean monthEndResetDay;
|
||||
|
||||
private TestableActivityAgentAnchorCountService(MongoTemplate mongoTemplate,
|
||||
boolean monthEndResetDay) {
|
||||
super(mongoTemplate);
|
||||
this.monthEndResetDay = monthEndResetDay;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isMonthEndResetDay() {
|
||||
return monthEndResetDay;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2,10 +2,8 @@ package com.red.circle.other.app.inner.convertor.activity;
|
||||
|
||||
import com.red.circle.framework.core.convertor.ConvertorModel;
|
||||
import com.red.circle.other.infra.database.rds.entity.activity.PropsActivityRuleConfig;
|
||||
import com.red.circle.other.inner.model.cmd.material.PropsActivityRuleConfigParamCmd;
|
||||
import com.red.circle.other.inner.model.dto.activity.props.ActivityPropsRule;
|
||||
import com.red.circle.other.inner.model.dto.activity.props.ActivityPropsRuleDTO;
|
||||
import com.red.circle.other.inner.model.dto.material.PropsActivityRuleConfigDTO;
|
||||
import java.util.Map;
|
||||
import org.mapstruct.Mapper;
|
||||
|
||||
@ -19,9 +17,4 @@ public interface PropsActivityInnerConvertor {
|
||||
|
||||
Map<Long, ActivityPropsRuleDTO> toActivityPropsRuleDTO(
|
||||
Map<Long, PropsActivityRuleConfig> ruleConfig);
|
||||
|
||||
PropsActivityRuleConfigDTO toPropsActivityRuleConfigDTO(
|
||||
PropsActivityRuleConfig propsActivityRuleConfig);
|
||||
|
||||
PropsActivityRuleConfig toPropsActivityRuleConfig(PropsActivityRuleConfigParamCmd param);
|
||||
}
|
||||
|
||||
@ -1,19 +1,15 @@
|
||||
package com.red.circle.other.app.inner.endpoint.activity;
|
||||
|
||||
import com.red.circle.common.business.core.enums.SysOriginPlatformEnum;
|
||||
import com.red.circle.framework.dto.PageResult;
|
||||
import com.red.circle.framework.dto.ResultResponse;
|
||||
import com.red.circle.other.app.inner.service.material.props.PropsActivityClientService;
|
||||
import com.red.circle.other.inner.endpoint.activity.api.PropsActivityClientApi;
|
||||
import com.red.circle.other.inner.enums.activity.PropsActivityTypeEnum;
|
||||
import com.red.circle.other.inner.model.cmd.activity.SendActivityRewardCmd;
|
||||
import com.red.circle.other.inner.model.cmd.material.PropsActivityRuleConfigParamCmd;
|
||||
import com.red.circle.other.inner.model.cmd.material.PropsActivityRuleConfigQryCmd;
|
||||
import com.red.circle.other.inner.model.dto.activity.props.ActivityPropsGroup;
|
||||
import com.red.circle.other.inner.model.dto.activity.props.ActivityPropsRule;
|
||||
import com.red.circle.other.inner.model.dto.activity.props.ActivityPropsRuleDTO;
|
||||
import com.red.circle.other.inner.model.dto.activity.props.ActivityResource;
|
||||
import com.red.circle.other.inner.model.dto.material.PropsActivityRuleConfigDTO;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -126,36 +122,4 @@ public class PropsActivityClientEndpoint implements PropsActivityClientApi {
|
||||
public ResultResponse<Map<Long, ActivityPropsRuleDTO>> mapRuleConfigByIds(Collection<Long> ids) {
|
||||
return ResultResponse.success(propsActivityClientService.mapRuleConfigByIds(ids));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultResponse<PageResult<PropsActivityRuleConfigDTO>> pagePropsActivityRuleConfig(
|
||||
PropsActivityRuleConfigQryCmd query) {
|
||||
return ResultResponse.success(propsActivityClientService.pagePropsActivityRuleConfig(query));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ResultResponse<Void> propsActivityRuleConfigDeleteById(Long id) {
|
||||
propsActivityClientService.propsActivityRuleConfigDeleteById(id);
|
||||
return ResultResponse.success();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultResponse<PropsActivityRuleConfigDTO> getPropsActivityRuleConfig(
|
||||
PropsActivityRuleConfigParamCmd param) {
|
||||
return ResultResponse.success(propsActivityClientService.getPropsActivityRuleConfig(param));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultResponse<Void> updatePropsActivityRuleConfigById(
|
||||
PropsActivityRuleConfigParamCmd param) {
|
||||
propsActivityClientService.updatePropsActivityRuleConfigById(param);
|
||||
return ResultResponse.success();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultResponse<Void> savePropsActivityRuleConfig(PropsActivityRuleConfigParamCmd param) {
|
||||
propsActivityClientService.savePropsActivityRuleConfig(param);
|
||||
return ResultResponse.success();
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,9 +4,11 @@ import com.red.circle.framework.dto.PageResult;
|
||||
import com.red.circle.framework.dto.ResultResponse;
|
||||
import com.red.circle.other.app.inner.service.material.props.PropsCommodityStoreClientService;
|
||||
import com.red.circle.other.inner.endpoint.material.props.api.PropsCommodityStoreClientApi;
|
||||
import com.red.circle.other.inner.model.cmd.material.PropsCommodityStoreQryCmd;
|
||||
import com.red.circle.other.inner.model.dto.material.PropsCommodityStoreDTO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import com.red.circle.other.inner.model.cmd.material.PropsCommodityStoreQryCmd;
|
||||
import com.red.circle.other.inner.model.dto.material.PropsCommodityStoreDTO;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
@ -26,14 +28,21 @@ public class PropsCommodityStoreClientEndpoint implements PropsCommodityStoreCli
|
||||
private final PropsCommodityStoreClientService propsCommodityStoreClientService;
|
||||
|
||||
@Override
|
||||
public ResultResponse<PropsCommodityStoreDTO> getPropsCommodityStore(String sysOrigin,
|
||||
Long sourceId) {
|
||||
return ResultResponse.success(
|
||||
propsCommodityStoreClientService.getPropsCommodityStore(sysOrigin, sourceId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultResponse<PageResult<PropsCommodityStoreDTO>> pageOps(
|
||||
public ResultResponse<PropsCommodityStoreDTO> getPropsCommodityStore(String sysOrigin,
|
||||
Long sourceId) {
|
||||
return ResultResponse.success(
|
||||
propsCommodityStoreClientService.getPropsCommodityStore(sysOrigin, sourceId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultResponse<Map<Long, PropsCommodityStoreDTO>> mapBySourceIds(String sysOrigin,
|
||||
Set<Long> sourceIds) {
|
||||
return ResultResponse.success(
|
||||
propsCommodityStoreClientService.mapBySourceIds(sysOrigin, sourceIds));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultResponse<PageResult<PropsCommodityStoreDTO>> pageOps(
|
||||
PropsCommodityStoreQryCmd qryCmd) {
|
||||
return ResultResponse.success(propsCommodityStoreClientService.pageOps(qryCmd));
|
||||
}
|
||||
|
||||
@ -1,16 +1,12 @@
|
||||
package com.red.circle.other.app.inner.service.material.props;
|
||||
|
||||
import com.red.circle.common.business.core.enums.SysOriginPlatformEnum;
|
||||
import com.red.circle.framework.dto.PageResult;
|
||||
import com.red.circle.other.inner.enums.activity.PropsActivityTypeEnum;
|
||||
import com.red.circle.other.inner.model.cmd.activity.SendActivityRewardCmd;
|
||||
import com.red.circle.other.inner.model.cmd.material.PropsActivityRuleConfigParamCmd;
|
||||
import com.red.circle.other.inner.model.cmd.material.PropsActivityRuleConfigQryCmd;
|
||||
import com.red.circle.other.inner.model.dto.activity.props.ActivityPropsGroup;
|
||||
import com.red.circle.other.inner.model.dto.activity.props.ActivityPropsRule;
|
||||
import com.red.circle.other.inner.model.dto.activity.props.ActivityPropsRuleDTO;
|
||||
import com.red.circle.other.inner.model.dto.activity.props.ActivityResource;
|
||||
import com.red.circle.other.inner.model.dto.material.PropsActivityRuleConfigDTO;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -136,31 +132,4 @@ public interface PropsActivityClientService {
|
||||
* 查询活动道具规则配置
|
||||
*/
|
||||
Map<Long, ActivityPropsRuleDTO> mapRuleConfigByIds(Collection<Long> ids);
|
||||
|
||||
/**
|
||||
* 分页-查询活动道具规则配置.
|
||||
*/
|
||||
PageResult<PropsActivityRuleConfigDTO> pagePropsActivityRuleConfig(
|
||||
PropsActivityRuleConfigQryCmd query);
|
||||
|
||||
|
||||
/**
|
||||
* 删除-查询活动道具规则配置.
|
||||
*/
|
||||
void propsActivityRuleConfigDeleteById(Long id);
|
||||
|
||||
/**
|
||||
* 查询活动道具规则配置.
|
||||
*/
|
||||
PropsActivityRuleConfigDTO getPropsActivityRuleConfig(PropsActivityRuleConfigParamCmd param);
|
||||
|
||||
/**
|
||||
* 修改-活动道具规则配置.
|
||||
*/
|
||||
void updatePropsActivityRuleConfigById(PropsActivityRuleConfigParamCmd param);
|
||||
|
||||
/**
|
||||
* 保存-活动道具规则配置.
|
||||
*/
|
||||
void savePropsActivityRuleConfig(PropsActivityRuleConfigParamCmd param);
|
||||
}
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
package com.red.circle.other.app.inner.service.material.props;
|
||||
|
||||
import com.red.circle.framework.dto.PageResult;
|
||||
import com.red.circle.other.inner.model.cmd.material.PropsCommodityStoreQryCmd;
|
||||
import com.red.circle.other.inner.model.dto.material.PropsCommodityStoreDTO;
|
||||
import com.red.circle.framework.dto.PageResult;
|
||||
import com.red.circle.other.inner.model.cmd.material.PropsCommodityStoreQryCmd;
|
||||
import com.red.circle.other.inner.model.dto.material.PropsCommodityStoreDTO;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 道具商店.
|
||||
@ -14,10 +16,15 @@ public interface PropsCommodityStoreClientService {
|
||||
/**
|
||||
* 获取平台指定资源.
|
||||
*/
|
||||
PropsCommodityStoreDTO getPropsCommodityStore(String sysOrigin, Long sourceId);
|
||||
|
||||
/**
|
||||
* 商店分页列表.
|
||||
PropsCommodityStoreDTO getPropsCommodityStore(String sysOrigin, Long sourceId);
|
||||
|
||||
/**
|
||||
* 批量获取资源对应商品.
|
||||
*/
|
||||
Map<Long, PropsCommodityStoreDTO> mapBySourceIds(String sysOrigin, Set<Long> sourceIds);
|
||||
|
||||
/**
|
||||
* 商店分页列表.
|
||||
*/
|
||||
PageResult<PropsCommodityStoreDTO> pageOps(PropsCommodityStoreQryCmd qryCmd);
|
||||
|
||||
|
||||
@ -1,23 +1,18 @@
|
||||
package com.red.circle.other.app.inner.service.material.props.impl;
|
||||
|
||||
import com.red.circle.common.business.core.enums.SysOriginPlatformEnum;
|
||||
import com.red.circle.framework.dto.PageResult;
|
||||
import com.red.circle.other.app.inner.convertor.activity.PropsActivityInnerConvertor;
|
||||
import com.red.circle.other.app.inner.service.material.props.PropsActivityClientService;
|
||||
import com.red.circle.other.domain.gateway.props.ActivitySourceGroupGateway;
|
||||
import com.red.circle.other.infra.common.activity.PropsActivitySendCommon;
|
||||
import com.red.circle.other.infra.common.activity.send.SendRewardGroup;
|
||||
import com.red.circle.other.infra.database.rds.entity.activity.PropsActivityRuleConfig;
|
||||
import com.red.circle.other.infra.database.rds.service.activity.PropsActivityRuleConfigService;
|
||||
import com.red.circle.other.inner.enums.activity.PropsActivityTypeEnum;
|
||||
import com.red.circle.other.inner.model.cmd.activity.SendActivityRewardCmd;
|
||||
import com.red.circle.other.inner.model.cmd.material.PropsActivityRuleConfigParamCmd;
|
||||
import com.red.circle.other.inner.model.cmd.material.PropsActivityRuleConfigQryCmd;
|
||||
import com.red.circle.other.inner.model.dto.activity.props.ActivityPropsGroup;
|
||||
import com.red.circle.other.inner.model.dto.activity.props.ActivityPropsRule;
|
||||
import com.red.circle.other.inner.model.dto.activity.props.ActivityPropsRuleDTO;
|
||||
import com.red.circle.other.inner.model.dto.activity.props.ActivityResource;
|
||||
import com.red.circle.other.inner.model.dto.material.PropsActivityRuleConfigDTO;
|
||||
import com.red.circle.tool.core.collection.CollectionUtils;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
@ -139,46 +134,4 @@ public class PropsActivityClientServiceImpl implements PropsActivityClientServic
|
||||
return propsActivityInnerConvertor.toActivityPropsRuleDTO(
|
||||
propsActivityRuleConfigService.mapByIds(ids));
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<PropsActivityRuleConfigDTO> pagePropsActivityRuleConfig(
|
||||
PropsActivityRuleConfigQryCmd query) {
|
||||
|
||||
PageResult<PropsActivityRuleConfig> pageResult = propsActivityRuleConfigService.pagePropsActivityRuleConfig(
|
||||
query);
|
||||
|
||||
return pageResult.convert(
|
||||
ruleConfig -> {
|
||||
PropsActivityRuleConfigDTO ruleConfigDTO = propsActivityInnerConvertor.toPropsActivityRuleConfigDTO(
|
||||
ruleConfig);
|
||||
ruleConfigDTO.setActivityTypeName(ruleConfigDTO.getActivityType());
|
||||
return ruleConfigDTO;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void propsActivityRuleConfigDeleteById(Long id) {
|
||||
propsActivityRuleConfigService.deleteById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PropsActivityRuleConfigDTO getPropsActivityRuleConfig(
|
||||
PropsActivityRuleConfigParamCmd param) {
|
||||
|
||||
return propsActivityInnerConvertor.toPropsActivityRuleConfigDTO(
|
||||
propsActivityRuleConfigService.getPropsActivityRuleConfig(
|
||||
param));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updatePropsActivityRuleConfigById(PropsActivityRuleConfigParamCmd param) {
|
||||
propsActivityRuleConfigService.updateSelectiveById(
|
||||
propsActivityInnerConvertor.toPropsActivityRuleConfig(param));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void savePropsActivityRuleConfig(PropsActivityRuleConfigParamCmd param) {
|
||||
propsActivityRuleConfigService.save(
|
||||
propsActivityInnerConvertor.toPropsActivityRuleConfig(param));
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,13 +4,19 @@ import com.red.circle.framework.dto.PageResult;
|
||||
import com.red.circle.framework.mybatis.constant.PageConstant;
|
||||
import com.red.circle.other.app.inner.convertor.material.PropsCommodityStoreInnerConvertor;
|
||||
import com.red.circle.other.app.inner.service.material.props.PropsCommodityStoreClientService;
|
||||
import com.red.circle.other.infra.database.rds.entity.props.PropsCommodityStore;
|
||||
import com.red.circle.other.infra.database.rds.service.props.PropsCommodityStoreService;
|
||||
import com.red.circle.other.inner.model.cmd.material.PropsCommodityStoreQryCmd;
|
||||
import com.red.circle.other.inner.model.dto.material.PropsCommodityStoreDTO;
|
||||
import com.red.circle.tool.core.text.StringUtils;
|
||||
import java.util.Objects;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import com.red.circle.other.infra.database.rds.entity.props.PropsCommodityStore;
|
||||
import com.red.circle.other.infra.database.rds.service.props.PropsCommodityStoreService;
|
||||
import com.red.circle.other.inner.model.cmd.material.PropsCommodityStoreQryCmd;
|
||||
import com.red.circle.other.inner.model.dto.material.PropsCommodityStoreDTO;
|
||||
import com.red.circle.tool.core.collection.CollectionUtils;
|
||||
import com.red.circle.tool.core.text.StringUtils;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
@ -26,14 +32,35 @@ public class PropsCommodityStoreClientServiceImpl implements PropsCommodityStore
|
||||
private final PropsCommodityStoreInnerConvertor propsCommodityStoreInnerConvertor;
|
||||
|
||||
@Override
|
||||
public PropsCommodityStoreDTO getPropsCommodityStore(String sysOrigin, Long sourceId) {
|
||||
return propsCommodityStoreInnerConvertor.toPropsCommodityStoreDTO(
|
||||
propsCommodityStoreService.getSourceIdById(sysOrigin, sourceId)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<PropsCommodityStoreDTO> pageOps(PropsCommodityStoreQryCmd qryCmd) {
|
||||
public PropsCommodityStoreDTO getPropsCommodityStore(String sysOrigin, Long sourceId) {
|
||||
return propsCommodityStoreInnerConvertor.toPropsCommodityStoreDTO(
|
||||
propsCommodityStoreService.getSourceIdById(sysOrigin, sourceId)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Long, PropsCommodityStoreDTO> mapBySourceIds(String sysOrigin, Set<Long> sourceIds) {
|
||||
if (StringUtils.isBlank(sysOrigin) || CollectionUtils.isEmpty(sourceIds)) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
return propsCommodityStoreService.query()
|
||||
.eq(PropsCommodityStore::getSysOrigin, sysOrigin)
|
||||
.in(PropsCommodityStore::getSourceId, sourceIds)
|
||||
.and(wrapper -> wrapper.eq(PropsCommodityStore::getDel, Boolean.FALSE)
|
||||
.or()
|
||||
.isNull(PropsCommodityStore::getDel))
|
||||
.orderByDesc(PropsCommodityStore::getUpdateTime)
|
||||
.list()
|
||||
.stream()
|
||||
.map(propsCommodityStoreInnerConvertor::toPropsCommodityStoreDTO)
|
||||
.collect(Collectors.toMap(
|
||||
PropsCommodityStoreDTO::getSourceId,
|
||||
Function.identity(),
|
||||
(first, ignored) -> first));
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<PropsCommodityStoreDTO> pageOps(PropsCommodityStoreQryCmd qryCmd) {
|
||||
return propsCommodityStoreInnerConvertor.toPagePropsCommodityStoreDTO(
|
||||
propsCommodityStoreService.query()
|
||||
.eq(Objects.nonNull(qryCmd.getId()), PropsCommodityStore::getId, qryCmd.getId())
|
||||
@ -45,10 +72,12 @@ public class PropsCommodityStoreClientServiceImpl implements PropsCommodityStore
|
||||
qryCmd.getPropsType())
|
||||
.like(StringUtils.isNotBlank(qryCmd.getCurrencyTypes()),
|
||||
PropsCommodityStore::getCurrencyTypes, qryCmd.getCurrencyTypes())
|
||||
.eq(Objects.nonNull(qryCmd.getShelfStatus()),
|
||||
PropsCommodityStore::getShelfStatus, qryCmd.getShelfStatus())
|
||||
.eq(PropsCommodityStore::getDel, Boolean.FALSE)
|
||||
.orderByDesc(PropsCommodityStore::getSort, PropsCommodityStore::getId)
|
||||
.eq(Objects.nonNull(qryCmd.getShelfStatus()),
|
||||
PropsCommodityStore::getShelfStatus, qryCmd.getShelfStatus())
|
||||
.and(wrapper -> wrapper.eq(PropsCommodityStore::getDel, Boolean.FALSE)
|
||||
.or()
|
||||
.isNull(PropsCommodityStore::getDel))
|
||||
.orderByDesc(PropsCommodityStore::getSort, PropsCommodityStore::getId)
|
||||
.page(qryCmd.getPageQuery())
|
||||
);
|
||||
}
|
||||
|
||||
@ -125,14 +125,22 @@ public class PropsSourceClientServiceImpl implements PropsSourceClientService {
|
||||
return propsResourcesInnerConvertor.toPagePropsResourcesDTO(
|
||||
propsSourceRecordService.query()
|
||||
.eq(PropsSourceRecord::getSysOrigin, qryCmd.getSysOrigin())
|
||||
.eq(Objects.nonNull(qryCmd.getType()), PropsSourceRecord::getType,
|
||||
qryCmd.getType())
|
||||
.eq(Objects.nonNull(qryCmd.getId()), PropsSourceRecord::getId,
|
||||
qryCmd.getId())
|
||||
.eq(Objects.nonNull(qryCmd.getDel()), PropsSourceRecord::getDel,
|
||||
qryCmd.getDel())
|
||||
.like(StringUtils.isNotBlank(qryCmd.getName()), PropsSourceRecord::getName,
|
||||
qryCmd.getName())
|
||||
.eq(Objects.nonNull(qryCmd.getType()), PropsSourceRecord::getType,
|
||||
qryCmd.getType())
|
||||
.eq(Objects.nonNull(qryCmd.getId()), PropsSourceRecord::getId,
|
||||
qryCmd.getId())
|
||||
.like(StringUtils.isNotBlank(qryCmd.getCode()), PropsSourceRecord::getCode,
|
||||
qryCmd.getCode())
|
||||
.eq(Objects.nonNull(qryCmd.getDel()), PropsSourceRecord::getDel,
|
||||
qryCmd.getDel())
|
||||
.eq(Objects.nonNull(qryCmd.getAdminFree()), PropsSourceRecord::getAdminFree,
|
||||
qryCmd.getAdminFree())
|
||||
.ge(Objects.nonNull(qryCmd.getAmountMin()), PropsSourceRecord::getAmount,
|
||||
qryCmd.getAmountMin())
|
||||
.le(Objects.nonNull(qryCmd.getAmountMax()), PropsSourceRecord::getAmount,
|
||||
qryCmd.getAmountMax())
|
||||
.like(StringUtils.isNotBlank(qryCmd.getName()), PropsSourceRecord::getName,
|
||||
qryCmd.getName())
|
||||
.orderByDesc(PropsSourceRecord::getCreateTime)
|
||||
.page(qryCmd.getPageQuery())
|
||||
);
|
||||
|
||||
@ -0,0 +1,150 @@
|
||||
package com.red.circle.framework.feign.decoder;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.red.circle.framework.core.exception.ResponseException;
|
||||
import com.red.circle.framework.core.request.ResponseHeaderConstant;
|
||||
import com.red.circle.framework.core.response.ResponseErrorCode;
|
||||
import com.red.circle.framework.core.spring.ApplicationContextUtils;
|
||||
import com.red.circle.framework.dto.ResultResponse;
|
||||
import com.red.circle.tool.core.http.HttpConstant;
|
||||
import com.red.circle.tool.core.json.JacksonUtils;
|
||||
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;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class FeignErrorDecoder implements ErrorDecoder {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(FeignErrorDecoder.class);
|
||||
|
||||
private static final List<Integer> PROCESS_RESULT_RESPONSE = List.of(
|
||||
ResponseErrorCode.NOT_ACCEPTABLE.getCode(),
|
||||
ResponseErrorCode.REQUEST_PARAMETER_ERROR.getCode()
|
||||
);
|
||||
|
||||
@Override
|
||||
public Exception decode(String methodKey, Response response) {
|
||||
if (PROCESS_RESULT_RESPONSE.stream()
|
||||
.anyMatch(code -> Objects.equals(response.status(), code))) {
|
||||
ResponseException exception = parseResponseException(methodKey, response);
|
||||
if (Objects.nonNull(exception)) {
|
||||
return exception;
|
||||
}
|
||||
return createResponseException(methodKey, response);
|
||||
}
|
||||
|
||||
if (HttpConstant.is4xx(response.status())) {
|
||||
return createResponseException(methodKey, response);
|
||||
}
|
||||
|
||||
if (ResponseHeaderConstant.existsHeaderValue(response.headers(), "RC-No-Retry", "true")) {
|
||||
ResponseException exception = parseResponseException(methodKey, response);
|
||||
if (Objects.nonNull(exception)) {
|
||||
return exception;
|
||||
}
|
||||
return createResponseException(methodKey, response);
|
||||
}
|
||||
|
||||
return createResponseException(methodKey, response);
|
||||
}
|
||||
|
||||
private static String reqMapping(Response response) {
|
||||
Request request = response.request();
|
||||
if (Objects.isNull(request)) {
|
||||
return "";
|
||||
}
|
||||
return request.httpMethod() + " " + request.url();
|
||||
}
|
||||
|
||||
private ResponseException createResponseException(String methodKey, Response response) {
|
||||
String errorCodeName = Objects.equals(response.status(), 429)
|
||||
? "REQ_LIMIT_INNER_DECODER"
|
||||
: "ERROR_INNER_DECODER";
|
||||
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(Response response) {
|
||||
return StringUtils.isBlankOrElse(response.reason(), "Inner service error");
|
||||
}
|
||||
|
||||
private ResponseException parseResponseException(String methodKey, Response response) {
|
||||
ResultResponse<Void> responseBody = parseResponse(response);
|
||||
if (Objects.nonNull(responseBody)) {
|
||||
Map<String, Object> extValues = Optional.ofNullable(responseBody.getExtValues())
|
||||
.orElseGet(HashMap::new);
|
||||
|
||||
extValues.put("innerError",
|
||||
"[" + methodKey + "]" + StringUtils.isBlankOrElse(response.reason(), ""));
|
||||
|
||||
return new ResponseException(
|
||||
responseBody.getErrorCode(),
|
||||
responseBody.getErrorCodeName(),
|
||||
responseBody.getErrorMsg(),
|
||||
extValues
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private ResultResponse<Void> parseResponse(Response response) {
|
||||
if (Objects.isNull(response.body())) {
|
||||
log.warn("Feign error response body is null, status={}, reason={}, request={}",
|
||||
response.status(), response.reason(), reqMapping(response));
|
||||
return null;
|
||||
}
|
||||
|
||||
try (InputStream inputStream = response.body().asInputStream()) {
|
||||
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<Void> responseBody = parseResponseBytes(response, bodyBytes, gzipEncoded);
|
||||
if (Objects.nonNull(responseBody)) {
|
||||
return responseBody;
|
||||
}
|
||||
return parseResponseBytes(response, bodyBytes, !gzipEncoded);
|
||||
} catch (IOException | RuntimeException ex) {
|
||||
log.warn("Read feign error response body failed, status={}, reason={}, request={}",
|
||||
response.status(), response.reason(), reqMapping(response), ex);
|
||||
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));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,117 @@
|
||||
package com.red.circle.framework.web.feign;
|
||||
|
||||
import com.red.circle.framework.core.request.RequestHeaderConstant;
|
||||
import com.red.circle.framework.core.request.ServiceTraceId;
|
||||
import feign.RequestInterceptor;
|
||||
import feign.RequestTemplate;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
/**
|
||||
* Forward only business headers to Feign requests.
|
||||
*/
|
||||
@Component
|
||||
@Order(Ordered.LOWEST_PRECEDENCE)
|
||||
public class FeignRequestInterceptor implements RequestInterceptor {
|
||||
|
||||
private static final String INNER_SERVICE = "FEIGN";
|
||||
|
||||
private static final List<String> FORWARD_HEADERS = List.of(
|
||||
RequestHeaderConstant.AUTHORIZATION,
|
||||
RequestHeaderConstant.REQ_SYS_ORIGIN,
|
||||
RequestHeaderConstant.REQ_CLIENT,
|
||||
RequestHeaderConstant.REQ_ZONE,
|
||||
RequestHeaderConstant.REQ_IMEI,
|
||||
RequestHeaderConstant.REQ_APP_INTEL,
|
||||
RequestHeaderConstant.REQ_VERSION,
|
||||
RequestHeaderConstant.REQ_LANG,
|
||||
RequestHeaderConstant.REQ_INNER_INTERNAL,
|
||||
RequestHeaderConstant.REQ_INNER_TRACE_ID
|
||||
);
|
||||
|
||||
private static final Set<String> BLOCKED_HEADERS = Set.of(
|
||||
"host",
|
||||
"content-length",
|
||||
"transfer-encoding",
|
||||
"connection",
|
||||
"keep-alive",
|
||||
"upgrade",
|
||||
"proxy-authorization",
|
||||
"proxy-authenticate",
|
||||
"te",
|
||||
"trailer"
|
||||
);
|
||||
|
||||
@Override
|
||||
public void apply(RequestTemplate requestTemplate) {
|
||||
removeBlockedHeaders(requestTemplate);
|
||||
|
||||
if (RequestContextHolder.getRequestAttributes() instanceof ServletRequestAttributes attrs) {
|
||||
forwardHeaders(requestTemplate, attrs.getRequest());
|
||||
}
|
||||
|
||||
if (!hasHeader(requestTemplate, RequestHeaderConstant.REQ_INNER_SERVICE)) {
|
||||
requestTemplate.header(RequestHeaderConstant.REQ_INNER_SERVICE, INNER_SERVICE);
|
||||
}
|
||||
if (!hasHeader(requestTemplate, RequestHeaderConstant.REQ_INNER_TRACE_ID)) {
|
||||
requestTemplate.header(
|
||||
RequestHeaderConstant.REQ_INNER_TRACE_ID,
|
||||
ServiceTraceId.genTraceId(ServiceTraceId.Origin.INNER_REQ));
|
||||
}
|
||||
|
||||
removeBlockedHeaders(requestTemplate);
|
||||
}
|
||||
|
||||
private void forwardHeaders(RequestTemplate requestTemplate, HttpServletRequest request) {
|
||||
for (String header : FORWARD_HEADERS) {
|
||||
String value = request.getHeader(header);
|
||||
if (hasHeader(requestTemplate, header) || !isUsableHeaderValue(value)) {
|
||||
continue;
|
||||
}
|
||||
requestTemplate.header(header, value);
|
||||
}
|
||||
}
|
||||
|
||||
private void removeBlockedHeaders(RequestTemplate requestTemplate) {
|
||||
List<String> names = new ArrayList<>(requestTemplate.headers().keySet());
|
||||
for (String name : names) {
|
||||
if (isBlockedHeader(name)) {
|
||||
requestTemplate.removeHeader(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasHeader(RequestTemplate requestTemplate, String header) {
|
||||
return requestTemplate.headers().keySet().stream()
|
||||
.anyMatch(name -> name.equalsIgnoreCase(header));
|
||||
}
|
||||
|
||||
private boolean isBlockedHeader(String header) {
|
||||
if (header == null) {
|
||||
return true;
|
||||
}
|
||||
String normalized = header.toLowerCase(Locale.ROOT);
|
||||
return BLOCKED_HEADERS.contains(normalized) || normalized.startsWith("proxy-");
|
||||
}
|
||||
|
||||
private boolean isUsableHeaderValue(String value) {
|
||||
if (value == null || value.isBlank()) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < value.length(); i++) {
|
||||
char c = value.charAt(i);
|
||||
if (c < 32 || c == 127) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,117 @@
|
||||
package com.red.circle.framework.web.feign;
|
||||
|
||||
import com.red.circle.framework.core.request.RequestHeaderConstant;
|
||||
import com.red.circle.framework.core.request.ServiceTraceId;
|
||||
import feign.RequestInterceptor;
|
||||
import feign.RequestTemplate;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
/**
|
||||
* Forward only business headers to Feign requests.
|
||||
*/
|
||||
@Component
|
||||
@Order(Ordered.LOWEST_PRECEDENCE)
|
||||
public class FeignRequestInterceptor implements RequestInterceptor {
|
||||
|
||||
private static final String INNER_SERVICE = "FEIGN";
|
||||
|
||||
private static final List<String> FORWARD_HEADERS = List.of(
|
||||
RequestHeaderConstant.AUTHORIZATION,
|
||||
RequestHeaderConstant.REQ_SYS_ORIGIN,
|
||||
RequestHeaderConstant.REQ_CLIENT,
|
||||
RequestHeaderConstant.REQ_ZONE,
|
||||
RequestHeaderConstant.REQ_IMEI,
|
||||
RequestHeaderConstant.REQ_APP_INTEL,
|
||||
RequestHeaderConstant.REQ_VERSION,
|
||||
RequestHeaderConstant.REQ_LANG,
|
||||
RequestHeaderConstant.REQ_INNER_INTERNAL,
|
||||
RequestHeaderConstant.REQ_INNER_TRACE_ID
|
||||
);
|
||||
|
||||
private static final Set<String> BLOCKED_HEADERS = Set.of(
|
||||
"host",
|
||||
"content-length",
|
||||
"transfer-encoding",
|
||||
"connection",
|
||||
"keep-alive",
|
||||
"upgrade",
|
||||
"proxy-authorization",
|
||||
"proxy-authenticate",
|
||||
"te",
|
||||
"trailer"
|
||||
);
|
||||
|
||||
@Override
|
||||
public void apply(RequestTemplate requestTemplate) {
|
||||
removeBlockedHeaders(requestTemplate);
|
||||
|
||||
if (RequestContextHolder.getRequestAttributes() instanceof ServletRequestAttributes attrs) {
|
||||
forwardHeaders(requestTemplate, attrs.getRequest());
|
||||
}
|
||||
|
||||
if (!hasHeader(requestTemplate, RequestHeaderConstant.REQ_INNER_SERVICE)) {
|
||||
requestTemplate.header(RequestHeaderConstant.REQ_INNER_SERVICE, INNER_SERVICE);
|
||||
}
|
||||
if (!hasHeader(requestTemplate, RequestHeaderConstant.REQ_INNER_TRACE_ID)) {
|
||||
requestTemplate.header(
|
||||
RequestHeaderConstant.REQ_INNER_TRACE_ID,
|
||||
ServiceTraceId.genTraceId(ServiceTraceId.Origin.INNER_REQ));
|
||||
}
|
||||
|
||||
removeBlockedHeaders(requestTemplate);
|
||||
}
|
||||
|
||||
private void forwardHeaders(RequestTemplate requestTemplate, HttpServletRequest request) {
|
||||
for (String header : FORWARD_HEADERS) {
|
||||
String value = request.getHeader(header);
|
||||
if (hasHeader(requestTemplate, header) || !isUsableHeaderValue(value)) {
|
||||
continue;
|
||||
}
|
||||
requestTemplate.header(header, value);
|
||||
}
|
||||
}
|
||||
|
||||
private void removeBlockedHeaders(RequestTemplate requestTemplate) {
|
||||
List<String> names = new ArrayList<>(requestTemplate.headers().keySet());
|
||||
for (String name : names) {
|
||||
if (isBlockedHeader(name)) {
|
||||
requestTemplate.removeHeader(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasHeader(RequestTemplate requestTemplate, String header) {
|
||||
return requestTemplate.headers().keySet().stream()
|
||||
.anyMatch(name -> name.equalsIgnoreCase(header));
|
||||
}
|
||||
|
||||
private boolean isBlockedHeader(String header) {
|
||||
if (header == null) {
|
||||
return true;
|
||||
}
|
||||
String normalized = header.toLowerCase(Locale.ROOT);
|
||||
return BLOCKED_HEADERS.contains(normalized) || normalized.startsWith("proxy-");
|
||||
}
|
||||
|
||||
private boolean isUsableHeaderValue(String value) {
|
||||
if (value == null || value.isBlank()) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < value.length(); i++) {
|
||||
char c = value.charAt(i);
|
||||
if (c < 32 || c == 127) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user