From acf68b1cec62da862210dae1fd180c15c71fcc12 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Mon, 17 Mar 2025 22:15:57 +0800 Subject: [PATCH] 【代码评审】AI:PPT API 的接入 --- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/wenduoduo/api/WddPptApi.java | 58 +++++++++++++++++++++++++++------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/api/XunfeiPptApi.java | 759 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xunfei/api/XunfeiPptApi.java | 766 ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/ppt/wdd/WddPptApiTests.java | 48 ++++++++++++++++++++++++++++-------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/ppt/xunfei/XunfeiPptApiTests.java | 124 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------------------------------- 5 files changed, 885 insertions(+), 870 deletions(-) create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/api/XunfeiPptApi.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xunfei/api/XunfeiPptApi.java 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 b752f73..c3757a7 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/xinghuo/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 new file mode 100644 index 0000000..c3320ef --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/api/XunfeiPptApi.java @@ -0,0 +1,759 @@ +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; +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; + +// TODO @新:要不改成 XunFeiPptApi +/** + * 讯飞智能 PPT 生成 API + * + * @see 智能 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] 调用失败!")); + }); + + // TODO @新:是不是不用 baseUrl 哈 + public XunfeiPptApi(String baseUrl, String appId, String apiSecret) { + // TODO @新:建议,增加 defaultheaders,例如说 appid 之类的;或者每个请求,通过 headers customer 处理。 + 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 { + // TODO @新:使用 hutool 简化 + 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); + // TODO @新:可以使用 ObjUtil.defaultIfNull + requestBody.put("pageSize", pageSize != null ? pageSize : 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, + // TODO @新:字段注释,去掉 + 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(); + } + + // TODO @新:这个可以用 lombok 简化么? + /** + * 构建器类 + */ + 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); + } + } + // 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; + } + + /** + * 直接生成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; + + // TODO @新:这个可以用 lombok 简化么? + + 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/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 deleted file mode 100644 index fe8ba4c..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xunfei/api/XunfeiPptApi.java +++ /dev/null @@ -1,766 +0,0 @@ -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/WddPptApiTests.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/ppt/wdd/WddPptApiTests.java index e01311c..3bb9898 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 b710f59..34088bf 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 -- libgit2 0.21.4