Преглед на файлове

Merge remote-tracking branch 'origin/midjourney-detach' into midjourney-detach

zwhui преди 2 години
родител
ревизия
f94ef10230

+ 11 - 0
midjourney/src/main/java/com/yhlxj/dao/mapper/midjourney/MidjourneyApiChannelMapper.java

@@ -0,0 +1,11 @@
+package com.yhlxj.dao.mapper.midjourney;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.yhlxj.dao.model.entity.MidjourneyApiChannel;
+
+/**
+ * @author chan
+ * @date 2024/7/10 17:46
+ */
+public interface MidjourneyApiChannelMapper extends BaseMapper<MidjourneyApiChannel> {
+}

+ 36 - 0
midjourney/src/main/java/com/yhlxj/dao/model/entity/MidjourneyApiChannel.java

@@ -0,0 +1,36 @@
+package com.yhlxj.dao.model.entity;
+
+import com.yhlxj.dao.model.BaseEntity;
+import lombok.Data;
+
+/**
+ * @author chan
+ * @date 2024/7/10 17:45
+ */
+@Data
+public class MidjourneyApiChannel extends BaseEntity {
+
+    private String name;
+
+    /**
+     * 1快速 2慢速
+     */
+    private Integer mode;
+
+    /**
+     * 接口地址
+     */
+    private String url;
+
+    /**
+     * 凭证
+     */
+    private String token;
+
+    /**
+     * 是否默认
+     */
+    private Boolean isDefault;
+
+
+}

+ 5 - 0
midjourney/src/main/java/com/yhlxj/dao/model/entity/MidjourneyUserConversation.java

@@ -33,6 +33,11 @@ public class MidjourneyUserConversation extends BaseEntity implements Serializab
      */
     private String action;
 
+    /**
+     * FAST模式服务商 id
+     */
+    private Long apiChannelId;
+
     /**
      * 频道id
      */

+ 4 - 0
midjourney/src/main/java/com/yhlxj/dao/model/response/SubmitResult.java

@@ -1,5 +1,6 @@
 package com.yhlxj.dao.model.response;
 
+import com.fasterxml.jackson.annotation.JsonIgnore;
 import lombok.Data;
 
 import java.util.HashMap;
@@ -24,6 +25,9 @@ public class SubmitResult {
 
     private Long instanceId;
 
+    @JsonIgnore
+    private Long apiChannelId = 1L;
+
     /**
      * 扩展字段
      */

+ 1 - 1
midjourney/src/main/java/com/yhlxj/service/midjourney/MidjourneyService.java

@@ -30,7 +30,7 @@ public interface MidjourneyService {
 
     MidjourneyUserConversation getConversationById(Long id);
 
-    SubmitResult submitAction(MidjourneyUser user,MidjourneyUserConversation conversation,Integer mode, Long taskId, String customId, Long num, String botType) throws Exception;
+    SubmitResult submitAction(MidjourneyUser user,MidjourneyUserConversation conversation,Integer mode, Long taskId, String customId, Long num, String botType, Long apiChannelId) throws Exception;
 
     void cancelConversation(MidjourneyUser user, Long id);
 

+ 93 - 27
midjourney/src/main/java/com/yhlxj/service/midjourney/impl/MidjourneyServiceImpl.java

@@ -17,6 +17,7 @@ import com.cyksj.common.util.Jsons;
 import com.cyksj.common.util.QueryWrapperUtils;
 import com.cyksj.common.util.StringUtil;
 import com.yhlxj.dao.mapper.GroupsRelationMapper;
+import com.yhlxj.dao.mapper.midjourney.MidjourneyApiChannelMapper;
 import com.yhlxj.dao.mapper.midjourney.MidjourneyUserConversationMapper;
 import com.yhlxj.dao.mapper.midjourney.MidjourneyUserMapper;
 import com.yhlxj.dao.model.dto.BlendDimensions;
@@ -79,6 +80,11 @@ public class MidjourneyServiceImpl implements MidjourneyService {
 
     private static final List<String> status = List.of("MODAL","CANCEL","FAILURE");
 
+    /**
+     * 需要切换账号或服务商进行重试的操作
+     */
+    private static final List<String> RETRY_ACTION = List.of("imagine","blend","describe","shorten");
+
     private static final String PROMPT = "--v 6 ";
     private static final String SETTINGS = "{\"version\":\"--v 6\",\"remix\":false,\"raw\":false,\"variation\":\"HIGH\",\"mode\":\"relax\",\"stylize\":\"MED\"}";
 
@@ -99,6 +105,8 @@ public class MidjourneyServiceImpl implements MidjourneyService {
 
     private final GroupsRelationMapper groupsRelationMapper;
 
+    private final MidjourneyApiChannelMapper midjourneyApiChannelMapper;
+
     @Override
     public MidjourneyUser getUser(String userToken) {
         MidjourneyUser midjourneyUser = midjourneyUserMapper.selectOne(Wrappers.lambdaQuery(MidjourneyUser.class)
@@ -139,17 +147,17 @@ public class MidjourneyServiceImpl implements MidjourneyService {
         if (CollectionUtil.isNotEmpty(base64Array)) {
             imagineParam.put("base64Array", base64Array);
         }
-        SubmitResult result = submit(user,mode,"imagine", null,imagineParam);
+        SubmitResult result = submit(user,mode,"imagine", null,imagineParam, null);
         if (mode == 2) {
             redisService.hincr(RedisService.key.MIDJOURNEY_ACCOUNT.getName(), result.getInstanceId().toString(), 1.0);
         }
-        saveConversation(user.getId(), mode,result,"IMAGINE", StringUtils.EMPTY, botType);
+        saveConversation(user.getId(), mode,result,"IMAGINE", StringUtils.EMPTY, botType,result.getApiChannelId());
         Map<String, Object> properties = result.getProperties();
         properties.put("messageContent","**"+prompt+"** - <@mj>" + (mode == 1 ? "(fast)" : "(relaxed)"));
         return result;
     }
 
-    public MidjourneyUserConversation saveConversation(Long userId,Integer mode,SubmitResult result, String action,String prompt, String botType) throws Exception {
+    public MidjourneyUserConversation saveConversation(Long userId,Integer mode,SubmitResult result, String action,String prompt, String botType, Long apiChannelId) throws Exception {
         Long taskId = Long.parseLong(result.getResult());
         Map<String, Object> properties = result.getProperties();
         MidjourneyUserConversation conversation = null;
@@ -176,6 +184,9 @@ public class MidjourneyServiceImpl implements MidjourneyService {
         if (conversation.getInstanceId() == null) {
             conversation.setInstanceId(result.getInstanceId());
         }
+        if(apiChannelId != null){
+            conversation.setApiChannelId(apiChannelId);
+        }
         if (conversation.getId() == null) {
             conversationMapper.insert(conversation);
         }else {
@@ -191,11 +202,11 @@ public class MidjourneyServiceImpl implements MidjourneyService {
                 .put("state", user.getId().toString())
                 .put("base64", base64)
                 .build();
-        SubmitResult result = submit(user,mode,"describe", null, param);
+        SubmitResult result = submit(user,mode,"describe", null, param, null);
         if (mode == 2) {
             redisService.hincr(RedisService.key.MIDJOURNEY_ACCOUNT.getName(), result.getInstanceId().toString(), 1.0);
         }
-        saveConversation(user.getId(), mode,result,"DESCRIBE", StringUtils.EMPTY, botType);
+        saveConversation(user.getId(), mode,result,"DESCRIBE", StringUtils.EMPTY, botType, result.getApiChannelId());
         return result;
     }
 
@@ -208,11 +219,11 @@ public class MidjourneyServiceImpl implements MidjourneyService {
         if (dimensions != null) {
             param.put("dimensions", dimensions);
         }
-        SubmitResult result = submit(user,mode,"blend", null, param);
+        SubmitResult result = submit(user,mode,"blend", null, param, null);
         if (mode == 2) {
             redisService.hincr(RedisService.key.MIDJOURNEY_ACCOUNT.getName(), result.getInstanceId().toString(), 1.0);
         }
-        saveConversation(user.getId(), mode,result,"BLEND", StringUtils.EMPTY, botType);
+        saveConversation(user.getId(), mode,result,"BLEND", StringUtils.EMPTY, botType, result.getApiChannelId());
         return result;
     }
 
@@ -232,11 +243,11 @@ public class MidjourneyServiceImpl implements MidjourneyService {
         if (StringUtils.isNotBlank(maskBase64)) {
             param.put("maskBase64", maskBase64);
         }
-        SubmitResult result = submit(user,mode,"modal", null, param);
+        SubmitResult result = submit(user,mode,"modal", null, param,midjourneyUserConversation.getApiChannelId());
         if (mode == 2) {
             redisService.hincr(RedisService.key.MIDJOURNEY_ACCOUNT.getName(), result.getInstanceId().toString(), 1.0);
         }
-        saveConversation(user.getId(),  mode,result,"MODAL", StringUtils.EMPTY, StringUtils.EMPTY);
+        saveConversation(user.getId(),  mode,result,"MODAL", StringUtils.EMPTY, StringUtils.EMPTY,midjourneyUserConversation.getApiChannelId());
         Map<String, Object> properties = result.getProperties();
         properties.put("finalPrompt",midjourneyUserConversation.getPrompt());
         properties.put("messageContent","**"+prompt+"** - <@mj>" + (mode == 1 ? "(fast)" : "(relaxed)"));
@@ -249,11 +260,11 @@ public class MidjourneyServiceImpl implements MidjourneyService {
                 .put("prompt", prompt)
                 .put("state", user.getId().toString())
                 .build();
-        SubmitResult result = submit(user,mode,"shorten", null, param);
+        SubmitResult result = submit(user,mode,"shorten", null, param, null);
         if (mode == 2) {
             redisService.hincr(RedisService.key.MIDJOURNEY_ACCOUNT.getName(), result.getInstanceId().toString(), 1.0);
         }
-        saveConversation(user.getId(), mode, result,"SHORTEN", StringUtils.EMPTY, botType);
+        saveConversation(user.getId(), mode, result,"SHORTEN", StringUtils.EMPTY, botType, result.getApiChannelId());
         return result;
     }
 
@@ -338,7 +349,7 @@ public class MidjourneyServiceImpl implements MidjourneyService {
 //    }
 
     @Override
-    public SubmitResult submitAction(MidjourneyUser user,MidjourneyUserConversation conversation,Integer mode, Long taskId, String customId, Long num, String botType) throws Exception {
+    public SubmitResult submitAction(MidjourneyUser user,MidjourneyUserConversation conversation,Integer mode, Long taskId, String customId, Long num, String botType, Long apiChannelId) throws Exception {
         AtomicBoolean modalFlag = new AtomicBoolean(false);
         if (StringUtils.isNotBlank(conversation.getButtons())){
             List<MessageButton> messageButtons = Jsons.parseList(conversation.getButtons(), MessageButton.class);
@@ -373,7 +384,7 @@ public class MidjourneyServiceImpl implements MidjourneyService {
                 .put("state", user.getId().toString())
                 .put("customId", customId)
                 .build();
-        SubmitResult result = submit(user,mode,"action", conversation.getInstanceId(),param);
+        SubmitResult result = submit(user,mode,"action", conversation.getInstanceId(),param, apiChannelId);
         if (result.getCode() == 21) {
             if (mode == 2 && modalFlag.get()){
                 SubmitResult modal = submitModal(user, mode,Long.valueOf(result.getResult()), conversation.getPrompt(), null);
@@ -395,20 +406,37 @@ public class MidjourneyServiceImpl implements MidjourneyService {
         Map<String, Object> properties = result.getProperties();
         properties.put("finalPrompt",conversation.getPrompt());
         properties.put("messageContent","**"+conversation.getPrompt()+"** - <@mj>" + (mode == 1 ? " (fast)" : " (relaxed)"));
-        MidjourneyUserConversation midjourneyUserConversation = saveConversation(user.getId(), mode, result, "ACTION", StringUtils.EMPTY, botType);
+        MidjourneyUserConversation midjourneyUserConversation = saveConversation(user.getId(), mode, result, "ACTION", StringUtils.EMPTY, botType, apiChannelId);
         properties.put("id",midjourneyUserConversation.getId());
         return result;
     }
 
 
-    public SubmitResult submit(MidjourneyUser user,Integer mode,String action,Long instanceId, Map<String, Object> param) throws Exception {
-        String url = "";
+    public SubmitResult submit(MidjourneyUser user, Integer mode, String action, Long instanceId, Map<String, Object> param, Long apiChannelId) throws Exception {
+
         String accountWithMinUsage = "";
+        String url = "";
+        String token = "";
         if (mode == 1) {
             param.put("mode", "FAST");
-            url = FAST_HOST;
+
+            MidjourneyApiChannel midjourneyApiChannel = midjourneyApiChannelMapper.selectOne(QueryWrapperUtils.buildWrapper((wrapper)->{
+                if(apiChannelId != null && apiChannelId != 0){
+                    wrapper.eq(MidjourneyApiChannel::getId,apiChannelId);
+                }else {
+                    wrapper.eq(MidjourneyApiChannel::getIsDefault, true);
+                }
+            }));
+            if (midjourneyApiChannel == null) {
+                throw BusinessRuntimeException.getInstance("FAST模式账号异常");
+            }
+
+            url = midjourneyApiChannel.getUrl();
+            token = midjourneyApiChannel.getToken();
+
         } else if (mode == 2) {
             url = RELAX_HOST;
+            token = RELAX_TOKEN;
             //慢速查询在使用次数最少的账号
             accountWithMinUsage = getAccountWithMinUsage(instanceId);
             if (accountWithMinUsage == null) {
@@ -418,25 +446,45 @@ public class MidjourneyServiceImpl implements MidjourneyService {
                     .put("instanceId",accountWithMinUsage).build());
             userSubmitLimit(user,action,param.get("customId"));
         }
-        url = url + getActionUrl(action);
-        param.put("notifyHook",midjourneyHost +(EnvCommonService.active.equals(envCommonService.getEnv()) ? "/8083":"/8082") + "/api/applets/midjourney/notifyHook");
-        HttpRequest request = HttpRequest.post(url).body(Jsons.toJson(param));
-        request = mode == 1 ? request.header("Authorization", FAST_TOKEN) : request.header("mj-api-secret", RELAX_TOKEN);
-        String body = request.execute().body();
-        log.info("action body:{}", body);
-        SubmitResult submitResult = Jsons.parseObject(body, SubmitResult.class);
+        SubmitResult submitResult = submit2MjProxy(url, token, mode, action, param);
         int code = submitResult.getCode();
         if (code != 1 && code != 21 && code != 22) {
             if (code == 3) {
-                if(mode == 2 && StringUtils.isNotBlank(accountWithMinUsage)){
+                if(mode == 2){
                     redisService.hdel(RedisService.key.MIDJOURNEY_ACCOUNT.getName(),accountWithMinUsage);
                     String finalAccountWithMinUsage = accountWithMinUsage;
                     TASK_EXECUTOR.execute(() -> {
                         MidjourneyAccount account = midjourneyAccountService.getOne(Wrappers.lambdaQuery(MidjourneyAccount.class).eq(MidjourneyAccount::getInstanceId, finalAccountWithMinUsage).last("limit 1"));
                         midjourneyAccountService.updateStatus(account.getId());
                     });
-                    submit(user,mode, action, instanceId, param);
+                    if(RETRY_ACTION.contains(action)){
+                        submit(user,mode, action, instanceId, param, null);
+                    }
+                } else if(mode == 1) {
+                    // 重试逻辑,切换供应商域名
+                    if(RETRY_ACTION.contains(action)){
+                        List<MidjourneyApiChannel> midjourneyApiChannels = midjourneyApiChannelMapper.selectList(QueryWrapperUtils.buildWrapper((wrapper) -> {
+                            wrapper.eq(MidjourneyApiChannel::getIsDefault, false);
+                        }));
+                        for (MidjourneyApiChannel midjourneyApiChannel : midjourneyApiChannels) {
+                            submitResult = submit2MjProxy(midjourneyApiChannel.getUrl(), midjourneyApiChannel.getToken(), mode, action, param);
+                            if (submitResult.getCode() == 1 || submitResult.getCode() == 22){
+                                submitResult.setApiChannelId(midjourneyApiChannel.getId());
+                            }else if (submitResult.getCode() == 24) {
+                                JSONObject jsonObject = JSONUtil.parseObj(submitResult.getResult());
+                                throw BusinessRuntimeException.getInstance("prompt包含敏感词:"+jsonObject.getStr("bannedWord"));
+                            }else {
+                                if(submitResult.getCode() == 3){
+                                    log.info("服务商 :{}, 账号短缺", midjourneyApiChannel.getName());
+                                }else {
+                                    log.error("服务商:{}, action:{}, error message:{}", midjourneyApiChannel.getName(), action, submitResult.getDescription());
+                                }
+                            }
+                        }
+                    }
+
                 }
+
                 throw BusinessRuntimeException.getInstance("该任务所属的源Mj账号已被官网封禁或暂时风控。请稍后再试,或重新生成图片");
             }
             if (code == 4) {
@@ -447,7 +495,7 @@ public class MidjourneyServiceImpl implements MidjourneyService {
                 throw BusinessRuntimeException.getInstance("prompt包含敏感词:"+jsonObject.getStr("bannedWord"));
             }
             log.error("action:" + action + " error message:" + submitResult.getDescription());
-            throw BusinessRuntimeException.getInstance("队列已满,请稍后尝试");
+            throw BusinessRuntimeException.getInstance("该任务源Mj账号队列已满,请稍后尝试");
         }
         if (StringUtils.isNotBlank(accountWithMinUsage) && mode == 2) {
             submitResult.setInstanceId(Long.valueOf(accountWithMinUsage));
@@ -455,6 +503,24 @@ public class MidjourneyServiceImpl implements MidjourneyService {
         return submitResult;
     }
 
+    private SubmitResult submit2MjProxy(String url, String token, Integer mode, String action, Map<String, Object> param) throws Exception {
+
+        url = url + getActionUrl(action);
+        param.put("notifyHook",midjourneyHost +(EnvCommonService.active.equals(envCommonService.getEnv()) ? "/8083":"/8082") + "/api/applets/midjourney/notifyHook");
+        HttpRequest request = HttpRequest.post(url).body(Jsons.toJson(param));
+        request = mode == 1 ? request.header("Authorization", token) : request.header("mj-api-secret", token);
+        String body = request.execute().body();
+        log.info("action body:{}", body);
+        SubmitResult submitResult = Jsons.parseObject(body, SubmitResult.class);
+        return submitResult;
+    }
+
+    private void switchSupplierDomain() {
+        // 实现切换供应商域名的逻辑
+        // 例如,重新设置FAST_HOST或RELAX_HOST
+        log.info("Switching supplier domain...");
+    }
+
 
     /**
      * 用户生图次数限制

+ 70 - 0
midjourney/src/main/java/com/yhlxj/service/task/Scheduler.java

@@ -1,17 +1,29 @@
 package com.yhlxj.service.task;//package com.cyksj.task;
 
+import cn.hutool.http.HttpUtil;
+import cn.hutool.json.JSONArray;
+import cn.hutool.json.JSONObject;
 import com.yhlxj.service.midjourney.MidjourneyAccountService;
 import com.yhlxj.service.midjourney.MidjourneyService;
+import lombok.Data;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Component;
 
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
 @Component
 @Slf4j
 @RequiredArgsConstructor
 public class Scheduler {
 
+    public static final Map<String, MjVersionStatus> MJ_VERSION_STATUS_MAP = new ConcurrentHashMap<>();
+
     private final MidjourneyService midjourneyService;
 
     private final MidjourneyAccountService midjourneyAccountService;
@@ -26,6 +38,28 @@ public class Scheduler {
         midjourneyAccountService.syncAccountByMJPlus();
     }
 
+    @Scheduled(cron = "0 0/2 * * * ?")
+    public void getMjStatus() throws Exception {
+        String url = "https://storage.googleapis.com/midjourney-status/status.json";
+        String res = HttpUtil.get(url);
+        JSONObject json = new JSONObject(res);
+        JSONArray metrics = json.getJSONArray("metrics");
+        List<MjVersionStatus> list = metrics.toList(MjVersionStatus.class);
+        for (MjVersionStatus mjVersionStatus : list) {
+            if (mjVersionStatus.getName().contains("jobs.v6")) {
+                // 分类数据并组合键
+                String[] parts = mjVersionStatus.getName().split("\\.");
+                String mainCategory = parts[1];
+                String subCategory = parts[2] + "-" + parts[3];
+                String key = mainCategory + "-" + subCategory;
+                MJ_VERSION_STATUS_MAP
+                        .putIfAbsent(key, mjVersionStatus);
+
+            }
+
+        }
+    }
+
 
     @Scheduled(cron = "0 0/5 * * * ?")
     public void syncMidjourneyStatus() throws Exception {
@@ -34,3 +68,39 @@ public class Scheduler {
 
 }
 
+@Data
+class MjVersionStatus {
+    //{
+    //    "name": "jobs.time_to_start.fast.10min.job_type_kdpt_diffusion_anime",
+    //        "date": "2024-07-11T03:24:01.784Z",
+    //        "value": "N/A"
+    //}
+
+    private String name;
+
+    private String date;
+
+    private String value;
+    /**
+     * 转换为秒
+     */
+    private Long second;
+
+
+    public void setValue(String value) {
+        this.value = value;
+        // 转换为秒并设置 second
+        if (value != null && !value.equals("N/A")) {
+            try {
+                double minutes = Double.parseDouble(value);
+                this.second = Math.round(minutes * 60);
+            } catch (NumberFormatException e) {
+                this.second = null;
+                System.err.println("Invalid value format: " + value);
+            }
+        } else {
+            this.second = null;
+        }
+    }
+
+}

+ 2 - 1
midjourney/src/main/java/com/yhlxj/web/mirror/MidjourneyController.java

@@ -316,6 +316,7 @@ public class MidjourneyController {
             throw BusinessRuntimeException.getInstance("关联任务不存在或已失效");
         }
         Integer mode = midjourneyUserConversation.getMode();
+        Long apiChannelId = midjourneyUserConversation.getApiChannelId();
         Long num = 0L;
         if (!actionDTO.getCustomId().contains("BOOKMARK")) {
             num = checkUserLimit(user,mode);
@@ -325,7 +326,7 @@ public class MidjourneyController {
         }
         SubmitResult conversation;
         try {
-            conversation = midjourneyService.submitAction(user,midjourneyUserConversation,mode, actionDTO.getTaskId(), actionDTO.getCustomId(),num, actionDTO.getBotType());
+            conversation = midjourneyService.submitAction(user,midjourneyUserConversation,mode, actionDTO.getTaskId(), actionDTO.getCustomId(),num, actionDTO.getBotType(), apiChannelId);
         } catch (Exception e) {
             recoverUserLimit(user.getId(), mode,num);
             throw BusinessRuntimeException.getInstance(e.getMessage());