|
|
@@ -10,6 +10,7 @@ import cn.hutool.core.lang.Assert;
|
|
|
import cn.hutool.core.util.ObjectUtil;
|
|
|
import cn.hutool.core.util.StrUtil;
|
|
|
import com.alibaba.excel.support.ExcelTypeEnum;
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
|
|
import com.cyksj.common.annotation.Log;
|
|
|
import com.cyksj.common.annotation.NoSubmit;
|
|
|
@@ -30,6 +31,7 @@ import com.cyksj.model.manage.views.AccountView;
|
|
|
import com.cyksj.model.manage.views.GroupsRelationBackView;
|
|
|
import com.cyksj.model.request.*;
|
|
|
import com.cyksj.model.response.BatchRechargeRemainNumResp;
|
|
|
+import com.cyksj.model.response.GptRechargeNumRecordResp;
|
|
|
import com.cyksj.model.response.GptRechargeStatisticsRecordResp;
|
|
|
import com.cyksj.model.views.*;
|
|
|
import com.cyksj.server.recharge.dto.TaskResult;
|
|
|
@@ -55,6 +57,7 @@ import org.springframework.web.bind.annotation.*;
|
|
|
import javax.servlet.http.HttpServletRequest;
|
|
|
import javax.servlet.http.HttpServletResponse;
|
|
|
import java.util.ArrayList;
|
|
|
+import java.util.Comparator;
|
|
|
import java.util.Date;
|
|
|
import java.util.HashMap;
|
|
|
import java.util.HashSet;
|
|
|
@@ -63,6 +66,7 @@ import java.util.Map;
|
|
|
import java.util.Objects;
|
|
|
import java.util.Optional;
|
|
|
import java.util.Set;
|
|
|
+import java.util.regex.Pattern;
|
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
/**
|
|
|
@@ -106,6 +110,23 @@ public class CmsGroupRelationController {
|
|
|
|
|
|
private final GptRechargeCardKeyChannelMapper gptRechargeCardKeyChannelMapper;
|
|
|
|
|
|
+ private final GptUserRechargeNumRecordMapper gptUserRechargeNumRecordMapper;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 代充记录查询: 订单号只允许字母数字/下划线/中划线, 防止异常输入
|
|
|
+ */
|
|
|
+ private static final Pattern RECHARGE_RECORD_ORDER_NO = Pattern.compile("^[A-Za-z0-9_-]{1,64}$");
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 代充记录查询: 单次最多返回条数, 防止按用户拉全量
|
|
|
+ */
|
|
|
+ private static final int RECHARGE_RECORD_LIMIT = 500;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 未支付/已关闭的订单不参与代充记录的订单归属
|
|
|
+ */
|
|
|
+ private static final List<String> RECHARGE_RECORD_IGNORE_ORDER_STATUS = List.of("noPayment", "close");
|
|
|
+
|
|
|
private List<Long> getOrderRelationIds(OrderDon orderDon) {
|
|
|
List<Long> relationIds = new ArrayList<>();
|
|
|
if (orderDon.getNum() != null && orderDon.getNum() > 1) {
|
|
|
@@ -605,6 +626,163 @@ public class CmsGroupRelationController {
|
|
|
EasyExcelUtils.createExcelStreamMutilByEasyExcel(response, ExcelGptRechargeStatisticsData.class, list, "代充操作统计数据", "代充操作统计数据", ExcelTypeEnum.XLSX, null);
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 代充记录查询
|
|
|
+ * 按订单号 / 用户ID 查询 gpt_user_recharge_num_record, 至少传一个; 两个都传时取交集
|
|
|
+ * 返回: 账号、卡密、时间、用户id、订单号
|
|
|
+ */
|
|
|
+ @SaCheckPermission("recharge:numRecord")
|
|
|
+ @GetMapping("/get/recharge/num/record")
|
|
|
+ @Log(module = "账号管理/用户代充/代充记录查询", businessType = BusinessType.OTHER, isSaveRequestData = true)
|
|
|
+ public Result<List<GptRechargeNumRecordResp>> getRechargeNumRecord(@RequestParam(required = false) String orderNo,
|
|
|
+ @RequestParam(required = false) Long userId) {
|
|
|
+ orderNo = StrUtil.trim(orderNo);
|
|
|
+ if (StrUtil.isEmpty(orderNo) && userId == null) {
|
|
|
+ throw BusinessRuntimeException.getInstance("请输入订单号或用户ID");
|
|
|
+ }
|
|
|
+ if (StrUtil.isNotEmpty(orderNo) && !RECHARGE_RECORD_ORDER_NO.matcher(orderNo).matches()) {
|
|
|
+ throw BusinessRuntimeException.getInstance("订单号格式不正确");
|
|
|
+ }
|
|
|
+ if (userId != null && userId <= 0) {
|
|
|
+ throw BusinessRuntimeException.getInstance("用户ID不正确");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 只查需要返回的列, 不把 operator/remark 等多余字段带出来
|
|
|
+ LambdaQueryWrapper<GptUserRechargeNumRecord> wrapper = Wrappers.lambdaQuery(GptUserRechargeNumRecord.class)
|
|
|
+ .select(GptUserRechargeNumRecord::getId, GptUserRechargeNumRecord::getUserId, GptUserRechargeNumRecord::getRelationId,
|
|
|
+ GptUserRechargeNumRecord::getAccount, GptUserRechargeNumRecord::getCardKey, GptUserRechargeNumRecord::getCreatedTime);
|
|
|
+
|
|
|
+ OrderDon queryOrder = null;
|
|
|
+ if (StrUtil.isNotEmpty(orderNo)) {
|
|
|
+ queryOrder = orderDonMapper.selectOne(Wrappers.lambdaQuery(OrderDon.class)
|
|
|
+ .eq(OrderDon::getOrderNo, orderNo)
|
|
|
+ .last("limit 1"));
|
|
|
+ if (queryOrder == null) {
|
|
|
+ throw BusinessRuntimeException.getInstance("订单不存在");
|
|
|
+ }
|
|
|
+ // relation_id 存的是车票id, 不是订单号; 多数量订单一个订单对应多张车票
|
|
|
+ wrapper.in(GptUserRechargeNumRecord::getRelationId, getOrderRelationIds(queryOrder));
|
|
|
+ // 车票可能被前一个订单/前一个用户使用过, 只取本订单创建之后的记录
|
|
|
+ if (queryOrder.getCreatedTime() != null) {
|
|
|
+ wrapper.ge(GptUserRechargeNumRecord::getCreatedTime, queryOrder.getCreatedTime());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (userId != null) {
|
|
|
+ wrapper.eq(GptUserRechargeNumRecord::getUserId, userId);
|
|
|
+ }
|
|
|
+ wrapper.orderByDesc(GptUserRechargeNumRecord::getCreatedTime)
|
|
|
+ .orderByDesc(GptUserRechargeNumRecord::getId)
|
|
|
+ .last("limit " + RECHARGE_RECORD_LIMIT);
|
|
|
+
|
|
|
+ List<GptUserRechargeNumRecord> records = gptUserRechargeNumRecordMapper.selectList(wrapper);
|
|
|
+ List<GptRechargeNumRecordResp> result = new ArrayList<>();
|
|
|
+ if (records.isEmpty()) {
|
|
|
+ return GatewayResponse.SUCCESS.newBuilder().toResult(result);
|
|
|
+ }
|
|
|
+
|
|
|
+ Set<Long> relationIds = records.stream()
|
|
|
+ .map(GptUserRechargeNumRecord::getRelationId)
|
|
|
+ .filter(Objects::nonNull)
|
|
|
+ .collect(Collectors.toSet());
|
|
|
+ Map<Long, List<OrderDon>> relationOrders = getRechargeRecordRelationOrders(relationIds, queryOrder);
|
|
|
+
|
|
|
+ for (GptUserRechargeNumRecord record : records) {
|
|
|
+ OrderDon owner = matchRechargeRecordOrder(relationOrders.get(record.getRelationId()), record.getCreatedTime());
|
|
|
+ // 按订单号查时, 同一车票上后续续费订单产生的记录不算本订单的
|
|
|
+ if (queryOrder != null && (owner == null || !Objects.equals(owner.getId(), queryOrder.getId()))) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ GptRechargeNumRecordResp resp = new GptRechargeNumRecordResp();
|
|
|
+ resp.setId(record.getId());
|
|
|
+ resp.setUserId(record.getUserId());
|
|
|
+ resp.setOrderNo(owner != null ? owner.getOrderNo() : null);
|
|
|
+ resp.setAccount(record.getAccount());
|
|
|
+ resp.setCardKey(record.getCardKey());
|
|
|
+ resp.setCreatedTime(record.getCreatedTime());
|
|
|
+ result.add(resp);
|
|
|
+ }
|
|
|
+ return GatewayResponse.SUCCESS.newBuilder().toResult(result);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 车票id -> 该车票上的订单(按创建时间升序), 包含单数量订单(order_don.relation_id)和多数量订单(order_don_ticket_record)
|
|
|
+ */
|
|
|
+ private Map<Long, List<OrderDon>> getRechargeRecordRelationOrders(Set<Long> relationIds, OrderDon queryOrder) {
|
|
|
+ Map<Long, List<OrderDon>> relationOrders = new HashMap<>();
|
|
|
+ if (relationIds.isEmpty()) {
|
|
|
+ return relationOrders;
|
|
|
+ }
|
|
|
+ Map<Long, OrderDon> orderMap = new HashMap<>();
|
|
|
+ Map<Long, Set<Long>> relationOrderIds = new HashMap<>();
|
|
|
+
|
|
|
+ orderDonMapper.selectList(Wrappers.lambdaQuery(OrderDon.class)
|
|
|
+ .select(OrderDon::getId, OrderDon::getOrderNo, OrderDon::getRelationId, OrderDon::getCreatedTime)
|
|
|
+ .in(OrderDon::getRelationId, relationIds)
|
|
|
+ .notIn(OrderDon::getStatus, RECHARGE_RECORD_IGNORE_ORDER_STATUS))
|
|
|
+ .forEach(order -> {
|
|
|
+ orderMap.put(order.getId(), order);
|
|
|
+ relationOrderIds.computeIfAbsent(order.getRelationId(), k -> new HashSet<>()).add(order.getId());
|
|
|
+ });
|
|
|
+
|
|
|
+ List<OrderDonTicketRecord> tickets = orderDonTicketRecordMapper.selectList(Wrappers.lambdaQuery(OrderDonTicketRecord.class)
|
|
|
+ .select(OrderDonTicketRecord::getOrderId, OrderDonTicketRecord::getRelationId)
|
|
|
+ .in(OrderDonTicketRecord::getRelationId, relationIds)
|
|
|
+ .eq(OrderDonTicketRecord::getStatus, OrderDonTicketRecord.Status.success));
|
|
|
+ Set<Long> missingOrderIds = tickets.stream()
|
|
|
+ .map(OrderDonTicketRecord::getOrderId)
|
|
|
+ .filter(id -> id != null && !orderMap.containsKey(id))
|
|
|
+ .collect(Collectors.toSet());
|
|
|
+ if (!missingOrderIds.isEmpty()) {
|
|
|
+ orderDonMapper.selectList(Wrappers.lambdaQuery(OrderDon.class)
|
|
|
+ .select(OrderDon::getId, OrderDon::getOrderNo, OrderDon::getRelationId, OrderDon::getCreatedTime)
|
|
|
+ .in(OrderDon::getId, missingOrderIds)
|
|
|
+ .notIn(OrderDon::getStatus, RECHARGE_RECORD_IGNORE_ORDER_STATUS))
|
|
|
+ .forEach(order -> orderMap.put(order.getId(), order));
|
|
|
+ }
|
|
|
+ for (OrderDonTicketRecord ticket : tickets) {
|
|
|
+ if (ticket.getOrderId() != null && orderMap.containsKey(ticket.getOrderId())) {
|
|
|
+ relationOrderIds.computeIfAbsent(ticket.getRelationId(), k -> new HashSet<>()).add(ticket.getOrderId());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 被查询的订单无论状态如何都参与归属
|
|
|
+ if (queryOrder != null) {
|
|
|
+ orderMap.put(queryOrder.getId(), queryOrder);
|
|
|
+ for (Long relationId : getOrderRelationIds(queryOrder)) {
|
|
|
+ if (relationIds.contains(relationId)) {
|
|
|
+ relationOrderIds.computeIfAbsent(relationId, k -> new HashSet<>()).add(queryOrder.getId());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ Comparator<OrderDon> byCreated = Comparator.<OrderDon, Date>comparing(OrderDon::getCreatedTime, Comparator.nullsFirst(Comparator.<Date>naturalOrder()))
|
|
|
+ .thenComparing(OrderDon::getId, Comparator.nullsFirst(Comparator.<Long>naturalOrder()));
|
|
|
+ relationOrderIds.forEach((relationId, orderIds) -> relationOrders.put(relationId, orderIds.stream()
|
|
|
+ .map(orderMap::get)
|
|
|
+ .filter(Objects::nonNull)
|
|
|
+ .sorted(byCreated)
|
|
|
+ .collect(Collectors.toList())));
|
|
|
+ return relationOrders;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 记录归属: 同一车票上, 记录时间之前最近创建的那个订单
|
|
|
+ */
|
|
|
+ private OrderDon matchRechargeRecordOrder(List<OrderDon> orders, Date recordTime) {
|
|
|
+ if (CollUtil.isEmpty(orders) || recordTime == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ OrderDon matched = null;
|
|
|
+ for (OrderDon order : orders) {
|
|
|
+ if (order.getCreatedTime() == null || !order.getCreatedTime().after(recordTime)) {
|
|
|
+ matched = order;
|
|
|
+ } else {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return matched;
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 编辑代充备注
|
|
|
*/
|