Jelajahi Sumber

Merge branch 'master' into pre

zoujiajian 11 jam lalu
induk
melakukan
0733992421

+ 45 - 0
netflix-dao/src/main/java/com/cyksj/model/response/GptRechargeNumRecordResp.java

@@ -0,0 +1,45 @@
+package com.cyksj.model.response;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.Date;
+
+/**
+ * 后台-代充记录查询 返回
+ * 仅暴露: 账号、卡密、时间、用户id、订单号
+ */
+@Getter
+@Setter
+public class GptRechargeNumRecordResp {
+
+    /**
+     * 记录id(前端表格 row-key 用)
+     */
+    private Long id;
+
+    /**
+     * 用户id(车票所属用户)
+     */
+    private Long userId;
+
+    /**
+     * 订单号(按车票+时间归属到对应订单, 续费订单会归属到续费单)
+     */
+    private String orderNo;
+
+    /**
+     * 代充账号
+     */
+    private String account;
+
+    /**
+     * 卡密
+     */
+    private String cardKey;
+
+    /**
+     * 时间
+     */
+    private Date createdTime;
+}

+ 2 - 4
netflix-web/src/main/java/com/cyksj/web/controller/group/MidJourneyGuideController.java

@@ -7,8 +7,6 @@ import com.cyksj.service.ticket.UserReceiveTrialTicketRecordService;
 import com.cyksj.web.util.StpUserUtil;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
@@ -28,7 +26,7 @@ public class MidJourneyGuideController {
 	/**
 	 * 领取mj试用车票
 	 */
-	@PostMapping("/receive/trial")
+//	@PostMapping("/receive/trial")
 	public Result<String> receiveTrialTicket() {
 		long userId = StpUserUtil.getLoginIdAsLong();
 		userReceiveTrialTicketRecordService.sendTicketAndRecord(userId, Constant.MIDJOURNEY_TRIAL_SKU_ID);
@@ -38,7 +36,7 @@ public class MidJourneyGuideController {
 	/**
 	 * 是否已领取mj试用车票
 	 */
-	@GetMapping("/hasReceived")
+//	@GetMapping("/hasReceived")
 	public Result<Boolean> isReceiveTrialTicket() {
 		long userId = StpUserUtil.getLoginIdAsLong();
 		Boolean flag = userReceiveTrialTicketRecordService.isReceiveTrialTicket(userId, Constant.MIDJOURNEY_TRIAL_SKU_ID);

+ 178 - 0
netflix-web/src/main/java/com/cyksj/web/controller/manage/group/CmsGroupRelationController.java

@@ -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;
+    }
+
     /**
      * 编辑代充备注
      */

+ 0 - 1
netflix-web/src/main/java/com/cyksj/web/controller/user/UserController.java

@@ -13,7 +13,6 @@ import com.cyksj.enums.GatewayApiCode;
 import com.cyksj.enums.GatewayResponse;
 import com.cyksj.mapper.*;
 import com.cyksj.mapper.manage.coupon.CouponUserMapper;
-import com.cyksj.mapper.manage.exchange.ExchangeCodeMapper;
 import com.cyksj.mapper.manage.distribute.*;
 import com.cyksj.mapper.manage.exchange.ExchangeCodeMapper;
 import com.cyksj.mapper.mirrorshop.UserMirrorShopMapper;