فهرست منبع

Merge branch 'mj'

# Conflicts:
#	netflix-service/src/main/java/com/cyksj/redis/RedisService.java
#	netflix-service/src/main/java/com/cyksj/service/order/impl/OrderDonServiceImpl.java
#	netflix-service/src/main/java/com/cyksj/service/relation/impl/GroupRelationFrontServiceImpl.java
#	netflix-web/src/main/java/com/cyksj/web/controller/goods/GoodsDonController.java
#	netflix-web/src/main/java/com/cyksj/web/controller/payment/OrderController.java
#	netflix-web/src/main/resources/application-dev.yml
#	netflix-web/src/main/resources/application-prd.yml
zwhui 2 سال پیش
والد
کامیت
8b4970e05f
69فایلهای تغییر یافته به همراه3821 افزوده شده و 34 حذف شده
  1. 6 0
      netflix-common/src/main/java/com/cyksj/common/constant/Constant.java
  2. 11 0
      netflix-dao/src/main/java/com/cyksj/mapper/MidjourneyAccountMapper.java
  3. 11 0
      netflix-dao/src/main/java/com/cyksj/mapper/MidjourneyUserConversationMapper.java
  4. 11 0
      netflix-dao/src/main/java/com/cyksj/mapper/MidjourneyUserMapper.java
  5. 13 0
      netflix-dao/src/main/java/com/cyksj/mapper/RefundUpgradeOrderDonMapper.java
  6. 11 0
      netflix-dao/src/main/java/com/cyksj/mapper/UpgradeOrderDonMapper.java
  7. 11 0
      netflix-dao/src/main/java/com/cyksj/mapper/UpgradePackageMapper.java
  8. 21 0
      netflix-dao/src/main/java/com/cyksj/model/dto/BlendDimensions.java
  9. 35 0
      netflix-dao/src/main/java/com/cyksj/model/dto/MessageButton.java
  10. 25 0
      netflix-dao/src/main/java/com/cyksj/model/dto/SubmitActionDTO.java
  11. 25 0
      netflix-dao/src/main/java/com/cyksj/model/dto/SubmitBlendDTO.java
  12. 19 0
      netflix-dao/src/main/java/com/cyksj/model/dto/SubmitDescribeDTO.java
  13. 26 0
      netflix-dao/src/main/java/com/cyksj/model/dto/SubmitImagineDTO.java
  14. 30 0
      netflix-dao/src/main/java/com/cyksj/model/dto/SubmitModalDTO.java
  15. 19 0
      netflix-dao/src/main/java/com/cyksj/model/dto/SubmitShortenDTO.java
  16. 11 0
      netflix-dao/src/main/java/com/cyksj/model/entity/GoodsDonSku.java
  17. 54 0
      netflix-dao/src/main/java/com/cyksj/model/entity/MidjourneyAccount.java
  18. 57 0
      netflix-dao/src/main/java/com/cyksj/model/entity/MidjourneyUser.java
  19. 102 0
      netflix-dao/src/main/java/com/cyksj/model/entity/MidjourneyUserConversation.java
  20. 6 0
      netflix-dao/src/main/java/com/cyksj/model/entity/OrderDetailView.java
  21. 55 0
      netflix-dao/src/main/java/com/cyksj/model/entity/RefundUpgradeOrderDon.java
  22. 138 0
      netflix-dao/src/main/java/com/cyksj/model/entity/UpgradeOrderDon.java
  23. 53 0
      netflix-dao/src/main/java/com/cyksj/model/entity/UpgradePackage.java
  24. 87 0
      netflix-dao/src/main/java/com/cyksj/model/excel/ExcelUpgradeOrderDonData.java
  25. 13 0
      netflix-dao/src/main/java/com/cyksj/model/manage/request/ReqGoodsSkuInsert.java
  26. 7 0
      netflix-dao/src/main/java/com/cyksj/model/manage/views/OrderDonBackView.java
  27. 65 0
      netflix-dao/src/main/java/com/cyksj/model/manage/views/UpgradeOrderDetailView.java
  28. 110 0
      netflix-dao/src/main/java/com/cyksj/model/manage/views/UpgradeOrderDonBackView.java
  29. 73 0
      netflix-dao/src/main/java/com/cyksj/model/manage/views/UpgradeOrderDonSearchView.java
  30. 90 0
      netflix-dao/src/main/java/com/cyksj/model/manage/views/UpgradeOrderDonView.java
  31. 10 0
      netflix-dao/src/main/java/com/cyksj/model/request/OrderPayRequest.java
  32. 29 0
      netflix-dao/src/main/java/com/cyksj/model/response/SubmitResult.java
  33. 11 0
      netflix-dao/src/main/java/com/cyksj/model/views/GoodsDonSkuFrontView.java
  34. 79 0
      netflix-dao/src/main/java/com/cyksj/model/views/MidjourneyAccountView.java
  35. 40 0
      netflix-dao/src/main/java/com/cyksj/model/views/MidjourneyUserView.java
  36. 82 0
      netflix-dao/src/main/java/com/cyksj/model/views/RefundUpdradeOrderDonView.java
  37. 21 0
      netflix-dao/src/main/java/com/cyksj/model/views/RenewalView.java
  38. 80 0
      netflix-dao/src/main/java/com/cyksj/model/views/UpgradePackageView.java
  39. 2 0
      netflix-service/src/main/java/com/cyksj/config/zfb/BaseZfbConfig.java
  40. 6 0
      netflix-service/src/main/java/com/cyksj/redis/RedisService.java
  41. 53 13
      netflix-service/src/main/java/com/cyksj/service/groups/impl/GroupFunServiceImpl.java
  42. 4 0
      netflix-service/src/main/java/com/cyksj/service/mange/CmsOrderDonService.java
  43. 133 3
      netflix-service/src/main/java/com/cyksj/service/mange/impl/CmsOrderDonServiceImpl.java
  44. 11 0
      netflix-service/src/main/java/com/cyksj/service/mange/upgrade/UpgradePackageService.java
  45. 19 0
      netflix-service/src/main/java/com/cyksj/service/mange/upgrade/impl/UpgradePackageServiceImpl.java
  46. 27 0
      netflix-service/src/main/java/com/cyksj/service/midjourney/MidjourneyAccountService.java
  47. 29 0
      netflix-service/src/main/java/com/cyksj/service/midjourney/MidjourneyService.java
  48. 283 0
      netflix-service/src/main/java/com/cyksj/service/midjourney/impl/MidJourneyAccountServiceImpl.java
  49. 302 0
      netflix-service/src/main/java/com/cyksj/service/midjourney/impl/MidjourneyServiceImpl.java
  50. 15 0
      netflix-service/src/main/java/com/cyksj/service/order/OrderDonService.java
  51. 7 3
      netflix-service/src/main/java/com/cyksj/service/order/OrderRefundService.java
  52. 338 0
      netflix-service/src/main/java/com/cyksj/service/order/impl/OrderDonServiceImpl.java
  53. 73 4
      netflix-service/src/main/java/com/cyksj/service/order/impl/OrderRefundServiceImpl.java
  54. 1 0
      netflix-service/src/main/java/com/cyksj/service/pay/AliPayCommonService.java
  55. 47 3
      netflix-service/src/main/java/com/cyksj/service/pay/impl/AliPayCommonServiceImpl.java
  56. 13 0
      netflix-service/src/main/java/com/cyksj/service/relation/impl/GroupRelationFrontServiceImpl.java
  57. 13 0
      netflix-web/src/main/java/com/cyksj/web/controller/goods/GoodsDonController.java
  58. 127 4
      netflix-web/src/main/java/com/cyksj/web/controller/manage/CmsOrderController.java
  59. 1 1
      netflix-web/src/main/java/com/cyksj/web/controller/manage/account/CmsAccountController.java
  60. 3 0
      netflix-web/src/main/java/com/cyksj/web/controller/manage/goods/CmsGoodsDonController.java
  61. 3 0
      netflix-web/src/main/java/com/cyksj/web/controller/manage/goods/CmsGoodsDonSkuController.java
  62. 106 0
      netflix-web/src/main/java/com/cyksj/web/controller/manage/mj/MidJourneyController.java
  63. 121 0
      netflix-web/src/main/java/com/cyksj/web/controller/manage/upgrade/UpgradePackageController.java
  64. 341 0
      netflix-web/src/main/java/com/cyksj/web/controller/mirror/MidjourneyController.java
  65. 35 0
      netflix-web/src/main/java/com/cyksj/web/controller/mirror/MirrorController.java
  66. 156 2
      netflix-web/src/main/java/com/cyksj/web/controller/payment/OrderController.java
  67. 0 0
      netflix-web/src/main/resources/application-dev.yml
  68. 0 0
      netflix-web/src/main/resources/application-prd.yml
  69. 5 1
      netflix-web/src/main/resources/application-pre.yml

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

@@ -292,6 +292,12 @@ public interface Constant {
 	 */
 	String GPT_CAR_ONLY_ACCOUNT = "gptcaroo21213";
 
+
+	/**
+	 * mj 镜像车队唯一账号
+	 */
+	String MJ_MIRROR_ONLY_ACCOUNT = "mjmirror1213";
+
 	/**
 	 * 免登录渠道
 	 */

+ 11 - 0
netflix-dao/src/main/java/com/cyksj/mapper/MidjourneyAccountMapper.java

@@ -0,0 +1,11 @@
+package com.cyksj.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.cyksj.model.entity.MidjourneyAccount;
+
+/**
+ * @author zwhui
+ * @date 2024/4/12 15:51
+ */
+public interface MidjourneyAccountMapper extends BaseMapper<MidjourneyAccount> {
+}

+ 11 - 0
netflix-dao/src/main/java/com/cyksj/mapper/MidjourneyUserConversationMapper.java

@@ -0,0 +1,11 @@
+package com.cyksj.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.cyksj.model.entity.MidjourneyUserConversation;
+
+/**
+ * @author zwhui
+ * @date 2024/4/23 16:03
+ */
+public interface MidjourneyUserConversationMapper extends BaseMapper<MidjourneyUserConversation> {
+}

+ 11 - 0
netflix-dao/src/main/java/com/cyksj/mapper/MidjourneyUserMapper.java

@@ -0,0 +1,11 @@
+package com.cyksj.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.cyksj.model.entity.MidjourneyUser;
+
+/**
+ * @author zwhui
+ * @date 2024/4/16 17:46
+ */
+public interface MidjourneyUserMapper extends BaseMapper<MidjourneyUser> {
+}

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

@@ -0,0 +1,13 @@
+package com.cyksj.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.cyksj.model.entity.RefundUpgradeOrderDon;
+
+/*
+ *项目名: netflix
+ *文件名: RefundOrderDonMapper
+ *创建者: JavaZou
+ *创建时间:2022/11/9 9:59
+ */
+public interface RefundUpgradeOrderDonMapper extends BaseMapper<RefundUpgradeOrderDon> {
+}

+ 11 - 0
netflix-dao/src/main/java/com/cyksj/mapper/UpgradeOrderDonMapper.java

@@ -0,0 +1,11 @@
+package com.cyksj.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.cyksj.model.entity.UpgradeOrderDon;
+
+/**
+ * @author zwhui
+ * @date 2024/4/24 17:29
+ */
+public interface UpgradeOrderDonMapper extends BaseMapper<UpgradeOrderDon> {
+}

+ 11 - 0
netflix-dao/src/main/java/com/cyksj/mapper/UpgradePackageMapper.java

@@ -0,0 +1,11 @@
+package com.cyksj.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.cyksj.model.entity.UpgradePackage;
+
+/**
+ * @author zwhui
+ * @date 2024/4/24 15:03
+ */
+public interface UpgradePackageMapper extends BaseMapper<UpgradePackage> {
+}

+ 21 - 0
netflix-dao/src/main/java/com/cyksj/model/dto/BlendDimensions.java

@@ -0,0 +1,21 @@
+package com.cyksj.model.dto;
+
+
+import lombok.Getter;
+
+@Getter
+public enum BlendDimensions {
+
+	PORTRAIT("2:3"),
+
+	SQUARE("1:1"),
+
+	LANDSCAPE("3:2");
+
+	private final String value;
+
+	BlendDimensions(String value) {
+		this.value = value;
+	}
+
+}

+ 35 - 0
netflix-dao/src/main/java/com/cyksj/model/dto/MessageButton.java

@@ -0,0 +1,35 @@
+package com.cyksj.model.dto;
+
+import lombok.Data;
+
+/**
+ * 可执行按钮
+ */
+@Data
+public class MessageButton {
+    /**
+     * 动作标识
+     */
+    private String customId;
+
+    /**
+     * 图标
+     */
+    private String emoji = "";
+
+    /**
+     * 文本
+     */
+    private String label;
+
+    /**
+     * 类型,系统内部使用
+     */
+    private int type = 2;
+
+    /**
+     * 样式: 2(Primary)、3(Green)
+     */
+    private int style = 2;
+
+}

+ 25 - 0
netflix-dao/src/main/java/com/cyksj/model/dto/SubmitActionDTO.java

@@ -0,0 +1,25 @@
+package com.cyksj.model.dto;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+
+/**
+ * @author zwhui
+ * @date 2024/4/24 10:56
+ */
+@Data
+public class SubmitActionDTO {
+    /**
+     * 任务对应按钮的customId
+     */
+    @NotBlank(message = "customId参数不能为空")
+    private String customId;
+
+    /**
+     * 执行Imagine任务生成的ID
+     */
+    @NotNull(message = "任务ID不能为空")
+    private Long taskId;
+}

+ 25 - 0
netflix-dao/src/main/java/com/cyksj/model/dto/SubmitBlendDTO.java

@@ -0,0 +1,25 @@
+package com.cyksj.model.dto;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import java.util.List;
+
+/**
+ * @author zwhui
+ * @date 2024/4/23 13:49
+ */
+@Data
+public class SubmitBlendDTO {
+
+	/**
+	 * base64
+	 */
+	@NotBlank(message = "请上传至少一张图片")
+	private List<String> base64Array;
+
+	/**
+	 * 比例: PORTRAIT(2:3); SQUARE(1:1); LANDSCAPE(3:2)
+	 */
+	private BlendDimensions dimensions = BlendDimensions.SQUARE;
+}

+ 19 - 0
netflix-dao/src/main/java/com/cyksj/model/dto/SubmitDescribeDTO.java

@@ -0,0 +1,19 @@
+package com.cyksj.model.dto;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+
+/**
+ * @author zwhui
+ * @date 2024/4/23 13:49
+ */
+@Data
+public class SubmitDescribeDTO {
+
+	/**
+	 * base64
+	 */
+	@NotBlank(message = "请上传图片")
+	private String base64;
+}

+ 26 - 0
netflix-dao/src/main/java/com/cyksj/model/dto/SubmitImagineDTO.java

@@ -0,0 +1,26 @@
+package com.cyksj.model.dto;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import java.util.List;
+
+/**
+ * @author zwhui
+ * @date 2024/4/23 13:49
+ */
+@Data
+public class SubmitImagineDTO {
+
+    /**
+     * 提示词
+     */
+    @NotBlank(message = "提示词不能为空")
+    private String prompt;
+
+    /**
+     * 垫图base64数组
+     */
+    private List<String> base64Array;
+
+}

+ 30 - 0
netflix-dao/src/main/java/com/cyksj/model/dto/SubmitModalDTO.java

@@ -0,0 +1,30 @@
+package com.cyksj.model.dto;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * @author zwhui
+ * @date 2024/4/23 18:04
+ */
+@Data
+public class SubmitModalDTO {
+
+    /**
+     * 局部重绘base64
+     */
+    private String maskBase64;
+
+    /**
+     * 提示词
+     */
+    private String prompt;
+
+    /**
+     * 任务id
+     */
+    @NotNull(message = "任务ID不能为空")
+    private Long taskId;
+
+}

+ 19 - 0
netflix-dao/src/main/java/com/cyksj/model/dto/SubmitShortenDTO.java

@@ -0,0 +1,19 @@
+package com.cyksj.model.dto;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+
+/**
+ * @author zwhui
+ * @date 2024/4/23 18:07
+ */
+@Data
+public class SubmitShortenDTO {
+
+    /**
+     * 提示词
+     */
+    @NotBlank(message = "提示词不能为空")
+    private String prompt;
+}

+ 11 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/GoodsDonSku.java

@@ -178,6 +178,17 @@ public class GoodsDonSku extends BaseEntity {
      */
     private Long gptLimitTime;
 
+    /**
+     * mj fast次数
+     */
+    private Integer mjFastNum;
+
+    /**
+     * mj relax次数
+     */
+    @TableField(updateStrategy = FieldStrategy.IGNORED)
+    private Integer mjRelaxNum;
+
     /**
      * 特定价格
      */

+ 54 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/MidjourneyAccount.java

@@ -0,0 +1,54 @@
+package com.cyksj.model.entity;
+
+import com.ejlchina.searcher.bean.SearchBean;
+import lombok.Data;
+
+/**
+ * @author zwhui
+ * @date 2024/4/12 10:52
+ */
+@Data
+@SearchBean(tables = "midjourney_account")
+public class MidjourneyAccount extends BaseEntity{
+
+    /**
+     * 账号id
+     */
+    private Long accountId;
+
+    /**
+     * 状态 true有效 false无效
+     */
+    private Boolean status;
+
+    /**
+     * 服务器id
+     */
+    private Long guildId;
+
+    /**
+     * 频道id
+     */
+    private Long channelId;
+
+    /**
+     * user_token
+     */
+    private String userToken;
+
+    /**
+     * plus服务实例id
+     */
+    private Long instanceId;
+
+    /**
+     * fast剩余时间
+     */
+    private String fastRemainTime;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+}

+ 57 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/MidjourneyUser.java

@@ -0,0 +1,57 @@
+package com.cyksj.model.entity;
+
+/**
+ * @author zwhui
+ * @date 2024/4/16 17:01
+ */
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.ejlchina.searcher.bean.SearchBean;
+import lombok.Data;
+
+import java.util.Date;
+
+@Data
+@SearchBean(tables = "midjourney_user")
+public class MidjourneyUser extends BaseEntity{
+
+    /**
+     * 名称
+     */
+    private String name;
+
+    /**
+     * 头像
+     */
+    private String img;
+
+    /**
+     * 用户凭证
+     */
+    private String userToken;
+
+    /**
+     * 过期时间
+     */
+    private Date expireTime;
+
+    /**
+     * 车票id
+     */
+    private Long relationId;
+
+    /**
+     * mj fast次数
+     */
+    private Integer mjFastNum;
+
+    /**
+     * mj relax次数
+     */
+    private Integer mjRelaxNum;
+
+    /**
+     * 当前模式 1:fast 2:relax
+     */
+    private Integer mode;
+}

+ 102 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/MidjourneyUserConversation.java

@@ -0,0 +1,102 @@
+package com.cyksj.model.entity;
+
+import com.ejlchina.searcher.bean.SearchBean;
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author zwhui
+ * @date 2024/4/23 11:10
+ */
+@Data
+@Accessors(chain = true)
+@SearchBean(tables = "midjourney_user_conversation")
+public class MidjourneyUserConversation extends BaseEntity implements Serializable {
+
+    private Long userId;
+
+    /**
+     * plus服务任务id
+     */
+    private Long taskId;
+
+    /**
+     * IMAGINE,UPSCALE,VARIATION,ZOOM,PAN,DESCRIBE,BLEND,SHORTEN
+     */
+    private String action;
+
+    /**
+     * 频道id
+     */
+    private Long channelId;
+
+    /**
+     * plus服务实例id
+     */
+    private Long instanceId;
+
+    /**
+     * 模式 1:fast 2:relax
+     */
+    private Integer mode;
+
+    /**
+     * 开始时间
+     */
+    private Long startTime;
+
+    /**
+     * 完成时间
+     */
+    private Long finishTime;
+
+    /**
+     * 提示词
+     */
+    private String prompt;
+
+    /**
+     * 描述
+     */
+    private String description;
+
+    /**
+     * 任务状态,可用值:IN_PROGRESS,FAILURE,SUCCESS,CANCEL
+     */
+    private String status;
+
+    /**
+     * 失败原因
+     */
+    private String failReason;
+
+    /**
+     * 进度
+     */
+    private String progress;
+
+    /**
+     * 图片地址
+     */
+    private String imageUrl;
+
+    /**
+     * 按钮
+     */
+    private String buttons;
+
+    /**
+     * 属性
+     */
+    private String properties;
+
+    /**
+     * 书签
+     */
+    private Boolean bookmark;
+
+}

+ 6 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/OrderDetailView.java

@@ -71,6 +71,12 @@ public class OrderDetailView {
 	@DbIgnore
 	private OrderDonTransport transport;
 
+	@DbIgnore
+	private Integer mjFastNum;
+
+	@DbIgnore
+	private Integer mjRelaxNum;
+
 	@DbField("o.relation_id")
 	private Long relationId;
 

+ 55 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/RefundUpgradeOrderDon.java

@@ -0,0 +1,55 @@
+package com.cyksj.model.entity;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import java.math.BigDecimal;
+
+/*
+ *项目名: netflix
+ *文件名: RefundOrderDon
+ *创建者: JavaZou
+ *创建时间:2022/11/8 18:16
+ */
+@Getter
+@Setter
+public class RefundUpgradeOrderDon extends BaseEntity{
+	private Long userId;
+
+	private Long orderId;
+
+	/**
+	 * 退款订单号
+	 */
+	private String outRefundNo;
+
+	/**
+	 * 套餐id
+	 */
+	private Long packageId;
+
+
+	private String packageName;
+
+	/**
+	 * 订单总金额
+	 */
+	private BigDecimal totalFee;
+
+	private BigDecimal refundFee;
+
+	private String refundDesc;
+
+	/**
+	 * 退款方式
+	 */
+	private String refundMethod;
+
+
+	private String operator;
+
+	/**
+	 * 聊天记录
+	 */
+	private String chatImg;
+}

+ 138 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/UpgradeOrderDon.java

@@ -0,0 +1,138 @@
+package com.cyksj.model.entity;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.Getter;
+
+import java.math.BigDecimal;
+import java.util.Arrays;
+import java.util.Date;
+
+/**
+ * @author zwhui
+ * @date 2024/4/24 17:08
+ */
+@Data
+public class UpgradeOrderDon extends BaseEntity {
+    /**
+     * 订单编号
+     */
+    private String orderNo;
+
+    /**
+     * 商品id
+     */
+    private Long goodsId;
+
+    /**
+     * 套餐id
+     */
+    private Long packageId;
+
+    /**
+     * 用户id
+     */
+    private Long userId;
+
+    private Long relationId;
+
+    /**
+     * 原订单号
+     */
+    private Long originalOrderId;
+
+    private String transactionId;
+
+    private Integer num;
+
+    private BigDecimal price;
+
+    /**
+     * 金额
+     */
+    private BigDecimal money;
+
+
+    /**
+     * 余额支付
+     */
+    private BigDecimal balance;
+
+    /**
+     * 退款金额
+     */
+    private BigDecimal refundMoney;
+
+    /**
+     * 退款余额
+     */
+    private BigDecimal refundBalance;
+
+    /**
+     * 退款原因
+     */
+    private String refundDesc;
+
+    /**
+     * 支付时间
+     */
+    private Date payTime;
+
+    private Status status;
+
+    private Type type;
+
+    private String shopId;
+    /**
+     * 买家支付id
+     */
+    private String buyerId;
+
+
+    private Boolean isWebPay;
+
+    public enum Status {
+        /**
+         * 订单状态
+         */
+        close("已关闭"),
+        noPayment("待付款"),
+        hasPayment("已付款"),
+        transport("已发货"),
+        received("已签收"),
+        complete("已完成"),
+        refund("已退款"),
+        ;
+
+        private String desc;
+
+        Status(String desc) {
+            this.desc = desc;
+        }
+
+        public String getDesc() {
+            return desc;
+        }
+    }
+
+    @Getter
+    @AllArgsConstructor
+    public enum Type{
+        WeChat("wechat","微信"),
+        ALI_PAY("zfb","支付宝"),
+        EX_CODE("exchange_code","兑换码"),
+        LOTTERY("lottery","抽奖"),
+        EQUIPMENT("equipment", "实物设备分销"),
+        PAY_EQUIPMENT("pay_equipment", "自购设备"),
+        BALANCE("balance", "余额支付"),
+        PAYPAL("paypal","paypal支付"),
+        STRIPE("stripe","stripe支付")
+        ;
+        String type;
+        String desc;
+
+        public static UpgradeOrderDon.Type getType(String type) {
+            return Arrays.stream(UpgradeOrderDon.Type.values()).filter(tp -> tp.getType().equals(type)).findFirst().orElse(ALI_PAY);
+        }
+    }
+}

+ 53 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/UpgradePackage.java

@@ -0,0 +1,53 @@
+package com.cyksj.model.entity;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * @author zwhui
+ * @date 2024/4/24 14:54
+ */
+@Data
+public class UpgradePackage extends BaseEntity{
+
+    /**
+     * 套餐名称
+     */
+    private String name;
+
+    /**
+     * 商品id
+     */
+    private Long goodsId;
+
+    /**
+     * 商品规格id
+     */
+    private Long skuId;
+
+    /**
+     * 套餐价格
+     */
+    private BigDecimal price;
+
+    /**
+     * 数量
+     */
+    private Integer num;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+    /**
+     * true 上架 / false 下架
+     */
+    private Boolean status;
+
+    /**
+     * true 存在 / false 删除
+     */
+    private Boolean deleted;
+}

+ 87 - 0
netflix-dao/src/main/java/com/cyksj/model/excel/ExcelUpgradeOrderDonData.java

@@ -0,0 +1,87 @@
+package com.cyksj.model.excel;
+
+import com.alibaba.excel.annotation.ExcelIgnore;
+import com.alibaba.excel.annotation.ExcelProperty;
+import com.cyksj.model.entity.OrderDon;
+import com.cyksj.model.entity.UpgradeOrderDon;
+import com.ejlchina.searcher.bean.DbField;
+import com.ejlchina.searcher.bean.DbIgnore;
+import com.ejlchina.searcher.bean.SearchBean;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/*
+ *项目名: netflix
+ *文件名: ExcelOrderDonData
+ *创建者: JavaZou
+ *创建时间:2022/12/29 10:25
+ */
+@Getter
+@Setter
+@SearchBean(tables = "upgrade_order_don o left join upgrade_package up on o.package_id = up.id " +
+		"left join user u on o.user_id = u.id "
+)
+public class ExcelUpgradeOrderDonData {
+
+	@DbField("o.id")
+	@ExcelIgnore
+	private Long id;
+
+	@DbField("o.relation_id")
+	@ExcelIgnore
+	private Long relationId;
+
+	@DbField("o.transaction_id")
+	@ExcelIgnore
+	private String transactionId;
+
+	@DbField("o.order_no")
+	@ExcelProperty("订单号")
+	private String orderNo;
+
+	@DbField("up.name")
+	@ExcelProperty("套餐名")
+	private String packageName;
+
+	@DbField("o.money")
+	@ExcelProperty("金额")
+	private BigDecimal money;
+
+	@DbField("u.id")
+	@ExcelProperty("用户ID")
+	private Long userId;
+
+	@DbField("u.nickname")
+	@ExcelProperty("用户名")
+	private String nickname;
+
+	@DbField("case when u.login_phone != '' then 'phone' when u.email != '' then 'email' when u.open_id != '' then 'wechat' else 'wechat' end")
+	@ExcelIgnore
+	private String register;
+
+	@DbField("case when u.login_phone != '' then concat('手机号',login_phone) when u.email != '' then concat('邮箱',email) when u.open_id != '' then '微信授权' else '微信授权' end")
+	@ExcelProperty("注册方式")
+	private String registerStr;
+
+	@DbField("o.status")
+	@ExcelIgnore
+	private UpgradeOrderDon.Status status;
+
+	@ExcelProperty("状态")
+	private String statusStr;
+
+	@DbField("o.created_time")
+	@ExcelProperty("创建时间")
+	private Date createdTime;
+
+	@DbField("o.type")
+	@ExcelIgnore
+	private UpgradeOrderDon.Type type;
+
+	@ExcelProperty("支付方式")
+	private String typeStr;
+
+}

+ 13 - 0
netflix-dao/src/main/java/com/cyksj/model/manage/request/ReqGoodsSkuInsert.java

@@ -1,5 +1,7 @@
 package com.cyksj.model.manage.request;
 
+import com.baomidou.mybatisplus.annotation.FieldStrategy;
+import com.baomidou.mybatisplus.annotation.TableField;
 import com.cyksj.model.entity.GoodsDiscountPlusPurchaseRelation;
 import lombok.Getter;
 import lombok.Setter;
@@ -150,6 +152,17 @@ public class ReqGoodsSkuInsert {
          * 限制时间
          */
         private int gptLimitTime;
+
+
+        /**
+         * mj fast次数
+         */
+        private Integer mjFastNum;
+
+        /**
+         * mj relax次数
+         */
+        private Integer mjRelaxNum;
     }
 
 }

+ 7 - 0
netflix-dao/src/main/java/com/cyksj/model/manage/views/OrderDonBackView.java

@@ -149,6 +149,13 @@ public class OrderDonBackView {
 	@DbField("sku.days")
 	private Integer days;
 
+	/**
+	 * 是否是镜像服务
+	 */
+	@DbField("sku.is_mirror")
+	private Boolean isMirror;
+
+
 	@DbField("o.num")
 	private Integer num;
 

+ 65 - 0
netflix-dao/src/main/java/com/cyksj/model/manage/views/UpgradeOrderDetailView.java

@@ -0,0 +1,65 @@
+package com.cyksj.model.manage.views;
+
+import com.cyksj.model.entity.OrderDon;
+import com.cyksj.model.entity.OrderDonTransport;
+import com.cyksj.model.entity.OrderDonWaybill;
+import com.cyksj.model.entity.UpgradeOrderDon;
+import com.ejlchina.searcher.bean.DbField;
+import com.ejlchina.searcher.bean.DbIgnore;
+import com.ejlchina.searcher.bean.SearchBean;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.math.BigDecimal;
+
+/*
+ *项目名: netflix
+ *文件名: OrderDetail
+ *创建者: JavaZou
+ *创建时间:2023/4/14 15:00
+ */
+@Getter
+@Setter
+@SearchBean(tables = "upgrade_order_don o left join upgrade_package up on o.package_id = up.id " +
+		"left join goods_don g on g.id = o.goods_id ")
+public class UpgradeOrderDetailView {
+	/**
+	 * 订单id
+	 */
+	@DbField("o.id")
+	private Long id;
+
+	/** 订单号 */
+	@DbField("o.order_no")
+	private String orderNo ;
+
+	@DbField("o.status")
+	private UpgradeOrderDon.Status status;
+
+	@DbField("g.id")
+	private Long goodsId;
+
+	@DbField("g.title")
+	private String title;
+
+	/**
+	 * 微信订单号
+	 */
+	@DbField("o.transaction_id")
+	private String transactionId;
+
+	@DbField("o.user_id")
+	private Long userId;
+
+	@DbField("o.money")
+	private BigDecimal money;
+
+	@DbField("o.relation_id")
+	private Long relationId;
+
+	@DbField("o.package_id")
+	private Long packageId;
+
+	@DbField("up.name")
+	private String packageName;
+}

+ 110 - 0
netflix-dao/src/main/java/com/cyksj/model/manage/views/UpgradeOrderDonBackView.java

@@ -0,0 +1,110 @@
+package com.cyksj.model.manage.views;
+
+import com.cyksj.model.entity.*;
+import com.cyksj.model.views.GoodsDonSkuView;
+import com.ejlchina.searcher.bean.DbField;
+import com.ejlchina.searcher.bean.DbIgnore;
+import com.ejlchina.searcher.bean.SearchBean;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.List;
+
+/*
+ *项目名: netflix
+ *文件名: OrderDonBackView
+ *创建者: JavaZou
+ *创建时间:2023/4/26 17:49
+ */
+@Getter
+@Setter
+@SearchBean(tables = "upgrade_order_don o left join upgrade_package up on o.package_id = up.id " +
+		"left join goods_don g on up.goods_id = g.id " +
+		"left join goods_don_sku gds on up.sku_id = gds.id ",
+		where = ":condition:"
+)
+public class UpgradeOrderDonBackView {
+	@DbField("o.id")
+	private Long id;
+
+	@DbField("o.relation_id")
+	private Long relationId;
+
+	@DbField("o.order_no")
+	private String orderNo;
+
+	@DbField("o.transaction_id")
+	private String transactionId;
+
+	@DbField("up.name")
+	private String packageName;
+
+	/**
+	 * 平台id
+	 */
+	@DbField("o.goods_id")
+	private Long goodsId;
+
+	@DbField("g.title")
+	private String goodsTitle;
+
+	@DbField("gds.id")
+	private Long skuId;
+
+	@DbField("gds.spec_val")
+	private String goodsSpecVal;
+
+	@DbField("o.user_id")
+	private Long userId;
+
+	@DbIgnore
+	private String nickname;
+
+	@DbIgnore
+	private String register;
+
+	/**
+	 * 登录注册手机号
+	 */
+	@DbIgnore
+	private String loginPhone;
+
+	/**
+	 * 登录注册邮箱
+	 */
+	@DbIgnore
+	private String email;
+
+	@DbField("o.money")
+	private BigDecimal money;
+
+	@DbField("o.balance")
+	private BigDecimal balance;
+
+	@DbField("o.refund_money")
+	private BigDecimal refundMoney;
+
+	@DbField("o.refund_balance")
+	private BigDecimal refundBalance;
+
+	@DbField("o.status")
+	private UpgradeOrderDon.Status status;
+
+	@DbField("o.type")
+	private UpgradeOrderDon.Type type;
+
+	@DbField("o.created_time")
+	private Date createdTime;
+
+	@DbField("o.pay_time")
+	private Date payTime;
+
+	/**
+	 * 下单手机号
+	 */
+	@DbField("o.phone")
+	private String orderPhone;
+
+}

+ 73 - 0
netflix-dao/src/main/java/com/cyksj/model/manage/views/UpgradeOrderDonSearchView.java

@@ -0,0 +1,73 @@
+package com.cyksj.model.manage.views;
+
+import com.cyksj.model.entity.GoodsDon;
+import com.cyksj.model.entity.OrderDon;
+import com.cyksj.model.entity.OrderDonWaybill;
+import com.cyksj.model.entity.UpgradeOrderDon;
+import com.cyksj.model.views.GoodsDonSkuView;
+import com.ejlchina.searcher.bean.DbField;
+import com.ejlchina.searcher.bean.DbIgnore;
+import com.ejlchina.searcher.bean.SearchBean;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.List;
+
+/*
+ *项目名: netflix
+ *文件名: OrderDonSearchView
+ *创建者: JavaZou
+ *创建时间:2023/7/28 16:21
+ */
+@Getter
+@Setter
+@SearchBean(tables = "(select * from upgrade_order_don :orderId:) o left join upgrade_package up on o.package_id = up.id " +
+		"left join goods_don g on g.id = up.goods_id " +
+		"left join goods_don_sku gs on gs.id = up.sku_id ")
+public class UpgradeOrderDonSearchView {
+	@DbField("o.id")
+	private Long id;
+
+	@DbField("up.name")
+	private String name;
+
+	@DbField("up.price")
+	private BigDecimal price;
+
+	@DbField("up.num")
+	private Integer num;
+
+	@DbField("g.title")
+	private String title;
+
+	@DbField("gs.spec_val")
+	private String specVal;
+
+	@DbField("o.money")
+	private BigDecimal money;
+
+	@DbField("o.balance")
+	private BigDecimal balance;
+
+	@DbField("o.refund_money")
+	private BigDecimal refundMoney;
+
+	@DbField("o.refund_balance")
+	private BigDecimal refundBalance;
+
+	@DbField("o.status")
+	private UpgradeOrderDon.Status status;
+
+	@DbField("o.type")
+	private UpgradeOrderDon.Type type;
+
+	@DbField("o.created_time")
+	private Date createdTime;
+
+	@DbField("o.pay_time")
+	private Date payTime;
+
+}

+ 90 - 0
netflix-dao/src/main/java/com/cyksj/model/manage/views/UpgradeOrderDonView.java

@@ -0,0 +1,90 @@
+package com.cyksj.model.manage.views;
+
+import com.cyksj.model.entity.GoodsDon;
+import com.cyksj.model.entity.OrderDon;
+import com.cyksj.model.entity.OrderDonWaybill;
+import com.cyksj.model.entity.UpgradeOrderDon;
+import com.cyksj.model.views.GoodsDonSkuView;
+import com.ejlchina.searcher.bean.DbField;
+import com.ejlchina.searcher.bean.DbIgnore;
+import com.ejlchina.searcher.bean.SearchBean;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * @author chan
+ * @date 2022/9/27 6:56 PM
+ */
+@Getter
+@Setter
+@SearchBean(tables = "upgrade_order_don o left join upgrade_package up on o.package_id = up.id " +
+        "left join goods_don g on g.id = o.goods_id " +
+        "left join goods_don_sku sku on up.sku_id = sku.id ")
+public class UpgradeOrderDonView {
+
+    @DbField("o.id")
+    private Long id;
+
+    @DbField("o.relation_id")
+    private Long relationId;
+
+    @DbField("o.order_no")
+    private String orderNo;
+
+    @DbField("o.transaction_id")
+    private String transactionId;
+
+    @DbField("g.title")
+    private String title;
+
+    @DbField("sku.spec_val")
+    private String specVal;
+
+    @DbField("g.id")
+    private Long goodsId;
+
+    @DbField("g.logo")
+    private String logo;
+
+    @DbField("up.name")
+    private String name;
+
+    @DbField("up.num")
+    private Integer num;
+
+
+    @DbField("o.user_id")
+    @JsonIgnore
+    private Long userId;
+
+    @DbField("o.money")
+    private BigDecimal money;
+
+    @DbField("o.refund_money")
+    @JsonIgnore
+    private BigDecimal refundMoney;
+
+    @DbField("o.balance")
+    private BigDecimal balance;
+
+    @DbField("o.refund_desc")
+    private String refundDesc;
+
+    @DbField("o.status")
+    private OrderDon.Status status;
+
+    @DbField("o.type")
+    private UpgradeOrderDon.Type type;
+
+    @DbField("o.created_time")
+    private Date createdTime;
+
+    @DbField("o.pay_time")
+    private Date payTime;
+
+}

+ 10 - 0
netflix-dao/src/main/java/com/cyksj/model/request/OrderPayRequest.java

@@ -165,4 +165,14 @@ public class OrderPayRequest {
      * 百亿补贴规格id
      */
     private Long subsidyId;
+
+    /**
+     * 套餐id
+     */
+    private Long packageId;
+
+    /**
+     * 源订单id
+     */
+    private Long originalOrderId;
 }

+ 29 - 0
netflix-dao/src/main/java/com/cyksj/model/response/SubmitResult.java

@@ -0,0 +1,29 @@
+package com.cyksj.model.response;
+
+import lombok.Data;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Data
+public class SubmitResult {
+    /**
+     * 状态码: 1(提交成功), 22(排队中), other(错误)
+     */
+    private int code;
+
+    /**
+     * 描述
+     */
+    private String description;
+
+    /**
+     * 任务ID
+     */
+    private String result;
+
+    /**
+     * 扩展字段
+     */
+    private Map<String, Object> properties = new HashMap<>();
+}

+ 11 - 0
netflix-dao/src/main/java/com/cyksj/model/views/GoodsDonSkuFrontView.java

@@ -140,6 +140,17 @@ public class GoodsDonSkuFrontView {
 	 */
 	private Long gptLimitTime;
 
+
+	/**
+	 * mj fast次数
+	 */
+	private Integer mjFastNum;
+
+	/**
+	 * mj relax次数
+	 */
+	private Integer mjRelaxNum;
+
 	private Boolean isCar;
 
 	private Boolean isMirror;

+ 79 - 0
netflix-dao/src/main/java/com/cyksj/model/views/MidjourneyAccountView.java

@@ -0,0 +1,79 @@
+package com.cyksj.model.views;
+
+import com.cyksj.model.entity.GroupsTrips;
+import com.ejlchina.searcher.bean.DbField;
+import com.ejlchina.searcher.bean.DbIgnore;
+import com.ejlchina.searcher.bean.SearchBean;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * @author zwhui
+ * @date 2024/4/16 16:04
+ */
+@Getter
+@Setter
+@SearchBean(tables = "midjourney_account ma left join account a on ma.account_id = a.id",
+        where = ":extra_sql:")
+public class MidjourneyAccountView {
+    @DbField("ma.id")
+    private Long id;
+
+    @DbField("ma.account_id")
+    private Long accountId;
+
+    @DbField("a.account")
+    private String account;
+
+    @DbField("a.password")
+    private String password;
+
+    @DbField("a.start_time")
+    private Date startTime;
+
+    @DbField("a.expiry_time")
+    private Date expiryTime;
+
+    /**
+     * 状态 true有效 false无效
+     */
+    @DbField("ma.status")
+    private Boolean status;
+
+    /**
+     * 服务器id
+     */
+    @DbField("ma.guild_id")
+    private Long guildId;
+
+    /**
+     * 频道id
+     */
+    @DbField("ma.channel_id")
+    private Long channelId;
+
+    /**
+     * user_token
+     */
+    @DbField("ma.user_token")
+    private String userToken;
+
+    /**
+     * fast剩余时间
+     */
+    @DbField("ma.fast_remain_time")
+    private String fastRemainTime;
+
+    /**
+     * 账号备注
+     */
+    @DbField("ma.remark")
+    private String remark;
+
+    @DbField("ma.created_time")
+    private Date createdTime;
+
+}

+ 40 - 0
netflix-dao/src/main/java/com/cyksj/model/views/MidjourneyUserView.java

@@ -0,0 +1,40 @@
+package com.cyksj.model.views;
+
+import com.ejlchina.searcher.bean.DbField;
+import com.ejlchina.searcher.bean.SearchBean;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * @author zwhui
+ * @date 2024/4/28 17:43
+ */
+@Data
+@SearchBean(tables = "midjourney_user mu left join groups_relation gr on mu.relation_id = gr.id ")
+public class MidjourneyUserView {
+
+
+    @DbField("mu.id")
+    private Long id;
+
+    @DbField("mu.relation_id")
+    private Long relationId;
+
+    @DbField("gr.user_id")
+    private Long userId;
+
+    @DbField("mu.mj_fast_num")
+    private Integer mjFastNum;
+
+    @DbField("mu.mj_relax_num")
+    private Integer mjRelaxNum;
+
+    @DbField("mu.expire_time")
+    private Date expireTime;
+
+
+
+
+
+}

+ 82 - 0
netflix-dao/src/main/java/com/cyksj/model/views/RefundUpdradeOrderDonView.java

@@ -0,0 +1,82 @@
+package com.cyksj.model.views;
+
+import com.ejlchina.searcher.bean.DbField;
+import com.ejlchina.searcher.bean.DbIgnore;
+import com.ejlchina.searcher.bean.SearchBean;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/*
+ *项目名: netflix
+ *文件名: RefundOrderDonView
+ *创建者: JavaZou
+ *创建时间:2023/4/17 15:49
+ */
+@Getter
+@Setter
+@SearchBean(tables = "refund_upgrade_order_don rd left join upgrade_order_don o on o.id = rd.order_id " +
+		"left join upgrade_package up on up.id = o.package_id left join goods_don_sku s on s.id = up.sku_id")
+public class RefundUpdradeOrderDonView {
+	@DbField("rd.id")
+	private Long id;
+
+	@DbField("rd.order_id")
+	private Long orderId;
+
+	@DbField("o.order_no")
+	private String orderNo;
+
+	@DbField("o.money")
+	private BigDecimal orderMoney;
+
+	@DbField("ifnull(o.refund_money,0)")
+	private BigDecimal orderRefundMoney;
+
+	@DbField("rd.user_id")
+	private Long userId;
+
+	@DbField("s.spec_val")
+	private String specVal;
+
+	@DbIgnore
+	private String nickname;
+
+	/**
+	 * 退款单号
+	 */
+	@DbField("rd.out_refund_no")
+	private String outRefundNo;
+
+	/**
+	 * 退款金额
+	 */
+	@DbField("rd.refund_fee")
+	private BigDecimal refundMoney;
+
+	@DbField("rd.package_id")
+	private Long packageId;
+
+	@DbField("rd.package_name")
+	private String packageName;
+
+	/**
+	 * 退款方式
+	 */
+	@DbField("rd.refund_method")
+	private String refundMethod;
+
+	@DbField("rd.operator")
+	private String operator;
+
+	@DbField("rd.refund_desc")
+	private String refundDesc;
+
+	@DbField("rd.chat_img")
+	private String chatImg;
+
+	@DbField("rd.created_time")
+	private Date createTime;
+}

+ 21 - 0
netflix-dao/src/main/java/com/cyksj/model/views/RenewalView.java

@@ -148,6 +148,8 @@ public class RenewalView {
 
     @DbIgnore
     private String orderNo;
+    @DbIgnore
+    private Long orderId;
 
     @DbIgnore
     private String transactionId;
@@ -183,4 +185,23 @@ public class RenewalView {
      */
     @DbField("sku.is_car")
     private Boolean isCar;
+
+
+    /**
+     * mj fast次数
+     */
+    @DbField("sku.mj_fast_num")
+    private Integer mjFastNum;
+
+    /**
+     * mj relax次数
+     */
+    @DbField("sku.mj_relax_num")
+    private Integer mjRelaxNum;
+
+    /**
+     * mj 套餐包最低单价
+     */
+    @DbIgnore
+    private BigDecimal mjPackagePrice;
 }

+ 80 - 0
netflix-dao/src/main/java/com/cyksj/model/views/UpgradePackageView.java

@@ -0,0 +1,80 @@
+package com.cyksj.model.views;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.ejlchina.searcher.bean.DbField;
+import com.ejlchina.searcher.bean.SearchBean;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * @author zwhui
+ * @date 2024/4/24 15:20
+ */
+@Data
+@SearchBean(tables = "upgrade_package up left join goods_don g on up.goods_id = g.id " +
+        "left join goods_don_sku s on up.sku_id = s.id", where = "up.deleted = true")
+public class UpgradePackageView {
+    @DbField("up.id")
+    private Long id;
+
+    /**
+     * 套餐名称
+     */
+    @DbField("up.name")
+    private String name;
+
+    /**
+     * 商品id
+     */
+    @DbField("up.goods_id")
+    private Long goodsId;
+
+    /**
+     * 商品名称
+     */
+    @DbField("g.title")
+    private String goodsName;
+
+    /**
+     * 商品规格id
+     */
+    @DbField("up.sku_id")
+    private Long skuId;
+
+    /**
+     * 商品规格名称
+     */
+    @DbField("s.spec_val")
+    private String specVal;
+
+    /**
+     * 套餐价格
+     */
+    @DbField("up.price")
+    private BigDecimal price;
+
+    /**
+     * 数量
+     */
+    @DbField("up.num")
+    private Integer num;
+
+    /**
+     * 备注
+     */
+    @DbField("up.remark")
+    private String remark;
+
+    /**
+     * true 上架 / false 下架
+     */
+    @DbField("up.status")
+    private Boolean status;
+
+    /**
+     * true 存在 / false 删除
+     */
+    @DbField("up.deleted")
+    private Boolean deleted;
+}

+ 2 - 0
netflix-service/src/main/java/com/cyksj/config/zfb/BaseZfbConfig.java

@@ -44,6 +44,8 @@ public class BaseZfbConfig {
 
 	public static String notifyUrl = "applets/order/prepay/zfb/notify";
 
+	public static String upgradeNotifyUrl = "applets/order/prepay/zfb/upgrade/notify";
+
 	public static String zfb_register_notify_url = "applets/register/prepay/zfb/notify";
 
 	/**

+ 6 - 0
netflix-service/src/main/java/com/cyksj/redis/RedisService.java

@@ -826,6 +826,7 @@ public class RedisService {
         EQUIPMENT_USER_SERVICE_RECORD("equipment_user_service_record:", "用户设备客服获取记录key", 60 * 60 * 24),
         INFORMATION_KEY("information_key:", "文章", 30 * 60L),
         CLOSE_ORDER_TIME_KEY("close_order_time_key:", "关闭订单key", 3 * 60),
+        CLOSE_UPGRADE_ORDER_TIME_KEY("close_upgrade_order_time_key:", "关闭订单key", 3 * 60),
         CHATGPT_CONVERSATION_LIMIT("chatgpt:conversation:limit:", "chatGpt 对话限制", 3 * 60 * 60L),
 
         CHATGPT_CAR_SCORES("chatgpt:car:scores","chatGpt 车队分数", 48 * 60 * 60L),
@@ -833,6 +834,11 @@ public class RedisService {
         CHATGPT_CAR_LOW_CHAT("chatgpt:car:low:chat:","chatGpt 3.5 对话次数", 48 * 60 * 60L),
         //车票到期前每日通知key
         TICKET_EXPIRY_NOTIFY_DAY("ticket_expiry_notify_day:%s:%s", "车票到期前每日通知key", 60 * 60 * 24L),
+        MIDJOURNEY_FAST_LIMIT("midjourney:fast:limit:", "midjourney fast次数", 60 * 60 * 48L),
+        MIDJOURNEY_RELAX_LIMIT("midjourney:relax:limit:", "midjourney relax次数", 60 * 60 * 48L),
+        MIDJOURNEY_EXPIRE_TIME("midjourney:expire:time:", "midjourney expire time", 60 * 60 * 48L),
+        MIDJOURNEY_USER("midjourney:user:", "midjourney user", 60 * 60 * 2L),
+
         ;
 
         private String name;

+ 53 - 13
netflix-service/src/main/java/com/cyksj/service/groups/impl/GroupFunServiceImpl.java

@@ -1,8 +1,11 @@
 package com.cyksj.service.groups.impl;
 
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.cyksj.common.constant.Constant;
+import com.cyksj.mapper.AccountMapper;
 import com.cyksj.mapper.GroupsMapper;
 import com.cyksj.mapper.GroupsRelationMapper;
+import com.cyksj.model.entity.Account;
 import com.cyksj.model.entity.GoodsDonSku;
 import com.cyksj.model.entity.GroupsRelation;
 import com.cyksj.model.entity.GroupsTrips;
@@ -12,6 +15,7 @@ import com.ejlchina.searcher.BeanSearcher;
 import com.ejlchina.searcher.util.MapUtils;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.dao.DuplicateKeyException;
 import org.springframework.stereotype.Service;
 
 /*
@@ -31,6 +35,8 @@ public class GroupFunServiceImpl implements GroupsFuncService {
 
 	private final GroupsRelationMapper groupsRelationMapper;
 
+	private final AccountMapper accountMapper;
+
 	@Override
 	public GroupsRelation createNewGroupsTrips(GoodsDonSku sku) {
 		//新增车次
@@ -62,19 +68,8 @@ public class GroupFunServiceImpl implements GroupsFuncService {
 		if (sku.getId() == 178l || sku.getId() == 423l) {
 			availableParkingNum = 4;
 		}
-		//如果是GPT镜像车队
-		//设置唯一账号
-		if (sku.getIsCar()) {
-			AccountView accountView = beanSearcher.searchFirst(AccountView.class, MapUtils.builder()
-					.field(AccountView::getGoodsId, sku.getGoodsId())
-					.field(AccountView::getAccount, Constant.GPT_CAR_ONLY_ACCOUNT).
-					onlySelect(AccountView::getId).build());
-			if (accountView != null) {
-				groups.setAccountId(accountView.getId());
-				groups.setStatus(GroupsTrips.Status.validity);
-				groups.setRemark("已设置镜像车队唯一账号");
-			}
-		}
+		//如果是镜像车队
+		setMirrorUnifyAccount(sku, groups);
 		groups.setAvailableParkingNum(availableParkingNum);
 
 		groupsMapper.insert(groups);
@@ -94,4 +89,49 @@ public class GroupFunServiceImpl implements GroupsFuncService {
 		}
 		return relation;
 	}
+
+	/**
+	 * 设置镜像统一账号
+	 */
+	public void setMirrorUnifyAccount(GoodsDonSku sku, GroupsTrips groups) {
+		//如果是GPT、MJ镜像车队
+		//设置唯一账号
+		Long goodsId = sku.getGoodsId();
+		Boolean isMirror = sku.getIsMirror();
+		Boolean isCar = sku.getIsCar();
+		String account = null;
+		if ((goodsId == 18 && isCar) || (goodsId == 26 && isMirror)) {
+			if (goodsId == 18) {
+				account = Constant.GPT_CAR_ONLY_ACCOUNT;
+			}
+			if (goodsId == 26) {
+				account = Constant.MJ_MIRROR_ONLY_ACCOUNT;
+			}
+			AccountView accountView = beanSearcher.searchFirst(AccountView.class, MapUtils.builder()
+					.field(AccountView::getGoodsId, goodsId)
+					.field(AccountView::getAccount, account).
+					onlySelect(AccountView::getId).build());
+			Long accountId;
+			if (accountView == null) {
+				try {
+					Account insert = new Account();
+					insert.setAccount(account);
+					insert.setPassword(account);
+					insert.setGoodsId(goodsId);
+					accountMapper.insert(insert);
+					accountId = insert.getId();
+				} catch (DuplicateKeyException e) {
+					accountId = accountMapper.selectOne(Wrappers.lambdaQuery(Account.class)
+							.eq(Account::getGoodsId, goodsId)
+							.eq(Account::getAccount, account)
+							.last("limit 1")).getId();
+				}
+			} else {
+				accountId = accountView.getId();
+			}
+			groups.setAccountId(accountId);
+			groups.setStatus(GroupsTrips.Status.validity);
+			groups.setRemark("已设置镜像车队唯一账号");
+		}
+	}
 }

+ 4 - 0
netflix-service/src/main/java/com/cyksj/service/mange/CmsOrderDonService.java

@@ -35,6 +35,10 @@ public interface CmsOrderDonService extends IService<OrderDon> {
 	 * 订单统一退款
 	 */
 	void unifiedRefund(OrderRefundReq refundReq) throws Exception;
+	/**
+	 * 套餐订单统一退款
+	 */
+	void unifiedUpgradeRefund(OrderRefundReq refundReq) throws Exception;
 
 	/**
 	 * 微信退款回调

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

@@ -177,6 +177,12 @@ public class CmsOrderDonServiceImpl extends ServiceImpl<OrderDonMapper,OrderDon>
 
     private final UserDistributeSubRewardsMapper userDistributeSubRewardsMapper;
 
+    private final UpgradeOrderDonMapper upgradeOrderDonMapper;
+
+    private final UpgradePackageMapper upgradePackageMapper;
+
+    private final MidjourneyUserMapper midjourneyUserMapper;
+
     private static final GlobalThreadPoolTaskExecutor TASK_POOL = GlobalThreadPoolTaskExecutor.getInstance();
 
     @Override
@@ -361,6 +367,121 @@ public class CmsOrderDonServiceImpl extends ServiceImpl<OrderDonMapper,OrderDon>
         groupRelationFrontService.setGroupsTripsParkingTimeEmpty(sku, orderDon.getRelationId());
     }
 
+    @Override
+    @Transactional(rollbackFor = Throwable.class)
+    public void unifiedUpgradeRefund(OrderRefundReq refundReq) throws Exception {
+        UpgradeOrderDon orderDon = upgradeOrderDonMapper.selectById(refundReq.getOrderId());
+        UpgradePackage upgradePackage = upgradePackageMapper.selectById(orderDon.getPackageId());
+        if (StrUtil.isEmpty(refundReq.getRefundType())) {
+            refundReq.setRefundType("money");
+        }
+        String refundType = refundReq.getRefundType();
+        //校验订单是否符合退款
+        checkUpgradeOrderRefund(orderDon, refundReq);
+        //空订单关闭
+        if (orderDon.getMoney().compareTo(BigDecimal.ZERO) == 0 && orderDon.getBalance().compareTo(BigDecimal.ZERO) == 0) {
+            orderDon.setStatus(UpgradeOrderDon.Status.refund);
+            upgradeOrderDonMapper.updateById(orderDon);
+            return;
+        }
+        //调用第三方接口退款
+        String outNo = orderRefundService.centerUpgradeRefund(refundReq, orderDon);
+        orderDon.setRefundDesc(refundReq.getRefundDesc());
+        //记录退款信息
+        commonUpgradeRefund(orderDon, refundReq.getRefundMoney(), refundReq.getOperator(), upgradePackage, outNo, refundType, refundReq.getIsOnly(), refundReq.getRefundBalance(), refundReq.getChatImg());
+        //修改订单状态
+        orderDon.setStatus(UpgradeOrderDon.Status.refund);
+        upgradeOrderDonMapper.updateById(orderDon);
+        //扣除套餐次数
+        if (upgradePackage.getGoodsId() == 26) {
+            MidjourneyUser midjourneyUser = midjourneyUserMapper.selectOne(Wrappers.lambdaQuery(MidjourneyUser.class).eq(MidjourneyUser::getRelationId, orderDon.getRelationId()));
+            if (midjourneyUser != null) {
+                int update = midjourneyUserMapper.update(null, Wrappers.lambdaUpdate(MidjourneyUser.class)
+                        .set(MidjourneyUser::getMjFastNum, midjourneyUser.getMjFastNum() < upgradePackage.getNum() ? 0 : midjourneyUser.getMjFastNum() - upgradePackage.getNum())
+                        .eq(MidjourneyUser::getId, midjourneyUser.getId()));
+                if (update == 0) {
+                    log.info("midjourneyUser 套餐包增值失败 id:{},orderId:{}", midjourneyUser.getId(), orderDon.getId());
+                }
+                Long decr = redisService.decr(RedisService.key.MIDJOURNEY_FAST_LIMIT.getName() + midjourneyUser.getId(), upgradePackage.getNum().longValue());
+                if (decr < 0) {
+                    redisService.set(RedisService.key.MIDJOURNEY_FAST_LIMIT.getName() + midjourneyUser.getId(), 0L);
+                }
+            }
+        }
+    }
+
+    /**
+     * 退款后 处理关联
+     */
+    public void commonUpgradeRefund(UpgradeOrderDon orderDon, BigDecimal refundMoney, String operator, UpgradePackage upgradePackage, String outNo, String refundType, Boolean isOnly, BigDecimal refundBalance, String chatImg) {
+        TASK_POOL.execute(() -> {
+            orderRefundService.refundUpgradeRecord(orderDon, refundMoney,upgradePackage, operator, outNo, refundType, chatImg);
+            //退等比例
+            if (isOnly != null && isOnly) {
+                userBenefitsService.addUserBalance(orderDon.getUserId(), refundBalance, UserBalanceSourceRecord.Source.refund, 0L, orderDon.getId(),  upgradePackage.getName()+ UserBalanceSourceRecord.Source.refund.getDesc(), true);
+                BigDecimal orderDonRefundBalance = Optional.ofNullable(orderDon.getRefundBalance()).orElse(BigDecimal.ZERO);
+                orderDon.setRefundBalance(orderDonRefundBalance.add(refundBalance));
+            }
+
+            //若存在余额支付,退还余额
+            if (isOnly == null && orderDon.getBalance().compareTo(BigDecimal.ZERO) > 0) {
+                userBenefitsService.addUserBalance(orderDon.getUserId(), orderDon.getBalance(), UserBalanceSourceRecord.Source.refund, 0L, orderDon.getId(), upgradePackage.getName() + UserBalanceSourceRecord.Source.refund.getDesc(), true);
+            }
+
+            if (UpgradeOrderDon.Type.BALANCE.getType().equals(refundType)) {
+                if (isOnly == null && orderDon.getMoney().compareTo(BigDecimal.ZERO) > 0) {
+                    userBenefitsService.addUserBalance(orderDon.getUserId(), orderDon.getRefundBalance(), UserBalanceSourceRecord.Source.refund, 0L, orderDon.getId(), upgradePackage.getName() + UserBalanceSourceRecord.Source.refund.getDesc(), true);
+                }
+            }
+        });
+    }
+    /**
+     * 校验订单是否符合退款操作
+     */
+    private void checkUpgradeOrderRefund(UpgradeOrderDon orderDon, OrderRefundReq refundReq) {
+        if (orderDon == null) {
+            throw BusinessRuntimeException.getInstance("订单不存在");
+        }
+        BigDecimal orderDonRefundMoney = Optional.ofNullable(orderDon.getRefundMoney()).orElse(BigDecimal.ZERO);
+        BigDecimal orderDonRefundBalance = Optional.ofNullable(orderDon.getRefundBalance()).orElse(BigDecimal.ZERO);
+        String refundType = refundReq.getRefundType();
+        if (orderDon.getType() == UpgradeOrderDon.Type.BALANCE && !UpgradeOrderDon.Type.BALANCE.getType().equals(refundType)) {
+            throw BusinessRuntimeException.getInstance("余额支付的订单不支持金额退款");
+        }
+        if (UpgradeOrderDon.Status.refund == orderDon.getStatus()) {
+            throw BusinessRuntimeException.getInstance("该订单已退款");
+        }
+        if (UpgradeOrderDon.Status.noPayment == orderDon.getStatus() || UpgradeOrderDon.Status.close == orderDon.getStatus()) {
+            throw BusinessRuntimeException.getInstance("订单{0}状态不支持退款", orderDon.getStatus());
+        }
+
+        BigDecimal refundMoney = refundReq.getRefundMoney();
+        BigDecimal money = orderDon.getMoney();
+        //余额退款
+        if (UpgradeOrderDon.Type.BALANCE.getType().equals(refundType)) {
+            BigDecimal balance = orderDon.getBalance();
+            refundMoney = orderDonRefundBalance.add(refundMoney);
+            if (UpgradeOrderDon.Type.BALANCE == orderDon.getType() && refundMoney.compareTo(balance) > 0) {
+                throw BusinessRuntimeException.getInstance("退款余额大于订单支付余额");
+            }
+            if (UpgradeOrderDon.Type.BALANCE != orderDon.getType() && refundMoney.compareTo(money) > 0) {
+                throw BusinessRuntimeException.getInstance("退款余额大于订单支付金额");
+            }
+            orderDon.setRefundBalance(refundMoney);
+            orderDon.setRefundMoney(BigDecimal.ZERO);
+        } else {
+            //退金额
+            refundMoney = orderDonRefundMoney.add(refundMoney);
+            if (refundMoney.compareTo(money) > 0) {
+                throw BusinessRuntimeException.getInstance("退款金额大于订单支付金额");
+            }
+            orderDon.setRefundMoney(refundMoney);
+        }
+        if (!redisService.setNx(String.format("%s", refundReq.getOrderId()), refundReq.getOrderId(), 10l)) {
+            throw BusinessRuntimeException.getInstance("请勿重复点击,正在退款中..");
+        }
+
+    }
     public String wxRefund(OrderRefundReq refundReq, OrderDon orderDon) throws Exception {
         if (orderDon.getMoney().compareTo(BigDecimal.ZERO) <=0){
             return StrUtil.EMPTY;
@@ -501,7 +622,7 @@ public class CmsOrderDonServiceImpl extends ServiceImpl<OrderDonMapper,OrderDon>
         relationClearService.clearTicket(relation, source);
 
         //是否是GPT镜像规格
-        delGptUser(orderDon.getGoodsId(), orderDon.getRelationId());
+        delMirrorUser(orderDon.getGoodsId(), orderDon.getRelationId());
 
         if (plusPurchaseSkuRelation != null && plus_relationList != null) {
             plus_relationList.remove(relationId);
@@ -850,7 +971,7 @@ public class CmsOrderDonServiceImpl extends ServiceImpl<OrderDonMapper,OrderDon>
                 giftCardOrderUseRecordService.returnGiftCardCash(orderDon.getId());
             }
             //是否是GPT镜像规格
-            delGptUser(orderDon.getGoodsId(), orderDon.getRelationId());
+            delMirrorUser(orderDon.getGoodsId(), orderDon.getRelationId());
             //是否是分销订单
             //处理下级渠道分销提成
             handleSubRewards(orderDon);
@@ -1127,7 +1248,7 @@ public class CmsOrderDonServiceImpl extends ServiceImpl<OrderDonMapper,OrderDon>
     /**
      * 删除gptUser
      */
-    public void delGptUser(Long goodsId, Long relationId) {
+    public void delMirrorUser(Long goodsId, Long relationId) {
         //gpt镜像
         if (goodsId != null && goodsId == 18) {
             Integer selectCount = chatgptUserMapper.selectCount(Wrappers.lambdaQuery(ChatgptUser.class)
@@ -1137,5 +1258,14 @@ public class CmsOrderDonServiceImpl extends ServiceImpl<OrderDonMapper,OrderDon>
                         .eq(ChatgptUser::getRelationId, relationId));
             }
         }
+        //mj镜像
+        if (goodsId != null && goodsId == 26) {
+            Integer selectCount = midjourneyUserMapper.selectCount(Wrappers.lambdaQuery(MidjourneyUser.class)
+                    .eq(MidjourneyUser::getRelationId, relationId));
+            if (selectCount > 0) {
+                midjourneyUserMapper.delete(Wrappers.lambdaQuery(MidjourneyUser.class)
+                        .eq(MidjourneyUser::getRelationId, relationId));
+            }
+        }
     }
 }

+ 11 - 0
netflix-service/src/main/java/com/cyksj/service/mange/upgrade/UpgradePackageService.java

@@ -0,0 +1,11 @@
+package com.cyksj.service.mange.upgrade;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.cyksj.model.entity.UpgradePackage;
+
+/**
+ * @author zwhui
+ * @date 2024/4/24 15:04
+ */
+public interface UpgradePackageService extends IService<UpgradePackage> {
+}

+ 19 - 0
netflix-service/src/main/java/com/cyksj/service/mange/upgrade/impl/UpgradePackageServiceImpl.java

@@ -0,0 +1,19 @@
+package com.cyksj.service.mange.upgrade.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.cyksj.mapper.UpgradePackageMapper;
+import com.cyksj.model.entity.UpgradePackage;
+import com.cyksj.service.mange.upgrade.UpgradePackageService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author zwhui
+ * @date 2024/4/24 15:05
+ */
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class UpgradePackageServiceImpl extends ServiceImpl<UpgradePackageMapper, UpgradePackage> implements UpgradePackageService {
+}

+ 27 - 0
netflix-service/src/main/java/com/cyksj/service/midjourney/MidjourneyAccountService.java

@@ -0,0 +1,27 @@
+package com.cyksj.service.midjourney;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.cyksj.model.entity.Account;
+import com.cyksj.model.entity.MidjourneyAccount;
+import com.cyksj.model.entity.MidjourneyUser;
+
+import java.util.List;
+
+/**
+ * @author zwhui
+ * @date 2024/4/12 15:54
+ */
+public interface MidjourneyAccountService extends IService<MidjourneyAccount> {
+    void addAccount(MidjourneyAccount midjourneyAccount) throws Exception;
+
+    void removeAccount(Long id);
+
+    void updateAccount(MidjourneyAccount midjourneyAccount);
+
+    void updateStatus(Long id);
+
+    void syncAccount(Long id);
+
+    MidjourneyUser getMidjourneyUserToken(Long userId, Long relationId);
+
+}

+ 29 - 0
netflix-service/src/main/java/com/cyksj/service/midjourney/MidjourneyService.java

@@ -0,0 +1,29 @@
+package com.cyksj.service.midjourney;
+
+import com.cyksj.model.dto.BlendDimensions;
+import com.cyksj.model.entity.MidjourneyUser;
+import com.cyksj.model.entity.MidjourneyUserConversation;
+
+import java.util.List;
+
+/**
+ * @author zwhui
+ * @date 2024/4/23 15:44
+ */
+public interface MidjourneyService {
+    MidjourneyUserConversation submitImagine(MidjourneyUser user, String prompt, List<String> base64Array) throws Exception;
+
+    MidjourneyUserConversation submitDescribe(MidjourneyUser user, String base64) throws Exception;
+
+    MidjourneyUserConversation submitBlend(MidjourneyUser user, BlendDimensions dimensions, List<String> base64Array) throws Exception;
+
+    MidjourneyUserConversation submitModal(MidjourneyUser user, Long taskId, String prompt, String maskBase64) throws Exception;
+
+    MidjourneyUserConversation submitShorten(MidjourneyUser user, String prompt) throws Exception;
+
+    List<MidjourneyUserConversation> listConversationByIds(Integer mode, List<Long> ids) throws Exception;
+
+    MidjourneyUserConversation submitAction(MidjourneyUser user, Long taskId, String customId) throws Exception;
+
+    MidjourneyUserConversation cancelConversation(MidjourneyUser user, Long id);
+}

+ 283 - 0
netflix-service/src/main/java/com/cyksj/service/midjourney/impl/MidJourneyAccountServiceImpl.java

@@ -0,0 +1,283 @@
+package com.cyksj.service.midjourney.impl;
+
+import cn.hutool.core.lang.UUID;
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.http.HttpRequest;
+import cn.hutool.http.HttpUtil;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.cyksj.common.exception.BusinessRuntimeException;
+import com.cyksj.common.util.Jsons;
+import com.cyksj.common.util.StringUtil;
+import com.cyksj.mapper.*;
+import com.cyksj.model.entity.*;
+import com.cyksj.redis.RedisService;
+import com.cyksj.service.midjourney.MidjourneyAccountService;
+import com.cyksj.service.user.UserBindRelationService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.imageio.stream.FileImageOutputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author zwhui
+ * @date 2024/4/12 15:54
+ */
+@Service
+@Slf4j
+@RequiredArgsConstructor
+public class MidJourneyAccountServiceImpl extends ServiceImpl<MidjourneyAccountMapper, MidjourneyAccount> implements MidjourneyAccountService {
+
+    private static final String HOST = "http://43.154.230.104:8080";
+
+    private final GroupsRelationMapper groupsRelationMapper;
+
+    private final GroupsMapper groupsMapper;
+
+    private final GoodsDonSkuMapper skuMapper;
+
+    private final UserMapper userMapper;
+
+    private final UserBindRelationService userBindRelationService;
+
+    private final MidjourneyUserMapper midjourneyUserMapper;
+
+    private final RedisService redisService;
+
+    @Override
+    @Transactional(rollbackFor = Throwable.class)
+    public void addAccount(MidjourneyAccount midjourneyAccount) throws Exception {
+        baseMapper.insert(midjourneyAccount);
+        addAcount(midjourneyAccount);
+        //拉取plus服务账号实例id
+        Long instanceId = getAccount(midjourneyAccount.getId());
+        midjourneyAccount.setInstanceId(instanceId);
+        baseMapper.updateById(midjourneyAccount);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Throwable.class)
+    public void removeAccount(Long id) {
+        MidjourneyAccount midjourneyAccount = baseMapper.selectById(id);
+        if (midjourneyAccount == null) {
+            throw BusinessRuntimeException.getInstance("账号不存在");
+        }
+        baseMapper.deleteById(id);
+        //删除plus服务账号信息
+        delAcount(midjourneyAccount.getInstanceId());
+    }
+
+    @Override
+    @Transactional(rollbackFor = Throwable.class)
+    public void updateAccount(MidjourneyAccount midjourneyAccount) {
+        MidjourneyAccount account = baseMapper.selectById(midjourneyAccount.getId());
+        if (account == null) {
+            throw BusinessRuntimeException.getInstance("账号不存在");
+        }
+        baseMapper.updateById(midjourneyAccount);
+        midjourneyAccount.setInstanceId(account.getInstanceId());
+        updAccount(midjourneyAccount);
+    }
+
+    @Override
+    public void updateStatus(Long id) {
+        MidjourneyAccount midjourneyAccount = baseMapper.selectById(id);
+        if (midjourneyAccount == null) {
+            throw BusinessRuntimeException.getInstance("账号不存在");
+        }
+        midjourneyAccount.setStatus(!midjourneyAccount.getStatus());
+        baseMapper.updateById(midjourneyAccount);
+        updAccount(midjourneyAccount);
+    }
+
+    @Override
+    public void syncAccount(Long id) {
+        MidjourneyAccount midjourneyAccount = baseMapper.selectById(id);
+        if (midjourneyAccount == null) {
+            throw BusinessRuntimeException.getInstance("账号不存在");
+        }
+        String fastRemainTime = syncInfo(midjourneyAccount.getInstanceId());
+        midjourneyAccount.setFastRemainTime(fastRemainTime);
+        baseMapper.updateById(midjourneyAccount);
+    }
+
+
+    private void addAcount(MidjourneyAccount midjourneyAccount) throws Exception {
+        Map<String, Object> accountParam = createAccountParam(midjourneyAccount);
+        String body = HttpRequest.post(HOST + "/mj/account/create").body(Jsons.toJson(accountParam)).execute().body();
+        log.info("addAccount body:{}", body);
+        JSONObject jsonObject = JSONUtil.parseObj(body);
+        if (jsonObject.getInt("code") != 1) {
+            throw BusinessRuntimeException.getInstance("addAccount error message:" + jsonObject.getStr("description"));
+        }
+    }
+
+    private Map<String, Object> createAccountParam(MidjourneyAccount midjourneyAccount) {
+        Map<String, Object> param = new HashMap<>();
+        param.put("channelId", midjourneyAccount.getChannelId());
+        param.put("guildId", midjourneyAccount.getGuildId());
+        param.put("userToken", midjourneyAccount.getUserToken());
+        param.put("remark", midjourneyAccount.getId());
+        return param;
+    }
+
+    private void delAcount(Long id) {
+        String body = HttpRequest.delete(HOST + "/mj/account/"+id+"/delete").execute().body();
+        log.info("delAcount body:{}", body);
+        JSONObject jsonObject = JSONUtil.parseObj(body);
+        if (jsonObject.getInt("code") != 1) {
+            throw BusinessRuntimeException.getInstance("delAcount error message:" + jsonObject.getStr("description"));
+        }
+    }
+
+    private Long getAccount(Long id) throws Exception {
+        String body = HttpRequest.post(HOST + "/mj/account/query").body(Jsons.toJson(MapUtil.of("remark", id))).execute().body();
+        log.info("getAccount body:{}", body);
+        JSONObject jsonObject = JSONUtil.parseObj(body);
+        return JSONUtil.parseObj(JSONUtil.parseArray(jsonObject.getStr("content")).get(0)).get("id", Long.class);
+    }
+
+    private void updAccount(MidjourneyAccount midjourneyAccount){
+        Map<String, Object> accountParam = createAccountParam(midjourneyAccount);
+        String body = HttpRequest.put(HOST + "/mj/account/"+midjourneyAccount.getInstanceId()+"/update-reconnect")
+                .body(JSONUtil.toJsonStr(accountParam)).execute().body();
+        log.info("updAccount body:{}", body);
+        JSONObject jsonObject = JSONUtil.parseObj(body);
+        if (jsonObject.getInt("code") != 1) {
+            throw BusinessRuntimeException.getInstance("updAccount error message:" + jsonObject.getStr("description"));
+        }
+    }
+
+    private String syncInfo(Long id){
+        String body = HttpRequest.post(HOST + "/mj/account/"+id+"/sync-info").execute().body();
+        log.info("syncInfo body:{}", body);
+        JSONObject jsonObject = JSONUtil.parseObj(body);
+        if (jsonObject.getInt("code") != 1) {
+            throw BusinessRuntimeException.getInstance("syncInfo error message:" + jsonObject.getStr("description"));
+        }
+        return JSONUtil.parseObj(jsonObject.getStr("result")).getStr("fastTimeRemaining");
+    }
+
+
+    @Override
+    public MidjourneyUser getMidjourneyUserToken(Long userId, Long relationId) {
+        GroupsRelation groupsRelation = getGroupsRelation(userId, relationId);
+        GroupsTrips groupsTrips = getGroupsTrips(groupsRelation);
+        GoodsDonSku goodsDonSku = skuMapper.selectById(groupsTrips.getSkuId());
+
+        if (goodsDonSku != null && goodsDonSku.getIsMirror()) {
+            return getMidjourneyUser(userId, relationId, groupsRelation, goodsDonSku);
+        } else {
+            throw BusinessRuntimeException.getInstance("服务器出了点问题");
+        }
+    }
+
+    /**
+     * 获取车位信息
+     *
+     * @param userId
+     * @param relationId
+     * @return
+     */
+    private GroupsRelation getGroupsRelation(Long userId, Long relationId) {
+        List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
+        GroupsRelation groupsRelation = groupsRelationMapper.selectOne(Wrappers.lambdaQuery(GroupsRelation.class).in(GroupsRelation::getUserId, userIdList).eq(GroupsRelation::getId, relationId));
+        if (groupsRelation == null) {
+            throw BusinessRuntimeException.getInstance("车票不存在");
+        }
+        return groupsRelation;
+    }
+
+    /**
+     * 获取车队信息
+     *
+     * @param groupsRelation
+     * @return
+     */
+    private GroupsTrips getGroupsTrips(GroupsRelation groupsRelation) {
+        GroupsTrips groupsTrips = groupsMapper.selectById(groupsRelation.getGroupsId());
+        if (groupsTrips == null) {
+            throw BusinessRuntimeException.getInstance("车队异常");
+        }
+        return groupsTrips;
+    }
+
+    private MidjourneyUser getMidjourneyUser(Long userId, Long relationId, GroupsRelation groupsRelation, GoodsDonSku goodsDonSku) {
+        MidjourneyUser midjourneyUser = midjourneyUserMapper.selectOne(Wrappers.lambdaQuery(MidjourneyUser.class).eq(MidjourneyUser::getRelationId, relationId));
+        User user = userMapper.selectById(userId);
+        if (midjourneyUser == null) {
+            midjourneyUser = createMidjourneyUser(groupsRelation, user, goodsDonSku);
+        } else {
+            updateMidjourneyUser(groupsRelation, user, midjourneyUser);
+        }
+        return midjourneyUser;
+    }
+
+    /**
+     * 创建MidjourneyUser
+     */
+    private synchronized MidjourneyUser createMidjourneyUser(GroupsRelation groupsRelation, User user, GoodsDonSku goodsDonSku) {
+        MidjourneyUser midjourneyUser = new MidjourneyUser();
+        midjourneyUser.setExpireTime(groupsRelation.getExpiryTime());
+        midjourneyUser.setName(user.getNickname());
+        midjourneyUser.setImg(getWxImg(user.getHeadimgurl(), user));
+        midjourneyUser.setRelationId(groupsRelation.getId());
+        midjourneyUser.setUserToken(UUID.randomUUID().toString());
+        midjourneyUser.setMjFastNum(goodsDonSku.getMjFastNum());
+        midjourneyUser.setMjRelaxNum((goodsDonSku.getMjRelaxNum() == null || goodsDonSku.getMjRelaxNum() == 0) ? null : goodsDonSku.getMjRelaxNum());
+        midjourneyUserMapper.insert(midjourneyUser);
+        redisService.set(RedisService.key.MIDJOURNEY_FAST_LIMIT.getName() + midjourneyUser.getId(), midjourneyUser.getMjFastNum(),RedisService.key.MIDJOURNEY_FAST_LIMIT.getTimeout());
+        redisService.set(RedisService.key.MIDJOURNEY_USER.getName() + midjourneyUser.getUserToken(), midjourneyUser,RedisService.key.MIDJOURNEY_USER.getTimeout());
+        if (midjourneyUser.getMjRelaxNum() != null) {
+            redisService.set(RedisService.key.MIDJOURNEY_RELAX_LIMIT.getName() + midjourneyUser.getId(), midjourneyUser.getMjRelaxNum(),RedisService.key.MIDJOURNEY_RELAX_LIMIT.getTimeout());
+        }
+        return midjourneyUser;
+    }
+
+    private void updateMidjourneyUser(GroupsRelation groupsRelation, User user, MidjourneyUser midjourneyUser) {
+        midjourneyUser.setName(user.getNickname());
+        midjourneyUser.setImg(getWxImg(user.getHeadimgurl(), user));
+        midjourneyUser.setExpireTime(groupsRelation.getExpiryTime());
+        midjourneyUserMapper.updateById(midjourneyUser);
+        redisService.set(RedisService.key.MIDJOURNEY_USER.getName() + midjourneyUser.getUserToken(), midjourneyUser,RedisService.key.MIDJOURNEY_USER.getTimeout());
+    }
+
+    /**
+     * 更换wx头像至oss
+     */
+    private String getWxImg(String headimgurl, User user) {
+        if (headimgurl.contains("thirdwx.qlogo.cn")) {
+            try {
+                return uploadPic(headimgurl, "wxheadimg-" + user.getId());
+            } catch (Exception ex) {
+                log.error("上传微信头像错误!msg:{}", StringUtil.getErrorText(ex));
+                return "./avatars.png";
+            }
+        } else {
+            return user.getHeadimgurl();
+        }
+    }
+
+    private static String uploadPic(String url, String prefix) throws IOException {
+        byte[] body = HttpUtil.downloadBytes(url);
+        Path tempFile = Files.createTempFile(prefix, ".jpeg");
+        try (FileImageOutputStream imageOutput = new FileImageOutputStream(tempFile.toFile())) {
+            imageOutput.write(body, 0, body.length);
+        }
+        Map<String, Object> paramMap = new HashMap<>();
+        paramMap.put("file", tempFile.toFile());
+        JSONObject result = JSONUtil.parseObj(HttpUtil.post("https://files.liuliangbang.vip/pic/ups", paramMap));
+        return result.getJSONObject("value").getJSONArray("saved").getJSONObject(0).getJSONObject("info").getStr("cdnUrl");
+    }
+
+}

+ 302 - 0
netflix-service/src/main/java/com/cyksj/service/midjourney/impl/MidjourneyServiceImpl.java

@@ -0,0 +1,302 @@
+package com.cyksj.service.midjourney.impl;
+
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.http.HttpRequest;
+import cn.hutool.http.HttpUtil;
+import cn.hutool.json.JSONArray;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.cyksj.common.exception.BusinessRuntimeException;
+import com.cyksj.common.task.GlobalThreadPoolTaskExecutor;
+import com.cyksj.common.util.Jsons;
+import com.cyksj.mapper.MidjourneyUserConversationMapper;
+import com.cyksj.model.dto.BlendDimensions;
+import com.cyksj.model.dto.MessageButton;
+import com.cyksj.model.entity.MidjourneyUser;
+import com.cyksj.model.entity.MidjourneyUserConversation;
+import com.cyksj.model.response.SubmitResult;
+import com.cyksj.redis.RedisService;
+import com.cyksj.service.midjourney.MidjourneyService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Service;
+
+import javax.imageio.stream.FileImageOutputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * @author zwhui
+ * @date 2024/4/23 15:44
+ */
+@Service
+@Slf4j
+@RequiredArgsConstructor
+public class MidjourneyServiceImpl implements MidjourneyService {
+    private static final String RELAX_HOST = "http://43.154.230.104:8080";
+    private static final String FAST_HOST = "https://aigc.api4midjourney.com/api";
+    private static final String FAST_TOKEN = "14a6b469-7cb2-4b99-9238-676c7c5cffef";
+
+    private final MidjourneyUserConversationMapper conversationMapper;
+
+    private static final List<String> progress = List.of("100%");
+
+    private static final List<String> status = List.of("MODAL","CANCEL","FAILURE");
+
+    private final RedisService redisService;
+
+    private static final GlobalThreadPoolTaskExecutor TASK_EXECUTOR = GlobalThreadPoolTaskExecutor.getInstance();
+
+
+    @Override
+    public MidjourneyUserConversation submitImagine(MidjourneyUser user, String prompt, List<String> base64Array) throws Exception {
+        Map<String, Object> imagineParam = MapUtil.builder(new HashMap<String,Object>())
+                .put("prompt", prompt).put("state", user.getId()).build();
+        if (CollectionUtil.isNotEmpty(base64Array)) {
+            imagineParam.put("base64Array", base64Array);
+        }
+        SubmitResult result = submit(user.getMode(),"imagine", imagineParam);
+        return saveConversation(user.getId(), user.getMode(),Long.parseLong(result.getResult()),result.getProperties(),"IMAGINE", StringUtils.EMPTY);
+    }
+
+    public MidjourneyUserConversation saveConversation(Long userId,Integer mode,Long taskId,Map<String,Object> properties, String action,String prompt) throws Exception {
+        MidjourneyUserConversation conversation = new MidjourneyUserConversation()
+                .setUserId(userId).setTaskId(taskId).setAction(action).setPrompt(prompt)
+                .setMode(mode).setStartTime(System.currentTimeMillis()).setProgress("0%")
+                .setStatus("IN_PROGRESS").setTaskId(taskId);
+        if (MapUtil.isNotEmpty(properties)) {
+            conversation.setProperties(Jsons.toJson(properties));
+            conversation.setChannelId(properties.get("discordChannelId") == null ? null : Long.parseLong(properties.get("discordChannelId").toString()))
+                    .setInstanceId(properties.get("discordInstanceId") == null ? null : Long.parseLong(properties.get("discordInstanceId").toString()));
+        }
+        conversationMapper.insert(conversation);
+        return conversation;
+    }
+
+
+    @Override
+    public MidjourneyUserConversation submitDescribe(MidjourneyUser user, String base64) throws Exception {
+        Map<String, Object> param = MapUtil.builder(new HashMap<String,Object>()).put("state", user.getId()).put("base64", base64).build();
+        SubmitResult result = submit(user.getMode(),"describe", param);
+        return saveConversation(user.getId(), user.getMode(),Long.parseLong(result.getResult()),result.getProperties(),"DESCRIBE", StringUtils.EMPTY);
+    }
+
+    @Override
+    public MidjourneyUserConversation submitBlend(MidjourneyUser user, BlendDimensions dimensions, List<String> base64Array) throws Exception {
+        Map<String, Object> param = MapUtil.builder(new HashMap<String,Object>())
+                .put("base64Array", base64Array).put("state", user.getId()).build();
+        if (dimensions != null) {
+            param.put("dimensions", dimensions);
+        }
+        SubmitResult result = submit(user.getMode(),"blend", param);
+        return saveConversation(user.getId(), user.getMode(), Long.parseLong(result.getResult()),result.getProperties(),"BLEND", StringUtils.EMPTY);
+    }
+
+    @Override
+    public MidjourneyUserConversation submitModal(MidjourneyUser user, Long taskId, String prompt, String maskBase64) throws Exception {
+        Map<String, Object> param = MapUtil.builder(new HashMap<String,Object>())
+                .put("taskId", taskId).put("state", user.getId()).build();
+        if (StringUtils.isNotBlank(prompt)) {
+            param.put("prompt", prompt);
+        }
+        if (StringUtils.isNotBlank(maskBase64)) {
+            param.put("maskBase64", maskBase64);
+        }
+        SubmitResult result = submit(user.getMode(),"modal", param);
+        return saveConversation(user.getId(),  user.getMode(),Long.parseLong(result.getResult()),result.getProperties(),"MODAL", StringUtils.EMPTY);
+    }
+
+    @Override
+    public MidjourneyUserConversation submitShorten(MidjourneyUser user, String prompt) throws Exception {
+        Map<String, Object> param = MapUtil.builder(new HashMap<String,Object>())
+                .put("prompt", prompt).put("state", user.getId()).build();
+        SubmitResult result = submit(user.getMode(),"shorten", param);
+        return saveConversation(user.getId(), user.getMode(), Long.parseLong(result.getResult()),result.getProperties(),"SHORTEN", StringUtils.EMPTY);
+    }
+
+    /**
+     * 恢复次数
+     */
+    public void recoverUserLimit(Long id,Integer mode,Integer num){
+        if (mode == 1){
+            redisService.incr(RedisService.key.MIDJOURNEY_FAST_LIMIT.getName() + id, 1L);
+        }
+        if (mode == 2){
+            if (num != null) {
+                redisService.incr(RedisService.key.MIDJOURNEY_RELAX_LIMIT.getName() + id, 1L);
+            }
+        }
+    }
+    @Override
+    public MidjourneyUserConversation submitAction(MidjourneyUser user, Long taskId, String customId) throws Exception {
+        MidjourneyUserConversation conversation = conversationMapper.selectOne(Wrappers.lambdaQuery(MidjourneyUserConversation.class).eq(MidjourneyUserConversation::getTaskId, taskId).last("limit 1"));
+        if (conversation == null) {
+            throw BusinessRuntimeException.getInstance("关联任务不存在或已失效");
+        }
+        if (!user.getMode().equals(conversation.getMode())) {
+            throw BusinessRuntimeException.getInstance("当前出图模式与关联任务出图模式不符");
+        }
+        AtomicBoolean flag = new AtomicBoolean(false);
+        if (StringUtils.isNotBlank(conversation.getButtons())){
+            List<MessageButton> messageButtons = Jsons.parseList(conversation.getButtons(), MessageButton.class);
+            messageButtons.forEach(button -> {
+                if (button.getCustomId().equals(customId)) {
+                    log.info("action customId:{}", customId);
+                    button.setStyle(3);
+                    if (button.getLabel().contains("Vary") || button.getCustomId().contains("::pan_")
+                            || button.getEmoji().equals("🔄") || button.getCustomId().contains("PromptAnalyzer:")
+                            || button.getCustomId().contains("PicReader::") || button.getCustomId().contains("::variation::")
+                            || button.getCustomId().contains("::CustomZoom::")) {
+                        flag.set(true);
+                    }
+                }
+            });
+            conversation.setButtons(Jsons.toJson(messageButtons));
+        }
+        conversation.setBookmark(customId.contains("BOOKMARK"));
+        conversationMapper.updateById(conversation);
+        if (conversation.getBookmark()) {
+            return conversation;
+        }
+        Map<String, Object> param = MapUtil.builder(new HashMap<String,Object>())
+                .put("taskId", taskId).put("state", user.getId()).put("customId", customId).build();
+        SubmitResult result = submit(user.getMode(),"action", param);
+        if (flag.get()) {
+            // 以上操作有弹窗确认,恢复次数
+            recoverUserLimit(user.getId(), user.getMode(),user.getMjRelaxNum());
+        }
+        return saveConversation(user.getId(), user.getMode(), Long.parseLong(result.getResult()),result.getProperties(),"ACTION", StringUtils.EMPTY);
+    }
+
+
+    public SubmitResult submit(Integer mode,String action, Map<String, Object> param) throws Exception {
+        if (mode == 1) {
+            param.put("mode", "FAST");
+        }
+        String url = (mode == 1 ? FAST_HOST : RELAX_HOST) + getActionUrl(action);
+        String body = HttpRequest.post(url).body(Jsons.toJson(param)).header("Authorization", FAST_TOKEN).execute().body();
+        log.info("action body:{}", body);
+        SubmitResult submitResult = Jsons.parseObject(body, SubmitResult.class);
+        int code = submitResult.getCode();
+        if (code != 1 && code != 21 && code != 22) {
+            if (code == 3) {
+                throw BusinessRuntimeException.getInstance("账号不存在");
+            }
+            if (code == 24) {
+                throw BusinessRuntimeException.getInstance("prompt包含敏感词");
+            }
+            log.error("action:" + action + " error message:" + submitResult.getDescription());
+            throw BusinessRuntimeException.getInstance("队列已满,请稍后尝试");
+        }
+        return submitResult;
+    }
+    private String getActionUrl(String action) {
+         switch (action) {
+            case "imagine":
+                return "/mj/submit/imagine";
+            case "action":
+                return "/mj/submit/action";
+            case "describe":
+                return "/mj/submit/describe";
+            case "blend":
+                return"/mj/submit/blend";
+            case "modal":
+                return "/mj/submit/modal";
+            case "shorten":
+                return "/mj/submit/shorten";
+            default:  throw new IllegalArgumentException("Unknown action: " + action);
+        }
+    }
+    @Override
+    public List<MidjourneyUserConversation> listConversationByIds(Integer mode, List<Long> ids) throws Exception {
+        List<MidjourneyUserConversation> list = new ArrayList<>();
+        listByIds(mode,ids).forEach(json -> {
+            JSONObject jsons = JSONUtil.parseObj(json);
+            MidjourneyUserConversation conversation = JSONUtil.toBean(jsons, MidjourneyUserConversation.class);
+            conversation.setUserId(jsons.getLong("state"));
+            conversation.setTaskId(jsons.getLong("id"));
+            list.add(conversation);
+            TASK_EXECUTOR.execute(() -> {
+                try {
+                    sync(conversation);
+                } catch (IOException e) {
+                    throw new RuntimeException(e);
+                }
+            });
+        });
+        return list;
+    }
+
+
+    public void sync(MidjourneyUserConversation conversation) throws IOException {
+        if ((StringUtils.isNotBlank(conversation.getProgress()) && progress.contains(conversation.getProgress())) || status.contains(conversation.getStatus())) {
+            log.info("同步任务:{},进度:{}",conversation.getTaskId(),conversation.getProgress());
+            MidjourneyUserConversation dbConversation = conversationMapper.selectOne(Wrappers.lambdaQuery(MidjourneyUserConversation.class).eq(MidjourneyUserConversation::getTaskId, conversation.getTaskId())
+                    .eq(MidjourneyUserConversation::getUserId, conversation.getUserId()).last("limit 1"));
+            if (dbConversation != null) {
+                try {
+                    if (StringUtils.isNotBlank(conversation.getImageUrl())) {
+                        conversation.setImageUrl(uploadPic(conversation.getImageUrl(), "conversation"+dbConversation.getId()));
+                    }
+                } catch (IOException e) {
+                    log.error("上传图片失败",e);
+                }finally {
+                    conversation.setId(dbConversation.getId());
+                    conversationMapper.updateById(conversation);
+                }
+            }
+        }
+    }
+
+
+    private static String uploadPic(String url, String prefix) throws IOException {
+        byte[] body = HttpUtil.downloadBytes(url);
+        Path tempFile = Files.createTempFile(prefix, ".png");
+        try (FileImageOutputStream imageOutput = new FileImageOutputStream(tempFile.toFile())) {
+            imageOutput.write(body, 0, body.length);
+        }
+        Map<String, Object> paramMap = new HashMap<>();
+        paramMap.put("file", tempFile.toFile());
+        JSONObject result = JSONUtil.parseObj(HttpUtil.post("https://files.liuliangbang.vip/pic/ups", paramMap));
+        return result.getJSONObject("value").getJSONArray("saved").getJSONObject(0).getJSONObject("info").getStr("cdnUrl");
+    }
+    public JSONArray listByIds(Integer mode,List<Long> ids) throws Exception {
+        Map<String, Object> param = MapUtil.builder(new HashMap<String, Object>()).put("ids", ids).build();
+        String body = HttpRequest.post((mode == 1 ? FAST_HOST : RELAX_HOST) + "/mj/task/list-by-condition").header("Authorization", FAST_TOKEN).body(Jsons.toJson(param)).execute().body();
+        log.info("listByIds body:{}", body);
+        return JSONUtil.parseArray(body);
+    }
+
+    @Override
+    public MidjourneyUserConversation cancelConversation(MidjourneyUser user, Long id) {
+        MidjourneyUserConversation conversation = conversationMapper.selectOne(Wrappers.lambdaQuery(MidjourneyUserConversation.class).eq(MidjourneyUserConversation::getId, id));
+        if (conversation == null) {
+            throw BusinessRuntimeException.getInstance("会话不存在");
+        }
+        if (!Objects.equals(conversation.getUserId(), user.getId())) {
+            throw BusinessRuntimeException.getInstance("不是您的会话");
+        }
+        conversation.setStatus("CANCEL");
+        conversationMapper.updateById(conversation);
+        TASK_EXECUTOR.execute(() -> {
+            cancel(user.getMode(),conversation.getTaskId());
+        });
+        return conversation;
+    }
+
+    public void cancel(Integer mode,Long taskId) {
+        String body = HttpUtil.post(mode == 1 ? FAST_HOST : RELAX_HOST + "/mj/task/"+taskId+"/cancel",MapUtil.empty());
+        log.info("cancel body:{}", body);
+        JSONObject jsonObject = JSONUtil.parseObj(body);
+        if (jsonObject.getInt("code") != 1) {
+            throw BusinessRuntimeException.getInstance("cancel error message:" + jsonObject.getStr("description"));
+        }
+    }
+
+}

+ 15 - 0
netflix-service/src/main/java/com/cyksj/service/order/OrderDonService.java

@@ -10,8 +10,11 @@ import com.cyksj.model.dto.H5JsPayParams;
 import com.cyksj.model.entity.OrderDetailView;
 import com.cyksj.model.entity.OrderDon;
 import com.cyksj.model.entity.RealGoodsInterestUser;
+import com.cyksj.model.entity.UpgradeOrderDon;
+import com.cyksj.model.manage.views.UpgradeOrderDetailView;
 import com.cyksj.model.request.AlipayOrderDonReq;
 import com.cyksj.model.request.OrderPayRequest;
+import org.springframework.transaction.annotation.Transactional;
 
 import java.util.Date;
 import java.util.List;
@@ -89,5 +92,17 @@ public interface OrderDonService extends IService<OrderDon> {
      */
     void zeroOrderConfirm(Long orderId);
 
+    void zeroUpgradeOrderConfirm(Long orderId);
+
     void orderNotify(OrderDon orderDon);
+
+    UpgradeOrderDon submitUpgrade(OrderPayRequest payRequest);
+
+    AliPayParams aliPayUpgrade(AlipayOrderDonReq re, String productCode, String qrPayMode) throws Exception;
+
+    void aliPayUpgradeNotify(Map<String, String> params) throws Exception;
+
+    void closeUpgradeOrder(Long orderId, Long userId);
+
+    UpgradeOrderDetailView getUpgradeOrderDetail(Long orderId, Long userId);
 }

+ 7 - 3
netflix-service/src/main/java/com/cyksj/service/order/OrderRefundService.java

@@ -1,8 +1,6 @@
 package com.cyksj.service.order;
 
-import com.cyksj.model.entity.GoodsDon;
-import com.cyksj.model.entity.GoodsDonSku;
-import com.cyksj.model.entity.OrderDon;
+import com.cyksj.model.entity.*;
 import com.cyksj.model.request.OrderRefundReq;
 
 import java.math.BigDecimal;
@@ -16,9 +14,15 @@ import java.math.BigDecimal;
 public interface OrderRefundService {
 
 	String centerRefund(OrderRefundReq refundReq, OrderDon orderDon) throws Exception;
+	String centerUpgradeRefund(OrderRefundReq refundReq, UpgradeOrderDon orderDon) throws Exception;
 
 	/**
 	 * 退款记录
 	 */
 	void refundRecord(OrderDon orderDon, BigDecimal refundMoney, String operator, GoodsDon goodsDon, GoodsDonSku sku, String outRefundNo, String refundType, String chatImg);
+	/**
+	 * 套餐包退款记录
+	 */
+	void refundUpgradeRecord(UpgradeOrderDon orderDon, BigDecimal refundMoney, UpgradePackage upgradePackage, String operator, String outRefundNo, String refundType, String chatImg);
+
 }

+ 338 - 0
netflix-service/src/main/java/com/cyksj/service/order/impl/OrderDonServiceImpl.java

@@ -64,6 +64,7 @@ import com.cyksj.model.manage.dto.GiftCardDto;
 import com.cyksj.model.manage.request.ReqGoodsSkuSpec;
 import com.cyksj.model.manage.views.GroupsRelationView;
 import com.cyksj.model.manage.views.OrderDonView;
+import com.cyksj.model.manage.views.UpgradeOrderDetailView;
 import com.cyksj.model.request.*;
 import com.cyksj.model.views.*;
 import com.cyksj.redis.RedisService;
@@ -295,6 +296,12 @@ public class OrderDonServiceImpl extends ServiceImpl<OrderDonMapper, OrderDon> i
 
 	private final WxCorpOps wxCorpOps;
 
+	private final UpgradePackageMapper upgradePackageMapper;
+
+	private final UpgradeOrderDonMapper upgradeOrderDonMapper;
+
+	private final MidjourneyUserMapper midjourneyUserMapper;
+
 	private final List<String> noChekGoodsTemp = List.of("Apple One", "Youtube", "Spotify");
 
 	@Transactional(rollbackFor = Throwable.class)
@@ -1246,6 +1253,27 @@ public class OrderDonServiceImpl extends ServiceImpl<OrderDonMapper, OrderDon> i
 		unifiedHandlerOrderNotify(orderDon, goodsDon, sku);
 	}
 
+	@Override
+	public void zeroUpgradeOrderConfirm(Long orderId) {
+		UpgradeOrderDon orderDon = upgradeOrderDonMapper.selectById(orderId);
+		if (orderDon == null) throw BusinessRuntimeException.getInstance("订单不存在");
+		if (orderDon.getMoney().compareTo(BigDecimal.ZERO) > 0) {
+			throw BusinessRuntimeException.getInstance("订单异常");
+		}
+		if (orderDon.getStatus() != UpgradeOrderDon.Status.noPayment) {
+			throw BusinessRuntimeException.getInstance("订单状态已修改");
+		}
+		UpgradePackage upgradePackage = null;
+		if (orderDon.getPackageId() != null) {
+			upgradePackage = upgradePackageMapper.selectById(orderDon.getPackageId());
+		}
+		if (orderDon.getBalance().compareTo(BigDecimal.ZERO) > 0) {
+			orderDon.setType(UpgradeOrderDon.Type.BALANCE);
+			orderDon.setPayTime(DateTime.now());
+		}
+		unifiedHandlerUpgradeOrderNotify(orderDon,upgradePackage);
+	}
+
 	public void updateCarParkAndSendMsg(GoodsDon goodsDon, GoodsDonSku sku, OrderDon order) {
 		if (goodsDon.getType() == 1) {
 			//修改车位状态
@@ -3131,4 +3159,314 @@ public class OrderDonServiceImpl extends ServiceImpl<OrderDonMapper, OrderDon> i
 			}
 		}
 	}
+
+
+	@Override
+	public UpgradeOrderDon submitUpgrade(OrderPayRequest payRequest) {
+		User user = null;
+		Long userId = payRequest.getUserId();
+		if (userId != null) {
+			int noPayCount = upgradeOrderDonMapper.selectCount(Wrappers.lambdaQuery(UpgradeOrderDon.class).eq(UpgradeOrderDon::getUserId, userId).eq(UpgradeOrderDon::getPackageId, payRequest.getPackageId())
+					.eq(UpgradeOrderDon::getStatus, UpgradeOrderDon.Status.noPayment.toString()));
+			if (noPayCount > 0) {
+				throw new BusinessRuntimeException("您有未支付订单.");
+			}
+		}
+		String orderPhone = payRequest.getPhone();
+		if (StrUtil.isNotBlank(orderPhone)) {
+			user = saveOrGetOrderPhoneUser(payRequest);
+			userId = user.getId();
+			payRequest.setUserId(userId);
+		}
+		if (userId == null) {
+			user = new User();
+			user.setId(0l);
+		} else {
+			user = userMapper.selectById(userId);
+			if (user == null) throw BusinessRuntimeException.getGatewayApiCode(GatewayApiCode.CMS_TOKEN_VALID);
+			if (user.getIsBlack()) throw BusinessRuntimeException.getInstance("系统检测操作异常,请稍后再试");
+		}
+		GroupsRelation groupsRelation = getGroupsRelation(payRequest.getUserId(), payRequest.getRelationId());
+		GroupsTrips groupsTrips = getGroupsTrips(groupsRelation);
+		GoodsDonSku goodsDonSku = goodsDonSkuMapper.selectById(groupsTrips.getSkuId());
+
+		if (goodsDonSku != null && goodsDonSku.getIsMirror()) {
+			UpgradePackage upgradePackage = upgradePackageMapper.selectById(payRequest.getPackageId());
+			if (upgradePackage == null) {
+				throw BusinessRuntimeException.getInstance("套餐包不存在");
+			}
+			if (upgradePackage.getPrice() == null || upgradePackage.getPrice().compareTo(BigDecimal.ZERO) <= 0) {
+				throw BusinessRuntimeException.getInstance("下单商品价格异常");
+			}
+			if (payRequest.getDonMoney().compareTo(upgradePackage.getPrice()) < 0) {
+				log.info("套餐包:{}支付金额:{}异常,调整用户userId:{}支付金额为:{}", upgradePackage.getName(), payRequest.getDonMoney(), user.getId(), upgradePackage.getPrice());
+				payRequest.setDonMoney(upgradePackage.getPrice());
+			}
+			MidjourneyUser midjourneyUser = midjourneyUserMapper.selectOne(Wrappers.lambdaQuery(MidjourneyUser.class).eq(MidjourneyUser::getRelationId, payRequest.getRelationId()));
+			if (midjourneyUser == null){
+				throw BusinessRuntimeException.getInstance("车票未激活Midjourney账号,请先激活");
+			}
+			String donNo = SEQUENCE.nextId().toString();
+			UpgradeOrderDon upgradeOrderDon = new UpgradeOrderDon();
+			upgradeOrderDon.setOrderNo(donNo);
+			upgradeOrderDon.setUserId(payRequest.getUserId());
+			upgradeOrderDon.setOriginalOrderId(payRequest.getOriginalOrderId());
+			upgradeOrderDon.setRelationId(payRequest.getRelationId());
+			upgradeOrderDon.setPackageId(payRequest.getPackageId());
+			upgradeOrderDon.setPrice(upgradePackage.getPrice());
+			upgradeOrderDon.setNum(upgradePackage.getNum());
+			upgradeOrderDon.setGoodsId(goodsDonSku.getGoodsId());
+			upgradeOrderDon.setMoney(payRequest.getDonMoney());
+			upgradeOrderDon.setStatus(UpgradeOrderDon.Status.noPayment);
+			upgradeOrderDon.setType(UpgradeOrderDon.Type.ALI_PAY);
+			upgradeOrderDonMapper.insert(upgradeOrderDon);
+
+			deductUpgradeBalancePayOrderMoney(upgradeOrderDon, payRequest.getBalance(),user );
+
+			if (upgradeOrderDon.getStatus() == UpgradeOrderDon.Status.noPayment) {
+				jobManager.addJob(EXPIRY_TIME, () -> {
+					closeUpgradeOrder(upgradeOrderDon.getId(), null);
+				});
+			}
+			return upgradeOrderDon;
+		}
+		return null;
+	}
+
+	public void deductUpgradeBalancePayOrderMoney(UpgradeOrderDon orderDon, BigDecimal balance, User user) {
+		//存在余额
+		if (balance != null && balance.compareTo(BigDecimal.ZERO) > 0) {
+			BigDecimal money = orderDon.getMoney();
+			//去掉余额 多出的部分
+			BigDecimal subBalance = balance;
+			orderDon.setMoney(money.subtract(subBalance));
+			if (balance.compareTo(money) >= 0) {
+				orderDon.setMoney(BigDecimal.ZERO);
+				orderDon.setBalance(money);
+				subBalance = money;
+				orderDon.setType(UpgradeOrderDon.Type.BALANCE);
+			}
+			BigDecimal userBalance = user.getBalance();
+			//扣除余额 是否成功
+			Integer success = userMapper.deductBalance(user.getId(), subBalance, userBalance);
+			if (success != 1) throw BusinessRuntimeException.getInstance("您的余额不足!");
+			orderDon.setBalance(subBalance);
+			upgradeOrderDonMapper.updateById(orderDon);
+		}
+	}
+
+	/**
+	 * 获取车位信息
+	 *
+	 * @param userId
+	 * @param relationId
+	 * @return
+	 */
+	private GroupsRelation getGroupsRelation(Long userId, Long relationId) {
+		List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
+		GroupsRelation groupsRelation = groupsRelationMapper.selectOne(Wrappers.lambdaQuery(GroupsRelation.class).in(GroupsRelation::getUserId, userIdList).eq(GroupsRelation::getId, relationId));
+		if (groupsRelation == null) {
+			throw BusinessRuntimeException.getInstance("车票不存在");
+		}
+		return groupsRelation;
+	}
+
+	/**
+	 * 获取车队信息
+	 *
+	 * @param groupsRelation
+	 * @return
+	 */
+	private GroupsTrips getGroupsTrips(GroupsRelation groupsRelation) {
+		GroupsTrips groupsTrips = groupsMapper.selectById(groupsRelation.getGroupsId());
+		if (groupsTrips == null) {
+			throw BusinessRuntimeException.getInstance("车队异常");
+		}
+		return groupsTrips;
+	}
+
+	@Override
+	public AliPayParams aliPayUpgrade(AlipayOrderDonReq re, String productCode, String qrPayMode) throws Exception {
+		return aliPayCommonService.unifiedPayUpgrade(re,productCode,qrPayMode);
+	}
+
+
+	@Override
+	@Transactional(rollbackFor = Throwable.class)
+	public void aliPayUpgradeNotify(Map<String, String> params) throws Exception {
+		//调用SDK验证签名
+		String body = Codec.DoBase64.custom().setData(params.get("body")).decodeAsText();
+		OrderAttach orderAttach = Jsons.parseObject(body, OrderAttach.class);
+		Long oderId = orderAttach.getO();
+		UpgradeOrderDon orderDon = upgradeOrderDonMapper.selectById(oderId);
+		if (null == orderDon) {
+			throw BusinessRuntimeException.getInstance("订单不存在.");
+		}
+		String appId = params.get("app_id");
+		boolean signVerified = AlipaySignature.rsaCheckV1(params, shopConfigMapper.getByAppId(appId).getPublicKey(), BaseZfbConfig.charset, BaseZfbConfig.signType);
+		if (!signVerified) {
+			log.error("支付宝回调校验签名错误,自定义入参:{}", params.get("body"));
+			throw BusinessRuntimeException.getInstance("支付宝回调校验签名错误");
+		}
+		String transactionId = params.get("trade_no");
+		String payTimeStr = params.get("gmt_payment");
+		//买家支付宝账号
+		String buyerId = params.get("buyer_id");
+		log.info("支付宝回调信息:{}", Jsons.toJson(params));
+		if (!orderDon.getStatus().equals(UpgradeOrderDon.Status.noPayment) && !orderDon.getStatus().equals(UpgradeOrderDon.Status.close)) {
+			log.info("支付宝已回调,重复修改状态 id:{}", orderDon.getId());
+			return;
+		}
+		UpgradePackage upgradePackage = upgradePackageMapper.selectById(orderDon.getPackageId());
+		Date payTime = DateTime.now();
+		if (StringUtils.isNotBlank(payTimeStr)) {
+			payTime = DateUtil.parse(payTimeStr, "yyyy-MM-dd HH:mm:ss");
+		}
+		// 更改订单状态
+		orderDon.setTransactionId(transactionId);
+		orderDon.setPayTime(payTime);
+		orderDon.setShopId(appId);
+		orderDon.setType(UpgradeOrderDon.Type.ALI_PAY);
+		orderDon.setBuyerId(buyerId);
+		upgradeOrderDonMapper.updateById(orderDon);
+		//该支付账号id 是否被后台拉黑
+		Integer count1 = userPayOrderBlockMapper.selectCount(Wrappers.lambdaQuery(UserPayOrderBlock.class)
+				.eq(UserPayOrderBlock::getBuyerId, buyerId));
+		if (count1 > 0) {
+			log.info("该支付账号id:{} 已被后台拉黑", buyerId);
+			//notifyRefundOrder(orderDon, goodsDon, sku);
+			return;
+		}
+
+		//回调
+		unifiedHandlerUpgradeOrderNotify(orderDon,upgradePackage );
+	}
+
+
+	/**
+	 * 统一订单信息回调
+	 */
+	public void unifiedHandlerUpgradeOrderNotify(UpgradeOrderDon orderDon, UpgradePackage upgradePackage) {
+		orderDon.setStatus(UpgradeOrderDon.Status.complete);
+		int isSuccess = upgradeOrderDonMapper.update(null,Wrappers.lambdaUpdate(UpgradeOrderDon.class)
+				.set(UpgradeOrderDon::getStatus, orderDon.getStatus())
+				.in(UpgradeOrderDon::getStatus, List.of(UpgradeOrderDon.Status.noPayment, UpgradeOrderDon.Status.close))
+				.eq(UpgradeOrderDon::getId, orderDon.getId()));
+		if (isSuccess == 0) {
+			log.info("该订单{}状态已修改", orderDon.getOrderNo());
+			return;
+		}
+
+		//余额使用记录
+		if (orderDon.getBalance().compareTo(BigDecimal.ZERO) > 0) {
+			userBenefitsService.recordBalanceBySource(orderDon.getUserId(), orderDon.getBalance().negate(), UserBalanceSourceRecord.Source.payment, orderDon.getId(), upgradePackage.getName() + UserBalanceSourceRecord.Source.payment.getDesc());
+		}
+
+		if (upgradePackage.getGoodsId() == 26) {
+			MidjourneyUser midjourneyUser = midjourneyUserMapper.selectOne(Wrappers.lambdaQuery(MidjourneyUser.class).eq(MidjourneyUser::getRelationId, orderDon.getRelationId()));
+			if (midjourneyUser != null) {
+				int update = midjourneyUserMapper.update(null, Wrappers.lambdaUpdate(MidjourneyUser.class)
+						.set(MidjourneyUser::getMjFastNum, midjourneyUser.getMjFastNum() + upgradePackage.getNum()).eq(MidjourneyUser::getId, midjourneyUser.getId()));
+				if (update == 0) {
+					log.info("midjourneyUser 套餐包增值失败 id:{},orderId:{}", midjourneyUser.getId(), orderDon.getId());
+				}
+				Object fastNum = redisService.get(RedisService.key.MIDJOURNEY_FAST_LIMIT.getName() + midjourneyUser.getId());
+				if (fastNum == null) {
+					redisService.set(RedisService.key.MIDJOURNEY_FAST_LIMIT.getName() + midjourneyUser.getId(), midjourneyUser.getMjFastNum() + upgradePackage.getNum().longValue(), RedisService.key.MIDJOURNEY_FAST_LIMIT.getTimeout());
+				}else {
+					redisService.incr(RedisService.key.MIDJOURNEY_FAST_LIMIT.getName() + midjourneyUser.getId(), upgradePackage.getNum().longValue());
+				}
+			}
+		}
+	}
+
+
+	@Transactional(rollbackFor = Throwable.class)
+	@Override
+	public void closeUpgradeOrder(Long orderId, Long userId) {
+		String orderCloseKey = RedisService.key.CLOSE_UPGRADE_ORDER_TIME_KEY.getName() + orderId;
+		if (!redisService.setNx(orderCloseKey, orderId, RedisService.key.CLOSE_UPGRADE_ORDER_TIME_KEY.getTimeout())) {
+			throw BusinessRuntimeException.getInstance("订单正在关闭中..");
+		}
+		//关闭订单逻辑
+		UpgradeOrderDon orderDon = upgradeOrderDonMapper.selectById(orderId);
+		if (orderDon == null) {
+			return;
+		}
+		if (userId != null) {
+			List<Long> relationUserIdList = userBindRelationService.getRelationUserIdList(userId, null);
+			if (!relationUserIdList.contains(orderDon.getUserId())) {
+				return;
+			}
+		}
+		log.info("关闭订单 orderId:{}", orderId);
+		if (UpgradeOrderDon.Status.noPayment.equals(orderDon.getStatus())) {
+			//调用微信、支付宝第三方接口查询订单是否支付
+			UpgradeOrderDon.Status status = isHasPaid(orderDon);
+			if (status == UpgradeOrderDon.Status.noPayment) {
+				//查询微信支付是否已支付
+				orderDon.setStatus(UpgradeOrderDon.Status.close);
+				upgradeOrderDonMapper.updateById(orderDon);
+				//返还余额
+				if (orderDon.getBalance().compareTo(BigDecimal.ZERO) > 0) {
+					userBenefitsService.addUserBalance(orderDon.getUserId(), orderDon.getBalance(), null, null, null, null, false);
+				}
+			}
+		}
+	}
+
+
+	/**
+	 * 查询第三放接口订单是否已支付
+	 */
+	public UpgradeOrderDon.Status isHasPaid(UpgradeOrderDon orderDon) {
+		UpgradePackage upgradePackage = upgradePackageMapper.selectById(orderDon.getPackageId());
+		if (orderDon.getType() == UpgradeOrderDon.Type.WeChat) {
+			try {
+				Map<String, String> reMap = weChatService.getWxOrderDetail(orderDon.getShopId(), orderDon.getOrderNo());
+				if (reMap != null) {
+					String orderStatus = reMap.get("trade_state");
+					if ("SUCCESS".equals(orderStatus)) {
+						orderDon.setStatus(UpgradeOrderDon.Status.complete);
+						orderDon.setTransactionId(reMap.get("transaction_id"));
+						return updateUpgradeNoPayment(orderDon, upgradePackage);
+					}
+				}
+			} catch (Exception e) {
+
+			}
+		} else if (orderDon.getType() == UpgradeOrderDon.Type.ALI_PAY) {
+			try {
+				AlipayTradeQueryResponse zfbOrderDetail = getZfbOrderDetail(orderDon.getShopId(), orderDon.getOrderNo());
+				if (zfbOrderDetail != null) {
+					String tradeStatus = zfbOrderDetail.getTradeStatus();
+					if ("TRADE_SUCCESS".equals(tradeStatus)) {
+						orderDon.setStatus(UpgradeOrderDon.Status.complete);
+						orderDon.setTransactionId(zfbOrderDetail.getTradeNo());
+						return updateUpgradeNoPayment(orderDon, upgradePackage);
+					}
+				}
+			} catch (Exception e) {
+
+			}
+		}
+		return orderDon.getStatus();
+	}
+
+	private UpgradeOrderDon.Status updateUpgradeNoPayment(UpgradeOrderDon orderDon,UpgradePackage upgradePackage) {
+		unifiedHandlerUpgradeOrderNotify(orderDon, upgradePackage);
+		return orderDon.getStatus();
+	}
+
+
+	@Override
+	public UpgradeOrderDetailView getUpgradeOrderDetail(Long orderId, Long userId) {
+			MapBuilder builder = MapUtils.builder()
+					.field(OrderDonView::getId, orderId)
+					.orderBy(OrderDonView::getId).desc();
+			if (userId != null) {
+				builder.field(UpgradeOrderDetailView::getUserId, userId);
+			}
+        return beanSearcher.searchFirst(UpgradeOrderDetailView.class, builder.build());
+	}
 }

+ 73 - 4
netflix-service/src/main/java/com/cyksj/service/order/impl/OrderRefundServiceImpl.java

@@ -10,10 +10,7 @@ import com.cyksj.common.exception.BusinessRuntimeException;
 import com.cyksj.common.util.Codec;
 import com.cyksj.config.WeChatConfig;
 import com.cyksj.config.zfb.AliPayClientFactory;
-import com.cyksj.mapper.OrderDonMapper;
-import com.cyksj.mapper.PaypalPayConfigMapper;
-import com.cyksj.mapper.RefundOrderDonMapper;
-import com.cyksj.mapper.StripePayConfigMapper;
+import com.cyksj.mapper.*;
 import com.cyksj.mapper.channel.ShopConfigMapper;
 import com.cyksj.model.entity.*;
 import com.cyksj.model.request.OrderRefundReq;
@@ -64,6 +61,8 @@ public class OrderRefundServiceImpl implements OrderRefundService {
 
 	private final RefundOrderDonMapper refundOrderDonMapper;
 
+	private final RefundUpgradeOrderDonMapper refundUpgradeOrderDonMapper;
+
 	@Override
 	public String centerRefund(OrderRefundReq refundReq, OrderDon orderDon) throws Exception {
 		OrderDon.Type type = orderDon.getType();
@@ -83,6 +82,16 @@ public class OrderRefundServiceImpl implements OrderRefundService {
 		return StrUtil.EMPTY;
 	}
 
+	@Override
+	public String centerUpgradeRefund(OrderRefundReq refundReq, UpgradeOrderDon orderDon) throws Exception {
+		UpgradeOrderDon.Type type = orderDon.getType();
+		String refundType = refundReq.getRefundType();
+		if (type == UpgradeOrderDon.Type.ALI_PAY && !refundType.equals(OrderDon.Type.BALANCE.getType())) {
+			return zfbUpgradeRefund(refundReq, orderDon);
+		}
+		return StrUtil.EMPTY;
+	}
+
 	@Override
 	public void refundRecord(OrderDon orderDon, BigDecimal refundMoney, String operator, GoodsDon goodsDon, GoodsDonSku sku, String outRefundNo, String refundType, String chatImg) {
 		RefundOrderDon refund = new RefundOrderDon();
@@ -109,6 +118,29 @@ public class OrderRefundServiceImpl implements OrderRefundService {
 		refundOrderDonMapper.insert(refund);
 	}
 
+	@Override
+	public void refundUpgradeRecord(UpgradeOrderDon orderDon, BigDecimal refundMoney, UpgradePackage upgradePackage,String operator, String outRefundNo, String refundType, String chatImg) {
+		RefundUpgradeOrderDon refund = new RefundUpgradeOrderDon();
+		refund.setOrderId(orderDon.getId());
+		refund.setOutRefundNo(outRefundNo);
+		refund.setTotalFee(orderDon.getMoney());
+		refund.setRefundDesc(orderDon.getRefundDesc());
+		//退款金额
+		refund.setRefundFee(refundMoney);
+		refund.setUserId(orderDon.getUserId());
+		refund.setPackageId(orderDon.getPackageId());
+		refund.setPackageName(upgradePackage == null ? null : upgradePackage.getName());
+		refund.setRefundMethod(orderDon.getType().name());
+		refund.setOperator(operator);
+		refund.setChatImg(chatImg);
+		//退款方式
+		if (StrUtil.isNotBlank(refundType) && refundType.equals(OrderDon.Type.BALANCE.getType())) {
+			refund.setRefundMethod(refundType);
+			refund.setRefundFee(orderDon.getRefundBalance());
+		}
+		refundUpgradeOrderDonMapper.insert(refund);
+	}
+
 
 	public String wxRefund(OrderRefundReq refundReq, OrderDon orderDon) throws Exception {
 		if (orderDon.getMoney().compareTo(BigDecimal.ZERO) <=0){
@@ -168,6 +200,43 @@ public class OrderRefundServiceImpl implements OrderRefundService {
 	}
 
 
+	public String zfbUpgradeRefund(OrderRefundReq refundReq, UpgradeOrderDon orderDon) throws Exception {
+		if (orderDon.getMoney().compareTo(BigDecimal.ZERO) <=0){
+			return StrUtil.EMPTY;
+		}
+		AlipayTradeRefundResponse response;
+		//支付宝退款
+		AlipayClient alipayClient = AliPayClientFactory.getAlipayClient(Optional.ofNullable(orderDon.getShopId()).orElse(ShopConfig.ZFB_DEFAULT_APP_ID));
+		AlipayTradeRefundRequest request = new AlipayTradeRefundRequest();
+		AlipayTradeRefundModel model = new AlipayTradeRefundModel();
+		//支付宝交易号
+		model.setTradeNo(orderDon.getTransactionId());
+		model.setRefundAmount(refundReq.getRefundMoney().divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_EVEN).toString());
+		model.setQueryOptions(List.of("gmt_refund_pay"));
+		long time = System.currentTimeMillis();
+		//退款订单号
+		String outRefundNo = Codec.DoDigest.custom()
+				.setAlgorithm(Codec.DoDigest.Algorithm.MD5)
+				.setStringData(time + String.valueOf(orderDon.getId()))
+				.toHexString();
+		//退款请求号
+		model.setOutRequestNo(outRefundNo);
+		request.setBizModel(model);
+		String shopId = orderDon.getShopId();
+		ShopConfig shopConfig = shopConfigMapper.selectOne(Wrappers.lambdaQuery(ShopConfig.class).eq(ShopConfig::getAppId, orderDon.getShopId()));
+		if (shopId != null && shopConfig != null && shopConfig.getIsCert()) {
+			response = alipayClient.certificateExecute(request);
+		} else {
+			response = alipayClient.execute(request);
+		}
+		if (!response.isSuccess()) throw BusinessRuntimeException.getInstance("退款失败,e:{0}", response.getSubMsg());
+//			AliPayRefundRep aliPayRefundRep = Jsons.parseObject(response.getBody(), AliPayRefundRep.class);
+//			AliPayRefundRep.AlipayTradeRefundResponse res = aliPayRefundRep.getResponse();
+//			orderDon.setRefundMoney(res.getRefundFee().multiply(BigDecimal.valueOf(100)));
+		return outRefundNo;
+	}
+
+
 	public Boolean paypalRefund(OrderRefundReq refundReq, OrderDon orderDon) throws PayPalRESTException {
 		BigDecimal net = refundReq.getRefundMoney().divide(new BigDecimal(7),2,RoundingMode.HALF_DOWN).subtract(orderDon.getAbroadTransactionFee());;
 		log.info("paypal return order:{},money:{}",orderDon.getId(),net);

+ 1 - 0
netflix-service/src/main/java/com/cyksj/service/pay/AliPayCommonService.java

@@ -20,4 +20,5 @@ public interface AliPayCommonService {
 	AliPayParams unifiedPayRegister(Long orderId, String productCode, String payMode, String returnUrl) throws Exception;
 
 	AliPayParams unifiedPay(AlipayOrderDonReq re, String productCode, String payMode) throws Exception;
+	AliPayParams unifiedPayUpgrade(AlipayOrderDonReq re, String productCode, String payMode) throws Exception;
 }

+ 47 - 3
netflix-service/src/main/java/com/cyksj/service/pay/impl/AliPayCommonServiceImpl.java

@@ -19,9 +19,7 @@ import com.cyksj.common.util.StringUtil;
 import com.cyksj.config.zfb.AliPayClientFactory;
 import com.cyksj.config.zfb.BaseZfbConfig;
 import com.cyksj.config.zfb.ZfbProductCode;
-import com.cyksj.mapper.GoodsDonMapper;
-import com.cyksj.mapper.OrderDonMapper;
-import com.cyksj.mapper.RegisterOrderDonMapper;
+import com.cyksj.mapper.*;
 import com.cyksj.mapper.channel.ShopConfigMapper;
 import com.cyksj.mapper.order.OrderDonPostMapper;
 import com.cyksj.model.dto.AliPayParams;
@@ -64,6 +62,10 @@ public class AliPayCommonServiceImpl implements AliPayCommonService {
 
 	private final OrderDonPostMapper orderDonPostMapper;
 
+	private final UpgradeOrderDonMapper	upgradeOrderDonMapper;
+
+	private final UpgradePackageMapper upgradePackageMapper;
+
 	@Override
 	public AliPayParams unifiedPayRegister(Long orderId, String productCode, String payMode, String returnUrl) throws Exception {
 		RegisterOrderDon orderDon = registerOrderDonMapper.selectById(orderId);
@@ -252,4 +254,46 @@ public class AliPayCommonServiceImpl implements AliPayCommonService {
 		}
 		return null;
 	}
+
+
+	@Override
+	public AliPayParams unifiedPayUpgrade(AlipayOrderDonReq re, String productCode, String payMode) throws Exception {
+		Long orderId = re.getOrderId();
+		String returnUrl = re.getReturnUrl();
+		UpgradeOrderDon orderDon = upgradeOrderDonMapper.selectById(orderId);
+		//获取支付宝默认商户
+		String appId = sysConfigService.getDefaultShopConfigByType("zfb");
+		if (UpgradeOrderDon.Type.ALI_PAY != orderDon.getType()) {
+			orderDon.setType(UpgradeOrderDon.Type.ALI_PAY);
+		}
+		orderDon.setShopId(appId);
+		if (ZfbProductCode.PC_WEB.getProductCode().equals(productCode)) {
+			orderDon.setIsWebPay(true);
+		}
+		if (ZfbProductCode.MOBILE_WEB.getProductCode().equals(productCode)) {
+			orderDon.setIsWebPay(false);
+		}
+		upgradeOrderDonMapper.updateById(orderDon);
+		UpgradePackage upgradePackage = upgradePackageMapper.selectById(orderDon.getPackageId());
+		String subject = upgradePackage.getName();
+		//回调地址
+		String host = envCommonService.getHost();
+		String notifyUrl = String.format("%s%s", host, BaseZfbConfig.upgradeNotifyUrl);
+		//是否在微信内支付 微信内支付宝支付后 支付宝回调页面不跳转
+		Boolean isWx = Optional.ofNullable(re.getIsWx()).orElse(false);
+		if (ZfbProductCode.MOBILE_WEB.getProductCode().equals(productCode) && !isWx) {
+			//默认跳转页
+			returnUrl = String.format("%s%s", envCommonService.getDomain(), BaseZfbConfig.DEFAULT_URL);
+		}
+        return builderPayParams(orderDon.getId(),
+				orderDon.getOrderNo(),
+				orderDon.getShopId(),
+				orderDon.getMoney(),
+				orderDon.getCreatedTime(),
+				subject,
+				productCode,
+				payMode,
+				notifyUrl,
+				returnUrl);
+	}
 }

+ 13 - 0
netflix-service/src/main/java/com/cyksj/service/relation/impl/GroupRelationFrontServiceImpl.java

@@ -1,5 +1,6 @@
 package com.cyksj.service.relation.impl;
 
+import cn.hutool.core.collection.CollectionUtil;
 import cn.hutool.core.date.BetweenFormater;
 import cn.hutool.core.date.DateTime;
 import cn.hutool.core.date.DateUtil;
@@ -43,6 +44,8 @@ import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
 import javax.servlet.http.HttpServletRequest;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
 import java.util.*;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.stream.Collectors;
@@ -106,6 +109,9 @@ public class GroupRelationFrontServiceImpl implements GroupRelationFrontService
 
 	//脆球邮箱后缀
 	private final static String CUI_QIU_EMAIL_SUFFIX = "@kindvd.com";
+	private final UpgradePackageMapper upgradePackageMapper;
+
+	private final static String VERIFY_CODE_SECRET = "q1w2e3";
 
 	@Override
 	public SearchResult<RenewalView> getMyTicket(long userId, Boolean isLoginPopularize, String customId, String account) {
@@ -191,6 +197,7 @@ public class GroupRelationFrontServiceImpl implements GroupRelationFrontService
 					.last("limit 1"));
 			if (orderDon != null) {
 				ticket.setOrderNo(orderDon.getOrderNo());
+				ticket.setOrderId(orderDon.getId());
 				ticket.setTransactionId(orderDon.getTransactionId());
 			}
 
@@ -223,6 +230,12 @@ public class GroupRelationFrontServiceImpl implements GroupRelationFrontService
 				}
 				ticket.setPayTime(payTime);
 			}
+			if (ticket.getGoodsId() == 26 && ticket.getIsMirror()){
+				List<UpgradePackage> upgradePackages = upgradePackageMapper.selectList(Wrappers.lambdaQuery(UpgradePackage.class).eq(UpgradePackage::getSkuId, ticket.getSkuId()).eq(UpgradePackage::getStatus, Boolean.TRUE));
+				if (CollectionUtil.isNotEmpty(upgradePackages)){
+					upgradePackages.stream().map(e -> e.getPrice().divide(BigDecimal.valueOf(e.getNum()), 2, RoundingMode.HALF_UP)).min(BigDecimal::compareTo).ifPresent(ticket::setMjPackagePrice);
+				}
+			}
 			List<GroupsRelationUserView> relationUserViews = groupsRelationMapper.getUserRelationUserViews(ticket.getGroupId(), ticket.getRelationId(), ticket.getUserId(), List.of(GroupsRelation.Status.validity, GroupsRelation.Status.outside));
 			ticket.setRelationViews(relationUserViews);
 			if (ticket.getExpiryTime() != null) {

+ 13 - 0
netflix-web/src/main/java/com/cyksj/web/controller/goods/GoodsDonController.java

@@ -1247,4 +1247,17 @@ public class GoodsDonController {
 		Map map = Jsons.parseObject(o, Map.class);
 		return GatewayResponse.SUCCESS.newBuilder().toResult(map);
 	}
+
+	/**
+	 * 获取套餐列表
+	 */
+	@GetMapping("/get/upgradePackage")
+	public Result<SearchResult<UpgradePackageView>> list() {
+		log.info("获取套餐列表");
+		Map<String, Object> build = MapUtils.flatBuilder(request.getParameterMap()).build();
+		SearchResult<UpgradePackageView> search = beanSearcher.search(UpgradePackageView.class, build);
+		return GatewayResponse.SUCCESS.newBuilder().toResult(search);
+	}
+
+
 }

+ 127 - 4
netflix-web/src/main/java/com/cyksj/web/controller/manage/CmsOrderController.java

@@ -3,6 +3,7 @@ package com.cyksj.web.controller.manage;
 import cn.dev33.satoken.annotation.SaCheckPermission;
 import cn.dev33.satoken.stp.StpUtil;
 import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.collection.CollectionUtil;
 import cn.hutool.core.date.DateTime;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.StrUtil;
@@ -28,16 +29,15 @@ import com.cyksj.mapper.manage.cms.CmsUserMapper;
 import com.cyksj.mapper.manage.coupon.CouponMapper;
 import com.cyksj.mapper.shop.YhShopMapper;
 import com.cyksj.model.entity.*;
-import com.cyksj.model.excel.ExcelOrderDonData;
-import com.cyksj.model.excel.ExcelRealGoodsOrderData;
-import com.cyksj.model.excel.ExcelRealGoodsUserInterestData;
-import com.cyksj.model.excel.ExcelRealGoodsUserInterestHasPaymentData;
+import com.cyksj.model.excel.*;
 import com.cyksj.model.manage.request.ReqSetLogisticsPost;
 import com.cyksj.model.manage.views.AccountView;
 import com.cyksj.model.manage.views.OrderDonBackView;
+import com.cyksj.model.manage.views.UpgradeOrderDonBackView;
 import com.cyksj.model.request.*;
 import com.cyksj.model.views.*;
 import com.cyksj.service.mange.CmsOrderDonService;
+import com.cyksj.service.midjourney.MidjourneyAccountService;
 import com.cyksj.service.order.OrderDonCommentService;
 import com.cyksj.service.user.UserService;
 import com.cyksj.web.util.EasyExcelUtils;
@@ -812,4 +812,127 @@ public class CmsOrderController {
 		}
 		return GatewayResponse.SUCCESS.newBuilder().toResult();
 	}
+
+
+
+	/**
+	 * 查询套餐订单列表
+	 */
+
+	@GetMapping("/upgrade/get")
+	@SaCheckPermission("upgradeOrder:view")
+	public Result<SearchResult<UpgradeOrderDonBackView>> getUpgradeOrder(String account, String showId, Long acId, String nickname, String register, Boolean isGoogle) {
+		MapBuilder builder = MapUtils.flatBuilder(request.getParameterMap());
+
+		if (StrUtil.isNotBlank(account)) {
+			GroupsRelation relation = relationMapper.selectOne(Wrappers.lambdaQuery(GroupsRelation.class)
+					.eq(GroupsRelation::getAccount, account).last("limit 1"));
+			if (relation != null) {
+				builder.field(OrderDonBackView::getRelationId, relation.getId());
+			} else {
+				builder.field(OrderDonBackView::getRelationId).op(Operator.IsNull);
+			}
+		}
+		if (StrUtil.isNotBlank(showId)) {
+			builder.field(OrderDonBackView::getUserId, userService.getUserIdByShowId(showId));
+		}
+		String conditionSql = StrUtil.EMPTY;
+		if (acId != null) {
+			conditionSql += String.format(" o.user_id in (select user_id from corp_user where ac_id = %s)", acId);
+		}
+		if (!ObjectUtil.isAllEmpty(nickname, register, isGoogle)) {
+			if (StrUtil.isNotEmpty(conditionSql)) {
+				conditionSql += " and ";
+			}
+			conditionSql += " exists (select u.id from user u where u.id = o.user_id";
+			if (StrUtil.isNotEmpty(nickname)) {
+				conditionSql += String.format(" and u.nickname like '%s%%'", nickname);
+			}
+			if (StrUtil.isNotEmpty(register)) {
+				if ("phone".equals(register)) {
+					conditionSql += " and u.login_phone != ''";
+				} else if ("email".equals(register)) {
+					conditionSql += " and u.email != ''";
+				} else if ("wechat".equals(register)) {
+					conditionSql += " and u.unionid != ''";
+				}
+			}
+			if (isGoogle != null && isGoogle) {
+				conditionSql += String.format(" and u.is_google is %s", isGoogle);
+			}
+			conditionSql += " limit 1)";
+		}
+		if (StrUtil.isNotBlank(conditionSql)) {
+			builder.put("condition", conditionSql);
+		}
+		SearchResult<UpgradeOrderDonBackView> search = beanSearcher.search(UpgradeOrderDonBackView.class, builder.build());
+		search.getDataList().forEach(data -> {
+			User user = userMapper.selectById(data.getUserId());
+			if (user != null) {
+				data.setNickname(user.getNickname());
+				data.setLoginPhone(user.getLoginPhone());
+				data.setEmail(user.getEmail());
+				data.setRegister(StrUtil.isNotEmpty(user.getUnionid()) ? "wechat" : StrUtil.isNotEmpty(user.getLoginPhone()) ? "phone" : "email");
+			}
+		});
+		return GatewayResponse.SUCCESS.newBuilder().toResult(search);
+	}
+
+	/**
+	 * 套餐退款订单列表
+	 */
+	@GetMapping("/get/upgrade/refund/list")
+	public Result<SearchResult<RefundUpdradeOrderDonView>> getRefundUpgradeOrderDonView() {
+		SearchResult<RefundUpdradeOrderDonView> search = beanSearcher.search(RefundUpdradeOrderDonView.class, MapUtils.flatBuilder(request.getParameterMap())
+				.build());
+		search.getDataList().forEach(data -> {
+			Optional.ofNullable(userService.getById(data.getUserId()))
+					.ifPresent(user -> data.setNickname(user.getNickname()));
+		});
+		return GatewayResponse.SUCCESS.newBuilder().toResult(search);
+	}
+
+	/**
+	 * 套餐订单导出
+	 */
+	@GetMapping("/upgrade/export")
+	@SaCheckPermission("upgradeOrder:export")
+	@Log(module = "套餐订单管理/导出订单", businessType = BusinessType.EXPORT, isSaveRequestData = true)
+	public void exportUpgradeOrders(HttpServletResponse response) {
+		List<ExcelUpgradeOrderDonData> list = beanSearcher.searchAll(ExcelUpgradeOrderDonData.class, MapUtils.flatBuilder(request.getParameterMap()).build());
+		list.forEach(data -> {
+			data.setMoney(data.getMoney().divide(BigDecimal.valueOf(100)));
+			UpgradeOrderDon.Type type = data.getType();
+			data.setTypeStr(type != null ? type.getDesc() : "");
+			UpgradeOrderDon.Status status = data.getStatus();
+			data.setStatusStr(status != null ? status.getDesc() : "");
+		});
+		EasyExcelUtils.createExcelStreamMutilByEasyExcel(response, ExcelUpgradeOrderDonData.class, list, "订单列表", "订单列表", ExcelTypeEnum.XLSX, null);
+	}
+
+	/**
+	 * 套餐订单统一退款
+	 */
+	@PostMapping("/unified/upgrade/refund")
+	@Log(module = "套餐订单管理/统一退款", businessType = BusinessType.PUT, isSaveRequestData = true)
+	public Result<String> unifiedUpgradeRefund(@RequestBody @Validated OrderRefundReq refundReq) throws Exception {
+		long adminId = StpUtil.getLoginIdAsLong();
+		CmsUser cmsUser = cmsUserMapper.selectById(adminId);
+		if (cmsUser == null) {
+			throw BusinessRuntimeException.getGatewayApiCode(GatewayApiCode.CMS_TOKEN_VALID);
+		}
+		refundReq.setOperator(cmsUser.getNickname());
+		cmsOrderDonService.unifiedUpgradeRefund(refundReq);
+		return GatewayResponse.SUCCESS.newBuilder().toResult("退款成功");
+	}
+
+	/**
+	 * midjourney查询订单用户账号剩余次数
+	 */
+	@GetMapping("/get/midjourney/user")
+	public Result<SearchResult<MidjourneyUserView>> getMidjourneyUser(Long userId,Long relationId) {
+		SearchResult<MidjourneyUserView> search = beanSearcher.search(MidjourneyUserView.class, MapUtils.flatBuilder(request.getParameterMap())
+				.field(MidjourneyUserView::getUserId,userId).field(MidjourneyUserView::getRelationId,relationId).build());
+		return GatewayResponse.SUCCESS.newBuilder().toResult(search);
+	}
 }

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

@@ -427,7 +427,7 @@ public class CmsAccountController {
 			    availableParkingNum = 4;
 		    }
 			GoodsDonSku goodsDonSku = goodsDonSkuMapper.selectById(account.getSkuId());
-			if (goodsDonSku.getIsMirror()) {
+			if (goodsDonSku.getIsMirror() && goodsDonSku.getGoodsId() == 18l) {
 				String gptRefreshToken = chatGptAccountService. addAccount(account);
 				accountService.update(null, Wrappers.lambdaUpdate(Account.class).eq(Account::getId, account.getId()).set(Account::getGptRefreshToken,gptRefreshToken ));
 			}

+ 3 - 0
netflix-web/src/main/java/com/cyksj/web/controller/manage/goods/CmsGoodsDonController.java

@@ -16,6 +16,7 @@ import com.cyksj.model.entity.GoodsDonSku;
 import com.cyksj.model.manage.request.ReqGoodsSpuInsert;
 import com.cyksj.model.manage.views.GoodsDonViewer;
 import com.cyksj.model.views.GoodsDonCategoryDetailView;
+import com.cyksj.model.views.UpgradePackageView;
 import com.cyksj.service.mange.CmsGoodsDonService;
 import com.cyksj.service.mange.GoodsDonCategoryService;
 import com.cyksj.service.mange.RefreshCacheService;
@@ -32,6 +33,7 @@ import org.springframework.web.bind.annotation.*;
 import javax.servlet.http.HttpServletRequest;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
 
@@ -223,4 +225,5 @@ public class CmsGoodsDonController {
         goodsDon.setCategoryList(categoryView.stream().map(GoodsDonCategory::getId).collect(Collectors.toList()));
         return GatewayResponse.SUCCESS.newBuilder().toResult(goodsDon);
     }
+
 }

+ 3 - 0
netflix-web/src/main/java/com/cyksj/web/controller/manage/goods/CmsGoodsDonSkuController.java

@@ -124,6 +124,9 @@ public class CmsGoodsDonSkuController {
         if (sku.getDays() == null) {
             sku.setDays(null);
         }
+        if (sku.getMjRelaxNum() == null) {
+            sku.setMjRelaxNum(null);
+        }
         String specificGoodsIds = sku.getSpecificGoodsIds();
         if (StrUtil.isEmpty(specificGoodsIds)) {
             sku.setSpecificPrice(BigDecimal.ZERO);

+ 106 - 0
netflix-web/src/main/java/com/cyksj/web/controller/manage/mj/MidJourneyController.java

@@ -0,0 +1,106 @@
+package com.cyksj.web.controller.manage.mj;
+
+import cn.hutool.core.util.StrUtil;
+import com.cyksj.dto.Result;
+import com.cyksj.enums.GatewayResponse;
+import com.cyksj.model.entity.MidjourneyAccount;
+import com.cyksj.model.manage.views.AccountView;
+import com.cyksj.model.views.MidjourneyAccountView;
+import com.cyksj.service.midjourney.MidjourneyAccountService;
+import com.ejlchina.searcher.BeanSearcher;
+import com.ejlchina.searcher.SearchResult;
+import com.ejlchina.searcher.param.Operator;
+import com.ejlchina.searcher.util.MapBuilder;
+import com.ejlchina.searcher.util.MapUtils;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * @author zwhui
+ * @date 2024/4/15 14:38
+ */
+@Slf4j
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/manage/mj")
+public class MidJourneyController {
+
+    private final BeanSearcher beanSearcher;
+
+    private final HttpServletRequest request;
+
+    private final MidjourneyAccountService midjourneyAccountService;
+
+
+    /**
+     * 移入MJ账号池
+     */
+    @PostMapping("/addAccount")
+    public Result<Void> addAccount(@RequestBody MidjourneyAccount midjourneyAccount) throws Exception {
+        log.info("移入MJ账号池 midjourneyAccount:{}", midjourneyAccount);
+        midjourneyAccountService.addAccount(midjourneyAccount);
+        return GatewayResponse.SUCCESS.newBuilder().toResult();
+    }
+
+    /**
+     * 移除MJ账号池
+     */
+    @DeleteMapping("/removeAccount/{id}")
+    public Result<Void> removeAccount(@PathVariable Long id) {
+        log.info("移除MJ账号池 id:{}", id);
+        midjourneyAccountService.removeAccount(id);
+        return GatewayResponse.SUCCESS.newBuilder().toResult();
+    }
+
+    /**
+     * 修改MJ账号池
+     */
+    @PutMapping("/updateAccount")
+    public Result<Void> updateAccount(@RequestBody MidjourneyAccount midjourneyAccount) {
+        log.info("修改MJ账号池 midjourneyAccount:{}", midjourneyAccount);
+        midjourneyAccountService.updateAccount(midjourneyAccount);
+        return GatewayResponse.SUCCESS.newBuilder().toResult();
+    }
+
+    /**
+     * 上下架MJ账号
+     */
+    @PutMapping("/updateStatus/{id}")
+    public Result<Void> updateStatus(@PathVariable Long id) {
+        log.info("上下架MJ账号 id:{}", id);
+        midjourneyAccountService.updateStatus(id);
+        return GatewayResponse.SUCCESS.newBuilder().toResult();
+    }
+
+    /**
+     * 同步账号信息
+     */
+    @PutMapping("/syncAccount/{id}")
+    public Result<Void> syncAccount(@PathVariable Long id) {
+        log.info("同步账号信息 id:{}", id);
+        midjourneyAccountService.syncAccount(id);
+        return GatewayResponse.SUCCESS.newBuilder().toResult();
+    }
+
+
+    /**
+     * 查询账号池列表
+     */
+    @GetMapping("/getAccount")
+    public Result<SearchResult<MidjourneyAccountView>> getAccount(String expiryStartTime, String expiryEndTime) {
+        log.info("查询账号池列表");
+        MapBuilder mapBuilder = MapUtils.flatBuilder(request.getParameterMap());
+        if (StrUtil.isNotEmpty(expiryStartTime) || StrUtil.isNotEmpty(expiryEndTime)) {
+            mapBuilder.field(MidjourneyAccountView::getExpiryTime, expiryStartTime, expiryEndTime)
+                    .op(Operator.Between);
+        }
+        SearchResult<MidjourneyAccountView> search = beanSearcher.search(MidjourneyAccountView.class, mapBuilder.build());
+        return GatewayResponse.SUCCESS.newBuilder().toResult(search);
+    }
+
+
+
+}

+ 121 - 0
netflix-web/src/main/java/com/cyksj/web/controller/manage/upgrade/UpgradePackageController.java

@@ -0,0 +1,121 @@
+package com.cyksj.web.controller.manage.upgrade;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.hutool.core.map.MapUtil;
+import com.cyksj.common.annotation.Log;
+import com.cyksj.common.exception.BusinessRuntimeException;
+import com.cyksj.dto.Result;
+import com.cyksj.enums.BusinessType;
+import com.cyksj.enums.GatewayResponse;
+import com.cyksj.model.entity.UpgradePackage;
+import com.cyksj.model.views.UpgradePackageView;
+import com.cyksj.service.mange.upgrade.UpgradePackageService;
+import com.ejlchina.searcher.BeanSearcher;
+import com.ejlchina.searcher.SearchResult;
+import com.ejlchina.searcher.util.MapBuilder;
+import com.ejlchina.searcher.util.MapUtils;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.Map;
+
+
+/**
+ * @author zwhui
+ * @date 2024/4/24 15:06
+ */
+@Slf4j
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/manage/upgrade")
+public class UpgradePackageController {
+
+    private final UpgradePackageService upgradePackageService;
+
+    private final HttpServletRequest request;
+
+    private final BeanSearcher beanSearcher;
+
+    /**
+     * 新增套餐
+     */
+    @PostMapping
+    @SaCheckPermission("upgrade:add")
+    @Log(module = "套餐管理/新增套餐", businessType = BusinessType.POST, isSaveRequestData = true)
+    public Result<String> post(@RequestBody UpgradePackage upgradePackage){
+        log.info("新增套餐:{}", upgradePackage);
+        upgradePackageService.save(upgradePackage);
+        return GatewayResponse.SUCCESS.newBuilder().toResult();
+    }
+
+    /**
+     * 修改套餐
+     */
+    @PutMapping
+    @SaCheckPermission("upgrade:edit")
+    @Log(module = "套餐管理/修改套餐", businessType = BusinessType.PUT, isSaveRequestData = true)
+    public Result<String> put(@RequestBody UpgradePackage upgradePackage){
+        log.info("修改套餐:{}", upgradePackage);
+        upgradePackageService.updateById(upgradePackage);
+        return GatewayResponse.SUCCESS.newBuilder().toResult();
+    }
+
+    /**
+     * 删除套餐
+     */
+    @DeleteMapping("/{id}")
+    @SaCheckPermission("upgrade:delete")
+    @Log(module = "套餐管理/删除套餐", businessType = BusinessType.DELETE, isSaveRequestData = true)
+    public Result<String> delete(@PathVariable Long id){
+        log.info("删除套餐:{}", id);
+        UpgradePackage byId = upgradePackageService.getById(id);
+        if (byId == null){
+            throw BusinessRuntimeException.getInstance("套餐不存在");
+        }
+        byId.setDeleted(false);
+        upgradePackageService.updateById(byId);
+        return GatewayResponse.SUCCESS.newBuilder().toResult();
+    }
+
+    /**
+     * 上下架套餐
+     */
+    @PutMapping("/put/status/{id}")
+    @SaCheckPermission("upgrade:putStatus")
+    @Log(module = "套餐管理/上下架套餐", businessType = BusinessType.PUT, isSaveRequestData = true)
+    public Result<Boolean> putStatus(@PathVariable Long id){
+        log.info("上下架套餐:{}", id);
+        UpgradePackage byId = upgradePackageService.getById(id);
+        if (byId == null){
+            throw BusinessRuntimeException.getInstance("套餐不存在");
+        }
+        byId.setStatus(!byId.getStatus());
+        upgradePackageService.updateById(byId);
+        return GatewayResponse.SUCCESS.newBuilder().toResult(byId.getStatus());
+    }
+
+    /**
+     * 获取套餐
+     */
+    @GetMapping("/{id}")
+    public Result<UpgradePackage> get(@PathVariable Long id){
+        log.info("获取套餐:{}", id);
+        UpgradePackage byId = upgradePackageService.getById(id);
+        return GatewayResponse.SUCCESS.newBuilder().toResult(byId);
+    }
+
+    /**
+     * 获取套餐列表
+     */
+    @GetMapping
+    public Result<SearchResult<UpgradePackageView>> list() {
+        log.info("获取套餐列表");
+        Map<String, Object> build = MapUtils.flatBuilder(request.getParameterMap()).build();
+        SearchResult<UpgradePackageView> search = beanSearcher.search(UpgradePackageView.class, build);
+        return GatewayResponse.SUCCESS.newBuilder().toResult(search);
+    }
+
+
+}

+ 341 - 0
netflix-web/src/main/java/com/cyksj/web/controller/mirror/MidjourneyController.java

@@ -0,0 +1,341 @@
+package com.cyksj.web.controller.mirror;
+
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.cyksj.common.exception.BusinessRuntimeException;
+import com.cyksj.dto.Result;
+import com.cyksj.model.dto.*;
+import com.cyksj.enums.GatewayResponse;
+import com.cyksj.mapper.MidjourneyUserMapper;
+import com.cyksj.model.entity.MidjourneyUser;
+import com.cyksj.model.entity.MidjourneyUserConversation;
+import com.cyksj.redis.RedisService;
+import com.cyksj.service.midjourney.MidjourneyService;
+import com.ejlchina.searcher.BeanSearcher;
+import com.ejlchina.searcher.SearchResult;
+import com.ejlchina.searcher.util.MapBuilder;
+import com.ejlchina.searcher.util.MapUtils;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * @author zwhui
+ * @date 2024/4/23 10:31
+ */
+@Slf4j
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/applets/midjourney")
+public class MidjourneyController {
+
+    private final MidjourneyUserMapper midjourneyUserMapper;
+
+    private final HttpServletRequest request;
+
+    private final MidjourneyService midjourneyService;
+
+    private final BeanSearcher beanSearcher;
+    
+    private final RedisService redisService;
+
+    public MidjourneyUser getUser(){
+        String userToken = request.getHeader("user-token");
+        MidjourneyUser midjourneyUser = (MidjourneyUser) redisService.get(RedisService.key.MIDJOURNEY_USER.getName() + userToken);
+        if (midjourneyUser == null){
+            midjourneyUser = midjourneyUserMapper.selectOne(Wrappers.lambdaQuery(MidjourneyUser.class)
+                    .eq(MidjourneyUser::getUserToken, userToken).last("limit 1"));
+            if (midjourneyUser == null){
+                throw new BusinessRuntimeException("账号不存在,请重新登录");
+            }
+            redisService.set(RedisService.key.MIDJOURNEY_USER.getName() + userToken, midjourneyUser, RedisService.key.MIDJOURNEY_USER.getTimeout());
+            Object fastNum = redisService.get(RedisService.key.MIDJOURNEY_FAST_LIMIT.getName() + midjourneyUser.getId());
+            if (fastNum == null){
+                redisService.set(RedisService.key.MIDJOURNEY_FAST_LIMIT.getName() + midjourneyUser.getId(), midjourneyUser.getMjFastNum(), RedisService.key.MIDJOURNEY_FAST_LIMIT.getTimeout());
+            }
+            Object relaxNum = redisService.get(RedisService.key.MIDJOURNEY_RELAX_LIMIT.getName() + midjourneyUser.getId());
+            if (relaxNum == null){
+                if (midjourneyUser.getMjRelaxNum() != null){
+                    redisService.set(RedisService.key.MIDJOURNEY_RELAX_LIMIT.getName() + midjourneyUser.getId(), midjourneyUser.getMjRelaxNum(), RedisService.key.MIDJOURNEY_RELAX_LIMIT.getTimeout());
+                }
+            }
+        }else {
+            Object fastNum = redisService.get(RedisService.key.MIDJOURNEY_FAST_LIMIT.getName() + midjourneyUser.getId());
+            if (fastNum == null){
+                redisService.set(RedisService.key.MIDJOURNEY_FAST_LIMIT.getName() + midjourneyUser.getId(), midjourneyUser.getMjFastNum(), RedisService.key.MIDJOURNEY_FAST_LIMIT.getTimeout());
+            }else {
+                midjourneyUser.setMjFastNum(Integer.parseInt(fastNum.toString()));
+            }
+            Object relaxNum = redisService.get(RedisService.key.MIDJOURNEY_RELAX_LIMIT.getName() + midjourneyUser.getId());
+            if (relaxNum == null){
+                if (midjourneyUser.getMjRelaxNum() != null){
+                    redisService.set(RedisService.key.MIDJOURNEY_RELAX_LIMIT.getName() + midjourneyUser.getId(), midjourneyUser.getMjRelaxNum(), RedisService.key.MIDJOURNEY_RELAX_LIMIT.getTimeout());
+                }
+            }else {
+                midjourneyUser.setMjRelaxNum(Integer.parseInt(relaxNum.toString()));
+            }
+        }
+        return midjourneyUser;
+    }
+
+
+    /**
+     * 检查用户次数
+     */
+    public Long checkUserLimit(MidjourneyUser user){
+        Long num = 0L;
+        if (user.getExpireTime().before(new Date())){
+            throw BusinessRuntimeException.getInstance("账号已过期");
+        }
+       if (user.getMode() == 1){
+           num = redisService.decr(RedisService.key.MIDJOURNEY_FAST_LIMIT.getName() + user.getId(), 1L);
+           if (num < 0) {
+               user.setMode(2);
+               redisService.incr(RedisService.key.MIDJOURNEY_FAST_LIMIT.getName() + user.getId(), 1L);
+           }
+       }
+       if (user.getMode() == 2){
+           Object relax = redisService.get(RedisService.key.MIDJOURNEY_RELAX_LIMIT.getName() + user.getId());
+           if (relax != null){
+               num = redisService.decr(RedisService.key.MIDJOURNEY_RELAX_LIMIT.getName() + user.getId(), 1L);
+               if (num < 0){
+                   redisService.incr(RedisService.key.MIDJOURNEY_RELAX_LIMIT.getName() + user.getId(), 1L);
+               }
+           }else {
+               num = null;
+           }
+       }
+       return num;
+    }
+
+    /**
+     * 恢复次数
+     */
+    public void recoverUserLimit(Long id,Integer mode,Long num){
+        if (mode == 1){
+            redisService.incr(RedisService.key.MIDJOURNEY_FAST_LIMIT.getName() + id, 1L);
+        }
+        if (mode == 2){
+            if (num != null){
+                redisService.incr(RedisService.key.MIDJOURNEY_RELAX_LIMIT.getName() + id, 1L);
+            }
+        }
+    }
+
+    /**
+     * 同步数据库
+     */
+    public void syncUser(Long id,Integer mode,Long num){
+        log.info("同步次数 id:{},mode:{},num:{}",id,mode,num);
+        if (num == null){
+            return;
+        }
+        LambdaUpdateWrapper<MidjourneyUser> wrapper = Wrappers.lambdaUpdate(MidjourneyUser.class)
+                .eq(MidjourneyUser::getId, id);
+        if (mode == 1){
+            wrapper.set(MidjourneyUser::getMjFastNum, num);
+        }
+        if (mode == 2){
+            wrapper.set(MidjourneyUser::getMjRelaxNum, num);
+        }
+        midjourneyUserMapper.update(null, wrapper);
+    }
+
+
+    /**
+     * 查询用户信息
+     */
+    @GetMapping("/whoami")
+    public Result<MidjourneyUser> queryUser(){
+        MidjourneyUser user = getUser();
+        return GatewayResponse.SUCCESS.newBuilder().toResult(user);
+    }
+
+
+    /**
+     * 提交Imagine任务
+     */
+    @PostMapping("/submit/imagine")
+    public Result<MidjourneyUserConversation> submitImagine(@RequestBody SubmitImagineDTO submitImagineDTO) {
+        log.info("提交Imagine任务,提示:{},base64数组长度:{}",submitImagineDTO.getPrompt(),submitImagineDTO.getBase64Array());
+        MidjourneyUser user = getUser();
+        Long num = checkUserLimit(user);
+        if (num != null && num < 0){
+            throw BusinessRuntimeException.getInstance("次数已用完");
+        }
+        MidjourneyUserConversation conversation;
+        try {
+            conversation = midjourneyService.submitImagine(user, submitImagineDTO.getPrompt(), submitImagineDTO.getBase64Array());
+        } catch (Exception e) {
+            recoverUserLimit(user.getId(), user.getMode(),num);
+            throw BusinessRuntimeException.getInstance(e.getMessage());
+        }
+        syncUser(user.getId(), user.getMode(), num);
+        return GatewayResponse.SUCCESS.newBuilder().toResult(conversation);
+    }
+
+    /**
+     * 提交Describe任务
+     */
+    @PostMapping("/submit/describe")
+    public Result<MidjourneyUserConversation> submitDescribe(@RequestBody SubmitDescribeDTO submitDescribeDTO) {
+        log.info("提交Describe任务");
+        MidjourneyUser user = getUser();
+        Long num = checkUserLimit(user);
+        if (num != null && num < 0){
+            throw BusinessRuntimeException.getInstance("次数已用完");
+        }
+        MidjourneyUserConversation conversation;
+        try {
+            conversation = midjourneyService.submitDescribe(user, submitDescribeDTO.getBase64());
+        } catch (Exception e) {
+            recoverUserLimit(user.getId(), user.getMode(),num);
+            throw BusinessRuntimeException.getInstance(e.getMessage());
+        }
+        syncUser(user.getId(), user.getMode(), num);
+        return GatewayResponse.SUCCESS.newBuilder().toResult(conversation);
+    }
+
+    /**
+     * 提交Blend任务
+     */
+    @PostMapping("/submit/blend")
+    public Result<MidjourneyUserConversation> submitBlend(@RequestBody SubmitBlendDTO submitBlendDTO) {
+        log.info("提交Blend任务 dimensions:{},base64数组长度:{}", submitBlendDTO.getDimensions(), submitBlendDTO.getBase64Array().size());
+        MidjourneyUser user = getUser();
+        Long num = checkUserLimit(user);
+        if (num != null && num < 0){
+            throw BusinessRuntimeException.getInstance("次数已用完");
+        }
+        MidjourneyUserConversation conversation;
+        try {
+            conversation = midjourneyService.submitBlend(user, submitBlendDTO.getDimensions(), submitBlendDTO.getBase64Array());
+        } catch (Exception e) {
+            recoverUserLimit(user.getId(), user.getMode(),num);
+            throw BusinessRuntimeException.getInstance(e.getMessage());
+        }
+        syncUser(user.getId(), user.getMode(), num);
+        return GatewayResponse.SUCCESS.newBuilder().toResult(conversation);
+    }
+
+    /**
+     * 提交Modal任务
+     */
+    @PostMapping("/submit/modal")
+    public Result<MidjourneyUserConversation> submitModal(@RequestBody SubmitModalDTO submitModalDTO) {
+        log.info("提交Modal任务,taskId:{},提示:{},base64数组长度:{}",submitModalDTO.getTaskId(),submitModalDTO.getPrompt(),submitModalDTO.getMaskBase64());
+        MidjourneyUser user = getUser();
+        Long num = checkUserLimit(user);
+        if (num != null && num < 0){
+            throw BusinessRuntimeException.getInstance("次数已用完");
+        }
+        MidjourneyUserConversation conversation;
+        try {
+            conversation = midjourneyService.submitModal(user, submitModalDTO.getTaskId(), submitModalDTO.getPrompt(), submitModalDTO.getMaskBase64());
+        } catch (Exception e) {
+            recoverUserLimit(user.getId(), user.getMode(),num);
+            throw BusinessRuntimeException.getInstance(e.getMessage());
+        }
+        syncUser(user.getId(), user.getMode(), num);
+        return GatewayResponse.SUCCESS.newBuilder().toResult(conversation);
+    }
+
+    /**
+     * 提交Shorten任务
+     */
+    @PostMapping("/submit/shorten")
+    public Result<MidjourneyUserConversation> submitShorten(@RequestBody SubmitShortenDTO submitShortenDTO) {
+        log.info("提交Shorten任务 提示词:{}",submitShortenDTO.getPrompt());
+        MidjourneyUser user = getUser();
+        Long num = checkUserLimit(user);
+        if (num != null && num < 0){
+            throw BusinessRuntimeException.getInstance("次数已用完");
+        }
+        MidjourneyUserConversation conversation;
+        try {
+            conversation = midjourneyService.submitShorten(user, submitShortenDTO.getPrompt());
+        } catch (Exception e) {
+            recoverUserLimit(user.getId(), user.getMode(),num);
+            throw BusinessRuntimeException.getInstance(e.getMessage());
+        }
+        syncUser(user.getId(), user.getMode(), num);
+        return GatewayResponse.SUCCESS.newBuilder().toResult(conversation);
+    }
+
+    /**
+     * 执行动作
+     */
+    @PostMapping("/submit/action")
+    public Result<MidjourneyUserConversation> action(@RequestBody SubmitActionDTO actionDTO) {
+        log.info("任务id:{},执行动作:{}",actionDTO.getTaskId(),actionDTO.getCustomId());
+        MidjourneyUser user = getUser();
+        Long num = checkUserLimit(user);
+        if (num != null && num < 0){
+            throw BusinessRuntimeException.getInstance("次数已用完");
+        }
+        MidjourneyUserConversation conversation;
+        try {
+            conversation = midjourneyService.submitAction(user, actionDTO.getTaskId(), actionDTO.getCustomId());
+        } catch (Exception e) {
+            recoverUserLimit(user.getId(), user.getMode(),num);
+            throw BusinessRuntimeException.getInstance(e.getMessage());
+        }
+        syncUser(user.getId(), user.getMode(), num);
+        return GatewayResponse.SUCCESS.newBuilder().toResult(conversation);
+    }
+
+
+    /**
+     * 查询会话列表
+     */
+    @GetMapping("/conversation/list")
+    public Result<SearchResult<MidjourneyUserConversation>> conversationList(){
+        MidjourneyUser user = getUser();
+        log.info("查询会话列表 userId:{}",user.getId());
+        MapBuilder builder = MapUtils.flatBuilder(request.getParameterMap());
+        return GatewayResponse.SUCCESS.newBuilder().toResult(beanSearcher.search(MidjourneyUserConversation.class,
+                builder.field(MidjourneyUserConversation::getUserId, user.getId())
+                        .orderBy(MidjourneyUserConversation::getId).desc().build()));
+    }
+
+    /**
+     * 根据ids查询会话
+     */
+    @GetMapping("/conversation/listByIds")
+    public Result<List<MidjourneyUserConversation>> conversationListByIds(Integer mode,@RequestParam(value = "ids" ) List<Long> ids) throws Exception {
+        MidjourneyUser user = getUser();
+        log.info("根据ids查询会话 userId:{}",user.getId());
+        return GatewayResponse.SUCCESS.newBuilder().toResult(midjourneyService.listConversationByIds(mode,ids));
+    }
+
+    /**
+     * 取消任务
+     */
+    @PostMapping("/conversation/{id}/cancel")
+    public Result<MidjourneyUserConversation> conversationCancel(@PathVariable("id") Long id){
+        MidjourneyUser user = getUser();
+        log.info("取消任务 id:{}",id);
+        return GatewayResponse.SUCCESS.newBuilder().toResult(midjourneyService.cancelConversation(user,id));
+    }
+
+
+    /**
+     * 修改midjourney账号模式
+     */
+    @PutMapping("/put/midjourney/user/{mode}")
+    public Result<String> putMidjourneyUserMode(@PathVariable Integer mode, Long userId) {
+        log.info("修改midjourney账号模式 userId:{},mode:{}", userId, mode);
+        MidjourneyUser midjourneyUser = midjourneyUserMapper.selectById(userId);
+        if (midjourneyUser == null) {
+            throw BusinessRuntimeException.getInstance("参数错误");
+        }
+        midjourneyUser.setMode(mode);
+        midjourneyUserMapper.updateById(midjourneyUser);
+        redisService.del(RedisService.key.MIDJOURNEY_USER.getName() + midjourneyUser.getUserToken());
+        return GatewayResponse.SUCCESS.newBuilder().toResult();
+    }
+}

+ 35 - 0
netflix-web/src/main/java/com/cyksj/web/controller/mirror/MirrorController.java

@@ -2,6 +2,7 @@ package com.cyksj.web.controller.mirror;
 
 import cn.hutool.core.date.DateUtil;
 import cn.hutool.json.JSONObject;
+import com.cyksj.common.EnvCommonService;
 import com.cyksj.common.exception.BusinessRuntimeException;
 import com.cyksj.common.task.GlobalThreadPoolTaskExecutor;
 import com.cyksj.common.util.IoKit;
@@ -9,6 +10,7 @@ import com.cyksj.dto.Result;
 import com.cyksj.enums.GatewayResponse;
 import com.cyksj.model.entity.ChatgptSession;
 import com.cyksj.model.entity.ChatgptUser;
+import com.cyksj.model.entity.MidjourneyUser;
 import com.cyksj.model.request.gpt.ConversationRequest;
 import com.cyksj.model.response.ConversationLimitResponse;
 import com.cyksj.model.views.ChatGptUserConversationRecordHistoryView;
@@ -16,6 +18,7 @@ import com.cyksj.model.views.ChatGptUserView;
 import com.cyksj.model.views.ChatgptCarInfoView;
 import com.cyksj.model.views.ChatgptConversionLimitView;
 import com.cyksj.service.chatgpt.ChatGptAccountService;
+import com.cyksj.service.midjourney.MidjourneyAccountService;
 import com.cyksj.web.util.StpUserUtil;
 import com.ejlchina.searcher.BeanSearcher;
 import com.ejlchina.searcher.SearchResult;
@@ -24,8 +27,10 @@ import com.ejlchina.searcher.util.MapUtils;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.web.bind.annotation.*;
 
+import javax.servlet.http.Cookie;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
@@ -58,6 +63,16 @@ public class MirrorController {
 
     private final BeanSearcher beanSearcher;
 
+    private final MidjourneyAccountService midjourneyAccountService;
+
+    private final EnvCommonService envCommonService;
+
+    @Value("${midjourney.url}")
+    private String midjourneyHost;
+
+    @Value("${midjourney.drawUrl}")
+    private String midjourneyDrawUrl;
+
 
     /**
      * GPT车票跳转登录
@@ -295,4 +310,24 @@ public class MirrorController {
 
 
 
+    /**
+     * MJ车票跳转登录
+     *
+     * @param relationId 车票id
+     */
+    @GetMapping("/midjourneyMirror/{relationId}")
+    public Result<String> midjourneyMirror(@PathVariable Long relationId) {
+        Long userId = StpUserUtil.getLoginIdAsLong();
+        MidjourneyUser midjourneyUser = midjourneyAccountService.getMidjourneyUserToken(userId, relationId);
+        return GatewayResponse.SUCCESS.newBuilder().toResult(midjourneyHost + (envCommonService.isPreEnv() ? "/8082":"/8081") +"/api/applets/mirror/midjourneyMirrorWithToken/" + midjourneyUser.getUserToken());
+    }
+
+    @GetMapping("/midjourneyMirrorWithToken/{userToken}")
+    public void midjourneyMirrorWithToken(@PathVariable String userToken) throws IOException {
+        Cookie cookie = new Cookie("userToken", userToken);
+        cookie.setMaxAge(31536000);
+        cookie.setPath("/");
+        response.addCookie(cookie);
+        response.sendRedirect(midjourneyDrawUrl);
+    }
 }

+ 156 - 2
netflix-web/src/main/java/com/cyksj/web/controller/payment/OrderController.java

@@ -26,13 +26,14 @@ import com.cyksj.mapper.market.task.UserBindDetailMapper;
 import com.cyksj.model.dto.AliPayParams;
 import com.cyksj.model.dto.H5JsPayParams;
 import com.cyksj.model.entity.*;
-import com.cyksj.model.manage.views.OrderDonSearchView;
-import com.cyksj.model.manage.views.OrderDonView;
+import com.cyksj.model.manage.views.*;
 import com.cyksj.model.request.AlipayOrderDonReq;
 import com.cyksj.model.request.OrderPayRequest;
 import com.cyksj.model.response.AliPayTradeStatus;
 import com.cyksj.model.views.GoodsDonSkuView;
+import com.cyksj.model.views.MidjourneyUserView;
 import com.cyksj.model.views.OrderCommentView;
+import com.cyksj.service.midjourney.MidjourneyAccountService;
 import com.cyksj.service.order.OrderDonService;
 import com.cyksj.service.paypal.PayPalService;
 import com.cyksj.service.paypal.PaypalPayConfigService;
@@ -54,6 +55,7 @@ import com.stripe.model.checkout.Session;
 import com.stripe.net.Webhook;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
@@ -119,6 +121,12 @@ public class OrderController {
     @Autowired
     private StripeService stripeService;
 
+    @Autowired
+    private YhShopFrontService yhShopFrontService;
+
+    @Autowired
+    private MidjourneyAccountService midjourneyAccountService;
+
     /**
      * 支付
      * @author chan
@@ -164,6 +172,25 @@ public class OrderController {
         return GatewayResponse.SUCCESS.newBuilder().toResult(orderDon);
     }
 
+    /**
+     * 套餐提交支付
+     */
+    @PostMapping("/post/upgrade/submit")
+    public Result<UpgradeOrderDon> submitUpgrade(@Validated @RequestBody OrderPayRequest payRequest) throws Exception {
+        final String methodName = "套餐吊起支付";
+        Long userId = StpUserUtil.getUserIdAfterLogin();
+        payRequest.setUserId(userId);
+        log.info("{}[start] params:{}", methodName, payRequest);
+
+        String ip = ServletUtil.getClientIP(request);
+
+        payRequest.setIp(ip);
+
+        UpgradeOrderDon orderDon = orderDonService.submitUpgrade(payRequest);
+
+        return GatewayResponse.SUCCESS.newBuilder().toResult(orderDon);
+    }
+
     /**
      * 0元订单确认
      */
@@ -173,6 +200,15 @@ public class OrderController {
         return GatewayResponse.SUCCESS.newBuilder().toResult();
     }
 
+    /**
+     * 套餐0元订单确认
+     */
+    @PostMapping("/post/upgrade/order/status/{orderId}")
+    public Result<String> zeroUpgradeOrderConfirm(@PathVariable Long orderId) {
+        orderDonService.zeroUpgradeOrderConfirm(orderId);
+        return GatewayResponse.SUCCESS.newBuilder().toResult();
+    }
+
 
     /**
      * 支付
@@ -287,6 +323,17 @@ public class OrderController {
         return GatewayResponse.SUCCESS.newBuilder().toResult(aliPayParams.getBody());
 	}
 
+    /**
+     * 套餐支付宝支付
+     */
+    @PostMapping("/post/pay/upgrade/zfb")
+    @Deprecated
+    public Result<String> aliPayUpgrade(@RequestBody @Validated AlipayOrderDonReq re) throws Exception {
+        AliPayParams aliPayParams = orderDonService.aliPayUpgrade(re, ZfbProductCode.MOBILE_WEB.getProductCode(),null);
+        return GatewayResponse.SUCCESS.newBuilder().toResult(aliPayParams.getBody());
+    }
+
+
     /**
      * 支付宝web支付
      * pc网页内嵌二维码
@@ -302,6 +349,21 @@ public class OrderController {
         return GatewayResponse.SUCCESS.newBuilder().toResult(aliPayParams.getBody());
     }
 
+    /**
+     * 支付宝web支付
+     * pc网页内嵌二维码
+     */
+    @PostMapping("/get/zfb/upgrade/orderQrCode")
+    public Result<String> zfbWebQrCodeUpgrade(@RequestBody @Validated AlipayOrderDonReq re) throws Exception {
+        AliPayParams aliPayParams = orderDonService.aliPayUpgrade(re, ZfbProductCode.PC_WEB.getProductCode(), BaseZfbConfig.qrPayMode_orderCode);
+//        response.setContentType("text/html;charset=" + BaseZfbConfig.charset);
+        //直接将完整的表单html输出到页面
+//        response.getWriter().write(aliPayParams.getBody());
+//        response.getWriter().flush();
+//        response.getWriter().close();
+        return GatewayResponse.SUCCESS.newBuilder().toResult(aliPayParams.getBody());
+    }
+
     /**
      * 支付宝web支付
      * pc网页跳转二维码
@@ -331,6 +393,26 @@ public class OrderController {
 		return "success";
 	}
 
+    /**
+     * 支付宝套餐包回调
+     */
+    @PostMapping("/prepay/zfb/upgrade/notify")
+    public String aliPayUpgradeNotify(HttpServletRequest request) throws Exception {
+        ////将异步通知中收到的所有参数都存放到map中
+        Map<String, String> params = convertRequestParamsToMap(request);
+        String tradeStatus = params.get("trade_status");
+        String orderNo = params.get("out_trade_no");
+        if (AliPayTradeStatus.TRADE_SUCCESS.getStatus().equals(tradeStatus) || AliPayTradeStatus.TRADE_FINISHED.getStatus().equals(tradeStatus)) {
+            log.info("支付宝商家订单号orderNo:{},status:{}开始回调", orderNo, tradeStatus);
+            orderDonService.aliPayUpgradeNotify(params);
+        } else {
+            log.error("支付宝回调 error 状态不符合 成功或完成 status:{} ,orderNo:{}", tradeStatus, orderNo);
+            return "fail";
+        }
+        return "success";
+    }
+
+
     /**
      * 支付宝转账
      */
@@ -430,6 +512,23 @@ public class OrderController {
         return GatewayResponse.SUCCESS.newBuilder().toResult(orderDonView);
     }
 
+    /**
+     * 查询套餐订单支付状态
+     */
+    @GetMapping("/get/upgrade/order/status/{orderId}")
+    public Result<UpgradeOrderDonSearchView> getUpgradeOrderStatusById(@PathVariable Long orderId) {
+        if (orderId < 1) {
+            throw BusinessRuntimeException.getInstance("订单不存在");
+        }
+        UpgradeOrderDonSearchView orderDonView = beanSearcher.searchFirst(UpgradeOrderDonSearchView.class, MapUtils.builder()
+                .put("orderId", String.format("where id = %s", orderId))
+                .build());
+        if (orderDonView == null) {
+            throw BusinessRuntimeException.getInstance("订单不存在");
+        }
+        return GatewayResponse.SUCCESS.newBuilder().toResult(orderDonView);
+    }
+
     @PostMapping("/get/noLogin/order")
     public Result<List<OrderDonView>> getSearchResult(@RequestBody List<Long> orderIds) {
         List<OrderDonView> orderDonViews = beanSearcher.searchAll(OrderDonView.class, MapUtils.builder()
@@ -646,4 +745,59 @@ public class OrderController {
                 .build());
         return GatewayResponse.SUCCESS.newBuilder().toResult(search);
     }
+
+
+    /**
+     * 获取套餐订单列表
+     */
+    @GetMapping("/upgrade/get")
+    public Result<SearchResult<UpgradeOrderDonView>> listUpgradeOrder(){
+        long userId = StpUserUtil.getLoginIdAsLong();
+        MapBuilder builder = MapUtils.flatBuilder(request.getParameterMap());
+        List<Long> userIds = UserBindRelationService.getRelationUserIdList(userId, null);
+        SearchResult<UpgradeOrderDonView> search = beanSearcher.search(UpgradeOrderDonView.class, builder
+                .field(UpgradeOrderDonView::getUserId, userIds).op(Operator.InList)
+                .orderBy(UpgradeOrderDonView::getId).desc()
+                .build());
+        return GatewayResponse.SUCCESS.newBuilder().toResult(search);
+    }
+
+
+    /**
+     * 获取套餐订单详情
+     */
+    @GetMapping("/get/upgrade/{orderId}")
+    public Result<UpgradeOrderDetailView> upgradeOrderDetail(@PathVariable Long orderId) {
+        Long userId = StpUserUtil.getUserIdAfterLogin();
+
+        UpgradeOrderDetailView detail = orderDonService.getUpgradeOrderDetail(orderId, userId);
+
+        return GatewayResponse.SUCCESS.newBuilder().toResult(detail);
+    }
+
+    /**
+     * 取消套餐订单
+     */
+    @GetMapping("/get/close/upgrade/{orderId}")
+    public Result<String> closeUpgradeOrder(@PathVariable Long orderId) {
+        long userId = StpUserUtil.getLoginIdAsLong();
+        orderDonService.closeUpgradeOrder(orderId, userId);
+        return GatewayResponse.SUCCESS.newBuilder().toResult();
+    }
+
+    /**
+     * midjourney查询订单用户账号剩余次数
+     */
+    @GetMapping("/get/midjourney/user")
+    public Result<MidjourneyUserView> getMidjourneyUser(Long relationId) {
+        long userId = StpUserUtil.getLoginIdAsLong();
+        MidjourneyUserView search = beanSearcher.searchFirst(MidjourneyUserView.class, MapUtils.flatBuilder(request.getParameterMap())
+                .field(MidjourneyUserView::getUserId, userId).field(MidjourneyUserView::getRelationId, relationId).build());
+        if (search == null) {
+            MidjourneyUser midjourneyUser = midjourneyAccountService.getMidjourneyUserToken(userId, relationId);
+            search = new MidjourneyUserView();
+            BeanUtils.copyProperties(midjourneyUser, search);
+        }
+        return GatewayResponse.SUCCESS.newBuilder().toResult(search);
+    }
 }

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
netflix-web/src/main/resources/application-dev.yml


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
netflix-web/src/main/resources/application-prd.yml


+ 5 - 1
netflix-web/src/main/resources/application-pre.yml

@@ -193,4 +193,8 @@ chatgpt:
 
 smsVerify:
   apikey: bc0737b7339ab0f275f954a161fdb313
-  shoppingApikey: 72d4ff765b6c962f35c9a4125100680d
+  shoppingApikey: 72d4ff765b6c962f35c9a4125100680d
+
+midjourney:
+  url: https://nf.video
+  drawUrl: https://nf.video/drawTest/web/

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است