Browse Source

Merge branch 'codex/multi-quantity-orders' into pre

# Conflicts:
#	netflix-dao/src/main/java/com/cyksj/mapper/order/OrderDonTicketQueryMapper.java
#	netflix-dao/src/main/java/com/cyksj/mapper/order/OrderDonTicketRecordMapper.java
zoujiajian 2 weeks ago
parent
commit
8f2c9701de

+ 2 - 2
netflix-dao/src/main/java/com/cyksj/mapper/order/OrderDonTicketQueryMapper.java

@@ -10,7 +10,7 @@ public interface OrderDonTicketQueryMapper {
 
     @Select({
             "<script>",
-            "select r.item_index as itemIndex, r.relation_id as relationId,",
+            "select r.item_index as itemIndex, r.active_relation_id as relationId,",
             "r.status as deliveryStatus, gr.status as relationStatus,",
             "gr.start_time as startTime, gr.expiry_time as expiryTime,",
             "<choose>",
@@ -25,7 +25,7 @@ public interface OrderDonTicketQueryMapper {
             "<otherwise>null as account, null as password</otherwise>",
             "</choose>",
             "from order_don_ticket_record r",
-            "left join groups_relation gr on gr.id = r.relation_id",
+            "left join groups_relation gr on gr.id = r.active_relation_id",
             "left join groups_trips gt on gt.id = gr.groups_id",
             "left join account a on a.id = gt.account_id",
             "where r.order_id = #{orderId}",

+ 14 - 6
netflix-dao/src/main/java/com/cyksj/mapper/order/OrderDonTicketRecordMapper.java

@@ -63,7 +63,9 @@ public interface OrderDonTicketRecordMapper extends BaseMapper<OrderDonTicketRec
     OrderDonTicketRecord lockClaim(@Param("id") Long id,
                                    @Param("workerToken") String workerToken);
 
-    @Update("update order_don_ticket_record set status = 'success', relation_id = #{relationId}, worker_token = null, processing_time = null, error_msg = null, update_time = now() where id = #{id} and status = 'processing' and worker_token = #{workerToken}")
+    @Update("update order_don_ticket_record set status = 'success', relation_id = #{relationId}, " +
+            "worker_token = null, processing_time = null, error_msg = null, update_time = now() " +
+            "where id = #{id} and status = 'processing' and worker_token = #{workerToken}")
     int markSuccess(@Param("id") Long id, @Param("relationId") Long relationId, @Param("workerToken") String workerToken);
 
     @Update("update order_don_ticket_record set status = 'failed', worker_token = null, processing_time = null, error_msg = #{errorMsg}, update_time = now() where id = #{id} and status = 'processing' and worker_token = #{workerToken}")
@@ -117,23 +119,29 @@ public interface OrderDonTicketRecordMapper extends BaseMapper<OrderDonTicketRec
     int replaceActiveRelation(@Param("oldRelationId") Long oldRelationId,
                               @Param("newRelationId") Long newRelationId);
 
-    @Select("select r.* from order_don_ticket_record r inner join groups_relation gr on gr.id = r.relation_id where gr.groups_id = #{groupsId} and r.status = 'success' order by r.item_index")
+    @Select("select r.* from order_don_ticket_record r inner join groups_relation gr " +
+            "on gr.id = r.active_relation_id where gr.groups_id = #{groupsId} " +
+            "and r.status = 'success' order by r.item_index")
     List<OrderDonTicketRecord> selectSuccessfulByGroupsId(@Param("groupsId") Long groupsId);
 
     @Select("select distinct r.order_id from order_don_ticket_record r " +
             "inner join order_don o on o.id = r.order_id " +
-            "inner join groups_relation gr on gr.id = r.relation_id " +
+            "inner join groups_relation gr on gr.id = r.active_relation_id " +
             "where gr.groups_id = #{groupsId} and r.status = 'success' " +
             "and o.status in ('hasPayment', 'complete') order by r.order_id")
     List<Long> selectSuccessfulOrderIdsByGroupsId(@Param("groupsId") Long groupsId);
 
-    @Update("update order_don_ticket_record set status = 'cleared', worker_token = null, processing_time = null, error_msg = null, update_time = now() where active_relation_id = #{relationId} and status = 'success'")
+    @Update("update order_don_ticket_record set status = 'cleared', worker_token = null, " +
+            "processing_time = null, error_msg = null, update_time = now() " +
+            "where active_relation_id = #{relationId} and status = 'success'")
     int clearActiveRelation(@Param("relationId") Long relationId);
 
-    @Select("select * from order_don_ticket_record where active_relation_id = #{relationId} limit 1")
+    @Select("select * from order_don_ticket_record where active_relation_id = #{relationId} " +
+            "and status in ('success', 'refunding')")
     OrderDonTicketRecord selectActiveByRelationId(@Param("relationId") Long relationId);
 
-    @Select("select * from order_don_ticket_record where active_relation_id = #{relationId} limit 1 for update")
+    @Select("select * from order_don_ticket_record where active_relation_id = #{relationId} " +
+            "and status in ('success', 'refunding') for update")
     OrderDonTicketRecord selectActiveByRelationIdForUpdate(@Param("relationId") Long relationId);
 
     @Select("select count(*) from order_don_ticket_record where order_id = #{orderId} and status = #{status}")

+ 8 - 0
netflix-dao/src/main/resources/mapper/GroupRelationMapper.xml

@@ -156,6 +156,10 @@
         where groups_id = #{groupsId}
           and user_id = 0
           and status = 'none'
+          and not exists (
+              select 1 from order_don_ticket_record ticket
+              where ticket.active_relation_id = groups_relation.id
+          )
         order by rand() limit 1
     </select>
 
@@ -177,6 +181,10 @@
           and user_id = 0
           and num > #{relationNum}
           and status = 'outside'
+          and not exists (
+              select 1 from order_don_ticket_record ticket
+              where ticket.active_relation_id = groups_relation.id
+          )
         order by rand() limit 1
     </select>
 

+ 1 - 1
netflix-service/src/main/java/com/cyksj/service/mange/impl/CmsOrderDonServiceImpl.java

@@ -848,7 +848,7 @@ public class CmsOrderDonServiceImpl extends ServiceImpl<OrderDonMapper,OrderDon>
         if (!isMultiQuantityTicketOwner(orderDon, relation)) {
             throw BusinessRuntimeException.getInstance("车票归属异常,relationId: {0}", relation.getId());
         }
-        relationClearService.clearTicket(relation, UserTicketClearedRecord.Source.refund);
+        relationClearService.clearRefundTicket(relation, activeRecord);
     }
 
     private Long currentTicketRelationId(OrderDonTicketRecord record) {

+ 1 - 18
netflix-service/src/main/java/com/cyksj/service/order/impl/MultiQuantityOrderServiceImpl.java

@@ -425,9 +425,8 @@ public class MultiQuantityOrderServiceImpl implements MultiQuantityOrderService
 		if (!ticketRecordService.markSuccess(recordId, relation.getId(), workerToken)) {
 			throw new IllegalStateException("ticket slot claim expired");
 		}
-        OrderDon oneTicketOrder = copyForOneTicket(order, relation.getId());
         try {
-            expiryRecordService.syncGroupsRelationExpiryRecord(oneTicketOrder, goods, sku);
+			expiryRecordService.syncGroupsRelationExpiryRecord(order, relation.getId(), goods, sku);
         } catch (DuplicateKeyException ignored) {
             // The expiry record is ancillary; slot ownership is already durable.
 		}
@@ -560,22 +559,6 @@ public class MultiQuantityOrderServiceImpl implements MultiQuantityOrderService
                 com.cyksj.model.entity.OrderDonGalaxyCoinRecord.Source.ordinary);
     }
 
-    private OrderDon copyForOneTicket(OrderDon source, Long relationId) {
-        OrderDon copy = new OrderDon();
-        copy.setId(source.getId());
-        copy.setOrderNo(source.getOrderNo());
-        copy.setGoodsId(source.getGoodsId());
-        copy.setSkuId(source.getSkuId());
-        copy.setUserId(source.getUserId());
-        copy.setPayTime(source.getPayTime());
-        copy.setCreatedTime(source.getCreatedTime());
-        copy.setRelationId(relationId);
-        copy.setNum(1);
-        copy.setOrderType(1);
-		copy.setYhsId(source.getYhsId() == null ? 0L : source.getYhsId());
-        return copy;
-    }
-
     private void afterCommit(Runnable action) {
         if (!TransactionSynchronizationManager.isSynchronizationActive()) {
             action.run();

+ 9 - 0
netflix-service/src/main/java/com/cyksj/service/relation/GroupRelationClearService.java

@@ -1,6 +1,7 @@
 package com.cyksj.service.relation;
 
 import com.cyksj.model.entity.GroupsRelation;
+import com.cyksj.model.entity.OrderDonTicketRecord;
 import com.cyksj.model.entity.UserTicketClearedRecord;
 
 /*
@@ -16,6 +17,14 @@ public interface GroupRelationClearService {
 	 */
 	void clearTicket(GroupsRelation relation, UserTicketClearedRecord.Source source);
 
+	/**
+	 * Clears one ticket as part of a frozen multi-quantity whole-order refund.
+	 * The caller supplies the already locked slot so ownership is checked once
+	 * without issuing a duplicate FOR UPDATE query. This method must be called
+	 * inside the same transaction that acquired that slot lock.
+	 */
+	void clearRefundTicket(GroupsRelation relation, OrderDonTicketRecord refundingTicket);
+
 	/**
 	 * 续费换车票,清理之前的车票
 	 */

+ 8 - 0
netflix-service/src/main/java/com/cyksj/service/relation/GroupsRelationExpiryRecordService.java

@@ -14,4 +14,12 @@ import com.cyksj.model.entity.OrderDon;
  */
 public interface GroupsRelationExpiryRecordService extends IService<GroupsRelationExpiryRecord> {
 	void syncGroupsRelationExpiryRecord(OrderDon orderDon, GoodsDon goodsDon, GoodsDonSku sku);
+
+	/**
+	 * Sync the renewal-follow record for one ticket of an order without fabricating a
+	 * one-ticket {@link OrderDon}. This overload is used by multi-quantity delivery:
+	 * all tickets still belong to the same order in {@code order_don}.
+	 */
+	void syncGroupsRelationExpiryRecord(OrderDon orderDon, Long relationId,
+			GoodsDon goodsDon, GoodsDonSku sku);
 }

+ 1 - 0
netflix-service/src/main/java/com/cyksj/service/relation/impl/CmsGroupsRelationServiceImpl.java

@@ -152,6 +152,7 @@ public class CmsGroupsRelationServiceImpl implements CmsGroupsRelationService {
 				.eq(GroupsRelation::getGroupsId, groupsId)
 				.eq(GroupsRelation::getStatus, GroupsRelation.Status.none)
 				.eq(GroupsRelation::getUserId, 0)
+				.apply("not exists (select 1 from order_don_ticket_record ticket where ticket.active_relation_id = groups_relation.id)")
 				.orderByAsc(GroupsRelation::getNum)
 				.last("limit 1"));
 		if (relation != null) {

+ 21 - 4
netflix-service/src/main/java/com/cyksj/service/relation/impl/GroupRelationClearServiceImpl.java

@@ -64,16 +64,33 @@ public class GroupRelationClearServiceImpl implements GroupRelationClearService
 
 	@Override
 	public void clearTicket(GroupsRelation relation, UserTicketClearedRecord.Source source) {
+		clearTicket(relation, source, null);
+	}
+
+	@Override
+	public void clearRefundTicket(GroupsRelation relation, OrderDonTicketRecord refundingTicket) {
+		if (relation == null || relation.getId() == null || refundingTicket == null
+				|| refundingTicket.getId() == null
+				|| refundingTicket.getStatus() != OrderDonTicketRecord.Status.refunding
+				|| !relation.getId().equals(refundingTicket.getRelationId())) {
+			throw new IllegalArgumentException("valid refunding ticket is required");
+		}
+		clearTicket(relation, UserTicketClearedRecord.Source.refund, refundingTicket);
+	}
+
+	private void clearTicket(GroupsRelation relation, UserTicketClearedRecord.Source source,
+							 OrderDonTicketRecord lockedActiveTicket) {
 		Long relationId = relation.getId();
 		Long newRelationId = relation.getNewRelationId();
 		Integer num = relation.getNum();
 		final GroupsRelation.Status relationStatus = relation.getStatus();
-		OrderDonTicketRecord activeTicket = orderDonTicketRecordService
-				.getActiveByRelationIdForUpdate(relationId);
+		OrderDonTicketRecord activeTicket = lockedActiveTicket != null
+				? lockedActiveTicket
+				: orderDonTicketRecordService.getActiveByRelationIdForUpdate(relationId);
 		if (activeTicket != null
 				&& activeTicket.getStatus() == OrderDonTicketRecord.Status.refunding
-				&& source != UserTicketClearedRecord.Source.refund) {
-			throw new IllegalStateException("ticket refund is processing");
+				&& (source != UserTicketClearedRecord.Source.refund || lockedActiveTicket == null)) {
+			throw new IllegalStateException("refunding ticket must be cleared by its locked slot");
 		}
 		//记录用户被清除车票历史记录
 		GroupsRelationView groupsRelation = beanSearcher.searchFirst(GroupsRelationView.class,

+ 28 - 25
netflix-service/src/main/java/com/cyksj/service/relation/impl/GroupRelationFrontServiceImpl.java

@@ -294,7 +294,7 @@ public class GroupRelationFrontServiceImpl implements GroupRelationFrontService
 			Long orderId = orderDon != null ? orderDon.getId() : 0l;
 			conditionSql += String.format(" and (exists(select 1 from order_don o where o.relation_id = gr.id and o.user_id = gr.user_id and o.id = %s)" +
 					" or exists(select 1 from order_don_ticket_record r inner join order_don o on o.id = r.order_id" +
-					" where r.relation_id = gr.id and r.status = 'success' and o.user_id = gr.user_id and o.id = %s))",
+					" where r.active_relation_id = gr.id and r.status = 'success' and o.user_id = gr.user_id and o.id = %s))",
 					orderId, orderId);
 		}
 		SearchResult<RenewalView> list = beanSearcher.search(RenewalView.class, builder
@@ -398,23 +398,7 @@ public class GroupRelationFrontServiceImpl implements GroupRelationFrontService
 					ticket.setRemainDays(remainDays);
 					ticket.setTotal(DateUtil.betweenDay(ticket.getStartTime(), ticket.getExpiryTime(), false) + 1);
 				}
-				OrderDon orderDon = orderDonMapper.selectOne(Wrappers.lambdaQuery(OrderDon.class)
-						.eq(OrderDon::getRelationId, ticket.getRelationId())
-						.in(OrderDon::getUserId, userIds)
-						.eq(OrderDon::getOrderType, 1)
-						.notIn(OrderDon::getStatus, Constant.noOrderAllStatus)
-						.orderByDesc(OrderDon::getId)
-						.last("limit 1"));
-				if (orderDon == null) {
-					OrderDonTicketRecord multiTicket = orderDonTicketRecordService.getActiveByRelationId(ticket.getRelationId());
-					if (multiTicket != null) {
-						orderDon = orderDonMapper.selectOne(Wrappers.lambdaQuery(OrderDon.class)
-								.eq(OrderDon::getId, multiTicket.getOrderId())
-								.in(OrderDon::getUserId, userIds)
-								.notIn(OrderDon::getStatus, Constant.noOrderAllStatus)
-								.last("limit 1"));
-					}
-				}
+				OrderDon orderDon = getCurrentTicketOrder(ticket.getRelationId(), userIds);
 				Long zhSkuId = ticket.getSkuId();
 				if (orderDon == null) {
 					if (ticket.getGoodsId() == Constant.NETFLIX_GID) {
@@ -643,6 +627,7 @@ public class GroupRelationFrontServiceImpl implements GroupRelationFrontService
 				.eq(GroupsRelation::getGroupsId, groupsId)
 				.eq(GroupsRelation::getStatus, GroupsRelation.Status.none)
 				.eq(GroupsRelation::getUserId, 0)
+				.apply("not exists (select 1 from order_don_ticket_record ticket where ticket.active_relation_id = groups_relation.id)")
 				.orderByAsc(GroupsRelation::getNum)
 				.last("limit 1"));
 		if (relation != null) {
@@ -725,6 +710,7 @@ public class GroupRelationFrontServiceImpl implements GroupRelationFrontService
 							.eq(GroupsRelation::getGroupsId, groupsId)
 							.eq(GroupsRelation::getUserId, 0)
 							.eq(GroupsRelation::getStatus, GroupsRelation.Status.outside)
+							.apply("not exists (select 1 from order_don_ticket_record ticket where ticket.active_relation_id = groups_relation.id)")
 							.last("limit 1"));
 					if (relation == null) {
 						relation = new GroupsRelation();
@@ -2138,13 +2124,7 @@ public class GroupRelationFrontServiceImpl implements GroupRelationFrontService
 				}
 			}
 
-			OrderDon orderDon = orderDonMapper.selectOne(Wrappers.lambdaQuery(OrderDon.class)
-					.eq(OrderDon::getRelationId, ticket.getRelationId())
-					.eq(OrderDon::getUserId, ticket.getUserId())
-					.eq(OrderDon::getOrderType, 1)
-					.notIn(OrderDon::getStatus, Constant.noOrderAllStatus)
-					.orderByDesc(OrderDon::getId)
-					.last("limit 1"));
+			OrderDon orderDon = getCurrentTicketOrder(ticket.getRelationId(), userIds);
 			if (orderDon != null) {
 				ticket.setOrderId(orderDon.getId());
 			}
@@ -2153,6 +2133,29 @@ public class GroupRelationFrontServiceImpl implements GroupRelationFrontService
 		return list;
 	}
 
+	/**
+	 * Resolve the order that currently owns a ticket. A relation id can be reused
+	 * after an old ticket expires or is cleared, so an active multi-quantity slot
+	 * must take precedence over historical order_don.relation_id rows.
+	 */
+	private OrderDon getCurrentTicketOrder(Long relationId, List<Long> userIds) {
+		OrderDonTicketRecord activeTicket = orderDonTicketRecordService.getActiveByRelationId(relationId);
+		if (activeTicket != null) {
+			return orderDonMapper.selectOne(Wrappers.lambdaQuery(OrderDon.class)
+					.eq(OrderDon::getId, activeTicket.getOrderId())
+					.in(OrderDon::getUserId, userIds)
+					.notIn(OrderDon::getStatus, Constant.noOrderAllStatus)
+					.last("limit 1"));
+		}
+		return orderDonMapper.selectOne(Wrappers.lambdaQuery(OrderDon.class)
+				.eq(OrderDon::getRelationId, relationId)
+				.in(OrderDon::getUserId, userIds)
+				.eq(OrderDon::getOrderType, 1)
+				.notIn(OrderDon::getStatus, Constant.noOrderAllStatus)
+				.orderByDesc(OrderDon::getId)
+				.last("limit 1"));
+	}
+
 	@Override
 	public TicketRenewUpgradeView getRenewUpgradeInfo(long userId, Long relationId) {
 		List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);

+ 2 - 0
netflix-service/src/main/java/com/cyksj/service/relation/impl/GroupsRelationChangeServiceImpl.java

@@ -77,6 +77,8 @@ public class GroupsRelationChangeServiceImpl implements GroupsRelationChangeServ
 			relation = relationMapper.selectOne(Wrappers.lambdaQuery(GroupsRelation.class)
 					.eq(GroupsRelation::getGroupsId, groupsTrips.getId())
 					.eq(GroupsRelation::getStatus, GroupsRelation.Status.none)
+					.eq(GroupsRelation::getUserId, 0)
+					.apply("not exists (select 1 from order_don_ticket_record ticket where ticket.active_relation_id = groups_relation.id)")
 					.last("limit 1"));
 		}
 		if (relation == null) {

+ 7 - 1
netflix-service/src/main/java/com/cyksj/service/relation/impl/GroupsRelationExpiryRecordServiceImpl.java

@@ -29,11 +29,17 @@ public class GroupsRelationExpiryRecordServiceImpl extends ServiceImpl<GroupsRel
 
 	@Override
 	public void syncGroupsRelationExpiryRecord(OrderDon orderDon, GoodsDon goodsDon, GoodsDonSku sku) {
+		syncGroupsRelationExpiryRecord(orderDon, orderDon.getRelationId(), goodsDon, sku);
+	}
+
+	@Override
+	public void syncGroupsRelationExpiryRecord(OrderDon orderDon, Long relationId,
+			GoodsDon goodsDon, GoodsDonSku sku) {
 		List<Long> userIdList = userBindRelationService.getRelationUserIdList(orderDon.getUserId(), null);
 		GroupsRelationExpiryRecord groupsRelationExpiryRecord = this.getOne(Wrappers.lambdaQuery(GroupsRelationExpiryRecord.class)
 				.in(GroupsRelationExpiryRecord::getUserId, userIdList)
 				.eq(GroupsRelationExpiryRecord::getIsReBuy, false)
-				.eq(GroupsRelationExpiryRecord::getRelationId, orderDon.getRelationId()).last("limit 1"));
+				.eq(GroupsRelationExpiryRecord::getRelationId, relationId).last("limit 1"));
 		if (groupsRelationExpiryRecord == null) {
 			List<GroupsRelationExpiryRecord> groupsRelationExpiryRecords = this.list(Wrappers.lambdaQuery(GroupsRelationExpiryRecord.class)
 					.in(GroupsRelationExpiryRecord::getUserId, userIdList)