Compare commits

..

18 Commits

Author SHA1 Message Date
hy001
32a1094edc 大屏 2026-05-18 21:57:22 +08:00
hy001
d9d690e53d 修复大屏 2026-05-18 21:12:16 +08:00
hy001
b76e6e3a93 数据大屏 2026-05-18 20:31:20 +08:00
hy001
f26bbbc310 Allow props store console go route 2026-05-18 19:22:15 +08:00
hy001
916ce628be fix: normalize channel login open ids 2026-05-18 14:04:17 +08:00
hy001
2a7be2ca92 fix: sanitize forwarded feign headers 2026-05-18 12:15:24 +08:00
hy001
d49341204c Fix gateway console go route 2026-05-18 11:10:20 +08:00
hy001
9a66e58a93 Fix console go gateway route precedence 2026-05-18 11:02:22 +08:00
hy001
7d1909858e Add registration failure diagnostics 2026-05-18 10:54:54 +08:00
hy001
b057119fd3 Show team bills by monthly cycle 2026-05-18 10:48:25 +08:00
hy001
9deeef5004 Use monthly team billing cycle 2026-05-17 22:40:43 +08:00
hy001
d9ff507e5f Revert "去掉动态模块"
This reverts commit 94b9063dd634128125744c2b538695e13c305077.
2026-05-17 14:48:11 +08:00
hy001
94b9063dd6 去掉动态模块 2026-05-17 14:27:40 +08:00
hy001
b5b20b43a8 Bypass app auth for console gateway routes 2026-05-17 14:02:36 +08:00
hy001
c0a6021982 Add console routes to gateway 2026-05-17 13:59:29 +08:00
hy001
6642ead223 去掉活动配置 2026-05-17 13:41:35 +08:00
hy001
3fc5d4a4ff feat: expose store status on props resources 2026-05-17 01:02:17 +08:00
hy001
d17f75e42d Fix anchor activity point reset 2026-05-16 22:05:45 +08:00
58 changed files with 2375 additions and 1254 deletions

View File

@ -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

View File

@ -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,

View File

@ -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));
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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(

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

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

View File

@ -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);
}
}

View File

@ -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();
}

View File

@ -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;
}
}
}
}

View File

@ -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;
}
);
}

View File

@ -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;

View File

@ -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;

View File

@ -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);
}

View File

@ -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

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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)

View File

@ -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(

View File

@ -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");

View File

@ -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());
}

View File

@ -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);
}
/**
* 测试方法手动触发.

View File

@ -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);
}
/**
* 测试方法手动触发.

View File

@ -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);
}
/**
* 测试方法手动触发.

View File

@ -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();

View File

@ -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());
}
}

View File

@ -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);
}
}

View File

@ -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) {

View File

@ -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());

View File

@ -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));
}
}

View File

@ -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)));
}
}

View File

@ -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) {

View File

@ -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) {

View File

@ -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);
}

View File

@ -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());
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}

View File

@ -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();
}
}

View File

@ -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));
}

View File

@ -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);
}

View File

@ -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);

View File

@ -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));
}
}

View File

@ -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())
);
}

View File

@ -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())
);

View File

@ -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));
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}