增加订单导出

This commit is contained in:
hy001 2026-06-09 15:43:05 +08:00
parent c8e9c8c257
commit c7016d5ae4
9 changed files with 368 additions and 96 deletions

View File

@ -102,4 +102,9 @@ public class InAppPurchaseDetailsQryCmd extends Command {
* 条数. * 条数.
*/ */
private Integer limit; private Integer limit;
/**
* 是否导出全部筛选结果导出时不使用 lastId limit 分页.
*/
private Boolean exportAll;
} }

View File

@ -2,8 +2,11 @@ package com.red.circle.console.adapter.app.order;
import com.red.circle.console.app.dto.clienobject.order.InAppPurchaseDetailsCO; import com.red.circle.console.app.dto.clienobject.order.InAppPurchaseDetailsCO;
import com.red.circle.console.app.service.app.order.InAppPurchaseBackService; import com.red.circle.console.app.service.app.order.InAppPurchaseBackService;
import com.red.circle.console.infra.annotations.OpsOperationReqLog;
import com.red.circle.framework.web.controller.BaseController; import com.red.circle.framework.web.controller.BaseController;
import com.red.circle.order.inner.model.cmd.inapp.InAppPurchaseDetailsQryCmd; import com.red.circle.order.inner.model.cmd.inapp.InAppPurchaseDetailsQryCmd;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@ -50,4 +53,14 @@ public class InAppPurchaseRestController extends BaseController {
return inAppPurchaseBackService.statistics(query); return inAppPurchaseBackService.statistics(query);
} }
/**
* 订单详情导出.
*/
@OpsOperationReqLog(value = "导出-订单详情", ignoreRequestParam = true)
@GetMapping("/details/export")
public void exportDetails(HttpServletResponse response, InAppPurchaseDetailsQryCmd query)
throws IOException {
inAppPurchaseBackService.exportDetails(response, query);
}
} }

View File

@ -0,0 +1,88 @@
package com.red.circle.console.app.excel.model.order;
import com.alibaba.excel.annotation.ExcelProperty;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 内购订单详情导出模型.
*/
@Data
@Accessors(chain = true)
public class InAppPurchaseDetailsExportModel implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@ExcelProperty(value = "Env")
private String env;
@ExcelProperty(value = "Sys Origin")
private String sysOrigin;
@ExcelProperty(value = "Platform")
private String platform;
@ExcelProperty(value = "Factory")
private String factoryCode;
@ExcelProperty(value = "Channel")
private String channel;
@ExcelProperty(value = "Buyer User ID")
private String buyerUserId;
@ExcelProperty(value = "Buyer Account")
private String buyerAccount;
@ExcelProperty(value = "Buyer Nickname")
private String buyerNickname;
@ExcelProperty(value = "Accept User ID")
private String acceptUserId;
@ExcelProperty(value = "Accept Account")
private String acceptAccount;
@ExcelProperty(value = "Accept Nickname")
private String acceptNickname;
@ExcelProperty(value = "Amount(USD)")
private BigDecimal amountUsd;
@ExcelProperty(value = "Amount")
private BigDecimal amount;
@ExcelProperty(value = "Currency")
private String currency;
@ExcelProperty(value = "Description")
private String productDescriptor;
@ExcelProperty(value = "Status")
private String status;
@ExcelProperty(value = "Receipt Type")
private String receiptType;
@ExcelProperty(value = "Third Party Order ID")
private String orderId;
@ExcelProperty(value = "App Order ID")
private String appOrderId;
@ExcelProperty(value = "Track ID")
private String trackId;
@ExcelProperty(value = "Country Code")
private String countryCode;
@ExcelProperty(value = "Create Time")
private String createTime;
@ExcelProperty(value = "Update Time")
private String updateTime;
}

View File

@ -1,16 +1,25 @@
package com.red.circle.console.app.service.app.order; package com.red.circle.console.app.service.app.order;
import com.alibaba.excel.EasyExcel;
import com.red.circle.console.app.dto.clienobject.order.InAppPurchaseDetailsCO; import com.red.circle.console.app.dto.clienobject.order.InAppPurchaseDetailsCO;
import com.red.circle.console.app.excel.EasyExcelUtils;
import com.red.circle.console.app.excel.model.order.InAppPurchaseDetailsExportModel;
import com.red.circle.console.infra.database.rds.entity.admin.User; import com.red.circle.console.infra.database.rds.entity.admin.User;
import com.red.circle.console.infra.database.rds.service.admin.UserService; import com.red.circle.console.infra.database.rds.service.admin.UserService;
import com.red.circle.framework.core.asserts.ResponseAssert; import com.red.circle.framework.core.asserts.ResponseAssert;
import com.red.circle.order.inner.asserts.OrderErrorCode; import com.red.circle.order.inner.asserts.OrderErrorCode;
import com.red.circle.order.inner.endpoint.InAppPurchaseDetailsClient; import com.red.circle.order.inner.endpoint.InAppPurchaseDetailsClient;
import com.red.circle.order.inner.model.cmd.inapp.InAppPurchaseDetailsQryCmd; import com.red.circle.order.inner.model.cmd.inapp.InAppPurchaseDetailsQryCmd;
import com.red.circle.order.inner.model.dto.inapp.InAppPurchaseFactoryDTO;
import com.red.circle.order.inner.model.dto.inapp.InAppPurchaseDetailsDTO; import com.red.circle.order.inner.model.dto.inapp.InAppPurchaseDetailsDTO;
import com.red.circle.tool.core.collection.CollectionUtils; import com.red.circle.tool.core.collection.CollectionUtils;
import com.red.circle.tool.core.text.StringUtils;
import com.red.circle.other.inner.model.dto.user.UserProfileDTO; import com.red.circle.other.inner.model.dto.user.UserProfileDTO;
import com.red.circle.other.inner.endpoint.user.user.UserProfileClient; import com.red.circle.other.inner.endpoint.user.user.UserProfileClient;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -133,4 +142,85 @@ public class InAppPurchaseBackServiceImpl implements InAppPurchaseBackService {
); );
} }
@Override
public void exportDetails(HttpServletResponse response, InAppPurchaseDetailsQryCmd query)
throws IOException {
query.setLastId(null);
query.setLimit(null);
query.setExportAll(Boolean.TRUE);
List<InAppPurchaseDetailsExportModel> exportRows = listDetails(query).stream()
.map(this::toExportModel)
.collect(Collectors.toList());
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
String fileName = URLEncoder.encode("InAppPurchaseOrders", StandardCharsets.UTF_8)
.replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
EasyExcel.write(response.getOutputStream(), InAppPurchaseDetailsExportModel.class)
.registerWriteHandler(EasyExcelUtils.getStyleStrategy())
.sheet("Orders")
.doWrite(exportRows);
}
private InAppPurchaseDetailsExportModel toExportModel(InAppPurchaseDetailsCO co) {
InAppPurchaseDetailsDTO details = co.getDetails();
InAppPurchaseFactoryDTO factory = Objects.isNull(details) ? null : details.getFactory();
UserProfileDTO buyer = pickBuyerProfile(co, details);
UserProfileDTO accept = co.getAcceptUserProfile();
return new InAppPurchaseDetailsExportModel()
.setEnv(text(Objects.isNull(details) ? null : details.getEnv()))
.setSysOrigin(text(Objects.isNull(details) ? null : details.getSysOrigin()))
.setPlatform(text(Objects.isNull(factory) ? null : factory.getPlatform()))
.setFactoryCode(text(Objects.isNull(factory) ? null : factory.getFactoryCode()))
.setChannel(text(Objects.isNull(factory) ? null : factory.getFactoryChannelCode()))
.setBuyerUserId(userId(buyer, Objects.isNull(details) ? null : details.getCreateUser()))
.setBuyerAccount(text(Objects.isNull(buyer) ? null : buyer.getAccount()))
.setBuyerNickname(buyerNickname(co, buyer))
.setAcceptUserId(userId(accept, Objects.isNull(details) ? null : details.getAcceptUserId()))
.setAcceptAccount(text(Objects.isNull(accept) ? null : accept.getAccount()))
.setAcceptNickname(text(Objects.isNull(accept) ? null : accept.getUserNickname()))
.setAmountUsd(Objects.isNull(details) ? null : details.getAmountUsd())
.setAmount(Objects.isNull(details) ? null : details.getAmount())
.setCurrency(text(Objects.isNull(details) ? null : details.getCurrency()))
.setProductDescriptor(text(Objects.isNull(details) ? null : details.getProductDescriptor()))
.setStatus(text(Objects.isNull(details) ? null : details.getStatus()))
.setReceiptType(text(Objects.isNull(details) ? null : details.getReceiptType()))
.setOrderId(text(Objects.isNull(details) ? null : details.getOrderId()))
.setAppOrderId(text(Objects.isNull(details) ? null : details.getId()))
.setTrackId(text(Objects.isNull(details) ? null : details.getTrackId()))
.setCountryCode(text(Objects.isNull(details) ? null : details.getCountryCode()))
.setCreateTime(text(Objects.isNull(details) ? null : details.getCreateTime()))
.setUpdateTime(text(Objects.isNull(details) ? null : details.getUpdateTime()));
}
private UserProfileDTO pickBuyerProfile(InAppPurchaseDetailsCO co, InAppPurchaseDetailsDTO details) {
if (Objects.nonNull(co.getCreateUserProfile())) {
return co.getCreateUserProfile();
}
if (Objects.nonNull(details) && Objects.equals(details.getCreateUser(), details.getAcceptUserId())) {
return co.getAcceptUserProfile();
}
return null;
}
private String buyerNickname(InAppPurchaseDetailsCO co, UserProfileDTO buyer) {
if (StringUtils.isNotBlank(co.getCreateNickname())) {
return co.getCreateNickname();
}
return text(Objects.isNull(buyer) ? null : buyer.getUserNickname());
}
private String userId(UserProfileDTO profile, Long fallback) {
if (Objects.nonNull(profile) && Objects.nonNull(profile.getId())) {
return Objects.toString(profile.getId());
}
return text(fallback);
}
private String text(Object value) {
return Objects.isNull(value) ? "" : Objects.toString(value);
}
} }

View File

@ -2,6 +2,8 @@ package com.red.circle.console.app.service.app.order;
import com.red.circle.console.app.dto.clienobject.order.InAppPurchaseDetailsCO; import com.red.circle.console.app.dto.clienobject.order.InAppPurchaseDetailsCO;
import com.red.circle.order.inner.model.cmd.inapp.InAppPurchaseDetailsQryCmd; import com.red.circle.order.inner.model.cmd.inapp.InAppPurchaseDetailsQryCmd;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -27,4 +29,10 @@ public interface InAppPurchaseBackService extends IOrderService {
*/ */
Map<String, Object> statistics(InAppPurchaseDetailsQryCmd query); Map<String, Object> statistics(InAppPurchaseDetailsQryCmd query);
/**
* 按筛选条件导出订单详情不使用分页条件.
*/
void exportDetails(HttpServletResponse response, InAppPurchaseDetailsQryCmd query)
throws IOException;
} }

View File

@ -157,6 +157,7 @@ public class InAppPurchaseDetailsServiceImpl implements InAppPurchaseDetailsServ
public List<InAppPurchaseDetails> list(InAppPurchaseDetailsQuery query) { public List<InAppPurchaseDetails> list(InAppPurchaseDetailsQuery query) {
Criteria criteria = new Criteria(); Criteria criteria = new Criteria();
boolean exportAll = Boolean.TRUE.equals(query.getExportAll());
if (StringUtils.isNotBlank(query.getId())) { if (StringUtils.isNotBlank(query.getId())) {
query.setLastId(null); query.setLastId(null);
@ -205,18 +206,20 @@ public class InAppPurchaseDetailsServiceImpl implements InAppPurchaseDetailsServ
); );
} }
if (StringUtils.isNotBlank(query.getLastId())) { if (!exportAll && StringUtils.isNotBlank(query.getLastId())) {
criteria.and(ID_FIELD).lt(query.getLastId()); criteria.and(ID_FIELD).lt(query.getLastId());
} }
if (Objects.isNull(query.getLimit()) || query.getLimit() <= 0) { Query mongoQuery = Query.query(criteria)
query.setLimit(PageConstant.DEFAULT_LIMIT_SIZE); .with(Sort.by(Sort.Order.desc(ID_FIELD)));
if (!exportAll) {
if (Objects.isNull(query.getLimit()) || query.getLimit() <= 0) {
query.setLimit(PageConstant.DEFAULT_LIMIT_SIZE);
}
mongoQuery.limit(query.getLimit());
} }
return mongoTemplate return mongoTemplate.find(mongoQuery, InAppPurchaseDetails.class);
.find(Query.query(criteria)
.with(Sort.by(Sort.Order.desc(ID_FIELD)))
.limit(query.getLimit()), InAppPurchaseDetails.class);
} }
@Override @Override

View File

@ -100,4 +100,9 @@ public class InAppPurchaseDetailsQuery implements Serializable {
* 条数. * 条数.
*/ */
private Integer limit; private Integer limit;
/**
* 是否导出全部筛选结果导出时不使用 lastId limit 分页.
*/
private Boolean exportAll;
} }

View File

@ -63,17 +63,21 @@ public class InAppPurchaseDetailsClientServiceImpl implements InAppPurchaseDetai
@Override @Override
public List<InAppPurchaseDetailsDTO> list(InAppPurchaseDetailsQryCmd qryCmd) { public List<InAppPurchaseDetailsDTO> list(InAppPurchaseDetailsQryCmd qryCmd) {
boolean exportAll = Boolean.TRUE.equals(qryCmd.getExportAll());
List<InAppPurchaseDetailsDTO> mongoOrders = safeList( List<InAppPurchaseDetailsDTO> mongoOrders = safeList(
inAppPurchaseDetailsInnerConvert.toListInAppPurchaseDetailsDTO( inAppPurchaseDetailsInnerConvert.toListInAppPurchaseDetailsDTO(
inAppPurchaseDetailsService.list( inAppPurchaseDetailsService.list(
inAppPurchaseDetailsInnerConvert.toInAppPurchaseDetailsQuery(qryCmd)) inAppPurchaseDetailsInnerConvert.toInAppPurchaseDetailsQuery(qryCmd))
)); ));
List<InAppPurchaseDetailsDTO> miFaPayOrders = listMiFaPayOrders(qryCmd, true).stream() List<InAppPurchaseDetailsDTO> miFaPayOrders = listMiFaPayOrders(qryCmd, !exportAll).stream()
.map(order -> inAppPurchaseDetailsInnerConvert.toInAppPurchaseDetailsDTO( .map(order -> inAppPurchaseDetailsInnerConvert.toInAppPurchaseDetailsDTO(
toMiFaPayDetails(order, false))) toMiFaPayDetails(order, false)))
.filter(Objects::nonNull) .filter(Objects::nonNull)
.collect(Collectors.toList()); .collect(Collectors.toList());
if (exportAll) {
return mergeAll(mongoOrders, miFaPayOrders);
}
return mergePage(mongoOrders, miFaPayOrders, normalizeLimit(qryCmd.getLimit())); return mergePage(mongoOrders, miFaPayOrders, normalizeLimit(qryCmd.getLimit()));
} }
@ -230,6 +234,13 @@ public class InAppPurchaseDetailsClientServiceImpl implements InAppPurchaseDetai
private List<InAppPurchaseDetailsDTO> mergePage(List<InAppPurchaseDetailsDTO> first, private List<InAppPurchaseDetailsDTO> mergePage(List<InAppPurchaseDetailsDTO> first,
List<InAppPurchaseDetailsDTO> second, int limit) { List<InAppPurchaseDetailsDTO> second, int limit) {
return mergeAll(first, second).stream()
.limit(limit)
.collect(Collectors.toList());
}
private List<InAppPurchaseDetailsDTO> mergeAll(List<InAppPurchaseDetailsDTO> first,
List<InAppPurchaseDetailsDTO> second) {
List<InAppPurchaseDetailsDTO> result = new ArrayList<>(); List<InAppPurchaseDetailsDTO> result = new ArrayList<>();
result.addAll(first); result.addAll(first);
result.addAll(second); result.addAll(second);
@ -237,7 +248,6 @@ public class InAppPurchaseDetailsClientServiceImpl implements InAppPurchaseDetai
.filter(Objects::nonNull) .filter(Objects::nonNull)
.sorted(Comparator.comparing(InAppPurchaseDetailsDTO::getId, .sorted(Comparator.comparing(InAppPurchaseDetailsDTO::getId,
this::compareIdDesc)) this::compareIdDesc))
.limit(limit)
.collect(Collectors.toList()); .collect(Collectors.toList());
} }

View File

@ -8,6 +8,7 @@ import com.red.circle.other.app.dto.clientobject.sys.GameListConfigCO;
import com.red.circle.other.app.dto.cmd.game.GameListQryCmd; import com.red.circle.other.app.dto.cmd.game.GameListQryCmd;
import com.red.circle.other.domain.gateway.user.UserProfileGateway; import com.red.circle.other.domain.gateway.user.UserProfileGateway;
import com.red.circle.other.domain.gateway.user.ability.UserRegionGateway; import com.red.circle.other.domain.gateway.user.ability.UserRegionGateway;
import com.red.circle.other.domain.model.user.UserConsumptionLevel;
import com.red.circle.other.domain.model.user.UserProfile; import com.red.circle.other.domain.model.user.UserProfile;
import com.red.circle.other.infra.database.cache.service.other.EnumConfigCacheService; import com.red.circle.other.infra.database.cache.service.other.EnumConfigCacheService;
import com.red.circle.other.infra.database.cache.service.other.GameListCacheService; import com.red.circle.other.infra.database.cache.service.other.GameListCacheService;
@ -46,6 +47,9 @@ public class GameListConfigQryExe {
private static final String COMMON = "COMMON"; private static final String COMMON = "COMMON";
private static final String IOS_GAME_USER_WHITE_LIST = "IOS_GAME_USER_WHITE_LIST"; private static final String IOS_GAME_USER_WHITE_LIST = "IOS_GAME_USER_WHITE_LIST";
private static final String GAME_LIST_TARGET_SYS_ORIGIN = "LIKEI";
private static final String GAME_LIST_TARGET_COUNTRY_CODE = "TR";
private static final int GAME_LIST_MIN_WEALTH_LEVEL = 2;
private final UserRegionGateway userRegionGateway; private final UserRegionGateway userRegionGateway;
private final UserProfileGateway userProfileGateway; private final UserProfileGateway userProfileGateway;
private final EnumConfigCacheService enumConfigCacheService; private final EnumConfigCacheService enumConfigCacheService;
@ -67,14 +71,60 @@ public class GameListConfigQryExe {
configs = configs.stream().filter(e -> !"CHAT_ROOM".equals(e.getGameMode())).toList(); configs = configs.stream().filter(e -> !"CHAT_ROOM".equals(e.getGameMode())).toList();
} }
// 获得用户区域 if (!canShowGameList(cmd)) {
return Lists.newArrayList();
}
// 获得用户区域配置为空代表全区域配置有值时按逗号拆分后精确匹配区域 ID
String regionId = userRegionGateway.getRegionId(cmd.requiredReqUserId()); String regionId = userRegionGateway.getRegionId(cmd.requiredReqUserId());
return configs.stream() return configs.stream()
.filter(configCO -> .filter(configCO -> matchRegion(configCO.getRegions(), regionId))
StringUtils.isBlank(configCO.getRegions()) || configCO.getRegions() .toList();
.contains(regionId) }
).toList();
private boolean canShowGameList(GameListQryCmd cmd) {
if (!GAME_LIST_TARGET_SYS_ORIGIN.equalsIgnoreCase(cmd.requireReqSysOrigin())) {
return true;
}
// LIKEI 游戏列表需要同时满足用户区域财富等级和当前 IP 国家任一条件不满足直接返回空列表
Long userId = cmd.requiredReqUserId();
if (!GAME_LIST_TARGET_COUNTRY_CODE.equalsIgnoreCase(userRegionGateway.getRegionCode(userId))) {
return false;
}
UserConsumptionLevel level = userProfileGateway.getUserConsumptionLevel(
cmd.requireReqSysOriginEnum(), userId);
int wealthLevel = Optional.ofNullable(level)
.map(UserConsumptionLevel::getWealthLevel)
.orElse(0);
if (wealthLevel < GAME_LIST_MIN_WEALTH_LEVEL) {
return false;
}
return GAME_LIST_TARGET_COUNTRY_CODE.equalsIgnoreCase(ipCountryUtils.getCountryCode(resolveClientIp(cmd)));
}
private boolean matchRegion(String regions, String regionId) {
if (StringUtils.isBlank(regions)) {
return true;
}
if (StringUtils.isBlank(regionId)) {
return false;
}
return Arrays.stream(regions.split(","))
.map(String::trim)
.anyMatch(regionId::equals);
}
private String resolveClientIp(GameListQryCmd cmd) {
if (StringUtils.isNotBlank(cmd.getReqIp())) {
return cmd.getReqIp();
}
return Optional.ofNullable(latestMobileDeviceService.getByUserId(cmd.requiredReqUserId()))
.map(LatestMobileDevice::getIp)
.orElse(null);
} }
private List<GameListConfigCO> isBarrageGame(AppExtCommand cmd, List<GameListConfigCO> configs) { private List<GameListConfigCO> isBarrageGame(AppExtCommand cmd, List<GameListConfigCO> configs) {