From 03f124a82b5e2a083c06b39ea44be9c0373e3d36 Mon Sep 17 00:00:00 2001 From: cherishsince Date: Wed, 10 Apr 2024 20:02:06 +0800 Subject: [PATCH] 1、请求公共部分抽离 2、修改为api和spring ai结构保持一致 --- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/api/MidjourneyInteractions.java | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/api/MidjourneyInteractionsApi.java | 148 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/interactions/MidjourneyInteractions.java | 192 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ 3 files changed, 231 insertions(+), 192 deletions(-) create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/api/MidjourneyInteractions.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/api/MidjourneyInteractionsApi.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/interactions/MidjourneyInteractions.java diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/api/MidjourneyInteractions.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/api/MidjourneyInteractions.java new file mode 100644 index 0000000..4077912 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/api/MidjourneyInteractions.java @@ -0,0 +1,83 @@ +package cn.iocoder.yudao.framework.ai.midjourney.api; + +import cn.hutool.core.util.IdUtil; +import cn.iocoder.yudao.framework.ai.midjourney.MidjourneyConfig; +import cn.iocoder.yudao.framework.ai.midjourney.constants.MidjourneyConstants; +import com.google.common.collect.Maps; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; + +import java.util.HashMap; + +// TODO @fansili:按照 spring ai 的封装习惯,这个类是不是 MidjourneyApi + +/** + * 图片生成 + * + * author: fansili + * time: 2024/4/3 17:36 + */ +@Slf4j +public abstract class MidjourneyInteractions { + + // TODO done @fansili:静态变量,放在最前面哈; + /** + * header - referer 头信息 + */ + private static final String HEADER_REFERER = "https://discord.com/channels/%s/%s"; + /** + * mj配置文件 + */ + protected final MidjourneyConfig midjourneyConfig; + + protected MidjourneyInteractions(MidjourneyConfig midjourneyConfig) { + this.midjourneyConfig = midjourneyConfig; + } + + /** + * 获取headers - application json + * + * @return + */ + protected HttpHeaders getHeadersOfAppJson() { + // 设置header值 + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setContentType(MediaType.APPLICATION_JSON); + httpHeaders.set("Authorization", midjourneyConfig.getToken()); + httpHeaders.set("User-Agent", midjourneyConfig.getUserAage()); + httpHeaders.set("Cookie", MidjourneyConstants.HTTP_COOKIE); + httpHeaders.set("Referer", String.format(HEADER_REFERER, midjourneyConfig.getGuildId(), midjourneyConfig.getChannelId())); + return httpHeaders; + } + + /** + * 获取headers - http form data + * + * @return + */ + protected HttpHeaders getHeadersOfFormData() { + // 设置header值 + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setContentType(MediaType.MULTIPART_FORM_DATA); + httpHeaders.set("Authorization", midjourneyConfig.getToken()); + httpHeaders.set("User-Agent", midjourneyConfig.getUserAage()); + httpHeaders.set("Cookie", MidjourneyConstants.HTTP_COOKIE); + httpHeaders.set("Referer", String.format(HEADER_REFERER, midjourneyConfig.getGuildId(), midjourneyConfig.getChannelId())); + return httpHeaders; + } + + /** + * 获取 - 默认参数 + * @return + */ + protected HashMap getDefaultParams() { + HashMap requestParams = Maps.newHashMap(); + // TODO @fansili:感觉参数的组装,可以搞成一个公用的方法;就是 config + 入参的感觉; + requestParams.put("guild_id", midjourneyConfig.getGuildId()); + requestParams.put("channel_id", midjourneyConfig.getChannelId()); + requestParams.put("session_id", midjourneyConfig.getSessionId()); + requestParams.put("nonce", String.valueOf(IdUtil.getSnowflakeNextId())); // TODO @fansili:建议用 uuid 之类的;nextId 跨进程未必合适哈; + return requestParams; + } +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/api/MidjourneyInteractionsApi.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/api/MidjourneyInteractionsApi.java new file mode 100644 index 0000000..f71a816 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/api/MidjourneyInteractionsApi.java @@ -0,0 +1,148 @@ +package cn.iocoder.yudao.framework.ai.midjourney.api; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.ai.midjourney.MidjourneyConfig; +import cn.iocoder.yudao.framework.ai.midjourney.util.MidjourneyUtil; +import cn.iocoder.yudao.framework.ai.midjourney.api.req.AttachmentsReq; +import cn.iocoder.yudao.framework.ai.midjourney.api.req.DescribeReq; +import cn.iocoder.yudao.framework.ai.midjourney.api.req.ReRollReq; +import cn.iocoder.yudao.framework.ai.midjourney.api.res.UploadAttachmentsRes; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.google.common.collect.Lists; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.io.FileSystemResource; +import org.springframework.http.*; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; + +import java.io.IOException; +import java.util.HashMap; + +// TODO @fansili:按照 spring ai 的封装习惯,这个类是不是 MidjourneyApi +/** + * 图片生成 + * + * author: fansili + * time: 2024/4/3 17:36 + */ +@Slf4j +public class MidjourneyInteractionsApi extends MidjourneyInteractions { + + private final String url; + private final RestTemplate restTemplate = new RestTemplate(); // TODO @fansili:优先级低:后续搞到统一的管理 + + public MidjourneyInteractionsApi(MidjourneyConfig midjourneyConfig) { + super(midjourneyConfig); + this.url = midjourneyConfig.getServerUrl().concat(midjourneyConfig.getApiInteractions()); + } + + public Boolean imagine(String prompt) { + // 获取请求模板 + String requestTemplate = midjourneyConfig.getRequestTemplates().get("imagine"); + // 设置参数 + HashMap requestParams = getDefaultParams(); + requestParams.put("prompt", prompt); + // 解析 template 参数占位符 + String requestBody = MidjourneyUtil.parseTemplate(requestTemplate, requestParams); + // 获取 header + HttpHeaders httpHeaders = getHeadersOfAppJson(); + // 发送请求 + HttpEntity requestEntity = new HttpEntity<>(requestBody, httpHeaders); + String res = restTemplate.postForObject(url, requestEntity, String.class); + // 这个 res 只要不返回值,就是成功! + // TODO @fansili:可以直接 if (StrUtil.isBlank(res)) + if (StrUtil.isBlank(res)) { + return true; + } else { + log.error("请求失败! 请求参数:{} 返回结果! {}", requestBody, res); + return false; + } + } + // TODO done @fansili:方法和方法之间,空一行哈; + + + public Boolean reRoll(ReRollReq reRoll) { + // 获取请求模板 + String requestTemplate = midjourneyConfig.getRequestTemplates().get("reroll"); + // 设置参数 + HashMap requestParams = getDefaultParams(); + requestParams.put("custom_id", reRoll.getCustomId()); + requestParams.put("message_id", reRoll.getMessageId()); + // 获取 header + HttpHeaders httpHeaders = getHeadersOfAppJson(); + // 设置参数 + String requestBody = MidjourneyUtil.parseTemplate(requestTemplate, requestParams); + // 发送请求 + HttpEntity requestEntity = new HttpEntity<>(requestBody, httpHeaders); + String res = restTemplate.postForObject(url, requestEntity, String.class); + // 这个 res 只要不返回值,就是成功! + boolean isSuccess = StrUtil.isBlank(res); + if (isSuccess) { + return true; + } + log.error("请求失败! 请求参数:{} 返回结果! {}", requestBody, res); + return isSuccess; + } + + // TODO @fansili:搞成私有方法,可能会好点; + public UploadAttachmentsRes uploadAttachments(AttachmentsReq attachments) { + // file + JSONObject fileObj = new JSONObject(); + fileObj.put("id", "0"); + fileObj.put("filename", attachments.getFileSystemResource().getFilename()); + // TODO @fansili:这块用 lombok 哪个异常处理,简化下代码; + try { + fileObj.put("file_size", attachments.getFileSystemResource().contentLength()); + } catch (IOException e) { + throw new RuntimeException(e); + } + // 创建用于存放表单数据的MultiValueMap + MultiValueMap multipartRequest = new LinkedMultiValueMap<>(); + multipartRequest.put("files", Lists.newArrayList(fileObj)); + // 设置header值 + HttpHeaders httpHeaders = getHeadersOfAppJson(); + // 创建HttpEntity对象,包含表单数据和头部信息 + HttpEntity> multiValueMapHttpEntity = new HttpEntity<>(multipartRequest, httpHeaders); + // 发送POST请求并接收响应 + String uri = String.format(midjourneyConfig.getApiAttachments(), midjourneyConfig.getChannelId()); + String response = restTemplate.postForObject(midjourneyConfig.getServerUrl().concat(uri), multiValueMapHttpEntity, String.class); + UploadAttachmentsRes uploadAttachmentsRes = JSON.parseObject(response, UploadAttachmentsRes.class); + + // + // 上传文件 + String uploadUrl = uploadAttachmentsRes.getAttachments().getFirst().getUploadUrl(); + httpHeaders.setContentType(MediaType.MULTIPART_FORM_DATA); + HttpEntity fileSystemResourceHttpEntity = new HttpEntity<>(attachments.getFileSystemResource(), httpHeaders); + ResponseEntity exchange = restTemplate.exchange(uploadUrl, HttpMethod.PUT, fileSystemResourceHttpEntity, String.class); + String uploadRes = exchange.getBody(); + return uploadAttachmentsRes; + } + + public Boolean describe(DescribeReq describe) { + // 获取请求模板 + String requestTemplate = midjourneyConfig.getRequestTemplates().get("describe"); + // 设置参数 + HashMap requestParams = getDefaultParams(); + requestParams.put("file_name", describe.getFileName()); + requestParams.put("final_file_name", describe.getFinalFileName()); + // 设置 header + HttpHeaders httpHeaders = getHeadersOfFormData(); + String requestBody = MidjourneyUtil.parseTemplate(requestTemplate, requestParams); + // 创建表单数据 + MultiValueMap formData = new LinkedMultiValueMap<>(); + formData.add("payload_json", requestBody); + // 发送请求 + HttpEntity> multiValueMapHttpEntity = new HttpEntity<>(formData, httpHeaders); + String res = restTemplate.postForObject(url, multiValueMapHttpEntity, String.class); + // 这个 res 只要不返回值,就是成功! + boolean isSuccess = StrUtil.isBlank(res); + if (isSuccess) { + return true; + } + log.error("请求失败! 请求参数:{} 返回结果! {}", requestBody, res); + return isSuccess; + } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/interactions/MidjourneyInteractions.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/interactions/MidjourneyInteractions.java deleted file mode 100644 index 104e1dd..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/interactions/MidjourneyInteractions.java +++ /dev/null @@ -1,192 +0,0 @@ -package cn.iocoder.yudao.framework.ai.midjourney.interactions; - -import cn.hutool.core.util.IdUtil; -import cn.hutool.core.util.StrUtil; -import cn.iocoder.yudao.framework.ai.midjourney.MidjourneyConfig; -import cn.iocoder.yudao.framework.ai.midjourney.constants.MidjourneyConstants; -import cn.iocoder.yudao.framework.ai.midjourney.util.MidjourneyUtil; -import cn.iocoder.yudao.framework.ai.midjourney.vo.Attachments; -import cn.iocoder.yudao.framework.ai.midjourney.vo.Describe; -import cn.iocoder.yudao.framework.ai.midjourney.vo.ReRoll; -import cn.iocoder.yudao.framework.ai.midjourney.vo.UploadAttachmentsRes; -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.JSONObject; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import lombok.extern.slf4j.Slf4j; -import org.jetbrains.annotations.NotNull; -import org.springframework.core.io.FileSystemResource; -import org.springframework.http.*; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; -import org.springframework.web.client.RestTemplate; - -import java.io.IOException; -import java.util.HashMap; - -// TODO @fansili:按照 spring ai 的封装习惯,这个类是不是 MidjourneyApi -/** - * 图片生成 - * - * author: fansili - * time: 2024/4/3 17:36 - */ -@Slf4j -public class MidjourneyInteractions { - - // TODO done @fansili:静态变量,放在最前面哈; - private static final String HEADER_REFERER = "https://discord.com/channels/%s/%s"; - - private final String url; - private final MidjourneyConfig midjourneyConfig; - private final RestTemplate restTemplate = new RestTemplate(); // TODO @fansili:优先级低:后续搞到统一的管理 - - - public MidjourneyInteractions(MidjourneyConfig midjourneyConfig) { - this.midjourneyConfig = midjourneyConfig; - this.url = midjourneyConfig.getServerUrl().concat(midjourneyConfig.getApiInteractions()); - } - - public Boolean imagine(String prompt) { - // 获取请求模板 - String requestTemplate = midjourneyConfig.getRequestTemplates().get("imagine"); - // 设置参数 - HashMap requestParams = Maps.newHashMap(); - // TODO @fansili:感觉参数的组装,可以搞成一个公用的方法;就是 config + 入参的感觉; - requestParams.put("guild_id", midjourneyConfig.getGuildId()); - requestParams.put("channel_id", midjourneyConfig.getChannelId()); - requestParams.put("session_id", midjourneyConfig.getSessionId()); - requestParams.put("nonce", String.valueOf(IdUtil.getSnowflakeNextId())); // TODO @fansili:建议用 uuid 之类的;nextId 跨进程未必合适哈; - requestParams.put("prompt", prompt); - // 解析 template 参数占位符 - String requestBody = MidjourneyUtil.parseTemplate(requestTemplate, requestParams); - // 获取 header - HttpHeaders httpHeaders = getHttpHeaders(); - // 发送请求 - HttpEntity requestEntity = new HttpEntity<>(requestBody, httpHeaders); - String res = restTemplate.postForObject(url, requestEntity, String.class); - // 这个 res 只要不返回值,就是成功! - // TODO @fansili:可以直接 if (StrUtil.isBlank(res)) - if (StrUtil.isBlank(res)) { - return true; - } else { - log.error("请求失败! 请求参数:{} 返回结果! {}", requestBody, res); - return false; - } - } - // TODO done @fansili:方法和方法之间,空一行哈; - - - public Boolean reRoll(ReRoll reRoll) { - // 获取请求模板 - String requestTemplate = midjourneyConfig.getRequestTemplates().get("reroll"); - // 设置参数 - HashMap requestParams = Maps.newHashMap(); - requestParams.put("guild_id", midjourneyConfig.getGuildId()); - requestParams.put("channel_id", midjourneyConfig.getChannelId()); - requestParams.put("session_id", midjourneyConfig.getSessionId()); - requestParams.put("nonce", String.valueOf(IdUtil.getSnowflakeNextId())); - requestParams.put("custom_id", reRoll.getCustomId()); - requestParams.put("message_id", reRoll.getMessageId()); - // 获取 header - HttpHeaders httpHeaders = getHttpHeaders(); - // 设置参数 - String requestBody = MidjourneyUtil.parseTemplate(requestTemplate, requestParams); - // 发送请求 - HttpEntity requestEntity = new HttpEntity<>(requestBody, httpHeaders); - String res = restTemplate.postForObject(url, requestEntity, String.class); - // 这个 res 只要不返回值,就是成功! - boolean isSuccess = StrUtil.isBlank(res); - if (isSuccess) { - return true; - } - log.error("请求失败! 请求参数:{} 返回结果! {}", requestBody, res); - return isSuccess; - } - - // TODO @fansili:搞成私有方法,可能会好点; - public UploadAttachmentsRes uploadAttachments(Attachments attachments) { - // file - JSONObject fileObj = new JSONObject(); - fileObj.put("id", "0"); - fileObj.put("filename", attachments.getFileSystemResource().getFilename()); - // TODO @fansili:这块用 lombok 哪个异常处理,简化下代码; - try { - fileObj.put("file_size", attachments.getFileSystemResource().contentLength()); - } catch (IOException e) { - throw new RuntimeException(e); - } - // 创建用于存放表单数据的MultiValueMap - MultiValueMap multipartRequest = new LinkedMultiValueMap<>(); - multipartRequest.put("files", Lists.newArrayList(fileObj)); - // 设置header值 - HttpHeaders httpHeaders = new HttpHeaders(); - // TODO @fansili:通用的 header 构建,抽一个方法哈; - httpHeaders.setContentType(MediaType.APPLICATION_JSON); - httpHeaders.set("Authorization", midjourneyConfig.getToken()); - httpHeaders.set("User-Agent", midjourneyConfig.getUserAage()); - httpHeaders.set("Cookie", MidjourneyConstants.HTTP_COOKIE); - httpHeaders.set("Referer", String.format(HEADER_REFERER, midjourneyConfig.getGuildId(), midjourneyConfig.getChannelId())); - // 创建HttpEntity对象,包含表单数据和头部信息 - HttpEntity> multiValueMapHttpEntity = new HttpEntity<>(multipartRequest, httpHeaders); - // 发送POST请求并接收响应 - String uri = String.format(midjourneyConfig.getApiAttachments(), midjourneyConfig.getChannelId()); - String response = restTemplate.postForObject(midjourneyConfig.getServerUrl().concat(uri), multiValueMapHttpEntity, String.class); - UploadAttachmentsRes uploadAttachmentsRes = JSON.parseObject(response, UploadAttachmentsRes.class); - - // - // 上传文件 - String uploadUrl = uploadAttachmentsRes.getAttachments().getFirst().getUploadUrl(); - httpHeaders.setContentType(MediaType.MULTIPART_FORM_DATA); - HttpEntity fileSystemResourceHttpEntity = new HttpEntity<>(attachments.getFileSystemResource(), httpHeaders); - ResponseEntity exchange = restTemplate.exchange(uploadUrl, HttpMethod.PUT, fileSystemResourceHttpEntity, String.class); - String uploadRes = exchange.getBody(); - return uploadAttachmentsRes; - } - - public Boolean describe(Describe describe) { - // 获取请求模板 - String requestTemplate = midjourneyConfig.getRequestTemplates().get("describe"); - // 设置参数 - HashMap requestParams = Maps.newHashMap(); - requestParams.put("guild_id", midjourneyConfig.getGuildId()); - requestParams.put("channel_id", midjourneyConfig.getChannelId()); - requestParams.put("session_id", midjourneyConfig.getSessionId()); - requestParams.put("nonce", String.valueOf(IdUtil.getSnowflakeNextId())); - requestParams.put("file_name", describe.getFileName()); - requestParams.put("final_file_name", describe.getFinalFileName()); - // 设置 header - HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.setContentType(MediaType.MULTIPART_FORM_DATA); // 设置内容类型为JSON - httpHeaders.set("Authorization", midjourneyConfig.getToken()); - httpHeaders.set("User-Agent", midjourneyConfig.getUserAage()); - httpHeaders.set("Cookie", MidjourneyConstants.HTTP_COOKIE); - httpHeaders.set("Referer", String.format(HEADER_REFERER, midjourneyConfig.getGuildId(), midjourneyConfig.getChannelId())); - String requestBody = MidjourneyUtil.parseTemplate(requestTemplate, requestParams); - // 创建表单数据 - MultiValueMap formData = new LinkedMultiValueMap<>(); - formData.add("payload_json", requestBody); - // 发送请求 - HttpEntity> multiValueMapHttpEntity = new HttpEntity<>(formData, httpHeaders); - String res = restTemplate.postForObject(url, multiValueMapHttpEntity, String.class); - // 这个 res 只要不返回值,就是成功! - boolean isSuccess = StrUtil.isBlank(res); - if (isSuccess) { - return true; - } - log.error("请求失败! 请求参数:{} 返回结果! {}", requestBody, res); - return isSuccess; - } - - @NotNull - private HttpHeaders getHttpHeaders() { - HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.setContentType(MediaType.APPLICATION_JSON); // 设置内容类型为JSON - httpHeaders.set("Authorization", midjourneyConfig.getToken()); - httpHeaders.set("User-Agent", midjourneyConfig.getUserAage()); - httpHeaders.set("Cookie", MidjourneyConstants.HTTP_COOKIE); - httpHeaders.set("Referer", String.format(HEADER_REFERER, midjourneyConfig.getGuildId(), midjourneyConfig.getChannelId())); - return httpHeaders; - } - -} -- libgit2 0.21.4