From ea1d9f0075f9dc5f00d7e9b26180140856f4ce09 Mon Sep 17 00:00:00 2001 From: xiaoxin <718949661@qq.com> Date: Sun, 9 Mar 2025 21:43:45 +0800 Subject: [PATCH 01/36] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E3=80=91AI=EF=BC=9A=E6=96=87=E5=A4=9A=E5=A4=9A?= =?UTF-8?q?=E5=AF=B9=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ai/core/model/wenduoduo/api/WddApi.java | 449 ++++++++++++++++++ 1 file changed, 449 insertions(+) create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/wenduoduo/api/WddApi.java diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/wenduoduo/api/WddApi.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/wenduoduo/api/WddApi.java new file mode 100644 index 0000000000..b5467002f4 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/wenduoduo/api/WddApi.java @@ -0,0 +1,449 @@ +package cn.iocoder.yudao.framework.ai.core.model.wenduoduo.api; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpRequest; +import org.springframework.http.HttpStatusCode; +import org.springframework.http.MediaType; +import org.springframework.web.reactive.function.client.ClientResponse; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; + +import java.util.Map; +import java.util.function.Function; +import java.util.function.Predicate; + +/** + * 文多多 API + *

+ *

+ * * 对接文多多:PPT 生成 API + * + * @author xiaoxin + */ +@Slf4j +public class WddApi { + + public static final String BASE_URL = "https://docmee.cn"; + + private final WebClient webClient; + + private final Predicate STATUS_PREDICATE = status -> !status.is2xxSuccessful(); + + private final Function>> EXCEPTION_FUNCTION = + reqParam -> response -> response.bodyToMono(String.class).handle((responseBody, sink) -> { + HttpRequest request = response.request(); + log.error("[wdd-api] 调用失败!请求方式:[{}],请求地址:[{}],请求参数:[{}],响应数据: [{}]", + request.getMethod(), request.getURI(), reqParam, responseBody); + sink.error(new IllegalStateException("[wdd-api] 调用失败!")); + }); + + public WddApi(String baseUrl) { + this.webClient = WebClient.builder() + .baseUrl(baseUrl) + .defaultHeaders((headers) -> headers.setContentType(MediaType.APPLICATION_JSON)) + .build(); + } + + /** + * 创建API令牌 + * + * @param apiKey API密钥 + * @param uid 用户ID + * @param limit 限制 + * @return API令牌 + */ + public String createApiToken(String apiKey, String uid, Integer limit) { + CreateApiTokenRequest request = new CreateApiTokenRequest(uid, limit); + return this.webClient.post() + .uri("/api/user/createApiToken") + .header("Api-Key", apiKey) + .body(Mono.just(request), CreateApiTokenRequest.class) + .retrieve() + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) + .bodyToMono(ApiResponse.class) + .handle((response, sink) -> { + if (response.code != 0) { + sink.error(new IllegalStateException("创建apiToken异常," + response.message)); + return; + } + sink.next(response.data.get("token").toString()); + }) + .block(); + } + + /** + * 解析文件数据 + * + * @param apiToken API令牌 + * @param content 内容 + * @param fileUrl 文件URL + * @return 数据URL + */ + public String parseFileData(String apiToken, String content, String fileUrl) { + ParseFileDataRequest request = new ParseFileDataRequest(content, fileUrl); + return this.webClient.post() + .uri("/api/ppt/parseFileData") + .header("token", apiToken) + .body(Mono.just(request), ParseFileDataRequest.class) + .retrieve() + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) + .bodyToMono(ApiResponse.class) + .handle((response, sink) -> { + if (response.code != 0) { + sink.error(new IllegalStateException("解析文件或内容异常," + response.message)); + return; + } + sink.next(response.data.get("dataUrl").toString()); + }) + .block(); + } + + /** + * 生成大纲 + * + * @param apiToken API令牌 + * @param subject 主题 + * @param dataUrl 数据URL + * @param prompt 提示词 + * @return 大纲内容 + */ + public String generateOutline(String apiToken, String subject, String dataUrl, String prompt) { + GenerateOutlineRequest request = new GenerateOutlineRequest(subject, dataUrl, prompt); + return this.webClient.post() + .uri("/api/ppt/generateOutline") + .header("token", apiToken) + .body(Mono.just(request), GenerateOutlineRequest.class) + .retrieve() + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) + .bodyToMono(String.class) + .block(); + } + + /** + * 生成大纲内容 + * + * @param apiToken API令牌 + * @param outlineMarkdown 大纲Markdown + * @param dataUrl 数据URL + * @param prompt 提示词 + * @return 大纲内容 + */ + public String generateContent(String apiToken, String outlineMarkdown, String dataUrl, String prompt) { + GenerateContentRequest request = new GenerateContentRequest(outlineMarkdown, dataUrl, prompt); + return this.webClient.post() + .uri("/api/ppt/generateContent") + .header("token", apiToken) + .body(Mono.just(request), GenerateContentRequest.class) + .retrieve() + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) + .bodyToMono(String.class) + .block(); + } + + /** + * 异步生成大纲内容 + * + * @param apiToken API令牌 + * @param outlineMarkdown 大纲Markdown + * @param dataUrl 数据URL + * @param templateId 模板ID + * @param prompt 提示词 + * @return 大纲内容和PPT ID + */ + public Map asyncGenerateContent(String apiToken, String outlineMarkdown, String dataUrl, String templateId, String prompt) { + AsyncGenerateContentRequest request = new AsyncGenerateContentRequest(outlineMarkdown, dataUrl, templateId, prompt); + return this.webClient.post() + .uri("/api/ppt/generateContent") + .header("token", apiToken) + .body(Mono.just(request), AsyncGenerateContentRequest.class) + .retrieve() + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) + .bodyToMono(new ParameterizedTypeReference>() { + }) + .block(); + } + + /** + * 随机获取一个模板ID + * + * @param apiToken API令牌 + * @return 模板ID + */ + public String randomOneTemplateId(String apiToken) { + RandomTemplateRequest request = new RandomTemplateRequest(1, new TemplateFilter(1)); + return this.webClient.post() + .uri("/api/ppt/randomTemplates") + .header("token", apiToken) + .body(Mono.just(request), RandomTemplateRequest.class) + .retrieve() + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) + .bodyToMono(ApiResponse.class) + .handle((response, sink) -> { + if (response.code != 0) { + sink.error(new IllegalStateException("获取模板异常," + response.message)); + return; + } + sink.next(((Map) ((Object[]) response.data.get("data"))[0]).get("id").toString()); + }) + .block(); + } + + /** + * 生成PPT + * + * @param apiToken API令牌 + * @param templateId 模板ID + * @param markdown Markdown内容 + * @param pptxProperty PPT属性 + * @return PPT信息 + */ + public Map generatePptx(String apiToken, String templateId, String markdown, boolean pptxProperty) { + GeneratePptxRequest request = new GeneratePptxRequest(templateId, markdown, pptxProperty); + return this.webClient.post() + .uri("/api/ppt/generatePptx") + .header("token", apiToken) + .body(Mono.just(request), GeneratePptxRequest.class) + .retrieve() + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) + .bodyToMono(ApiResponse.class) + .>handle((response, sink) -> { + if (response.code != 0) { + sink.error(new IllegalStateException("生成PPT异常," + response.message)); + return; + } + sink.next((Map) response.data.get("pptInfo")); + }) + .block(); + } + + /** + * 下载PPT + * + * @param apiToken API令牌 + * @param id PPT ID + * @return 下载信息 + */ + public Map downloadPptx(String apiToken, String id) { + DownloadPptxRequest request = new DownloadPptxRequest(id); + return this.webClient.post() + .uri("/api/ppt/downloadPptx") + .header("token", apiToken) + .body(Mono.just(request), DownloadPptxRequest.class) + .retrieve() + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) + .bodyToMono(ApiResponse.class) + .>handle((response, sink) -> { + if (response.code != 0) { + sink.error(new IllegalStateException("下载PPT异常," + response.message)); + return; + } + sink.next(response.data); + }) + .block(); + } + + /** + * 直接生成PPT + * + * @param apiToken API令牌 + * @param templateId 模板ID + * @param subject 主题 + * @param dataUrl 数据URL + * @param prompt 提示词 + * @param pptxProperty PPT属性 + * @return PPT信息 + */ + public Map directGeneratePptx(String apiToken, String templateId, String subject, String dataUrl, String prompt, boolean pptxProperty) { + DirectGeneratePptxRequest request = new DirectGeneratePptxRequest(false, templateId, subject, dataUrl, prompt, pptxProperty); + return this.webClient.post() + .uri("/api/ppt/directGeneratePptx") + .header("token", apiToken) + .body(Mono.just(request), DirectGeneratePptxRequest.class) + .retrieve() + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) + .bodyToMono(ApiResponse.class) + .>handle((response, sink) -> { + if (response.code != 0) { + sink.error(new IllegalStateException("生成PPT异常," + response.message)); + return; + } + sink.next((Map) response.data.get("pptInfo")); + }) + .block(); + } + + /** + * 查询所有PPT列表 + * + * @param apiToken API令牌 + * @param body 请求体 + * @return PPT列表 + */ + public Map listAllPptx(String apiToken, String body) { + return this.webClient.post() + .uri("/api/ppt/listAllPptx") + .header("token", apiToken) + .bodyValue(body) + .retrieve() + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(body)) + .bodyToMono(ApiResponse.class) + .>handle((response, sink) -> { + if (response.code != 0) { + sink.error(new IllegalStateException("查询所有PPT列表异常," + response.message)); + return; + } + sink.next(response.data); + }) + .block(); + } + + /** + * 分页查询PPT模板 + * + * @param apiToken API令牌 + * @param body 请求体 + * @return 模板列表 + */ + public Map getPptTemplates(String apiToken, String body) { + return this.webClient.post() + .uri("/api/ppt/templates") + .header("token", apiToken) + .bodyValue(body) + .retrieve() + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(body)) + .bodyToMono(ApiResponse.class) + .>handle((response, sink) -> { + if (response.code != 0) { + sink.error(new IllegalStateException("分页查询PPT模板异常," + response.message)); + return; + } + sink.next(response.data); + }) + .block(); + } + + /** + * API响应 + */ + @JsonInclude(value = JsonInclude.Include.NON_NULL) + public record ApiResponse( + Integer code, + String message, + Map data + ) { + } + + /** + * 创建API令牌请求 + */ + @JsonInclude(value = JsonInclude.Include.NON_NULL) + public record CreateApiTokenRequest( + String uid, + Integer limit + ) { + } + + /** + * 解析文件数据请求 + */ + @JsonInclude(value = JsonInclude.Include.NON_NULL) + public record ParseFileDataRequest( + String content, + String fileUrl + ) { + } + + /** + * 生成大纲请求 + */ + @JsonInclude(value = JsonInclude.Include.NON_NULL) + public record GenerateOutlineRequest( + String subject, + String dataUrl, + String prompt + ) { + } + + /** + * 生成大纲内容请求 + */ + @JsonInclude(value = JsonInclude.Include.NON_NULL) + public record GenerateContentRequest( + String outlineMarkdown, + String dataUrl, + String prompt + ) { + } + + /** + * 异步生成大纲内容请求 + */ + @JsonInclude(value = JsonInclude.Include.NON_NULL) + public record AsyncGenerateContentRequest( + String outlineMarkdown, + String dataUrl, + String templateId, + String prompt, + @JsonProperty("asyncGenPptx") boolean asyncGenPptx + ) { + public AsyncGenerateContentRequest(String outlineMarkdown, String dataUrl, String templateId, String prompt) { + this(outlineMarkdown, dataUrl, templateId, prompt, true); + } + } + + /** + * 随机模板请求 + */ + @JsonInclude(value = JsonInclude.Include.NON_NULL) + public record RandomTemplateRequest( + Integer size, + TemplateFilter filters + ) { + } + + /** + * 模板过滤器 + */ + @JsonInclude(value = JsonInclude.Include.NON_NULL) + public record TemplateFilter( + Integer type + ) { + } + + /** + * 生成PPT请求 + */ + @JsonInclude(value = JsonInclude.Include.NON_NULL) + public record GeneratePptxRequest( + String templateId, + @JsonProperty("outlineContentMarkdown") String outlineContentMarkdown, + boolean pptxProperty + ) { + } + + /** + * 下载PPT请求 + */ + @JsonInclude(value = JsonInclude.Include.NON_NULL) + public record DownloadPptxRequest( + String id + ) { + } + + /** + * 直接生成PPT请求 + */ + @JsonInclude(value = JsonInclude.Include.NON_NULL) + public record DirectGeneratePptxRequest( + boolean stream, + String templateId, + String subject, + String dataUrl, + String prompt, + boolean pptxProperty + ) { + } +} \ No newline at end of file From a82abed2b56f4c1bc07e8905b582bb4f3c7634b6 Mon Sep 17 00:00:00 2001 From: xiaoxin <718949661@qq.com> Date: Sat, 15 Mar 2025 22:38:09 +0800 Subject: [PATCH 02/36] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E3=80=91AI=EF=BC=9A=E5=AF=B9=E6=8E=A5=E6=96=87?= =?UTF-8?q?=E5=A4=9A=E5=A4=9A=20v2=20=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ai/core/model/wenduoduo/api/WddApi.java | 510 ++++++++---------- 1 file changed, 217 insertions(+), 293 deletions(-) diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/wenduoduo/api/WddApi.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/wenduoduo/api/WddApi.java index b5467002f4..66583f50af 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/wenduoduo/api/WddApi.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/wenduoduo/api/WddApi.java @@ -1,17 +1,26 @@ package cn.iocoder.yudao.framework.ai.core.model.wenduoduo.api; +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; +import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; import lombok.extern.slf4j.Slf4j; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpRequest; import org.springframework.http.HttpStatusCode; import org.springframework.http.MediaType; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.client.ClientResponse; import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import java.time.LocalDateTime; +import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.function.Function; import java.util.function.Predicate; @@ -47,26 +56,24 @@ public class WddApi { .build(); } + /** - * 创建API令牌 + * 创建 token * - * @param apiKey API密钥 - * @param uid 用户ID - * @param limit 限制 - * @return API令牌 + * @param request 请求信息 + * @return token */ - public String createApiToken(String apiKey, String uid, Integer limit) { - CreateApiTokenRequest request = new CreateApiTokenRequest(uid, limit); + public String createApiToken(CreateTokenRequest request) { return this.webClient.post() .uri("/api/user/createApiToken") - .header("Api-Key", apiKey) - .body(Mono.just(request), CreateApiTokenRequest.class) + .header("Api-Key", request.apiKey) + .body(Mono.just(request), CreateTokenRequest.class) .retrieve() .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) .bodyToMono(ApiResponse.class) .handle((response, sink) -> { if (response.code != 0) { - sink.error(new IllegalStateException("创建apiToken异常," + response.message)); + sink.error(new IllegalStateException("创建 token 异常," + response.message)); return; } sink.next(response.data.get("token").toString()); @@ -74,259 +81,159 @@ public class WddApi { .block(); } + /** - * 解析文件数据 + * 创建任务 * - * @param apiToken API令牌 - * @param content 内容 - * @param fileUrl 文件URL - * @return 数据URL + * @param type 类型 + * @param content 内容 + * @param files 文件列表 + * @return 任务ID */ - public String parseFileData(String apiToken, String content, String fileUrl) { - ParseFileDataRequest request = new ParseFileDataRequest(content, fileUrl); + public ApiResponse createTask(String token, Integer type, String content, List files) { + MultiValueMap formData = new LinkedMultiValueMap<>(); + formData.add("type", type); + if (content != null) { + formData.add("content", content); + } + if (files != null) { + for (MultipartFile file : files) { + formData.add("file", file.getResource()); + } + } + return this.webClient.post() - .uri("/api/ppt/parseFileData") - .header("token", apiToken) - .body(Mono.just(request), ParseFileDataRequest.class) + .uri("/api/ppt/v2/createTask") + .header("token", token) + .contentType(MediaType.MULTIPART_FORM_DATA) + .body(BodyInserters.fromMultipartData(formData)) .retrieve() - .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(formData)) .bodyToMono(ApiResponse.class) - .handle((response, sink) -> { - if (response.code != 0) { - sink.error(new IllegalStateException("解析文件或内容异常," + response.message)); - return; - } - sink.next(response.data.get("dataUrl").toString()); - }) .block(); } /** - * 生成大纲 + * 获取生成选项 * - * @param apiToken API令牌 - * @param subject 主题 - * @param dataUrl 数据URL - * @param prompt 提示词 - * @return 大纲内容 + * @param lang 语种 + * @return 生成选项 */ - public String generateOutline(String apiToken, String subject, String dataUrl, String prompt) { - GenerateOutlineRequest request = new GenerateOutlineRequest(subject, dataUrl, prompt); - return this.webClient.post() - .uri("/api/ppt/generateOutline") - .header("token", apiToken) - .body(Mono.just(request), GenerateOutlineRequest.class) + public Map getOptions(String lang) { + String uri = "/api/ppt/v2/options"; + if (lang != null) { + uri += "?lang=" + lang; + } + return this.webClient.get() + .uri(uri) .retrieve() - .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) - .bodyToMono(String.class) + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(null)) + .bodyToMono(new ParameterizedTypeReference() { + }) + .>handle((response, sink) -> { + if (response.code != 0) { + sink.error(new IllegalStateException("获取生成选项异常," + response.message)); + return; + } + sink.next(response.data); + }) .block(); } /** * 生成大纲内容 * - * @param apiToken API令牌 - * @param outlineMarkdown 大纲Markdown - * @param dataUrl 数据URL - * @param prompt 提示词 - * @return 大纲内容 + * @return 大纲内容流 */ - public String generateContent(String apiToken, String outlineMarkdown, String dataUrl, String prompt) { - GenerateContentRequest request = new GenerateContentRequest(outlineMarkdown, dataUrl, prompt); + public Flux> generateOutlineContent(String token, GenerateOutlineRequest request) { return this.webClient.post() - .uri("/api/ppt/generateContent") - .header("token", apiToken) - .body(Mono.just(request), GenerateContentRequest.class) + .uri("/api/ppt/v2/generateContent") + .header("token", token) + .body(Mono.just(request), GenerateOutlineRequest.class) .retrieve() .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) - .bodyToMono(String.class) - .block(); + .bodyToFlux(new ParameterizedTypeReference<>() { + }); } /** - * 异步生成大纲内容 + * 修改大纲内容 * - * @param apiToken API令牌 - * @param outlineMarkdown 大纲Markdown - * @param dataUrl 数据URL - * @param templateId 模板ID - * @param prompt 提示词 - * @return 大纲内容和PPT ID + * @param id 任务ID + * @param markdown 大纲内容markdown + * @param question 用户修改建议 + * @return 大纲内容流 */ - public Map asyncGenerateContent(String apiToken, String outlineMarkdown, String dataUrl, String templateId, String prompt) { - AsyncGenerateContentRequest request = new AsyncGenerateContentRequest(outlineMarkdown, dataUrl, templateId, prompt); + public Flux> updateOutlineContent(String token, String id, String markdown, String question) { + UpdateOutlineRequest request = new UpdateOutlineRequest(id, markdown, question); return this.webClient.post() - .uri("/api/ppt/generateContent") - .header("token", apiToken) - .body(Mono.just(request), AsyncGenerateContentRequest.class) + .uri("/api/ppt/v2/updateContent") + .header("token", token) + .body(Mono.just(request), UpdateOutlineRequest.class) .retrieve() .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) - .bodyToMono(new ParameterizedTypeReference>() { - }) - .block(); + .bodyToFlux(new ParameterizedTypeReference>() { + }); } - /** - * 随机获取一个模板ID - * - * @param apiToken API令牌 - * @return 模板ID - */ - public String randomOneTemplateId(String apiToken) { - RandomTemplateRequest request = new RandomTemplateRequest(1, new TemplateFilter(1)); - return this.webClient.post() - .uri("/api/ppt/randomTemplates") - .header("token", apiToken) - .body(Mono.just(request), RandomTemplateRequest.class) - .retrieve() - .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) - .bodyToMono(ApiResponse.class) - .handle((response, sink) -> { - if (response.code != 0) { - sink.error(new IllegalStateException("获取模板异常," + response.message)); - return; - } - sink.next(((Map) ((Object[]) response.data.get("data"))[0]).get("id").toString()); - }) - .block(); - } - - /** - * 生成PPT - * - * @param apiToken API令牌 - * @param templateId 模板ID - * @param markdown Markdown内容 - * @param pptxProperty PPT属性 - * @return PPT信息 - */ - public Map generatePptx(String apiToken, String templateId, String markdown, boolean pptxProperty) { - GeneratePptxRequest request = new GeneratePptxRequest(templateId, markdown, pptxProperty); - return this.webClient.post() - .uri("/api/ppt/generatePptx") - .header("token", apiToken) - .body(Mono.just(request), GeneratePptxRequest.class) - .retrieve() - .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) - .bodyToMono(ApiResponse.class) - .>handle((response, sink) -> { - if (response.code != 0) { - sink.error(new IllegalStateException("生成PPT异常," + response.message)); - return; - } - sink.next((Map) response.data.get("pptInfo")); - }) - .block(); - } - - /** - * 下载PPT - * - * @param apiToken API令牌 - * @param id PPT ID - * @return 下载信息 - */ - public Map downloadPptx(String apiToken, String id) { - DownloadPptxRequest request = new DownloadPptxRequest(id); - return this.webClient.post() - .uri("/api/ppt/downloadPptx") - .header("token", apiToken) - .body(Mono.just(request), DownloadPptxRequest.class) - .retrieve() - .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) - .bodyToMono(ApiResponse.class) - .>handle((response, sink) -> { - if (response.code != 0) { - sink.error(new IllegalStateException("下载PPT异常," + response.message)); - return; - } - sink.next(response.data); - }) - .block(); - } - - /** - * 直接生成PPT - * - * @param apiToken API令牌 - * @param templateId 模板ID - * @param subject 主题 - * @param dataUrl 数据URL - * @param prompt 提示词 - * @param pptxProperty PPT属性 - * @return PPT信息 - */ - public Map directGeneratePptx(String apiToken, String templateId, String subject, String dataUrl, String prompt, boolean pptxProperty) { - DirectGeneratePptxRequest request = new DirectGeneratePptxRequest(false, templateId, subject, dataUrl, prompt, pptxProperty); - return this.webClient.post() - .uri("/api/ppt/directGeneratePptx") - .header("token", apiToken) - .body(Mono.just(request), DirectGeneratePptxRequest.class) - .retrieve() - .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) - .bodyToMono(ApiResponse.class) - .>handle((response, sink) -> { - if (response.code != 0) { - sink.error(new IllegalStateException("生成PPT异常," + response.message)); - return; - } - sink.next((Map) response.data.get("pptInfo")); - }) - .block(); - } - - /** - * 查询所有PPT列表 - * - * @param apiToken API令牌 - * @param body 请求体 - * @return PPT列表 - */ - public Map listAllPptx(String apiToken, String body) { - return this.webClient.post() - .uri("/api/ppt/listAllPptx") - .header("token", apiToken) - .bodyValue(body) - .retrieve() - .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(body)) - .bodyToMono(ApiResponse.class) - .>handle((response, sink) -> { - if (response.code != 0) { - sink.error(new IllegalStateException("查询所有PPT列表异常," + response.message)); - return; - } - sink.next(response.data); - }) - .block(); - } /** * 分页查询PPT模板 * - * @param apiToken API令牌 - * @param body 请求体 + * @param token 令牌 + * @param request 请求体 * @return 模板列表 */ - public Map getPptTemplates(String apiToken, String body) { + public PagePptTemplateInfo getPptTemplatePage(String token, TemplateQueryRequest request) { return this.webClient.post() .uri("/api/ppt/templates") - .header("token", apiToken) - .bodyValue(body) + .header("token", token) + .bodyValue(request) .retrieve() - .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(body)) - .bodyToMono(ApiResponse.class) - .>handle((response, sink) -> { - if (response.code != 0) { - sink.error(new IllegalStateException("分页查询PPT模板异常," + response.message)); - return; - } - sink.next(response.data); + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) + .bodyToMono(new ParameterizedTypeReference() { }) .block(); } + /** - * API响应 + * 生成PPT + * + * @return PPT信息 + */ + public PptInfo generatePptx(String token, GeneratePptxRequest request) { + return this.webClient.post() + .uri("/api/ppt/v2/generatePptx") + .header("token", token) + .body(Mono.just(request), GeneratePptxRequest.class) + .retrieve() + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) + .bodyToMono(ApiResponse.class) + .handle((response, sink) -> { + if (response.code != 0) { + sink.error(new IllegalStateException("生成 PPT 异常," + response.message)); + return; + } + sink.next(Objects.requireNonNull(JsonUtils.parseObject(JsonUtils.toJsonString(response.data.get("pptInfo")), PptInfo.class))); + }) + .block(); + } + + + @JsonInclude(value = JsonInclude.Include.NON_NULL) + public record CreateTokenRequest( + String apiKey, + String uid, + Integer limit + ) { + public CreateTokenRequest(String apiKey) { + this(apiKey, null, null); + } + } + + /** + * API 通用响应 */ @JsonInclude(value = JsonInclude.Include.NON_NULL) public record ApiResponse( @@ -337,33 +244,13 @@ public class WddApi { } /** - * 创建API令牌请求 + * 创建任务 */ @JsonInclude(value = JsonInclude.Include.NON_NULL) - public record CreateApiTokenRequest( - String uid, - Integer limit - ) { - } - - /** - * 解析文件数据请求 - */ - @JsonInclude(value = JsonInclude.Include.NON_NULL) - public record ParseFileDataRequest( + public record CreateTaskRequest( + Integer type, String content, - String fileUrl - ) { - } - - /** - * 生成大纲请求 - */ - @JsonInclude(value = JsonInclude.Include.NON_NULL) - public record GenerateOutlineRequest( - String subject, - String dataUrl, - String prompt + List files ) { } @@ -371,45 +258,24 @@ public class WddApi { * 生成大纲内容请求 */ @JsonInclude(value = JsonInclude.Include.NON_NULL) - public record GenerateContentRequest( - String outlineMarkdown, - String dataUrl, + public record GenerateOutlineRequest( + String id, + String length, + String scene, + String audience, + String lang, String prompt ) { } /** - * 异步生成大纲内容请求 + * 修改大纲内容请求 */ @JsonInclude(value = JsonInclude.Include.NON_NULL) - public record AsyncGenerateContentRequest( - String outlineMarkdown, - String dataUrl, - String templateId, - String prompt, - @JsonProperty("asyncGenPptx") boolean asyncGenPptx - ) { - public AsyncGenerateContentRequest(String outlineMarkdown, String dataUrl, String templateId, String prompt) { - this(outlineMarkdown, dataUrl, templateId, prompt, true); - } - } - - /** - * 随机模板请求 - */ - @JsonInclude(value = JsonInclude.Include.NON_NULL) - public record RandomTemplateRequest( - Integer size, - TemplateFilter filters - ) { - } - - /** - * 模板过滤器 - */ - @JsonInclude(value = JsonInclude.Include.NON_NULL) - public record TemplateFilter( - Integer type + public record UpdateOutlineRequest( + String id, + String markdown, + String question ) { } @@ -418,32 +284,90 @@ public class WddApi { */ @JsonInclude(value = JsonInclude.Include.NON_NULL) public record GeneratePptxRequest( + String id, String templateId, - @JsonProperty("outlineContentMarkdown") String outlineContentMarkdown, - boolean pptxProperty + String markdown ) { } - /** - * 下载PPT请求 - */ - @JsonInclude(value = JsonInclude.Include.NON_NULL) - public record DownloadPptxRequest( - String id - ) { - } - /** - * 直接生成PPT请求 - */ @JsonInclude(value = JsonInclude.Include.NON_NULL) - public record DirectGeneratePptxRequest( - boolean stream, - String templateId, + public record PptInfo( + String id, + String name, String subject, - String dataUrl, - String prompt, - boolean pptxProperty + String coverUrl, + String fileUrl, + String templateId, + String pptxProperty, + String userId, + String userName, + int companyId, + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + LocalDateTime updateTime, + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + LocalDateTime createTime, + String createUser, + String updateUser ) { } -} \ No newline at end of file + + + @JsonInclude(value = JsonInclude.Include.NON_NULL) + public record TemplateQueryRequest( + int page, + int size, + Filter filters + ) { + @JsonInclude(value = JsonInclude.Include.NON_NULL) + public record Filter( + int type, + String category, + String style, + String themeColor + ) { + } + } + + + @JsonInclude(value = JsonInclude.Include.NON_NULL) + public record PagePptTemplateInfo( + List data, + String total + ) { + + } + + + @JsonInclude(value = JsonInclude.Include.NON_NULL) + public record PptTemplateInfo( + String id, + int type, + Integer subType, + String layout, + String category, + String style, + String themeColor, + String lang, + boolean animation, + String subject, + String coverUrl, + String fileUrl, + List pageCoverUrls, + String pptxProperty, + int sort, + int num, + Integer imgNum, + int isDeleted, + String userId, + int companyId, + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + LocalDateTime updateTime, + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + LocalDateTime createTime, + String createUser, + String updateUser + ) { + } + +} \ No newline at end of file From 8ed3066506e3984e9f3ab4cfcf063e89de53e4e4 Mon Sep 17 00:00:00 2001 From: smallNorthLee <18210040298@163.com> Date: Sun, 16 Mar 2025 21:41:47 +0800 Subject: [PATCH 03/36] =?UTF-8?q?review:=20=E9=87=8D=E6=9E=84=E7=BB=84?= =?UTF-8?q?=E8=A3=85=E4=B8=8B=E4=B8=80=E4=B8=AA=E8=8A=82=E7=82=B9=E5=AE=A1?= =?UTF-8?q?=E6=89=B9=E4=BA=BA=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../task/BpmProcessInstanceServiceImpl.java | 39 +++++++++++++------ 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java index 1e0a777d02..0a63cf1b6c 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java @@ -267,24 +267,41 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService // 3.1 获取下一个将要执行的节点集合 FlowElement flowElement = bpmnModel.getFlowElement(task.getTaskDefinitionKey()); List nextFlowNodes = BpmnModelUtils.getNextFlowNodes(flowElement, bpmnModel, processVariables); - return convertList(nextFlowNodes, node -> { + + // 2. 收集所有节点的候选用户 ID + Set allCandidateUsers = new HashSet<>(); + for (FlowNode node : nextFlowNodes) { List candidateUserIds = getTaskCandidateUserList(bpmnModel, node.getId(), loginUserId, historicProcessInstance.getProcessDefinitionId(), processVariables); - // 3.2 获取节点的审批人信息 - Map userMap = adminUserApi.getUserMap(candidateUserIds); - // 3.3 获取节点的审批人部门信息 - Map deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId)); - // 3.4 存在一个节点多人审批的情况,组装审批人信息 + allCandidateUsers.addAll(candidateUserIds); + } + + // 3. 批量查询用户和部门信息 + Map userMap = adminUserApi.getUserMap(new ArrayList<>(allCandidateUsers)); + Map deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId)); + + // 4. 组装节点信息 + return convertList(nextFlowNodes, node -> { + // 4.1 获取当前节点的候选用户 ID + List candidateUserIds = getTaskCandidateUserList(bpmnModel, node.getId(), + loginUserId, historicProcessInstance.getProcessDefinitionId(), processVariables); + + // 4.2 组装候选用户信息 List candidateUsers = new ArrayList<>(); - userMap.forEach((key, value) -> candidateUsers.add(BpmProcessInstanceConvert.INSTANCE.buildUser(key, userMap, deptMap))); - return new ActivityNode().setNodeType(BpmSimpleModelNodeTypeEnum.APPROVE_NODE.getType()) + for (Long userId : candidateUserIds) { + AdminUserRespDTO user = userMap.get(userId); + if (user != null) { + candidateUsers.add(BpmProcessInstanceConvert.INSTANCE.buildUser(userId, userMap, deptMap)); + } + } + + // 4.3 构建节点信息 + return new ActivityNode() + .setNodeType(BpmSimpleModelNodeTypeEnum.APPROVE_NODE.getType()) .setId(node.getId()) .setName(node.getName()) .setStatus(BpmTaskStatusEnum.RUNNING.getStatus()) .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(node)) - // TODO @小北:先把 candidateUserIds 设置完,然后最后拼接 candidateUsers 信息。这样,如果有多个节点,就不用重复查询啦;类似 buildApprovalDetail 思路; - // TODO 先拼接处 List ActivityNode - // TODO 接着,再起一段,处理 adminUserApi.getUserMap(candidateUserIds)、deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId)) .setCandidateUsers(candidateUsers); }); } From 7ed96c4c6c5b247a791357ff78ada94e11061aff Mon Sep 17 00:00:00 2001 From: smallNorthLee <18210040298@163.com> Date: Sun, 16 Mar 2025 22:32:42 +0800 Subject: [PATCH 04/36] =?UTF-8?q?review:=20=E9=87=8D=E6=9E=84=E7=BB=84?= =?UTF-8?q?=E8=A3=85=E4=B8=8B=E4=B8=80=E4=B8=AA=E8=8A=82=E7=82=B9=E5=AE=A1?= =?UTF-8?q?=E6=89=B9=E4=BA=BA=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bpm/service/task/BpmProcessInstanceServiceImpl.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java index 0a63cf1b6c..05ac86e69a 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java @@ -289,9 +289,9 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService // 4.2 组装候选用户信息 List candidateUsers = new ArrayList<>(); for (Long userId : candidateUserIds) { - AdminUserRespDTO user = userMap.get(userId); - if (user != null) { - candidateUsers.add(BpmProcessInstanceConvert.INSTANCE.buildUser(userId, userMap, deptMap)); + UserSimpleBaseVO user = BpmProcessInstanceConvert.INSTANCE.buildUser(userId, userMap, deptMap); + if (user != null){ + candidateUsers.add(user); } } From 58c667f728d4217a95e2a43b10581fbb6861724e Mon Sep 17 00:00:00 2001 From: Lesan <1960681385@qq.com> Date: Mon, 17 Mar 2025 08:54:34 +0800 Subject: [PATCH 05/36] =?UTF-8?q?feat:=20=E4=BB=BB=E5=8A=A1=E5=89=8D?= =?UTF-8?q?=E5=90=8E=E7=BD=AE=E9=80=9A=E7=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../vo/model/BpmModelMetaInfoVO.java | 6 +++ .../BpmProcessDefinitionInfoDO.java | 12 ++++++ .../core/listener/BpmTaskEventListener.java | 7 +++- .../bpm/service/task/BpmTaskService.java | 7 ++++ .../bpm/service/task/BpmTaskServiceImpl.java | 41 +++++++++++++++++++ 5 files changed, 72 insertions(+), 1 deletion(-) diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java index cf9ca3e5fd..957b010c87 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java @@ -88,6 +88,12 @@ public class BpmModelMetaInfoVO { @Schema(description = "流程后置通知设置", example = "{}") private HttpRequestSetting processAfterTriggerSetting; + @Schema(description = "任务前置通知设置", example = "{}") + private HttpRequestSetting taskBeforeTriggerSetting; + + @Schema(description = "任务后置通知设置", example = "{}") + private HttpRequestSetting taskAfterTriggerSetting; + @Schema(description = "流程 ID 规则") @Data @Valid diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java index 86c83ed611..2f79ec1c9e 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java @@ -199,4 +199,16 @@ public class BpmProcessDefinitionInfoDO extends BaseDO { @TableField(typeHandler = JacksonTypeHandler.class) private BpmModelMetaInfoVO.HttpRequestSetting processAfterTriggerSetting; + /** + * 任务前置通知设置 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private BpmModelMetaInfoVO.HttpRequestSetting taskBeforeTriggerSetting; + + /** + * 任务后置通知设置 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private BpmModelMetaInfoVO.HttpRequestSetting taskAfterTriggerSetting; + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java index 329241f799..e50df0bcf0 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java @@ -47,7 +47,7 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener { public static final Set TASK_EVENTS = ImmutableSet.builder() .add(FlowableEngineEventType.TASK_CREATED) .add(FlowableEngineEventType.TASK_ASSIGNED) -// .add(FlowableEngineEventType.TASK_COMPLETED) // 由于审批通过时,已经记录了 task 的 status 为通过,所以不需要监听了。 + .add(FlowableEngineEventType.TASK_COMPLETED) // 由于审批通过时,已经记录了 task 的 status 为通过,这里仅处理任务后置通知。 .add(FlowableEngineEventType.ACTIVITY_CANCELLED) .add(FlowableEngineEventType.TIMER_FIRED) // 监听审批超时 .build(); @@ -66,6 +66,11 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener { taskService.processTaskAssigned((Task) event.getEntity()); } + @Override + protected void taskCompleted(FlowableEngineEntityEvent event) { + taskService.processTaskCompleted((Task) event.getEntity()); + } + @Override protected void activityCancelled(FlowableActivityCancelledEvent event) { List activityList = taskService.getHistoricActivityListByExecutionId(event.getExecutionId()); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java index e99d974356..0a5c866fda 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java @@ -278,6 +278,13 @@ public interface BpmTaskService { */ void processTaskAssigned(Task task); + /** + * 处理 Task 完成事件,目前是发送任务后置通知 + * + * @param task 任务实体 + */ + void processTaskCompleted(Task task); + /** * 处理 Task 审批超时事件,可能会处理多个当前审批中的任务 * diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java index 242ed77475..7becc5fd58 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java @@ -12,6 +12,7 @@ import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission; import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils; +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*; import cn.iocoder.yudao.module.bpm.convert.task.BpmTaskConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO; @@ -23,6 +24,7 @@ import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskSignTypeEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmHttpRequestUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.service.definition.BpmFormService; @@ -1180,6 +1182,21 @@ public class BpmTaskServiceImpl implements BpmTaskService { log.error("[processTaskCreated][taskId({}) 没有找到流程实例]", task.getId()); return; } + BpmProcessDefinitionInfoDO processDefinitionInfo = bpmProcessDefinitionService. + getProcessDefinitionInfo(processInstance.getProcessDefinitionId()); + if (processDefinitionInfo == null) { + log.error("[processTaskCreated][processDefinitionId({}) 没有找到流程定义]", processInstance.getProcessDefinitionId()); + return; + } + // 任务前置通知 + if (ObjUtil.isNotNull(processDefinitionInfo.getTaskBeforeTriggerSetting())){ + BpmModelMetaInfoVO.HttpRequestSetting setting = processDefinitionInfo.getTaskBeforeTriggerSetting(); + BpmHttpRequestUtils.executeBpmHttpRequest(processInstance, + setting.getUrl(), + setting.getHeader(), + setting.getBody(), + true, setting.getResponse()); + } BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processInstance.getProcessDefinitionId()); FlowElement userTaskElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); Integer approveType = BpmnModelUtils.parseApproveType(userTaskElement); @@ -1391,6 +1408,30 @@ public class BpmTaskServiceImpl implements BpmTaskService { }); } + @Override + public void processTaskCompleted(Task task) { + ProcessInstance processInstance = processInstanceService.getProcessInstance(task.getProcessInstanceId()); + if (processInstance == null) { + log.error("[processTaskCompleted][taskId({}) 没有找到流程实例]", task.getId()); + return; + } + BpmProcessDefinitionInfoDO processDefinitionInfo = bpmProcessDefinitionService. + getProcessDefinitionInfo(processInstance.getProcessDefinitionId()); + if (processDefinitionInfo == null) { + log.error("[processTaskCompleted][processDefinitionId({}) 没有找到流程定义]", processInstance.getProcessDefinitionId()); + return; + } + // 任务前置通知 + if (ObjUtil.isNotNull(processDefinitionInfo.getTaskAfterTriggerSetting())){ + BpmModelMetaInfoVO.HttpRequestSetting setting = processDefinitionInfo.getTaskAfterTriggerSetting(); + BpmHttpRequestUtils.executeBpmHttpRequest(processInstance, + setting.getUrl(), + setting.getHeader(), + setting.getBody(), + true, setting.getResponse()); + } + } + @Override @Transactional(rollbackFor = Exception.class) public void processTaskTimeout(String processInstanceId, String taskDefineKey, Integer handlerType) { From c79273c5d6c151e5a9ed516bd1f4db15d8714643 Mon Sep 17 00:00:00 2001 From: xiaoxin <718949661@qq.com> Date: Mon, 17 Mar 2025 09:46:47 +0800 Subject: [PATCH 06/36] =?UTF-8?q?=E3=80=90=E6=B5=8B=E8=AF=95=E7=94=A8?= =?UTF-8?q?=E4=BE=8B=E6=96=B0=E5=A2=9E=E3=80=91AI=EF=BC=9A=E6=96=87?= =?UTF-8?q?=E5=A4=9A=E5=A4=9A=20API=20=E6=B5=8B=E8=AF=95=E5=AE=8C=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ai/core/model/wenduoduo/api/WddApi.java | 28 +- .../framework/ai/ppt/wdd/WddApiTests.java | 306 ++++++++++++++++++ 2 files changed, 330 insertions(+), 4 deletions(-) create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/ppt/wdd/WddApiTests.java diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/wenduoduo/api/WddApi.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/wenduoduo/api/WddApi.java index 66583f50af..4a827b04a6 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/wenduoduo/api/WddApi.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/wenduoduo/api/WddApi.java @@ -86,8 +86,29 @@ public class WddApi { * 创建任务 * * @param type 类型 + * 1.智能生成(主题、要求) + * 2.上传文件生成 + * 3.上传思维导图生成 + * 4.通过word精准转ppt + * 5.通过网页链接生成 + * 6.粘贴文本内容生成 + * 7.Markdown大纲生成 * @param content 内容 + * type=1 用户输入主题或要求(不超过1000字符) + * type=2、4 不传 + * type=3 幕布等分享链接 + * type=5 网页链接地址(http/https) + * type=6 粘贴文本内容(不超过20000字符) + * type=7 大纲内容(markdown) * @param files 文件列表 + * 文件列表(文件数不超过5个,总大小不超过50M): + * type=1 上传参考文件(非必传,支持多个) + * type=2 上传文件(支持多个) + * type=3 上传思维导图(xmind/mm/md)(仅支持一个) + * type=4 上传word文件(仅支持一个) + * type=5、6、7 不传 + *

+ * 支持格式:doc/docx/pdf/ppt/pptx/txt/md/xls/xlsx/csv/html/epub/mobi/xmind/mm * @return 任务ID */ public ApiResponse createTask(String token, Integer type, String content, List files) { @@ -127,7 +148,7 @@ public class WddApi { return this.webClient.get() .uri(uri) .retrieve() - .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(null)) + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(lang)) .bodyToMono(new ParameterizedTypeReference() { }) .>handle((response, sink) -> { @@ -164,15 +185,14 @@ public class WddApi { * @param question 用户修改建议 * @return 大纲内容流 */ - public Flux> updateOutlineContent(String token, String id, String markdown, String question) { - UpdateOutlineRequest request = new UpdateOutlineRequest(id, markdown, question); + public Flux> updateOutlineContent(String token, UpdateOutlineRequest request) { return this.webClient.post() .uri("/api/ppt/v2/updateContent") .header("token", token) .body(Mono.just(request), UpdateOutlineRequest.class) .retrieve() .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) - .bodyToFlux(new ParameterizedTypeReference>() { + .bodyToFlux(new ParameterizedTypeReference<>() { }); } diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/ppt/wdd/WddApiTests.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/ppt/wdd/WddApiTests.java new file mode 100644 index 0000000000..b64a4e3fe1 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/ppt/wdd/WddApiTests.java @@ -0,0 +1,306 @@ +package cn.iocoder.yudao.framework.ai.ppt.wdd; + +import cn.iocoder.yudao.framework.ai.core.model.wenduoduo.api.WddApi; +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import reactor.core.publisher.Flux; + +import java.util.Map; +import java.util.Objects; + + +/** + * {@link WddApi} 集成测试 + * + * @author xiaoxin + */ +public class WddApiTests { + + private final WddApi wddApi = new WddApi("https://docmee.cn"); + + + private final String token = "sk_FJo7sKErrrEs5CIZz1"; + + + @Test //获取token + @Disabled + public void testCreateApiToken() { + // 准备参数 + String apiKey = "ak_RK1rm7TrEv3E3JSWIK"; + WddApi.CreateTokenRequest request = new WddApi.CreateTokenRequest(apiKey); + // 调用方法 + String token = wddApi.createApiToken(request); + // 打印结果 + System.out.println(token); + } + + + @Test // 创建任务 + @Disabled + public void testCreateTask() { + WddApi.ApiResponse apiResponse = wddApi.createTask(token, 1, "dify 介绍", null); + System.out.println(apiResponse); + } + + + @Test // 创建大纲 + @Disabled + public void testGenerateOutlineRequest() { + WddApi.GenerateOutlineRequest request = new WddApi.GenerateOutlineRequest("1901449466163105792", "medium", null, null, null, null); + //调用 + Flux> flux = wddApi.generateOutlineContent(token, request); + StringBuffer contentBuffer = new StringBuffer(); + flux.doOnNext(chunk -> { + contentBuffer.append(chunk.get("text")); + if (Objects.equals(Integer.parseInt(String.valueOf(chunk.get("status"))), 4)) { + // status 为 4,最终 markdown 结构树 + System.out.println(JsonUtils.toJsonString(chunk.get("result"))); + System.out.println(" ########################################################################"); + } + }).then().block(); + // 打印结果 + System.out.println(contentBuffer); + + } + + @Test // 修改大纲 + @Disabled + public void testUpdateOutlineContentRequest() { + WddApi.UpdateOutlineRequest request = new WddApi.UpdateOutlineRequest("1901449466163105792", TEST_OUT_LINE_CONTENT, "精简一点,三个章节即可"); + //调用 + Flux> flux = wddApi.updateOutlineContent(token, request); + StringBuffer contentBuffer = new StringBuffer(); + flux.doOnNext(chunk -> { + contentBuffer.append(chunk.get("text")); + if (Objects.equals(Integer.parseInt(String.valueOf(chunk.get("status"))), 4)) { + // status 为 4,最终 markdown 结构树 + System.out.println(JsonUtils.toJsonString(chunk.get("result"))); + System.out.println(" ########################################################################"); + } + }).then().block(); + // 打印结果 + System.out.println(contentBuffer); + + } + + @Test // 获取 PPT 模版分页 + @Disabled + public void testGetPptTemplatePage() { + // 准备参数 + WddApi.TemplateQueryRequest.Filter filter = new WddApi.TemplateQueryRequest.Filter(1, null, null, null); + WddApi.TemplateQueryRequest request = new WddApi.TemplateQueryRequest(1, 10, filter); + //调用 + WddApi.PagePptTemplateInfo pptTemplatePage = wddApi.getPptTemplatePage(token, request); + // 打印结果 + System.out.println(pptTemplatePage); + } + + + @Test // 生成 PPT + @Disabled + public void testGeneratePptx() { + // 准备参数 + WddApi.GeneratePptxRequest request = new WddApi.GeneratePptxRequest("1900913633555255296", "1804885538940116992", TEST_OUT_LINE_CONTENT); + //调用 + WddApi.PptInfo pptInfo = wddApi.generatePptx("", request); + // 打印结果 + System.out.println(pptInfo); + } + + + private final String TEST_OUT_LINE_CONTENT = """ + # Dify:新一代AI应用开发平台 + + ## 1 什么是Dify + ### 1.1 Dify定义:AI应用开发平台 + #### 1.1.1 低代码开发 + Dify是一个低代码AI应用开发平台,旨在简化AI应用的构建过程,让开发者无需编写大量代码即可快速创建各种智能应用。 + #### 1.1.2 核心功能 + Dify的核心功能包括数据集成、模型选择、流程编排和应用部署,提供一站式解决方案,加速AI应用的落地和迭代。 + #### 1.1.3 开源与商业 + Dify提供开源版本和商业版本,满足不同用户的需求,开源版本适合个人开发者和小型团队,商业版本则提供更强大的功能和技术支持。 + + ### 1.2 Dify解决的问题:AI开发痛点 + #### 1.2.1 开发周期长 + 传统AI应用开发周期长,需要大量的人力和时间投入,Dify通过可视化界面和预置组件,大幅缩短开发周期。 + #### 1.2.2 技术门槛高 + AI技术门槛高,需要专业的知识和技能,Dify降低技术门槛,让更多开发者能够参与到AI应用的开发中来。 + #### 1.2.3 部署和维护复杂 + AI应用的部署和维护复杂,需要专业的运维团队,Dify提供自动化的部署和维护工具,简化流程,降低成本。 + + ### 1.3 Dify发展历程 + #### 1.3.1 早期探索 + Dify的早期版本主要关注于自然语言处理领域的应用,通过集成各种NLP模型,提供文本分类、情感分析等功能。 + #### 1.3.2 功能扩展 + 随着用户需求的不断增长,Dify的功能逐渐扩展到图像识别、语音识别等领域,支持更多类型的AI应用。 + #### 1.3.3 生态建设 + Dify积极建设开发者生态,提供丰富的文档、教程和案例,帮助开发者更好地使用Dify平台,共同推动AI技术的发展。 + + ## 2 Dify的核心功能 + ### 2.1 数据集成:连接各种数据源 + #### 2.1.1 支持多种数据源 + Dify支持连接各种数据源,包括关系型数据库、NoSQL数据库、文件系统、云存储等,满足不同场景的数据需求。 + #### 2.1.2 数据转换和清洗 + Dify提供数据转换和清洗功能,可以将不同格式的数据转换为统一的格式,并去除无效数据,提高数据质量。 + #### 2.1.3 数据安全 + Dify注重数据安全,采用各种安全措施保护用户的数据,包括数据加密、访问控制、权限管理等。 + + ### 2.2 模型选择:丰富的AI模型库 + #### 2.2.1 预置模型 + Dify预置了丰富的AI模型,包括自然语言处理、图像识别、语音识别等领域的模型,开发者可以直接使用这些模型,无需自行训练,极大的简化了开发流程。 + #### 2.2.2 自定义模型 + Dify支持开发者上传自定义模型,满足个性化的需求。开发者可以将自己训练的模型部署到Dify平台上,与其他开发者共享。 + #### 2.2.3 模型评估 + Dify提供模型评估功能,可以对不同模型进行评估,选择最优的模型,提高应用性能。 + + ### 2.3 流程编排:可视化流程设计器 + #### 2.3.1 可视化界面 + Dify提供可视化的流程设计器,开发者可以通过拖拽组件的方式,设计AI应用的流程,无需编写代码,简单高效。 + #### 2.3.2 灵活的流程控制 + Dify支持灵活的流程控制,可以根据不同的条件执行不同的分支,实现复杂的业务逻辑。 + #### 2.3.3 实时调试 + Dify提供实时调试功能,可以在设计流程的过程中,实时查看流程的执行结果,及时发现和解决问题。 + + ### 2.4 应用部署:一键部署和管理 + #### 2.4.1 快速部署 + Dify提供一键部署功能,可以将AI应用快速部署到各种环境,包括本地环境、云环境、容器环境等。 + #### 2.4.2 自动伸缩 + Dify支持自动伸缩,可以根据应用的负载自动调整资源,保证应用的稳定性和性能。 + #### 2.4.3 监控和告警 + Dify提供监控和告警功能,可以实时监控应用的状态,并在出现问题时及时告警,方便运维人员进行处理。 + + ## 3 Dify的特点和优势 + ### 3.1 低代码:降低开发门槛 + #### 3.1.1 可视化开发 + Dify采用可视化开发模式,开发者无需编写大量代码,只需通过拖拽组件即可完成AI应用的开发,降低了开发门槛。 + #### 3.1.2 预置组件 + Dify预置了丰富的组件,包括数据源组件、模型组件、流程控制组件等,开发者可以直接使用这些组件,提高开发效率。 + #### 3.1.3 减少代码量 + Dify可以显著减少代码量,降低开发难度,让更多开发者能够参与到AI应用的开发中来。 + + ### 3.2 灵活:满足不同场景需求 + #### 3.2.1 支持多种数据源 + Dify支持多种数据源,可以连接各种数据源,满足不同场景的数据需求。 + #### 3.2.2 支持自定义模型 + Dify支持自定义模型,开发者可以将自己训练的模型部署到Dify平台上,满足个性化的需求。 + #### 3.2.3 灵活的流程控制 + Dify支持灵活的流程控制,可以根据不同的条件执行不同的分支,实现复杂的业务逻辑。 + + ### 3.3 高效:加速应用落地 + #### 3.3.1 快速开发 + Dify通过可视化界面和预置组件,大幅缩短开发周期,加速AI应用的落地。 + #### 3.3.2 快速部署 + Dify提供一键部署功能,可以将AI应用快速部署到各种环境,提高部署效率。 + #### 3.3.3 自动化运维 + Dify提供自动化的运维工具,简化运维流程,降低运维成本。 + + ### 3.4 开放:构建繁荣生态 + #### 3.4.1 开源社区 + Dify拥有活跃的开源社区,开发者可以在社区中交流经验、分享资源、共同推动Dify的发展。 + #### 3.4.2 丰富的文档 + Dify提供丰富的文档、教程和案例,帮助开发者更好地使用Dify平台。 + #### 3.4.3 API支持 + Dify提供API支持,开发者可以通过API将Dify集成到自己的系统中,扩展Dify的功能。 + + ## 4 Dify的使用场景 + ### 4.1 智能客服:提升客户服务质量 + #### 4.1.1 自动回复 + Dify可以用于构建智能客服系统,实现自动回复客户的常见问题,提高客户服务效率。 + #### 4.1.2 情感分析 + Dify可以对客户的语音或文本进行情感分析,判断客户的情绪,并根据情绪提供个性化的服务。 + #### 4.1.3 知识库问答 + Dify可以构建知识库问答系统,让客户通过提问的方式获取所需的信息,提高客户满意度。 + + ### 4.2 金融风控:提高风险识别能力 + #### 4.2.1 欺诈检测 + Dify可以用于构建金融风控系统,实现欺诈检测,识别可疑交易,降低风险。 + #### 4.2.2 信用评估 + Dify可以对用户的信用进行评估,并根据评估结果提供不同的金融服务。 + #### 4.2.3 反洗钱 + Dify可以用于反洗钱,识别可疑资金流动,防止犯罪行为。 + + ### 4.3 智慧医疗:提升医疗服务水平 + #### 4.3.1 疾病诊断 + Dify可以用于辅助疾病诊断,提高诊断准确率,缩短诊断时间。 + #### 4.3.2 药物研发 + Dify可以用于药物研发,加速新药的发现和开发。 + #### 4.3.3 智能健康管理 + Dify可以构建智能健康管理系统,为用户提供个性化的健康建议和服务。 + + ### 4.4 智慧城市:提升城市管理效率 + #### 4.4.1 交通优化 + Dify可以用于交通优化,提高交通效率,缓解交通拥堵。 + #### 4.4.2 环境监测 + Dify可以用于环境监测,实时监测空气质量、水质等环境指标,及时发现和解决环境问题。 + #### 4.4.3 智能安防 + Dify可以用于智能安防,提高城市安全水平,预防犯罪行为。 + + ## 5 Dify的成功案例 + ### 5.1 Case 1:某电商平台的智能客服 + #### 5.1.1 项目背景 + 该电商平台客户服务压力大,人工客服成本高,需要一种智能化的解决方案。 + #### 5.1.2 解决方案 + 使用Dify构建智能客服系统,实现自动回复客户的常见问题,并根据客户的情绪提供个性化的服务。 + #### 5.1.3 效果 + 客户服务效率提高50%,客户满意度提高20%,人工客服成本降低30%。 + + ### 5.2 Case 2:某银行的金融风控系统 + #### 5.2.1 项目背景 + 该银行面临日益增长的金融风险,需要一种更有效的风险识别和控制手段。 + #### 5.2.2 解决方案 + 使用Dify构建金融风控系统,实现欺诈检测、信用评估和反洗钱等功能,提高风险识别能力。 + #### 5.2.3 效果 + 欺诈交易识别率提高40%,信用评估准确率提高30%,洗钱风险降低25%。 + + ### 5.3 Case 3:某医院的辅助疾病诊断系统 + #### 5.3.1 项目背景 + 该医院医生工作压力大,疾病诊断准确率有待提高,需要一种辅助诊断工具。 + #### 5.3.2 解决方案 + 使用Dify构建辅助疾病诊断系统,根据患者的病历和症状,提供诊断建议,提高诊断准确率。 + #### 5.3.3 效果 + 疾病诊断准确率提高20%,诊断时间缩短15%,医生工作效率提高10%。 + + ## 6 Dify的未来展望 + ### 6.1 技术升级 + #### 6.1.1 模型优化 + Dify将不断优化预置模型,提高模型性能,并支持更多类型的AI模型。 + #### 6.1.2 流程引擎升级 + Dify将升级流程引擎,提高流程的灵活性和可扩展性,支持更复杂的业务逻辑。 + #### 6.1.3 平台性能优化 + Dify将不断优化平台性能,提高平台的稳定性和可靠性,满足大规模应用的需求。 + + ### 6.2 生态建设 + #### 6.2.1 社区建设 + Dify将继续加强开源社区建设,吸引更多开发者参与,共同推动Dify的发展。 + #### 6.2.2 合作伙伴拓展 + Dify将拓展合作伙伴,与更多的企业和机构合作,共同推动AI技术的应用。 + #### 6.2.3 应用商店 + Dify将构建应用商店,让开发者可以分享自己的应用,用户可以购买和使用这些应用,构建繁荣的生态系统。 + + ### 6.3 应用领域拓展 + #### 6.3.1 智能制造 + Dify将拓展到智能制造领域,为企业提供智能化的生产管理和质量控制解决方案。 + #### 6.3.2 智慧农业 + Dify将拓展到智慧农业领域,为农民提供智能化的种植和养殖管理解决方案。 + #### 6.3.3 更多领域 + Dify将拓展到更多领域,为各行各业提供智能化的解决方案,推动社会发展。 + + ## 7 总结 + ### 7.1 Dify的价值 + #### 7.1.1 降低AI开发门槛 + Dify通过低代码的方式,让更多开发者能够参与到AI应用的开发中来。 + #### 7.1.2 加速AI应用落地 + Dify提供一站式解决方案,加速AI应用的落地和迭代。 + #### 7.1.3 构建繁荣的AI生态 + Dify通过开源社区和应用商店,构建繁荣的AI生态系统。 + + ### 7.2 共同发展 + #### 7.2.1 欢迎加入Dify社区 + 欢迎更多开发者加入Dify社区,共同推动Dify的发展。 + #### 7.2.2 合作共赢 + 期待与更多的企业和机构合作,共同推动AI技术的应用。 + #### 7.2.3 共创未来 + 让我们一起用AI技术改变世界,共创美好未来。"""; + +} From 6fbcac3c13f5604605b4390c7f17c947abcafced Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=83=91=E5=A8=81?= Date: Mon, 17 Mar 2025 15:31:34 +0800 Subject: [PATCH 07/36] =?UTF-8?q?=E3=80=90Simple=E8=AE=BE=E8=AE=A1?= =?UTF-8?q?=E5=99=A8=E3=80=91=E6=B5=81=E7=A8=8B=E6=A8=A1=E5=9E=8B->?= =?UTF-8?q?=E5=9F=BA=E6=9C=AC=E4=BF=A1=E6=81=AF->=E8=B0=81=E5=8F=AF?= =?UTF-8?q?=E4=BB=A5=E5=8F=91=E8=B5=B7=EF=BC=8C=E6=94=AF=E6=8C=81=E6=8C=87?= =?UTF-8?q?=E5=AE=9A=E5=A4=9A=E4=B8=AA=E9=83=A8=E9=97=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 指定部门可以在部门新增成员后无需重新修改相关流程 --- .../admin/base/dept/DeptSimpleBaseVO.java | 13 ++++++++++ .../admin/definition/BpmModelController.java | 12 ++++++++- .../vo/model/BpmModelMetaInfoVO.java | 3 +++ .../definition/vo/model/BpmModelRespVO.java | 4 +++ .../convert/definition/BpmModelConvert.java | 15 ++++++++--- .../BpmProcessDefinitionInfoDO.java | 8 ++++++ .../BpmProcessDefinitionServiceImpl.java | 25 +++++++++++++++---- 7 files changed, 70 insertions(+), 10 deletions(-) create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/base/dept/DeptSimpleBaseVO.java diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/base/dept/DeptSimpleBaseVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/base/dept/DeptSimpleBaseVO.java new file mode 100644 index 0000000000..f6a9c126b3 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/base/dept/DeptSimpleBaseVO.java @@ -0,0 +1,13 @@ +package cn.iocoder.yudao.module.bpm.controller.admin.base.dept; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "部门精简信息 VO") +@Data +public class DeptSimpleBaseVO { + @Schema(description = "部门编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Long id; + @Schema(description = "部门名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿") + private String name; +} \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java index 5578114f9d..0b3f9c3333 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java @@ -12,6 +12,8 @@ import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService; import cn.iocoder.yudao.module.bpm.service.definition.BpmFormService; import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; +import cn.iocoder.yudao.module.system.api.dept.DeptApi; +import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import io.swagger.v3.oas.annotations.Operation; @@ -53,6 +55,8 @@ public class BpmModelController { @Resource private AdminUserApi adminUserApi; + @Resource + private DeptApi deptApi; @GetMapping("/list") @Operation(summary = "获得模型分页") @@ -85,8 +89,14 @@ public class BpmModelController { return metaInfo != null ? metaInfo.getStartUserIds().stream() : Stream.empty(); }); Map userMap = adminUserApi.getUserMap(userIds); + // 获得 Dept Map + Set deptIds = convertSetByFlatMap(list, model -> { + BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model); + return metaInfo != null && metaInfo.getStartDeptIds() != null ? metaInfo.getStartDeptIds().stream() : Stream.empty(); + }); + Map deptMap = deptApi.getDeptMap(deptIds); return success(BpmModelConvert.INSTANCE.buildModelList(list, - formMap, categoryMap, deploymentMap, processDefinitionMap, userMap)); + formMap, categoryMap, deploymentMap, processDefinitionMap, userMap, deptMap)); } @GetMapping("/get") diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java index cf9ca3e5fd..db54c947e9 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java @@ -59,6 +59,9 @@ public class BpmModelMetaInfoVO { @Schema(description = "可发起用户编号数组", example = "[1,2,3]") private List startUserIds; + @Schema(description = "可发起部门编号数组") + private List startDeptIds; + @Schema(description = "可管理用户编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "[2,4,6]") @NotEmpty(message = "可管理用户编号数组不能为空") private List managerUserIds; diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelRespVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelRespVO.java index 275368c7c6..6a9150aa27 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelRespVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelRespVO.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model; +import cn.iocoder.yudao.module.bpm.controller.admin.base.dept.DeptSimpleBaseVO; import cn.iocoder.yudao.module.bpm.controller.admin.base.user.UserSimpleBaseVO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO; @@ -39,6 +40,9 @@ public class BpmModelRespVO extends BpmModelMetaInfoVO { @Schema(description = "可发起的用户数组") private List startUsers; + @Schema(description = "可发起的部门数组") + private List startDepts; + @Schema(description = "BPMN XML") private String bpmnXml; diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmModelConvert.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmModelConvert.java index 19ec8491ee..5f3faa33e6 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmModelConvert.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmModelConvert.java @@ -4,6 +4,7 @@ import cn.hutool.core.util.ArrayUtil; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.bpm.controller.admin.base.dept.DeptSimpleBaseVO; import cn.iocoder.yudao.module.bpm.controller.admin.base.user.UserSimpleBaseVO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelRespVO; @@ -13,6 +14,7 @@ import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmPro import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; +import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import org.flowable.common.engine.impl.db.SuspensionState; import org.flowable.engine.repository.Deployment; @@ -43,7 +45,8 @@ public interface BpmModelConvert { Map categoryMap, Map deploymentMap, Map processDefinitionMap, - Map userMap) { + Map userMap, + Map deptMap) { List result = convertList(list, model -> { BpmModelMetaInfoVO metaInfo = parseMetaInfo(model); BpmFormDO form = metaInfo != null ? formMap.get(metaInfo.getFormId()) : null; @@ -52,7 +55,8 @@ public interface BpmModelConvert { ProcessDefinition processDefinition = model.getDeploymentId() != null ? processDefinitionMap.get(model.getDeploymentId()) : null; List startUsers = metaInfo != null ? convertList(metaInfo.getStartUserIds(), userMap::get) : null; - return buildModel0(model, metaInfo, form, category, deployment, processDefinition, startUsers); + List startDepts = metaInfo != null ? convertList(metaInfo.getStartDeptIds(), deptMap::get) : null; + return buildModel0(model, metaInfo, form, category, deployment, processDefinition, startUsers, startDepts); }); // 排序 result.sort(Comparator.comparing(BpmModelMetaInfoVO::getSort)); @@ -61,7 +65,7 @@ public interface BpmModelConvert { default BpmModelRespVO buildModel(Model model, byte[] bpmnBytes, BpmSimpleModelNodeVO simpleModel) { BpmModelMetaInfoVO metaInfo = parseMetaInfo(model); - BpmModelRespVO modelVO = buildModel0(model, metaInfo, null, null, null, null, null); + BpmModelRespVO modelVO = buildModel0(model, metaInfo, null, null, null, null, null, null); if (ArrayUtil.isNotEmpty(bpmnBytes)) { modelVO.setBpmnXml(BpmnModelUtils.getBpmnXml(bpmnBytes)); } @@ -72,7 +76,8 @@ public interface BpmModelConvert { default BpmModelRespVO buildModel0(Model model, BpmModelMetaInfoVO metaInfo, BpmFormDO form, BpmCategoryDO category, Deployment deployment, ProcessDefinition processDefinition, - List startUsers) { + List startUsers, + List startDepts) { BpmModelRespVO modelRespVO = new BpmModelRespVO().setId(model.getId()).setName(model.getName()) .setKey(model.getKey()).setCategory(model.getCategory()) .setCreateTime(DateUtils.of(model.getCreateTime())); @@ -96,6 +101,8 @@ public interface BpmModelConvert { } // User modelRespVO.setStartUsers(BeanUtils.toBean(startUsers, UserSimpleBaseVO.class)); + // Dept + modelRespVO.setStartDepts(BeanUtils.toBean(startDepts, DeptSimpleBaseVO.class)); return modelRespVO; } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java index 86c83ed611..786a96c3bc 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java @@ -151,6 +151,14 @@ public class BpmProcessDefinitionInfoDO extends BaseDO { @TableField(typeHandler = LongListTypeHandler.class) // 为了可以使用 find_in_set 进行过滤 private List startUserIds; + /** + * 可发起部门编号数组 + * + * 关联 {@link AdminUserRespDTO#getId()} 字段的数组 + */ + @TableField(typeHandler = LongListTypeHandler.class) + private List startDeptIds; + /** * 可管理用户编号数组 * diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java index 14ee084063..86bb93062d 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java @@ -12,6 +12,8 @@ import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitio import cn.iocoder.yudao.module.bpm.dal.mysql.definition.BpmProcessDefinitionInfoMapper; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; @@ -49,6 +51,8 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ @Resource private BpmProcessDefinitionInfoMapper processDefinitionMapper; + @Resource + private AdminUserApi adminUserApi; @Override public ProcessDefinition getProcessDefinition(String id) { @@ -88,12 +92,23 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ if (processDefinition == null) { return false; } - // 为空,则所有人都可以发起 - if (CollUtil.isEmpty(processDefinition.getStartUserIds())) { - return true; + + // 获取用户所在部门 + AdminUserRespDTO user = adminUserApi.getUser(userId); + Long userDeptId = user != null ? user.getDeptId() : null; + + // 校验用户是否在允许发起的用户列表中 + if (!CollUtil.isEmpty(processDefinition.getStartUserIds())) { + return processDefinition.getStartUserIds().contains(userId); } - // 不为空,则需要存在里面 - return processDefinition.getStartUserIds().contains(userId); + + // 校验用户是否在允许发起的部门列表中 + if (!CollUtil.isEmpty(processDefinition.getStartDeptIds()) && userDeptId != null) { + return processDefinition.getStartDeptIds().contains(userDeptId); + } + + // 都为空,则所有人都可以发起 + return true; } @Override From ecc3bd281c3680865546b83772d8bc8d2b3f2177 Mon Sep 17 00:00:00 2001 From: xiaoxin <718949661@qq.com> Date: Mon, 17 Mar 2025 15:44:17 +0800 Subject: [PATCH 08/36] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E3=80=91AI=EF=BC=9A=E8=AE=AF=E9=A3=9E=20PPT=20API=20?= =?UTF-8?q?=E5=AF=B9=E6=8E=A5=EF=BC=8C=E6=B5=8B=E8=AF=95=E7=94=A8=E4=BE=8B?= =?UTF-8?q?=E5=AE=8C=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/{WddApi.java => WddPptApi.java} | 82 +- .../core/model/xunfei/api/XunfeiPptApi.java | 766 ++++++++++++++++++ .../{WddApiTests.java => WddPptApiTests.java} | 38 +- .../ai/ppt/xunfei/XunfeiPptApiTests.java | 300 +++++++ 4 files changed, 1124 insertions(+), 62 deletions(-) rename yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/wenduoduo/api/{WddApi.java => WddPptApi.java} (93%) create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xunfei/api/XunfeiPptApi.java rename yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/ppt/wdd/{WddApiTests.java => WddPptApiTests.java} (91%) create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/ppt/xunfei/XunfeiPptApiTests.java diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/wenduoduo/api/WddApi.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/wenduoduo/api/WddPptApi.java similarity index 93% rename from yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/wenduoduo/api/WddApi.java rename to yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/wenduoduo/api/WddPptApi.java index 4a827b04a6..b752f73352 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/wenduoduo/api/WddApi.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/wenduoduo/api/WddPptApi.java @@ -33,7 +33,7 @@ import java.util.function.Predicate; * @author xiaoxin */ @Slf4j -public class WddApi { +public class WddPptApi { public static final String BASE_URL = "https://docmee.cn"; @@ -49,7 +49,7 @@ public class WddApi { sink.error(new IllegalStateException("[wdd-api] 调用失败!")); }); - public WddApi(String baseUrl) { + public WddPptApi(String baseUrl) { this.webClient = WebClient.builder() .baseUrl(baseUrl) .defaultHeaders((headers) -> headers.setContentType(MediaType.APPLICATION_JSON)) @@ -161,42 +161,6 @@ public class WddApi { .block(); } - /** - * 生成大纲内容 - * - * @return 大纲内容流 - */ - public Flux> generateOutlineContent(String token, GenerateOutlineRequest request) { - return this.webClient.post() - .uri("/api/ppt/v2/generateContent") - .header("token", token) - .body(Mono.just(request), GenerateOutlineRequest.class) - .retrieve() - .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) - .bodyToFlux(new ParameterizedTypeReference<>() { - }); - } - - /** - * 修改大纲内容 - * - * @param id 任务ID - * @param markdown 大纲内容markdown - * @param question 用户修改建议 - * @return 大纲内容流 - */ - public Flux> updateOutlineContent(String token, UpdateOutlineRequest request) { - return this.webClient.post() - .uri("/api/ppt/v2/updateContent") - .header("token", token) - .body(Mono.just(request), UpdateOutlineRequest.class) - .retrieve() - .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) - .bodyToFlux(new ParameterizedTypeReference<>() { - }); - } - - /** * 分页查询PPT模板 * @@ -204,7 +168,7 @@ public class WddApi { * @param request 请求体 * @return 模板列表 */ - public PagePptTemplateInfo getPptTemplatePage(String token, TemplateQueryRequest request) { + public PagePptTemplateInfo getTemplatePage(String token, TemplateQueryRequest request) { return this.webClient.post() .uri("/api/ppt/templates") .header("token", token) @@ -216,17 +180,49 @@ public class WddApi { .block(); } + /** + * 生成大纲内容 + * + * @return 大纲内容流 + */ + public Flux> createOutline(String token, CreateOutlineRequest request) { + return this.webClient.post() + .uri("/api/ppt/v2/generateContent") + .header("token", token) + .body(Mono.just(request), CreateOutlineRequest.class) + .retrieve() + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) + .bodyToFlux(new ParameterizedTypeReference<>() { + }); + } + + /** + * 修改大纲内容 + * + * @param request 请求体 + * @return 大纲内容流 + */ + public Flux> updateOutline(String token, UpdateOutlineRequest request) { + return this.webClient.post() + .uri("/api/ppt/v2/updateContent") + .header("token", token) + .body(Mono.just(request), UpdateOutlineRequest.class) + .retrieve() + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) + .bodyToFlux(new ParameterizedTypeReference<>() { + }); + } /** * 生成PPT * * @return PPT信息 */ - public PptInfo generatePptx(String token, GeneratePptxRequest request) { + public PptInfo create(String token, CreatePptRequest request) { return this.webClient.post() .uri("/api/ppt/v2/generatePptx") .header("token", token) - .body(Mono.just(request), GeneratePptxRequest.class) + .body(Mono.just(request), CreatePptRequest.class) .retrieve() .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) .bodyToMono(ApiResponse.class) @@ -278,7 +274,7 @@ public class WddApi { * 生成大纲内容请求 */ @JsonInclude(value = JsonInclude.Include.NON_NULL) - public record GenerateOutlineRequest( + public record CreateOutlineRequest( String id, String length, String scene, @@ -303,7 +299,7 @@ public class WddApi { * 生成PPT请求 */ @JsonInclude(value = JsonInclude.Include.NON_NULL) - public record GeneratePptxRequest( + public record CreatePptRequest( String id, String templateId, String markdown diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xunfei/api/XunfeiPptApi.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xunfei/api/XunfeiPptApi.java new file mode 100644 index 0000000000..fe8ba4c046 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xunfei/api/XunfeiPptApi.java @@ -0,0 +1,766 @@ +package cn.iocoder.yudao.framework.ai.core.model.xunfei.api; + +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.http.HttpStatusCode; +import org.springframework.http.MediaType; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.reactive.function.BodyInserters; +import org.springframework.web.reactive.function.client.ClientResponse; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Base64; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.function.Predicate; + +/** + * 讯飞智能PPT生成 API + *

+ * 对接讯飞:智能 PPT 生成 API + * + * @author xiaoxin + */ +@Slf4j +public class XunfeiPptApi { + + public static final String BASE_URL = "https://zwapi.xfyun.cn/api/ppt/v2"; + + private final WebClient webClient; + private final String appId; + private final String apiSecret; + + private final Predicate STATUS_PREDICATE = status -> !status.is2xxSuccessful(); + + private final Function>> EXCEPTION_FUNCTION = + reqParam -> response -> response.bodyToMono(String.class).handle((responseBody, sink) -> { + log.error("[xunfei-ppt-api] 调用失败!请求参数:[{}],响应数据: [{}]", reqParam, responseBody); + sink.error(new IllegalStateException("[xunfei-ppt-api] 调用失败!")); + }); + + public XunfeiPptApi(String baseUrl, String appId, String apiSecret) { + this.webClient = WebClient.builder() + .baseUrl(baseUrl) + .build(); + this.appId = appId; + this.apiSecret = apiSecret; + } + + /** + * 获取签名 + * + * @return 签名信息 + */ + private SignatureInfo getSignature() { + long timestamp = System.currentTimeMillis() / 1000; + String ts = String.valueOf(timestamp); + String signature = generateSignature(appId, apiSecret, timestamp); + return new SignatureInfo(appId, ts, signature); + } + + /** + * 生成签名 + * + * @param appId 应用ID + * @param apiSecret 应用密钥 + * @param timestamp 时间戳(秒) + * @return 签名 + */ + private String generateSignature(String appId, String apiSecret, long timestamp) { + try { + String auth = md5(appId + timestamp); + return hmacSHA1Encrypt(auth, apiSecret); + } catch (NoSuchAlgorithmException | InvalidKeyException e) { + log.error("[xunfei-ppt-api] 生成签名失败", e); + throw new IllegalStateException("[xunfei-ppt-api] 生成签名失败"); + } + } + + /** + * HMAC SHA1 加密 + */ + private String hmacSHA1Encrypt(String encryptText, String encryptKey) + throws NoSuchAlgorithmException, InvalidKeyException { + SecretKeySpec keySpec = new SecretKeySpec( + encryptKey.getBytes(StandardCharsets.UTF_8), "HmacSHA1"); + + Mac mac = Mac.getInstance("HmacSHA1"); + mac.init(keySpec); + byte[] result = mac.doFinal(encryptText.getBytes(StandardCharsets.UTF_8)); + + return Base64.getEncoder().encodeToString(result); + } + + /** + * MD5 哈希 + */ + private String md5(String text) throws NoSuchAlgorithmException { + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] digest = md.digest(text.getBytes(StandardCharsets.UTF_8)); + + StringBuilder sb = new StringBuilder(); + for (byte b : digest) { + sb.append(String.format("%02x", b)); + } + return sb.toString(); + } + + /** + * 获取PPT模板列表 + * + * @param style 风格,如"商务" + * @param pageSize 每页数量 + * @return 模板列表 + */ + public TemplatePageResponse getTemplatePage(String style, Integer pageSize) { + SignatureInfo signInfo = getSignature(); + Map requestBody = new HashMap<>(); + requestBody.put("style", style); + requestBody.put("pageSize", pageSize != null ? pageSize.toString() : "10"); + + return this.webClient.post() + .uri("/template/list") + .header("appId", signInfo.appId) + .header("timestamp", signInfo.timestamp) + .header("signature", signInfo.signature) + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(requestBody) + .retrieve() + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(requestBody)) + .bodyToMono(TemplatePageResponse.class) + .block(); + } + + /** + * 创建大纲(通过文本) + * + * @param query 查询文本 + * @return 大纲创建响应 + */ + public CreateResponse createOutline(String query) { + SignatureInfo signInfo = getSignature(); + MultiValueMap formData = new LinkedMultiValueMap<>(); + formData.add("query", query); + + return this.webClient.post() + .uri("/createOutline") + .header("appId", signInfo.appId) + .header("timestamp", signInfo.timestamp) + .header("signature", signInfo.signature) + .contentType(MediaType.MULTIPART_FORM_DATA) + .body(BodyInserters.fromMultipartData(formData)) + .retrieve() + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(formData)) + .bodyToMono(CreateResponse.class) + .block(); + } + + + /** + * 直接创建PPT(简化版 - 通过文本) + * + * @param query 查询文本 + * @return 创建响应 + */ + public CreateResponse create(String query) { + CreatePptRequest request = CreatePptRequest.builder() + .query(query) + .build(); + return create(request); + } + + /** + * 直接创建PPT(简化版 - 通过文件) + * + * @param file 文件 + * @param fileName 文件名 + * @return 创建响应 + */ + public CreateResponse create(MultipartFile file, String fileName) { + CreatePptRequest request = CreatePptRequest.builder() + .file(file) + .fileName(fileName) + .build(); + return create(request); + } + + /** + * 直接创建PPT(完整版) + * + * @param request 请求参数 + * @return 创建响应 + */ + public CreateResponse create(CreatePptRequest request) { + SignatureInfo signInfo = getSignature(); + MultiValueMap formData = buildCreateFormData(request); + + return this.webClient.post() + .uri("/create") + .header("appId", signInfo.appId) + .header("timestamp", signInfo.timestamp) + .header("signature", signInfo.signature) + .contentType(MediaType.MULTIPART_FORM_DATA) + .body(BodyInserters.fromMultipartData(formData)) + .retrieve() + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(formData)) + .bodyToMono(CreateResponse.class) + .block(); + } + + + /** + * 通过大纲创建PPT(简化版) + * + * @param outline 大纲内容 + * @param query 查询文本 + * @return 创建响应 + */ + public CreateResponse createPptByOutline(OutlineData outline, String query) { + CreatePptByOutlineRequest request = CreatePptByOutlineRequest.builder() + .outline(outline) + .query(query) + .build(); + return createPptByOutline(request); + } + + /** + * 通过大纲创建PPT(完整版) + * + * @param request 请求参数 + * @return 创建响应 + */ + public CreateResponse createPptByOutline(CreatePptByOutlineRequest request) { + SignatureInfo signInfo = getSignature(); + + return this.webClient.post() + .uri("/createPptByOutline") + .header("appId", signInfo.appId) + .header("timestamp", signInfo.timestamp) + .header("signature", signInfo.signature) + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(request) + .retrieve() + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) + .bodyToMono(CreateResponse.class) + .block(); + } + + /** + * 检查PPT生成进度 + * + * @param sid 任务ID + * @return 进度响应 + */ + public ProgressResponse checkProgress(String sid) { + SignatureInfo signInfo = getSignature(); + + return this.webClient.get() + .uri(uriBuilder -> uriBuilder + .path("/progress") + .queryParam("sid", sid) + .build()) + .header("appId", signInfo.appId) + .header("timestamp", signInfo.timestamp) + .header("signature", signInfo.signature) + .retrieve() + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(sid)) + .bodyToMono(ProgressResponse.class) + .block(); + } + + /** + * 签名信息 + */ + @JsonInclude(value = JsonInclude.Include.NON_NULL) + private record SignatureInfo( + String appId, + String timestamp, + String signature + ) { + } + + /** + * 模板列表响应 + */ + @JsonInclude(value = JsonInclude.Include.NON_NULL) + public record TemplatePageResponse( + boolean flag, + int code, + String desc, + Integer count, + TemplatePageData data + ) { + } + + /** + * 模板列表数据 + */ + @JsonInclude(value = JsonInclude.Include.NON_NULL) + public record TemplatePageData( + String total, + List records, + Integer pageNum + ) { + } + + /** + * 模板信息 + */ + @JsonInclude(value = JsonInclude.Include.NON_NULL) + public record TemplateInfo( + String templateIndexId, + Integer pageCount, + String type, + String color, + String industry, + String style, + String detailImage + ) { + } + + /** + * 创建响应 + */ + @JsonInclude(value = JsonInclude.Include.NON_NULL) + public record CreateResponse( + boolean flag, + int code, + String desc, + Integer count, + CreateResponseData data + ) { + } + + /** + * 创建响应数据 + */ + @JsonInclude(value = JsonInclude.Include.NON_NULL) + public record CreateResponseData( + String sid, + String coverImgSrc, + String title, + String subTitle, + OutlineData outline + ) { + } + + /** + * 大纲数据结构 + */ + @JsonInclude(value = JsonInclude.Include.NON_NULL) + public record OutlineData( + String title, + String subTitle, + List chapters + ) { + /** + * 章节结构 + */ + @JsonInclude(value = JsonInclude.Include.NON_NULL) + public record Chapter( + String chapterTitle, + List chapterContents + ) { + /** + * 章节内容 + */ + @JsonInclude(value = JsonInclude.Include.NON_NULL) + public record ChapterContent( + String chapterTitle + ) { + } + } + + /** + * 将大纲对象转换为JSON字符串 + * + * @return 大纲JSON字符串 + */ + public String toJsonString() { + return JsonUtils.toJsonString(this); + } + } + + /** + * 进度响应 + */ + @JsonInclude(value = JsonInclude.Include.NON_NULL) + public record ProgressResponse( + int code, + String desc, + ProgressResponseData data + ) { + } + + /** + * 进度响应数据 + */ + @JsonInclude(value = JsonInclude.Include.NON_NULL) + public record ProgressResponseData( + int process, + String pptId, + String pptUrl, + String pptStatus, // PPT构建状态:building(构建中),done(已完成),build_failed(生成失败) + String aiImageStatus, // ai配图状态:building(构建中),done(已完成) + String cardNoteStatus, // 演讲备注状态:building(构建中),done(已完成) + String errMsg, // 生成PPT的失败信息 + Integer totalPages, // 生成PPT的总页数 + Integer donePages // 生成PPT的完成页数 + ) { + /** + * 是否全部完成 + * + * @return 是否全部完成 + */ + public boolean isAllDone() { + return "done".equals(pptStatus) + && ("done".equals(aiImageStatus) || aiImageStatus == null) + && ("done".equals(cardNoteStatus) || cardNoteStatus == null); + } + + /** + * 是否失败 + * + * @return 是否失败 + */ + public boolean isFailed() { + return "build_failed".equals(pptStatus); + } + + /** + * 获取进度百分比 + * + * @return 进度百分比 + */ + public int getProgressPercent() { + if (totalPages == null || totalPages == 0 || donePages == null) { + return process; // 兼容旧版返回 + } + return (int) (donePages * 100.0 / totalPages); + } + } + + /** + * 通过大纲创建PPT请求参数 + */ + @JsonInclude(value = JsonInclude.Include.NON_NULL) + public record CreatePptByOutlineRequest( + String query, // 用户生成PPT要求(最多8000字) + String outlineSid, // 已生成大纲后,响应返回的请求大纲唯一id + OutlineData outline, // 大纲内容 + String templateId, // 模板ID + String businessId, // 业务ID(非必传) + String author, // PPT作者名 + Boolean isCardNote, // 是否生成PPT演讲备注 + Boolean search, // 是否联网搜索 + String language, // 语种 + String fileUrl, // 文件地址 + String fileName, // 文件名(带文件名后缀) + Boolean isFigure, // 是否自动配图 + String aiImage // ai配图类型:normal、advanced + ) { + /** + * 创建构建器 + * + * @return 构建器 + */ + public static Builder builder() { + return new Builder(); + } + + /** + * 构建器类 + */ + public static class Builder { + private String query; + private String outlineSid; + private OutlineData outline; + private String templateId; + private String businessId; + private String author; + private Boolean isCardNote; + private Boolean search; + private String language; + private String fileUrl; + private String fileName; + private Boolean isFigure; + private String aiImage; + + public Builder query(String query) { + this.query = query; + return this; + } + + public Builder outlineSid(String outlineSid) { + this.outlineSid = outlineSid; + return this; + } + + public Builder outline(OutlineData outline) { + this.outline = outline; + return this; + } + + public Builder templateId(String templateId) { + this.templateId = templateId; + return this; + } + + public Builder businessId(String businessId) { + this.businessId = businessId; + return this; + } + + public Builder author(String author) { + this.author = author; + return this; + } + + public Builder isCardNote(Boolean isCardNote) { + this.isCardNote = isCardNote; + return this; + } + + public Builder search(Boolean search) { + this.search = search; + return this; + } + + public Builder language(String language) { + this.language = language; + return this; + } + + public Builder fileUrl(String fileUrl) { + this.fileUrl = fileUrl; + return this; + } + + public Builder fileName(String fileName) { + this.fileName = fileName; + return this; + } + + public Builder isFigure(Boolean isFigure) { + this.isFigure = isFigure; + return this; + } + + public Builder aiImage(String aiImage) { + this.aiImage = aiImage; + return this; + } + + public CreatePptByOutlineRequest build() { + return new CreatePptByOutlineRequest( + query, outlineSid, outline, templateId, businessId, author, + isCardNote, search, language, fileUrl, fileName, isFigure, aiImage + ); + } + } + } + + /** + * 构建创建PPT的表单数据 + * + * @param request 请求参数 + * @return 表单数据 + */ + private MultiValueMap buildCreateFormData(CreatePptRequest request) { + MultiValueMap formData = new LinkedMultiValueMap<>(); + // 添加请求参数 + if (request.query() != null) { + formData.add("query", request.query()); + } + + if (request.file() != null) { + try { + formData.add("file", new ByteArrayResource(request.file().getBytes()) { + @Override + public String getFilename() { + return request.file().getOriginalFilename(); + } + }); + } catch (IOException e) { + log.error("[xunfei-ppt-api] 文件处理失败", e); + throw new IllegalStateException("[xunfei-ppt-api] 文件处理失败", e); + } + } + + if (request.fileUrl() != null) { + formData.add("fileUrl", request.fileUrl()); + } + + if (request.fileName() != null) { + formData.add("fileName", request.fileName()); + } + + if (request.templateId() != null) { + formData.add("templateId", request.templateId()); + } + + if (request.businessId() != null) { + formData.add("businessId", request.businessId()); + } + + if (request.author() != null) { + formData.add("author", request.author()); + } + + if (request.isCardNote() != null) { + formData.add("isCardNote", request.isCardNote().toString()); + } + + if (request.search() != null) { + formData.add("search", request.search().toString()); + } + + if (request.language() != null) { + formData.add("language", request.language()); + } + + if (request.isFigure() != null) { + formData.add("isFigure", request.isFigure().toString()); + } + + if (request.aiImage() != null) { + formData.add("aiImage", request.aiImage()); + } + + return formData; + } + + /** + * 直接生成PPT请求参数 + */ + @JsonInclude(value = JsonInclude.Include.NON_NULL) + public record CreatePptRequest( + String query, // 用户生成PPT要求(最多8000字) + MultipartFile file, // 上传文件 + String fileUrl, // 文件地址 + String fileName, // 文件名(带文件名后缀) + String templateId, // 模板ID + String businessId, // 业务ID(非必传) + String author, // PPT作者名 + Boolean isCardNote, // 是否生成PPT演讲备注 + Boolean search, // 是否联网搜索 + String language, // 语种 + Boolean isFigure, // 是否自动配图 + String aiImage // ai配图类型:normal、advanced + ) { + /** + * 创建构建器 + * + * @return 构建器 + */ + public static Builder builder() { + return new Builder(); + } + + /** + * 构建器类 + */ + public static class Builder { + private String query; + private MultipartFile file; + private String fileUrl; + private String fileName; + private String templateId; + private String businessId; + private String author; + private Boolean isCardNote; + private Boolean search; + private String language; + private Boolean isFigure; + private String aiImage; + + public Builder query(String query) { + this.query = query; + return this; + } + + public Builder file(MultipartFile file) { + this.file = file; + return this; + } + + public Builder fileUrl(String fileUrl) { + this.fileUrl = fileUrl; + return this; + } + + public Builder fileName(String fileName) { + this.fileName = fileName; + return this; + } + + public Builder templateId(String templateId) { + this.templateId = templateId; + return this; + } + + public Builder businessId(String businessId) { + this.businessId = businessId; + return this; + } + + public Builder author(String author) { + this.author = author; + return this; + } + + public Builder isCardNote(Boolean isCardNote) { + this.isCardNote = isCardNote; + return this; + } + + public Builder search(Boolean search) { + this.search = search; + return this; + } + + public Builder language(String language) { + this.language = language; + return this; + } + + public Builder isFigure(Boolean isFigure) { + this.isFigure = isFigure; + return this; + } + + public Builder aiImage(String aiImage) { + this.aiImage = aiImage; + return this; + } + + public CreatePptRequest build() { + // 验证参数 + if (query == null && file == null && fileUrl == null) { + throw new IllegalArgumentException("query、file、fileUrl必填其一"); + } + if ((file != null || fileUrl != null) && fileName == null) { + throw new IllegalArgumentException("如果传file或者fileUrl,fileName必填"); + } + return new CreatePptRequest( + query, file, fileUrl, fileName, templateId, businessId, author, + isCardNote, search, language, isFigure, aiImage + ); + } + } + } +} \ No newline at end of file diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/ppt/wdd/WddApiTests.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/ppt/wdd/WddPptApiTests.java similarity index 91% rename from yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/ppt/wdd/WddApiTests.java rename to yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/ppt/wdd/WddPptApiTests.java index b64a4e3fe1..e01311c8bd 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/ppt/wdd/WddApiTests.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/ppt/wdd/WddPptApiTests.java @@ -1,6 +1,6 @@ package cn.iocoder.yudao.framework.ai.ppt.wdd; -import cn.iocoder.yudao.framework.ai.core.model.wenduoduo.api.WddApi; +import cn.iocoder.yudao.framework.ai.core.model.wenduoduo.api.WddPptApi; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -11,26 +11,26 @@ import java.util.Objects; /** - * {@link WddApi} 集成测试 + * {@link WddPptApi} 集成测试 * * @author xiaoxin */ -public class WddApiTests { +public class WddPptApiTests { - private final WddApi wddApi = new WddApi("https://docmee.cn"); + private final WddPptApi wddPptApi = new WddPptApi("https://docmee.cn"); - private final String token = "sk_FJo7sKErrrEs5CIZz1"; + private final String token = ""; @Test //获取token @Disabled public void testCreateApiToken() { // 准备参数 - String apiKey = "ak_RK1rm7TrEv3E3JSWIK"; - WddApi.CreateTokenRequest request = new WddApi.CreateTokenRequest(apiKey); + String apiKey = ""; + WddPptApi.CreateTokenRequest request = new WddPptApi.CreateTokenRequest(apiKey); // 调用方法 - String token = wddApi.createApiToken(request); + String token = wddPptApi.createApiToken(request); // 打印结果 System.out.println(token); } @@ -39,7 +39,7 @@ public class WddApiTests { @Test // 创建任务 @Disabled public void testCreateTask() { - WddApi.ApiResponse apiResponse = wddApi.createTask(token, 1, "dify 介绍", null); + WddPptApi.ApiResponse apiResponse = wddPptApi.createTask(token, 1, "dify 介绍", null); System.out.println(apiResponse); } @@ -47,9 +47,9 @@ public class WddApiTests { @Test // 创建大纲 @Disabled public void testGenerateOutlineRequest() { - WddApi.GenerateOutlineRequest request = new WddApi.GenerateOutlineRequest("1901449466163105792", "medium", null, null, null, null); + WddPptApi.CreateOutlineRequest request = new WddPptApi.CreateOutlineRequest("1901539019628613632", "medium", null, null, null, null); //调用 - Flux> flux = wddApi.generateOutlineContent(token, request); + Flux> flux = wddPptApi.createOutline(token, request); StringBuffer contentBuffer = new StringBuffer(); flux.doOnNext(chunk -> { contentBuffer.append(chunk.get("text")); @@ -66,10 +66,10 @@ public class WddApiTests { @Test // 修改大纲 @Disabled - public void testUpdateOutlineContentRequest() { - WddApi.UpdateOutlineRequest request = new WddApi.UpdateOutlineRequest("1901449466163105792", TEST_OUT_LINE_CONTENT, "精简一点,三个章节即可"); + public void testUpdateOutlineRequest() { + WddPptApi.UpdateOutlineRequest request = new WddPptApi.UpdateOutlineRequest("1901539019628613632", TEST_OUT_LINE_CONTENT, "精简一点,三个章节即可"); //调用 - Flux> flux = wddApi.updateOutlineContent(token, request); + Flux> flux = wddPptApi.updateOutline(token, request); StringBuffer contentBuffer = new StringBuffer(); flux.doOnNext(chunk -> { contentBuffer.append(chunk.get("text")); @@ -88,10 +88,10 @@ public class WddApiTests { @Disabled public void testGetPptTemplatePage() { // 准备参数 - WddApi.TemplateQueryRequest.Filter filter = new WddApi.TemplateQueryRequest.Filter(1, null, null, null); - WddApi.TemplateQueryRequest request = new WddApi.TemplateQueryRequest(1, 10, filter); + WddPptApi.TemplateQueryRequest.Filter filter = new WddPptApi.TemplateQueryRequest.Filter(1, null, null, null); + WddPptApi.TemplateQueryRequest request = new WddPptApi.TemplateQueryRequest(1, 10, filter); //调用 - WddApi.PagePptTemplateInfo pptTemplatePage = wddApi.getPptTemplatePage(token, request); + WddPptApi.PagePptTemplateInfo pptTemplatePage = wddPptApi.getTemplatePage(token, request); // 打印结果 System.out.println(pptTemplatePage); } @@ -101,9 +101,9 @@ public class WddApiTests { @Disabled public void testGeneratePptx() { // 准备参数 - WddApi.GeneratePptxRequest request = new WddApi.GeneratePptxRequest("1900913633555255296", "1804885538940116992", TEST_OUT_LINE_CONTENT); + WddPptApi.CreatePptRequest request = new WddPptApi.CreatePptRequest("1901539019628613632", "1805081814809960448", TEST_OUT_LINE_CONTENT); //调用 - WddApi.PptInfo pptInfo = wddApi.generatePptx("", request); + WddPptApi.PptInfo pptInfo = wddPptApi.create(token, request); // 打印结果 System.out.println(pptInfo); } diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/ppt/xunfei/XunfeiPptApiTests.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/ppt/xunfei/XunfeiPptApiTests.java new file mode 100644 index 0000000000..b710f59942 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/ppt/xunfei/XunfeiPptApiTests.java @@ -0,0 +1,300 @@ +package cn.iocoder.yudao.framework.ai.ppt.xunfei; + +import cn.iocoder.yudao.framework.ai.core.model.xunfei.api.XunfeiPptApi; +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; + +/** + * {@link XunfeiPptApi} 集成测试 + * + * @author xiaoxin + */ +public class XunfeiPptApiTests { + + // 讯飞API配置信息,实际使用时请替换为您的应用信息 + private static final String APP_ID = ""; + private static final String API_SECRET = ""; + + private final XunfeiPptApi xunfeiPptApi = new XunfeiPptApi(XunfeiPptApi.BASE_URL, APP_ID, API_SECRET); + + @Test // 获取PPT模板列表 + @Disabled + public void testGetTemplatePage() { + // 调用方法 + XunfeiPptApi.TemplatePageResponse response = xunfeiPptApi.getTemplatePage("商务", 10); + // 打印结果 + System.out.println("模板列表响应:" + JsonUtils.toJsonString(response)); + + if (response != null && response.data() != null && response.data().records() != null) { + System.out.println("模板总数:" + response.data().total()); + System.out.println("当前页码:" + response.data().pageNum()); + System.out.println("模板数量:" + response.data().records().size()); + + // 打印第一个模板的信息(如果存在) + if (!response.data().records().isEmpty()) { + XunfeiPptApi.TemplateInfo firstTemplate = response.data().records().get(0); + System.out.println("模板ID:" + firstTemplate.templateIndexId()); + System.out.println("模板风格:" + firstTemplate.style()); + System.out.println("模板颜色:" + firstTemplate.color()); + System.out.println("模板行业:" + firstTemplate.industry()); + } + } + } + + @Test // 创建大纲(通过文本) + @Disabled + public void testCreateOutline() { + XunfeiPptApi.CreateResponse response = getCreateResponse(); + // 打印结果 + System.out.println("创建大纲响应:" + JsonUtils.toJsonString(response)); + + // 保存sid和outline用于后续测试 + if (response != null && response.data() != null) { + System.out.println("sid: " + response.data().sid()); + if (response.data().outline() != null) { + // 使用OutlineData的toJsonString方法 + System.out.println("outline: " + response.data().outline().toJsonString()); + // 将outline对象转换为JSON字符串,用于后续createPptByOutline测试 + String outlineJson = response.data().outline().toJsonString(); + System.out.println("可用于createPptByOutline的outline字符串: " + outlineJson); + } + } + } + + // 创建大纲(通过文本) + private XunfeiPptApi.CreateResponse getCreateResponse() { + // 准备参数 + String param = "智能体平台 Dify 介绍"; + // 调用方法 + return xunfeiPptApi.createOutline(param); + } + + @Test // 通过大纲创建PPT(完整参数) + @Disabled + public void testCreatePptByOutlineWithFullParams() { + // 创建大纲对象 + XunfeiPptApi.CreateResponse createResponse = getCreateResponse(); + // 调用方法 + XunfeiPptApi.CreateResponse response = xunfeiPptApi.createPptByOutline(createResponse.data().outline(), "精简一些,不要超过6个章节"); + // 打印结果 + System.out.println("通过大纲创建PPT响应:" + JsonUtils.toJsonString(response)); + + // 保存sid用于后续进度查询 + if (response != null && response.data() != null) { + System.out.println("sid: " + response.data().sid()); + if (response.data().coverImgSrc() != null) { + System.out.println("封面图片: " + response.data().coverImgSrc()); + } + } + } + + @Test // 检查PPT生成进度 + @Disabled + public void testCheckProgress() { + // 准备参数 - 使用之前创建PPT时返回的sid + String sid = "e96dac09f2ec4ee289f029a5fb874ecd"; // 替换为实际的sid + + // 调用方法 + XunfeiPptApi.ProgressResponse response = xunfeiPptApi.checkProgress(sid); + // 打印结果 + System.out.println("检查进度响应:" + JsonUtils.toJsonString(response)); + + // 安全地访问响应数据 + if (response != null && response.data() != null) { + XunfeiPptApi.ProgressResponseData data = response.data(); + + // 打印PPT生成状态 + System.out.println("PPT构建状态: " + data.pptStatus()); + System.out.println("AI配图状态: " + data.aiImageStatus()); + System.out.println("演讲备注状态: " + data.cardNoteStatus()); + + // 打印进度信息 + if (data.totalPages() != null && data.donePages() != null) { + System.out.println("总页数: " + data.totalPages()); + System.out.println("已完成页数: " + data.donePages()); + System.out.println("完成进度: " + data.getProgressPercent() + "%"); + } else { + System.out.println("进度: " + data.process() + "%"); + } + + // 检查是否完成 + if (data.isAllDone()) { + System.out.println("PPT生成已完成!"); + System.out.println("PPT下载链接: " + data.pptUrl()); + } + // 检查是否失败 + else if (data.isFailed()) { + System.out.println("PPT生成失败!"); + System.out.println("错误信息: " + data.errMsg()); + } + // 正在进行中 + else { + System.out.println("PPT生成中,请稍后再查询..."); + } + } + } + + @Test // 轮询检查PPT生成进度直到完成 + @Disabled + public void testPollCheckProgress() throws InterruptedException { + // 准备参数 - 使用之前创建PPT时返回的sid + String sid = "fa36e926f2ed434987fcb4c1f0776ffb"; // 替换为实际的sid + + // 最大轮询次数 + int maxPolls = 20; + // 轮询间隔(毫秒)- 讯飞API限流为3秒一次 + long pollInterval = 3500; + + for (int i = 0; i < maxPolls; i++) { + System.out.println("第" + (i + 1) + "次查询进度..."); + + // 调用方法 + XunfeiPptApi.ProgressResponse response = xunfeiPptApi.checkProgress(sid); + + // 安全地访问响应数据 + if (response != null && response.data() != null) { + XunfeiPptApi.ProgressResponseData data = response.data(); + + // 打印进度信息 + System.out.println("PPT构建状态: " + data.pptStatus()); + if (data.totalPages() != null && data.donePages() != null) { + System.out.println("完成进度: " + data.donePages() + "/" + data.totalPages() + + " (" + data.getProgressPercent() + "%)"); + } + + // 检查是否完成 + if (data.isAllDone()) { + System.out.println("PPT生成已完成!"); + System.out.println("PPT下载链接: " + data.pptUrl()); + break; + } + // 检查是否失败 + else if (data.isFailed()) { + System.out.println("PPT生成失败!"); + System.out.println("错误信息: " + data.errMsg()); + break; + } + // 正在进行中,继续轮询 + else { + System.out.println("PPT生成中,等待" + (pollInterval / 1000) + "秒后继续查询..."); + Thread.sleep(pollInterval); + } + } else { + System.out.println("查询失败,等待" + (pollInterval / 1000) + "秒后重试..."); + Thread.sleep(pollInterval); + } + } + } + + @Test // 直接创建PPT(通过文本) + @Disabled + public void testCreatePptByText() { + // 准备参数 + String query = "合肥天气趋势分析,包括近5年的气温变化、降水量变化、极端天气事件,以及对城市生活的影响"; + + // 调用方法 + XunfeiPptApi.CreateResponse response = xunfeiPptApi.create(query); + // 打印结果 + System.out.println("直接创建PPT响应:" + JsonUtils.toJsonString(response)); + + // 保存sid用于后续进度查询 + if (response != null && response.data() != null) { + System.out.println("sid: " + response.data().sid()); + if (response.data().coverImgSrc() != null) { + System.out.println("封面图片: " + response.data().coverImgSrc()); + } + System.out.println("标题: " + response.data().title()); + System.out.println("副标题: " + response.data().subTitle()); + } + } + + @Test // 直接创建PPT(通过文件) + @Disabled + public void testCreatePptByFile() throws IOException { + // 准备参数 + File file = new File("src/test/resources/test.txt"); // 请确保此文件存在 + MultipartFile multipartFile = convertFileToMultipartFile(file); + + // 调用方法 + XunfeiPptApi.CreateResponse response = xunfeiPptApi.create(multipartFile, file.getName()); + // 打印结果 + System.out.println("通过文件创建PPT响应:" + JsonUtils.toJsonString(response)); + + // 保存sid用于后续进度查询 + if (response != null && response.data() != null) { + System.out.println("sid: " + response.data().sid()); + if (response.data().coverImgSrc() != null) { + System.out.println("封面图片: " + response.data().coverImgSrc()); + } + System.out.println("标题: " + response.data().title()); + System.out.println("副标题: " + response.data().subTitle()); + } + } + + @Test // 直接创建PPT(完整参数) + @Disabled + public void testCreatePptWithFullParams() throws IOException { + // 准备参数 + String query = "合肥天气趋势分析,包括近5年的气温变化、降水量变化、极端天气事件,以及对城市生活的影响"; + + // 创建请求对象 + XunfeiPptApi.CreatePptRequest request = XunfeiPptApi.CreatePptRequest.builder() + .query(query) + .language("cn") + .isCardNote(true) + .search(true) + .isFigure(true) + .aiImage("advanced") + .author("测试用户") + .build(); + + // 调用方法 + XunfeiPptApi.CreateResponse response = xunfeiPptApi.create(request); + // 打印结果 + System.out.println("使用完整参数创建PPT响应:" + JsonUtils.toJsonString(response)); + + // 保存sid用于后续进度查询 + if (response != null && response.data() != null) { + String sid = response.data().sid(); + System.out.println("sid: " + sid); + if (response.data().coverImgSrc() != null) { + System.out.println("封面图片: " + response.data().coverImgSrc()); + } + System.out.println("标题: " + response.data().title()); + System.out.println("副标题: " + response.data().subTitle()); + + // 立即查询一次进度 + System.out.println("立即查询进度..."); + XunfeiPptApi.ProgressResponse progressResponse = xunfeiPptApi.checkProgress(sid); + if (progressResponse != null && progressResponse.data() != null) { + XunfeiPptApi.ProgressResponseData progressData = progressResponse.data(); + System.out.println("PPT构建状态: " + progressData.pptStatus()); + if (progressData.totalPages() != null && progressData.donePages() != null) { + System.out.println("完成进度: " + progressData.donePages() + "/" + progressData.totalPages() + + " (" + progressData.getProgressPercent() + "%)"); + } + } + } + } + + /** + * 将File转换为MultipartFile + */ + private MultipartFile convertFileToMultipartFile(File file) throws IOException { + FileInputStream input = new FileInputStream(file); + return new MockMultipartFile( + "file", + file.getName(), + "text/plain", + input.readAllBytes() + ); + } + +} \ No newline at end of file From 71add4b0586368181a569bdf0fb3d7272bc1986d Mon Sep 17 00:00:00 2001 From: YunaiV Date: Mon, 17 Mar 2025 20:45:26 +0800 Subject: [PATCH 09/36] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=E3=80=91IoT=EF=BC=9A=E6=95=B4=E4=BD=93=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../framework/mybatis/core/type/EncryptTypeHandler.java | 2 +- .../iot/framework/job/core/IotSchedulerManager.java | 9 ++------- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/type/EncryptTypeHandler.java b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/type/EncryptTypeHandler.java index 7ef0f4ece4..9327ebbfed 100644 --- a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/type/EncryptTypeHandler.java +++ b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/type/EncryptTypeHandler.java @@ -13,7 +13,7 @@ import java.sql.ResultSet; import java.sql.SQLException; /** - * 字段字段的 TypeHandler 实现类,基于 {@link cn.hutool.crypto.symmetric.AES} 实现 + * 字段字段的 TypeHandler 实现类,基于 {@link AES} 实现 * 可通过 jasypt.encryptor.password 配置项,设置密钥 * * @author 芋道源码 diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/job/core/IotSchedulerManager.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/job/core/IotSchedulerManager.java index c52164b6e9..015b9ec3f0 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/job/core/IotSchedulerManager.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/job/core/IotSchedulerManager.java @@ -1,6 +1,5 @@ package cn.iocoder.yudao.module.iot.framework.job.core; -import cn.iocoder.yudao.framework.quartz.core.enums.JobDataKeyEnum; import lombok.extern.slf4j.Slf4j; import org.quartz.*; import org.springframework.context.ApplicationContext; @@ -173,12 +172,8 @@ public class IotSchedulerManager { * @param jobName 任务名 * @throws SchedulerException 触发异常 */ - public void triggerJob(String jobName) - throws SchedulerException { - // 触发任务 - JobDataMap data = new JobDataMap(); - data.put(JobDataKeyEnum.JOB_HANDLER_NAME.name(), jobName); - scheduler.triggerJob(new JobKey(jobName), data); + public void triggerJob(String jobName) throws SchedulerException { + scheduler.triggerJob(new JobKey(jobName)); } private Trigger buildTrigger(String jobName, String cronExpression) { From 67e548d54567777c60bfbde9f1213491bb87cdfa Mon Sep 17 00:00:00 2001 From: YunaiV Date: Mon, 17 Mar 2025 21:37:15 +0800 Subject: [PATCH 10/36] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=E3=80=91BPM=EF=BC=9A=E4=BB=BB=E5=8A=A1=E5=89=8D?= =?UTF-8?q?=E7=BD=AE=E3=80=81=E5=90=8E=E7=BD=AE=E9=80=9A=E7=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../task/BpmProcessInstanceServiceImpl.java | 14 ++++---------- .../bpm/service/task/BpmTaskServiceImpl.java | 19 ++++++++----------- .../task/listener/BpmUserTaskListener.java | 5 +---- .../trigger/http/BpmHttpCallbackTrigger.java | 5 +---- .../http/BpmSyncHttpRequestTrigger.java | 5 +---- 5 files changed, 15 insertions(+), 33 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java index 05ac86e69a..7fd065e833 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java @@ -63,7 +63,6 @@ import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; -import org.springframework.web.client.RestTemplate; import java.util.*; @@ -268,6 +267,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService FlowElement flowElement = bpmnModel.getFlowElement(task.getTaskDefinitionKey()); List nextFlowNodes = BpmnModelUtils.getNextFlowNodes(flowElement, bpmnModel, processVariables); + // TODO @小北:还是可以优化下哈;“4. 组装节点信息” 只拼接出 candidateUserIds;之后,再第二次循环,查询用户和部门信息,进行拼接 // 2. 收集所有节点的候选用户 ID Set allCandidateUsers = new HashSet<>(); for (FlowNode node : nextFlowNodes) { @@ -290,7 +290,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService List candidateUsers = new ArrayList<>(); for (Long userId : candidateUserIds) { UserSimpleBaseVO user = BpmProcessInstanceConvert.INSTANCE.buildUser(userId, userMap, deptMap); - if (user != null){ + if (user != null) { candidateUsers.add(user); } } @@ -932,10 +932,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService BpmModelMetaInfoVO.HttpRequestSetting setting = processDefinitionInfo.getProcessAfterTriggerSetting(); BpmHttpRequestUtils.executeBpmHttpRequest(instance, - setting.getUrl(), - setting.getHeader(), - setting.getBody(), - true, setting.getResponse()); + setting.getUrl(), setting.getHeader(), setting.getBody(), true, setting.getResponse()); } } }); @@ -954,10 +951,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService } BpmModelMetaInfoVO.HttpRequestSetting setting = processDefinitionInfo.getProcessBeforeTriggerSetting(); BpmHttpRequestUtils.executeBpmHttpRequest(instance, - setting.getUrl(), - setting.getHeader(), - setting.getBody(), - true, setting.getResponse()); + setting.getUrl(), setting.getHeader(), setting.getBody(), true, setting.getResponse()); }); } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java index 7becc5fd58..08e0cdad1e 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java @@ -1176,7 +1176,6 @@ public class BpmTaskServiceImpl implements BpmTaskService { } updateTaskStatus(task.getId(), BpmTaskStatusEnum.RUNNING.getStatus()); - // 2. 处理自动通过的情况,例如说:1)无审批人时,是否自动通过、不通过;2)非【人工审核】时,是否自动通过、不通过 ProcessInstance processInstance = processInstanceService.getProcessInstance(task.getProcessInstanceId()); if (processInstance == null) { log.error("[processTaskCreated][taskId({}) 没有找到流程实例]", task.getId()); @@ -1188,15 +1187,15 @@ public class BpmTaskServiceImpl implements BpmTaskService { log.error("[processTaskCreated][processDefinitionId({}) 没有找到流程定义]", processInstance.getProcessDefinitionId()); return; } - // 任务前置通知 + + // 2. 任务前置通知 if (ObjUtil.isNotNull(processDefinitionInfo.getTaskBeforeTriggerSetting())){ BpmModelMetaInfoVO.HttpRequestSetting setting = processDefinitionInfo.getTaskBeforeTriggerSetting(); BpmHttpRequestUtils.executeBpmHttpRequest(processInstance, - setting.getUrl(), - setting.getHeader(), - setting.getBody(), - true, setting.getResponse()); + setting.getUrl(), setting.getHeader(), setting.getBody(), true, setting.getResponse()); } + + // 3. 处理自动通过的情况,例如说:1)无审批人时,是否自动通过、不通过;2)非【人工审核】时,是否自动通过、不通过 BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processInstance.getProcessDefinitionId()); FlowElement userTaskElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); Integer approveType = BpmnModelUtils.parseApproveType(userTaskElement); @@ -1421,14 +1420,12 @@ public class BpmTaskServiceImpl implements BpmTaskService { log.error("[processTaskCompleted][processDefinitionId({}) 没有找到流程定义]", processInstance.getProcessDefinitionId()); return; } - // 任务前置通知 + + // 任务后置通知 if (ObjUtil.isNotNull(processDefinitionInfo.getTaskAfterTriggerSetting())){ BpmModelMetaInfoVO.HttpRequestSetting setting = processDefinitionInfo.getTaskAfterTriggerSetting(); BpmHttpRequestUtils.executeBpmHttpRequest(processInstance, - setting.getUrl(), - setting.getHeader(), - setting.getBody(), - true, setting.getResponse()); + setting.getUrl(), setting.getHeader(), setting.getBody(), true, setting.getResponse()); } } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/listener/BpmUserTaskListener.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/listener/BpmUserTaskListener.java index e16fafa2d1..0ddaba4769 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/listener/BpmUserTaskListener.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/listener/BpmUserTaskListener.java @@ -52,10 +52,7 @@ public class BpmUserTaskListener implements TaskListener { listenerHandler.getBody().add(new BpmSimpleModelNodeVO.HttpRequestParam().setKey("taskId") .setType(BpmHttpRequestParamTypeEnum.FIXED_VALUE.getType()).setValue(delegateTask.getId())); BpmHttpRequestUtils.executeBpmHttpRequest(processInstance, - listenerHandler.getPath(), - listenerHandler.getHeader(), - listenerHandler.getBody(), - false, null); + listenerHandler.getPath(), listenerHandler.getHeader(), listenerHandler.getBody(), false, null); // 3. 是否需要后续操作?TODO 芋艿:待定! } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/trigger/http/BpmHttpCallbackTrigger.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/trigger/http/BpmHttpCallbackTrigger.java index 351b57ddd4..d2cc03e27a 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/trigger/http/BpmHttpCallbackTrigger.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/trigger/http/BpmHttpCallbackTrigger.java @@ -45,10 +45,7 @@ public class BpmHttpCallbackTrigger extends BpmAbstractHttpRequestTrigger { .setKey("taskDefineKey") // 重要:回调请求 taskDefineKey 需要传给被调用方,用于回调执行 .setType(BpmHttpRequestParamTypeEnum.FIXED_VALUE.getType()).setValue(setting.getCallbackTaskDefineKey())); BpmHttpRequestUtils.executeBpmHttpRequest(processInstance, - setting.getUrl(), - setting.getHeader(), - setting.getBody(), - false, null); + setting.getUrl(), setting.getHeader(), setting.getBody(), false, null); } } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/trigger/http/BpmSyncHttpRequestTrigger.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/trigger/http/BpmSyncHttpRequestTrigger.java index 2ac04117e4..cecbe6a3c8 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/trigger/http/BpmSyncHttpRequestTrigger.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/trigger/http/BpmSyncHttpRequestTrigger.java @@ -40,10 +40,7 @@ public class BpmSyncHttpRequestTrigger extends BpmAbstractHttpRequestTrigger { // 2. 发起请求 ProcessInstance processInstance = processInstanceService.getProcessInstance(processInstanceId); BpmHttpRequestUtils.executeBpmHttpRequest(processInstance, - setting.getUrl(), - setting.getHeader(), - setting.getBody(), - true, setting.getResponse()); + setting.getUrl(), setting.getHeader(), setting.getBody(), true, setting.getResponse()); } } From acf68b1cec62da862210dae1fd180c15c71fcc12 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Mon, 17 Mar 2025 22:15:57 +0800 Subject: [PATCH 11/36] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=E3=80=91AI=EF=BC=9APPT=20API=20=E7=9A=84=E6=8E=A5?= =?UTF-8?q?=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/model/wenduoduo/api/WddPptApi.java | 58 ++++---- .../{xunfei => xinghuo}/api/XunfeiPptApi.java | 97 +++++++------- .../framework/ai/ppt/wdd/WddPptApiTests.java | 48 ++++--- .../ai/ppt/xunfei/XunfeiPptApiTests.java | 124 ++++++++++-------- 4 files changed, 171 insertions(+), 156 deletions(-) rename yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/{xunfei => xinghuo}/api/XunfeiPptApi.java (94%) diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/wenduoduo/api/WddPptApi.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/wenduoduo/api/WddPptApi.java index b752f73352..c3757a73c0 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/wenduoduo/api/WddPptApi.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/wenduoduo/api/WddPptApi.java @@ -24,11 +24,11 @@ import java.util.Objects; import java.util.function.Function; import java.util.function.Predicate; +// TODO @新:要不改成 WenDuoDuoPptApi /** * 文多多 API - *

- *

- * * 对接文多多:PPT 生成 API + * + * @see PPT 生成 API * * @author xiaoxin */ @@ -49,14 +49,15 @@ public class WddPptApi { sink.error(new IllegalStateException("[wdd-api] 调用失败!")); }); + // TODO @新:是不是不用 baseUrl 哈 public WddPptApi(String baseUrl) { this.webClient = WebClient.builder() .baseUrl(baseUrl) + // TODO @新:建议,token 作为 defaultHeader .defaultHeaders((headers) -> headers.setContentType(MediaType.APPLICATION_JSON)) .build(); } - /** * 创建 token * @@ -81,7 +82,7 @@ public class WddPptApi { .block(); } - + // TODO @xin:是不是给个 API 连接,这样 type、content、files 都不用写注释太细了 /** * 创建任务 * @@ -122,7 +123,6 @@ public class WddPptApi { formData.add("file", file.getResource()); } } - return this.webClient.post() .uri("/api/ppt/v2/createTask") .header("token", token) @@ -162,7 +162,7 @@ public class WddPptApi { } /** - * 分页查询PPT模板 + * 分页查询 PPT 模板 * * @param token 令牌 * @param request 请求体 @@ -214,7 +214,7 @@ public class WddPptApi { } /** - * 生成PPT + * 生成 PPT * * @return PPT信息 */ @@ -243,9 +243,11 @@ public class WddPptApi { String uid, Integer limit ) { + public CreateTokenRequest(String apiKey) { this(apiKey, null, null); } + } /** @@ -256,8 +258,7 @@ public class WddPptApi { Integer code, String message, Map data - ) { - } + ) { } /** * 创建任务 @@ -267,8 +268,7 @@ public class WddPptApi { Integer type, String content, List files - ) { - } + ) { } /** * 生成大纲内容请求 @@ -281,8 +281,7 @@ public class WddPptApi { String audience, String lang, String prompt - ) { - } + ) { } /** * 修改大纲内容请求 @@ -292,21 +291,20 @@ public class WddPptApi { String id, String markdown, String question - ) { - } + ) { } /** - * 生成PPT请求 + * 生成 PPT 请求 */ + // TODO @新:要不按照 PptCreateRequest 这样的风格 @JsonInclude(value = JsonInclude.Include.NON_NULL) public record CreatePptRequest( String id, String templateId, String markdown - ) { - } - + ) { } + // TODO @新:要不写下类注释 @JsonInclude(value = JsonInclude.Include.NON_NULL) public record PptInfo( String id, @@ -325,36 +323,35 @@ public class WddPptApi { LocalDateTime createTime, String createUser, String updateUser - ) { - } - + ) { } + // TODO @新:要不写下类注释 @JsonInclude(value = JsonInclude.Include.NON_NULL) public record TemplateQueryRequest( int page, int size, Filter filters ) { + @JsonInclude(value = JsonInclude.Include.NON_NULL) public record Filter( int type, String category, String style, String themeColor - ) { - } + ) { } + } - + // TODO @新:要不写下类注释 @JsonInclude(value = JsonInclude.Include.NON_NULL) public record PagePptTemplateInfo( List data, String total - ) { - - } + ) {} + // TODO @新:要不写下类注释 @JsonInclude(value = JsonInclude.Include.NON_NULL) public record PptTemplateInfo( String id, @@ -383,7 +380,6 @@ public class WddPptApi { LocalDateTime createTime, String createUser, String updateUser - ) { - } + ) { } } \ No newline at end of file diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xunfei/api/XunfeiPptApi.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/api/XunfeiPptApi.java similarity index 94% rename from yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xunfei/api/XunfeiPptApi.java rename to yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/api/XunfeiPptApi.java index fe8ba4c046..c3320ef21c 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xunfei/api/XunfeiPptApi.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/api/XunfeiPptApi.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.framework.ai.core.model.xunfei.api; +package cn.iocoder.yudao.framework.ai.core.model.xinghuo.api; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import com.fasterxml.jackson.annotation.JsonInclude; @@ -28,10 +28,11 @@ import java.util.Map; import java.util.function.Function; import java.util.function.Predicate; +// TODO @新:要不改成 XunFeiPptApi /** - * 讯飞智能PPT生成 API - *

- * 对接讯飞:智能 PPT 生成 API + * 讯飞智能 PPT 生成 API + * + * @see 智能 PPT 生成 API * * @author xiaoxin */ @@ -52,7 +53,9 @@ public class XunfeiPptApi { sink.error(new IllegalStateException("[xunfei-ppt-api] 调用失败!")); }); + // TODO @新:是不是不用 baseUrl 哈 public XunfeiPptApi(String baseUrl, String appId, String apiSecret) { + // TODO @新:建议,增加 defaultheaders,例如说 appid 之类的;或者每个请求,通过 headers customer 处理。 this.webClient = WebClient.builder() .baseUrl(baseUrl) .build(); @@ -82,6 +85,7 @@ public class XunfeiPptApi { */ private String generateSignature(String appId, String apiSecret, long timestamp) { try { + // TODO @新:使用 hutool 简化 String auth = md5(appId + timestamp); return hmacSHA1Encrypt(auth, apiSecret); } catch (NoSuchAlgorithmException | InvalidKeyException e) { @@ -120,7 +124,7 @@ public class XunfeiPptApi { } /** - * 获取PPT模板列表 + * 获取 PPT 模板列表 * * @param style 风格,如"商务" * @param pageSize 每页数量 @@ -130,8 +134,8 @@ public class XunfeiPptApi { SignatureInfo signInfo = getSignature(); Map requestBody = new HashMap<>(); requestBody.put("style", style); - requestBody.put("pageSize", pageSize != null ? pageSize.toString() : "10"); - + // TODO @新:可以使用 ObjUtil.defaultIfNull + requestBody.put("pageSize", pageSize != null ? pageSize : 10); return this.webClient.post() .uri("/template/list") .header("appId", signInfo.appId) @@ -155,7 +159,6 @@ public class XunfeiPptApi { SignatureInfo signInfo = getSignature(); MultiValueMap formData = new LinkedMultiValueMap<>(); formData.add("query", query); - return this.webClient.post() .uri("/createOutline") .header("appId", signInfo.appId) @@ -171,7 +174,7 @@ public class XunfeiPptApi { /** - * 直接创建PPT(简化版 - 通过文本) + * 直接创建 PPT(简化版 - 通过文本) * * @param query 查询文本 * @return 创建响应 @@ -184,7 +187,7 @@ public class XunfeiPptApi { } /** - * 直接创建PPT(简化版 - 通过文件) + * 直接创建 PPT(简化版 - 通过文件) * * @param file 文件 * @param fileName 文件名 @@ -192,14 +195,12 @@ public class XunfeiPptApi { */ public CreateResponse create(MultipartFile file, String fileName) { CreatePptRequest request = CreatePptRequest.builder() - .file(file) - .fileName(fileName) - .build(); + .file(file).fileName(fileName).build(); return create(request); } /** - * 直接创建PPT(完整版) + * 直接创建 PPT(完整版) * * @param request 请求参数 * @return 创建响应 @@ -207,7 +208,6 @@ public class XunfeiPptApi { public CreateResponse create(CreatePptRequest request) { SignatureInfo signInfo = getSignature(); MultiValueMap formData = buildCreateFormData(request); - return this.webClient.post() .uri("/create") .header("appId", signInfo.appId) @@ -223,7 +223,7 @@ public class XunfeiPptApi { /** - * 通过大纲创建PPT(简化版) + * 通过大纲创建 PPT(简化版) * * @param outline 大纲内容 * @param query 查询文本 @@ -238,14 +238,13 @@ public class XunfeiPptApi { } /** - * 通过大纲创建PPT(完整版) + * 通过大纲创建 PPT(完整版) * * @param request 请求参数 * @return 创建响应 */ public CreateResponse createPptByOutline(CreatePptByOutlineRequest request) { SignatureInfo signInfo = getSignature(); - return this.webClient.post() .uri("/createPptByOutline") .header("appId", signInfo.appId) @@ -260,14 +259,13 @@ public class XunfeiPptApi { } /** - * 检查PPT生成进度 + * 检查 PPT 生成进度 * - * @param sid 任务ID + * @param sid 任务 ID * @return 进度响应 */ public ProgressResponse checkProgress(String sid) { SignatureInfo signInfo = getSignature(); - return this.webClient.get() .uri(uriBuilder -> uriBuilder .path("/progress") @@ -290,8 +288,7 @@ public class XunfeiPptApi { String appId, String timestamp, String signature - ) { - } + ) { } /** * 模板列表响应 @@ -303,8 +300,7 @@ public class XunfeiPptApi { String desc, Integer count, TemplatePageData data - ) { - } + ) { } /** * 模板列表数据 @@ -314,8 +310,7 @@ public class XunfeiPptApi { String total, List records, Integer pageNum - ) { - } + ) { } /** * 模板信息 @@ -329,8 +324,7 @@ public class XunfeiPptApi { String industry, String style, String detailImage - ) { - } + ) { } /** * 创建响应 @@ -342,8 +336,7 @@ public class XunfeiPptApi { String desc, Integer count, CreateResponseData data - ) { - } + ) { } /** * 创建响应数据 @@ -355,8 +348,7 @@ public class XunfeiPptApi { String title, String subTitle, OutlineData outline - ) { - } + ) { } /** * 大纲数据结构 @@ -367,6 +359,7 @@ public class XunfeiPptApi { String subTitle, List chapters ) { + /** * 章节结构 */ @@ -375,14 +368,15 @@ public class XunfeiPptApi { String chapterTitle, List chapterContents ) { + /** * 章节内容 */ @JsonInclude(value = JsonInclude.Include.NON_NULL) public record ChapterContent( String chapterTitle - ) { - } + ) { } + } /** @@ -403,8 +397,7 @@ public class XunfeiPptApi { int code, String desc, ProgressResponseData data - ) { - } + ) { } /** * 进度响应数据 @@ -414,6 +407,7 @@ public class XunfeiPptApi { int process, String pptId, String pptUrl, + // TODO @新:字段注释,去掉 String pptStatus, // PPT构建状态:building(构建中),done(已完成),build_failed(生成失败) String aiImageStatus, // ai配图状态:building(构建中),done(已完成) String cardNoteStatus, // 演讲备注状态:building(构建中),done(已完成) @@ -421,6 +415,7 @@ public class XunfeiPptApi { Integer totalPages, // 生成PPT的总页数 Integer donePages // 生成PPT的完成页数 ) { + /** * 是否全部完成 * @@ -452,10 +447,11 @@ public class XunfeiPptApi { } return (int) (donePages * 100.0 / totalPages); } + } /** - * 通过大纲创建PPT请求参数 + * 通过大纲创建 PPT 请求参数 */ @JsonInclude(value = JsonInclude.Include.NON_NULL) public record CreatePptByOutlineRequest( @@ -473,6 +469,7 @@ public class XunfeiPptApi { Boolean isFigure, // 是否自动配图 String aiImage // ai配图类型:normal、advanced ) { + /** * 创建构建器 * @@ -482,10 +479,12 @@ public class XunfeiPptApi { return new Builder(); } + // TODO @新:这个可以用 lombok 简化么? /** * 构建器类 */ public static class Builder { + private String query; private String outlineSid; private OutlineData outline; @@ -575,7 +574,7 @@ public class XunfeiPptApi { } /** - * 构建创建PPT的表单数据 + * 构建创建 PPT 的表单数据 * * @param request 请求参数 * @return 表单数据 @@ -586,7 +585,6 @@ public class XunfeiPptApi { if (request.query() != null) { formData.add("query", request.query()); } - if (request.file() != null) { try { formData.add("file", new ByteArrayResource(request.file().getBytes()) { @@ -600,47 +598,37 @@ public class XunfeiPptApi { throw new IllegalStateException("[xunfei-ppt-api] 文件处理失败", e); } } - + // TODO @新:要不搞个 MapUtil.addIfPresent 方法? if (request.fileUrl() != null) { formData.add("fileUrl", request.fileUrl()); } - if (request.fileName() != null) { formData.add("fileName", request.fileName()); } - if (request.templateId() != null) { formData.add("templateId", request.templateId()); } - if (request.businessId() != null) { formData.add("businessId", request.businessId()); } - if (request.author() != null) { formData.add("author", request.author()); } - if (request.isCardNote() != null) { formData.add("isCardNote", request.isCardNote().toString()); } - if (request.search() != null) { formData.add("search", request.search().toString()); } - if (request.language() != null) { formData.add("language", request.language()); } - if (request.isFigure() != null) { formData.add("isFigure", request.isFigure().toString()); } - if (request.aiImage() != null) { formData.add("aiImage", request.aiImage()); } - return formData; } @@ -662,6 +650,7 @@ public class XunfeiPptApi { Boolean isFigure, // 是否自动配图 String aiImage // ai配图类型:normal、advanced ) { + /** * 创建构建器 * @@ -675,6 +664,7 @@ public class XunfeiPptApi { * 构建器类 */ public static class Builder { + private String query; private MultipartFile file; private String fileUrl; @@ -688,6 +678,8 @@ public class XunfeiPptApi { private Boolean isFigure; private String aiImage; + // TODO @新:这个可以用 lombok 简化么? + public Builder query(String query) { this.query = query; return this; @@ -763,4 +755,5 @@ public class XunfeiPptApi { } } } -} \ No newline at end of file + +} \ No newline at end of file diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/ppt/wdd/WddPptApiTests.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/ppt/wdd/WddPptApiTests.java index e01311c8bd..3bb9898ad8 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/ppt/wdd/WddPptApiTests.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/ppt/wdd/WddPptApiTests.java @@ -9,7 +9,6 @@ import reactor.core.publisher.Flux; import java.util.Map; import java.util.Objects; - /** * {@link WddPptApi} 集成测试 * @@ -19,11 +18,9 @@ public class WddPptApiTests { private final WddPptApi wddPptApi = new WddPptApi("https://docmee.cn"); + private final String token = ""; // API Token - private final String token = ""; - - - @Test //获取token + @Test @Disabled public void testCreateApiToken() { // 准备参数 @@ -35,8 +32,10 @@ public class WddPptApiTests { System.out.println(token); } - - @Test // 创建任务 + /** + * 创建任务 + */ + @Test @Disabled public void testCreateTask() { WddPptApi.ApiResponse apiResponse = wddPptApi.createTask(token, 1, "dify 介绍", null); @@ -47,8 +46,9 @@ public class WddPptApiTests { @Test // 创建大纲 @Disabled public void testGenerateOutlineRequest() { - WddPptApi.CreateOutlineRequest request = new WddPptApi.CreateOutlineRequest("1901539019628613632", "medium", null, null, null, null); - //调用 + WddPptApi.CreateOutlineRequest request = new WddPptApi.CreateOutlineRequest( + "1901539019628613632", "medium", null, null, null, null); + // 调用 Flux> flux = wddPptApi.createOutline(token, request); StringBuffer contentBuffer = new StringBuffer(); flux.doOnNext(chunk -> { @@ -61,14 +61,17 @@ public class WddPptApiTests { }).then().block(); // 打印结果 System.out.println(contentBuffer); - } - @Test // 修改大纲 + /** + * 修改大纲 + */ + @Test @Disabled public void testUpdateOutlineRequest() { - WddPptApi.UpdateOutlineRequest request = new WddPptApi.UpdateOutlineRequest("1901539019628613632", TEST_OUT_LINE_CONTENT, "精简一点,三个章节即可"); - //调用 + WddPptApi.UpdateOutlineRequest request = new WddPptApi.UpdateOutlineRequest( + "1901539019628613632", TEST_OUT_LINE_CONTENT, "精简一点,三个章节即可"); + // 调用 Flux> flux = wddPptApi.updateOutline(token, request); StringBuffer contentBuffer = new StringBuffer(); flux.doOnNext(chunk -> { @@ -84,31 +87,36 @@ public class WddPptApiTests { } - @Test // 获取 PPT 模版分页 + /** + * 获取 PPT 模版分页 + */ + @Test @Disabled public void testGetPptTemplatePage() { // 准备参数 - WddPptApi.TemplateQueryRequest.Filter filter = new WddPptApi.TemplateQueryRequest.Filter(1, null, null, null); + WddPptApi.TemplateQueryRequest.Filter filter = new WddPptApi.TemplateQueryRequest.Filter( + 1, null, null, null); WddPptApi.TemplateQueryRequest request = new WddPptApi.TemplateQueryRequest(1, 10, filter); - //调用 + // 调用 WddPptApi.PagePptTemplateInfo pptTemplatePage = wddPptApi.getTemplatePage(token, request); // 打印结果 System.out.println(pptTemplatePage); } - - @Test // 生成 PPT + /** + * 生成 PPT + */ + @Test @Disabled public void testGeneratePptx() { // 准备参数 WddPptApi.CreatePptRequest request = new WddPptApi.CreatePptRequest("1901539019628613632", "1805081814809960448", TEST_OUT_LINE_CONTENT); - //调用 + // 调用 WddPptApi.PptInfo pptInfo = wddPptApi.create(token, request); // 打印结果 System.out.println(pptInfo); } - private final String TEST_OUT_LINE_CONTENT = """ # Dify:新一代AI应用开发平台 diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/ppt/xunfei/XunfeiPptApiTests.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/ppt/xunfei/XunfeiPptApiTests.java index b710f59942..34088bf544 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/ppt/xunfei/XunfeiPptApiTests.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/ppt/xunfei/XunfeiPptApiTests.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.framework.ai.ppt.xunfei; -import cn.iocoder.yudao.framework.ai.core.model.xunfei.api.XunfeiPptApi; +import cn.hutool.core.io.FileUtil; +import cn.iocoder.yudao.framework.ai.core.model.xinghuo.api.XunfeiPptApi; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -8,8 +9,6 @@ import org.springframework.mock.web.MockMultipartFile; import org.springframework.web.multipart.MultipartFile; import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; /** * {@link XunfeiPptApi} 集成测试 @@ -18,13 +17,16 @@ import java.io.IOException; */ public class XunfeiPptApiTests { - // 讯飞API配置信息,实际使用时请替换为您的应用信息 + // 讯飞 API 配置信息,实际使用时请替换为您的应用信息 private static final String APP_ID = ""; private static final String API_SECRET = ""; private final XunfeiPptApi xunfeiPptApi = new XunfeiPptApi(XunfeiPptApi.BASE_URL, APP_ID, API_SECRET); - @Test // 获取PPT模板列表 + /** + * 获取 PPT 模板列表 + */ + @Test @Disabled public void testGetTemplatePage() { // 调用方法 @@ -48,35 +50,42 @@ public class XunfeiPptApiTests { } } - @Test // 创建大纲(通过文本) + /** + * 创建大纲(通过文本) + */ + @Test @Disabled public void testCreateOutline() { XunfeiPptApi.CreateResponse response = getCreateResponse(); // 打印结果 System.out.println("创建大纲响应:" + JsonUtils.toJsonString(response)); - // 保存sid和outline用于后续测试 + // 保存 sid 和 outline 用于后续测试 if (response != null && response.data() != null) { System.out.println("sid: " + response.data().sid()); if (response.data().outline() != null) { - // 使用OutlineData的toJsonString方法 + // 使用 OutlineData 的 toJsonString 方法 System.out.println("outline: " + response.data().outline().toJsonString()); - // 将outline对象转换为JSON字符串,用于后续createPptByOutline测试 + // 将 outline 对象转换为 JSON 字符串,用于后续 createPptByOutline 测试 String outlineJson = response.data().outline().toJsonString(); - System.out.println("可用于createPptByOutline的outline字符串: " + outlineJson); + System.out.println("可用于 createPptByOutline 的 outline 字符串: " + outlineJson); } } } - // 创建大纲(通过文本) + /** + * 创建大纲(通过文本) + * @return 创建大纲响应 + */ private XunfeiPptApi.CreateResponse getCreateResponse() { - // 准备参数 String param = "智能体平台 Dify 介绍"; - // 调用方法 return xunfeiPptApi.createOutline(param); } - @Test // 通过大纲创建PPT(完整参数) + /** + * 通过大纲创建 PPT(完整参数) + */ + @Test @Disabled public void testCreatePptByOutlineWithFullParams() { // 创建大纲对象 @@ -84,7 +93,7 @@ public class XunfeiPptApiTests { // 调用方法 XunfeiPptApi.CreateResponse response = xunfeiPptApi.createPptByOutline(createResponse.data().outline(), "精简一些,不要超过6个章节"); // 打印结果 - System.out.println("通过大纲创建PPT响应:" + JsonUtils.toJsonString(response)); + System.out.println("通过大纲创建 PPT 响应:" + JsonUtils.toJsonString(response)); // 保存sid用于后续进度查询 if (response != null && response.data() != null) { @@ -95,10 +104,13 @@ public class XunfeiPptApiTests { } } - @Test // 检查PPT生成进度 + /** + * 检查 PPT 生成进度 + */ + @Test @Disabled public void testCheckProgress() { - // 准备参数 - 使用之前创建PPT时返回的sid + // 准备参数 - 使用之前创建 PPT 时返回的 sid String sid = "e96dac09f2ec4ee289f029a5fb874ecd"; // 替换为实际的sid // 调用方法 @@ -111,8 +123,8 @@ public class XunfeiPptApiTests { XunfeiPptApi.ProgressResponseData data = response.data(); // 打印PPT生成状态 - System.out.println("PPT构建状态: " + data.pptStatus()); - System.out.println("AI配图状态: " + data.aiImageStatus()); + System.out.println("PPT 构建状态: " + data.pptStatus()); + System.out.println("AI 配图状态: " + data.aiImageStatus()); System.out.println("演讲备注状态: " + data.cardNoteStatus()); // 打印进度信息 @@ -126,30 +138,33 @@ public class XunfeiPptApiTests { // 检查是否完成 if (data.isAllDone()) { - System.out.println("PPT生成已完成!"); - System.out.println("PPT下载链接: " + data.pptUrl()); + System.out.println("PPT 生成已完成!"); + System.out.println("PPT 下载链接: " + data.pptUrl()); } // 检查是否失败 else if (data.isFailed()) { - System.out.println("PPT生成失败!"); + System.out.println("PPT 生成失败!"); System.out.println("错误信息: " + data.errMsg()); } // 正在进行中 else { - System.out.println("PPT生成中,请稍后再查询..."); + System.out.println("PPT 生成中,请稍后再查询..."); } } } - @Test // 轮询检查PPT生成进度直到完成 + /** + * 轮询检查 PPT 生成进度直到完成 + */ + @Test @Disabled public void testPollCheckProgress() throws InterruptedException { - // 准备参数 - 使用之前创建PPT时返回的sid + // 准备参数 - 使用之前创建 PP T时返回的 sid String sid = "fa36e926f2ed434987fcb4c1f0776ffb"; // 替换为实际的sid // 最大轮询次数 int maxPolls = 20; - // 轮询间隔(毫秒)- 讯飞API限流为3秒一次 + // 轮询间隔(毫秒)- 讯飞 API 限流为 3 秒一次 long pollInterval = 3500; for (int i = 0; i < maxPolls; i++) { @@ -163,7 +178,7 @@ public class XunfeiPptApiTests { XunfeiPptApi.ProgressResponseData data = response.data(); // 打印进度信息 - System.out.println("PPT构建状态: " + data.pptStatus()); + System.out.println("PPT 构建状态: " + data.pptStatus()); if (data.totalPages() != null && data.donePages() != null) { System.out.println("完成进度: " + data.donePages() + "/" + data.totalPages() + " (" + data.getProgressPercent() + "%)"); @@ -171,19 +186,19 @@ public class XunfeiPptApiTests { // 检查是否完成 if (data.isAllDone()) { - System.out.println("PPT生成已完成!"); - System.out.println("PPT下载链接: " + data.pptUrl()); + System.out.println("PPT 生成已完成!"); + System.out.println("PPT 下载链接: " + data.pptUrl()); break; } // 检查是否失败 else if (data.isFailed()) { - System.out.println("PPT生成失败!"); + System.out.println("PPT 生成失败!"); System.out.println("错误信息: " + data.errMsg()); break; } // 正在进行中,继续轮询 else { - System.out.println("PPT生成中,等待" + (pollInterval / 1000) + "秒后继续查询..."); + System.out.println("PPT 生成中,等待" + (pollInterval / 1000) + "秒后继续查询..."); Thread.sleep(pollInterval); } } else { @@ -193,7 +208,10 @@ public class XunfeiPptApiTests { } } - @Test // 直接创建PPT(通过文本) + /** + * 直接创建 PPT(通过文本) + */ + @Test @Disabled public void testCreatePptByText() { // 准备参数 @@ -202,9 +220,9 @@ public class XunfeiPptApiTests { // 调用方法 XunfeiPptApi.CreateResponse response = xunfeiPptApi.create(query); // 打印结果 - System.out.println("直接创建PPT响应:" + JsonUtils.toJsonString(response)); + System.out.println("直接创建 PPT 响应:" + JsonUtils.toJsonString(response)); - // 保存sid用于后续进度查询 + // 保存 sid 用于后续进度查询 if (response != null && response.data() != null) { System.out.println("sid: " + response.data().sid()); if (response.data().coverImgSrc() != null) { @@ -215,9 +233,12 @@ public class XunfeiPptApiTests { } } - @Test // 直接创建PPT(通过文件) + /** + * 直接创建 PPT(通过文件) + */ + @Test @Disabled - public void testCreatePptByFile() throws IOException { + public void testCreatePptByFile() { // 准备参数 File file = new File("src/test/resources/test.txt"); // 请确保此文件存在 MultipartFile multipartFile = convertFileToMultipartFile(file); @@ -227,7 +248,7 @@ public class XunfeiPptApiTests { // 打印结果 System.out.println("通过文件创建PPT响应:" + JsonUtils.toJsonString(response)); - // 保存sid用于后续进度查询 + // 保存 sid 用于后续进度查询 if (response != null && response.data() != null) { System.out.println("sid: " + response.data().sid()); if (response.data().coverImgSrc() != null) { @@ -238,11 +259,14 @@ public class XunfeiPptApiTests { } } - @Test // 直接创建PPT(完整参数) + /** + * 直接创建 PPT(完整参数) + */ + @Test @Disabled - public void testCreatePptWithFullParams() throws IOException { + public void testCreatePptWithFullParams() { // 准备参数 - String query = "合肥天气趋势分析,包括近5年的气温变化、降水量变化、极端天气事件,以及对城市生活的影响"; + String query = "合肥天气趋势分析,包括近 5 年的气温变化、降水量变化、极端天气事件,以及对城市生活的影响"; // 创建请求对象 XunfeiPptApi.CreatePptRequest request = XunfeiPptApi.CreatePptRequest.builder() @@ -258,9 +282,9 @@ public class XunfeiPptApiTests { // 调用方法 XunfeiPptApi.CreateResponse response = xunfeiPptApi.create(request); // 打印结果 - System.out.println("使用完整参数创建PPT响应:" + JsonUtils.toJsonString(response)); + System.out.println("使用完整参数创建 PPT 响应:" + JsonUtils.toJsonString(response)); - // 保存sid用于后续进度查询 + // 保存 sid 用于后续进度查询 if (response != null && response.data() != null) { String sid = response.data().sid(); System.out.println("sid: " + sid); @@ -275,7 +299,7 @@ public class XunfeiPptApiTests { XunfeiPptApi.ProgressResponse progressResponse = xunfeiPptApi.checkProgress(sid); if (progressResponse != null && progressResponse.data() != null) { XunfeiPptApi.ProgressResponseData progressData = progressResponse.data(); - System.out.println("PPT构建状态: " + progressData.pptStatus()); + System.out.println("PPT 构建状态: " + progressData.pptStatus()); if (progressData.totalPages() != null && progressData.donePages() != null) { System.out.println("完成进度: " + progressData.donePages() + "/" + progressData.totalPages() + " (" + progressData.getProgressPercent() + "%)"); @@ -285,16 +309,10 @@ public class XunfeiPptApiTests { } /** - * 将File转换为MultipartFile + * 将 File 转换为 MultipartFile */ - private MultipartFile convertFileToMultipartFile(File file) throws IOException { - FileInputStream input = new FileInputStream(file); - return new MockMultipartFile( - "file", - file.getName(), - "text/plain", - input.readAllBytes() - ); + private MultipartFile convertFileToMultipartFile(File file) { + return new MockMultipartFile("file", file.getName(), "text/plain", FileUtil.readBytes(file)); } -} \ No newline at end of file +} \ No newline at end of file From 7b3401e216265f099d860f4d1658376e387b2c91 Mon Sep 17 00:00:00 2001 From: zzt <976209226@qq.com> Date: Tue, 18 Mar 2025 00:22:15 +0800 Subject: [PATCH 12/36] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E3=80=91AI=EF=BC=9A=E7=94=BB=E5=9B=BE=E9=80=9A?= =?UTF-8?q?=E7=94=A8=E5=8A=9F=E8=83=BD=E5=A2=9E=E5=8A=A0=E7=A1=85=E5=9F=BA?= =?UTF-8?q?=E6=B5=81=E5=8A=A8=E5=B9=B3=E5=8F=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ai/service/image/AiImageServiceImpl.java | 8 +- .../ai/config/YudaoAiAutoConfiguration.java | 6 +- .../ai/core/factory/AiModelFactoryImpl.java | 14 ++ .../siliconflow/SiiconflowApiConstants.java | 36 +++ .../model/siliconflow/SiiconflowmageApi.java | 207 ++++++++++++++++++ .../siliconflow/SiliconFlowChatModel.java | 4 - .../siliconflow/SiliconflowImageModel.java | 166 ++++++++++++++ .../siliconflow/SiliconflowImageOptions.java | 166 ++++++++++++++ .../yudao/framework/ai/core/util/AiUtils.java | 4 +- .../ai/chat/SiliconFlowChatModelTests.java | 5 +- 10 files changed, 605 insertions(+), 11 deletions(-) create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiiconflowApiConstants.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiiconflowmageApi.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiliconflowImageModel.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiliconflowImageOptions.java diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageServiceImpl.java index 60ca9ac996..f0ef418301 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageServiceImpl.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageServiceImpl.java @@ -11,6 +11,7 @@ import cn.hutool.extra.spring.SpringUtil; import cn.hutool.http.HttpUtil; import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; +import cn.iocoder.yudao.framework.ai.core.model.siliconflow.SiliconflowImageOptions; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageDrawReqVO; @@ -144,7 +145,12 @@ public class AiImageServiceImpl implements AiImageService { .withStyle(MapUtil.getStr(draw.getOptions(), "style")) // 风格 .withResponseFormat("b64_json") .build(); - } else if (ObjUtil.equal(model.getPlatform(), AiPlatformEnum.STABLE_DIFFUSION.getPlatform())) { + } else if (ObjUtil.equal(model.getPlatform(), AiPlatformEnum.SILICON_FLOW.getPlatform())) { + // https://docs.siliconflow.cn/cn/api-reference/images/images-generations + return SiliconflowImageOptions.builder().withModel(model.getModel()) + .withHeight(draw.getHeight()).withWidth(draw.getWidth()) + .build(); + } else if (ObjUtil.equal(model.getPlatform(), AiPlatformEnum.STABLE_DIFFUSION.getPlatform())) { // https://platform.stability.ai/docs/api-reference#tag/SDXL-and-SD1.6/operation/textToImage // https://platform.stability.ai/docs/api-reference#tag/Text-to-Image/operation/textToImage return StabilityAiImageOptions.builder().model(model.getModel()) diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java index ef3314a48b..dc1846ef99 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java @@ -8,6 +8,8 @@ import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatModel; import cn.iocoder.yudao.framework.ai.core.model.doubao.DouBaoChatModel; import cn.iocoder.yudao.framework.ai.core.model.hunyuan.HunYuanChatModel; import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; +import cn.iocoder.yudao.framework.ai.core.model.siliconflow.SiiconflowApiConstants; +import cn.iocoder.yudao.framework.ai.core.model.siliconflow.SiiconflowmageApi; import cn.iocoder.yudao.framework.ai.core.model.siliconflow.SiliconFlowChatModel; import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel; @@ -113,11 +115,11 @@ public class YudaoAiAutoConfiguration { public SiliconFlowChatModel buildSiliconFlowChatClient(YudaoAiProperties.SiliconFlowProperties properties) { if (StrUtil.isEmpty(properties.getModel())) { - properties.setModel(SiliconFlowChatModel.MODEL_DEFAULT); + properties.setModel(SiiconflowApiConstants.MODEL_DEFAULT); } OpenAiChatModel openAiChatModel = OpenAiChatModel.builder() .openAiApi(OpenAiApi.builder() - .baseUrl(SiliconFlowChatModel.BASE_URL) + .baseUrl(SiiconflowApiConstants.DEFAULT_BASE_URL) .apiKey(properties.getApiKey()) .build()) .defaultOptions(OpenAiChatOptions.builder() diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java index 356715be26..6c72b2b5f3 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java @@ -15,7 +15,10 @@ import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatModel; import cn.iocoder.yudao.framework.ai.core.model.doubao.DouBaoChatModel; import cn.iocoder.yudao.framework.ai.core.model.hunyuan.HunYuanChatModel; import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; +import cn.iocoder.yudao.framework.ai.core.model.siliconflow.SiiconflowApiConstants; +import cn.iocoder.yudao.framework.ai.core.model.siliconflow.SiiconflowmageApi; import cn.iocoder.yudao.framework.ai.core.model.siliconflow.SiliconFlowChatModel; +import cn.iocoder.yudao.framework.ai.core.model.siliconflow.SiliconflowImageModel; import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel; import cn.iocoder.yudao.framework.common.util.spring.SpringUtils; @@ -224,6 +227,8 @@ public class AiModelFactoryImpl implements AiModelFactory { return buildZhiPuAiImageModel(apiKey, url); case OPENAI: return buildOpenAiImageModel(apiKey, url); + case SILICON_FLOW: + return buildSiiconflowImageModel(apiKey,url); case STABLE_DIFFUSION: return buildStabilityAiImageModel(apiKey, url); default: @@ -468,6 +473,15 @@ public class AiModelFactoryImpl implements AiModelFactory { return new OpenAiImageModel(openAiApi); } + /** + * Siiconflow + */ + private SiliconflowImageModel buildSiiconflowImageModel(String apiToken, String url) { + url = StrUtil.blankToDefault(url, SiiconflowApiConstants.DEFAULT_BASE_URL); + SiiconflowmageApi openAiApi = SiiconflowmageApi.builder().baseUrl(url).apiKey(apiToken).build(); + return new SiliconflowImageModel(openAiApi); + } + /** * 可参考 {@link OllamaAutoConfiguration} 的 ollamaApi 方法 */ diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiiconflowApiConstants.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiiconflowApiConstants.java new file mode 100644 index 0000000000..589d05855d --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiiconflowApiConstants.java @@ -0,0 +1,36 @@ +/* + * Copyright 2023-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.iocoder.yudao.framework.ai.core.model.siliconflow; + +/** + * Common value constants for Siiconflow api. + * + * @author zzt + */ +public final class SiiconflowApiConstants { + + public static final String DEFAULT_BASE_URL = "https://api.siliconflow.cn"; + + public static final String MODEL_DEFAULT = "deepseek-ai/DeepSeek-R1-Distill-Qwen-7B"; + + public static final String PROVIDER_NAME = "Siiconflow"; + + private SiiconflowApiConstants() { + + } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiiconflowmageApi.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiiconflowmageApi.java new file mode 100644 index 0000000000..8d0cf925af --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiiconflowmageApi.java @@ -0,0 +1,207 @@ +/* + * Copyright 2023-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.iocoder.yudao.framework.ai.core.model.siliconflow; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.springframework.ai.model.ApiKey; +import org.springframework.ai.model.NoopApiKey; +import org.springframework.ai.model.SimpleApiKey; +import org.springframework.ai.openai.api.OpenAiImageApi; +import org.springframework.ai.retry.RetryUtils; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.ResponseErrorHandler; +import org.springframework.web.client.RestClient; + +import java.util.Map; + +/** + * Siiconflow Image API. + * + * @see Images + * + * @author zzt + */ +public class SiiconflowmageApi { + + private final RestClient restClient; + + /** + * Create a new Siiconflow Image api with base URL set. + * @param aiToken OpenAI apiKey. + */ + public SiiconflowmageApi(String aiToken) { + this(SiiconflowApiConstants.DEFAULT_BASE_URL, aiToken, RestClient.builder()); + } + + /** + * Create a new Siiconflow Image API with the provided base URL. + * @param baseUrl the base URL for the OpenAI API. + * @param openAiToken Siiconflow apiKey. + */ + public SiiconflowmageApi(String baseUrl, String openAiToken, RestClient.Builder restClientBuilder) { + this(baseUrl, openAiToken, restClientBuilder, RetryUtils.DEFAULT_RESPONSE_ERROR_HANDLER); + } + + /** + * Create a new OpenAI Image API with the provided base URL. + * @param baseUrl the base URL for the OpenAI API. + * @param apiKey OpenAI apiKey. + * @param restClientBuilder the rest client builder to use. + */ + public SiiconflowmageApi(String baseUrl, String apiKey, RestClient.Builder restClientBuilder, + ResponseErrorHandler responseErrorHandler) { + this(baseUrl, apiKey, CollectionUtils.toMultiValueMap(Map.of()), restClientBuilder, responseErrorHandler); + } + + /** + * Create a new OpenAI Image API with the provided base URL. + * @param baseUrl the base URL for the OpenAI API. + * @param apiKey OpenAI apiKey. + * @param headers the http headers to use. + * @param restClientBuilder the rest client builder to use. + * @param responseErrorHandler the response error handler to use. + */ + public SiiconflowmageApi(String baseUrl, String apiKey, MultiValueMap headers, + RestClient.Builder restClientBuilder, ResponseErrorHandler responseErrorHandler) { + + this(baseUrl, new SimpleApiKey(apiKey), headers, restClientBuilder, responseErrorHandler); + } + + /** + * Create a new OpenAI Image API with the provided base URL. + * @param baseUrl the base URL for the OpenAI API. + * @param apiKey OpenAI apiKey. + * @param headers the http headers to use. + * @param restClientBuilder the rest client builder to use. + * @param responseErrorHandler the response error handler to use. + */ + public SiiconflowmageApi(String baseUrl, ApiKey apiKey, MultiValueMap headers, + RestClient.Builder restClientBuilder, ResponseErrorHandler responseErrorHandler) { + + // @formatter:off + this.restClient = restClientBuilder.baseUrl(baseUrl) + .defaultHeaders(h -> { + if(!(apiKey instanceof NoopApiKey)) { + h.setBearerAuth(apiKey.getValue()); + } + h.setContentType(MediaType.APPLICATION_JSON); + h.addAll(headers); + }) + .defaultStatusHandler(responseErrorHandler) + .build(); + // @formatter:on + } + + public ResponseEntity createImage(SiliconflowImageRequest siliconflowImageRequest) { + Assert.notNull(siliconflowImageRequest, "Image request cannot be null."); + Assert.hasLength(siliconflowImageRequest.prompt(), "Prompt cannot be empty."); + + return this.restClient.post() + .uri("v1/images/generations") + .body(siliconflowImageRequest) + .retrieve() + .toEntity(OpenAiImageApi.OpenAiImageResponse.class); + } + + + // @formatter:off + @JsonInclude(JsonInclude.Include.NON_NULL) + public record SiliconflowImageRequest ( + @JsonProperty("prompt") String prompt, + @JsonProperty("model") String model, + @JsonProperty("batch_size") Integer batchSize, + @JsonProperty("negative_prompt") String negativePrompt, + @JsonProperty("seed") Integer seed, + @JsonProperty("num_inference_steps") Integer numInferenceSteps, + @JsonProperty("guidance_scale") Float guidanceScale, + @JsonProperty("image") String image) { + + public SiliconflowImageRequest(String prompt, String model) { + this(prompt, model, null, null, null, null, null, null); + } + } + + public static Builder builder() { + return new Builder(); + } + + /** + * Builder to construct {@link SiiconflowmageApi} instance. + */ + public static class Builder { + + private String baseUrl = SiiconflowApiConstants.DEFAULT_BASE_URL; + + private ApiKey apiKey; + + private MultiValueMap headers = new LinkedMultiValueMap<>(); + + private RestClient.Builder restClientBuilder = RestClient.builder(); + + private ResponseErrorHandler responseErrorHandler = RetryUtils.DEFAULT_RESPONSE_ERROR_HANDLER; + + public Builder baseUrl(String baseUrl) { + Assert.hasText(baseUrl, "baseUrl cannot be null or empty"); + this.baseUrl = baseUrl; + return this; + } + + public Builder apiKey(ApiKey apiKey) { + Assert.notNull(apiKey, "apiKey cannot be null"); + this.apiKey = apiKey; + return this; + } + + public Builder apiKey(String simpleApiKey) { + Assert.notNull(simpleApiKey, "simpleApiKey cannot be null"); + this.apiKey = new SimpleApiKey(simpleApiKey); + return this; + } + + public Builder headers(MultiValueMap headers) { + Assert.notNull(headers, "headers cannot be null"); + this.headers = headers; + return this; + } + + public Builder restClientBuilder(RestClient.Builder restClientBuilder) { + Assert.notNull(restClientBuilder, "restClientBuilder cannot be null"); + this.restClientBuilder = restClientBuilder; + return this; + } + + public Builder responseErrorHandler(ResponseErrorHandler responseErrorHandler) { + Assert.notNull(responseErrorHandler, "responseErrorHandler cannot be null"); + this.responseErrorHandler = responseErrorHandler; + return this; + } + + public SiiconflowmageApi build() { + Assert.notNull(this.apiKey, "apiKey must be set"); + return new SiiconflowmageApi(this.baseUrl, this.apiKey, this.headers, this.restClientBuilder, + this.responseErrorHandler); + } + + } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiliconFlowChatModel.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiliconFlowChatModel.java index cada37987d..cda2cb378a 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiliconFlowChatModel.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiliconFlowChatModel.java @@ -20,10 +20,6 @@ import reactor.core.publisher.Flux; @RequiredArgsConstructor public class SiliconFlowChatModel implements ChatModel { - public static final String BASE_URL = "https://api.siliconflow.cn"; - - public static final String MODEL_DEFAULT = "deepseek-ai/DeepSeek-R1-Distill-Qwen-7B"; - /** * 兼容 OpenAI 接口,进行复用 */ diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiliconflowImageModel.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiliconflowImageModel.java new file mode 100644 index 0000000000..0e137da726 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiliconflowImageModel.java @@ -0,0 +1,166 @@ +/* + * Copyright 2023-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.iocoder.yudao.framework.ai.core.model.siliconflow; + +import io.micrometer.observation.ObservationRegistry; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ai.image.*; +import org.springframework.ai.image.observation.DefaultImageModelObservationConvention; +import org.springframework.ai.image.observation.ImageModelObservationContext; +import org.springframework.ai.image.observation.ImageModelObservationConvention; +import org.springframework.ai.image.observation.ImageModelObservationDocumentation; +import org.springframework.ai.model.ModelOptionsUtils; +import org.springframework.ai.openai.api.OpenAiImageApi; +import org.springframework.ai.openai.api.common.OpenAiApiConstants; +import org.springframework.ai.openai.metadata.OpenAiImageGenerationMetadata; +import org.springframework.ai.retry.RetryUtils; +import org.springframework.http.ResponseEntity; +import org.springframework.retry.support.RetryTemplate; +import org.springframework.util.Assert; + +import java.util.List; + +/** + * cv openapi图片模型方法 + * + * @author zzt + */ +public class SiliconflowImageModel implements ImageModel { + + private static final Logger logger = LoggerFactory.getLogger(SiliconflowImageModel.class); + + private static final ImageModelObservationConvention DEFAULT_OBSERVATION_CONVENTION = new DefaultImageModelObservationConvention(); + + /** + * The default options used for the image completion requests. + */ + private final SiliconflowImageOptions defaultOptions; + + /** + * The retry template used to retry the OpenAI Image API calls. + */ + private final RetryTemplate retryTemplate; + + /** + * Low-level access to the OpenAI Image API. + */ + private final SiiconflowmageApi siiconflowmageApi; + + /** + * Observation registry used for instrumentation. + */ + private final ObservationRegistry observationRegistry; + + /** + * Conventions to use for generating observations. + */ + private ImageModelObservationConvention observationConvention = DEFAULT_OBSERVATION_CONVENTION; + + /** + * Creates an instance of the OpenAiImageModel. + * @param siiconflowmageApi The OpenAiImageApi instance to be used for interacting with + * the OpenAI Image API. + * @throws IllegalArgumentException if openAiImageApi is null + */ + public SiliconflowImageModel(SiiconflowmageApi siiconflowmageApi) { + this(siiconflowmageApi, SiliconflowImageOptions.builder().build(), RetryUtils.DEFAULT_RETRY_TEMPLATE); + } + + /** + * Initializes a new instance of the OpenAiImageModel. + * @param siiconflowmageApi The OpenAiImageApi instance to be used for interacting with + * the OpenAI Image API. + * @param options The OpenAiImageOptions to configure the image model. + * @param retryTemplate The retry template. + */ + public SiliconflowImageModel(SiiconflowmageApi siiconflowmageApi, SiliconflowImageOptions options, RetryTemplate retryTemplate) { + this(siiconflowmageApi, options, retryTemplate, ObservationRegistry.NOOP); + } + + /** + * Initializes a new instance of the OpenAiImageModel. + * @param siiconflowmageApi The OpenAiImageApi instance to be used for interacting with + * the OpenAI Image API. + * @param options The OpenAiImageOptions to configure the image model. + * @param retryTemplate The retry template. + * @param observationRegistry The ObservationRegistry used for instrumentation. + */ + public SiliconflowImageModel(SiiconflowmageApi siiconflowmageApi, SiliconflowImageOptions options, RetryTemplate retryTemplate, + ObservationRegistry observationRegistry) { + Assert.notNull(siiconflowmageApi, "OpenAiImageApi must not be null"); + Assert.notNull(options, "options must not be null"); + Assert.notNull(retryTemplate, "retryTemplate must not be null"); + Assert.notNull(observationRegistry, "observationRegistry must not be null"); + this.siiconflowmageApi = siiconflowmageApi; + this.defaultOptions = options; + this.retryTemplate = retryTemplate; + this.observationRegistry = observationRegistry; + } + + @Override + public ImageResponse call(ImagePrompt imagePrompt) { + SiiconflowmageApi.SiliconflowImageRequest imageRequest = createRequest(imagePrompt); + + var observationContext = ImageModelObservationContext.builder() + .imagePrompt(imagePrompt) + .provider(OpenAiApiConstants.PROVIDER_NAME) + .requestOptions(imagePrompt.getOptions()) + .build(); + + return ImageModelObservationDocumentation.IMAGE_MODEL_OPERATION + .observation(this.observationConvention, DEFAULT_OBSERVATION_CONVENTION, () -> observationContext, + this.observationRegistry) + .observe(() -> { + ResponseEntity imageResponseEntity = this.retryTemplate + .execute(ctx -> this.siiconflowmageApi.createImage(imageRequest)); + + ImageResponse imageResponse = convertResponse(imageResponseEntity, imageRequest); + + observationContext.setResponse(imageResponse); + + return imageResponse; + }); + } + + private SiiconflowmageApi.SiliconflowImageRequest createRequest(ImagePrompt imagePrompt) { + String instructions = imagePrompt.getInstructions().get(0).getText(); + + SiiconflowmageApi.SiliconflowImageRequest imageRequest = new SiiconflowmageApi.SiliconflowImageRequest(instructions, + imagePrompt.getOptions().getModel()); + + return ModelOptionsUtils.merge(imagePrompt.getOptions(), imageRequest, SiiconflowmageApi.SiliconflowImageRequest.class); + } + + private ImageResponse convertResponse(ResponseEntity imageResponseEntity, + SiiconflowmageApi.SiliconflowImageRequest siliconflowImageRequest) { + OpenAiImageApi.OpenAiImageResponse imageApiResponse = imageResponseEntity.getBody(); + if (imageApiResponse == null) { + logger.warn("No image response returned for request: {}", siliconflowImageRequest); + return new ImageResponse(List.of()); + } + + List imageGenerationList = imageApiResponse.data() + .stream() + .map(entry -> new ImageGeneration(new Image(entry.url(), entry.b64Json()), + new OpenAiImageGenerationMetadata(entry.revisedPrompt()))) + .toList(); + + ImageResponseMetadata openAiImageResponseMetadata = new ImageResponseMetadata(imageApiResponse.created()); + return new ImageResponse(imageGenerationList, openAiImageResponseMetadata); + } +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiliconflowImageOptions.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiliconflowImageOptions.java new file mode 100644 index 0000000000..8af0cbeea8 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiliconflowImageOptions.java @@ -0,0 +1,166 @@ +package cn.iocoder.yudao.framework.ai.core.model.siliconflow; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import org.springframework.ai.image.ImageOptions; +import org.springframework.ai.openai.OpenAiImageOptions; + +/** + * 硅基流动画图能力 + * + * @author zzt + */ +@Data +public class SiliconflowImageOptions implements ImageOptions { + + @JsonProperty("model") + private String model; + + @JsonProperty("negative_prompt") + private String negativePrompt; + + /** + * The number of images to generate. Must be between 1 and 4. + */ + @JsonProperty("image_size") + private String imageSize; + + /** + * The number of images to generate. Must be between 1 and 4. + */ + @JsonProperty("batch_size") + private Integer batchSize = 1; + + /** + * number of inference steps + */ + @JsonProperty("num_inference_steps") + private Integer numInferenceSteps = 25; + + + /** + * This value is used to control the degree of match between the generated image and the given prompt. The higher the value, the more the generated image will tend to strictly match the text prompt. The lower the value, the more creative and diverse the generated image will be, potentially containing more unexpected elements. + * + * Required range: 0 <= x <= 20 + */ + @JsonProperty("guidance_scale") + private Float guidanceScale = 0.75F; + + /** + * 如果想要每次都生成固定的图片,可以把seed设置为固定值。 + * + */ + @JsonProperty("seed") + private Integer seed = (int)(Math.random() * 1_000_000_000); + + /** + * The image that needs to be uploaded should be converted into base64 format. + */ + @JsonProperty("image") + private String image; + + + /** + * 宽 + */ + private Integer width; + + + /** + * 高 + */ + private Integer height; + + public void setHeight(Integer height) { + this.height = height; + if (this.width != null && this.height != null) { + this.imageSize = this.width + "x" + this.height; + } + } + + public void setWidth(Integer width) { + this.width = width; + if (this.width != null && this.height != null) { + this.imageSize = this.width + "x" + this.height; + } + } + + /** + * 硅基流动 + * @return + */ + public static SiliconflowImageOptions.Builder builder() { + return new SiliconflowImageOptions.Builder(); + } + + @Override + public String toString() { + + return "SiliconflowImageOptions{" + "model='" + getModel() + '\'' + ", batch_size=" + batchSize + ", imageSize=" + imageSize + ", negativePrompt='" + + negativePrompt + '\'' + '}'; + } + + @Override + public Integer getN() { + return null; + } + + @Override + public String getResponseFormat() { + return null; + } + + @Override + public String getStyle() { + return null; + } + + public static class Builder extends OpenAiImageOptions{ + + private final SiliconflowImageOptions options; + + private Builder() { + this.options = new SiliconflowImageOptions(); + } + + public SiliconflowImageOptions.Builder model(String model) { + this.options.setModel(model); + return this; + } + + public SiliconflowImageOptions.Builder withBatchSize(Integer batchSize) { + options.setBatchSize(batchSize); + return this; + } + + public SiliconflowImageOptions.Builder withModel(String model) { + options.setModel(model); + return this; + } + + public SiliconflowImageOptions.Builder withWidth(Integer width) { + options.setWidth(width); + return this; + } + + public SiliconflowImageOptions.Builder withHeight(Integer height) { + options.setHeight(height); + return this; + } + + public SiliconflowImageOptions.Builder withSeed(Integer seed) { + options.setSeed(seed); + return this; + } + + public SiliconflowImageOptions.Builder withNegativePrompt(String negativePrompt) { + options.setNegativePrompt(negativePrompt); + return this; + } + + public SiliconflowImageOptions build() { + return options; + } + + } +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/util/AiUtils.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/util/AiUtils.java index becc54ee43..8ca8772a4d 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/util/AiUtils.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/util/AiUtils.java @@ -50,8 +50,8 @@ public class AiUtils { case HUN_YUAN: // 复用 OpenAI 客户端 case XING_HUO: // 复用 OpenAI 客户端 case SILICON_FLOW: // 复用 OpenAI 客户端 - return OpenAiChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens) - .toolNames(toolNames).build(); + OpenAiChatOptions.Builder builder = OpenAiChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens); + return toolNames == null ? builder.build() : builder.toolNames(toolNames).build(); case AZURE_OPENAI: // TODO 芋艿:貌似没 model 字段???! return AzureOpenAiChatOptions.builder().deploymentName(model).temperature(temperature).maxTokens(maxTokens) diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/SiliconFlowChatModelTests.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/SiliconFlowChatModelTests.java index 880795fe96..1344a7179f 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/SiliconFlowChatModelTests.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/SiliconFlowChatModelTests.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.framework.ai.chat; +import cn.iocoder.yudao.framework.ai.core.model.siliconflow.SiiconflowApiConstants; import cn.iocoder.yudao.framework.ai.core.model.siliconflow.SiliconFlowChatModel; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -25,11 +26,11 @@ public class SiliconFlowChatModelTests { private final OpenAiChatModel openAiChatModel = OpenAiChatModel.builder() .openAiApi(OpenAiApi.builder() - .baseUrl(SiliconFlowChatModel.BASE_URL) + .baseUrl(SiiconflowApiConstants.DEFAULT_BASE_URL) .apiKey("sk-epsakfenqnyzoxhmbucsxlhkdqlcbnimslqoivkshalvdozz") // apiKey .build()) .defaultOptions(OpenAiChatOptions.builder() - .model(SiliconFlowChatModel.MODEL_DEFAULT) // 模型 + .model(SiiconflowApiConstants.MODEL_DEFAULT) // 模型 // .model("deepseek-ai/DeepSeek-R1") // 模型(deepseek-ai/DeepSeek-R1)可用赠费 // .model("Pro/deepseek-ai/DeepSeek-R1") // 模型(Pro/deepseek-ai/DeepSeek-R1)需要付费 .temperature(0.7) From 41639d5dd7c9b8b272e615435fa7e2707998a478 Mon Sep 17 00:00:00 2001 From: smallNorthLee <18210040298@163.com> Date: Tue, 18 Mar 2025 19:12:39 +0800 Subject: [PATCH 13/36] =?UTF-8?q?review:=20=E9=87=8D=E6=9E=84=E7=BB=84?= =?UTF-8?q?=E8=A3=85=E4=B8=8B=E4=B8=80=E4=B8=AA=E8=8A=82=E7=82=B9=E5=AE=A1?= =?UTF-8?q?=E6=89=B9=E4=BA=BA=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/task/BpmProcessInstanceServiceImpl.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java index 7fd065e833..f987620802 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java @@ -267,7 +267,6 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService FlowElement flowElement = bpmnModel.getFlowElement(task.getTaskDefinitionKey()); List nextFlowNodes = BpmnModelUtils.getNextFlowNodes(flowElement, bpmnModel, processVariables); - // TODO @小北:还是可以优化下哈;“4. 组装节点信息” 只拼接出 candidateUserIds;之后,再第二次循环,查询用户和部门信息,进行拼接 // 2. 收集所有节点的候选用户 ID Set allCandidateUsers = new HashSet<>(); for (FlowNode node : nextFlowNodes) { @@ -287,13 +286,13 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService loginUserId, historicProcessInstance.getProcessDefinitionId(), processVariables); // 4.2 组装候选用户信息 - List candidateUsers = new ArrayList<>(); - for (Long userId : candidateUserIds) { - UserSimpleBaseVO user = BpmProcessInstanceConvert.INSTANCE.buildUser(userId, userMap, deptMap); + List candidateUsers = convertList(candidateUserIds, userId -> { + AdminUserRespDTO user = userMap.get(userId); if (user != null) { - candidateUsers.add(user); + return BpmProcessInstanceConvert.INSTANCE.buildUser(userId, userMap, deptMap); } - } + return null; + }); // 4.3 构建节点信息 return new ActivityNode() From b6c700af6babf05a9d08f70c34c115e6257bfc9f Mon Sep 17 00:00:00 2001 From: lizhixian <18210040298@163.com> Date: Wed, 19 Mar 2025 17:21:29 +0800 Subject: [PATCH 14/36] =?UTF-8?q?fix=EF=BC=9A=E4=BF=AE=E5=A4=8Dsimple?= =?UTF-8?q?=E8=AE=BE=E8=AE=A1=E5=99=A8=E7=AC=AC=E4=B8=80=E4=B8=AA=E5=8F=91?= =?UTF-8?q?=E8=B5=B7=E4=BA=BA=E8=8A=82=E7=82=B9=EF=BC=8C=E5=AE=A1=E6=89=B9?= =?UTF-8?q?=E6=97=B6=E6=A0=A1=E9=AA=8C=E6=98=AF=E5=90=A6=E5=AD=98=E5=9C=A8?= =?UTF-8?q?=E5=AE=A1=E6=89=B9=E4=BA=BA=E5=AF=BC=E8=87=B4=E6=B5=81=E7=A8=8B?= =?UTF-8?q?=E5=BC=82=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yudao/module/bpm/service/task/BpmTaskServiceImpl.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java index 08e0cdad1e..451bc3b996 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java @@ -599,6 +599,9 @@ public class BpmTaskServiceImpl implements BpmTaskService { */ private Map validateAndSetNextAssignees(String taskDefinitionKey, Map variables, BpmnModel bpmnModel, Map> nextAssignees, ProcessInstance processInstance) { + if (taskDefinitionKey.equals(START_USER_NODE_ID)) { + return variables; + } // 1. 获取下一个将要执行的节点集合 FlowElement flowElement = bpmnModel.getFlowElement(taskDefinitionKey); List nextFlowNodes = getNextFlowNodes(flowElement, bpmnModel, variables); From f70a50472fefd6f17119ab91a0a8e4a6f551cbab Mon Sep 17 00:00:00 2001 From: lizhixian <18210040298@163.com> Date: Wed, 19 Mar 2025 17:23:12 +0800 Subject: [PATCH 15/36] =?UTF-8?q?fix=EF=BC=9A=E4=BF=AE=E5=A4=8Dsimple?= =?UTF-8?q?=E8=AE=BE=E8=AE=A1=E5=99=A8=E7=AC=AC=E4=B8=80=E4=B8=AA=E5=8F=91?= =?UTF-8?q?=E8=B5=B7=E4=BA=BA=E8=8A=82=E7=82=B9=EF=BC=8C=E5=AE=A1=E6=89=B9?= =?UTF-8?q?=E6=97=B6=E6=A0=A1=E9=AA=8C=E6=98=AF=E5=90=A6=E5=AD=98=E5=9C=A8?= =?UTF-8?q?=E5=AE=A1=E6=89=B9=E4=BA=BA=E5=AF=BC=E8=87=B4=E6=B5=81=E7=A8=8B?= =?UTF-8?q?=E5=BC=82=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yudao/module/bpm/service/task/BpmTaskServiceImpl.java | 1 + 1 file changed, 1 insertion(+) diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java index 451bc3b996..c0098f706e 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java @@ -599,6 +599,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { */ private Map validateAndSetNextAssignees(String taskDefinitionKey, Map variables, BpmnModel bpmnModel, Map> nextAssignees, ProcessInstance processInstance) { + // Simple设计器第一个节点默认为发起人节点,不校验是否存在审批人 if (taskDefinitionKey.equals(START_USER_NODE_ID)) { return variables; } From ecf4df86204e278cc1c66f788947b4eb9906bc87 Mon Sep 17 00:00:00 2001 From: zzt <976209226@qq.com> Date: Thu, 20 Mar 2025 08:04:32 +0800 Subject: [PATCH 16/36] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E3=80=91AI=EF=BC=9A=E8=85=BE=E8=AE=AF=E5=9B=BE?= =?UTF-8?q?=E5=83=8F=E5=88=9B=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../creation/AiartCreationController.java | 36 ++++++++++ .../admin/creation/AiartStyleController.java | 40 ++++++++++++ .../vo/style/AiartReplaceBackgroundReqVO.java | 17 +++++ .../vo/style/AiartReplaceBackgroundResVO.java | 18 +++++ .../vo/style/BackgroundTemplateReqVO.java | 16 +++++ .../vo/style/BackgroundTemplateResVO.java | 28 ++++++++ .../AiartStyleBackgroundTemplateDO.java | 65 +++++++++++++++++++ .../AiartStyleBackgroundTemplateMapper.java | 33 ++++++++++ .../creation/AiartCreationService.java | 21 ++++++ .../creation/AiartCreationServiceImpl.java | 22 +++++++ .../service/creation/AiartStyleService.java | 23 +++++++ .../creation/AiartStyleServiceImpl.java | 27 ++++++++ .../yudao-spring-boot-starter-ai/pom.xml | 7 ++ 13 files changed, 353 insertions(+) create mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/creation/AiartCreationController.java create mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/creation/AiartStyleController.java create mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/creation/vo/style/AiartReplaceBackgroundReqVO.java create mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/creation/vo/style/AiartReplaceBackgroundResVO.java create mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/creation/vo/style/BackgroundTemplateReqVO.java create mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/creation/vo/style/BackgroundTemplateResVO.java create mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/creation/AiartStyleBackgroundTemplateDO.java create mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/creation/AiartStyleBackgroundTemplateMapper.java create mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/creation/AiartCreationService.java create mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/creation/AiartCreationServiceImpl.java create mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/creation/AiartStyleService.java create mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/creation/AiartStyleServiceImpl.java diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/creation/AiartCreationController.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/creation/AiartCreationController.java new file mode 100644 index 0000000000..64605d7177 --- /dev/null +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/creation/AiartCreationController.java @@ -0,0 +1,36 @@ +package cn.iocoder.yudao.module.ai.controller.admin.creation; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.module.ai.controller.admin.creation.vo.style.AiartReplaceBackgroundReqVO; +import cn.iocoder.yudao.module.ai.controller.admin.creation.vo.style.AiartReplaceBackgroundResVO; +import cn.iocoder.yudao.module.ai.service.creation.AiartCreationService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; + +@Tag(name = "管理后台 - 艺术创作") +@RestController +@RequestMapping("/ai/artcreation") +@Validated +public class AiartCreationController { + + @Resource + private AiartCreationService aiartCreationService; + + @Operation(summary = "商品图替换背景") + @PostMapping("/replacebackground") + public CommonResult replaceBackground(@Valid @RequestBody AiartReplaceBackgroundReqVO replaceBackgroundReqVO) { + return success(aiartCreationService.replaceBackground(getLoginUserId(), replaceBackgroundReqVO)); + } + + +} \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/creation/AiartStyleController.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/creation/AiartStyleController.java new file mode 100644 index 0000000000..4b70525e4a --- /dev/null +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/creation/AiartStyleController.java @@ -0,0 +1,40 @@ +package cn.iocoder.yudao.module.ai.controller.admin.creation; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.ai.controller.admin.creation.vo.style.BackgroundTemplateReqVO; +import cn.iocoder.yudao.module.ai.controller.admin.creation.vo.style.BackgroundTemplateResVO; +import cn.iocoder.yudao.module.ai.dal.dataobject.creation.AiartStyleBackgroundTemplateDO; +import cn.iocoder.yudao.module.ai.service.creation.AiartStyleService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - 艺术创作模版") +@RestController +@RequestMapping("/ai/artstyle") +@Validated +public class AiartStyleController { + + @Resource + private AiartStyleService aiartStyleService; + + @PostMapping("/backgroundtemplate-list") + @Operation(summary = "获取商品背景模版列表") + @PreAuthorize("@ss.hasPermission('ai:artstyle-background:query')") + public CommonResult> queryBackgroundStyle(@Valid @RequestBody BackgroundTemplateReqVO backgroundTemplateReqVO) { + List res = aiartStyleService.queryBackgroundStyle(backgroundTemplateReqVO); + return success(BeanUtils.toBean(res,BackgroundTemplateResVO.class)); + } +} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/creation/vo/style/AiartReplaceBackgroundReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/creation/vo/style/AiartReplaceBackgroundReqVO.java new file mode 100644 index 0000000000..8f2e9f9284 --- /dev/null +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/creation/vo/style/AiartReplaceBackgroundReqVO.java @@ -0,0 +1,17 @@ +package cn.iocoder.yudao.module.ai.controller.admin.creation.vo.style; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.Size; +import lombok.Data; + +@Schema(description = "管理后台 - 商品替换背景请求 VO") +@Data +public class AiartReplaceBackgroundReqVO { + + @Schema(description = "提示词", requiredMode = Schema.RequiredMode.REQUIRED, example = "画一个长城") + @NotEmpty(message = "提示词不能为空") + @Size(max = 1200, message = "提示词最大 1200") + private String prompt; + +} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/creation/vo/style/AiartReplaceBackgroundResVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/creation/vo/style/AiartReplaceBackgroundResVO.java new file mode 100644 index 0000000000..43ec35c774 --- /dev/null +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/creation/vo/style/AiartReplaceBackgroundResVO.java @@ -0,0 +1,18 @@ +package cn.iocoder.yudao.module.ai.controller.admin.creation.vo.style; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 商品替换背景结果 VO") +@Data +public class AiartReplaceBackgroundResVO { + + @Schema(description = "结果url", requiredMode = Schema.RequiredMode.REQUIRED, example = "根据参数不同不一样") + private String resultImage; + + @Schema(description = "唯一请求 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "由服务端生成") + private String requestId; + + @Schema(description = "MaskUrl", requiredMode = Schema.RequiredMode.REQUIRED, example = "如果 MaskUrl 未传,则返回使用内置商品分割算法得到的 Mask 结果") + private String maskImage; +} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/creation/vo/style/BackgroundTemplateReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/creation/vo/style/BackgroundTemplateReqVO.java new file mode 100644 index 0000000000..42ad20c174 --- /dev/null +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/creation/vo/style/BackgroundTemplateReqVO.java @@ -0,0 +1,16 @@ +package cn.iocoder.yudao.module.ai.controller.admin.creation.vo.style; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - AI API 密钥新增/修改 Request VO") +@Data +public class BackgroundTemplateReqVO { + + @Schema(description = "一级级次名称", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "通用") + private String fistLevelName; + + @Schema(description = "二级级次名称", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "色彩背景") + private String secondLevelName; + +} \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/creation/vo/style/BackgroundTemplateResVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/creation/vo/style/BackgroundTemplateResVO.java new file mode 100644 index 0000000000..2fb0ae3ac0 --- /dev/null +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/creation/vo/style/BackgroundTemplateResVO.java @@ -0,0 +1,28 @@ +package cn.iocoder.yudao.module.ai.controller.admin.creation.vo.style; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 商品替换背景模版 VO") +@Data +public class BackgroundTemplateResVO { + + @Schema(description = "一级分类名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "汽车、通用") + private String levelFirst; + + @Schema(description = "二级分类名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "色彩背景") + private String levelSecond; + + @Schema(description = "背景模版名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "彩虹渐变") + private String name; + + @Schema(description = "背景模版提示词", requiredMode = Schema.RequiredMode.REQUIRED, example = "背景为彩虹渐变墙面,亮暖色,清新简约风格,喷涂效果") + private String prompt; + + @Schema(description = "背景模版商品示例", requiredMode = Schema.RequiredMode.REQUIRED, example = "小熊玩偶") + private String demProduct; + + @Schema(description = "背景模版商品示例效果", requiredMode = Schema.RequiredMode.REQUIRED, example = "url") + private String demUrl; +} + diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/creation/AiartStyleBackgroundTemplateDO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/creation/AiartStyleBackgroundTemplateDO.java new file mode 100644 index 0000000000..0cc538efca --- /dev/null +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/creation/AiartStyleBackgroundTemplateDO.java @@ -0,0 +1,65 @@ +package cn.iocoder.yudao.module.ai.dal.dataobject.creation; + +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * AI art商品背景风格 DO + * + * @author 芋道源码 + */ +@TableName("ai_backgroundtemplate") +@KeySequence("ai_chat_conversation_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class AiartStyleBackgroundTemplateDO extends BaseDO { + /** + * 编号 + */ + @TableId + private Long id; + + /** + * 名称 + */ + private String levelFirst; + + /** + * 名称 + */ + private String levelSecond; + /** + * 模版名称 + */ + private String name; + /** + * 模版提示词 + */ + private String prompt; + /** + * 示例商品 + */ + private String demoProduct; + + /** + * 示例效果 + */ + private String demoUrl; + + /** + * 状态 + * + * 枚举 {@link CommonStatusEnum} + */ + private Integer status; + +} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/creation/AiartStyleBackgroundTemplateMapper.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/creation/AiartStyleBackgroundTemplateMapper.java new file mode 100644 index 0000000000..a0ac51a7e6 --- /dev/null +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/creation/AiartStyleBackgroundTemplateMapper.java @@ -0,0 +1,33 @@ +package cn.iocoder.yudao.module.ai.dal.mysql.creation; + +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.ai.controller.admin.creation.vo.style.BackgroundTemplateReqVO; +import cn.iocoder.yudao.module.ai.dal.dataobject.creation.AiartStyleBackgroundTemplateDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +/** + * AI style分隔 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface AiartStyleBackgroundTemplateMapper extends BaseMapperX { + + /** + * 获取有效的商品后端模版 + * @param reqVO 请求对象 + * @return 结果 + */ + default List selectList(BackgroundTemplateReqVO reqVO) { + return selectList(new LambdaQueryWrapperX() + .eqIfPresent(AiartStyleBackgroundTemplateDO::getLevelFirst,reqVO.getFistLevelName()) + .eqIfPresent(AiartStyleBackgroundTemplateDO::getLevelSecond,reqVO.getSecondLevelName()) + .eqIfPresent(AiartStyleBackgroundTemplateDO::getStatus, CommonStatusEnum.ENABLE.getStatus()) + .orderByDesc(AiartStyleBackgroundTemplateDO::getId)); + } + +} \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/creation/AiartCreationService.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/creation/AiartCreationService.java new file mode 100644 index 0000000000..e2300a8082 --- /dev/null +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/creation/AiartCreationService.java @@ -0,0 +1,21 @@ +package cn.iocoder.yudao.module.ai.service.creation; + +import cn.iocoder.yudao.module.ai.controller.admin.creation.vo.style.AiartReplaceBackgroundReqVO; +import cn.iocoder.yudao.module.ai.controller.admin.creation.vo.style.AiartReplaceBackgroundResVO; + +/** + * AI创作Service 接口 + * + * @author zzt + */ +public interface AiartCreationService { + + + /** + * 替换商品背景图片 + * @param loginUserId 当前登录用户 + * @param replaceBackgroundReqVO 请求对象 + * @return 结果 + */ + AiartReplaceBackgroundResVO replaceBackground(Long loginUserId, AiartReplaceBackgroundReqVO replaceBackgroundReqVO); +} \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/creation/AiartCreationServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/creation/AiartCreationServiceImpl.java new file mode 100644 index 0000000000..f4f14cab19 --- /dev/null +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/creation/AiartCreationServiceImpl.java @@ -0,0 +1,22 @@ +package cn.iocoder.yudao.module.ai.service.creation; + +import cn.iocoder.yudao.module.ai.controller.admin.creation.vo.style.AiartReplaceBackgroundReqVO; +import cn.iocoder.yudao.module.ai.controller.admin.creation.vo.style.AiartReplaceBackgroundResVO; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +/** + * AI创作Service 实现类 + * + * @author zzt + */ +@Service +@Validated +public class AiartCreationServiceImpl implements AiartCreationService { + + + @Override + public AiartReplaceBackgroundResVO replaceBackground(Long loginUserId, AiartReplaceBackgroundReqVO replaceBackgroundReqVO) { + return null; + } +} \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/creation/AiartStyleService.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/creation/AiartStyleService.java new file mode 100644 index 0000000000..635577086a --- /dev/null +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/creation/AiartStyleService.java @@ -0,0 +1,23 @@ +package cn.iocoder.yudao.module.ai.service.creation; + +import cn.iocoder.yudao.module.ai.controller.admin.creation.vo.style.BackgroundTemplateReqVO; +import cn.iocoder.yudao.module.ai.controller.admin.creation.vo.style.BackgroundTemplateResVO; +import cn.iocoder.yudao.module.ai.dal.dataobject.creation.AiartStyleBackgroundTemplateDO; + +import java.util.List; + +/** + * AI风格Service 接口 + * + * @author zzt + */ +public interface AiartStyleService { + + + /** + * 查询商品背景模版列表 + * @param backgroundTemplateReqVO 商品背景模版请求对象 + * @return 结果 + */ + List queryBackgroundStyle(BackgroundTemplateReqVO backgroundTemplateReqVO); +} \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/creation/AiartStyleServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/creation/AiartStyleServiceImpl.java new file mode 100644 index 0000000000..9b85b0788f --- /dev/null +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/creation/AiartStyleServiceImpl.java @@ -0,0 +1,27 @@ +package cn.iocoder.yudao.module.ai.service.creation; + +import cn.iocoder.yudao.module.ai.controller.admin.creation.vo.style.BackgroundTemplateReqVO; +import cn.iocoder.yudao.module.ai.controller.admin.creation.vo.style.BackgroundTemplateResVO; +import cn.iocoder.yudao.module.ai.dal.dataobject.creation.AiartStyleBackgroundTemplateDO; +import cn.iocoder.yudao.module.ai.dal.mysql.creation.AiartStyleBackgroundTemplateMapper; +import cn.iocoder.yudao.module.ai.dal.mysql.model.AiApiKeyMapper; +import jakarta.annotation.Resource; + +import java.util.List; + +/** + * AI风格Service 接口 + * + * @author zzt + */ +public class AiartStyleServiceImpl implements AiartStyleService{ + + @Resource + private AiartStyleBackgroundTemplateMapper backgroundTemplateMapper; + + + @Override + public List queryBackgroundStyle(BackgroundTemplateReqVO backgroundTemplateReqVO) { + return backgroundTemplateMapper.selectList(backgroundTemplateReqVO); + } +} \ No newline at end of file diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml b/yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml index f37f3709c6..a433d9f94f 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml @@ -117,6 +117,13 @@ + + + com.tencentcloudapi + tencentcloud-sdk-java-aiart + 3.1.1215 + + org.springframework.boot From 629bf284959cafbdb8f8e454759faa250b6219bb Mon Sep 17 00:00:00 2001 From: lizhixian <18210040298@163.com> Date: Fri, 21 Mar 2025 14:08:24 +0800 Subject: [PATCH 17/36] =?UTF-8?q?fix=EF=BC=9A=E4=BF=AE=E5=A4=8D=E5=AE=A1?= =?UTF-8?q?=E6=89=B9=E6=8E=A5=E5=8F=A3=EF=BC=8C=E6=A0=A1=E9=AA=8C=E4=BB=BB?= =?UTF-8?q?=E5=8A=A1=E8=A7=84=E5=88=99=E4=B8=BA=E5=8F=91=E8=B5=B7=E4=BA=BA?= =?UTF-8?q?=E8=87=AA=E9=80=89=E6=97=B6=EF=BC=8C=E5=85=88=E4=BB=8E=E5=8E=86?= =?UTF-8?q?=E5=8F=B2=E5=8F=98=E9=87=8F=E4=B8=AD=E6=9F=A5=E8=AF=A2=E5=AE=A1?= =?UTF-8?q?=E6=89=B9=E4=BA=BA=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bpm/service/task/BpmTaskServiceImpl.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java index c0098f706e..9eb25323ee 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java @@ -613,16 +613,17 @@ public class BpmTaskServiceImpl implements BpmTaskService { Integer candidateStrategy = parseCandidateStrategy(nextFlowNode); // 2.1 情况一:如果节点中的审批人策略为 发起人自选 if (ObjUtil.equals(candidateStrategy, BpmTaskCandidateStrategyEnum.START_USER_SELECT.getStrategy())) { - // 如果节点存在,但未配置审批人 - List assignees = nextAssignees != null ? nextAssignees.get(nextFlowNode.getId()) : null; - if (CollUtil.isEmpty(assignees)) { - throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, nextFlowNode.getName()); - } + // 先从历史中获取审批人,在发起人会把所有的审批人保存到历史中,这里从历史中获取 processVariables = FlowableUtils.getStartUserSelectAssignees(processInstance.getProcessVariables()); // 特殊:如果当前节点已经存在审批人,则不允许覆盖 if (processVariables != null && CollUtil.isNotEmpty(processVariables.get(nextFlowNode.getId()))) { continue; } + // 如果节点存在,但未配置审批人 + List assignees = nextAssignees != null ? nextAssignees.get(nextFlowNode.getId()) : null; + if (CollUtil.isEmpty(assignees)) { + throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, nextFlowNode.getName()); + } // 设置 PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES if (processVariables == null) { processVariables = new HashMap<>(); @@ -632,12 +633,13 @@ public class BpmTaskServiceImpl implements BpmTaskService { } // 2.2 情况二:如果节点中的审批人策略为 审批人,在审批时选择下一个节点的审批人,并且该节点的审批人为空 if (ObjUtil.equals(candidateStrategy, BpmTaskCandidateStrategyEnum.APPROVE_USER_SELECT.getStrategy())) { + // 获取审批人自选的历史变量 + processVariables = FlowableUtils.getApproveUserSelectAssignees(processInstance.getProcessVariables()); // 如果节点存在,但未配置审批人 List assignees = nextAssignees != null ? nextAssignees.get(nextFlowNode.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_APPROVE_USER_SELECT_ASSIGNEES_NOT_CONFIG, nextFlowNode.getName()); } - processVariables = FlowableUtils.getApproveUserSelectAssignees(processInstance.getProcessVariables()); if (processVariables == null) { processVariables = new HashMap<>(); } From 9b11199665f00790ecabad6e10f449c4148635f1 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 22 Mar 2025 09:41:22 +0800 Subject: [PATCH 18/36] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E3=80=91system=EF=BC=9A=E5=A2=9E=E5=8A=A0=E7=A7=9F?= =?UTF-8?q?=E6=88=B7=E7=9A=84=E4=B8=8B=E6=8B=89=E9=80=89=E6=8B=A9=EF=BC=8C?= =?UTF-8?q?=E7=94=A8=E4=BA=8E=E7=99=BB=E5=BD=95=E7=95=8C=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/admin/auth/AuthController.java | 16 ++------------ .../admin/permission/MenuController.java | 4 ++-- .../admin/tenant/TenantController.java | 22 ++++++++++++++----- .../tenant/vo/tenant/TenantSimpleRespVO.java | 16 -------------- .../system/dal/mysql/tenant/TenantMapper.java | 4 ++++ .../system/service/tenant/TenantService.java | 10 ++++++++- .../service/tenant/TenantServiceImpl.java | 5 +++++ .../src/main/resources/application.yaml | 1 + 8 files changed, 40 insertions(+), 38 deletions(-) delete mode 100755 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/tenant/TenantSimpleRespVO.java diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java index 1e7a99a322..d9269470d0 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java @@ -7,14 +7,7 @@ import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.security.config.SecurityProperties; import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; -import cn.iocoder.yudao.module.system.controller.admin.auth.vo.AuthLoginReqVO; -import cn.iocoder.yudao.module.system.controller.admin.auth.vo.AuthLoginRespVO; -import cn.iocoder.yudao.module.system.controller.admin.auth.vo.AuthPermissionInfoRespVO; -import cn.iocoder.yudao.module.system.controller.admin.auth.vo.AuthRegisterReqVO; -import cn.iocoder.yudao.module.system.controller.admin.auth.vo.AuthResetPasswordReqVO; -import cn.iocoder.yudao.module.system.controller.admin.auth.vo.AuthSmsLoginReqVO; -import cn.iocoder.yudao.module.system.controller.admin.auth.vo.AuthSmsSendReqVO; -import cn.iocoder.yudao.module.system.controller.admin.auth.vo.AuthSocialLoginReqVO; +import cn.iocoder.yudao.module.system.controller.admin.auth.vo.*; import cn.iocoder.yudao.module.system.convert.auth.AuthConvert; import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO; import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO; @@ -36,12 +29,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import java.util.Collections; import java.util.List; diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/MenuController.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/MenuController.java index b6d067f1fb..824fe41048 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/MenuController.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/MenuController.java @@ -67,8 +67,8 @@ public class MenuController { } @GetMapping({"/list-all-simple", "simple-list"}) - @Operation(summary = "获取菜单精简信息列表", description = "只包含被开启的菜单,用于【角色分配菜单】功能的选项。" + - "在多租户的场景下,会只返回租户所在套餐有的菜单") + @Operation(summary = "获取菜单精简信息列表", + description = "只包含被开启的菜单,用于【角色分配菜单】功能的选项。在多租户的场景下,会只返回租户所在套餐有的菜单") public CommonResult> getSimpleMenuList() { List list = menuService.getMenuListByTenant( new MenuListReqVO().setStatus(CommonStatusEnum.ENABLE.getStatus())); diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/TenantController.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/TenantController.java index 090c6a80ff..f698b92d0c 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/TenantController.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/TenantController.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.system.controller.admin.tenant; import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageResult; @@ -9,7 +10,6 @@ import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantPageReqVO; import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantRespVO; import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantSaveReqVO; -import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantSimpleRespVO; import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantDO; import cn.iocoder.yudao.module.system.service.tenant.TenantService; import io.swagger.v3.oas.annotations.Operation; @@ -27,6 +27,7 @@ import java.util.List; import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; @Tag(name = "管理后台 - 租户") @RestController @@ -45,13 +46,25 @@ public class TenantController { return success(tenant != null ? tenant.getId() : null); } + @GetMapping({ "simple-list" }) + @PermitAll + @Operation(summary = "获取租户精简信息列表", description = "只包含被开启的租户,用于【首页】功能的选择租户选项") + public CommonResult> getTenantSimpleList() { + List list = tenantService.getTenantListByStatus(CommonStatusEnum.ENABLE.getStatus()); + return success(convertList(list, tenantDO -> + new TenantRespVO().setId(tenantDO.getId()).setName(tenantDO.getName()))); + } + @GetMapping("/get-by-website") @PermitAll @Operation(summary = "使用域名,获得租户信息", description = "登录界面,根据用户的域名,获得租户信息") @Parameter(name = "website", description = "域名", required = true, example = "www.iocoder.cn") - public CommonResult getTenantByWebsite(@RequestParam("website") String website) { + public CommonResult getTenantByWebsite(@RequestParam("website") String website) { TenantDO tenant = tenantService.getTenantByWebsite(website); - return success(BeanUtils.toBean(tenant, TenantSimpleRespVO.class)); + if (tenant == null || CommonStatusEnum.isDisable(tenant.getStatus())) { + return success(null); + } + return success(new TenantRespVO().setId(tenant.getId()).setName(tenant.getName())); } @PostMapping("/create") @@ -99,8 +112,7 @@ public class TenantController { @Operation(summary = "导出租户 Excel") @PreAuthorize("@ss.hasPermission('system:tenant:export')") @ApiAccessLog(operateType = EXPORT) - public void exportTenantExcel(@Valid TenantPageReqVO exportReqVO, - HttpServletResponse response) throws IOException { + public void exportTenantExcel(@Valid TenantPageReqVO exportReqVO, HttpServletResponse response) throws IOException { exportReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); List list = tenantService.getTenantPage(exportReqVO).getList(); // 导出 Excel diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/tenant/TenantSimpleRespVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/tenant/TenantSimpleRespVO.java deleted file mode 100755 index 49752278da..0000000000 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/tenant/TenantSimpleRespVO.java +++ /dev/null @@ -1,16 +0,0 @@ -package cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -@Schema(description = "管理后台 - 租户精简 Response VO") -@Data -public class TenantSimpleRespVO { - - @Schema(description = "租户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") - private Long id; - - @Schema(description = "租户名", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道") - private String name; - -} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/tenant/TenantMapper.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/tenant/TenantMapper.java index aaca0160ab..8ddf06065a 100755 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/tenant/TenantMapper.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/tenant/TenantMapper.java @@ -43,4 +43,8 @@ public interface TenantMapper extends BaseMapperX { return selectList(TenantDO::getPackageId, packageId); } + default List selectListByStatus(Integer status) { + return selectList(TenantDO::getStatus, status); + } + } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantService.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantService.java index e0bd9d291c..425d18d609 100755 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantService.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantService.java @@ -38,7 +38,7 @@ public interface TenantService { * 更新租户的角色菜单 * * @param tenantId 租户编号 - * @param menuIds 菜单编号数组 + * @param menuIds 菜单编号数组 */ void updateTenantRoleMenu(Long tenantId, Set menuIds); @@ -97,6 +97,14 @@ public interface TenantService { */ List getTenantListByPackageId(Long packageId); + /** + * 获得指定状态的租户列表 + * + * @param status 状态 + * @return 租户列表 + */ + List getTenantListByStatus(Integer status); + /** * 进行租户的信息处理逻辑 * 其中,租户编号从 {@link TenantContextHolder} 上下文中获取 diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImpl.java index b5b3c358ef..c3b09ec5a9 100755 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImpl.java @@ -265,6 +265,11 @@ public class TenantServiceImpl implements TenantService { return tenantMapper.selectListByPackageId(packageId); } + @Override + public List getTenantListByStatus(Integer status) { + return tenantMapper.selectListByStatus(status); + } + @Override public void handleTenantInfo(TenantInfoHandler handler) { // 如果禁用,则不执行逻辑 diff --git a/yudao-server/src/main/resources/application.yaml b/yudao-server/src/main/resources/application.yaml index 3941181b2d..13683df543 100644 --- a/yudao-server/src/main/resources/application.yaml +++ b/yudao-server/src/main/resources/application.yaml @@ -271,6 +271,7 @@ yudao: ignore-urls: - /admin-api/system/tenant/get-id-by-name # 基于名字获取租户,不许带租户编号 - /admin-api/system/tenant/get-by-website # 基于域名获取租户,不许带租户编号 + - /admin-api/system/tenant/simple-list # 获取租户列表,不许带租户编号 - /admin-api/system/captcha/get # 获取图片验证码,和租户无关 - /admin-api/system/captcha/check # 校验图片验证码,和租户无关 - /admin-api/infra/file/*/get/** # 获取图片,和租户无关 From 113dfae73255e38b686f0ee846b8d97a83edb7aa Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 22 Mar 2025 23:20:31 +0800 Subject: [PATCH 19/36] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E3=80=91BPM=EF=BC=9A1297=20fix:=20=E4=BF=AE=E5=A4=8Ds?= =?UTF-8?q?imple=E8=AE=BE=E8=AE=A1=E5=99=A8=E7=AC=AC=E4=B8=80=E4=B8=AA?= =?UTF-8?q?=E5=8F=91=E8=B5=B7=E4=BA=BA=E8=8A=82=E7=82=B9=EF=BC=8C=E5=AE=A1?= =?UTF-8?q?=E6=89=B9=E6=97=B6=E6=A0=A1=E9=AA=8C=E6=98=AF=E5=90=A6=E5=AD=98?= =?UTF-8?q?=E5=9C=A8=E5=AE=A1=E6=89=B9=E4=BA=BA=E5=AF=BC=E8=87=B4=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E5=BC=82=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../task/BpmProcessInstanceServiceImpl.java | 57 +++++++------------ .../bpm/service/task/BpmTaskServiceImpl.java | 36 ++++++------ 2 files changed, 39 insertions(+), 54 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java index f987620802..aa8f58b45d 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java @@ -14,7 +14,6 @@ import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; -import cn.iocoder.yudao.module.bpm.controller.admin.base.user.UserSimpleBaseVO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; @@ -262,47 +261,31 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService processVariables.putAll(reqVO.getProcessVariables()); } - // 3 获取当前任务节点的信息 - // 3.1 获取下一个将要执行的节点集合 + // 3. 获取下一个将要执行的节点集合 FlowElement flowElement = bpmnModel.getFlowElement(task.getTaskDefinitionKey()); List nextFlowNodes = BpmnModelUtils.getNextFlowNodes(flowElement, bpmnModel, processVariables); - - // 2. 收集所有节点的候选用户 ID - Set allCandidateUsers = new HashSet<>(); - for (FlowNode node : nextFlowNodes) { - List candidateUserIds = getTaskCandidateUserList(bpmnModel, node.getId(), - loginUserId, historicProcessInstance.getProcessDefinitionId(), processVariables); - allCandidateUsers.addAll(candidateUserIds); + List nextActivityNodes = convertList(nextFlowNodes, node -> new ActivityNode().setId(node.getId()) + .setName(node.getName()).setNodeType(BpmSimpleModelNodeTypeEnum.APPROVE_NODE.getType()) + .setStatus(BpmTaskStatusEnum.RUNNING.getStatus()) + .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(node)) + .setCandidateUserIds(getTaskCandidateUserList(bpmnModel, node.getId(), + loginUserId, historicProcessInstance.getProcessDefinitionId(), processVariables))); + if (CollUtil.isNotEmpty(nextActivityNodes)) { + return nextActivityNodes; } - // 3. 批量查询用户和部门信息 - Map userMap = adminUserApi.getUserMap(new ArrayList<>(allCandidateUsers)); + // 4. 拼接基础信息 + Map userMap = adminUserApi.getUserMap( + convertSetByFlatMap(nextActivityNodes, ActivityNode::getCandidateUserIds, Collection::stream)); Map deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId)); - - // 4. 组装节点信息 - return convertList(nextFlowNodes, node -> { - // 4.1 获取当前节点的候选用户 ID - List candidateUserIds = getTaskCandidateUserList(bpmnModel, node.getId(), - loginUserId, historicProcessInstance.getProcessDefinitionId(), processVariables); - - // 4.2 组装候选用户信息 - List candidateUsers = convertList(candidateUserIds, userId -> { - AdminUserRespDTO user = userMap.get(userId); - if (user != null) { - return BpmProcessInstanceConvert.INSTANCE.buildUser(userId, userMap, deptMap); - } - return null; - }); - - // 4.3 构建节点信息 - return new ActivityNode() - .setNodeType(BpmSimpleModelNodeTypeEnum.APPROVE_NODE.getType()) - .setId(node.getId()) - .setName(node.getName()) - .setStatus(BpmTaskStatusEnum.RUNNING.getStatus()) - .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(node)) - .setCandidateUsers(candidateUsers); - }); + nextActivityNodes.forEach(node -> node.setCandidateUsers(convertList(node.getCandidateUserIds(), userId -> { + AdminUserRespDTO user = userMap.get(userId); + if (user != null) { + return BpmProcessInstanceConvert.INSTANCE.buildUser(userId, userMap, deptMap); + } + return null; + }))); + return nextActivityNodes; } @Override diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java index 9eb25323ee..2f293dd4fe 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java @@ -6,6 +6,7 @@ import cn.hutool.core.lang.Assert; import cn.hutool.core.util.*; import cn.hutool.extra.spring.SpringUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.number.NumberUtils; import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; @@ -599,8 +600,8 @@ public class BpmTaskServiceImpl implements BpmTaskService { */ private Map validateAndSetNextAssignees(String taskDefinitionKey, Map variables, BpmnModel bpmnModel, Map> nextAssignees, ProcessInstance processInstance) { - // Simple设计器第一个节点默认为发起人节点,不校验是否存在审批人 - if (taskDefinitionKey.equals(START_USER_NODE_ID)) { + // simple 设计器第一个节点默认为发起人节点,不校验是否存在审批人 + if (Objects.equals(taskDefinitionKey, START_USER_NODE_ID)) { return variables; } // 1. 获取下一个将要执行的节点集合 @@ -608,15 +609,13 @@ public class BpmTaskServiceImpl implements BpmTaskService { List nextFlowNodes = getNextFlowNodes(flowElement, bpmnModel, variables); // 2. 校验选择的下一个节点的审批人,是否合法 - Map> processVariables; for (FlowNode nextFlowNode : nextFlowNodes) { Integer candidateStrategy = parseCandidateStrategy(nextFlowNode); // 2.1 情况一:如果节点中的审批人策略为 发起人自选 if (ObjUtil.equals(candidateStrategy, BpmTaskCandidateStrategyEnum.START_USER_SELECT.getStrategy())) { - // 先从历史中获取审批人,在发起人会把所有的审批人保存到历史中,这里从历史中获取 - processVariables = FlowableUtils.getStartUserSelectAssignees(processInstance.getProcessVariables()); // 特殊:如果当前节点已经存在审批人,则不允许覆盖 - if (processVariables != null && CollUtil.isNotEmpty(processVariables.get(nextFlowNode.getId()))) { + Map> startUserSelectAssignees = FlowableUtils.getStartUserSelectAssignees(processInstance.getProcessVariables()); + if (startUserSelectAssignees != null && CollUtil.isNotEmpty(startUserSelectAssignees.get(nextFlowNode.getId()))) { continue; } // 如果节点存在,但未配置审批人 @@ -624,28 +623,31 @@ public class BpmTaskServiceImpl implements BpmTaskService { if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, nextFlowNode.getName()); } + // 设置 PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES - if (processVariables == null) { - processVariables = new HashMap<>(); + if (startUserSelectAssignees == null) { + startUserSelectAssignees = new HashMap<>(); } - processVariables.put(nextFlowNode.getId(), assignees); - variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, processVariables); + startUserSelectAssignees.put(nextFlowNode.getId(), assignees); + variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); + continue; } + // 2.2 情况二:如果节点中的审批人策略为 审批人,在审批时选择下一个节点的审批人,并且该节点的审批人为空 if (ObjUtil.equals(candidateStrategy, BpmTaskCandidateStrategyEnum.APPROVE_USER_SELECT.getStrategy())) { - // 获取审批人自选的历史变量 - processVariables = FlowableUtils.getApproveUserSelectAssignees(processInstance.getProcessVariables()); // 如果节点存在,但未配置审批人 + Map> approveUserSelectAssignees = FlowableUtils.getApproveUserSelectAssignees(processInstance.getProcessVariables()); List assignees = nextAssignees != null ? nextAssignees.get(nextFlowNode.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_APPROVE_USER_SELECT_ASSIGNEES_NOT_CONFIG, nextFlowNode.getName()); } - if (processVariables == null) { - processVariables = new HashMap<>(); - } + // 设置 PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES - processVariables.put(nextFlowNode.getId(), assignees); - variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES, processVariables); + if (approveUserSelectAssignees == null) { + approveUserSelectAssignees = new HashMap<>(); + } + approveUserSelectAssignees.put(nextFlowNode.getId(), assignees); + variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES, approveUserSelectAssignees); } } return variables; From 0c9dd349812b10090a965c00d4f2fe64466cdcbb Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sun, 23 Mar 2025 09:00:33 +0800 Subject: [PATCH 20/36] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E3=80=91IOT=EF=BC=9A=E5=A4=9A=E4=B8=AA=20ProductCateg?= =?UTF-8?q?oryMapper=20=E7=9A=84=E5=90=8D=E5=AD=97=E5=86=B2=E7=AA=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../IotProductCategoryServiceImpl.java | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductCategoryServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductCategoryServiceImpl.java index f03186866d..13a8d488e9 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductCategoryServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductCategoryServiceImpl.java @@ -29,7 +29,7 @@ import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.PRODUCT_CATEG public class IotProductCategoryServiceImpl implements IotProductCategoryService { @Resource - private IotProductCategoryMapper productCategoryMapper; + private IotProductCategoryMapper iotProductCategoryMapper; @Resource private IotProductService productService; @@ -40,7 +40,7 @@ public class IotProductCategoryServiceImpl implements IotProductCategoryService public Long createProductCategory(IotProductCategorySaveReqVO createReqVO) { // 插入 IotProductCategoryDO productCategory = BeanUtils.toBean(createReqVO, IotProductCategoryDO.class); - productCategoryMapper.insert(productCategory); + iotProductCategoryMapper.insert(productCategory); // 返回 return productCategory.getId(); } @@ -51,7 +51,7 @@ public class IotProductCategoryServiceImpl implements IotProductCategoryService validateProductCategoryExists(updateReqVO.getId()); // 更新 IotProductCategoryDO updateObj = BeanUtils.toBean(updateReqVO, IotProductCategoryDO.class); - productCategoryMapper.updateById(updateObj); + iotProductCategoryMapper.updateById(updateObj); } @Override @@ -59,18 +59,18 @@ public class IotProductCategoryServiceImpl implements IotProductCategoryService // 校验存在 validateProductCategoryExists(id); // 删除 - productCategoryMapper.deleteById(id); + iotProductCategoryMapper.deleteById(id); } private void validateProductCategoryExists(Long id) { - if (productCategoryMapper.selectById(id) == null) { + if (iotProductCategoryMapper.selectById(id) == null) { throw exception(PRODUCT_CATEGORY_NOT_EXISTS); } } @Override public IotProductCategoryDO getProductCategory(Long id) { - return productCategoryMapper.selectById(id); + return iotProductCategoryMapper.selectById(id); } @Override @@ -78,28 +78,28 @@ public class IotProductCategoryServiceImpl implements IotProductCategoryService if (CollUtil.isEmpty(ids)) { return CollUtil.newArrayList(); } - return productCategoryMapper.selectBatchIds(ids); + return iotProductCategoryMapper.selectBatchIds(ids); } @Override public PageResult getProductCategoryPage(IotProductCategoryPageReqVO pageReqVO) { - return productCategoryMapper.selectPage(pageReqVO); + return iotProductCategoryMapper.selectPage(pageReqVO); } @Override public List getProductCategoryListByStatus(Integer status) { - return productCategoryMapper.selectListByStatus(status); + return iotProductCategoryMapper.selectListByStatus(status); } @Override public Long getProductCategoryCount(LocalDateTime createTime) { - return productCategoryMapper.selectCountByCreateTime(createTime); + return iotProductCategoryMapper.selectCountByCreateTime(createTime); } @Override public Map getProductCategoryDeviceCountMap() { // 1. 获取所有数据 - List categoryList = productCategoryMapper.selectList(); + List categoryList = iotProductCategoryMapper.selectList(); List productList = productService.getProductList(); // TODO @super:不要 list 查询,返回内存,而是查询一个 Map Map deviceCountMapByProductId = deviceService.getDeviceCountMapByProductId(); From 813e7af8467c1f4b693593fdbe9c20b96a0228e6 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sun, 23 Mar 2025 10:27:01 +0800 Subject: [PATCH 21/36] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=E3=80=91AI=EF=BC=9A=E7=A1=85=E5=9F=BA=E6=B5=81?= =?UTF-8?q?=E5=8A=A8=E7=9A=84=E5=9B=BE=E7=89=87=E7=94=9F=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../creation/AiartCreationController.java | 36 ---------- .../admin/creation/AiartStyleController.java | 40 ------------ .../vo/style/AiartReplaceBackgroundReqVO.java | 17 ----- .../vo/style/AiartReplaceBackgroundResVO.java | 18 ----- .../vo/style/BackgroundTemplateReqVO.java | 16 ----- .../vo/style/BackgroundTemplateResVO.java | 28 -------- .../AiartStyleBackgroundTemplateDO.java | 65 ------------------- .../AiartStyleBackgroundTemplateMapper.java | 33 ---------- .../creation/AiartCreationService.java | 21 ------ .../creation/AiartCreationServiceImpl.java | 22 ------- .../service/creation/AiartStyleService.java | 23 ------- .../creation/AiartStyleServiceImpl.java | 27 -------- .../ai/service/image/AiImageServiceImpl.java | 4 +- .../yudao-spring-boot-starter-ai/pom.xml | 7 -- .../ai/config/YudaoAiAutoConfiguration.java | 1 - .../yudao/framework/ai/core/util/AiUtils.java | 4 +- .../ai/image/SiliconFlowImageModelTests.java | 35 ++++++++++ 17 files changed, 39 insertions(+), 358 deletions(-) delete mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/creation/AiartCreationController.java delete mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/creation/AiartStyleController.java delete mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/creation/vo/style/AiartReplaceBackgroundReqVO.java delete mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/creation/vo/style/AiartReplaceBackgroundResVO.java delete mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/creation/vo/style/BackgroundTemplateReqVO.java delete mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/creation/vo/style/BackgroundTemplateResVO.java delete mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/creation/AiartStyleBackgroundTemplateDO.java delete mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/creation/AiartStyleBackgroundTemplateMapper.java delete mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/creation/AiartCreationService.java delete mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/creation/AiartCreationServiceImpl.java delete mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/creation/AiartStyleService.java delete mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/creation/AiartStyleServiceImpl.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/image/SiliconFlowImageModelTests.java diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/creation/AiartCreationController.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/creation/AiartCreationController.java deleted file mode 100644 index 64605d7177..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/creation/AiartCreationController.java +++ /dev/null @@ -1,36 +0,0 @@ -package cn.iocoder.yudao.module.ai.controller.admin.creation; - -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.module.ai.controller.admin.creation.vo.style.AiartReplaceBackgroundReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.creation.vo.style.AiartReplaceBackgroundResVO; -import cn.iocoder.yudao.module.ai.service.creation.AiartCreationService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.annotation.Resource; -import jakarta.validation.Valid; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; -import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; - -@Tag(name = "管理后台 - 艺术创作") -@RestController -@RequestMapping("/ai/artcreation") -@Validated -public class AiartCreationController { - - @Resource - private AiartCreationService aiartCreationService; - - @Operation(summary = "商品图替换背景") - @PostMapping("/replacebackground") - public CommonResult replaceBackground(@Valid @RequestBody AiartReplaceBackgroundReqVO replaceBackgroundReqVO) { - return success(aiartCreationService.replaceBackground(getLoginUserId(), replaceBackgroundReqVO)); - } - - -} \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/creation/AiartStyleController.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/creation/AiartStyleController.java deleted file mode 100644 index 4b70525e4a..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/creation/AiartStyleController.java +++ /dev/null @@ -1,40 +0,0 @@ -package cn.iocoder.yudao.module.ai.controller.admin.creation; - -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.ai.controller.admin.creation.vo.style.BackgroundTemplateReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.creation.vo.style.BackgroundTemplateResVO; -import cn.iocoder.yudao.module.ai.dal.dataobject.creation.AiartStyleBackgroundTemplateDO; -import cn.iocoder.yudao.module.ai.service.creation.AiartStyleService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.annotation.Resource; -import jakarta.validation.Valid; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import java.util.List; - -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; - -@Tag(name = "管理后台 - 艺术创作模版") -@RestController -@RequestMapping("/ai/artstyle") -@Validated -public class AiartStyleController { - - @Resource - private AiartStyleService aiartStyleService; - - @PostMapping("/backgroundtemplate-list") - @Operation(summary = "获取商品背景模版列表") - @PreAuthorize("@ss.hasPermission('ai:artstyle-background:query')") - public CommonResult> queryBackgroundStyle(@Valid @RequestBody BackgroundTemplateReqVO backgroundTemplateReqVO) { - List res = aiartStyleService.queryBackgroundStyle(backgroundTemplateReqVO); - return success(BeanUtils.toBean(res,BackgroundTemplateResVO.class)); - } -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/creation/vo/style/AiartReplaceBackgroundReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/creation/vo/style/AiartReplaceBackgroundReqVO.java deleted file mode 100644 index 8f2e9f9284..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/creation/vo/style/AiartReplaceBackgroundReqVO.java +++ /dev/null @@ -1,17 +0,0 @@ -package cn.iocoder.yudao.module.ai.controller.admin.creation.vo.style; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.Size; -import lombok.Data; - -@Schema(description = "管理后台 - 商品替换背景请求 VO") -@Data -public class AiartReplaceBackgroundReqVO { - - @Schema(description = "提示词", requiredMode = Schema.RequiredMode.REQUIRED, example = "画一个长城") - @NotEmpty(message = "提示词不能为空") - @Size(max = 1200, message = "提示词最大 1200") - private String prompt; - -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/creation/vo/style/AiartReplaceBackgroundResVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/creation/vo/style/AiartReplaceBackgroundResVO.java deleted file mode 100644 index 43ec35c774..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/creation/vo/style/AiartReplaceBackgroundResVO.java +++ /dev/null @@ -1,18 +0,0 @@ -package cn.iocoder.yudao.module.ai.controller.admin.creation.vo.style; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -@Schema(description = "管理后台 - 商品替换背景结果 VO") -@Data -public class AiartReplaceBackgroundResVO { - - @Schema(description = "结果url", requiredMode = Schema.RequiredMode.REQUIRED, example = "根据参数不同不一样") - private String resultImage; - - @Schema(description = "唯一请求 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "由服务端生成") - private String requestId; - - @Schema(description = "MaskUrl", requiredMode = Schema.RequiredMode.REQUIRED, example = "如果 MaskUrl 未传,则返回使用内置商品分割算法得到的 Mask 结果") - private String maskImage; -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/creation/vo/style/BackgroundTemplateReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/creation/vo/style/BackgroundTemplateReqVO.java deleted file mode 100644 index 42ad20c174..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/creation/vo/style/BackgroundTemplateReqVO.java +++ /dev/null @@ -1,16 +0,0 @@ -package cn.iocoder.yudao.module.ai.controller.admin.creation.vo.style; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -@Schema(description = "管理后台 - AI API 密钥新增/修改 Request VO") -@Data -public class BackgroundTemplateReqVO { - - @Schema(description = "一级级次名称", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "通用") - private String fistLevelName; - - @Schema(description = "二级级次名称", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "色彩背景") - private String secondLevelName; - -} \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/creation/vo/style/BackgroundTemplateResVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/creation/vo/style/BackgroundTemplateResVO.java deleted file mode 100644 index 2fb0ae3ac0..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/creation/vo/style/BackgroundTemplateResVO.java +++ /dev/null @@ -1,28 +0,0 @@ -package cn.iocoder.yudao.module.ai.controller.admin.creation.vo.style; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -@Schema(description = "管理后台 - 商品替换背景模版 VO") -@Data -public class BackgroundTemplateResVO { - - @Schema(description = "一级分类名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "汽车、通用") - private String levelFirst; - - @Schema(description = "二级分类名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "色彩背景") - private String levelSecond; - - @Schema(description = "背景模版名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "彩虹渐变") - private String name; - - @Schema(description = "背景模版提示词", requiredMode = Schema.RequiredMode.REQUIRED, example = "背景为彩虹渐变墙面,亮暖色,清新简约风格,喷涂效果") - private String prompt; - - @Schema(description = "背景模版商品示例", requiredMode = Schema.RequiredMode.REQUIRED, example = "小熊玩偶") - private String demProduct; - - @Schema(description = "背景模版商品示例效果", requiredMode = Schema.RequiredMode.REQUIRED, example = "url") - private String demUrl; -} - diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/creation/AiartStyleBackgroundTemplateDO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/creation/AiartStyleBackgroundTemplateDO.java deleted file mode 100644 index 0cc538efca..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/creation/AiartStyleBackgroundTemplateDO.java +++ /dev/null @@ -1,65 +0,0 @@ -package cn.iocoder.yudao.module.ai.dal.dataobject.creation; - -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; -import com.baomidou.mybatisplus.annotation.KeySequence; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -/** - * AI art商品背景风格 DO - * - * @author 芋道源码 - */ -@TableName("ai_backgroundtemplate") -@KeySequence("ai_chat_conversation_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class AiartStyleBackgroundTemplateDO extends BaseDO { - /** - * 编号 - */ - @TableId - private Long id; - - /** - * 名称 - */ - private String levelFirst; - - /** - * 名称 - */ - private String levelSecond; - /** - * 模版名称 - */ - private String name; - /** - * 模版提示词 - */ - private String prompt; - /** - * 示例商品 - */ - private String demoProduct; - - /** - * 示例效果 - */ - private String demoUrl; - - /** - * 状态 - * - * 枚举 {@link CommonStatusEnum} - */ - private Integer status; - -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/creation/AiartStyleBackgroundTemplateMapper.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/creation/AiartStyleBackgroundTemplateMapper.java deleted file mode 100644 index a0ac51a7e6..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/creation/AiartStyleBackgroundTemplateMapper.java +++ /dev/null @@ -1,33 +0,0 @@ -package cn.iocoder.yudao.module.ai.dal.mysql.creation; - -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; -import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; -import cn.iocoder.yudao.module.ai.controller.admin.creation.vo.style.BackgroundTemplateReqVO; -import cn.iocoder.yudao.module.ai.dal.dataobject.creation.AiartStyleBackgroundTemplateDO; -import org.apache.ibatis.annotations.Mapper; - -import java.util.List; - -/** - * AI style分隔 Mapper - * - * @author 芋道源码 - */ -@Mapper -public interface AiartStyleBackgroundTemplateMapper extends BaseMapperX { - - /** - * 获取有效的商品后端模版 - * @param reqVO 请求对象 - * @return 结果 - */ - default List selectList(BackgroundTemplateReqVO reqVO) { - return selectList(new LambdaQueryWrapperX() - .eqIfPresent(AiartStyleBackgroundTemplateDO::getLevelFirst,reqVO.getFistLevelName()) - .eqIfPresent(AiartStyleBackgroundTemplateDO::getLevelSecond,reqVO.getSecondLevelName()) - .eqIfPresent(AiartStyleBackgroundTemplateDO::getStatus, CommonStatusEnum.ENABLE.getStatus()) - .orderByDesc(AiartStyleBackgroundTemplateDO::getId)); - } - -} \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/creation/AiartCreationService.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/creation/AiartCreationService.java deleted file mode 100644 index e2300a8082..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/creation/AiartCreationService.java +++ /dev/null @@ -1,21 +0,0 @@ -package cn.iocoder.yudao.module.ai.service.creation; - -import cn.iocoder.yudao.module.ai.controller.admin.creation.vo.style.AiartReplaceBackgroundReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.creation.vo.style.AiartReplaceBackgroundResVO; - -/** - * AI创作Service 接口 - * - * @author zzt - */ -public interface AiartCreationService { - - - /** - * 替换商品背景图片 - * @param loginUserId 当前登录用户 - * @param replaceBackgroundReqVO 请求对象 - * @return 结果 - */ - AiartReplaceBackgroundResVO replaceBackground(Long loginUserId, AiartReplaceBackgroundReqVO replaceBackgroundReqVO); -} \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/creation/AiartCreationServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/creation/AiartCreationServiceImpl.java deleted file mode 100644 index f4f14cab19..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/creation/AiartCreationServiceImpl.java +++ /dev/null @@ -1,22 +0,0 @@ -package cn.iocoder.yudao.module.ai.service.creation; - -import cn.iocoder.yudao.module.ai.controller.admin.creation.vo.style.AiartReplaceBackgroundReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.creation.vo.style.AiartReplaceBackgroundResVO; -import org.springframework.stereotype.Service; -import org.springframework.validation.annotation.Validated; - -/** - * AI创作Service 实现类 - * - * @author zzt - */ -@Service -@Validated -public class AiartCreationServiceImpl implements AiartCreationService { - - - @Override - public AiartReplaceBackgroundResVO replaceBackground(Long loginUserId, AiartReplaceBackgroundReqVO replaceBackgroundReqVO) { - return null; - } -} \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/creation/AiartStyleService.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/creation/AiartStyleService.java deleted file mode 100644 index 635577086a..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/creation/AiartStyleService.java +++ /dev/null @@ -1,23 +0,0 @@ -package cn.iocoder.yudao.module.ai.service.creation; - -import cn.iocoder.yudao.module.ai.controller.admin.creation.vo.style.BackgroundTemplateReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.creation.vo.style.BackgroundTemplateResVO; -import cn.iocoder.yudao.module.ai.dal.dataobject.creation.AiartStyleBackgroundTemplateDO; - -import java.util.List; - -/** - * AI风格Service 接口 - * - * @author zzt - */ -public interface AiartStyleService { - - - /** - * 查询商品背景模版列表 - * @param backgroundTemplateReqVO 商品背景模版请求对象 - * @return 结果 - */ - List queryBackgroundStyle(BackgroundTemplateReqVO backgroundTemplateReqVO); -} \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/creation/AiartStyleServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/creation/AiartStyleServiceImpl.java deleted file mode 100644 index 9b85b0788f..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/creation/AiartStyleServiceImpl.java +++ /dev/null @@ -1,27 +0,0 @@ -package cn.iocoder.yudao.module.ai.service.creation; - -import cn.iocoder.yudao.module.ai.controller.admin.creation.vo.style.BackgroundTemplateReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.creation.vo.style.BackgroundTemplateResVO; -import cn.iocoder.yudao.module.ai.dal.dataobject.creation.AiartStyleBackgroundTemplateDO; -import cn.iocoder.yudao.module.ai.dal.mysql.creation.AiartStyleBackgroundTemplateMapper; -import cn.iocoder.yudao.module.ai.dal.mysql.model.AiApiKeyMapper; -import jakarta.annotation.Resource; - -import java.util.List; - -/** - * AI风格Service 接口 - * - * @author zzt - */ -public class AiartStyleServiceImpl implements AiartStyleService{ - - @Resource - private AiartStyleBackgroundTemplateMapper backgroundTemplateMapper; - - - @Override - public List queryBackgroundStyle(BackgroundTemplateReqVO backgroundTemplateReqVO) { - return backgroundTemplateMapper.selectList(backgroundTemplateReqVO); - } -} \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageServiceImpl.java index f0ef418301..caa84e7c63 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageServiceImpl.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageServiceImpl.java @@ -147,8 +147,8 @@ public class AiImageServiceImpl implements AiImageService { .build(); } else if (ObjUtil.equal(model.getPlatform(), AiPlatformEnum.SILICON_FLOW.getPlatform())) { // https://docs.siliconflow.cn/cn/api-reference/images/images-generations - return SiliconflowImageOptions.builder().withModel(model.getModel()) - .withHeight(draw.getHeight()).withWidth(draw.getWidth()) + return SiliconflowImageOptions.builder().model(model.getModel()) + .withHeight(draw.getHeight()).withHeight(draw.getWidth()) .build(); } else if (ObjUtil.equal(model.getPlatform(), AiPlatformEnum.STABLE_DIFFUSION.getPlatform())) { // https://platform.stability.ai/docs/api-reference#tag/SDXL-and-SD1.6/operation/textToImage diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml b/yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml index a433d9f94f..f37f3709c6 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml @@ -117,13 +117,6 @@ - - - com.tencentcloudapi - tencentcloud-sdk-java-aiart - 3.1.1215 - - org.springframework.boot diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java index dc1846ef99..a9fca193ad 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java @@ -9,7 +9,6 @@ import cn.iocoder.yudao.framework.ai.core.model.doubao.DouBaoChatModel; import cn.iocoder.yudao.framework.ai.core.model.hunyuan.HunYuanChatModel; import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; import cn.iocoder.yudao.framework.ai.core.model.siliconflow.SiiconflowApiConstants; -import cn.iocoder.yudao.framework.ai.core.model.siliconflow.SiiconflowmageApi; import cn.iocoder.yudao.framework.ai.core.model.siliconflow.SiliconFlowChatModel; import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel; diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/util/AiUtils.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/util/AiUtils.java index 8ca8772a4d..becc54ee43 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/util/AiUtils.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/util/AiUtils.java @@ -50,8 +50,8 @@ public class AiUtils { case HUN_YUAN: // 复用 OpenAI 客户端 case XING_HUO: // 复用 OpenAI 客户端 case SILICON_FLOW: // 复用 OpenAI 客户端 - OpenAiChatOptions.Builder builder = OpenAiChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens); - return toolNames == null ? builder.build() : builder.toolNames(toolNames).build(); + return OpenAiChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens) + .toolNames(toolNames).build(); case AZURE_OPENAI: // TODO 芋艿:貌似没 model 字段???! return AzureOpenAiChatOptions.builder().deploymentName(model).temperature(temperature).maxTokens(maxTokens) diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/image/SiliconFlowImageModelTests.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/image/SiliconFlowImageModelTests.java new file mode 100644 index 0000000000..40ad3d3ede --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/image/SiliconFlowImageModelTests.java @@ -0,0 +1,35 @@ +package cn.iocoder.yudao.framework.ai.image; + +import cn.iocoder.yudao.framework.ai.core.model.siliconflow.SiiconflowmageApi; +import cn.iocoder.yudao.framework.ai.core.model.siliconflow.SiliconflowImageModel; +import cn.iocoder.yudao.framework.ai.core.model.siliconflow.SiliconflowImageOptions; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.springframework.ai.image.ImagePrompt; +import org.springframework.ai.image.ImageResponse; + +/** + * {@link SiliconflowImageModel} 集成测试 + */ +public class SiliconFlowImageModelTests { + + private final SiliconflowImageModel imageModel = new SiliconflowImageModel( + new SiiconflowmageApi("sk-epsakfenqnyzoxhmbucsxlhkdqlcbnimslqoivkshalvdozz") // 密钥 + ); + + @Test + @Disabled + public void testCall() { + // 准备参数 + SiliconflowImageOptions imageOptions = SiliconflowImageOptions.builder() + .model("Kwai-Kolors/Kolors") + .build(); + ImagePrompt prompt = new ImagePrompt("万里长城", imageOptions); + + // 方法调用 + ImageResponse response = imageModel.call(prompt); + // 打印结果 + System.out.println(response); + } + +} From 59c744520ad3d19e87aa830f2cc3cb761d30daef Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sun, 23 Mar 2025 10:41:51 +0800 Subject: [PATCH 22/36] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E3=80=91AI=EF=BC=9A=E7=A1=85=E5=9F=BA=E6=B5=81?= =?UTF-8?q?=E5=8A=A8=E7=9A=84=E5=9B=BE=E7=89=87=E7=94=9F=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ai/service/image/AiImageServiceImpl.java | 6 +- .../ai/config/YudaoAiAutoConfiguration.java | 6 +- .../ai/core/factory/AiModelFactoryImpl.java | 22 +- .../model/siliconflow/SiiconflowmageApi.java | 207 ------------------ ...ants.java => SiliconFlowApiConstants.java} | 8 +- .../siliconflow/SiliconFlowImageApi.java | 115 ++++++++++ ...eModel.java => SiliconFlowImageModel.java} | 80 ++----- ...ions.java => SiliconFlowImageOptions.java} | 79 +------ .../ai/chat/SiliconFlowChatModelTests.java | 6 +- .../ai/image/SiliconFlowImageModelTests.java | 14 +- 10 files changed, 179 insertions(+), 364 deletions(-) delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiiconflowmageApi.java rename yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/{SiiconflowApiConstants.java => SiliconFlowApiConstants.java} (87%) create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiliconFlowImageApi.java rename yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/{SiliconflowImageModel.java => SiliconFlowImageModel.java} (60%) rename yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/{SiliconflowImageOptions.java => SiliconFlowImageOptions.java} (52%) diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageServiceImpl.java index caa84e7c63..c6c9fa43c1 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageServiceImpl.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageServiceImpl.java @@ -11,7 +11,7 @@ import cn.hutool.extra.spring.SpringUtil; import cn.hutool.http.HttpUtil; import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; -import cn.iocoder.yudao.framework.ai.core.model.siliconflow.SiliconflowImageOptions; +import cn.iocoder.yudao.framework.ai.core.model.siliconflow.SiliconFlowImageOptions; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageDrawReqVO; @@ -147,8 +147,8 @@ public class AiImageServiceImpl implements AiImageService { .build(); } else if (ObjUtil.equal(model.getPlatform(), AiPlatformEnum.SILICON_FLOW.getPlatform())) { // https://docs.siliconflow.cn/cn/api-reference/images/images-generations - return SiliconflowImageOptions.builder().model(model.getModel()) - .withHeight(draw.getHeight()).withHeight(draw.getWidth()) + return SiliconFlowImageOptions.builder().model(model.getModel()) + .height(draw.getHeight()).width(draw.getWidth()) .build(); } else if (ObjUtil.equal(model.getPlatform(), AiPlatformEnum.STABLE_DIFFUSION.getPlatform())) { // https://platform.stability.ai/docs/api-reference#tag/SDXL-and-SD1.6/operation/textToImage diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java index a9fca193ad..e014a4cd9f 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java @@ -8,7 +8,7 @@ import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatModel; import cn.iocoder.yudao.framework.ai.core.model.doubao.DouBaoChatModel; import cn.iocoder.yudao.framework.ai.core.model.hunyuan.HunYuanChatModel; import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; -import cn.iocoder.yudao.framework.ai.core.model.siliconflow.SiiconflowApiConstants; +import cn.iocoder.yudao.framework.ai.core.model.siliconflow.SiliconFlowApiConstants; import cn.iocoder.yudao.framework.ai.core.model.siliconflow.SiliconFlowChatModel; import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel; @@ -114,11 +114,11 @@ public class YudaoAiAutoConfiguration { public SiliconFlowChatModel buildSiliconFlowChatClient(YudaoAiProperties.SiliconFlowProperties properties) { if (StrUtil.isEmpty(properties.getModel())) { - properties.setModel(SiiconflowApiConstants.MODEL_DEFAULT); + properties.setModel(SiliconFlowApiConstants.MODEL_DEFAULT); } OpenAiChatModel openAiChatModel = OpenAiChatModel.builder() .openAiApi(OpenAiApi.builder() - .baseUrl(SiiconflowApiConstants.DEFAULT_BASE_URL) + .baseUrl(SiliconFlowApiConstants.DEFAULT_BASE_URL) .apiKey(properties.getApiKey()) .build()) .defaultOptions(OpenAiChatOptions.builder() diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java index 6c72b2b5f3..77ff76486a 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java @@ -15,10 +15,10 @@ import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatModel; import cn.iocoder.yudao.framework.ai.core.model.doubao.DouBaoChatModel; import cn.iocoder.yudao.framework.ai.core.model.hunyuan.HunYuanChatModel; import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; -import cn.iocoder.yudao.framework.ai.core.model.siliconflow.SiiconflowApiConstants; -import cn.iocoder.yudao.framework.ai.core.model.siliconflow.SiiconflowmageApi; +import cn.iocoder.yudao.framework.ai.core.model.siliconflow.SiliconFlowApiConstants; import cn.iocoder.yudao.framework.ai.core.model.siliconflow.SiliconFlowChatModel; -import cn.iocoder.yudao.framework.ai.core.model.siliconflow.SiliconflowImageModel; +import cn.iocoder.yudao.framework.ai.core.model.siliconflow.SiliconFlowImageApi; +import cn.iocoder.yudao.framework.ai.core.model.siliconflow.SiliconFlowImageModel; import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel; import cn.iocoder.yudao.framework.common.util.spring.SpringUtils; @@ -45,6 +45,7 @@ import org.springframework.ai.autoconfigure.moonshot.MoonshotAutoConfiguration; import org.springframework.ai.autoconfigure.ollama.OllamaAutoConfiguration; import org.springframework.ai.autoconfigure.openai.OpenAiAutoConfiguration; import org.springframework.ai.autoconfigure.qianfan.QianFanAutoConfiguration; +import org.springframework.ai.autoconfigure.stabilityai.StabilityAiImageAutoConfiguration; import org.springframework.ai.autoconfigure.vectorstore.milvus.MilvusServiceClientConnectionDetails; import org.springframework.ai.autoconfigure.vectorstore.milvus.MilvusServiceClientProperties; import org.springframework.ai.autoconfigure.vectorstore.milvus.MilvusVectorStoreAutoConfiguration; @@ -228,7 +229,7 @@ public class AiModelFactoryImpl implements AiModelFactory { case OPENAI: return buildOpenAiImageModel(apiKey, url); case SILICON_FLOW: - return buildSiiconflowImageModel(apiKey,url); + return buildSiliconFlowImageModel(apiKey,url); case STABLE_DIFFUSION: return buildStabilityAiImageModel(apiKey, url); default: @@ -474,12 +475,12 @@ public class AiModelFactoryImpl implements AiModelFactory { } /** - * Siiconflow + * 创建 SiliconFlowImageModel 对象 */ - private SiliconflowImageModel buildSiiconflowImageModel(String apiToken, String url) { - url = StrUtil.blankToDefault(url, SiiconflowApiConstants.DEFAULT_BASE_URL); - SiiconflowmageApi openAiApi = SiiconflowmageApi.builder().baseUrl(url).apiKey(apiToken).build(); - return new SiliconflowImageModel(openAiApi); + private SiliconFlowImageModel buildSiliconFlowImageModel(String apiToken, String url) { + url = StrUtil.blankToDefault(url, SiliconFlowApiConstants.DEFAULT_BASE_URL); + SiliconFlowImageApi openAiApi = new SiliconFlowImageApi(url, apiToken); + return new SiliconFlowImageModel(openAiApi); } /** @@ -490,6 +491,9 @@ public class AiModelFactoryImpl implements AiModelFactory { return OllamaChatModel.builder().ollamaApi(ollamaApi).toolCallingManager(getToolCallingManager()).build(); } + /** + * 可参考 {@link StabilityAiImageAutoConfiguration} 的 stabilityAiImageModel 方法 + */ private StabilityAiImageModel buildStabilityAiImageModel(String apiKey, String url) { url = StrUtil.blankToDefault(url, StabilityAiApi.DEFAULT_BASE_URL); StabilityAiApi stabilityAiApi = new StabilityAiApi(apiKey, StabilityAiApi.DEFAULT_IMAGE_MODEL, url); diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiiconflowmageApi.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiiconflowmageApi.java deleted file mode 100644 index 8d0cf925af..0000000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiiconflowmageApi.java +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Copyright 2023-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cn.iocoder.yudao.framework.ai.core.model.siliconflow; - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import org.springframework.ai.model.ApiKey; -import org.springframework.ai.model.NoopApiKey; -import org.springframework.ai.model.SimpleApiKey; -import org.springframework.ai.openai.api.OpenAiImageApi; -import org.springframework.ai.retry.RetryUtils; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; -import org.springframework.web.client.ResponseErrorHandler; -import org.springframework.web.client.RestClient; - -import java.util.Map; - -/** - * Siiconflow Image API. - * - * @see Images - * - * @author zzt - */ -public class SiiconflowmageApi { - - private final RestClient restClient; - - /** - * Create a new Siiconflow Image api with base URL set. - * @param aiToken OpenAI apiKey. - */ - public SiiconflowmageApi(String aiToken) { - this(SiiconflowApiConstants.DEFAULT_BASE_URL, aiToken, RestClient.builder()); - } - - /** - * Create a new Siiconflow Image API with the provided base URL. - * @param baseUrl the base URL for the OpenAI API. - * @param openAiToken Siiconflow apiKey. - */ - public SiiconflowmageApi(String baseUrl, String openAiToken, RestClient.Builder restClientBuilder) { - this(baseUrl, openAiToken, restClientBuilder, RetryUtils.DEFAULT_RESPONSE_ERROR_HANDLER); - } - - /** - * Create a new OpenAI Image API with the provided base URL. - * @param baseUrl the base URL for the OpenAI API. - * @param apiKey OpenAI apiKey. - * @param restClientBuilder the rest client builder to use. - */ - public SiiconflowmageApi(String baseUrl, String apiKey, RestClient.Builder restClientBuilder, - ResponseErrorHandler responseErrorHandler) { - this(baseUrl, apiKey, CollectionUtils.toMultiValueMap(Map.of()), restClientBuilder, responseErrorHandler); - } - - /** - * Create a new OpenAI Image API with the provided base URL. - * @param baseUrl the base URL for the OpenAI API. - * @param apiKey OpenAI apiKey. - * @param headers the http headers to use. - * @param restClientBuilder the rest client builder to use. - * @param responseErrorHandler the response error handler to use. - */ - public SiiconflowmageApi(String baseUrl, String apiKey, MultiValueMap headers, - RestClient.Builder restClientBuilder, ResponseErrorHandler responseErrorHandler) { - - this(baseUrl, new SimpleApiKey(apiKey), headers, restClientBuilder, responseErrorHandler); - } - - /** - * Create a new OpenAI Image API with the provided base URL. - * @param baseUrl the base URL for the OpenAI API. - * @param apiKey OpenAI apiKey. - * @param headers the http headers to use. - * @param restClientBuilder the rest client builder to use. - * @param responseErrorHandler the response error handler to use. - */ - public SiiconflowmageApi(String baseUrl, ApiKey apiKey, MultiValueMap headers, - RestClient.Builder restClientBuilder, ResponseErrorHandler responseErrorHandler) { - - // @formatter:off - this.restClient = restClientBuilder.baseUrl(baseUrl) - .defaultHeaders(h -> { - if(!(apiKey instanceof NoopApiKey)) { - h.setBearerAuth(apiKey.getValue()); - } - h.setContentType(MediaType.APPLICATION_JSON); - h.addAll(headers); - }) - .defaultStatusHandler(responseErrorHandler) - .build(); - // @formatter:on - } - - public ResponseEntity createImage(SiliconflowImageRequest siliconflowImageRequest) { - Assert.notNull(siliconflowImageRequest, "Image request cannot be null."); - Assert.hasLength(siliconflowImageRequest.prompt(), "Prompt cannot be empty."); - - return this.restClient.post() - .uri("v1/images/generations") - .body(siliconflowImageRequest) - .retrieve() - .toEntity(OpenAiImageApi.OpenAiImageResponse.class); - } - - - // @formatter:off - @JsonInclude(JsonInclude.Include.NON_NULL) - public record SiliconflowImageRequest ( - @JsonProperty("prompt") String prompt, - @JsonProperty("model") String model, - @JsonProperty("batch_size") Integer batchSize, - @JsonProperty("negative_prompt") String negativePrompt, - @JsonProperty("seed") Integer seed, - @JsonProperty("num_inference_steps") Integer numInferenceSteps, - @JsonProperty("guidance_scale") Float guidanceScale, - @JsonProperty("image") String image) { - - public SiliconflowImageRequest(String prompt, String model) { - this(prompt, model, null, null, null, null, null, null); - } - } - - public static Builder builder() { - return new Builder(); - } - - /** - * Builder to construct {@link SiiconflowmageApi} instance. - */ - public static class Builder { - - private String baseUrl = SiiconflowApiConstants.DEFAULT_BASE_URL; - - private ApiKey apiKey; - - private MultiValueMap headers = new LinkedMultiValueMap<>(); - - private RestClient.Builder restClientBuilder = RestClient.builder(); - - private ResponseErrorHandler responseErrorHandler = RetryUtils.DEFAULT_RESPONSE_ERROR_HANDLER; - - public Builder baseUrl(String baseUrl) { - Assert.hasText(baseUrl, "baseUrl cannot be null or empty"); - this.baseUrl = baseUrl; - return this; - } - - public Builder apiKey(ApiKey apiKey) { - Assert.notNull(apiKey, "apiKey cannot be null"); - this.apiKey = apiKey; - return this; - } - - public Builder apiKey(String simpleApiKey) { - Assert.notNull(simpleApiKey, "simpleApiKey cannot be null"); - this.apiKey = new SimpleApiKey(simpleApiKey); - return this; - } - - public Builder headers(MultiValueMap headers) { - Assert.notNull(headers, "headers cannot be null"); - this.headers = headers; - return this; - } - - public Builder restClientBuilder(RestClient.Builder restClientBuilder) { - Assert.notNull(restClientBuilder, "restClientBuilder cannot be null"); - this.restClientBuilder = restClientBuilder; - return this; - } - - public Builder responseErrorHandler(ResponseErrorHandler responseErrorHandler) { - Assert.notNull(responseErrorHandler, "responseErrorHandler cannot be null"); - this.responseErrorHandler = responseErrorHandler; - return this; - } - - public SiiconflowmageApi build() { - Assert.notNull(this.apiKey, "apiKey must be set"); - return new SiiconflowmageApi(this.baseUrl, this.apiKey, this.headers, this.restClientBuilder, - this.responseErrorHandler); - } - - } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiiconflowApiConstants.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiliconFlowApiConstants.java similarity index 87% rename from yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiiconflowApiConstants.java rename to yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiliconFlowApiConstants.java index 589d05855d..25bf4a1755 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiiconflowApiConstants.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiliconFlowApiConstants.java @@ -17,11 +17,11 @@ package cn.iocoder.yudao.framework.ai.core.model.siliconflow; /** - * Common value constants for Siiconflow api. + * SiliconFlow API 枚举类 * * @author zzt */ -public final class SiiconflowApiConstants { +public final class SiliconFlowApiConstants { public static final String DEFAULT_BASE_URL = "https://api.siliconflow.cn"; @@ -29,8 +29,4 @@ public final class SiiconflowApiConstants { public static final String PROVIDER_NAME = "Siiconflow"; - private SiiconflowApiConstants() { - - } - } diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiliconFlowImageApi.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiliconFlowImageApi.java new file mode 100644 index 0000000000..1408fbe2e4 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiliconFlowImageApi.java @@ -0,0 +1,115 @@ +/* + * Copyright 2023-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.iocoder.yudao.framework.ai.core.model.siliconflow; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.springframework.ai.model.ApiKey; +import org.springframework.ai.model.NoopApiKey; +import org.springframework.ai.model.SimpleApiKey; +import org.springframework.ai.openai.api.OpenAiImageApi; +import org.springframework.ai.retry.RetryUtils; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.ResponseErrorHandler; +import org.springframework.web.client.RestClient; + +import java.util.Map; + +/** + * 硅基流动 Image API + * + * @see Images + * + * @author zzt + */ +public class SiliconFlowImageApi { + + private final RestClient restClient; + + public SiliconFlowImageApi(String aiToken) { + this(SiliconFlowApiConstants.DEFAULT_BASE_URL, aiToken, RestClient.builder()); + } + + public SiliconFlowImageApi(String baseUrl, String openAiToken) { + this(baseUrl, openAiToken, RestClient.builder()); + } + + public SiliconFlowImageApi(String baseUrl, String openAiToken, RestClient.Builder restClientBuilder) { + this(baseUrl, openAiToken, restClientBuilder, RetryUtils.DEFAULT_RESPONSE_ERROR_HANDLER); + } + + public SiliconFlowImageApi(String baseUrl, String apiKey, RestClient.Builder restClientBuilder, + ResponseErrorHandler responseErrorHandler) { + this(baseUrl, apiKey, CollectionUtils.toMultiValueMap(Map.of()), restClientBuilder, responseErrorHandler); + } + + public SiliconFlowImageApi(String baseUrl, String apiKey, MultiValueMap headers, + RestClient.Builder restClientBuilder, ResponseErrorHandler responseErrorHandler) { + this(baseUrl, new SimpleApiKey(apiKey), headers, restClientBuilder, responseErrorHandler); + } + + public SiliconFlowImageApi(String baseUrl, ApiKey apiKey, MultiValueMap headers, + RestClient.Builder restClientBuilder, ResponseErrorHandler responseErrorHandler) { + + // @formatter:off + this.restClient = restClientBuilder.baseUrl(baseUrl) + .defaultHeaders(h -> { + if(!(apiKey instanceof NoopApiKey)) { + h.setBearerAuth(apiKey.getValue()); + } + h.setContentType(MediaType.APPLICATION_JSON); + h.addAll(headers); + }) + .defaultStatusHandler(responseErrorHandler) + .build(); + // @formatter:on + } + + public ResponseEntity createImage(SiliconflowImageRequest siliconflowImageRequest) { + Assert.notNull(siliconflowImageRequest, "Image request cannot be null."); + Assert.hasLength(siliconflowImageRequest.prompt(), "Prompt cannot be empty."); + + return this.restClient.post() + .uri("v1/images/generations") + .body(siliconflowImageRequest) + .retrieve() + .toEntity(OpenAiImageApi.OpenAiImageResponse.class); + } + + + // @formatter:off + @JsonInclude(JsonInclude.Include.NON_NULL) + public record SiliconflowImageRequest ( + @JsonProperty("prompt") String prompt, + @JsonProperty("model") String model, + @JsonProperty("batch_size") Integer batchSize, + @JsonProperty("negative_prompt") String negativePrompt, + @JsonProperty("seed") Integer seed, + @JsonProperty("num_inference_steps") Integer numInferenceSteps, + @JsonProperty("guidance_scale") Float guidanceScale, + @JsonProperty("image") String image) { + + public SiliconflowImageRequest(String prompt, String model) { + this(prompt, model, null, null, null, null, null, null); + } + } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiliconflowImageModel.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiliconFlowImageModel.java similarity index 60% rename from yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiliconflowImageModel.java rename to yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiliconFlowImageModel.java index 0e137da726..1e3965ed91 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiliconflowImageModel.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiliconFlowImageModel.java @@ -17,6 +17,7 @@ package cn.iocoder.yudao.framework.ai.core.model.siliconflow; import io.micrometer.observation.ObservationRegistry; +import lombok.Setter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.ai.image.*; @@ -25,8 +26,8 @@ import org.springframework.ai.image.observation.ImageModelObservationContext; import org.springframework.ai.image.observation.ImageModelObservationConvention; import org.springframework.ai.image.observation.ImageModelObservationDocumentation; import org.springframework.ai.model.ModelOptionsUtils; +import org.springframework.ai.openai.OpenAiImageModel; import org.springframework.ai.openai.api.OpenAiImageApi; -import org.springframework.ai.openai.api.common.OpenAiApiConstants; import org.springframework.ai.openai.metadata.OpenAiImageGenerationMetadata; import org.springframework.ai.retry.RetryUtils; import org.springframework.http.ResponseEntity; @@ -36,77 +37,44 @@ import org.springframework.util.Assert; import java.util.List; /** - * cv openapi图片模型方法 + * 硅基流动 {@link ImageModel} 实现类 + * + * 参考 {@link OpenAiImageModel} 实现 * * @author zzt */ -public class SiliconflowImageModel implements ImageModel { +public class SiliconFlowImageModel implements ImageModel { - private static final Logger logger = LoggerFactory.getLogger(SiliconflowImageModel.class); + private static final Logger logger = LoggerFactory.getLogger(SiliconFlowImageModel.class); private static final ImageModelObservationConvention DEFAULT_OBSERVATION_CONVENTION = new DefaultImageModelObservationConvention(); - /** - * The default options used for the image completion requests. - */ - private final SiliconflowImageOptions defaultOptions; + private final SiliconFlowImageOptions defaultOptions; - /** - * The retry template used to retry the OpenAI Image API calls. - */ private final RetryTemplate retryTemplate; - /** - * Low-level access to the OpenAI Image API. - */ - private final SiiconflowmageApi siiconflowmageApi; + private final SiliconFlowImageApi siliconFlowImageApi; - /** - * Observation registry used for instrumentation. - */ private final ObservationRegistry observationRegistry; - /** - * Conventions to use for generating observations. - */ + @Setter private ImageModelObservationConvention observationConvention = DEFAULT_OBSERVATION_CONVENTION; - /** - * Creates an instance of the OpenAiImageModel. - * @param siiconflowmageApi The OpenAiImageApi instance to be used for interacting with - * the OpenAI Image API. - * @throws IllegalArgumentException if openAiImageApi is null - */ - public SiliconflowImageModel(SiiconflowmageApi siiconflowmageApi) { - this(siiconflowmageApi, SiliconflowImageOptions.builder().build(), RetryUtils.DEFAULT_RETRY_TEMPLATE); + public SiliconFlowImageModel(SiliconFlowImageApi siliconFlowImageApi) { + this(siliconFlowImageApi, SiliconFlowImageOptions.builder().build(), RetryUtils.DEFAULT_RETRY_TEMPLATE); } - /** - * Initializes a new instance of the OpenAiImageModel. - * @param siiconflowmageApi The OpenAiImageApi instance to be used for interacting with - * the OpenAI Image API. - * @param options The OpenAiImageOptions to configure the image model. - * @param retryTemplate The retry template. - */ - public SiliconflowImageModel(SiiconflowmageApi siiconflowmageApi, SiliconflowImageOptions options, RetryTemplate retryTemplate) { - this(siiconflowmageApi, options, retryTemplate, ObservationRegistry.NOOP); + public SiliconFlowImageModel(SiliconFlowImageApi siliconFlowImageApi, SiliconFlowImageOptions options, RetryTemplate retryTemplate) { + this(siliconFlowImageApi, options, retryTemplate, ObservationRegistry.NOOP); } - /** - * Initializes a new instance of the OpenAiImageModel. - * @param siiconflowmageApi The OpenAiImageApi instance to be used for interacting with - * the OpenAI Image API. - * @param options The OpenAiImageOptions to configure the image model. - * @param retryTemplate The retry template. - * @param observationRegistry The ObservationRegistry used for instrumentation. - */ - public SiliconflowImageModel(SiiconflowmageApi siiconflowmageApi, SiliconflowImageOptions options, RetryTemplate retryTemplate, + public SiliconFlowImageModel(SiliconFlowImageApi siliconFlowImageApi, SiliconFlowImageOptions options, RetryTemplate retryTemplate, ObservationRegistry observationRegistry) { - Assert.notNull(siiconflowmageApi, "OpenAiImageApi must not be null"); + Assert.notNull(siliconFlowImageApi, "OpenAiImageApi must not be null"); Assert.notNull(options, "options must not be null"); Assert.notNull(retryTemplate, "retryTemplate must not be null"); Assert.notNull(observationRegistry, "observationRegistry must not be null"); - this.siiconflowmageApi = siiconflowmageApi; + this.siliconFlowImageApi = siliconFlowImageApi; this.defaultOptions = options; this.retryTemplate = retryTemplate; this.observationRegistry = observationRegistry; @@ -114,11 +82,11 @@ public class SiliconflowImageModel implements ImageModel { @Override public ImageResponse call(ImagePrompt imagePrompt) { - SiiconflowmageApi.SiliconflowImageRequest imageRequest = createRequest(imagePrompt); + SiliconFlowImageApi.SiliconflowImageRequest imageRequest = createRequest(imagePrompt); var observationContext = ImageModelObservationContext.builder() .imagePrompt(imagePrompt) - .provider(OpenAiApiConstants.PROVIDER_NAME) + .provider(SiliconFlowApiConstants.PROVIDER_NAME) .requestOptions(imagePrompt.getOptions()) .build(); @@ -127,7 +95,7 @@ public class SiliconflowImageModel implements ImageModel { this.observationRegistry) .observe(() -> { ResponseEntity imageResponseEntity = this.retryTemplate - .execute(ctx -> this.siiconflowmageApi.createImage(imageRequest)); + .execute(ctx -> this.siliconFlowImageApi.createImage(imageRequest)); ImageResponse imageResponse = convertResponse(imageResponseEntity, imageRequest); @@ -137,17 +105,17 @@ public class SiliconflowImageModel implements ImageModel { }); } - private SiiconflowmageApi.SiliconflowImageRequest createRequest(ImagePrompt imagePrompt) { + private SiliconFlowImageApi.SiliconflowImageRequest createRequest(ImagePrompt imagePrompt) { String instructions = imagePrompt.getInstructions().get(0).getText(); - SiiconflowmageApi.SiliconflowImageRequest imageRequest = new SiiconflowmageApi.SiliconflowImageRequest(instructions, + SiliconFlowImageApi.SiliconflowImageRequest imageRequest = new SiliconFlowImageApi.SiliconflowImageRequest(instructions, imagePrompt.getOptions().getModel()); - return ModelOptionsUtils.merge(imagePrompt.getOptions(), imageRequest, SiiconflowmageApi.SiliconflowImageRequest.class); + return ModelOptionsUtils.merge(imagePrompt.getOptions(), imageRequest, SiliconFlowImageApi.SiliconflowImageRequest.class); } private ImageResponse convertResponse(ResponseEntity imageResponseEntity, - SiiconflowmageApi.SiliconflowImageRequest siliconflowImageRequest) { + SiliconFlowImageApi.SiliconflowImageRequest siliconflowImageRequest) { OpenAiImageApi.OpenAiImageResponse imageApiResponse = imageResponseEntity.getBody(); if (imageApiResponse == null) { logger.warn("No image response returned for request: {}", siliconflowImageRequest); diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiliconflowImageOptions.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiliconFlowImageOptions.java similarity index 52% rename from yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiliconflowImageOptions.java rename to yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiliconFlowImageOptions.java index 8af0cbeea8..84f93de06f 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiliconflowImageOptions.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiliconFlowImageOptions.java @@ -1,17 +1,22 @@ package cn.iocoder.yudao.framework.ai.core.model.siliconflow; import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Data; +import lombok.NoArgsConstructor; import org.springframework.ai.image.ImageOptions; -import org.springframework.ai.openai.OpenAiImageOptions; /** - * 硅基流动画图能力 + * 硅基流动 {@link ImageOptions} * * @author zzt */ @Data -public class SiliconflowImageOptions implements ImageOptions { +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class SiliconFlowImageOptions implements ImageOptions { @JsonProperty("model") private String model; @@ -37,7 +42,6 @@ public class SiliconflowImageOptions implements ImageOptions { @JsonProperty("num_inference_steps") private Integer numInferenceSteps = 25; - /** * This value is used to control the degree of match between the generated image and the given prompt. The higher the value, the more the generated image will tend to strictly match the text prompt. The lower the value, the more creative and diverse the generated image will be, potentially containing more unexpected elements. * @@ -47,7 +51,7 @@ public class SiliconflowImageOptions implements ImageOptions { private Float guidanceScale = 0.75F; /** - * 如果想要每次都生成固定的图片,可以把seed设置为固定值。 + * 如果想要每次都生成固定的图片,可以把 seed 设置为固定值 * */ @JsonProperty("seed") @@ -59,13 +63,11 @@ public class SiliconflowImageOptions implements ImageOptions { @JsonProperty("image") private String image; - /** * 宽 */ private Integer width; - /** * 高 */ @@ -85,21 +87,6 @@ public class SiliconflowImageOptions implements ImageOptions { } } - /** - * 硅基流动 - * @return - */ - public static SiliconflowImageOptions.Builder builder() { - return new SiliconflowImageOptions.Builder(); - } - - @Override - public String toString() { - - return "SiliconflowImageOptions{" + "model='" + getModel() + '\'' + ", batch_size=" + batchSize + ", imageSize=" + imageSize + ", negativePrompt='" - + negativePrompt + '\'' + '}'; - } - @Override public Integer getN() { return null; @@ -115,52 +102,4 @@ public class SiliconflowImageOptions implements ImageOptions { return null; } - public static class Builder extends OpenAiImageOptions{ - - private final SiliconflowImageOptions options; - - private Builder() { - this.options = new SiliconflowImageOptions(); - } - - public SiliconflowImageOptions.Builder model(String model) { - this.options.setModel(model); - return this; - } - - public SiliconflowImageOptions.Builder withBatchSize(Integer batchSize) { - options.setBatchSize(batchSize); - return this; - } - - public SiliconflowImageOptions.Builder withModel(String model) { - options.setModel(model); - return this; - } - - public SiliconflowImageOptions.Builder withWidth(Integer width) { - options.setWidth(width); - return this; - } - - public SiliconflowImageOptions.Builder withHeight(Integer height) { - options.setHeight(height); - return this; - } - - public SiliconflowImageOptions.Builder withSeed(Integer seed) { - options.setSeed(seed); - return this; - } - - public SiliconflowImageOptions.Builder withNegativePrompt(String negativePrompt) { - options.setNegativePrompt(negativePrompt); - return this; - } - - public SiliconflowImageOptions build() { - return options; - } - - } } diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/SiliconFlowChatModelTests.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/SiliconFlowChatModelTests.java index 1344a7179f..b6139b4081 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/SiliconFlowChatModelTests.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/SiliconFlowChatModelTests.java @@ -1,6 +1,6 @@ package cn.iocoder.yudao.framework.ai.chat; -import cn.iocoder.yudao.framework.ai.core.model.siliconflow.SiiconflowApiConstants; +import cn.iocoder.yudao.framework.ai.core.model.siliconflow.SiliconFlowApiConstants; import cn.iocoder.yudao.framework.ai.core.model.siliconflow.SiliconFlowChatModel; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -26,11 +26,11 @@ public class SiliconFlowChatModelTests { private final OpenAiChatModel openAiChatModel = OpenAiChatModel.builder() .openAiApi(OpenAiApi.builder() - .baseUrl(SiiconflowApiConstants.DEFAULT_BASE_URL) + .baseUrl(SiliconFlowApiConstants.DEFAULT_BASE_URL) .apiKey("sk-epsakfenqnyzoxhmbucsxlhkdqlcbnimslqoivkshalvdozz") // apiKey .build()) .defaultOptions(OpenAiChatOptions.builder() - .model(SiiconflowApiConstants.MODEL_DEFAULT) // 模型 + .model(SiliconFlowApiConstants.MODEL_DEFAULT) // 模型 // .model("deepseek-ai/DeepSeek-R1") // 模型(deepseek-ai/DeepSeek-R1)可用赠费 // .model("Pro/deepseek-ai/DeepSeek-R1") // 模型(Pro/deepseek-ai/DeepSeek-R1)需要付费 .temperature(0.7) diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/image/SiliconFlowImageModelTests.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/image/SiliconFlowImageModelTests.java index 40ad3d3ede..323c4de513 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/image/SiliconFlowImageModelTests.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/image/SiliconFlowImageModelTests.java @@ -1,27 +1,27 @@ package cn.iocoder.yudao.framework.ai.image; -import cn.iocoder.yudao.framework.ai.core.model.siliconflow.SiiconflowmageApi; -import cn.iocoder.yudao.framework.ai.core.model.siliconflow.SiliconflowImageModel; -import cn.iocoder.yudao.framework.ai.core.model.siliconflow.SiliconflowImageOptions; +import cn.iocoder.yudao.framework.ai.core.model.siliconflow.SiliconFlowImageApi; +import cn.iocoder.yudao.framework.ai.core.model.siliconflow.SiliconFlowImageModel; +import cn.iocoder.yudao.framework.ai.core.model.siliconflow.SiliconFlowImageOptions; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.ai.image.ImagePrompt; import org.springframework.ai.image.ImageResponse; /** - * {@link SiliconflowImageModel} 集成测试 + * {@link SiliconFlowImageModel} 集成测试 */ public class SiliconFlowImageModelTests { - private final SiliconflowImageModel imageModel = new SiliconflowImageModel( - new SiiconflowmageApi("sk-epsakfenqnyzoxhmbucsxlhkdqlcbnimslqoivkshalvdozz") // 密钥 + private final SiliconFlowImageModel imageModel = new SiliconFlowImageModel( + new SiliconFlowImageApi("sk-epsakfenqnyzoxhmbucsxlhkdqlcbnimslqoivkshalvdozz") // 密钥 ); @Test @Disabled public void testCall() { // 准备参数 - SiliconflowImageOptions imageOptions = SiliconflowImageOptions.builder() + SiliconFlowImageOptions imageOptions = SiliconFlowImageOptions.builder() .model("Kwai-Kolors/Kolors") .build(); ImagePrompt prompt = new ImagePrompt("万里长城", imageOptions); From ef5e56d5605ca716785bb8c2427aa86ef4e7bd0e Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sun, 23 Mar 2025 11:22:46 +0800 Subject: [PATCH 23/36] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E3=80=91AI=EF=BC=9A=E7=A1=85=E5=9F=BA=E6=B5=81?= =?UTF-8?q?=E5=8A=A8=E7=9A=84=E5=9B=BE=E7=89=87=E7=94=9F=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ai/core/factory/AiModelFactoryImpl.java | 2 ++ .../siliconflow/SiliconFlowApiConstants.java | 2 ++ .../siliconflow/SiliconFlowImageModel.java | 33 ++++++++++++++++--- .../siliconflow/SiliconFlowImageOptions.java | 4 +-- 4 files changed, 35 insertions(+), 6 deletions(-) diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java index 77ff76486a..3c9f51cf63 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java @@ -207,6 +207,8 @@ public class AiModelFactoryImpl implements AiModelFactory { return SpringUtil.getBean(QianFanImageModel.class); case ZHI_PU: return SpringUtil.getBean(ZhiPuAiImageModel.class); + case SILICON_FLOW: + return SpringUtil.getBean(SiliconFlowImageModel.class); case OPENAI: return SpringUtil.getBean(OpenAiImageModel.class); case STABLE_DIFFUSION: diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiliconFlowApiConstants.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiliconFlowApiConstants.java index 25bf4a1755..4df1b3f3df 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiliconFlowApiConstants.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiliconFlowApiConstants.java @@ -27,6 +27,8 @@ public final class SiliconFlowApiConstants { public static final String MODEL_DEFAULT = "deepseek-ai/DeepSeek-R1-Distill-Qwen-7B"; + public static final String DEFAULT_IMAGE_MODEL = "Kwai-Kolors/Kolors"; + public static final String PROVIDER_NAME = "Siiconflow"; } diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiliconFlowImageModel.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiliconFlowImageModel.java index 1e3965ed91..e345ebaf8f 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiliconFlowImageModel.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiliconFlowImageModel.java @@ -31,6 +31,7 @@ import org.springframework.ai.openai.api.OpenAiImageApi; import org.springframework.ai.openai.metadata.OpenAiImageGenerationMetadata; import org.springframework.ai.retry.RetryUtils; import org.springframework.http.ResponseEntity; +import org.springframework.lang.Nullable; import org.springframework.retry.support.RetryTemplate; import org.springframework.util.Assert; @@ -82,7 +83,8 @@ public class SiliconFlowImageModel implements ImageModel { @Override public ImageResponse call(ImagePrompt imagePrompt) { - SiliconFlowImageApi.SiliconflowImageRequest imageRequest = createRequest(imagePrompt); + SiliconFlowImageOptions requestImageOptions = mergeOptions(imagePrompt.getOptions(), this.defaultOptions); + SiliconFlowImageApi.SiliconflowImageRequest imageRequest = createRequest(imagePrompt, requestImageOptions); var observationContext = ImageModelObservationContext.builder() .imagePrompt(imagePrompt) @@ -105,13 +107,14 @@ public class SiliconFlowImageModel implements ImageModel { }); } - private SiliconFlowImageApi.SiliconflowImageRequest createRequest(ImagePrompt imagePrompt) { + private SiliconFlowImageApi.SiliconflowImageRequest createRequest(ImagePrompt imagePrompt, + SiliconFlowImageOptions requestImageOptions) { String instructions = imagePrompt.getInstructions().get(0).getText(); SiliconFlowImageApi.SiliconflowImageRequest imageRequest = new SiliconFlowImageApi.SiliconflowImageRequest(instructions, - imagePrompt.getOptions().getModel()); + SiliconFlowApiConstants.DEFAULT_IMAGE_MODEL); - return ModelOptionsUtils.merge(imagePrompt.getOptions(), imageRequest, SiliconFlowImageApi.SiliconflowImageRequest.class); + return ModelOptionsUtils.merge(requestImageOptions, imageRequest, SiliconFlowImageApi.SiliconflowImageRequest.class); } private ImageResponse convertResponse(ResponseEntity imageResponseEntity, @@ -131,4 +134,26 @@ public class SiliconFlowImageModel implements ImageModel { ImageResponseMetadata openAiImageResponseMetadata = new ImageResponseMetadata(imageApiResponse.created()); return new ImageResponse(imageGenerationList, openAiImageResponseMetadata); } + + private SiliconFlowImageOptions mergeOptions(@Nullable ImageOptions runtimeOptions, SiliconFlowImageOptions defaultOptions) { + var runtimeOptionsForProvider = ModelOptionsUtils.copyToTarget(runtimeOptions, ImageOptions.class, + SiliconFlowImageOptions.class); + + if (runtimeOptionsForProvider == null) { + return defaultOptions; + } + + return SiliconFlowImageOptions.builder() + // Handle portable image options + .model(ModelOptionsUtils.mergeOption(runtimeOptionsForProvider.getModel(), defaultOptions.getModel())) + .batchSize(ModelOptionsUtils.mergeOption(runtimeOptionsForProvider.getN(), defaultOptions.getN())) + .width(ModelOptionsUtils.mergeOption(runtimeOptionsForProvider.getWidth(), defaultOptions.getWidth())) + .height(ModelOptionsUtils.mergeOption(runtimeOptionsForProvider.getHeight(), defaultOptions.getHeight())) + // Handle OpenAI specific image options + .negativePrompt(ModelOptionsUtils.mergeOption(runtimeOptionsForProvider.getNegativePrompt(), defaultOptions.getNegativePrompt())) + .numInferenceSteps(ModelOptionsUtils.mergeOption(runtimeOptionsForProvider.getNumInferenceSteps(), defaultOptions.getNumInferenceSteps())) + .guidanceScale(ModelOptionsUtils.mergeOption(runtimeOptionsForProvider.getGuidanceScale(), defaultOptions.getGuidanceScale())) + .seed(ModelOptionsUtils.mergeOption(runtimeOptionsForProvider.getSeed(), defaultOptions.getSeed())) + .build(); + } } diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiliconFlowImageOptions.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiliconFlowImageOptions.java index 84f93de06f..bdd82e9c89 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiliconFlowImageOptions.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiliconFlowImageOptions.java @@ -89,12 +89,12 @@ public class SiliconFlowImageOptions implements ImageOptions { @Override public Integer getN() { - return null; + return batchSize; } @Override public String getResponseFormat() { - return null; + return "url"; } @Override From cd4813f7dd78c9ce6bf256bbc351ff622905641f Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sun, 23 Mar 2025 12:18:37 +0800 Subject: [PATCH 24/36] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E3=80=91AI=EF=BC=9A=E7=99=BE=E5=B7=9D=E6=A8=A1?= =?UTF-8?q?=E5=9E=8B=E7=9A=84=E6=8E=A5=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ai/config/YudaoAiAutoConfiguration.java | 28 ++++++++ .../ai/config/YudaoAiProperties.java | 19 ++++++ .../ai/core/enums/AiPlatformEnum.java | 1 + .../ai/core/factory/AiModelFactoryImpl.java | 14 ++++ .../model/baichuan/BaiChuanChatModel.java | 45 ++++++++++++ .../siliconflow/SiliconFlowImageModel.java | 2 +- .../yudao/framework/ai/core/util/AiUtils.java | 1 + .../ai/chat/BaiChuanChatModelTests.java | 68 +++++++++++++++++++ 8 files changed, 177 insertions(+), 1 deletion(-) create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/baichuan/BaiChuanChatModel.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/BaiChuanChatModelTests.java diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java index e014a4cd9f..a454e40e8b 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java @@ -4,6 +4,7 @@ import cn.hutool.core.util.StrUtil; import cn.hutool.extra.spring.SpringUtil; import cn.iocoder.yudao.framework.ai.core.factory.AiModelFactory; import cn.iocoder.yudao.framework.ai.core.factory.AiModelFactoryImpl; +import cn.iocoder.yudao.framework.ai.core.model.baichuan.BaiChuanChatModel; import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatModel; import cn.iocoder.yudao.framework.ai.core.model.doubao.DouBaoChatModel; import cn.iocoder.yudao.framework.ai.core.model.hunyuan.HunYuanChatModel; @@ -193,6 +194,33 @@ public class YudaoAiAutoConfiguration { return new XingHuoChatModel(openAiChatModel); } + @Bean + @ConditionalOnProperty(value = "yudao.ai.baichuan.enable", havingValue = "true") + public BaiChuanChatModel baiChuanChatClient(YudaoAiProperties yudaoAiProperties) { + YudaoAiProperties.BaiChuanProperties properties = yudaoAiProperties.getBaichuan(); + return buildBaiChuanChatClient(properties); + } + + public BaiChuanChatModel buildBaiChuanChatClient(YudaoAiProperties.BaiChuanProperties properties) { + if (StrUtil.isEmpty(properties.getModel())) { + properties.setModel(BaiChuanChatModel.MODEL_DEFAULT); + } + OpenAiChatModel openAiChatModel = OpenAiChatModel.builder() + .openAiApi(OpenAiApi.builder() + .baseUrl(BaiChuanChatModel.BASE_URL) + .apiKey(properties.getApiKey()) + .build()) + .defaultOptions(OpenAiChatOptions.builder() + .model(properties.getModel()) + .temperature(properties.getTemperature()) + .maxTokens(properties.getMaxTokens()) + .topP(properties.getTopP()) + .build()) + .toolCallingManager(getToolCallingManager()) + .build(); + return new BaiChuanChatModel(openAiChatModel); + } + @Bean @ConditionalOnProperty(value = "yudao.ai.midjourney.enable", havingValue = "true") public MidjourneyApi midjourneyApi(YudaoAiProperties yudaoAiProperties) { diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiProperties.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiProperties.java index 296e0af8bc..86d1084ccc 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiProperties.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiProperties.java @@ -43,6 +43,12 @@ public class YudaoAiProperties { @SuppressWarnings("SpellCheckingInspection") private XingHuoProperties xinghuo; + /** + * 百川 + */ + @SuppressWarnings("SpellCheckingInspection") + private BaiChuanProperties baichuan; + /** * Midjourney 绘图 */ @@ -122,6 +128,19 @@ public class YudaoAiProperties { } + @Data + public static class BaiChuanProperties { + + private String enable; + private String apiKey; + + private String model; + private Double temperature; + private Integer maxTokens; + private Double topP; + + } + @Data public static class MidjourneyProperties { diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/enums/AiPlatformEnum.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/enums/AiPlatformEnum.java index 5a8a5c4539..be65f2986f 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/enums/AiPlatformEnum.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/enums/AiPlatformEnum.java @@ -27,6 +27,7 @@ public enum AiPlatformEnum implements ArrayValuable { SILICON_FLOW("SiliconFlow", "硅基流动"), // 硅基流动 MINI_MAX("MiniMax", "MiniMax"), // 稀宇科技 MOONSHOT("Moonshot", "月之暗灭"), // KIMI + BAI_CHUAN("BaiChuan", "百川智能"), // 百川智能 // ========== 国外平台 ========== diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java index 3c9f51cf63..6d664eb65f 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java @@ -11,6 +11,7 @@ import cn.hutool.extra.spring.SpringUtil; import cn.iocoder.yudao.framework.ai.config.YudaoAiAutoConfiguration; import cn.iocoder.yudao.framework.ai.config.YudaoAiProperties; import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; +import cn.iocoder.yudao.framework.ai.core.model.baichuan.BaiChuanChatModel; import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatModel; import cn.iocoder.yudao.framework.ai.core.model.doubao.DouBaoChatModel; import cn.iocoder.yudao.framework.ai.core.model.hunyuan.HunYuanChatModel; @@ -150,6 +151,8 @@ public class AiModelFactoryImpl implements AiModelFactory { return buildMoonshotChatModel(apiKey, url); case XING_HUO: return buildXingHuoChatModel(apiKey); + case BAI_CHUAN: + return buildBaiChuanChatModel(apiKey); case OPENAI: return buildOpenAiChatModel(apiKey, url); case AZURE_OPENAI: @@ -186,6 +189,8 @@ public class AiModelFactoryImpl implements AiModelFactory { return SpringUtil.getBean(MoonshotChatModel.class); case XING_HUO: return SpringUtil.getBean(XingHuoChatModel.class); + case BAI_CHUAN: + return SpringUtil.getBean(AzureOpenAiChatModel.class); case OPENAI: return SpringUtil.getBean(OpenAiChatModel.class); case AZURE_OPENAI: @@ -441,6 +446,15 @@ public class AiModelFactoryImpl implements AiModelFactory { return new YudaoAiAutoConfiguration().buildXingHuoChatClient(properties); } + /** + * 可参考 {@link YudaoAiAutoConfiguration#baiChuanChatClient(YudaoAiProperties)} + */ + private BaiChuanChatModel buildBaiChuanChatModel(String apiKey) { + YudaoAiProperties.BaiChuanProperties properties = new YudaoAiProperties.BaiChuanProperties() + .setApiKey(apiKey); + return new YudaoAiAutoConfiguration().buildBaiChuanChatClient(properties); + } + /** * 可参考 {@link OpenAiAutoConfiguration} 的 openAiChatModel 方法 */ diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/baichuan/BaiChuanChatModel.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/baichuan/BaiChuanChatModel.java new file mode 100644 index 0000000000..ac59b70266 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/baichuan/BaiChuanChatModel.java @@ -0,0 +1,45 @@ +package cn.iocoder.yudao.framework.ai.core.model.baichuan; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.chat.model.ChatResponse; +import org.springframework.ai.chat.prompt.ChatOptions; +import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.ai.openai.OpenAiChatModel; +import reactor.core.publisher.Flux; + +/** + * 百川 {@link ChatModel} 实现类 + * + * @author 芋道源码 + */ +@Slf4j +@RequiredArgsConstructor +public class BaiChuanChatModel implements ChatModel { + + public static final String BASE_URL = "https://api.baichuan-ai.com"; + + public static final String MODEL_DEFAULT = "Baichuan4-Turbo"; + + /** + * 兼容 OpenAI 接口,进行复用 + */ + private final OpenAiChatModel openAiChatModel; + + @Override + public ChatResponse call(Prompt prompt) { + return openAiChatModel.call(prompt); + } + + @Override + public Flux stream(Prompt prompt) { + return openAiChatModel.stream(prompt); + } + + @Override + public ChatOptions getDefaultOptions() { + return openAiChatModel.getDefaultOptions(); + } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiliconFlowImageModel.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiliconFlowImageModel.java index e345ebaf8f..235699ee66 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiliconFlowImageModel.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/siliconflow/SiliconFlowImageModel.java @@ -149,7 +149,7 @@ public class SiliconFlowImageModel implements ImageModel { .batchSize(ModelOptionsUtils.mergeOption(runtimeOptionsForProvider.getN(), defaultOptions.getN())) .width(ModelOptionsUtils.mergeOption(runtimeOptionsForProvider.getWidth(), defaultOptions.getWidth())) .height(ModelOptionsUtils.mergeOption(runtimeOptionsForProvider.getHeight(), defaultOptions.getHeight())) - // Handle OpenAI specific image options + // Handle SiliconFlow specific image options .negativePrompt(ModelOptionsUtils.mergeOption(runtimeOptionsForProvider.getNegativePrompt(), defaultOptions.getNegativePrompt())) .numInferenceSteps(ModelOptionsUtils.mergeOption(runtimeOptionsForProvider.getNumInferenceSteps(), defaultOptions.getNumInferenceSteps())) .guidanceScale(ModelOptionsUtils.mergeOption(runtimeOptionsForProvider.getGuidanceScale(), defaultOptions.getGuidanceScale())) diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/util/AiUtils.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/util/AiUtils.java index becc54ee43..3b858b4bc5 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/util/AiUtils.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/util/AiUtils.java @@ -50,6 +50,7 @@ public class AiUtils { case HUN_YUAN: // 复用 OpenAI 客户端 case XING_HUO: // 复用 OpenAI 客户端 case SILICON_FLOW: // 复用 OpenAI 客户端 + case BAI_CHUAN: // 复用 OpenAI 客户端 return OpenAiChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens) .toolNames(toolNames).build(); case AZURE_OPENAI: diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/BaiChuanChatModelTests.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/BaiChuanChatModelTests.java new file mode 100644 index 0000000000..9ae36dbb87 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/BaiChuanChatModelTests.java @@ -0,0 +1,68 @@ +package cn.iocoder.yudao.framework.ai.chat; + +import cn.iocoder.yudao.framework.ai.core.model.baichuan.BaiChuanChatModel; +import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatModel; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.springframework.ai.chat.messages.Message; +import org.springframework.ai.chat.messages.SystemMessage; +import org.springframework.ai.chat.messages.UserMessage; +import org.springframework.ai.chat.model.ChatResponse; +import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.ai.openai.OpenAiChatModel; +import org.springframework.ai.openai.OpenAiChatOptions; +import org.springframework.ai.openai.api.OpenAiApi; +import reactor.core.publisher.Flux; + +import java.util.ArrayList; +import java.util.List; + +/** + * {@link BaiChuanChatModel} 集成测试 + * + * @author 芋道源码 + */ +public class BaiChuanChatModelTests { + + private final OpenAiChatModel openAiChatModel = OpenAiChatModel.builder() + .openAiApi(OpenAiApi.builder() + .baseUrl(BaiChuanChatModel.BASE_URL) + .apiKey("sk-61b6766a94c70786ed02673f5e16af3c") // apiKey + .build()) + .defaultOptions(OpenAiChatOptions.builder() + .model("Baichuan4-Turbo") // 模型(https://platform.baichuan-ai.com/docs/api) + .temperature(0.7) + .build()) + .build(); + + private final DeepSeekChatModel chatModel = new DeepSeekChatModel(openAiChatModel); + + @Test + @Disabled + public void testCall() { + // 准备参数 + List messages = new ArrayList<>(); + messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); + messages.add(new UserMessage("1 + 1 = ?")); + + // 调用 + ChatResponse response = chatModel.call(new Prompt(messages)); + // 打印结果 + System.out.println(response); + } + + @Test + @Disabled + public void testStream() { + // 准备参数 + List messages = new ArrayList<>(); + messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); + messages.add(new UserMessage("1 + 1 = ?")); + + // 调用 + Flux flux = chatModel.stream(new Prompt(messages)); + // 打印结果 + flux.doOnNext(System.out::println).then().block(); + } + +} From 138239324c6b30bf36788f9112cf78c37e4fb00e Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sun, 23 Mar 2025 12:48:13 +0800 Subject: [PATCH 25/36] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E3=80=91AI=EF=BC=9A=E7=99=BE=E5=B7=9D=E6=A8=A1?= =?UTF-8?q?=E5=9E=8B=E7=9A=84=E6=8E=A5=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yudao-server/src/main/resources/application.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/yudao-server/src/main/resources/application.yaml b/yudao-server/src/main/resources/application.yaml index 13683df543..57b3b87d0a 100644 --- a/yudao-server/src/main/resources/application.yaml +++ b/yudao-server/src/main/resources/application.yaml @@ -212,6 +212,10 @@ yudao: appKey: 75b161ed2aef4719b275d6e7f2a4d4cd secretKey: YWYxYWI2MTA4ODI2NGZlYTQyNjAzZTcz model: generalv3.5 + baichuan: # 百川智能 + enable: true + api-key: sk-abc + model: Baichuan4-Turbo midjourney: enable: true # base-url: https://api.holdai.top/mj-relax/mj From b634a1e97b978734b885de7b01c9fc8ec968f5e0 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sun, 23 Mar 2025 16:54:48 +0800 Subject: [PATCH 26/36] =?UTF-8?q?=E3=80=90=E7=BC=BA=E9=99=B7=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E3=80=91CRM=EF=BC=9A=E4=BF=AE=E5=A4=8D=E8=B7=9F?= =?UTF-8?q?=E8=BF=9B=E6=97=A5=E5=BF=97=E7=9A=84=E6=93=8D=E4=BD=9C=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E8=AE=B0=E5=BD=95=E5=A4=B1=E8=B4=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yudao/module/crm/service/contact/CrmContactServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactServiceImpl.java index 174db9b3a2..9348c5e463 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactServiceImpl.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactServiceImpl.java @@ -223,7 +223,7 @@ public class CrmContactServiceImpl implements CrmContactService { } @Override - @LogRecord(type = CRM_CONTACT_TYPE, subType = CRM_CONTACT_FOLLOW_UP_SUB_TYPE, bizNo = "{{#id}", + @LogRecord(type = CRM_CONTACT_TYPE, subType = CRM_CONTACT_FOLLOW_UP_SUB_TYPE, bizNo = "{{#id}}", success = CRM_CONTACT_FOLLOW_UP_SUCCESS) @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTACT, bizId = "#id", level = CrmPermissionLevelEnum.WRITE) public void updateContactFollowUp(Long id, LocalDateTime contactNextTime, String contactLastContent) { From 4fbe9fa480b25fde992687399206af5da8ffecc8 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sun, 23 Mar 2025 17:28:08 +0800 Subject: [PATCH 27/36] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E3=80=91BPM=EF=BC=9A=E6=B5=81=E7=A8=8B=E6=A8=A1?= =?UTF-8?q?=E5=9E=8B->=E5=9F=BA=E6=9C=AC=E4=BF=A1=E6=81=AF->=E8=B0=81?= =?UTF-8?q?=E5=8F=AF=E4=BB=A5=E5=8F=91=E8=B5=B7=EF=BC=8C=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E6=8C=87=E5=AE=9A=E5=A4=9A=E4=B8=AA=E9=83=A8=E9=97=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/base/dept/DeptSimpleBaseVO.java | 4 +++- .../admin/definition/BpmModelController.java | 3 +-- .../definition/vo/model/BpmModelMetaInfoVO.java | 2 +- .../bpm/convert/definition/BpmModelConvert.java | 10 ++++------ .../definition/BpmProcessDefinitionInfoDO.java | 2 +- .../BpmProcessDefinitionServiceImpl.java | 14 +++++++------- 6 files changed, 17 insertions(+), 18 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/base/dept/DeptSimpleBaseVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/base/dept/DeptSimpleBaseVO.java index f6a9c126b3..ba8049a4b6 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/base/dept/DeptSimpleBaseVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/base/dept/DeptSimpleBaseVO.java @@ -6,8 +6,10 @@ import lombok.Data; @Schema(description = "部门精简信息 VO") @Data public class DeptSimpleBaseVO { + @Schema(description = "部门编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") private Long id; - @Schema(description = "部门名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿") + @Schema(description = "部门名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "技术部") private String name; + } \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java index 0b3f9c3333..aa4c484fee 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java @@ -83,13 +83,12 @@ public class BpmModelController { List processDefinitions = processDefinitionService.getProcessDefinitionListByDeploymentIds( deploymentMap.keySet()); Map processDefinitionMap = convertMap(processDefinitions, ProcessDefinition::getDeploymentId); - // 获得 User Map + // 获得 User Map、Dept Map Set userIds = convertSetByFlatMap(list, model -> { BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model); return metaInfo != null ? metaInfo.getStartUserIds().stream() : Stream.empty(); }); Map userMap = adminUserApi.getUserMap(userIds); - // 获得 Dept Map Set deptIds = convertSetByFlatMap(list, model -> { BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model); return metaInfo != null && metaInfo.getStartDeptIds() != null ? metaInfo.getStartDeptIds().stream() : Stream.empty(); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java index fbad5bdc6b..d2316f58eb 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java @@ -59,7 +59,7 @@ public class BpmModelMetaInfoVO { @Schema(description = "可发起用户编号数组", example = "[1,2,3]") private List startUserIds; - @Schema(description = "可发起部门编号数组") + @Schema(description = "可发起部门编号数组", example = "[2,4,6]") private List startDeptIds; @Schema(description = "可管理用户编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "[2,4,6]") diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmModelConvert.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmModelConvert.java index 5f3faa33e6..2101b22b28 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmModelConvert.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmModelConvert.java @@ -76,8 +76,7 @@ public interface BpmModelConvert { default BpmModelRespVO buildModel0(Model model, BpmModelMetaInfoVO metaInfo, BpmFormDO form, BpmCategoryDO category, Deployment deployment, ProcessDefinition processDefinition, - List startUsers, - List startDepts) { + List startUsers, List startDepts) { BpmModelRespVO modelRespVO = new BpmModelRespVO().setId(model.getId()).setName(model.getName()) .setKey(model.getKey()).setCategory(model.getCategory()) .setCreateTime(DateUtils.of(model.getCreateTime())); @@ -99,10 +98,9 @@ public interface BpmModelConvert { modelRespVO.getProcessDefinition().setDeploymentTime(DateUtils.of(deployment.getDeploymentTime())); } } - // User - modelRespVO.setStartUsers(BeanUtils.toBean(startUsers, UserSimpleBaseVO.class)); - // Dept - modelRespVO.setStartDepts(BeanUtils.toBean(startDepts, DeptSimpleBaseVO.class)); + // User、Dept + modelRespVO.setStartUsers(BeanUtils.toBean(startUsers, UserSimpleBaseVO.class)) + .setStartDepts(BeanUtils.toBean(startDepts, DeptSimpleBaseVO.class)); return modelRespVO; } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java index 9f006e0400..c2799ef67f 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java @@ -154,7 +154,7 @@ public class BpmProcessDefinitionInfoDO extends BaseDO { /** * 可发起部门编号数组 * - * 关联 {@link AdminUserRespDTO#getId()} 字段的数组 + * 关联 {@link AdminUserRespDTO#getDeptId()} 字段的数组 */ @TableField(typeHandler = LongListTypeHandler.class) private List startDeptIds; diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java index 86bb93062d..836b5181c7 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java @@ -51,6 +51,7 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ @Resource private BpmProcessDefinitionInfoMapper processDefinitionMapper; + @Resource private AdminUserApi adminUserApi; @@ -93,18 +94,17 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ return false; } - // 获取用户所在部门 - AdminUserRespDTO user = adminUserApi.getUser(userId); - Long userDeptId = user != null ? user.getDeptId() : null; - // 校验用户是否在允许发起的用户列表中 - if (!CollUtil.isEmpty(processDefinition.getStartUserIds())) { + if (CollUtil.isNotEmpty(processDefinition.getStartUserIds())) { return processDefinition.getStartUserIds().contains(userId); } // 校验用户是否在允许发起的部门列表中 - if (!CollUtil.isEmpty(processDefinition.getStartDeptIds()) && userDeptId != null) { - return processDefinition.getStartDeptIds().contains(userDeptId); + if (CollUtil.isNotEmpty(processDefinition.getStartDeptIds())) { + AdminUserRespDTO user = adminUserApi.getUser(userId); + return user != null + && user.getDeptId() != null + && processDefinition.getStartDeptIds().contains(user.getDeptId()); } // 都为空,则所有人都可以发起 From 587504c36ac3467df9ff9672045640e245a849f8 Mon Sep 17 00:00:00 2001 From: Lesan <1960681385@qq.com> Date: Mon, 24 Mar 2025 15:15:27 +0800 Subject: [PATCH 28/36] =?UTF-8?q?feat:=20AI=E5=B7=A5=E4=BD=9C=E6=B5=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module/ai/enums/ErrorCodeConstants.java | 4 + .../admin/workflow/AiWorkflowController.java | 85 ++++++++++ .../workflow/vo/AiWorkflowPageReqVO.java | 26 +++ .../admin/workflow/vo/AiWorkflowRespVO.java | 27 +++ .../workflow/vo/AiWorkflowSaveReqVO.java | 22 +++ .../workflow/vo/AiWorkflowTestReqVO.java | 20 +++ .../vo/AiWorkflowUpdateModelReqVO.java | 18 ++ .../dal/dataobject/workflow/AiWorkflowDO.java | 40 +++++ .../dal/mysql/workflow/AiWorkflowMapper.java | 28 ++++ .../service/workflow/AiWorkflowService.java | 69 ++++++++ .../workflow/AiWorkflowServiceImpl.java | 157 ++++++++++++++++++ .../yudao-spring-boot-starter-ai/pom.xml | 8 + 12 files changed, 504 insertions(+) create mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/AiWorkflowController.java create mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/vo/AiWorkflowPageReqVO.java create mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/vo/AiWorkflowRespVO.java create mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/vo/AiWorkflowSaveReqVO.java create mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/vo/AiWorkflowTestReqVO.java create mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/vo/AiWorkflowUpdateModelReqVO.java create mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/workflow/AiWorkflowDO.java create mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/workflow/AiWorkflowMapper.java create mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/workflow/AiWorkflowService.java create mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/workflow/AiWorkflowServiceImpl.java diff --git a/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/ErrorCodeConstants.java b/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/ErrorCodeConstants.java index 8b235f9ac5..fa072f3fcb 100644 --- a/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/ErrorCodeConstants.java +++ b/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/ErrorCodeConstants.java @@ -61,4 +61,8 @@ public interface ErrorCodeConstants { ErrorCode TOOL_NOT_EXISTS = new ErrorCode(1_040_010_000, "工具不存在"); ErrorCode TOOL_NAME_NOT_EXISTS = new ErrorCode(1_040_010_001, "工具({})找不到 Bean"); + // ========== AI 工作流 1-040-011-000 ========== + ErrorCode WORKFLOW_NOT_EXISTS = new ErrorCode(1_040_011_000, "工作流不存在"); + ErrorCode WORKFLOW_KEY_EXISTS = new ErrorCode(1_040_011_001, "工作流标识已存在"); + } diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/AiWorkflowController.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/AiWorkflowController.java new file mode 100644 index 0000000000..1270f7679c --- /dev/null +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/AiWorkflowController.java @@ -0,0 +1,85 @@ +package cn.iocoder.yudao.module.ai.controller.admin.workflow; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.ai.controller.admin.workflow.vo.*; +import cn.iocoder.yudao.module.ai.dal.dataobject.workflow.AiWorkflowDO; +import cn.iocoder.yudao.module.ai.service.workflow.AiWorkflowService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - AI 工作流") +@RestController +@RequestMapping("/ai/workflow") +@Slf4j +public class AiWorkflowController { + + @Resource + private AiWorkflowService workflowService; + + @PostMapping("/create") + @Operation(summary = "创建 AI 工作流") + @PreAuthorize("@ss.hasPermission('ai:workflow:create')") + public CommonResult createWorkflow(@Valid @RequestBody AiWorkflowSaveReqVO createReqVO) { + return success(workflowService.createWorkflow(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新 AI 工作流") + @PreAuthorize("@ss.hasPermission('ai:workflow:update')") + public CommonResult updateWorkflow(@Valid @RequestBody AiWorkflowSaveReqVO updateReqVO) { + workflowService.updateWorkflow(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除 AI 工作流") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('ai:workflow:delete')") + public CommonResult deleteWorkflow(@RequestParam("id") Long id) { + workflowService.deleteWorkflow(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得 AI 工作流") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('ai:workflow:query')") + public CommonResult getWorkflow(@RequestParam("id") Long id) { + AiWorkflowDO workflow = workflowService.getWorkflow(id); + return success(BeanUtils.toBean(workflow, AiWorkflowRespVO.class)); + } + + @GetMapping("/page") + @Operation(summary = "获得 AI 工作流分页") + @PreAuthorize("@ss.hasPermission('ai:workflow:query')") + public CommonResult> getWorkflowPage(@Valid AiWorkflowPageReqVO pageReqVO) { + PageResult pageResult = workflowService.getWorkflowPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, AiWorkflowRespVO.class)); + } + + @PutMapping("/updateWorkflowModel") + @Operation(summary = "更新 AI 工作流模型") + @PreAuthorize("@ss.hasPermission('ai:workflow:update')") + public CommonResult updateWorkflowModel(@Valid @RequestBody AiWorkflowUpdateModelReqVO updateReqVO) { + workflowService.updateWorkflowModel(updateReqVO); + return success(true); + } + + @PostMapping("/test") + @Operation(summary = "测试 AI 工作流") + @PreAuthorize("@ss.hasPermission('ai:workflow:test')") + public CommonResult testWorkflow(@Valid @RequestBody AiWorkflowTestReqVO testReqVO) { + return success(workflowService.testWorkflow(testReqVO)); + } + +} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/vo/AiWorkflowPageReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/vo/AiWorkflowPageReqVO.java new file mode 100644 index 0000000000..5418057817 --- /dev/null +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/vo/AiWorkflowPageReqVO.java @@ -0,0 +1,26 @@ +package cn.iocoder.yudao.module.ai.controller.admin.workflow.vo; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - AI 工作流分页 Request VO") +@Data +public class AiWorkflowPageReqVO extends PageParam { + + @Schema(description = "名称", example = "工作流") + private String name; + + @Schema(description = "标识", example = "FLOW") + private String definitionKey; + + @Schema(description = "创建时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/vo/AiWorkflowRespVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/vo/AiWorkflowRespVO.java new file mode 100644 index 0000000000..b1c66b91de --- /dev/null +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/vo/AiWorkflowRespVO.java @@ -0,0 +1,27 @@ +package cn.iocoder.yudao.module.ai.controller.admin.workflow.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - AI 工作流 Response VO") +@Data +public class AiWorkflowRespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Long id; + + @Schema(description = "工作流标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "FLOW") + private String definitionKey; + + @Schema(description = "工作流名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "工作流") + private String name; + + @Schema(description = "工作流模型 JSON", requiredMode = Schema.RequiredMode.REQUIRED, example = "{}") + private String model; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "时间戳格式") + private LocalDateTime createTime; + +} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/vo/AiWorkflowSaveReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/vo/AiWorkflowSaveReqVO.java new file mode 100644 index 0000000000..ec269cc034 --- /dev/null +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/vo/AiWorkflowSaveReqVO.java @@ -0,0 +1,22 @@ +package cn.iocoder.yudao.module.ai.controller.admin.workflow.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +@Schema(description = "管理后台 - AI 工作流新增/修改 Request VO") +@Data +public class AiWorkflowSaveReqVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Long id; + + @Schema(description = "工作流标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "FLOW") + @NotEmpty(message = "工作流标识不能为空") + private String definitionKey; + + @Schema(description = "工作流名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "工作流") + @NotEmpty(message = "工作流名称不能为空") + private String name; + +} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/vo/AiWorkflowTestReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/vo/AiWorkflowTestReqVO.java new file mode 100644 index 0000000000..077a3020a6 --- /dev/null +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/vo/AiWorkflowTestReqVO.java @@ -0,0 +1,20 @@ +package cn.iocoder.yudao.module.ai.controller.admin.workflow.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +import java.util.Map; + +@Schema(description = "管理后台 - AI 工作流测试 Request VO") +@Data +public class AiWorkflowTestReqVO { + + @Schema(description = "工作流模型", requiredMode = Schema.RequiredMode.REQUIRED, example = "{}") + @NotEmpty(message = "工作流模型不能为空") + private String model; + + @Schema(description = "参数", requiredMode = Schema.RequiredMode.REQUIRED, example = "{}") + private Map params; + +} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/vo/AiWorkflowUpdateModelReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/vo/AiWorkflowUpdateModelReqVO.java new file mode 100644 index 0000000000..00a279ba54 --- /dev/null +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/vo/AiWorkflowUpdateModelReqVO.java @@ -0,0 +1,18 @@ +package cn.iocoder.yudao.module.ai.controller.admin.workflow.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +@Schema(description = "管理后台 - AI 工作流修改流程模型 Request VO") +@Data +public class AiWorkflowUpdateModelReqVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Long id; + + @Schema(description = "工作流模型", requiredMode = Schema.RequiredMode.REQUIRED, example = "{}") + @NotEmpty(message = "工作流模型不能为空") + private String model; + +} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/workflow/AiWorkflowDO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/workflow/AiWorkflowDO.java new file mode 100644 index 0000000000..db6c090467 --- /dev/null +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/workflow/AiWorkflowDO.java @@ -0,0 +1,40 @@ +package cn.iocoder.yudao.module.ai.dal.dataobject.workflow; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + * AI 工作流 DO + * + * @author lesan + */ +@TableName(value = "ai_workflow", autoResultMap = true) +@KeySequence("ai_workflow") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +public class AiWorkflowDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + + /** + * 工作流标识 + */ + private String definitionKey; + + /** + * 工作流名称 + */ + private String name; + + /** + * 工作流模型 JSON 数据 + */ + private String model; + +} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/workflow/AiWorkflowMapper.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/workflow/AiWorkflowMapper.java new file mode 100644 index 0000000000..f6be0cec7b --- /dev/null +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/workflow/AiWorkflowMapper.java @@ -0,0 +1,28 @@ +package cn.iocoder.yudao.module.ai.dal.mysql.workflow; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.ai.controller.admin.workflow.vo.AiWorkflowPageReqVO; +import cn.iocoder.yudao.module.ai.dal.dataobject.workflow.AiWorkflowDO; +import org.apache.ibatis.annotations.Mapper; + +/** + * AI 工作流 Mapper + * + * @author lesan + */ +@Mapper +public interface AiWorkflowMapper extends BaseMapperX { + + default AiWorkflowDO selectByKey(String key) { + return selectOne(AiWorkflowDO::getDefinitionKey, key); + } + + default PageResult selectPage(AiWorkflowPageReqVO pageReqVO) { + return selectPage(pageReqVO, new LambdaQueryWrapperX() + .likeIfPresent(AiWorkflowDO::getName, pageReqVO.getName()) + .likeIfPresent(AiWorkflowDO::getDefinitionKey, pageReqVO.getDefinitionKey())); + } + +} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/workflow/AiWorkflowService.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/workflow/AiWorkflowService.java new file mode 100644 index 0000000000..31fd476540 --- /dev/null +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/workflow/AiWorkflowService.java @@ -0,0 +1,69 @@ +package cn.iocoder.yudao.module.ai.service.workflow; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.ai.controller.admin.workflow.vo.AiWorkflowPageReqVO; +import cn.iocoder.yudao.module.ai.controller.admin.workflow.vo.AiWorkflowSaveReqVO; +import cn.iocoder.yudao.module.ai.controller.admin.workflow.vo.AiWorkflowTestReqVO; +import cn.iocoder.yudao.module.ai.controller.admin.workflow.vo.AiWorkflowUpdateModelReqVO; +import cn.iocoder.yudao.module.ai.dal.dataobject.workflow.AiWorkflowDO; +import jakarta.validation.Valid; + +/** + * AI 工作流 Service 接口 + * + * @author lesan + */ +public interface AiWorkflowService { + + /** + * 创建 AI 工作流 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createWorkflow(@Valid AiWorkflowSaveReqVO createReqVO); + + /** + * 更新 AI 工作流 + * + * @param updateReqVO 更新信息 + */ + void updateWorkflow(@Valid AiWorkflowSaveReqVO updateReqVO); + + /** + * 删除 AI 工作流 + * + * @param id 编号 + */ + void deleteWorkflow(Long id); + + /** + * 获得 AI 工作流 + * + * @param id 编号 + * @return AI 工作流 + */ + AiWorkflowDO getWorkflow(Long id); + + /** + * 获得 AI 工作流分页 + * + * @param pageReqVO 分页查询 + * @return AI 工作流分页 + */ + PageResult getWorkflowPage(AiWorkflowPageReqVO pageReqVO); + + /** + * 修改工作流模型 JSON 数据 + * + * @param updateReqVO 更新数据 + */ + void updateWorkflowModel(AiWorkflowUpdateModelReqVO updateReqVO); + + /** + * 测试 AI 工作流 + * + * @param testReqVO 测试数据 + */ + Object testWorkflow(AiWorkflowTestReqVO testReqVO); +} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/workflow/AiWorkflowServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/workflow/AiWorkflowServiceImpl.java new file mode 100644 index 0000000000..53128b7c2a --- /dev/null +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/workflow/AiWorkflowServiceImpl.java @@ -0,0 +1,157 @@ +package cn.iocoder.yudao.module.ai.service.workflow; + +import cn.hutool.core.util.ObjUtil; +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.ai.controller.admin.workflow.vo.AiWorkflowPageReqVO; +import cn.iocoder.yudao.module.ai.controller.admin.workflow.vo.AiWorkflowSaveReqVO; +import cn.iocoder.yudao.module.ai.controller.admin.workflow.vo.AiWorkflowTestReqVO; +import cn.iocoder.yudao.module.ai.controller.admin.workflow.vo.AiWorkflowUpdateModelReqVO; +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiApiKeyDO; +import cn.iocoder.yudao.module.ai.dal.dataobject.workflow.AiWorkflowDO; +import cn.iocoder.yudao.module.ai.dal.mysql.workflow.AiWorkflowMapper; +import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import dev.tinyflow.core.Tinyflow; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.WORKFLOW_KEY_EXISTS; +import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.WORKFLOW_NOT_EXISTS; + +/** + * AI 工作流 Service 实现类 + * + * @author lesan + */ +@Service +@Slf4j +public class AiWorkflowServiceImpl implements AiWorkflowService { + + @Resource + private AiWorkflowMapper workflowMapper; + + @Resource + private AiApiKeyService apiKeyService; + + @Override + public Long createWorkflow(AiWorkflowSaveReqVO createReqVO) { + validateWorkflowForCreateOrUpdate(null, createReqVO.getDefinitionKey()); + AiWorkflowDO workflow = BeanUtils.toBean(createReqVO, AiWorkflowDO.class); + workflow.setModel(StrUtil.EMPTY); + workflowMapper.insert(workflow); + return workflow.getId(); + } + + @Override + public void updateWorkflow(AiWorkflowSaveReqVO updateReqVO) { + validateWorkflowForCreateOrUpdate(updateReqVO.getId(), updateReqVO.getDefinitionKey()); + AiWorkflowDO workflow = BeanUtils.toBean(updateReqVO, AiWorkflowDO.class); + workflowMapper.updateById(workflow); + } + + @Override + public void deleteWorkflow(Long id) { + validateWorkflowExists(id); + workflowMapper.deleteById(id); + } + + @Override + public AiWorkflowDO getWorkflow(Long id) { + return workflowMapper.selectById(id); + } + + @Override + public PageResult getWorkflowPage(AiWorkflowPageReqVO pageReqVO) { + return workflowMapper.selectPage(pageReqVO); + } + + @Override + public void updateWorkflowModel(AiWorkflowUpdateModelReqVO updateReqVO) { + validateWorkflowExists(updateReqVO.getId()); + workflowMapper.updateById(new AiWorkflowDO().setId(updateReqVO.getId()).setModel(updateReqVO.getModel())); + } + + @Override + public Object testWorkflow(AiWorkflowTestReqVO testReqVO) { + Map variables = testReqVO.getParams(); + Tinyflow tinyflow = parseFlowParam(testReqVO.getModel()); + return tinyflow.toChain().executeForResult(variables); + } + + private void validateWorkflowForCreateOrUpdate(Long id, String key) { + validateWorkflowExists(id); + validateKeyUnique(id, key); + } + + private void validateWorkflowExists(Long id) { + if (ObjUtil.isNull(id)) { + return; + } + AiWorkflowDO workflow = workflowMapper.selectById(id); + if (ObjUtil.isNull(workflow)) { + throw exception(WORKFLOW_NOT_EXISTS); + } + } + + private void validateKeyUnique(Long id, String key) { + if (StrUtil.isBlank(key)) { + return; + } + AiWorkflowDO workflow = workflowMapper.selectByKey(key); + if (ObjUtil.isNull(workflow)) { + return; + } + if (ObjUtil.isNull(id)) { + throw exception(WORKFLOW_KEY_EXISTS); + } + if (ObjUtil.notEqual(workflow.getId(), id)) { + throw exception(WORKFLOW_KEY_EXISTS); + } + } + + private Tinyflow parseFlowParam(String model) { + JSONObject json = JSONObject.parseObject(model); + JSONArray nodeArr = json.getJSONArray("nodes"); + Tinyflow tinyflow = new Tinyflow(json.toJSONString()); + for (int i = 0; i < nodeArr.size(); i++) { + JSONObject node = nodeArr.getJSONObject(i); + switch (node.getString("type")) { + case "llmNode": + JSONObject data = node.getJSONObject("data"); + AiApiKeyDO apiKey = apiKeyService.getApiKey(data.getLong("llmId")); + switch (apiKey.getPlatform()) { + // TODO @lesan 需要讨论一下这里怎么弄 + case "OpenAI": + break; + case "Ollama": + break; + case "YiYan": + break; + case "XingHuo": + break; + case "TongYi": + break; + case "DeepSeek": + break; + case "ZhiPu": + break; + } + break; + case "internalNode": + break; + default: + break; + } + } + return tinyflow; + } + + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml b/yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml index f37f3709c6..5740cf2041 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml @@ -15,6 +15,7 @@ AI 大模型拓展,接入国内外大模型 1.0.0-M6 + 1.0.0-rc.3 @@ -117,6 +118,13 @@ + + + dev.tinyflow + tinyflow-java-core + ${tinyflow.version} + + org.springframework.boot From ba4d4540abc67433ad17a6ebb4e98ef5a66640d0 Mon Sep 17 00:00:00 2001 From: qiny <2589love> Date: Mon, 24 Mar 2025 16:58:42 +0800 Subject: [PATCH 29/36] =?UTF-8?q?=E6=9D=A1=E4=BB=B6=E5=88=A4=E6=96=AD?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=94=AF=E6=8C=81JOIN=E8=A1=A8=E7=9A=84?= =?UTF-8?q?=E5=B1=9E=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mybatis/core/query/MPJLambdaWrapperX.java | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/query/MPJLambdaWrapperX.java b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/query/MPJLambdaWrapperX.java index 7950a2f96f..933451865f 100644 --- a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/query/MPJLambdaWrapperX.java +++ b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/query/MPJLambdaWrapperX.java @@ -20,7 +20,7 @@ import java.util.function.Consumer; */ public class MPJLambdaWrapperX extends MPJLambdaWrapper { - public MPJLambdaWrapperX likeIfPresent(SFunction column, String val) { + public MPJLambdaWrapperX likeIfPresent(SFunction column, String val) { MPJWrappers.lambdaJoin().like(column, val); if (StringUtils.hasText(val)) { return (MPJLambdaWrapperX) super.like(column, val); @@ -28,63 +28,63 @@ public class MPJLambdaWrapperX extends MPJLambdaWrapper { return this; } - public MPJLambdaWrapperX inIfPresent(SFunction column, Collection values) { + public MPJLambdaWrapperX inIfPresent(SFunction column, Collection values) { if (ObjectUtil.isAllNotEmpty(values) && !ArrayUtil.isEmpty(values)) { return (MPJLambdaWrapperX) super.in(column, values); } return this; } - public MPJLambdaWrapperX inIfPresent(SFunction column, Object... values) { + public MPJLambdaWrapperX inIfPresent(SFunction column, Object... values) { if (ObjectUtil.isAllNotEmpty(values) && !ArrayUtil.isEmpty(values)) { return (MPJLambdaWrapperX) super.in(column, values); } return this; } - public MPJLambdaWrapperX eqIfPresent(SFunction column, Object val) { + public MPJLambdaWrapperX eqIfPresent(SFunction column, Object val) { if (ObjectUtil.isNotEmpty(val)) { return (MPJLambdaWrapperX) super.eq(column, val); } return this; } - public MPJLambdaWrapperX neIfPresent(SFunction column, Object val) { + public MPJLambdaWrapperX neIfPresent(SFunction column, Object val) { if (ObjectUtil.isNotEmpty(val)) { return (MPJLambdaWrapperX) super.ne(column, val); } return this; } - public MPJLambdaWrapperX gtIfPresent(SFunction column, Object val) { + public MPJLambdaWrapperX gtIfPresent(SFunction column, Object val) { if (val != null) { return (MPJLambdaWrapperX) super.gt(column, val); } return this; } - public MPJLambdaWrapperX geIfPresent(SFunction column, Object val) { + public MPJLambdaWrapperX geIfPresent(SFunction column, Object val) { if (val != null) { return (MPJLambdaWrapperX) super.ge(column, val); } return this; } - public MPJLambdaWrapperX ltIfPresent(SFunction column, Object val) { + public MPJLambdaWrapperX ltIfPresent(SFunction column, Object val) { if (val != null) { return (MPJLambdaWrapperX) super.lt(column, val); } return this; } - public MPJLambdaWrapperX leIfPresent(SFunction column, Object val) { + public MPJLambdaWrapperX leIfPresent(SFunction column, Object val) { if (val != null) { return (MPJLambdaWrapperX) super.le(column, val); } return this; } - public MPJLambdaWrapperX betweenIfPresent(SFunction column, Object val1, Object val2) { + public MPJLambdaWrapperX betweenIfPresent(SFunction column, Object val1, Object val2) { if (val1 != null && val2 != null) { return (MPJLambdaWrapperX) super.between(column, val1, val2); } @@ -97,7 +97,7 @@ public class MPJLambdaWrapperX extends MPJLambdaWrapper { return this; } - public MPJLambdaWrapperX betweenIfPresent(SFunction column, Object[] values) { + public MPJLambdaWrapperX betweenIfPresent(SFunction column, Object[] values) { Object val1 = ArrayUtils.get(values, 0); Object val2 = ArrayUtils.get(values, 1); return betweenIfPresent(column, val1, val2); @@ -310,4 +310,4 @@ public class MPJLambdaWrapperX extends MPJLambdaWrapper { return this; } -} +} \ No newline at end of file From 4bb52bb37d1df17e75ca528594003aba00926172 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Tue, 25 Mar 2025 09:55:05 +0800 Subject: [PATCH 30/36] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=E3=80=91AI=EF=BC=9A=E5=B7=A5=E4=BD=9C=E6=B5=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/workflow/AiWorkflowController.java | 1 + .../admin/workflow/vo/AiWorkflowSaveReqVO.java | 2 +- .../ai/dal/dataobject/workflow/AiWorkflowDO.java | 12 ++++++------ .../ai/service/workflow/AiWorkflowService.java | 1 + .../ai/service/workflow/AiWorkflowServiceImpl.java | 3 ++- yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml | 2 +- 6 files changed, 12 insertions(+), 9 deletions(-) diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/AiWorkflowController.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/AiWorkflowController.java index 1270f7679c..44e60a0e98 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/AiWorkflowController.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/AiWorkflowController.java @@ -67,6 +67,7 @@ public class AiWorkflowController { return success(BeanUtils.toBean(pageResult, AiWorkflowRespVO.class)); } + // TODO @lesan:要不融合到 updateWorkflow 接口? @PutMapping("/updateWorkflowModel") @Operation(summary = "更新 AI 工作流模型") @PreAuthorize("@ss.hasPermission('ai:workflow:update')") diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/vo/AiWorkflowSaveReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/vo/AiWorkflowSaveReqVO.java index ec269cc034..5c4ebc4a4b 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/vo/AiWorkflowSaveReqVO.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/vo/AiWorkflowSaveReqVO.java @@ -8,7 +8,7 @@ import lombok.Data; @Data public class AiWorkflowSaveReqVO { - @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @Schema(description = "编号", example = "1") private Long id; @Schema(description = "工作流标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "FLOW") diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/workflow/AiWorkflowDO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/workflow/AiWorkflowDO.java index db6c090467..ef05f7ea17 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/workflow/AiWorkflowDO.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/workflow/AiWorkflowDO.java @@ -21,17 +21,17 @@ public class AiWorkflowDO extends BaseDO { */ @TableId private Long id; - - /** - * 工作流标识 - */ - private String definitionKey; - /** * 工作流名称 */ private String name; + /** + * 工作流标识 + */ + // TODO @lesan:要不换成 code?主要想,和 bpm 工作流,有点区分,字段上。 + private String definitionKey; + // TODO @lesan:graph 用这个如何?发现大家貌似更爱用这个字段哈。图! /** * 工作流模型 JSON 数据 */ diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/workflow/AiWorkflowService.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/workflow/AiWorkflowService.java index 31fd476540..bbc167d486 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/workflow/AiWorkflowService.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/workflow/AiWorkflowService.java @@ -66,4 +66,5 @@ public interface AiWorkflowService { * @param testReqVO 测试数据 */ Object testWorkflow(AiWorkflowTestReqVO testReqVO); + } diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/workflow/AiWorkflowServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/workflow/AiWorkflowServiceImpl.java index 53128b7c2a..0bf299da49 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/workflow/AiWorkflowServiceImpl.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/workflow/AiWorkflowServiceImpl.java @@ -117,6 +117,7 @@ public class AiWorkflowServiceImpl implements AiWorkflowService { } private Tinyflow parseFlowParam(String model) { + // TODO @lesan:可以使用 jackson 哇? JSONObject json = JSONObject.parseObject(model); JSONArray nodeArr = json.getJSONArray("nodes"); Tinyflow tinyflow = new Tinyflow(json.toJSONString()); @@ -128,6 +129,7 @@ public class AiWorkflowServiceImpl implements AiWorkflowService { AiApiKeyDO apiKey = apiKeyService.getApiKey(data.getLong("llmId")); switch (apiKey.getPlatform()) { // TODO @lesan 需要讨论一下这里怎么弄 + // TODO @lesan llmId 对应 model 的编号如何?这样的话,就是 apiModelService 提供一个获取 LLM 的方法。然后,创建的方法,也在 AiModelFactory 提供。可以先接个 deepseek 先。deepseek yyds! case "OpenAI": break; case "Ollama": @@ -153,5 +155,4 @@ public class AiWorkflowServiceImpl implements AiWorkflowService { return tinyflow; } - } diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml b/yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml index 5740cf2041..3ed8724b76 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml @@ -118,8 +118,8 @@ + - dev.tinyflow tinyflow-java-core ${tinyflow.version} From e14716a3078561f90df4d0384b69e2a38c3ff7cf Mon Sep 17 00:00:00 2001 From: Lesan <1960681385@qq.com> Date: Tue, 25 Mar 2025 10:24:34 +0800 Subject: [PATCH 31/36] =?UTF-8?q?fix:=20=E5=AD=98=E5=9C=A8=E5=AD=90?= =?UTF-8?q?=E6=B5=81=E7=A8=8B=E6=83=85=E5=86=B5=E4=B8=8B=E7=9A=84=E5=8F=96?= =?UTF-8?q?=E6=B6=88=E9=80=BB=E8=BE=91=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yudao/module/bpm/enums/ErrorCodeConstants.java | 1 + .../yudao/module/bpm/enums/task/BpmReasonEnum.java | 1 + .../admin/task/BpmProcessInstanceController.java | 1 - .../flowable/core/util/SimpleModelUtils.java | 1 - .../service/task/BpmProcessInstanceServiceImpl.java | 13 +++++++++++++ 5 files changed, 15 insertions(+), 2 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java index 65605142c2..23bdba9933 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java @@ -42,6 +42,7 @@ public interface ErrorCodeConstants { ErrorCode PROCESS_INSTANCE_CANCEL_FAIL_NOT_ALLOW = new ErrorCode(1_009_004_005, "流程取消失败,该流程不允许取消"); ErrorCode PROCESS_INSTANCE_HTTP_TRIGGER_CALL_ERROR = new ErrorCode(1_009_004_006, "流程 Http 触发器请求调用失败"); ErrorCode PROCESS_INSTANCE_APPROVE_USER_SELECT_ASSIGNEES_NOT_CONFIG = new ErrorCode(1_009_004_007, "下一个任务({})的审批人未配置"); + ErrorCode CHILD_PROCESS_INSTANCE_CANCEL_FAIL_NOT_ALLOW = new ErrorCode(1_009_004_008, "子流程取消失败,子流程不允许取消"); // ========== 流程任务 1-009-005-000 ========== ErrorCode TASK_OPERATE_FAIL_ASSIGN_NOT_SELF = new ErrorCode(1_009_005_001, "操作失败,原因:该任务的审批人不是你"); diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmReasonEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmReasonEnum.java index b0ade75299..eca18cce6e 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmReasonEnum.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmReasonEnum.java @@ -18,6 +18,7 @@ public enum BpmReasonEnum { REJECT_TASK("审批不通过任务,原因:{}"), // 场景:用户审批不通过任务。修改文案时,需要注意 isRejectReason 方法 CANCEL_PROCESS_INSTANCE_BY_START_USER("用户主动取消流程,原因:{}"), // 场景:用户主动取消流程 CANCEL_PROCESS_INSTANCE_BY_ADMIN("管理员【{}】取消流程,原因:{}"), // 场景:管理员取消流程 + CANCEL_CHILD_PROCESS_INSTANCE_BY_MAIN_PROCESS("子流程自动取消,原因:子流程已取消"), // ========== 流程任务的独有原因 ========== diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java index 9316261678..8a3777772e 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java @@ -148,7 +148,6 @@ public class BpmProcessInstanceController { processDefinition, processDefinitionInfo, startUser, dept)); } - // TODO @lesan:【子流程】子流程如果取消,主流程应该是通过、还是不通过哈?还是禁用掉子流程的取消? @DeleteMapping("/cancel-by-start-user") @Operation(summary = "用户取消流程实例", description = "取消发起的流程") @PreAuthorize("@ss.hasPermission('bpm:process-instance:cancel')") diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java index 3b5bad52c7..ae7e08536d 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java @@ -813,7 +813,6 @@ public class SimpleModelUtils { callActivity.setCalledElementType("key"); // 1. 是否异步 if (node.getChildProcessSetting().getAsync()) { - // TODO @lesan: 这里目前测试没有跳过执行call activity 后面的节点 callActivity.setAsynchronous(true); } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java index aa8f58b45d..efb2cf23b7 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java @@ -823,6 +823,10 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService && Boolean.FALSE.equals(processDefinitionInfo.getAllowCancelRunningProcess())) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_ALLOW); } + // 1.4 子流程不允许取消 + if (StrUtil.isNotBlank(instance.getSuperExecutionId())) { + throw exception(CHILD_PROCESS_INSTANCE_CANCEL_FAIL_NOT_ALLOW); + } // 2. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), @@ -851,6 +855,15 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService // 2. 结束流程 taskService.moveTaskToEnd(id, reason); + + // 3. 取消所有子流程 + List subProcessInstances = runtimeService.createProcessInstanceQuery() + .superProcessInstanceId(id) + .list(); + subProcessInstances.forEach(processInstance -> { + updateProcessInstanceCancel(processInstance.getProcessInstanceId(), + BpmReasonEnum.CANCEL_CHILD_PROCESS_INSTANCE_BY_MAIN_PROCESS.getReason()); + }); } @Override From b471dc55c38815cc14a83345aeb84ae111a51fce Mon Sep 17 00:00:00 2001 From: Lesan <1960681385@qq.com> Date: Tue, 25 Mar 2025 10:37:38 +0800 Subject: [PATCH 32/36] =?UTF-8?q?fix:=20=E5=AD=98=E5=9C=A8=E5=AD=90?= =?UTF-8?q?=E6=B5=81=E7=A8=8B=E6=83=85=E5=86=B5=E4=B8=8B=E7=9A=84=E5=8F=96?= =?UTF-8?q?=E6=B6=88=E9=80=BB=E8=BE=91=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yudao/module/bpm/enums/task/BpmReasonEnum.java | 2 +- .../bpm/service/task/BpmProcessInstanceServiceImpl.java | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmReasonEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmReasonEnum.java index eca18cce6e..46d1482a5e 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmReasonEnum.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmReasonEnum.java @@ -18,7 +18,7 @@ public enum BpmReasonEnum { REJECT_TASK("审批不通过任务,原因:{}"), // 场景:用户审批不通过任务。修改文案时,需要注意 isRejectReason 方法 CANCEL_PROCESS_INSTANCE_BY_START_USER("用户主动取消流程,原因:{}"), // 场景:用户主动取消流程 CANCEL_PROCESS_INSTANCE_BY_ADMIN("管理员【{}】取消流程,原因:{}"), // 场景:管理员取消流程 - CANCEL_CHILD_PROCESS_INSTANCE_BY_MAIN_PROCESS("子流程自动取消,原因:子流程已取消"), + CANCEL_CHILD_PROCESS_INSTANCE_BY_MAIN_PROCESS("子流程自动取消,原因:主流程已取消"), // ========== 流程任务的独有原因 ========== diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java index efb2cf23b7..709779bf64 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java @@ -853,10 +853,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); - // 2. 结束流程 - taskService.moveTaskToEnd(id, reason); - - // 3. 取消所有子流程 + // 2. 取消所有子流程 List subProcessInstances = runtimeService.createProcessInstanceQuery() .superProcessInstanceId(id) .list(); @@ -864,6 +861,9 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService updateProcessInstanceCancel(processInstance.getProcessInstanceId(), BpmReasonEnum.CANCEL_CHILD_PROCESS_INSTANCE_BY_MAIN_PROCESS.getReason()); }); + + // 3. 结束流程 + taskService.moveTaskToEnd(id, reason); } @Override From c9a8548920f2507338b35bc77a75430dec7c4de5 Mon Sep 17 00:00:00 2001 From: Lesan <1960681385@qq.com> Date: Tue, 25 Mar 2025 11:07:24 +0800 Subject: [PATCH 33/36] =?UTF-8?q?feat:=20=E6=B5=81=E7=A8=8B=E6=97=B6?= =?UTF-8?q?=E9=97=B4=E7=BA=BF=E9=80=82=E9=85=8D=E5=AD=90=E6=B5=81=E7=A8=8B?= =?UTF-8?q?=E8=8A=82=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../vo/instance/BpmApprovalDetailRespVO.java | 3 +++ .../task/BpmProcessInstanceServiceImpl.java | 19 +++++++++++++++---- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailRespVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailRespVO.java index 888f59ada2..226878a048 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailRespVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailRespVO.java @@ -72,6 +72,9 @@ public class BpmApprovalDetailRespVO { @Schema(description = "候选人用户列表") private List candidateUsers; // 只包含未生成 ApprovalTaskInfo 的用户列表 + @Schema(description = "流程编号", example = "8761d8e0-0922-11f0-bd37-00ff1db677bf") + private String processInstanceId; + } @Schema(description = "活动节点的任务信息") diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java index 709779bf64..d9db8ed94f 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java @@ -387,8 +387,6 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService List activities, List tasks) { // 遍历 tasks 列表,只处理已结束的 UserTask // 为什么不通过 activities 呢?因为,加签场景下,它只存在于 tasks,没有 activities,导致如果遍历 activities 的话,它无法成为一个节点 - // TODO @芋艿:子流程只有activity,这里获取不到已结束的子流程; - // TODO @lesan:【子流程】基于 activities 查询出 usertask、callactivity,然后拼接?如果是子流程,就是可以点击过去? List endTasks = filterList(tasks, task -> task.getEndTime() != null); List approvalNodes = convertList(endTasks, task -> { FlowElement flowNode = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); @@ -410,7 +408,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService // 遍历 activities,只处理已结束的 StartEvent、EndEvent List endActivities = filterList(activities, activity -> activity.getEndTime() != null - && (StrUtil.equalsAny(activity.getActivityType(), ELEMENT_EVENT_START, ELEMENT_EVENT_END))); + && (StrUtil.equalsAny(activity.getActivityType(), ELEMENT_EVENT_START, ELEMENT_CALL_ACTIVITY, ELEMENT_EVENT_END))); endActivities.forEach(activity -> { // StartEvent:只处理 BPMN 的场景。因为,SIMPLE 情况下,已经有 START_USER_NODE 节点 if (ELEMENT_EVENT_START.equals(activity.getActivityType()) @@ -444,7 +442,18 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService } approvalNodes.add(endNode); } + // CallActivity + if (ELEMENT_CALL_ACTIVITY.equals(activity.getActivityType())) { + ActivityNode callActivity = new ActivityNode().setId(activity.getId()) + .setName(BpmSimpleModelNodeTypeEnum.CHILD_PROCESS.getName()) + .setNodeType(BpmSimpleModelNodeTypeEnum.CHILD_PROCESS.getType()).setStatus(processInstanceStatus) + .setStartTime(DateUtils.of(activity.getStartTime())) + .setEndTime(DateUtils.of(activity.getEndTime())) + .setProcessInstanceId(activity.getProcessInstanceId()); + approvalNodes.add(callActivity); + } }); + approvalNodes.sort(Comparator.comparing(ActivityNode::getStartTime)); return approvalNodes; } @@ -464,7 +473,6 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService HistoricActivityInstance::getActivityId); // 按照 activityId 分组,构建 ApprovalNodeInfo 节点 - // TODO @lesan:【子流程】在子流程进行审批的时候,HistoricActivityInstance 里面可以拿到 runActivities.get(0).getCalledProcessInstanceId()。要不要支持跳转??? Map taskMap = convertMap(tasks, HistoricTaskInstance::getId); return convertList(runningTaskMap.entrySet(), entry -> { String activityId = entry.getKey(); @@ -510,6 +518,9 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService approvalTaskInfo.getAssignee())); // 委派或者向前加签情况,需要先比较 owner activityNode.setCandidateUserIds(CollUtil.sub(candidateUserIds, index + 1, candidateUserIds.size())); } + if (BpmSimpleModelNodeTypeEnum.CHILD_PROCESS.getType().equals(activityNode.getNodeType())) { + activityNode.setProcessInstanceId(firstActivity.getProcessInstanceId()); + } return activityNode; }); } From f0c26d5b5ac7fcc44d8a9f807c9ca49a6988219b Mon Sep 17 00:00:00 2001 From: YunaiV Date: Tue, 25 Mar 2025 12:49:34 +0800 Subject: [PATCH 34/36] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=E3=80=91BPM=EF=BC=9A=E5=AD=90=E6=B5=81=E7=A8=8B?= =?UTF-8?q?=E7=9A=84=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module/bpm/enums/ErrorCodeConstants.java | 2 +- .../task/vo/instance/BpmApprovalDetailRespVO.java | 2 +- .../task/BpmProcessInstanceServiceImpl.java | 15 +++++++-------- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java index 23bdba9933..d5d6fa77c4 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java @@ -42,7 +42,7 @@ public interface ErrorCodeConstants { ErrorCode PROCESS_INSTANCE_CANCEL_FAIL_NOT_ALLOW = new ErrorCode(1_009_004_005, "流程取消失败,该流程不允许取消"); ErrorCode PROCESS_INSTANCE_HTTP_TRIGGER_CALL_ERROR = new ErrorCode(1_009_004_006, "流程 Http 触发器请求调用失败"); ErrorCode PROCESS_INSTANCE_APPROVE_USER_SELECT_ASSIGNEES_NOT_CONFIG = new ErrorCode(1_009_004_007, "下一个任务({})的审批人未配置"); - ErrorCode CHILD_PROCESS_INSTANCE_CANCEL_FAIL_NOT_ALLOW = new ErrorCode(1_009_004_008, "子流程取消失败,子流程不允许取消"); + ErrorCode PROCESS_INSTANCE_CANCEL_CHILD_FAIL_NOT_ALLOW = new ErrorCode(1_009_004_008, "子流程取消失败,子流程不允许取消"); // ========== 流程任务 1-009-005-000 ========== ErrorCode TASK_OPERATE_FAIL_ASSIGN_NOT_SELF = new ErrorCode(1_009_005_001, "操作失败,原因:该任务的审批人不是你"); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailRespVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailRespVO.java index 226878a048..38c2bc1013 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailRespVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailRespVO.java @@ -73,7 +73,7 @@ public class BpmApprovalDetailRespVO { private List candidateUsers; // 只包含未生成 ApprovalTaskInfo 的用户列表 @Schema(description = "流程编号", example = "8761d8e0-0922-11f0-bd37-00ff1db677bf") - private String processInstanceId; + private String processInstanceId; // 当且仅当,该节点是子流程节点时,才会有值(CallActivity 的 processInstanceId 字段) } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java index d9db8ed94f..a46068040e 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java @@ -453,6 +453,8 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService approvalNodes.add(callActivity); } }); + + // 按照时间排序 approvalNodes.sort(Comparator.comparing(ActivityNode::getStartTime)); return approvalNodes; } @@ -836,7 +838,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService } // 1.4 子流程不允许取消 if (StrUtil.isNotBlank(instance.getSuperExecutionId())) { - throw exception(CHILD_PROCESS_INSTANCE_CANCEL_FAIL_NOT_ALLOW); + throw exception(PROCESS_INSTANCE_CANCEL_CHILD_FAIL_NOT_ALLOW); } // 2. 取消流程 @@ -865,13 +867,10 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 取消所有子流程 - List subProcessInstances = runtimeService.createProcessInstanceQuery() - .superProcessInstanceId(id) - .list(); - subProcessInstances.forEach(processInstance -> { - updateProcessInstanceCancel(processInstance.getProcessInstanceId(), - BpmReasonEnum.CANCEL_CHILD_PROCESS_INSTANCE_BY_MAIN_PROCESS.getReason()); - }); + List childProcessInstances = runtimeService.createProcessInstanceQuery() + .superProcessInstanceId(id).list(); + childProcessInstances.forEach(processInstance -> updateProcessInstanceCancel( + processInstance.getProcessInstanceId(), BpmReasonEnum.CANCEL_CHILD_PROCESS_INSTANCE_BY_MAIN_PROCESS.getReason())); // 3. 结束流程 taskService.moveTaskToEnd(id, reason); From 2aff972600005080775f2d3df10763a0fb83e001 Mon Sep 17 00:00:00 2001 From: Lesan <1960681385@qq.com> Date: Fri, 28 Mar 2025 15:54:46 +0800 Subject: [PATCH 35/36] =?UTF-8?q?feat:=20AI=E5=B7=A5=E4=BD=9C=E6=B5=81?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module/ai/enums/ErrorCodeConstants.java | 2 +- .../admin/workflow/AiWorkflowController.java | 9 ----- .../workflow/vo/AiWorkflowPageReqVO.java | 8 ++++- .../admin/workflow/vo/AiWorkflowRespVO.java | 10 ++++-- .../workflow/vo/AiWorkflowSaveReqVO.java | 14 +++++++- .../workflow/vo/AiWorkflowTestReqVO.java | 2 +- .../vo/AiWorkflowUpdateModelReqVO.java | 18 ---------- .../dal/dataobject/workflow/AiWorkflowDO.java | 19 +++++++--- .../dal/mysql/workflow/AiWorkflowMapper.java | 8 +++-- .../service/workflow/AiWorkflowService.java | 8 ----- .../workflow/AiWorkflowServiceImpl.java | 36 ++++++++----------- 11 files changed, 64 insertions(+), 70 deletions(-) delete mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/vo/AiWorkflowUpdateModelReqVO.java diff --git a/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/ErrorCodeConstants.java b/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/ErrorCodeConstants.java index fa072f3fcb..8a8a388324 100644 --- a/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/ErrorCodeConstants.java +++ b/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/ErrorCodeConstants.java @@ -63,6 +63,6 @@ public interface ErrorCodeConstants { // ========== AI 工作流 1-040-011-000 ========== ErrorCode WORKFLOW_NOT_EXISTS = new ErrorCode(1_040_011_000, "工作流不存在"); - ErrorCode WORKFLOW_KEY_EXISTS = new ErrorCode(1_040_011_001, "工作流标识已存在"); + ErrorCode WORKFLOW_CODE_EXISTS = new ErrorCode(1_040_011_001, "工作流标识已存在"); } diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/AiWorkflowController.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/AiWorkflowController.java index 44e60a0e98..d558d90454 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/AiWorkflowController.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/AiWorkflowController.java @@ -67,15 +67,6 @@ public class AiWorkflowController { return success(BeanUtils.toBean(pageResult, AiWorkflowRespVO.class)); } - // TODO @lesan:要不融合到 updateWorkflow 接口? - @PutMapping("/updateWorkflowModel") - @Operation(summary = "更新 AI 工作流模型") - @PreAuthorize("@ss.hasPermission('ai:workflow:update')") - public CommonResult updateWorkflowModel(@Valid @RequestBody AiWorkflowUpdateModelReqVO updateReqVO) { - workflowService.updateWorkflowModel(updateReqVO); - return success(true); - } - @PostMapping("/test") @Operation(summary = "测试 AI 工作流") @PreAuthorize("@ss.hasPermission('ai:workflow:test')") diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/vo/AiWorkflowPageReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/vo/AiWorkflowPageReqVO.java index 5418057817..e55b85ea90 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/vo/AiWorkflowPageReqVO.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/vo/AiWorkflowPageReqVO.java @@ -1,6 +1,8 @@ package cn.iocoder.yudao.module.ai.controller.admin.workflow.vo; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.validation.InEnum; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import org.springframework.format.annotation.DateTimeFormat; @@ -17,7 +19,11 @@ public class AiWorkflowPageReqVO extends PageParam { private String name; @Schema(description = "标识", example = "FLOW") - private String definitionKey; + private String code; + + @Schema(description = "状态", example = "1") + @InEnum(CommonStatusEnum.class) + private Integer status; @Schema(description = "创建时间") @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/vo/AiWorkflowRespVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/vo/AiWorkflowRespVO.java index b1c66b91de..e3a28ad648 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/vo/AiWorkflowRespVO.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/vo/AiWorkflowRespVO.java @@ -13,13 +13,19 @@ public class AiWorkflowRespVO { private Long id; @Schema(description = "工作流标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "FLOW") - private String definitionKey; + private String code; @Schema(description = "工作流名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "工作流") private String name; + @Schema(description = "备注", requiredMode = Schema.RequiredMode.REQUIRED, example = "工作流") + private String remark; + + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer status; + @Schema(description = "工作流模型 JSON", requiredMode = Schema.RequiredMode.REQUIRED, example = "{}") - private String model; + private String graph; @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "时间戳格式") private LocalDateTime createTime; diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/vo/AiWorkflowSaveReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/vo/AiWorkflowSaveReqVO.java index 5c4ebc4a4b..0a63c37732 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/vo/AiWorkflowSaveReqVO.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/vo/AiWorkflowSaveReqVO.java @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.ai.controller.admin.workflow.vo; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; import lombok.Data; @Schema(description = "管理后台 - AI 工作流新增/修改 Request VO") @@ -13,10 +14,21 @@ public class AiWorkflowSaveReqVO { @Schema(description = "工作流标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "FLOW") @NotEmpty(message = "工作流标识不能为空") - private String definitionKey; + private String code; @Schema(description = "工作流名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "工作流") @NotEmpty(message = "工作流名称不能为空") private String name; + @Schema(description = "备注", example = "FLOW") + private String remark; + + @Schema(description = "工作流模型", requiredMode = Schema.RequiredMode.REQUIRED, example = "{}") + @NotEmpty(message = "工作流模型不能为空") + private String graph; + + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "FLOW") + @NotNull(message = "状态不能为空") + private Integer status; + } diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/vo/AiWorkflowTestReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/vo/AiWorkflowTestReqVO.java index 077a3020a6..4dc509e89d 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/vo/AiWorkflowTestReqVO.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/vo/AiWorkflowTestReqVO.java @@ -12,7 +12,7 @@ public class AiWorkflowTestReqVO { @Schema(description = "工作流模型", requiredMode = Schema.RequiredMode.REQUIRED, example = "{}") @NotEmpty(message = "工作流模型不能为空") - private String model; + private String graph; @Schema(description = "参数", requiredMode = Schema.RequiredMode.REQUIRED, example = "{}") private Map params; diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/vo/AiWorkflowUpdateModelReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/vo/AiWorkflowUpdateModelReqVO.java deleted file mode 100644 index 00a279ba54..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/vo/AiWorkflowUpdateModelReqVO.java +++ /dev/null @@ -1,18 +0,0 @@ -package cn.iocoder.yudao.module.ai.controller.admin.workflow.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotEmpty; -import lombok.Data; - -@Schema(description = "管理后台 - AI 工作流修改流程模型 Request VO") -@Data -public class AiWorkflowUpdateModelReqVO { - - @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - private Long id; - - @Schema(description = "工作流模型", requiredMode = Schema.RequiredMode.REQUIRED, example = "{}") - @NotEmpty(message = "工作流模型不能为空") - private String model; - -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/workflow/AiWorkflowDO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/workflow/AiWorkflowDO.java index ef05f7ea17..d844f7da2e 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/workflow/AiWorkflowDO.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/workflow/AiWorkflowDO.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.ai.dal.dataobject.workflow; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; import com.baomidou.mybatisplus.annotation.KeySequence; import com.baomidou.mybatisplus.annotation.TableId; @@ -28,13 +29,23 @@ public class AiWorkflowDO extends BaseDO { /** * 工作流标识 */ - // TODO @lesan:要不换成 code?主要想,和 bpm 工作流,有点区分,字段上。 - private String definitionKey; + private String code; - // TODO @lesan:graph 用这个如何?发现大家貌似更爱用这个字段哈。图! /** * 工作流模型 JSON 数据 */ - private String model; + private String graph; + + /** + * 备注 + */ + private String remark; + + /** + * 状态 + * + * 枚举 {@link CommonStatusEnum} + */ + private Integer status; } diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/workflow/AiWorkflowMapper.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/workflow/AiWorkflowMapper.java index f6be0cec7b..3770dbf0b5 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/workflow/AiWorkflowMapper.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/workflow/AiWorkflowMapper.java @@ -15,14 +15,16 @@ import org.apache.ibatis.annotations.Mapper; @Mapper public interface AiWorkflowMapper extends BaseMapperX { - default AiWorkflowDO selectByKey(String key) { - return selectOne(AiWorkflowDO::getDefinitionKey, key); + default AiWorkflowDO selectByCode(String code) { + return selectOne(AiWorkflowDO::getCode, code); } default PageResult selectPage(AiWorkflowPageReqVO pageReqVO) { return selectPage(pageReqVO, new LambdaQueryWrapperX() + .eqIfPresent(AiWorkflowDO::getStatus, pageReqVO.getStatus()) .likeIfPresent(AiWorkflowDO::getName, pageReqVO.getName()) - .likeIfPresent(AiWorkflowDO::getDefinitionKey, pageReqVO.getDefinitionKey())); + .likeIfPresent(AiWorkflowDO::getCode, pageReqVO.getCode()) + .betweenIfPresent(AiWorkflowDO::getCreateTime, pageReqVO.getCreateTime())); } } diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/workflow/AiWorkflowService.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/workflow/AiWorkflowService.java index bbc167d486..51a3aea751 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/workflow/AiWorkflowService.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/workflow/AiWorkflowService.java @@ -4,7 +4,6 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.ai.controller.admin.workflow.vo.AiWorkflowPageReqVO; import cn.iocoder.yudao.module.ai.controller.admin.workflow.vo.AiWorkflowSaveReqVO; import cn.iocoder.yudao.module.ai.controller.admin.workflow.vo.AiWorkflowTestReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.workflow.vo.AiWorkflowUpdateModelReqVO; import cn.iocoder.yudao.module.ai.dal.dataobject.workflow.AiWorkflowDO; import jakarta.validation.Valid; @@ -53,13 +52,6 @@ public interface AiWorkflowService { */ PageResult getWorkflowPage(AiWorkflowPageReqVO pageReqVO); - /** - * 修改工作流模型 JSON 数据 - * - * @param updateReqVO 更新数据 - */ - void updateWorkflowModel(AiWorkflowUpdateModelReqVO updateReqVO); - /** * 测试 AI 工作流 * diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/workflow/AiWorkflowServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/workflow/AiWorkflowServiceImpl.java index 0bf299da49..70d28496c8 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/workflow/AiWorkflowServiceImpl.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/workflow/AiWorkflowServiceImpl.java @@ -7,7 +7,6 @@ import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.ai.controller.admin.workflow.vo.AiWorkflowPageReqVO; import cn.iocoder.yudao.module.ai.controller.admin.workflow.vo.AiWorkflowSaveReqVO; import cn.iocoder.yudao.module.ai.controller.admin.workflow.vo.AiWorkflowTestReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.workflow.vo.AiWorkflowUpdateModelReqVO; import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiApiKeyDO; import cn.iocoder.yudao.module.ai.dal.dataobject.workflow.AiWorkflowDO; import cn.iocoder.yudao.module.ai.dal.mysql.workflow.AiWorkflowMapper; @@ -22,7 +21,7 @@ import org.springframework.stereotype.Service; import java.util.Map; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.WORKFLOW_KEY_EXISTS; +import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.WORKFLOW_CODE_EXISTS; import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.WORKFLOW_NOT_EXISTS; /** @@ -42,16 +41,15 @@ public class AiWorkflowServiceImpl implements AiWorkflowService { @Override public Long createWorkflow(AiWorkflowSaveReqVO createReqVO) { - validateWorkflowForCreateOrUpdate(null, createReqVO.getDefinitionKey()); + validateWorkflowForCreateOrUpdate(null, createReqVO.getCode()); AiWorkflowDO workflow = BeanUtils.toBean(createReqVO, AiWorkflowDO.class); - workflow.setModel(StrUtil.EMPTY); workflowMapper.insert(workflow); return workflow.getId(); } @Override public void updateWorkflow(AiWorkflowSaveReqVO updateReqVO) { - validateWorkflowForCreateOrUpdate(updateReqVO.getId(), updateReqVO.getDefinitionKey()); + validateWorkflowForCreateOrUpdate(updateReqVO.getId(), updateReqVO.getCode()); AiWorkflowDO workflow = BeanUtils.toBean(updateReqVO, AiWorkflowDO.class); workflowMapper.updateById(workflow); } @@ -72,22 +70,16 @@ public class AiWorkflowServiceImpl implements AiWorkflowService { return workflowMapper.selectPage(pageReqVO); } - @Override - public void updateWorkflowModel(AiWorkflowUpdateModelReqVO updateReqVO) { - validateWorkflowExists(updateReqVO.getId()); - workflowMapper.updateById(new AiWorkflowDO().setId(updateReqVO.getId()).setModel(updateReqVO.getModel())); - } - @Override public Object testWorkflow(AiWorkflowTestReqVO testReqVO) { Map variables = testReqVO.getParams(); - Tinyflow tinyflow = parseFlowParam(testReqVO.getModel()); + Tinyflow tinyflow = parseFlowParam(testReqVO.getGraph()); return tinyflow.toChain().executeForResult(variables); } - private void validateWorkflowForCreateOrUpdate(Long id, String key) { + private void validateWorkflowForCreateOrUpdate(Long id, String code) { validateWorkflowExists(id); - validateKeyUnique(id, key); + validateCodeUnique(id, code); } private void validateWorkflowExists(Long id) { @@ -100,27 +92,27 @@ public class AiWorkflowServiceImpl implements AiWorkflowService { } } - private void validateKeyUnique(Long id, String key) { - if (StrUtil.isBlank(key)) { + private void validateCodeUnique(Long id, String code) { + if (StrUtil.isBlank(code)) { return; } - AiWorkflowDO workflow = workflowMapper.selectByKey(key); + AiWorkflowDO workflow = workflowMapper.selectByCode(code); if (ObjUtil.isNull(workflow)) { return; } if (ObjUtil.isNull(id)) { - throw exception(WORKFLOW_KEY_EXISTS); + throw exception(WORKFLOW_CODE_EXISTS); } if (ObjUtil.notEqual(workflow.getId(), id)) { - throw exception(WORKFLOW_KEY_EXISTS); + throw exception(WORKFLOW_CODE_EXISTS); } } - private Tinyflow parseFlowParam(String model) { + private Tinyflow parseFlowParam(String graph) { // TODO @lesan:可以使用 jackson 哇? - JSONObject json = JSONObject.parseObject(model); + JSONObject json = JSONObject.parseObject(graph); JSONArray nodeArr = json.getJSONArray("nodes"); - Tinyflow tinyflow = new Tinyflow(json.toJSONString()); + Tinyflow tinyflow = new Tinyflow(json.toJSONString()); for (int i = 0; i < nodeArr.size(); i++) { JSONObject node = nodeArr.getJSONObject(i); switch (node.getString("type")) { From d7a785d1de97a24d54fd169512359c488c938814 Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Fri, 28 Mar 2025 21:41:40 +0800 Subject: [PATCH 36/36] =?UTF-8?q?=E3=80=90=E9=97=AE=E9=A2=98=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E3=80=91=20=E5=B9=B6=E8=A1=8C=E7=BD=91=E5=85=B3?= =?UTF-8?q?=E4=BF=9D=E5=AD=98=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../vo/model/simple/BpmSimpleModelNodeVO.java | 4 ++-- .../flowable/core/util/SimpleModelUtils.java | 14 +++++++++----- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java index f002e6894a..1cbebe06e4 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java @@ -112,7 +112,7 @@ public class BpmSimpleModelNodeVO { /** * 条件节点设置 */ - private ConditionSetting conditionSetting; // 仅用于条件节点 BpmSimpleModelNodeType.CONDITION_NODE + private ConditionSetting conditionSetting; // 仅用于条件节点 BpmSimpleModelNodeTypeEnum.CONDITION_NODE @Schema(description = "路由分支组", example = "[]") private List routerGroups; @@ -241,7 +241,7 @@ public class BpmSimpleModelNodeVO { @Schema(description = "条件设置") @Data @Valid - // 仅用于条件节点 BpmSimpleModelNodeType.CONDITION_NODE + // 仅用于条件节点 BpmSimpleModelNodeTypeEnum.CONDITION_NODE public static class ConditionSetting { @Schema(description = "条件类型", example = "1") diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java index ae7e08536d..bd427e32f2 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java @@ -187,7 +187,7 @@ public class SimpleModelUtils { /** * 构建有附加节点的连线 * - * @param nodeId 当前节点 ID + * @param nodeId 当前节点 ID * @param attachNodeId 附属节点 ID * @param targetNodeId 目标节点 ID */ @@ -662,6 +662,10 @@ public class SimpleModelUtils { * 构造条件表达式 */ public static String buildConditionExpression(BpmSimpleModelNodeVO.ConditionSetting conditionSetting) { + // 并行网关不需要设置条件 + if (conditionSetting == null) { + return null; + } return buildConditionExpression(conditionSetting.getConditionType(), conditionSetting.getConditionExpression(), conditionSetting.getConditionGroups()); } @@ -958,8 +962,8 @@ public class SimpleModelUtils { if (nodeType == BpmSimpleModelNodeTypeEnum.CONDITION_BRANCH_NODE) { // 查找满足条件的 BpmSimpleModelNodeVO 节点 BpmSimpleModelNodeVO matchConditionNode = CollUtil.findOne(currentNode.getConditionNodes(), - conditionNode -> !BooleanUtil.isTrue(conditionNode.getConditionSetting().getDefaultFlow()) - && evalConditionExpress(variables, conditionNode.getConditionSetting())); + conditionNode -> !BooleanUtil.isTrue(conditionNode.getConditionSetting().getDefaultFlow()) + && evalConditionExpress(variables, conditionNode.getConditionSetting())); if (matchConditionNode == null) { matchConditionNode = CollUtil.findOne(currentNode.getConditionNodes(), conditionNode -> BooleanUtil.isTrue(conditionNode.getConditionSetting().getDefaultFlow())); @@ -973,8 +977,8 @@ public class SimpleModelUtils { if (nodeType == BpmSimpleModelNodeTypeEnum.INCLUSIVE_BRANCH_NODE) { // 查找满足条件的 BpmSimpleModelNodeVO 节点 Collection matchConditionNodes = CollUtil.filterNew(currentNode.getConditionNodes(), - conditionNode -> !BooleanUtil.isTrue(conditionNode.getConditionSetting().getDefaultFlow()) - && evalConditionExpress(variables, conditionNode.getConditionSetting())); + conditionNode -> !BooleanUtil.isTrue(conditionNode.getConditionSetting().getDefaultFlow()) + && evalConditionExpress(variables, conditionNode.getConditionSetting())); if (CollUtil.isEmpty(matchConditionNodes)) { matchConditionNodes = CollUtil.filterNew(currentNode.getConditionNodes(), conditionNode -> BooleanUtil.isTrue(conditionNode.getConditionSetting().getDefaultFlow()));