MidjourneyServiceImpl.java 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581
  1. package com.cyksj.service.midjourney.impl;
  2. import cn.hutool.core.collection.CollectionUtil;
  3. import cn.hutool.core.io.FileUtil;
  4. import cn.hutool.core.map.MapUtil;
  5. import cn.hutool.core.util.URLUtil;
  6. import cn.hutool.http.HttpRequest;
  7. import cn.hutool.http.HttpUtil;
  8. import cn.hutool.json.JSONArray;
  9. import cn.hutool.json.JSONObject;
  10. import cn.hutool.json.JSONUtil;
  11. import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
  12. import com.baomidou.mybatisplus.core.toolkit.Wrappers;
  13. import com.cyksj.common.EnvCommonService;
  14. import com.cyksj.common.exception.BusinessRuntimeException;
  15. import com.cyksj.common.task.GlobalThreadPoolTaskExecutor;
  16. import com.cyksj.common.util.Jsons;
  17. import com.cyksj.common.util.StringUtil;
  18. import com.cyksj.mapper.MidjourneyUserConversationMapper;
  19. import com.cyksj.mapper.MidjourneyUserMapper;
  20. import com.cyksj.model.dto.BlendDimensions;
  21. import com.cyksj.model.dto.MessageButton;
  22. import com.cyksj.model.dto.SubmitModalDTO;
  23. import com.cyksj.model.entity.MidjourneyAccount;
  24. import com.cyksj.model.entity.MidjourneyUser;
  25. import com.cyksj.model.entity.MidjourneyUserConversation;
  26. import com.cyksj.model.response.SubmitResult;
  27. import com.cyksj.redis.RedisService;
  28. import com.cyksj.service.midjourney.MidjourneyAccountService;
  29. import com.cyksj.service.midjourney.MidjourneyService;
  30. import lombok.RequiredArgsConstructor;
  31. import lombok.extern.slf4j.Slf4j;
  32. import org.apache.commons.lang3.StringUtils;
  33. import org.springframework.beans.factory.annotation.Value;
  34. import org.springframework.stereotype.Service;
  35. import javax.imageio.stream.FileImageOutputStream;
  36. import java.io.*;
  37. import java.net.URL;
  38. import java.nio.file.Files;
  39. import java.nio.file.Path;
  40. import java.util.*;
  41. import java.util.concurrent.atomic.AtomicBoolean;
  42. import java.util.stream.Collectors;
  43. /**
  44. * @author zwhui
  45. * @date 2024/4/23 15:44
  46. */
  47. @Service
  48. @Slf4j
  49. @RequiredArgsConstructor
  50. public class MidjourneyServiceImpl implements MidjourneyService {
  51. private static final String RELAX_HOST = "http://43.154.230.104:8080";
  52. private static final String FAST_HOST = "https://aigc.api4midjourney.com/api";
  53. private static final String FAST_TOKEN = "14a6b469-7cb2-4b99-9238-676c7c5cffef";
  54. private static final String RELAX_TOKEN = "5a9a3e9b-5a5e-4b6b-9a5a-9a5a5a5a5a5a";
  55. private final MidjourneyUserConversationMapper conversationMapper;
  56. private static final List<String> progress = List.of("100%");
  57. private static final List<String> status = List.of("MODAL","CANCEL","FAILURE");
  58. private final RedisService redisService;
  59. private final MidjourneyUserMapper midjourneyUserMapper;
  60. @Value("${midjourney.url}")
  61. private String midjourneyHost;
  62. private final EnvCommonService envCommonService;
  63. private static final GlobalThreadPoolTaskExecutor TASK_EXECUTOR = GlobalThreadPoolTaskExecutor.getInstance();
  64. private final MidjourneyAccountService midjourneyAccountService;
  65. @Override
  66. public MidjourneyUserConversation submitImagine(MidjourneyUser user, String prompt,String botType, List<String> base64Array) throws Exception {
  67. Map<String, Object> imagineParam = MapUtil.builder(new HashMap<String,Object>())
  68. .put("prompt", prompt)
  69. .put("state", user.getId())
  70. .build();
  71. if (CollectionUtil.isNotEmpty(base64Array)) {
  72. imagineParam.put("base64Array", base64Array);
  73. }
  74. SubmitResult result = submit(user,"imagine", null,imagineParam);
  75. if (user.getMode() == 2) {
  76. redisService.hincr(RedisService.key.MIDJOURNEY_ACCOUNT.getName(), result.getInstanceId().toString(), 1.0);
  77. }
  78. return saveConversation(user.getId(), user.getMode(),result,"IMAGINE", StringUtils.EMPTY, botType);
  79. }
  80. public MidjourneyUserConversation saveConversation(Long userId,Integer mode,SubmitResult result, String action,String prompt, String botType) throws Exception {
  81. Long taskId = Long.parseLong(result.getResult());
  82. Map<String, Object> properties = result.getProperties();
  83. MidjourneyUserConversation conversation = new MidjourneyUserConversation()
  84. .setUserId(userId).setTaskId(taskId).setAction(action).setPrompt(prompt)
  85. .setMode(mode).setStartTime(System.currentTimeMillis()).setProgress("0%")
  86. .setStatus(result.getCode() == 21 ? "NOT_START" : "IN_PROGRESS").setTaskId(taskId);
  87. if (MapUtil.isNotEmpty(properties)) {
  88. conversation.setProperties(Jsons.toJson(properties));
  89. conversation.setChannelId(properties.get("discordChannelId") == null ? null : Long.parseLong(properties.get("discordChannelId").toString()))
  90. .setInstanceId(properties.get("discordInstanceId") == null ? null : Long.parseLong(properties.get("discordInstanceId").toString()));
  91. }
  92. if (conversation.getInstanceId() == null) {
  93. conversation.setInstanceId(result.getInstanceId());
  94. }
  95. conversationMapper.insert(conversation);
  96. return conversation;
  97. }
  98. @Override
  99. public MidjourneyUserConversation submitDescribe(MidjourneyUser user, String botType, String base64) throws Exception {
  100. Map<String, Object> param = MapUtil.builder(new HashMap<String,Object>())
  101. .put("state", user.getId())
  102. .put("base64", base64)
  103. .build();
  104. SubmitResult result = submit(user,"describe", null, param);
  105. if (user.getMode() == 2) {
  106. redisService.hincr(RedisService.key.MIDJOURNEY_ACCOUNT.getName(), result.getInstanceId().toString(), 1.0);
  107. }
  108. return saveConversation(user.getId(), user.getMode(),result,"DESCRIBE", StringUtils.EMPTY, botType);
  109. }
  110. @Override
  111. public MidjourneyUserConversation submitBlend(MidjourneyUser user, BlendDimensions dimensions, String botType, List<String> base64Array) throws Exception {
  112. Map<String, Object> param = MapUtil.builder(new HashMap<String,Object>())
  113. .put("base64Array", base64Array)
  114. .put("state", user.getId())
  115. .build();
  116. if (dimensions != null) {
  117. param.put("dimensions", dimensions);
  118. }
  119. SubmitResult result = submit(user,"blend", null, param);
  120. if (user.getMode() == 2) {
  121. redisService.hincr(RedisService.key.MIDJOURNEY_ACCOUNT.getName(), result.getInstanceId().toString(), 1.0);
  122. }
  123. return saveConversation(user.getId(), user.getMode(),result,"BLEND", StringUtils.EMPTY, botType);
  124. }
  125. @Override
  126. public MidjourneyUserConversation submitModal(MidjourneyUser user, Long taskId, String prompt, String maskBase64) throws Exception {
  127. Map<String, Object> param = MapUtil.builder(new HashMap<String,Object>())
  128. .put("taskId", taskId)
  129. .put("state", user.getId())
  130. .build();
  131. if (StringUtils.isNotBlank(prompt)) {
  132. param.put("prompt", prompt);
  133. }
  134. if (StringUtils.isNotBlank(maskBase64)) {
  135. param.put("maskBase64", maskBase64);
  136. }
  137. SubmitResult result = submit(user,"modal", null, param);
  138. if (user.getMode() == 2) {
  139. redisService.hincr(RedisService.key.MIDJOURNEY_ACCOUNT.getName(), result.getInstanceId().toString(), 1.0);
  140. }
  141. return saveConversation(user.getId(), user.getMode(),result,"MODAL", StringUtils.EMPTY, StringUtils.EMPTY);
  142. }
  143. @Override
  144. public MidjourneyUserConversation submitShorten(MidjourneyUser user, String botType, String prompt) throws Exception {
  145. Map<String, Object> param = MapUtil.builder(new HashMap<String,Object>())
  146. .put("prompt", prompt)
  147. .put("state", user.getId())
  148. .build();
  149. SubmitResult result = submit(user,"shorten", null, param);
  150. if (user.getMode() == 2) {
  151. redisService.hincr(RedisService.key.MIDJOURNEY_ACCOUNT.getName(), result.getInstanceId().toString(), 1.0);
  152. }
  153. return saveConversation(user.getId(), user.getMode(), result,"SHORTEN", StringUtils.EMPTY, botType);
  154. }
  155. /**
  156. * 恢复次数
  157. */
  158. public void recoverUserLimit(Long id,Integer mode,Long num){
  159. if (mode == 1){
  160. redisService.incr(RedisService.key.MIDJOURNEY_FAST_LIMIT.getName() + id, 1L);
  161. midjourneyUserMapper.incrFastNum(id, 1);
  162. }
  163. if (mode == 2){
  164. if (num != null){
  165. redisService.incr(RedisService.key.MIDJOURNEY_RELAX_LIMIT.getName() + id, 1L);
  166. midjourneyUserMapper.incrRelaxNum(id, 1);
  167. }
  168. }
  169. }
  170. // /**
  171. // * 同步数据库
  172. // */
  173. // public void syncUser(Long id,Integer mode,Long num){
  174. // log.info("同步次数 id:{},mode:{},num:{}",id,mode,num);
  175. // if (num == null){
  176. // return;
  177. // }
  178. // LambdaUpdateWrapper<MidjourneyUser> wrapper = Wrappers.lambdaUpdate(MidjourneyUser.class)
  179. // .eq(MidjourneyUser::getId, id);
  180. // if (mode == 1){
  181. // wrapper.set(MidjourneyUser::getMjFastNum, num);
  182. // }
  183. // if (mode == 2){
  184. // wrapper.set(MidjourneyUser::getMjRelaxNum, num);
  185. // }
  186. // midjourneyUserMapper.update(null, wrapper);
  187. // }
  188. @Override
  189. public Object submitAction(MidjourneyUser user, Long taskId, String customId, Long num, String botType) throws Exception {
  190. MidjourneyUserConversation conversation = conversationMapper.selectOne(Wrappers.lambdaQuery(MidjourneyUserConversation.class).eq(MidjourneyUserConversation::getTaskId, taskId).last("limit 1"));
  191. if (conversation == null) {
  192. throw BusinessRuntimeException.getInstance("关联任务不存在或已失效");
  193. }
  194. if (!user.getMode().equals(conversation.getMode())) {
  195. throw BusinessRuntimeException.getInstance("当前出图模式与关联任务出图模式不符");
  196. }
  197. AtomicBoolean modalFlag = new AtomicBoolean(false);
  198. if (StringUtils.isNotBlank(conversation.getButtons())){
  199. List<MessageButton> messageButtons = Jsons.parseList(conversation.getButtons(), MessageButton.class);
  200. messageButtons.forEach(button -> {
  201. if (button.getCustomId().equals(customId)) {
  202. log.info("action customId:{}", customId);
  203. button.setStyle(3);
  204. if (!"Vary (Region)".equals(button.getLabel()) && !"Custom Zoom".equals(button.getLabel()) && !customId.contains("PicReader")) {
  205. modalFlag.set(true);
  206. }
  207. }
  208. });
  209. conversation.setButtons(Jsons.toJson(messageButtons));
  210. }
  211. if (customId.contains("BOOKMARK")) {
  212. conversation.setBookmark(true);
  213. conversationMapper.updateById(conversation);
  214. return null;
  215. }
  216. Map<String, Object> param = MapUtil.builder(new HashMap<String,Object>())
  217. .put("taskId", taskId)
  218. .put("state", user.getId())
  219. .put("customId", customId)
  220. .build();
  221. SubmitResult result = submit(user,"action", conversation.getInstanceId(),param);
  222. if (result.getCode() == 21) {
  223. if (user.getMode() == 2 && modalFlag.get()){
  224. MidjourneyUserConversation modal = submitModal(user, Long.valueOf(result.getResult()), conversation.getPrompt(), null);
  225. log.info("action-modal:{}", modal);
  226. return modal;
  227. }else {
  228. // 以上操作有弹窗确认,恢复次数
  229. recoverUserLimit(user.getId(), user.getMode(),num);
  230. }
  231. }else {
  232. //syncUser(user.getId(), user.getMode(),num);
  233. if (!customId.contains("upsample") && user.getMode() == 2) {
  234. redisService.hincr(RedisService.key.MIDJOURNEY_ACCOUNT.getName(), result.getInstanceId().toString(), 1.0);
  235. }
  236. }
  237. saveConversation(user.getId(), user.getMode(), result,"ACTION", StringUtils.EMPTY, botType);
  238. return result;
  239. }
  240. public SubmitResult submit(MidjourneyUser user,String action,Long instanceId, Map<String, Object> param) throws Exception {
  241. String url = "";
  242. String accountWithMinUsage = "";
  243. Integer mode = user.getMode();
  244. if (mode == 1) {
  245. param.put("mode", "FAST");
  246. url = FAST_HOST;
  247. } else if (mode == 2) {
  248. url = RELAX_HOST;
  249. //慢速查询在使用次数最少的账号
  250. accountWithMinUsage = getAccountWithMinUsage(instanceId);
  251. param.put("accountFilter",MapUtil.builder(new HashMap<String,Object>())
  252. .put("instanceId",accountWithMinUsage).build());
  253. Object customId = param.get("customId");
  254. userSubmitLimit(user,action,customId);
  255. }
  256. url = url + getActionUrl(action);
  257. param.put("notifyHook",midjourneyHost +(EnvCommonService.active.equals(envCommonService.getEnv()) ? "/8081":"/8082") + "/api/applets/midjourney/notifyHook");
  258. HttpRequest request = HttpRequest.post(url).body(Jsons.toJson(param));
  259. request = mode == 1 ? request.header("Authorization", FAST_TOKEN) : request.header("mj-api-secret", RELAX_TOKEN);
  260. String body = request.execute().body();
  261. log.info("action body:{}", body);
  262. SubmitResult submitResult = Jsons.parseObject(body, SubmitResult.class);
  263. int code = submitResult.getCode();
  264. if (code != 1 && code != 21 && code != 22) {
  265. if (code == 3) {
  266. if(mode == 2 && StringUtils.isNotBlank(accountWithMinUsage)){
  267. redisService.hdel(RedisService.key.MIDJOURNEY_ACCOUNT.getName(),accountWithMinUsage);
  268. String finalAccountWithMinUsage = accountWithMinUsage;
  269. TASK_EXECUTOR.execute(() -> {
  270. MidjourneyAccount account = midjourneyAccountService.getOne(Wrappers.lambdaQuery(MidjourneyAccount.class).eq(MidjourneyAccount::getInstanceId, finalAccountWithMinUsage).last("limit 1"));
  271. midjourneyAccountService.updateStatus(account.getId());
  272. });
  273. }
  274. throw BusinessRuntimeException.getInstance("账号不存在");
  275. }
  276. if (code == 4) {
  277. throw BusinessRuntimeException.getInstance(submitResult.getDescription());
  278. }
  279. if (code == 24) {
  280. Map<String, Object> properties = submitResult.getProperties();
  281. throw BusinessRuntimeException.getInstance("prompt包含敏感词:"+properties.get("bannedWord"));
  282. }
  283. log.error("action:" + action + " error message:" + submitResult.getDescription());
  284. throw BusinessRuntimeException.getInstance("队列已满,请稍后尝试");
  285. }
  286. if (StringUtils.isNotBlank(accountWithMinUsage) && mode == 2) {
  287. submitResult.setInstanceId(Long.valueOf(accountWithMinUsage));
  288. }
  289. return submitResult;
  290. }
  291. public String getAccountWithMinUsage(Long instanceId) {
  292. String key = RedisService.key.MIDJOURNEY_ACCOUNT.getName();
  293. try {
  294. // 获取所有账号ID和使用次数
  295. Map<Object, Object> accountsUsage = redisService.hmget(key);
  296. if (accountsUsage == null || accountsUsage.isEmpty()) {
  297. accountsUsage = midjourneyAccountService.list(Wrappers.lambdaQuery(MidjourneyAccount.class).eq(MidjourneyAccount::getStatus,Boolean.TRUE)).stream().collect(Collectors.toMap(k -> k.getInstanceId().toString(), v -> 0));
  298. // 保存所有账号ID和使用次数
  299. redisService.hmset(key, accountsUsage);
  300. }
  301. // 找到使用次数最少的账号ID
  302. String minAccountId = null;
  303. if (instanceId != null) {
  304. minAccountId = instanceId.toString();
  305. }else {
  306. int minUsage = Integer.MAX_VALUE;
  307. for (Map.Entry<Object, Object> entry : accountsUsage.entrySet()) {
  308. int usage = Integer.parseInt(entry.getValue().toString());
  309. if (usage < minUsage) {
  310. minUsage = usage;
  311. minAccountId = entry.getKey().toString();
  312. }
  313. }
  314. }
  315. return minAccountId;
  316. } catch (Exception e) {
  317. throw BusinessRuntimeException.getInstance("获取账号失败");
  318. }
  319. }
  320. private String getActionUrl(String action) {
  321. switch (action) {
  322. case "imagine":
  323. return "/mj/submit/imagine";
  324. case "action":
  325. return "/mj/submit/action";
  326. case "describe":
  327. return "/mj/submit/describe";
  328. case "blend":
  329. return"/mj/submit/blend";
  330. case "modal":
  331. return "/mj/submit/modal";
  332. case "shorten":
  333. return "/mj/submit/shorten";
  334. default: throw new IllegalArgumentException("Unknown action: " + action);
  335. }
  336. }
  337. /**
  338. * 用户生图次数限制
  339. */
  340. public void userSubmitLimit(MidjourneyUser user,String action,Object customId) {
  341. if (StringUtils.equals("modal",action)) {
  342. return;
  343. }
  344. if (customId != null && customId.toString().contains("upsample")) {
  345. return;
  346. }
  347. String key = RedisService.key.MIDJOURNEY_USER_TIME.getName() + user.getId();
  348. Object count = redisService.get(key);
  349. if (count == null) {
  350. redisService.set(key, 1L,RedisService.key.MIDJOURNEY_USER_TIME.getTimeout());
  351. }else {
  352. if (redisService.incr(key, 1L) > 200) {
  353. //24小时内提交次数超过200次 拉黑
  354. user.setIsBlack(true);
  355. midjourneyUserMapper.updateById(user);
  356. }
  357. }
  358. //用户30秒内只能提交一次
  359. if (redisService.hasKey(RedisService.key.MIDJOURNEY_USER_SUBMIT.getName() + user.getId())) {
  360. throw BusinessRuntimeException.getInstance("操作太快了,请等待"+redisService.getExpire(RedisService.key.MIDJOURNEY_USER_SUBMIT.getName() + user.getId())+"秒后再试.为了防止爬虫,让大家有个良好的使用环境,relax模式命令最短时间间隔30秒,fast模式最短间隔5秒");
  361. }else {
  362. redisService.set(RedisService.key.MIDJOURNEY_USER_SUBMIT.getName() + user.getId(), 1L, 30L);
  363. }
  364. }
  365. @Override
  366. public List<MidjourneyUserConversation> listConversationByIds(Integer mode, List<Long> ids) {
  367. List<MidjourneyUserConversation> list = new ArrayList<>();
  368. ids.forEach(id ->{
  369. String queryKey = RedisService.key.MIDJOURNEY_QUERY.getName();
  370. Long count = redisService.incr(queryKey + id, 1L);
  371. if (count%5 == 0) {
  372. try {
  373. listByIds(mode,List.of(id)).forEach(json ->{
  374. JSONObject jsons = JSONUtil.parseObj(json);
  375. MidjourneyUserConversation conversation = JSONUtil.toBean(jsons, MidjourneyUserConversation.class);
  376. if (StringUtil.isNotBlank(conversation.getImageUrl())) {
  377. conversation.setImageUrl(conversation.getImageUrl().replace("cdn.discordapp.com", "cdn.mj.liuliangbang.vip").replace("mj.galaxydvd.com", "cdn.mj.liuliangbang.vip"));
  378. }
  379. conversation.setUserId(jsons.getLong("state"));
  380. conversation.setTaskId(jsons.getLong("id"));
  381. list.add(conversation);
  382. TASK_EXECUTOR.execute(() -> {
  383. try {
  384. sync(conversation);
  385. } catch (IOException e) {
  386. throw new RuntimeException(e);
  387. }
  388. });
  389. });
  390. } catch (Exception e) {
  391. throw new RuntimeException(e);
  392. }
  393. }else {
  394. list.add((MidjourneyUserConversation) redisService.get(RedisService.key.MIDJOURNEY_CONVERSATION.getName() + id));
  395. }
  396. });
  397. return list;
  398. }
  399. public void sync(MidjourneyUserConversation conversation) throws IOException {
  400. if ((StringUtils.isNotBlank(conversation.getProgress()) && progress.contains(conversation.getProgress())) || status.contains(conversation.getStatus())) {
  401. log.info("同步任务:{},进度:{}",conversation.getTaskId(),conversation.getProgress());
  402. Long userId = conversation.getUserId();
  403. MidjourneyUserConversation dbConversation = conversationMapper.selectOne(Wrappers.lambdaQuery(MidjourneyUserConversation.class).eq(MidjourneyUserConversation::getTaskId, conversation.getTaskId())
  404. .eq(MidjourneyUserConversation::getUserId, userId).orderByDesc(MidjourneyUserConversation::getId).last("limit 1"));
  405. if (dbConversation != null) {
  406. if ("SUCCESS".equals(dbConversation.getStatus()) || "FAILURE".equals(dbConversation.getStatus()) || "MODAL".equals(dbConversation.getStatus())) {
  407. return;
  408. }
  409. conversation.setId(dbConversation.getId());
  410. conversationMapper.updateById(conversation);
  411. TASK_EXECUTOR.execute(() -> {
  412. try {
  413. String imageUrl = conversation.getImageUrl();
  414. if (StringUtil.isNotBlank(imageUrl)) {
  415. conversation.setImageUrl(uploadPic(imageUrl, "conversation"+dbConversation.getId()));
  416. conversationMapper.updateById(conversation);
  417. }
  418. } catch (IOException e) {
  419. log.error("上传图片失败",e);
  420. }
  421. });
  422. //失败返还次数
  423. if ("FAILURE".equals(conversation.getStatus())){
  424. if (StringUtils.equals("未知频道",conversation.getFailReason())) {
  425. redisService.hdel(RedisService.key.MIDJOURNEY_ACCOUNT.getName(),dbConversation.getInstanceId());
  426. String finalAccountWithMinUsage = dbConversation.getInstanceId().toString();
  427. TASK_EXECUTOR.execute(() -> {
  428. MidjourneyAccount account = midjourneyAccountService.getOne(Wrappers.lambdaQuery(MidjourneyAccount.class).eq(MidjourneyAccount::getInstanceId, finalAccountWithMinUsage).last("limit 1"));
  429. if (account.getStatus()) {
  430. midjourneyAccountService.updateStatus(account.getId());
  431. }
  432. });
  433. }
  434. Object num = redisService.get(RedisService.key.MIDJOURNEY_RELAX_LIMIT.getName() + userId);
  435. LambdaUpdateWrapper<MidjourneyUser> wrapper = Wrappers.lambdaUpdate(MidjourneyUser.class)
  436. .eq(MidjourneyUser::getId, userId);
  437. Integer mode = dbConversation.getMode();
  438. if (mode == 1){
  439. num = redisService.incr(RedisService.key.MIDJOURNEY_FAST_LIMIT.getName() + userId, 1L);
  440. wrapper.set(MidjourneyUser::getMjFastNum, num);
  441. midjourneyUserMapper.update(null, wrapper);
  442. }
  443. if (mode == 2){
  444. if (num != null) {
  445. num = redisService.incr(RedisService.key.MIDJOURNEY_RELAX_LIMIT.getName() + userId, 1L);
  446. wrapper.set(MidjourneyUser::getMjRelaxNum, num);
  447. midjourneyUserMapper.update(null, wrapper);
  448. }
  449. }
  450. }
  451. }
  452. }
  453. }
  454. private static String uploadPic(String url, String prefix) throws IOException {
  455. if (StringUtil.isNotBlank(url)) {
  456. URL URL = new URL(url);
  457. // 使用 Hutool 提取各个部分并构建新 URL
  458. url = URLUtil.toURI(new URL(URL.getProtocol(), "cdn.mj.liuliangbang.vip", URL.getPort(), URL.getFile()) + "?" + URL.getQuery()).toString();
  459. //
  460. }
  461. byte[] body = HttpUtil.downloadBytes(url);
  462. Path tempFile = Files.createTempFile(prefix, ".png");
  463. File file = tempFile.toFile();
  464. try (FileImageOutputStream imageOutput = new FileImageOutputStream(file)) {
  465. imageOutput.write(body, 0, body.length);
  466. }
  467. Map<String, Object> paramMap = new HashMap<>();
  468. paramMap.put("file", file);
  469. String json = HttpUtil.post("https://files.liuliangbang.vip/pic/ups", paramMap);
  470. log.info("上传图片结果:url:{},json:{}",url, json);
  471. FileUtil.del(file);
  472. return new JSONObject(json).getJSONObject("value").getJSONArray("saved").getJSONObject(0).getJSONObject("info").getStr("cdnUrl");
  473. }
  474. private static List<String> uploadBase64Pic(List<String> base64Array) throws IOException {
  475. List<String> list = new ArrayList<>();
  476. for (String base64 : base64Array) {
  477. String fileExt = "jpeg";
  478. if (base64.contains(";")){
  479. fileExt = base64.split(";")[0].split("/")[1];
  480. base64 = base64.split(",")[1];
  481. }
  482. byte[] body = Base64.getDecoder().decode(base64);
  483. Path tempFile = Files.createTempFile(UUID.randomUUID().toString(), "." + fileExt);
  484. try (FileImageOutputStream imageOutput = new FileImageOutputStream(tempFile.toFile())) {
  485. imageOutput.write(body, 0, body.length);
  486. }
  487. Map<String, Object> paramMap = new HashMap<>();
  488. paramMap.put("file", tempFile.toFile());
  489. String json = HttpUtil.post("https://files.liuliangbang.vip/pic/ups", paramMap);
  490. list.add(new JSONObject(json).getJSONObject("value").getJSONArray("saved").getJSONObject(0).getJSONObject("info").getStr("cdnUrl"));
  491. }
  492. return list;
  493. }
  494. public JSONArray listByIds(Integer mode,List<Long> ids) throws Exception {
  495. Map<String, Object> param = MapUtil.builder(new HashMap<String, Object>()).put("ids", ids).build();
  496. 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();
  497. log.info("listByIds body:{}", body);
  498. return JSONUtil.parseArray(body);
  499. }
  500. @Override
  501. public MidjourneyUserConversation cancelConversation(MidjourneyUser user, Long id) {
  502. MidjourneyUserConversation conversation = conversationMapper.selectOne(Wrappers.lambdaQuery(MidjourneyUserConversation.class).eq(MidjourneyUserConversation::getId, id));
  503. if (conversation == null) {
  504. throw BusinessRuntimeException.getInstance("会话不存在");
  505. }
  506. if (!Objects.equals(conversation.getUserId(), user.getId())) {
  507. throw BusinessRuntimeException.getInstance("不是您的会话");
  508. }
  509. conversation.setStatus("CANCEL");
  510. conversationMapper.updateById(conversation);
  511. TASK_EXECUTOR.execute(() -> {
  512. cancel(user.getMode(),conversation.getTaskId());
  513. });
  514. return conversation;
  515. }
  516. public void cancel(Integer mode,Long taskId) {
  517. String body = HttpUtil.post(mode == 1 ? FAST_HOST : RELAX_HOST + "/mj/task/"+taskId+"/cancel",MapUtil.empty());
  518. log.info("cancel body:{}", body);
  519. JSONObject jsonObject = JSONUtil.parseObj(body);
  520. if (jsonObject.getInt("code") != 1) {
  521. throw BusinessRuntimeException.getInstance("cancel error message:" + jsonObject.getStr("description"));
  522. }
  523. }
  524. @Override
  525. public void notifyHook(String json) {
  526. log.info("notifyHook:{}", json);
  527. JSONObject jsons = JSONUtil.parseObj(json);
  528. MidjourneyUserConversation conversation = JSONUtil.toBean(jsons, MidjourneyUserConversation.class);
  529. conversation.setUserId(jsons.getLong("state"));
  530. conversation.setTaskId(jsons.getLong("id"));
  531. if (StringUtil.isNotBlank(conversation.getImageUrl())) {
  532. conversation.setImageUrl(conversation.getImageUrl().replace("cdn.discordapp.com", "cdn.mj.liuliangbang.vip").replace("mj.galaxydvd.com", "cdn.mj.liuliangbang.vip"));
  533. }
  534. redisService.set(RedisService.key.MIDJOURNEY_CONVERSATION.getName() + conversation.getTaskId(), conversation,RedisService.key.MIDJOURNEY_CONVERSATION.getTimeout());
  535. TASK_EXECUTOR.execute(() -> {
  536. try {
  537. sync(conversation);
  538. } catch (IOException e) {
  539. throw new RuntimeException(e);
  540. }
  541. });
  542. }
  543. }