Bladeren bron

Merge branch 'error_nf_change'

zoujiajian 1 jaar geleden
bovenliggende
commit
18ddc85660
26 gewijzigde bestanden met toevoegingen van 982 en 24 verwijderingen
  1. 5 0
      netflix-common/src/main/java/com/cyksj/common/constant/Constant.java
  2. 13 0
      netflix-dao/src/main/java/com/cyksj/mapper/NetflixUserAccountSubmitRecoverRecordMapper.java
  3. 13 0
      netflix-dao/src/main/java/com/cyksj/mapper/NetflixUserChangeOtherTicketRecordMapper.java
  4. 20 0
      netflix-dao/src/main/java/com/cyksj/model/dto/ErrorNetflixChangeOtherSkuTicketDto.java
  5. 18 0
      netflix-dao/src/main/java/com/cyksj/model/dto/TicketChangeDto.java
  6. 34 0
      netflix-dao/src/main/java/com/cyksj/model/entity/NetflixUserAccountSubmitRecoverRecord.java
  7. 33 0
      netflix-dao/src/main/java/com/cyksj/model/entity/NetflixUserChangeOtherTicketRecord.java
  8. 2 1
      netflix-dao/src/main/java/com/cyksj/model/entity/OrderDon.java
  9. 1 0
      netflix-dao/src/main/java/com/cyksj/model/entity/UserTicketClearedRecord.java
  10. 12 0
      netflix-dao/src/main/java/com/cyksj/model/manage/views/GroupsRelationView.java
  11. 23 0
      netflix-dao/src/main/java/com/cyksj/model/request/ErrorNetflixChangeReq.java
  12. 24 0
      netflix-dao/src/main/java/com/cyksj/model/response/NetflixErrorAccountStatus.java
  13. 21 0
      netflix-dao/src/main/java/com/cyksj/model/response/NetflixRecoverResp.java
  14. 25 0
      netflix-dao/src/main/java/com/cyksj/model/response/NetflixRefundInfoResp.java
  15. 39 0
      netflix-dao/src/main/java/com/cyksj/model/views/GoodsCategorySkuView.java
  16. 63 0
      netflix-dao/src/main/java/com/cyksj/model/views/NetflixErrorRecoverView.java
  17. 1 1
      netflix-service/src/main/java/com/cyksj/service/mange/CmsGroupService.java
  18. 1 1
      netflix-service/src/main/java/com/cyksj/service/mange/account/CmsAccountServiceImpl.java
  19. 70 1
      netflix-service/src/main/java/com/cyksj/service/mange/group/CmsGroupServiceImpl.java
  20. 24 0
      netflix-service/src/main/java/com/cyksj/service/mange/impl/CmsOrderDonServiceImpl.java
  21. 16 1
      netflix-service/src/main/java/com/cyksj/service/relation/GroupRelationFrontService.java
  22. 415 12
      netflix-service/src/main/java/com/cyksj/service/relation/impl/GroupRelationFrontServiceImpl.java
  23. 63 3
      netflix-web/src/main/java/com/cyksj/web/controller/group/GroupRelationController.java
  24. 3 3
      netflix-web/src/main/java/com/cyksj/web/controller/manage/account/CmsAccountController.java
  25. 42 0
      netflix-web/src/main/java/com/cyksj/web/controller/manage/account/NetflixErrorRecoverController.java
  26. 1 1
      netflix-web/src/main/java/com/cyksj/web/controller/manage/group/CmsGroupController.java

+ 5 - 0
netflix-common/src/main/java/com/cyksj/common/constant/Constant.java

@@ -611,4 +611,9 @@ public interface Constant {
 	 * 提现默认支付宝
 	 */
 	String TAKE_MONEY_DEFAULT_ZFB = "take_money_default_zfb";
+
+	/**
+	 * 异常奈飞车票更换其他规格key
+	 */
+	String ERROR_NETFLIX_CHANGE_OTHER_SKU_KEY = "error_netflix_change_other_sku_key";
 }

+ 13 - 0
netflix-dao/src/main/java/com/cyksj/mapper/NetflixUserAccountSubmitRecoverRecordMapper.java

@@ -0,0 +1,13 @@
+package com.cyksj.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.cyksj.model.entity.NetflixUserAccountSubmitRecoverRecord;
+
+/**
+ * 项目名: yhlxj
+ * 文件名: NetflixUserAccountSubmitRecoverRecordMapper
+ * 创建者: JavaZou
+ * 创建时间:2025/6/24 10:18
+ */
+public interface NetflixUserAccountSubmitRecoverRecordMapper extends BaseMapper<NetflixUserAccountSubmitRecoverRecord> {
+}

+ 13 - 0
netflix-dao/src/main/java/com/cyksj/mapper/NetflixUserChangeOtherTicketRecordMapper.java

@@ -0,0 +1,13 @@
+package com.cyksj.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.cyksj.model.entity.NetflixUserChangeOtherTicketRecord;
+
+/**
+ * 项目名: yhlxj
+ * 文件名: NetflixUserChangeOtherTicketRecordMapper
+ * 创建者: JavaZou
+ * 创建时间:2025/6/24 10:24
+ */
+public interface NetflixUserChangeOtherTicketRecordMapper extends BaseMapper<NetflixUserChangeOtherTicketRecord> {
+}

+ 20 - 0
netflix-dao/src/main/java/com/cyksj/model/dto/ErrorNetflixChangeOtherSkuTicketDto.java

@@ -0,0 +1,20 @@
+package com.cyksj.model.dto;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.List;
+
+/**
+ * 项目名: yhlxj
+ * 文件名: ErrorNetflixChangeOtherSkuTicketDto
+ * 创建者: JavaZou
+ * 创建时间:2025/6/24 11:06
+ */
+@Getter
+@Setter
+public class ErrorNetflixChangeOtherSkuTicketDto {
+	private Long remainDays;
+
+	private List<Long> skuIds;
+}

+ 18 - 0
netflix-dao/src/main/java/com/cyksj/model/dto/TicketChangeDto.java

@@ -0,0 +1,18 @@
+package com.cyksj.model.dto;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * 项目名: yhlxj
+ * 文件名: TicketChangeDto
+ * 创建者: JavaZou
+ * 创建时间:2025/6/24 11:01
+ */
+@Getter
+@Setter
+public class TicketChangeDto {
+	private Long otherRelationId;
+
+	private Long otherOrderId;
+}

+ 34 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/NetflixUserAccountSubmitRecoverRecord.java

@@ -0,0 +1,34 @@
+package com.cyksj.model.entity;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * 项目名: yhlxj
+ * 文件名: NetflixUserAccountSubmitRecoverRecord
+ * 创建者: JavaZou
+ * 创建时间:2025/6/24 10:17
+ */
+@Getter
+@Setter
+public class NetflixUserAccountSubmitRecoverRecord extends BaseEntity{
+	private Long userId;
+
+	private Long relationId;
+
+	private Long skuId;
+
+	private String account;
+
+	private Long remainDays;
+
+	/**
+	 * 0未更换 1.替换成功 2.车票失效
+	 */
+	private Integer status;
+
+	/**
+	 * 替换后车票id
+	 */
+	private Long replaceRelationId;
+}

+ 33 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/NetflixUserChangeOtherTicketRecord.java

@@ -0,0 +1,33 @@
+package com.cyksj.model.entity;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.Date;
+
+/**
+ * 项目名: yhlxj
+ * 文件名: NetflixUserChangeOtherTicketReocrd
+ * 创建者: JavaZou
+ * 创建时间:2025/6/24 10:22
+ */
+@Getter
+@Setter
+public class NetflixUserChangeOtherTicketRecord extends BaseEntity{
+	private Long userId;
+
+	private Long orderId;
+
+	private Long relationId;
+
+	private Long relateOrderId;
+
+	private Date startTime;
+
+	private Date expiryTime;
+
+	/**
+	 * 其他车票id
+	 */
+	private Long otherRelationId;
+}

+ 2 - 1
netflix-dao/src/main/java/com/cyksj/model/entity/OrderDon.java

@@ -344,7 +344,8 @@ public class OrderDon extends BaseEntity implements Serializable {
         PAY_EQUIPMENT("pay_equipment", "自购设备"),
         BALANCE("balance", "余额支付"),
         PAYPAL("paypal","paypal支付"),
-        STRIPE("stripe","stripe支付")
+        STRIPE("stripe","stripe支付"),
+        TICKET_REPLACE("ticket_replace", "车票替换"),
         ;
         String type;
         String desc;

+ 1 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/UserTicketClearedRecord.java

@@ -65,6 +65,7 @@ public class UserTicketClearedRecord extends BaseEntity{
 		manual("手动清除"),
 		refund("退款"),
 		vip_refund("退款"),
+		ticket_replace("替换车票")
 		;
 		String desc;
 

+ 12 - 0
netflix-dao/src/main/java/com/cyksj/model/manage/views/GroupsRelationView.java

@@ -139,4 +139,16 @@ public class GroupsRelationView {
     @DbIgnore
     private String errorMsg;
 
+    /**
+     * 奈飞检测id
+     */
+    @DbIgnore
+    private Long nfCheckId;
+
+    /**
+     * 奈飞原先订单id
+     */
+    @DbIgnore
+    private Long nfOriOrderId;
+
 }

+ 23 - 0
netflix-dao/src/main/java/com/cyksj/model/request/ErrorNetflixChangeReq.java

@@ -0,0 +1,23 @@
+package com.cyksj.model.request;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * 项目名: yhlxj
+ * 文件名: ErrorNetflixChangeReq
+ * 创建者: JavaZou
+ * 创建时间:2025/6/24 10:37
+ */
+@Getter
+@Setter
+public class ErrorNetflixChangeReq {
+	private Long relationId;
+
+	private Long userId;
+
+	/**
+	 * 更换规格
+	 */
+	private Long skuId;
+}

+ 24 - 0
netflix-dao/src/main/java/com/cyksj/model/response/NetflixErrorAccountStatus.java

@@ -0,0 +1,24 @@
+package com.cyksj.model.response;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * 项目名: yhlxj
+ * 文件名: NetflixErrorAccountStatus
+ * 创建者: JavaZou
+ * 创建时间:2025/6/24 14:34
+ */
+@Getter
+@Setter
+public class NetflixErrorAccountStatus {
+	/**
+	 * 账号状态
+	 */
+	private Integer nfStatus;
+
+	/**
+	 * 申请恢复状态
+	 */
+	private Integer recoverStatus;
+}

+ 21 - 0
netflix-dao/src/main/java/com/cyksj/model/response/NetflixRecoverResp.java

@@ -0,0 +1,21 @@
+package com.cyksj.model.response;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * 项目名: yhlxj
+ * 文件名: NetflixRecoverResp
+ * 创建者: JavaZou
+ * 创建时间:2025/6/24 16:46
+ */
+@Getter
+@Setter
+public class NetflixRecoverResp {
+	private Integer index;
+
+	/**
+	 * 剩余天数
+	 */
+	private Long remainDays;
+}

+ 25 - 0
netflix-dao/src/main/java/com/cyksj/model/response/NetflixRefundInfoResp.java

@@ -0,0 +1,25 @@
+package com.cyksj.model.response;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import java.math.BigDecimal;
+
+/**
+ * 项目名: yhlxj
+ * 文件名: NetflixRefundInfoResp
+ * 创建者: JavaZou
+ * 创建时间:2025/6/24 17:06
+ */
+@Getter
+@Setter
+public class NetflixRefundInfoResp {
+	/**
+	 * 是否可退款
+	 */
+	private Boolean isRefund;
+
+	private Long remainDays;
+
+	private BigDecimal price;
+}

+ 39 - 0
netflix-dao/src/main/java/com/cyksj/model/views/GoodsCategorySkuView.java

@@ -0,0 +1,39 @@
+package com.cyksj.model.views;
+
+import com.ejlchina.searcher.bean.DbField;
+import com.ejlchina.searcher.bean.SearchBean;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.math.BigDecimal;
+
+/**
+ * 项目名: yhlxj
+ * 文件名: GoodsCategorySkuView
+ * 创建者: JavaZou
+ * 创建时间:2025/6/24 11:37
+ */
+@Getter
+@Setter
+@SearchBean(tables = "goods_don_category gc left join goods_don_category_relate gcr on gcr.category = gc.id" +
+		" left join goods_don g on g.id = gcr.goods_id and g.deleted is true" +
+		" left join goods_don_sku sku on sku.goods_id = g.id")
+public class GoodsCategorySkuView {
+	@DbField("gc.name")
+	private String categoryName;
+
+	@DbField("g.id")
+	private Long goodsId;
+
+	@DbField("g.title")
+	private String title;
+
+	@DbField("sku.id")
+	private Long skuId;
+
+	@DbField("sku.price")
+	private BigDecimal price;
+
+	@DbField("sku.spec_val")
+	private String specVal;
+}

+ 63 - 0
netflix-dao/src/main/java/com/cyksj/model/views/NetflixErrorRecoverView.java

@@ -0,0 +1,63 @@
+package com.cyksj.model.views;
+
+import com.ejlchina.searcher.bean.DbField;
+import com.ejlchina.searcher.bean.SearchBean;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.Date;
+
+/**
+ * 项目名: yhlxj
+ * 文件名: NetflixErrorRecoverView
+ * 创建者: JavaZou
+ * 创建时间:2025/6/24 13:45
+ */
+@Getter
+@Setter
+@SearchBean(tables = "netflix_user_account_submit_recover_record ar left join goods_don_sku sku on sku.id = ar.sku_id" +
+		" left join goods_don g on g.id = sku.goods_id" +
+		" left join user u on u.id = ar.user_id")
+public class NetflixErrorRecoverView {
+	@DbField("ar.id")
+	private Long id;
+
+	@DbField("ar.user_id")
+	private Long userId;
+
+	@DbField("u.nickname")
+	private String nickname;
+
+	@DbField("ar.account")
+	private String account;
+
+	@DbField("g.id")
+	private Long goodsId;
+
+	@DbField("g.title")
+	private String title;
+
+	@DbField("ar.sku_id")
+	private Long skuId;
+
+	@DbField("sku.spec_val")
+	private String specVal;
+
+	@DbField("ar.remain_days")
+	private Integer remainDays;
+
+	@DbField("ar.created_time")
+	private Date createdTime;
+
+	/**
+	 * 0未更换 1.替换成功 2.车票失效
+	 */
+	@DbField("ar.status")
+	private Integer status;
+
+	/**
+	 * 位置
+	 */
+	@DbField("select count(1) + 1 from netflix_user_account_submit_recover_record ar2 where ar2.created_time < ar.created_time and ar2.status = 0")
+	private Integer indx;
+}

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

@@ -10,7 +10,7 @@ import com.cyksj.model.request.ReplaceTripsAccountReq;
  */
 public interface CmsGroupService extends IService<GroupsTrips> {
 
-    GroupsTrips issuance(GroupsTrips groups, String verifyCodes);
+    GroupsTrips issuance(GroupsTrips groups, String verifyCodes, Boolean isReover);
 
     void close(Long groupId);
 

+ 1 - 1
netflix-service/src/main/java/com/cyksj/service/mange/account/CmsAccountServiceImpl.java

@@ -431,7 +431,7 @@ public class CmsAccountServiceImpl extends ServiceImpl<AccountMapper, Account> i
 		GroupsTrips groupsTrips = new GroupsTrips();
 		groupsTrips.setSkuId(skuId);
 		groupsTrips.setAccountId(accountId);
-		cmsGroupService.issuance(groupsTrips, null);
+		cmsGroupService.issuance(groupsTrips, null, false);
 	}
 
 	@Override

+ 70 - 1
netflix-service/src/main/java/com/cyksj/service/mange/group/CmsGroupServiceImpl.java

@@ -23,8 +23,10 @@ import com.cyksj.model.views.GroupsAccountView;
 import com.cyksj.redis.RedisService;
 import com.cyksj.service.mange.AccountCommonService;
 import com.cyksj.service.mange.CmsGroupService;
+import com.cyksj.service.relation.GroupRelationClearService;
 import com.cyksj.service.relation.GroupsRelationNetflixService;
 import com.cyksj.service.sms.SmsService;
+import com.cyksj.service.user.UserBindRelationService;
 import com.cyksj.service.user.UserSysMsgNotifyService;
 import com.ejlchina.searcher.BeanSearcher;
 import com.ejlchina.searcher.param.Operator;
@@ -86,11 +88,19 @@ public class CmsGroupServiceImpl extends ServiceImpl<GroupsMapper,GroupsTrips> i
 
     private final UserMapper userMapper;
 
+    private final NetflixUserAccountSubmitRecoverRecordMapper netflixUserAccountSubmitRecoverRecordMapper;
+
+    private final GroupRelationClearService clearService;
+
+    private final UserBindRelationService userBindRelationService;
+
+    private final NetflixUserAccountStatusRecordMapper netflixUserAccountStatusRecordMapper;
+
     private static final GlobalThreadPoolTaskExecutor TASK_EXECUTOR = GlobalThreadPoolTaskExecutor.getInstance();
 
 
     @Override
-    public GroupsTrips issuance(GroupsTrips groups, String verifyCodes) {
+    public GroupsTrips issuance(GroupsTrips groups, String verifyCodes, Boolean isRecover) {
         GoodsDonSku sku = skuMapper.selectById(groups.getSkuId());
         if (sku == null) {
             throw new BusinessRuntimeException("该商品规格不存在!");
@@ -117,7 +127,15 @@ public class CmsGroupServiceImpl extends ServiceImpl<GroupsMapper,GroupsTrips> i
             if (verifyCodeList != null && verifyCodeList.size() > 0 && verifyCodeList.size() >= i) {
                 relation.setVerifyCode(verifyCodeList.get(i - 1));
             }
+            NetflixUserAccountSubmitRecoverRecord recoverRecord = null;
+            if (sku.getGoodsId() == Constant.NETFLIX_GID) {
+                recoverRecord = updateRelationIfRecoverTicket(isRecover, sku, relation);
+            }
             relationMapper.insert(relation);
+            if (recoverRecord != null) {
+                recoverRecord.setReplaceRelationId(relation.getId());
+                netflixUserAccountSubmitRecoverRecordMapper.updateById(recoverRecord);
+            }
         }
         Integer alertCount = alertMapper.selectCount(Wrappers.lambdaQuery(GroupsRelationAlert.class)
                 .eq(GroupsRelationAlert::getSkuId, sku.getId())
@@ -534,4 +552,55 @@ public class CmsGroupServiceImpl extends ServiceImpl<GroupsMapper,GroupsTrips> i
             }
         }
     }
+
+
+    /**
+     * 奈飞异常恢复车票
+     */
+    private NetflixUserAccountSubmitRecoverRecord updateRelationIfRecoverTicket(Boolean isRecover, GoodsDonSku sku, GroupsRelation relation) {
+        if (isRecover != null && isRecover) {
+            NetflixUserAccountSubmitRecoverRecord recoverRecord = netflixUserAccountSubmitRecoverRecordMapper.selectOne(Wrappers.lambdaQuery(NetflixUserAccountSubmitRecoverRecord.class)
+                    .eq(NetflixUserAccountSubmitRecoverRecord::getSkuId, sku.getId())
+                    .eq(NetflixUserAccountSubmitRecoverRecord::getStatus, 0)
+                    .orderByAsc(NetflixUserAccountSubmitRecoverRecord::getCreatedTime)
+                    .last("limit 1"));
+            if (recoverRecord != null) {
+                relation.setUserId(recoverRecord.getUserId());
+                relation.setStatus(GroupsRelation.Status.validity);
+                relation.setStartTime(DateTime.now());
+                relation.setExpiryTime(DateUtil.offsetDay(relation.getStartTime(), recoverRecord.getRemainDays().intValue()));
+                //清除车票
+                List<Long> userIdList = userBindRelationService.getRelationUserIdList(recoverRecord.getUserId(), null);
+                GroupsRelation clearRelation = relationMapper.selectOne(Wrappers.lambdaQuery(GroupsRelation.class)
+                        .eq(GroupsRelation::getId, recoverRecord.getRelationId())
+                        .in(GroupsRelation::getUserId, userIdList)
+                        .last("limit 1"));
+                if (clearRelation != null) {
+                    clearService.clearTicket(clearRelation, UserTicketClearedRecord.Source.ticket_replace);
+                }
+                recoverRecord.setStatus(1);
+                OrderDon orderDon = orderDonMapper.selectOne(Wrappers.lambdaQuery(OrderDon.class)
+                        .in(OrderDon::getUserId, userIdList)
+                        .eq(OrderDon::getRelationId, recoverRecord.getRelationId())
+                        .notIn(OrderDon::getStatus, Constant.noOrderAllStatus)
+                        .orderByDesc(OrderDon::getId)
+                        .last("limit 1"));
+                if (orderDon != null) {
+                    orderDon.setRelationId(relation.getId());
+                    orderDonMapper.updateById(orderDon);
+                }
+                NetflixUserAccountStatusRecord netflixUserAccountStatusRecord = netflixUserAccountStatusRecordMapper.selectOne(Wrappers.lambdaQuery(NetflixUserAccountStatusRecord.class)
+                        .in(NetflixUserAccountStatusRecord::getUserId, userIdList)
+                        .eq(NetflixUserAccountStatusRecord::getRelationId, recoverRecord.getRelationId())
+                        .eq(NetflixUserAccountStatusRecord::getDeleted, 1)
+                        .last("limit 1"));
+                if (netflixUserAccountStatusRecord != null) {
+                    netflixUserAccountStatusRecord.setDeleted(false);
+                    netflixUserAccountStatusRecordMapper.updateById(netflixUserAccountStatusRecord);
+                }
+                return recoverRecord;
+            }
+        }
+        return null;
+    }
 }

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

@@ -218,6 +218,10 @@ public class CmsOrderDonServiceImpl extends ServiceImpl<OrderDonMapper,OrderDon>
 
     private final UserTicketClearedRecordMapper userTicketClearedRecordMapper;
 
+    private final NetflixUserAccountSubmitRecoverRecordMapper netflixUserAccountSubmitRecoverRecordMapper;
+
+    private final NetflixUserChangeOtherTicketRecordMapper netflixUserChangeOtherTicketRecordMapper;
+
     private static final GlobalThreadPoolTaskExecutor TASK_POOL = GlobalThreadPoolTaskExecutor.getInstance();
 
     @Override
@@ -912,6 +916,13 @@ public class CmsOrderDonServiceImpl extends ServiceImpl<OrderDonMapper,OrderDon>
         if (OrderDon.Status.noPayment == orderDon.getStatus() || OrderDon.Status.close == orderDon.getStatus()) {
             throw BusinessRuntimeException.getInstance("订单{0}状态不支持退款", orderDon.getStatus());
         }
+        if (goodsDon.getId() == Constant.NETFLIX_GID) {
+            Integer selectCount = netflixUserChangeOtherTicketRecordMapper.selectCount(Wrappers.lambdaQuery(NetflixUserChangeOtherTicketRecord.class)
+                    .eq(NetflixUserChangeOtherTicketRecord::getOrderId, orderDon.getId()));
+            if (selectCount > 0) {
+                throw BusinessRuntimeException.getInstance("该奈飞车票已替换其他车票,无法退款");
+            }
+        }
         if (goodsDon.getType() == 1 && sku != null) {
             //虚拟原订单是否已过期
             Integer months = sku.getMonths();
@@ -1164,6 +1175,19 @@ public class CmsOrderDonServiceImpl extends ServiceImpl<OrderDonMapper,OrderDon>
         }
         //HBO GO退款,车队上架
         setUpIfEmpty(orderDon.getGoodsId(), orderDon.getRelationId());
+
+        if (orderDon.getGoodsId() == Constant.NETFLIX_GID) {
+            List<Long> userIdList = userBindRelationService.getRelationUserIdList(orderDon.getUserId(), null);
+            NetflixUserAccountSubmitRecoverRecord recoverRecord = netflixUserAccountSubmitRecoverRecordMapper.selectOne(Wrappers.lambdaQuery(NetflixUserAccountSubmitRecoverRecord.class)
+                    .eq(NetflixUserAccountSubmitRecoverRecord::getRelationId, orderDon.getRelationId())
+                    .in(NetflixUserAccountSubmitRecoverRecord::getUserId, userIdList)
+                    .eq(NetflixUserAccountSubmitRecoverRecord::getStatus, 0)
+                    .last("limit 1"));
+            if (recoverRecord != null) {
+                recoverRecord.setStatus(3);
+                netflixUserAccountSubmitRecoverRecordMapper.updateById(recoverRecord);
+            }
+        }
     }
 
     //处理下级渠道分销提成

+ 16 - 1
netflix-service/src/main/java/com/cyksj/service/relation/GroupRelationFrontService.java

@@ -5,8 +5,13 @@ import com.cyksj.model.entity.GoodsDonSku;
 import com.cyksj.model.entity.GroupsRelation;
 import com.cyksj.model.entity.NetflixUserAccountStatusRecord;
 import com.cyksj.model.manage.views.AccountView;
+import com.cyksj.model.request.ErrorNetflixChangeReq;
 import com.cyksj.model.request.SpecialGoodsTicketReq;
 import com.cyksj.model.request.TicketLoginReq;
+import com.cyksj.model.response.NetflixErrorAccountStatus;
+import com.cyksj.model.response.NetflixRecoverResp;
+import com.cyksj.model.response.NetflixRefundInfoResp;
+import com.cyksj.model.views.GoodsCategorySkuView;
 import com.cyksj.model.views.RenewalView;
 import com.cyksj.model.views.UserRecommendGoodsFrontView;
 import com.ejlchina.searcher.SearchResult;
@@ -77,7 +82,7 @@ public interface GroupRelationFrontService {
 
 	void nfErrorAccountSubmit(long userId, Long relationId);
 
-	Integer getNetflixErrorAccountStatus(long userId, Long relationId);
+	NetflixErrorAccountStatus getNetflixErrorAccountStatus(long userId, Long relationId);
 
 	List<NetflixUserAccountStatusRecord> getUserNetflixSubmitRecord(long userId);
 
@@ -98,4 +103,14 @@ public interface GroupRelationFrontService {
 	Long getTicket(Long userId, Long skuId, Boolean isVip);
 
 	Boolean getVipMirrorTicketIfEmpty(long userId, Long skuId);
+
+	NetflixRecoverResp netflixSubmitRecover(ErrorNetflixChangeReq req);
+
+	void errorNetflixSubmitChangeOtherTicket(ErrorNetflixChangeReq req) throws Exception;
+
+	List<GoodsCategorySkuView> getNetflixReplaceOtherSkus(long userId, Long relationId) throws Exception;
+
+	Boolean refundNetflix(ErrorNetflixChangeReq req) throws Exception;
+
+	NetflixRefundInfoResp refundNetflixInfo(Long userId, Long relationId);
 }

+ 415 - 12
netflix-service/src/main/java/com/cyksj/service/relation/impl/GroupRelationFrontServiceImpl.java

@@ -13,6 +13,7 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.cyksj.common.constant.Constant;
 import com.cyksj.common.exception.BusinessRuntimeException;
+import com.cyksj.common.snowflake.Sequence;
 import com.cyksj.common.task.GlobalThreadPoolTaskExecutor;
 import com.cyksj.common.util.Jsons;
 import com.cyksj.common.util.StringUtil;
@@ -28,11 +29,18 @@ import com.cyksj.mapper.sys.SysConfigMapper;
 import com.cyksj.mapper.sys.SysEmailMapper;
 import com.cyksj.mapper.vip.VipGoodsSkuMapper;
 import com.cyksj.mapper.vip.VipUserMapper;
+import com.cyksj.model.dto.ErrorNetflixChangeOtherSkuTicketDto;
+import com.cyksj.model.dto.TicketChangeDto;
 import com.cyksj.model.entity.*;
 import com.cyksj.model.manage.views.AccountView;
 import com.cyksj.model.manage.views.GroupsRelationView;
+import com.cyksj.model.request.ErrorNetflixChangeReq;
+import com.cyksj.model.request.OrderRefundReq;
 import com.cyksj.model.request.SpecialGoodsTicketReq;
 import com.cyksj.model.request.TicketLoginReq;
+import com.cyksj.model.response.NetflixErrorAccountStatus;
+import com.cyksj.model.response.NetflixRecoverResp;
+import com.cyksj.model.response.NetflixRefundInfoResp;
 import com.cyksj.model.views.*;
 import com.cyksj.redis.RedisService;
 import com.cyksj.service.chatgpt.ChatGptAccountService;
@@ -44,6 +52,8 @@ import com.cyksj.service.mail.CommonMailService;
 import com.cyksj.service.mail.MailService;
 import com.cyksj.service.mange.coupon.CouponCommonService;
 import com.cyksj.service.midjourney.MidjourneyAccountService;
+import com.cyksj.service.order.OrderRefundService;
+import com.cyksj.service.relation.GroupRelationClearService;
 import com.cyksj.service.relation.GroupRelationFrontService;
 import com.cyksj.service.relation.GroupsRelationChangeService;
 import com.cyksj.service.relation.GroupsRelationNetflixService;
@@ -57,6 +67,7 @@ import com.ejlchina.searcher.util.MapUtils;
 import com.github.rholder.retry.*;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.dao.DuplicateKeyException;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -178,6 +189,16 @@ public class GroupRelationFrontServiceImpl implements GroupRelationFrontService
 
 	private final VipGoodsSkuMapper vipGoodsSkuMapper;
 
+	private static final Sequence SEQUENCE = new Sequence(0);
+
+	private final NetflixUserChangeOtherTicketRecordMapper netflixUserChangeOtherTicketRecordMapper;
+
+	private final NetflixUserAccountSubmitRecoverRecordMapper netflixUserAccountSubmitRecoverRecordMapper;
+
+	private final OrderRefundService orderRefundService;
+
+	private final GroupRelationClearService clearService;
+
 	@Override
 	public SearchResult<RenewalView> getMyTicket(long userId, Boolean isLoginPopularize, String customId, String account, String orderNo, Integer cmt) {
 		User u = userMapper.selectById(userId);
@@ -1177,8 +1198,9 @@ public class GroupRelationFrontServiceImpl implements GroupRelationFrontService
 
 		Integer selectCount = netflixUserAccountStatusRecordMapper.selectCount(Wrappers.lambdaQuery(NetflixUserAccountStatusRecord.class)
 				.eq(NetflixUserAccountStatusRecord::getRelationId, relationId)
-				.eq(NetflixUserAccountStatusRecord::getUserId, userId)
-				.eq(NetflixUserAccountStatusRecord::getAccount, account));
+				.in(NetflixUserAccountStatusRecord::getUserId, userIdList)
+				.eq(NetflixUserAccountStatusRecord::getAccount, account)
+				.eq(NetflixUserAccountStatusRecord::getDeleted, 1));
 		if (selectCount > 0) {
 			throw BusinessRuntimeException.getInstance("该账号正在检测中");
 		}
@@ -1189,16 +1211,22 @@ public class GroupRelationFrontServiceImpl implements GroupRelationFrontService
 		if (accountNetflixState != null && accountNetflixState.getState() == 3) {
 			state = accountNetflixState.getState();
 		}
+		//非正常状态
+		if (state != 1) {
+			//20号之前得校验 直接失败
+			if (renewalView.getCreatedTime().compareTo(DateUtil.parse("2025-06-20")) < 0) {
+				state = 3;
+			}
+		}
 		NetflixUserAccountStatusRecord netflixUserAccountStatusRecord = new NetflixUserAccountStatusRecord();
 		netflixUserAccountStatusRecord.setAccount(account);
 		netflixUserAccountStatusRecord.setRelationId(relationId);
 		netflixUserAccountStatusRecord.setPassword(password);
 		netflixUserAccountStatusRecord.setUserId(renewalView.getUserId());
 		netflixUserAccountStatusRecord.setState(state);
-
 		OrderDon orderDon = orderDonMapper.selectOne(Wrappers.lambdaQuery(OrderDon.class)
 				.eq(OrderDon::getRelationId, renewalView.getRelationId())
-				.eq(OrderDon::getUserId, renewalView.getUserId())
+				.in(OrderDon::getUserId, userIdList)
 				.notIn(OrderDon::getStatus, Constant.noOrderAllStatus)
 				.orderByDesc(OrderDon::getId)
 				.last("limit 1"));
@@ -1209,28 +1237,47 @@ public class GroupRelationFrontServiceImpl implements GroupRelationFrontService
 	}
 
 	@Override
-	public Integer getNetflixErrorAccountStatus(long userId, Long relationId) {
+	public NetflixErrorAccountStatus getNetflixErrorAccountStatus(long userId, Long relationId) {
 		List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
 		NetflixUserAccountStatusRecord netflixUseAccountStatusRecord = netflixUserAccountStatusRecordMapper.selectOne(Wrappers.lambdaQuery(NetflixUserAccountStatusRecord.class)
 				.eq(NetflixUserAccountStatusRecord::getRelationId, relationId)
 				.eq(NetflixUserAccountStatusRecord::getDeleted, 1)
 				.in(NetflixUserAccountStatusRecord::getUserId, userIdList)
 				.last("limit 1"));
+		NetflixErrorAccountStatus status = new NetflixErrorAccountStatus();
 		if (netflixUseAccountStatusRecord == null) {
-			throw BusinessRuntimeException.getInstance("未有提交记录");
+			//是否已经更换
+			NetflixUserAccountSubmitRecoverRecord replaceRecover = netflixUserAccountSubmitRecoverRecordMapper.selectOne(Wrappers.lambdaQuery(NetflixUserAccountSubmitRecoverRecord.class)
+					.eq(NetflixUserAccountSubmitRecoverRecord::getUserId, userIdList)
+					.eq(NetflixUserAccountSubmitRecoverRecord::getReplaceRelationId, relationId)
+					.last("limit 1"));
+			if (replaceRecover != null) {
+				status.setRecoverStatus(replaceRecover.getStatus());
+			}
+			return status;
 		}
 		Integer state = netflixUseAccountStatusRecord.getState();
 		//2是登录失败 3是被封号
-		if (state == 3) {
-			if (netflixUseAccountStatusRecord.getCreatedTime().compareTo(DateUtil.offsetMinute(DateTime.now(), -10)) > 0) {
-				state = 0;
-			}
-		}
+//		if (state == 3) {
+//			if (netflixUseAccountStatusRecord.getCreatedTime().compareTo(DateUtil.offsetMinute(DateTime.now(), -10)) > 0) {
+//				state = 0;
+//			}
+//		}
 		//登录失败不算异常
 		if (state == 2) {
 			state = 1;
 		}
-		return state;
+		//账号检测状态
+		status.setNfStatus(state);
+		NetflixUserAccountSubmitRecoverRecord recoverRecord = netflixUserAccountSubmitRecoverRecordMapper.selectOne(Wrappers.lambdaQuery(NetflixUserAccountSubmitRecoverRecord.class)
+				.in(NetflixUserAccountSubmitRecoverRecord::getUserId, userIdList)
+				.eq(NetflixUserAccountSubmitRecoverRecord::getRelationId, relationId)
+				.last("limit 1"));
+		if (recoverRecord != null) {
+			//申请恢复状态
+			status.setRecoverStatus(recoverRecord.getStatus());
+		}
+		return status;
 	}
 
 	@Override
@@ -1406,6 +1453,248 @@ public class GroupRelationFrontServiceImpl implements GroupRelationFrontService
 		return false;
 	}
 
+	@Override
+	public NetflixRecoverResp netflixSubmitRecover(ErrorNetflixChangeReq req) {
+		Long relationId = req.getRelationId();
+		Long userId = req.getUserId();
+		//校验奈飞账号状态
+		GroupsRelationView netflixRelationView = checkNetflixErrorStatus(relationId, userId);
+		Date expiryTime = netflixRelationView.getExpiryTime();
+		List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
+		NetflixUserAccountSubmitRecoverRecord recoverRecord = netflixUserAccountSubmitRecoverRecordMapper.selectOne(Wrappers.lambdaQuery(NetflixUserAccountSubmitRecoverRecord.class)
+				.eq(NetflixUserAccountSubmitRecoverRecord::getRelationId, relationId)
+				.in(NetflixUserAccountSubmitRecoverRecord::getUserId, userIdList)
+				.eq(NetflixUserAccountSubmitRecoverRecord::getStatus, 0)
+				.last("limit 1"));
+		if (recoverRecord == null) {
+			recoverRecord = new NetflixUserAccountSubmitRecoverRecord();
+			recoverRecord.setRelationId(relationId);
+			recoverRecord.setUserId(netflixRelationView.getUserId());
+			recoverRecord.setSkuId(netflixRelationView.getSkuId());
+			recoverRecord.setAccount(netflixRelationView.getAccount());
+			Long betweenDay = DateUtil.betweenDay(DateTime.now(), expiryTime, false) + 1;
+			recoverRecord.setRemainDays(betweenDay);
+			recoverRecord.setAccount(netflixRelationView.getTripsAccount());
+			try {
+				netflixUserAccountSubmitRecoverRecordMapper.insert(recoverRecord);
+			} catch (DuplicateKeyException e) {
+				throw BusinessRuntimeException.getInstance("您已申请恢复");
+			}
+		}
+		//等待位数
+		Integer selectCount = netflixUserAccountSubmitRecoverRecordMapper.selectCount(Wrappers.lambdaQuery(NetflixUserAccountSubmitRecoverRecord.class)
+				.lt(NetflixUserAccountSubmitRecoverRecord::getCreatedTime, recoverRecord.getCreatedTime())
+				.eq(NetflixUserAccountSubmitRecoverRecord::getStatus, 0));
+		NetflixRecoverResp recoverResp = new NetflixRecoverResp();
+		recoverResp.setIndex(selectCount + 1);
+		recoverResp.setRemainDays(recoverRecord.getRemainDays());
+		return recoverResp;
+	}
+
+	@Override
+	public void errorNetflixSubmitChangeOtherTicket(ErrorNetflixChangeReq req) throws Exception {
+		Long relationId = req.getRelationId();
+		Long userId = req.getUserId();
+		//校验奈飞账号状态
+		GroupsRelationView netflixRelationView = checkNetflixErrorStatus(relationId, userId);
+		Date expiryTime = netflixRelationView.getExpiryTime();
+		//小于15 不可替换其他车票
+		Long betweenDay = DateUtil.betweenDay(DateTime.now(), expiryTime, false) + 1;
+		if (betweenDay <= 15) {
+			throw BusinessRuntimeException.getInstance("您的车票有效期未超过15天");
+		}
+		List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
+		Integer count = netflixUserAccountSubmitRecoverRecordMapper.selectCount(Wrappers.lambdaQuery(NetflixUserAccountSubmitRecoverRecord.class)
+				.eq(NetflixUserAccountSubmitRecoverRecord::getRelationId, relationId)
+				.in(NetflixUserAccountSubmitRecoverRecord::getUserId, userIdList));
+		if (count > 0) {
+			throw BusinessRuntimeException.getInstance("您的车票申请恢复中...");
+		}
+		String key = RedisService.key.NETFLIX_CHANGE_TICKET_KEY.getName() + relationId;
+		if (!redisService.setNx(key, relationId, RedisService.key.NETFLIX_CHANGE_TICKET_KEY.getTimeout())) {
+			throw BusinessRuntimeException.getInstance("正在更换其他车票中..");
+		}
+		try {
+			Long skuId = req.getSkuId();
+			SysConfig sysConfig = sysConfigMapper.selectOne(Wrappers.lambdaQuery(SysConfig.class)
+					.eq(SysConfig::getSysKey, Constant.ERROR_NETFLIX_CHANGE_OTHER_SKU_KEY)
+					.last("limit 1"));
+			List<ErrorNetflixChangeOtherSkuTicketDto> errorNetflixChangeOtherSkuTicketDtos = Jsons.parseList(sysConfig.getSysValue(), ErrorNetflixChangeOtherSkuTicketDto.class);
+			ErrorNetflixChangeOtherSkuTicketDto errorNetflixChangeOtherSkuTicketDto;
+			//自定义顺序 直接获取
+			if (betweenDay <= 60) {
+				//检验规格
+				errorNetflixChangeOtherSkuTicketDto = errorNetflixChangeOtherSkuTicketDtos.get(0);
+			} else if (betweenDay <= 120) {
+				errorNetflixChangeOtherSkuTicketDto = errorNetflixChangeOtherSkuTicketDtos.get(1);
+			} else {
+				errorNetflixChangeOtherSkuTicketDto = errorNetflixChangeOtherSkuTicketDtos.get(2);
+			}
+			List<Long> availableSkuIds = errorNetflixChangeOtherSkuTicketDto.getSkuIds();
+			if (!availableSkuIds.contains(skuId)) {
+				throw BusinessRuntimeException.getInstance("不支持更换该类型车票");
+			}
+			//替换车票
+			TicketChangeDto changeDto = getTicketAndGenerateOrder(userId, skuId, null, null);
+			//替换记录
+			NetflixUserChangeOtherTicketRecord changeOtherTicketRecord = new NetflixUserChangeOtherTicketRecord();
+			changeOtherTicketRecord.setUserId(userId);
+			changeOtherTicketRecord.setRelationId(relationId);
+			changeOtherTicketRecord.setOtherRelationId(changeDto.getOtherRelationId());
+			changeOtherTicketRecord.setRelateOrderId(changeDto.getOtherOrderId());
+			//该车票订单id
+			changeOtherTicketRecord.setOrderId(netflixRelationView.getNfOriOrderId());
+			changeOtherTicketRecord.setStartTime(netflixRelationView.getStartTime());
+			changeOtherTicketRecord.setExpiryTime(expiryTime);
+			netflixUserChangeOtherTicketRecordMapper.insert(changeOtherTicketRecord);
+			if (netflixRelationView.getNfCheckId() != null) {
+				netflixUserAccountStatusRecordMapper.update(null, Wrappers.lambdaUpdate(NetflixUserAccountStatusRecord.class)
+						.set(NetflixUserAccountStatusRecord::getDeleted, false)
+						.eq(NetflixUserAccountStatusRecord::getId, netflixRelationView.getNfCheckId()));
+			}
+			//清除车票
+			GroupsRelation clearRelation = groupsRelationMapper.selectOne(Wrappers.lambdaQuery(GroupsRelation.class)
+					.eq(GroupsRelation::getId, relationId)
+					.in(GroupsRelation::getUserId, userIdList)
+					.last("limit 1"));
+			if (clearRelation != null) {
+				clearService.clearTicket(clearRelation, UserTicketClearedRecord.Source.ticket_replace);
+			}
+		} finally {
+			redisService.del(key);
+		}
+	}
+	@Override
+	public List<GoodsCategorySkuView> getNetflixReplaceOtherSkus(long userId, Long relationId) throws Exception {
+		//校验奈飞账号状态
+		GroupsRelationView netflixRelationView = checkNetflixErrorStatus(relationId, userId);
+		Date expiryTime = netflixRelationView.getExpiryTime();
+		//小于15 不可替换其他车票
+		Long betweenDay = DateUtil.betweenDay(DateTime.now(), expiryTime, false) + 1;
+		SysConfig sysConfig = sysConfigMapper.selectOne(Wrappers.lambdaQuery(SysConfig.class)
+				.eq(SysConfig::getSysKey, Constant.ERROR_NETFLIX_CHANGE_OTHER_SKU_KEY)
+				.last("limit 1"));
+		List<ErrorNetflixChangeOtherSkuTicketDto> errorNetflixChangeOtherSkuTicketDtos = Jsons.parseList(sysConfig.getSysValue(), ErrorNetflixChangeOtherSkuTicketDto.class);
+		ErrorNetflixChangeOtherSkuTicketDto errorNetflixChangeOtherSkuTicketDto;
+		if (betweenDay <= 15) {
+			return null;
+		}
+		//自定义顺序 直接获取
+		if (betweenDay <= 60) {
+			//检验规格
+			errorNetflixChangeOtherSkuTicketDto = errorNetflixChangeOtherSkuTicketDtos.get(0);
+		} else if (betweenDay <= 120) {
+			errorNetflixChangeOtherSkuTicketDto = errorNetflixChangeOtherSkuTicketDtos.get(1);
+		} else {
+			errorNetflixChangeOtherSkuTicketDto = errorNetflixChangeOtherSkuTicketDtos.get(2);
+		}
+		List<GoodsCategorySkuView> goodsCategorySkuViews = beanSearcher.searchAll(GoodsCategorySkuView.class, MapUtils.builder()
+				.field(GoodsCategorySkuView::getSkuId, errorNetflixChangeOtherSkuTicketDto.getSkuIds()).op(Operator.InList)
+				.orderBy(GoodsCategorySkuView::getPrice)
+				.build());
+		return goodsCategorySkuViews;
+	}
+
+	@Override
+	public NetflixRefundInfoResp refundNetflixInfo(Long userId, Long relationId) {
+		//校验奈飞账号状态
+		GroupsRelationView netflixRelationView = checkNetflixErrorStatus(relationId, userId);
+		Date expiryTime = netflixRelationView.getExpiryTime();
+		//小于15
+		Long betweenDay = DateUtil.betweenDay(DateTime.now(), expiryTime, false) + 1;
+		NetflixRefundInfoResp refundInfoResp = new NetflixRefundInfoResp();
+		refundInfoResp.setIsRefund(true);
+		if (betweenDay > 15) {
+			refundInfoResp.setIsRefund(false);
+		}
+		List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
+		OrderDon orderDon = orderDonMapper.selectOne(Wrappers.lambdaQuery(OrderDon.class)
+				.eq(OrderDon::getRelationId, relationId)
+				.in(OrderDon::getUserId, userIdList)
+				.notIn(OrderDon::getStatus, Constant.noOrderAllStatus)
+				.orderByDesc(OrderDon::getId)
+				.last("limit 1"));
+		if (orderDon == null) {
+			refundInfoResp.setIsRefund(false);
+		}
+		Long skuId = netflixRelationView.getSkuId();
+		GoodsDonSku sku = goodsDonSkuMapper.selectById(skuId);
+		BigDecimal orderMoney = orderDon == null ? BigDecimal.ZERO : orderDon.getMoney();
+		if (orderMoney.compareTo(BigDecimal.ZERO) == 0) {
+			refundInfoResp.setIsRefund(false);
+		}
+		//每天单价
+		BigDecimal dayPrice = sku.getPrice().divide(BigDecimal.valueOf(sku.getMonths() * 30), RoundingMode.DOWN);
+		//需要退款金额
+		BigDecimal refundMoney = dayPrice.multiply(BigDecimal.valueOf(betweenDay));
+		refundInfoResp.setPrice(refundMoney);
+		BigDecimal balance = Optional.ofNullable(orderDon.getBalance()).orElse(BigDecimal.ZERO);
+		//超过本身订单金额 不允许退款
+		if (refundMoney.compareTo(orderMoney) > 0) {
+			refundInfoResp.setIsRefund(false);
+		}
+		if (refundMoney.compareTo(orderMoney.add(balance)) > 0) {
+			refundInfoResp.setPrice(orderMoney.add(balance));
+		}
+		refundInfoResp.setRemainDays(betweenDay);
+		return refundInfoResp;
+	}
+
+	@Override
+	public Boolean refundNetflix(ErrorNetflixChangeReq req) throws Exception {
+		Long relationId = req.getRelationId();
+		Long userId = req.getUserId();
+		//校验奈飞账号状态
+		GroupsRelationView netflixRelationView = checkNetflixErrorStatus(relationId, userId);
+		Date expiryTime = netflixRelationView.getExpiryTime();
+		//小于15
+		Long betweenDay = DateUtil.betweenDay(DateTime.now(), expiryTime, false) + 1;
+		if (betweenDay > 15) {
+			throw BusinessRuntimeException.getInstance("车票有效期不超过15天可退款");
+		}
+		List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
+		OrderDon orderDon = orderDonMapper.selectOne(Wrappers.lambdaQuery(OrderDon.class)
+				.eq(OrderDon::getRelationId, relationId)
+				.in(OrderDon::getUserId, userIdList)
+				.notIn(OrderDon::getStatus, Constant.noOrderAllStatus)
+				.orderByDesc(OrderDon::getId)
+				.last("limit 1"));
+		if (orderDon == null) {
+			throw BusinessRuntimeException.getInstance("对应车票订单不存在,请联系客服");
+		}
+		Long skuId = netflixRelationView.getSkuId();
+		GoodsDonSku sku = goodsDonSkuMapper.selectById(skuId);
+		BigDecimal orderMoney = orderDon.getMoney();
+		if (orderMoney.compareTo(BigDecimal.ZERO) == 0) {
+			throw BusinessRuntimeException.getInstance("不符合退款条件");
+		}
+		//每天单价
+		BigDecimal dayPrice = sku.getPrice().divide(BigDecimal.valueOf(sku.getMonths() * 30), RoundingMode.DOWN);
+		//需要退款金额
+		BigDecimal refundMoney = dayPrice.multiply(BigDecimal.valueOf(betweenDay));
+		if (refundMoney.compareTo(orderMoney) > 0) {
+			throw BusinessRuntimeException.getInstance("不符合退款条件");
+		}
+		//调用第三方接口退款
+		OrderRefundReq refundReq = new OrderRefundReq();
+		refundReq.setRefundMoney(refundMoney);
+		refundReq.setRefundType("money");
+		String outNo = orderRefundService.centerRefund(refundReq, orderDon);
+		orderDon.setStatus(OrderDon.Status.refund);
+		orderDon.setRefundMoney(refundMoney);
+		GoodsDon goodsDon = goodsDonMapper.selectById(orderDon.getGoodsId());
+		orderRefundService.refundRecord(orderDon, refundMoney, "系统", goodsDon, sku, outNo, orderDon.getType().name(), null);
+		//清除车票
+		GroupsRelation relation = groupsRelationMapper.selectById(relationId);
+		clearService.clearTicket(relation, UserTicketClearedRecord.Source.refund);
+		if (netflixRelationView.getNfCheckId() != null) {
+			netflixUserAccountStatusRecordMapper.update(null, Wrappers.lambdaUpdate(NetflixUserAccountStatusRecord.class)
+					.set(NetflixUserAccountStatusRecord::getDeleted, false)
+					.eq(NetflixUserAccountStatusRecord::getId, netflixRelationView.getNfCheckId()));
+		}
+		return true;
+	}
+
 	private DistributePosterGoodsDetailView setGoodsPoster(RenewalView ticket, User user, List<Long> userIds) {
 		DistributePosterGoodsDetailView distributePosterGoodsDetailView = beanSearcher.searchFirst(DistributePosterGoodsDetailView.class, MapUtils.flatBuilder(request.getParameterMap())
 				.field(DistributePosterGoodsFrontView::getGoodsId, ticket.getGoodsId()).build());
@@ -1625,4 +1914,118 @@ public class GroupRelationFrontServiceImpl implements GroupRelationFrontService
 			}
 		}
 	}
+
+	/**
+	 * 检验奈飞账号状态
+	 */
+	public GroupsRelationView checkNetflixErrorStatus(Long relationId, Long userId) {
+		List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
+		NetflixUserAccountStatusRecord netflixUseAccountStatusRecord = netflixUserAccountStatusRecordMapper.selectOne(Wrappers.lambdaQuery(NetflixUserAccountStatusRecord.class)
+				.eq(NetflixUserAccountStatusRecord::getRelationId, relationId)
+				.eq(NetflixUserAccountStatusRecord::getDeleted, 1)
+				.in(NetflixUserAccountStatusRecord::getUserId, userIdList)
+				.last("limit 1"));
+		if (netflixUseAccountStatusRecord == null) {
+			throw BusinessRuntimeException.getInstance("请先提交账号检测");
+		}
+		Integer state = netflixUseAccountStatusRecord.getState();
+		if (state == 0) {
+			throw BusinessRuntimeException.getInstance("您的账号检测中");
+		}
+		//2是登录失败 3是被封号
+		if (state != 3) {
+			throw BusinessRuntimeException.getInstance("您的账号未封禁");
+		}
+		GroupsRelationView netflixRelationView = beanSearcher.searchFirst(GroupsRelationView.class, MapUtils.builder()
+				.field(GroupsRelationView::getId, relationId)
+				.field(GroupsRelationView::getUserId, userIdList).op(Operator.InList)
+				.field(GroupsRelationView::getGoodsId, Constant.NETFLIX_GID)
+				.field(GroupsRelationView::getExpiryTime, DateTime.now()).op(Operator.GreaterThan)
+				.build());
+		if (netflixRelationView == null) {
+			throw BusinessRuntimeException.getInstance("奈飞车票已过期");
+		}
+		netflixRelationView.setNfCheckId(netflixUseAccountStatusRecord.getId());
+		netflixRelationView.setNfOriOrderId(netflixUseAccountStatusRecord.getOrderId());
+		return netflixRelationView;
+	}
+
+	/**
+	 * 获取车票 并生成订单
+	 */
+	public TicketChangeDto getTicketAndGenerateOrder(Long userId, Long skuId, Date startTime, Date expiryTime) {
+		Retryer<TicketChangeDto> build = RetryerBuilder.<TicketChangeDto>newBuilder()
+				.retryIfException()
+				//运行时异常重试
+				.retryIfRuntimeException()
+				//false重试
+				.retryIfResult(res -> res == null)
+				//1s 间隔
+				.withWaitStrategy(WaitStrategies.fixedWait(1, TimeUnit.SECONDS))
+				//停止策略 : 尝试请求1次
+				.withStopStrategy(StopStrategies.stopAfterAttempt(3)).build();
+		if (startTime == null) {
+			startTime = DateTime.now();
+		}
+		GoodsDonSku sku = goodsDonSkuMapper.selectById(skuId);
+		if (expiryTime == null) {
+			if (sku.getDays() != null) {
+				expiryTime = DateUtil.offsetDay(startTime, sku.getDays());
+			} else {
+				expiryTime = DateUtil.offsetMonth(startTime, sku.getMonths());
+			}
+		}
+		Date finalStartTime = startTime;
+		Date finalExpiryTime = expiryTime;
+		try {
+			return build.call(() -> {
+				GroupsTrips groups = groupsMapper.selectCollegeStudentUserGptTicket(skuId);
+				GroupsRelation relation = null;
+				if (groups != null) {
+					relation = groupsRelationMapper.selectRandomOneRelationByGroupsId(groups.getId());
+				}
+				//生成空车队
+				if (relation == null) {
+					relation = groupsFuncService.createNewGroupsTrips(sku);
+				}
+				int update = groupsRelationMapper.update(null, Wrappers.lambdaUpdate(GroupsRelation.class)
+						.set(GroupsRelation::getUserId, userId)
+						.set(GroupsRelation::getStartTime, finalStartTime)
+						.set(GroupsRelation::getExpiryTime, finalExpiryTime)
+						.set(GroupsRelation::getStatus, GroupsRelation.Status.validity)
+						.eq(GroupsRelation::getId, relation.getId())
+						.eq(GroupsRelation::getUserId, 0));
+				if (update > 0) {
+					groupsMapper.decrAvailableNum(groups.getId());
+					Long relationId = relation.getId();
+					//生成新的订单
+					OrderDon otherOrderDon = generateNewTicketOrder(relationId, userId, sku);
+
+					TicketChangeDto ticketChangeDto = new TicketChangeDto();
+					ticketChangeDto.setOtherRelationId(relationId);
+					ticketChangeDto.setOtherOrderId(otherOrderDon.getId());
+					return ticketChangeDto;
+				}
+				log.error("发送用户:{} GPT镜像车票失败,进行重试", userId);
+				return null;
+			});
+		} catch (ExecutionException | RetryException e) {
+			log.error("发送用户GPT镜像失败:{}", StringUtil.getErrorText(e));
+			return null;
+		}
+	}
+
+	private OrderDon generateNewTicketOrder(Long relationId, Long userId, GoodsDonSku sku) {
+		OrderDon orderDon = new OrderDon();
+		orderDon.setUserId(userId);
+		orderDon.setOrderNo(SEQUENCE.nextId().toString());
+		orderDon.setMoney(BigDecimal.ZERO);
+		orderDon.setRelationId(relationId);
+		orderDon.setGoodsId(sku.getGoodsId());
+		orderDon.setSkuId(sku.getId());
+		orderDon.setType(OrderDon.Type.TICKET_REPLACE);
+		orderDon.setStatus(OrderDon.Status.complete);
+		orderDonMapper.insert(orderDon);
+		return orderDon;
+	}
 }

+ 63 - 3
netflix-web/src/main/java/com/cyksj/web/controller/group/GroupRelationController.java

@@ -32,6 +32,9 @@ import com.cyksj.model.manage.views.AccountView;
 import com.cyksj.model.manage.views.GroupsRelationView;
 import com.cyksj.model.request.*;
 import com.cyksj.model.response.GptStandbyResp;
+import com.cyksj.model.response.NetflixErrorAccountStatus;
+import com.cyksj.model.response.NetflixRecoverResp;
+import com.cyksj.model.response.NetflixRefundInfoResp;
 import com.cyksj.model.views.*;
 import com.cyksj.redis.RedisService;
 import com.cyksj.service.chatgpt.ChatGptAuthService;
@@ -1142,6 +1145,7 @@ public class GroupRelationController {
      * 申请更换车票
      */
     @PostMapping("/change/ticket")
+    @Deprecated
     public Result<String> changeTicket(@RequestBody ChangeTicketReq changeTicketReq) {
         long userId = StpUserUtil.getLoginIdAsLong();
         List<Long> relationIds = changeTicketReq.getRelationIds();
@@ -1205,10 +1209,66 @@ public class GroupRelationController {
      * 奈飞账号检测结果
      */
     @GetMapping("/get/netflix/error/account/status/{relationId}")
-    public Result<Integer> getNetflixErrorAccountStatus(@PathVariable Long relationId) {
+    public Result<NetflixErrorAccountStatus> getNetflixErrorAccountStatus(@PathVariable Long relationId) {
         long userId = StpUserUtil.getLoginIdAsLong();
-        Integer state = groupRelationFrontService.getNetflixErrorAccountStatus(userId, relationId);
-        return GatewayResponse.SUCCESS.newBuilder().toResult(state);
+        NetflixErrorAccountStatus status = groupRelationFrontService.getNetflixErrorAccountStatus(userId, relationId);
+        return GatewayResponse.SUCCESS.newBuilder().toResult(status);
+    }
+
+    /**
+     * 奈飞可替换其他规格车票列表
+     */
+    @GetMapping("/get/netflix/replace/other/skus/{relationId}")
+    public Result<List<GoodsCategorySkuView>> getNetflixReplaceOtherSkus(@PathVariable Long relationId) throws Exception {
+        long userId = StpUserUtil.getLoginIdAsLong();
+        List<GoodsCategorySkuView> goodsCategorySkuViews = groupRelationFrontService.getNetflixReplaceOtherSkus(userId, relationId);
+        return GatewayResponse.SUCCESS.newBuilder().toResult(goodsCategorySkuViews);
+    }
+
+    /**
+     * 奈飞账号申请恢复
+     */
+    @PostMapping("/netflix/submit/recover")
+    public Result<NetflixRecoverResp> netflixSubmitRecover(@RequestBody ErrorNetflixChangeReq req) {
+        long userId = StpUserUtil.getLoginIdAsLong();
+        req.setUserId(userId);
+        NetflixRecoverResp recoverResp = groupRelationFrontService.netflixSubmitRecover(req);
+        return GatewayResponse.SUCCESS.newBuilder().toResult(recoverResp);
+    }
+
+    /**
+     * 奈飞更换其他车票
+     */
+    @PostMapping("/netflix/submit/change/otherTicket")
+    public Result<Integer> errorNetflixSubmitChangeOtherTicket(@RequestBody ErrorNetflixChangeReq req) throws Exception {
+        long userId = StpUserUtil.getLoginIdAsLong();
+        req.setUserId(userId);
+        groupRelationFrontService.errorNetflixSubmitChangeOtherTicket(req);
+        return GatewayResponse.SUCCESS.newBuilder().toResult();
+    }
+
+    /**
+     * 不超过15天 奈飞退款info
+     */
+    @GetMapping("/netflix/refund/info/{relationId}")
+    public Result<NetflixRefundInfoResp> refundNetflixInfo(@PathVariable Long relationId) {
+        long userId = StpUserUtil.getLoginIdAsLong();
+        //是否可进行退款 true可 false否 走恢复账号流程
+        NetflixRefundInfoResp refundInfoResp = groupRelationFrontService.refundNetflixInfo(userId, relationId);
+        return GatewayResponse.SUCCESS.newBuilder().toResult(refundInfoResp);
+    }
+
+    /**
+     * 奈飞不超过15天有效期进行退款
+     * 是否可进行退款 true可 false否 走恢复账号流程
+     */
+    @PostMapping("/netflix/refund")
+    public Result<Boolean> refundNetflix(@RequestBody ErrorNetflixChangeReq req) throws Exception {
+        long userId = StpUserUtil.getLoginIdAsLong();
+        req.setUserId(userId);
+        //是否可进行退款 true可 false否 走恢复账号流程
+        Boolean availableRefund = groupRelationFrontService.refundNetflix(req);
+        return GatewayResponse.SUCCESS.newBuilder().toResult(availableRefund);
     }
 
     /**

+ 3 - 3
netflix-web/src/main/java/com/cyksj/web/controller/manage/account/CmsAccountController.java

@@ -502,7 +502,7 @@ public class CmsAccountController {
 
 		    groupsTrips.setSkuId(account.getSkuId());
 		    groupsTrips.setAccountId(account.getId());
-		    cmsGroupService.issuance(groupsTrips, account.getVerifyCodes());
+		    cmsGroupService.issuance(groupsTrips, account.getVerifyCodes(), false);
 	    }
 
         return GatewayResponse.SUCCESS.newBuilder().toResult();
@@ -520,7 +520,7 @@ public class CmsAccountController {
 	 * 导入账号
 	 */
 	@PostMapping("/import/account/issuance")
-	public Result<String> importAccountAndIssuance(Long goodsId, Long skuId, MultipartFile file) throws Exception {
+	public Result<String> importAccountAndIssuance(Long goodsId, Long skuId, MultipartFile file, Boolean isRecover) throws Exception {
 		GoodsDonSkuView goodsDonSkuView = beanSearcher.searchFirst(GoodsDonSkuView.class, MapUtils.builder().field(GoodsDonSkuView::getGoodsId, goodsId).field(GoodsDonSkuView::getSkuId, skuId).build());
 		if (goodsDonSkuView == null) {
 			throw BusinessRuntimeException.getInstance("所选平台与规格不匹配");
@@ -587,7 +587,7 @@ public class CmsAccountController {
 					groupsTrips.setAvailableParkingNum(availableParkingNum);
 					groupsTrips.setSkuId(at.getSkuId());
 					groupsTrips.setAccountId(at.getId());
-					cmsGroupService.issuance(groupsTrips, null);
+					cmsGroupService.issuance(groupsTrips, null, isRecover);
 				}
 			}
 

+ 42 - 0
netflix-web/src/main/java/com/cyksj/web/controller/manage/account/NetflixErrorRecoverController.java

@@ -0,0 +1,42 @@
+package com.cyksj.web.controller.manage.account;
+
+import com.cyksj.dto.Result;
+import com.cyksj.enums.GatewayResponse;
+import com.cyksj.model.views.NetflixErrorRecoverView;
+import com.ejlchina.searcher.BeanSearcher;
+import com.ejlchina.searcher.SearchResult;
+import com.ejlchina.searcher.util.MapUtils;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * 项目名: yhlxj
+ * 文件名: NetflixErrorRecoverController
+ * 创建者: JavaZou
+ * 创建时间:2025/6/24 13:41
+ */
+@Slf4j
+@RequiredArgsConstructor
+@RequestMapping("/manage/netflix")
+@RestController
+public class NetflixErrorRecoverController {
+	private final BeanSearcher beanSearcher;
+
+	private final HttpServletRequest request;
+
+	/**
+	 * 奈飞恢复列表
+	 */
+	@GetMapping("/get/recover/list")
+	public Result<SearchResult<NetflixErrorRecoverView>> getRecoverList() {
+		SearchResult<NetflixErrorRecoverView> search = beanSearcher.search(NetflixErrorRecoverView.class, MapUtils.flatBuilder(request.getParameterMap())
+				.field(NetflixErrorRecoverView::getStatus, 0)
+				.build());
+		return GatewayResponse.SUCCESS.newBuilder().toResult(search);
+	}
+}

+ 1 - 1
netflix-web/src/main/java/com/cyksj/web/controller/manage/group/CmsGroupController.java

@@ -165,7 +165,7 @@ public class CmsGroupController {
     @Log(module = "发布车票/新增车队", businessType = BusinessType.POST, isSaveRequestData = true)
     public Result<GroupsTrips> add(@RequestBody GroupsTrips groups){
 
-        GroupsTrips groupsTrips = groupService.issuance(groups, null);
+        GroupsTrips groupsTrips = groupService.issuance(groups, null, false);
 
         return GatewayResponse.SUCCESS.newBuilder().toResult(groupsTrips);
     }