Commit b2ec4d38a0f187e74a5cf519dc75b7fd5c8fcaaa

Authored by xiaoxin
1 parent 270fea0c

【优化代码】AI PPT:优化文多多、讯飞 API

yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/wenduoduo/api/WenDuoDuoPptApi.java
... ... @@ -8,6 +8,7 @@ import org.springframework.core.ParameterizedTypeReference;
8 8 import org.springframework.http.HttpRequest;
9 9 import org.springframework.http.HttpStatusCode;
10 10 import org.springframework.http.MediaType;
  11 +import org.springframework.util.Assert;
11 12 import org.springframework.util.LinkedMultiValueMap;
12 13 import org.springframework.util.MultiValueMap;
13 14 import org.springframework.web.multipart.MultipartFile;
... ... @@ -35,6 +36,7 @@ import java.util.function.Predicate;
35 36 public class WenDuoDuoPptApi {
36 37  
37 38 public static final String BASE_URL = "https://docmee.cn";
  39 + public static final String TOKEN_NAME = "token";
38 40  
39 41 private final WebClient webClient;
40 42  
... ... @@ -43,17 +45,24 @@ public class WenDuoDuoPptApi {
43 45 private final Function<Object, Function<ClientResponse, Mono<? extends Throwable>>> EXCEPTION_FUNCTION =
44 46 reqParam -> response -> response.bodyToMono(String.class).handle((responseBody, sink) -> {
45 47 HttpRequest request = response.request();
46   - log.error("[wdd-api] 调用失败!请求方式:[{}],请求地址:[{}],请求参数:[{}],响应数据: [{}]",
  48 + log.error("[WenDuoDuoPptApi] 调用失败!请求方式:[{}],请求地址:[{}],请求参数:[{}],响应数据: [{}]",
47 49 request.getMethod(), request.getURI(), reqParam, responseBody);
48   - sink.error(new IllegalStateException("[wdd-api] 调用失败!"));
  50 + sink.error(new IllegalStateException("[WenDuoDuoPptApi] 调用失败!"));
49 51 });
50 52  
51   - // TODO @新:是不是不用 baseUrl 哈
52   - public WenDuoDuoPptApi(String baseUrl) {
  53 + /**
  54 + * 构造方法
  55 + *
  56 + * @param token API令牌,可为空,后续API调用时单独指定
  57 + */
  58 + public WenDuoDuoPptApi(String token) {
  59 + Assert.hasText(token, "token 不能为空");
53 60 this.webClient = WebClient.builder()
54   - .baseUrl(baseUrl)
55   - // TODO @新:建议,token 作为 defaultHeader
56   - .defaultHeaders((headers) -> headers.setContentType(MediaType.APPLICATION_JSON))
  61 + .baseUrl(BASE_URL)
  62 + .defaultHeaders((headers) -> {
  63 + headers.setContentType(MediaType.APPLICATION_JSON);
  64 + headers.add(TOKEN_NAME, token);
  65 + })
57 66 .build();
58 67 }
59 68  
... ... @@ -90,7 +99,7 @@ public class WenDuoDuoPptApi {
90 99 * @return 任务ID
91 100 * @see <a href="https://docmee.cn/open-platform/api#%E5%88%9B%E5%BB%BA%E4%BB%BB%E5%8A%A1">创建任务</a>
92 101 */
93   - public ApiResponse createTask(String token, Integer type, String content, List<MultipartFile> files) {
  102 + public ApiResponse createTask(Integer type, String content, List<MultipartFile> files) {
94 103 MultiValueMap<String, Object> formData = new LinkedMultiValueMap<>();
95 104 formData.add("type", type);
96 105 if (content != null) {
... ... @@ -103,7 +112,6 @@ public class WenDuoDuoPptApi {
103 112 }
104 113 return this.webClient.post()
105 114 .uri("/api/ppt/v2/createTask")
106   - .header("token", token)
107 115 .contentType(MediaType.MULTIPART_FORM_DATA)
108 116 .body(BodyInserters.fromMultipartData(formData))
109 117 .retrieve()
... ... @@ -146,10 +154,9 @@ public class WenDuoDuoPptApi {
146 154 * @param request 请求体
147 155 * @return 模板列表
148 156 */
149   - public PagePptTemplateInfo getTemplatePage(String token, TemplateQueryRequest request) {
  157 + public PagePptTemplateInfo getTemplatePage(TemplateQueryRequest request) {
150 158 return this.webClient.post()
151 159 .uri("/api/ppt/templates")
152   - .header("token", token)
153 160 .bodyValue(request)
154 161 .retrieve()
155 162 .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request))
... ... @@ -163,10 +170,9 @@ public class WenDuoDuoPptApi {
163 170 *
164 171 * @return 大纲内容流
165 172 */
166   - public Flux<Map<String, Object>> createOutline(String token, CreateOutlineRequest request) {
  173 + public Flux<Map<String, Object>> createOutline(CreateOutlineRequest request) {
167 174 return this.webClient.post()
168 175 .uri("/api/ppt/v2/generateContent")
169   - .header("token", token)
170 176 .body(Mono.just(request), CreateOutlineRequest.class)
171 177 .retrieve()
172 178 .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request))
... ... @@ -180,10 +186,9 @@ public class WenDuoDuoPptApi {
180 186 * @param request 请求体
181 187 * @return 大纲内容流
182 188 */
183   - public Flux<Map<String, Object>> updateOutline(String token, UpdateOutlineRequest request) {
  189 + public Flux<Map<String, Object>> updateOutline(UpdateOutlineRequest request) {
184 190 return this.webClient.post()
185 191 .uri("/api/ppt/v2/updateContent")
186   - .header("token", token)
187 192 .body(Mono.just(request), UpdateOutlineRequest.class)
188 193 .retrieve()
189 194 .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request))
... ... @@ -196,11 +201,10 @@ public class WenDuoDuoPptApi {
196 201 *
197 202 * @return PPT信息
198 203 */
199   - public PptInfo create(String token, CreatePptRequest request) {
  204 + public PptInfo create(PptCreateRequest request) {
200 205 return this.webClient.post()
201 206 .uri("/api/ppt/v2/generatePptx")
202   - .header("token", token)
203   - .body(Mono.just(request), CreatePptRequest.class)
  207 + .body(Mono.just(request), PptCreateRequest.class)
204 208 .retrieve()
205 209 .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request))
206 210 .bodyToMono(ApiResponse.class)
... ... @@ -214,7 +218,9 @@ public class WenDuoDuoPptApi {
214 218 .block();
215 219 }
216 220  
217   -
  221 + /**
  222 + * 创建Token请求参数
  223 + */
218 224 @JsonInclude(value = JsonInclude.Include.NON_NULL)
219 225 public record CreateTokenRequest(
220 226 String apiKey,
... ... @@ -276,18 +282,19 @@ public class WenDuoDuoPptApi {
276 282 }
277 283  
278 284 /**
279   - * 生成 PPT 请求
  285 + * 生成 PPT 请求参数
280 286 */
281   - // TODO @新:要不按照 PptCreateRequest 这样的风格
282 287 @JsonInclude(value = JsonInclude.Include.NON_NULL)
283   - public record CreatePptRequest(
  288 + public record PptCreateRequest(
284 289 String id,
285 290 String templateId,
286 291 String markdown
287 292 ) {
288 293 }
289 294  
290   - // TODO @新:要不写下类注释
  295 + /**
  296 + * PPT 信息
  297 + */
291 298 @JsonInclude(value = JsonInclude.Include.NON_NULL)
292 299 public record PptInfo(
293 300 String id,
... ... @@ -309,7 +316,9 @@ public class WenDuoDuoPptApi {
309 316 ) {
310 317 }
311 318  
312   - // TODO @新:要不写下类注释
  319 + /**
  320 + * 模板查询请求参数
  321 + */
313 322 @JsonInclude(value = JsonInclude.Include.NON_NULL)
314 323 public record TemplateQueryRequest(
315 324 int page,
... ... @@ -317,6 +326,9 @@ public class WenDuoDuoPptApi {
317 326 Filter filters
318 327 ) {
319 328  
  329 + /**
  330 + * 模板查询过滤条件
  331 + */
320 332 @JsonInclude(value = JsonInclude.Include.NON_NULL)
321 333 public record Filter(
322 334 int type,
... ... @@ -328,7 +340,9 @@ public class WenDuoDuoPptApi {
328 340  
329 341 }
330 342  
331   - // TODO @新:要不写下类注释
  343 + /**
  344 + * PPT模板分页信息
  345 + */
332 346 @JsonInclude(value = JsonInclude.Include.NON_NULL)
333 347 public record PagePptTemplateInfo(
334 348 List<PptTemplateInfo> data,
... ... @@ -336,8 +350,9 @@ public class WenDuoDuoPptApi {
336 350 ) {
337 351 }
338 352  
339   -
340   - // TODO @新:要不写下类注释
  353 + /**
  354 + * PPT模板信息
  355 + */
341 356 @JsonInclude(value = JsonInclude.Include.NON_NULL)
342 357 public record PptTemplateInfo(
343 358 String id,
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/api/XunFeiPptApi.java
1 1 package cn.iocoder.yudao.framework.ai.core.model.xinghuo.api;
2 2  
3 3 import cn.hutool.core.util.ObjUtil;
  4 +import cn.hutool.crypto.SecureUtil;
  5 +import cn.hutool.crypto.digest.HmacAlgorithm;
4 6 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
5 7 import com.fasterxml.jackson.annotation.JsonInclude;
  8 +import lombok.Builder;
6 9 import lombok.extern.slf4j.Slf4j;
7 10 import org.springframework.core.io.ByteArrayResource;
8 11 import org.springframework.http.HttpStatusCode;
9 12 import org.springframework.http.MediaType;
10 13 import org.springframework.util.LinkedMultiValueMap;
11 14 import org.springframework.util.MultiValueMap;
  15 +import org.springframework.util.StringUtils;
12 16 import org.springframework.web.multipart.MultipartFile;
13 17 import org.springframework.web.reactive.function.BodyInserters;
14 18 import org.springframework.web.reactive.function.client.ClientResponse;
15 19 import org.springframework.web.reactive.function.client.WebClient;
16 20 import reactor.core.publisher.Mono;
17 21  
18   -import javax.crypto.Mac;
19   -import javax.crypto.spec.SecretKeySpec;
20 22 import java.io.IOException;
21   -import java.nio.charset.StandardCharsets;
22   -import java.security.InvalidKeyException;
23   -import java.security.MessageDigest;
24   -import java.security.NoSuchAlgorithmException;
25   -import java.util.Base64;
26 23 import java.util.HashMap;
27 24 import java.util.List;
28 25 import java.util.Map;
29 26 import java.util.function.Function;
30 27 import java.util.function.Predicate;
31 28  
32   -
33   -
34 29 /**
35 30 * 讯飞智能 PPT 生成 API
36 31 *
... ... @@ -41,6 +36,9 @@ import java.util.function.Predicate;
41 36 public class XunFeiPptApi {
42 37  
43 38 public static final String BASE_URL = "https://zwapi.xfyun.cn/api/ppt/v2";
  39 + private static final String HEADER_APP_ID = "appId";
  40 + private static final String HEADER_TIMESTAMP = "timestamp";
  41 + private static final String HEADER_SIGNATURE = "signature";
44 42  
45 43 private final WebClient webClient;
46 44 private final String appId;
... ... @@ -50,18 +48,21 @@ public class XunFeiPptApi {
50 48  
51 49 private final Function<Object, Function<ClientResponse, Mono<? extends Throwable>>> EXCEPTION_FUNCTION =
52 50 reqParam -> response -> response.bodyToMono(String.class).handle((responseBody, sink) -> {
53   - log.error("[xunfei-ppt-api] 调用失败!请求参数:[{}],响应数据: [{}]", reqParam, responseBody);
54   - sink.error(new IllegalStateException("[xunfei-ppt-api] 调用失败!"));
  51 + log.error("[XunFeiPptApi] 调用失败!请求参数:[{}],响应数据: [{}]", reqParam, responseBody);
  52 + sink.error(new IllegalStateException("[XunFeiPptApi] 调用失败!"));
55 53 });
56 54  
57   - // TODO @新:是不是不用 baseUrl 哈
58   - public XunFeiPptApi(String baseUrl, String appId, String apiSecret) {
59   - // TODO @新:建议,增加 defaultheaders,例如说 appid 之类的;或者每个请求,通过 headers customer 处理。
60   - this.webClient = WebClient.builder()
61   - .baseUrl(baseUrl)
62   - .build();
  55 + public XunFeiPptApi(String appId, String apiSecret) {
63 56 this.appId = appId;
64 57 this.apiSecret = apiSecret;
  58 + this.webClient = WebClient.builder()
  59 + .baseUrl(BASE_URL)
  60 + .defaultHeaders((headers) -> {
  61 + headers.setContentType(MediaType.APPLICATION_JSON);
  62 + headers.add(HEADER_APP_ID, appId);
  63 + })
  64 + .build();
  65 +
65 66 }
66 67  
67 68 /**
... ... @@ -73,7 +74,7 @@ public class XunFeiPptApi {
73 74 long timestamp = System.currentTimeMillis() / 1000;
74 75 String ts = String.valueOf(timestamp);
75 76 String signature = generateSignature(appId, apiSecret, timestamp);
76   - return new SignatureInfo(appId, ts, signature);
  77 + return new SignatureInfo(ts, signature);
77 78 }
78 79  
79 80 /**
... ... @@ -85,43 +86,8 @@ public class XunFeiPptApi {
85 86 * @return 签名
86 87 */
87 88 private String generateSignature(String appId, String apiSecret, long timestamp) {
88   - try {
89   - // TODO @新:使用 hutool 简化
90   - String auth = md5(appId + timestamp);
91   - return hmacSHA1Encrypt(auth, apiSecret);
92   - } catch (NoSuchAlgorithmException | InvalidKeyException e) {
93   - log.error("[xunfei-ppt-api] 生成签名失败", e);
94   - throw new IllegalStateException("[xunfei-ppt-api] 生成签名失败");
95   - }
96   - }
97   -
98   - /**
99   - * HMAC SHA1 加密
100   - */
101   - private String hmacSHA1Encrypt(String encryptText, String encryptKey)
102   - throws NoSuchAlgorithmException, InvalidKeyException {
103   - SecretKeySpec keySpec = new SecretKeySpec(
104   - encryptKey.getBytes(StandardCharsets.UTF_8), "HmacSHA1");
105   -
106   - Mac mac = Mac.getInstance("HmacSHA1");
107   - mac.init(keySpec);
108   - byte[] result = mac.doFinal(encryptText.getBytes(StandardCharsets.UTF_8));
109   -
110   - return Base64.getEncoder().encodeToString(result);
111   - }
112   -
113   - /**
114   - * MD5 哈希
115   - */
116   - private String md5(String text) throws NoSuchAlgorithmException {
117   - MessageDigest md = MessageDigest.getInstance("MD5");
118   - byte[] digest = md.digest(text.getBytes(StandardCharsets.UTF_8));
119   -
120   - StringBuilder sb = new StringBuilder();
121   - for (byte b : digest) {
122   - sb.append(String.format("%02x", b));
123   - }
124   - return sb.toString();
  89 + String auth = SecureUtil.md5(appId + timestamp);
  90 + return SecureUtil.hmac(HmacAlgorithm.HmacSHA1, apiSecret).digestBase64(auth, false);
125 91 }
126 92  
127 93 /**
... ... @@ -138,9 +104,8 @@ public class XunFeiPptApi {
138 104 requestBody.put("pageSize", ObjUtil.defaultIfNull(pageSize, 20));
139 105 return this.webClient.post()
140 106 .uri("/template/list")
141   - .header("appId", signInfo.appId)
142   - .header("timestamp", signInfo.timestamp)
143   - .header("signature", signInfo.signature)
  107 + .header(HEADER_TIMESTAMP, signInfo.timestamp)
  108 + .header(HEADER_SIGNATURE, signInfo.signature)
144 109 .contentType(MediaType.APPLICATION_JSON)
145 110 .bodyValue(requestBody)
146 111 .retrieve()
... ... @@ -161,9 +126,8 @@ public class XunFeiPptApi {
161 126 formData.add("query", query);
162 127 return this.webClient.post()
163 128 .uri("/createOutline")
164   - .header("appId", signInfo.appId)
165   - .header("timestamp", signInfo.timestamp)
166   - .header("signature", signInfo.signature)
  129 + .header(HEADER_TIMESTAMP, signInfo.timestamp)
  130 + .header(HEADER_SIGNATURE, signInfo.signature)
167 131 .contentType(MediaType.MULTIPART_FORM_DATA)
168 132 .body(BodyInserters.fromMultipartData(formData))
169 133 .retrieve()
... ... @@ -207,12 +171,11 @@ public class XunFeiPptApi {
207 171 */
208 172 public CreateResponse create(CreatePptRequest request) {
209 173 SignatureInfo signInfo = getSignature();
210   - MultiValueMap<String, Object> formData = buildCreateFormData(request);
  174 + MultiValueMap<String, Object> formData = buildCreatePptFormData(request);
211 175 return this.webClient.post()
212 176 .uri("/create")
213   - .header("appId", signInfo.appId)
214   - .header("timestamp", signInfo.timestamp)
215   - .header("signature", signInfo.signature)
  177 + .header(HEADER_TIMESTAMP, signInfo.timestamp)
  178 + .header(HEADER_SIGNATURE, signInfo.signature)
216 179 .contentType(MediaType.MULTIPART_FORM_DATA)
217 180 .body(BodyInserters.fromMultipartData(formData))
218 181 .retrieve()
... ... @@ -247,9 +210,8 @@ public class XunFeiPptApi {
247 210 SignatureInfo signInfo = getSignature();
248 211 return this.webClient.post()
249 212 .uri("/createPptByOutline")
250   - .header("appId", signInfo.appId)
251   - .header("timestamp", signInfo.timestamp)
252   - .header("signature", signInfo.signature)
  213 + .header(HEADER_TIMESTAMP, signInfo.timestamp)
  214 + .header(HEADER_SIGNATURE, signInfo.signature)
253 215 .contentType(MediaType.APPLICATION_JSON)
254 216 .bodyValue(request)
255 217 .retrieve()
... ... @@ -271,9 +233,8 @@ public class XunFeiPptApi {
271 233 .path("/progress")
272 234 .queryParam("sid", sid)
273 235 .build())
274   - .header("appId", signInfo.appId)
275   - .header("timestamp", signInfo.timestamp)
276   - .header("signature", signInfo.signature)
  236 + .header(HEADER_TIMESTAMP, signInfo.timestamp)
  237 + .header(HEADER_SIGNATURE, signInfo.signature)
277 238 .retrieve()
278 239 .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(sid))
279 240 .bodyToMono(ProgressResponse.class)
... ... @@ -285,7 +246,6 @@ public class XunFeiPptApi {
285 246 */
286 247 @JsonInclude(value = JsonInclude.Include.NON_NULL)
287 248 private record SignatureInfo(
288   - String appId,
289 249 String timestamp,
290 250 String signature
291 251 ) {
... ... @@ -461,6 +421,7 @@ public class XunFeiPptApi {
461 421 * 通过大纲创建 PPT 请求参数
462 422 */
463 423 @JsonInclude(value = JsonInclude.Include.NON_NULL)
  424 + @Builder
464 425 public record CreatePptByOutlineRequest(
465 426 String query, // 用户生成PPT要求(最多8000字)
466 427 String outlineSid, // 已生成大纲后,响应返回的请求大纲唯一id
... ... @@ -476,123 +437,18 @@ public class XunFeiPptApi {
476 437 Boolean isFigure, // 是否自动配图
477 438 String aiImage // ai配图类型:normal、advanced
478 439 ) {
479   -
480   - /**
481   - * 创建构建器
482   - *
483   - * @return 构建器
484   - */
485   - public static Builder builder() {
486   - return new Builder();
487   - }
488   -
489   - // TODO @新:这个可以用 lombok 简化么?
490   -
491   - /**
492   - * 构建器类
493   - */
494   - public static class Builder {
495   -
496   - private String query;
497   - private String outlineSid;
498   - private OutlineData outline;
499   - private String templateId;
500   - private String businessId;
501   - private String author;
502   - private Boolean isCardNote;
503   - private Boolean search;
504   - private String language;
505   - private String fileUrl;
506   - private String fileName;
507   - private Boolean isFigure;
508   - private String aiImage;
509   -
510   - public Builder query(String query) {
511   - this.query = query;
512   - return this;
513   - }
514   -
515   - public Builder outlineSid(String outlineSid) {
516   - this.outlineSid = outlineSid;
517   - return this;
518   - }
519   -
520   - public Builder outline(OutlineData outline) {
521   - this.outline = outline;
522   - return this;
523   - }
524   -
525   - public Builder templateId(String templateId) {
526   - this.templateId = templateId;
527   - return this;
528   - }
529   -
530   - public Builder businessId(String businessId) {
531   - this.businessId = businessId;
532   - return this;
533   - }
534   -
535   - public Builder author(String author) {
536   - this.author = author;
537   - return this;
538   - }
539   -
540   - public Builder isCardNote(Boolean isCardNote) {
541   - this.isCardNote = isCardNote;
542   - return this;
543   - }
544   -
545   - public Builder search(Boolean search) {
546   - this.search = search;
547   - return this;
548   - }
549   -
550   - public Builder language(String language) {
551   - this.language = language;
552   - return this;
553   - }
554   -
555   - public Builder fileUrl(String fileUrl) {
556   - this.fileUrl = fileUrl;
557   - return this;
558   - }
559   -
560   - public Builder fileName(String fileName) {
561   - this.fileName = fileName;
562   - return this;
563   - }
564   -
565   - public Builder isFigure(Boolean isFigure) {
566   - this.isFigure = isFigure;
567   - return this;
568   - }
569   -
570   - public Builder aiImage(String aiImage) {
571   - this.aiImage = aiImage;
572   - return this;
573   - }
574   -
575   - public CreatePptByOutlineRequest build() {
576   - return new CreatePptByOutlineRequest(
577   - query, outlineSid, outline, templateId, businessId, author,
578   - isCardNote, search, language, fileUrl, fileName, isFigure, aiImage
579   - );
580   - }
581   - }
582 440 }
583 441  
  442 +
584 443 /**
585 444 * 构建创建 PPT 的表单数据
586 445 *
587 446 * @param request 请求参数
588 447 * @return 表单数据
589 448 */
590   - private MultiValueMap<String, Object> buildCreateFormData(CreatePptRequest request) {
  449 + private MultiValueMap<String, Object> buildCreatePptFormData(CreatePptRequest request) {
591 450 MultiValueMap<String, Object> formData = new LinkedMultiValueMap<>();
592   - // 添加请求参数
593   - if (request.query() != null) {
594   - formData.add("query", request.query());
595   - }
  451 +
596 452 if (request.file() != null) {
597 453 try {
598 454 formData.add("file", new ByteArrayResource(request.file().getBytes()) {
... ... @@ -602,48 +458,52 @@ public class XunFeiPptApi {
602 458 }
603 459 });
604 460 } catch (IOException e) {
605   - log.error("[xunfei-ppt-api] 文件处理失败", e);
606   - throw new IllegalStateException("[xunfei-ppt-api] 文件处理失败", e);
  461 + log.error("[XunFeiPptApi] 文件处理失败", e);
  462 + throw new IllegalStateException("[XunFeiPptApi] 文件处理失败", e);
607 463 }
608 464 }
609   - // TODO @新:要不搞个 MapUtil.addIfPresent 方法?
610   - if (request.fileUrl() != null) {
611   - formData.add("fileUrl", request.fileUrl());
612   - }
613   - if (request.fileName() != null) {
614   - formData.add("fileName", request.fileName());
615   - }
616   - if (request.templateId() != null) {
617   - formData.add("templateId", request.templateId());
618   - }
619   - if (request.businessId() != null) {
620   - formData.add("businessId", request.businessId());
621   - }
622   - if (request.author() != null) {
623   - formData.add("author", request.author());
624   - }
625   - if (request.isCardNote() != null) {
626   - formData.add("isCardNote", request.isCardNote().toString());
627   - }
628   - if (request.search() != null) {
629   - formData.add("search", request.search().toString());
630   - }
631   - if (request.language() != null) {
632   - formData.add("language", request.language());
  465 + Map<String, Object> param = new HashMap<>();
  466 + addIfPresent(param, "query", request.query());
  467 + addIfPresent(param, "fileUrl", request.fileUrl());
  468 + addIfPresent(param, "fileName", request.fileName());
  469 + addIfPresent(param, "templateId", request.templateId());
  470 + addIfPresent(param, "businessId", request.businessId());
  471 + addIfPresent(param, "author", request.author());
  472 + addIfPresent(param, "isCardNote", request.isCardNote());
  473 + addIfPresent(param, "search", request.search());
  474 + addIfPresent(param, "language", request.language());
  475 + addIfPresent(param, "isFigure", request.isFigure());
  476 + addIfPresent(param, "aiImage", request.aiImage());
  477 + param.forEach(formData::add);
  478 + return formData;
  479 + }
  480 +
  481 + public static <K, V> void addIfPresent(Map<K, V> map, K key, V value) {
  482 + if (ObjUtil.isNull(key) || ObjUtil.isNull(map)) {
  483 + return;
633 484 }
634   - if (request.isFigure() != null) {
635   - formData.add("isFigure", request.isFigure().toString());
  485 +
  486 + boolean isPresent = false;
  487 + if (ObjUtil.isNotNull(value)) {
  488 + if (value instanceof String) {
  489 + // 字符串:需要有实际内容
  490 + isPresent = StringUtils.hasText((String) value);
  491 + } else {
  492 + // 其他类型:非 null 即视为存在
  493 + isPresent = true;
  494 + }
636 495 }
637   - if (request.aiImage() != null) {
638   - formData.add("aiImage", request.aiImage());
  496 +
  497 + if (isPresent) {
  498 + map.put(key, value);
639 499 }
640   - return formData;
641 500 }
642 501  
643 502 /**
644 503 * 直接生成PPT请求参数
645 504 */
646 505 @JsonInclude(value = JsonInclude.Include.NON_NULL)
  506 + @Builder
647 507 public record CreatePptRequest(
648 508 String query, // 用户生成PPT要求(最多8000字)
649 509 MultipartFile file, // 上传文件
... ... @@ -659,109 +519,6 @@ public class XunFeiPptApi {
659 519 String aiImage // ai配图类型:normal、advanced
660 520 ) {
661 521  
662   - /**
663   - * 创建构建器
664   - *
665   - * @return 构建器
666   - */
667   - public static Builder builder() {
668   - return new Builder();
669   - }
670   -
671   - /**
672   - * 构建器类
673   - */
674   - public static class Builder {
675   -
676   - private String query;
677   - private MultipartFile file;
678   - private String fileUrl;
679   - private String fileName;
680   - private String templateId;
681   - private String businessId;
682   - private String author;
683   - private Boolean isCardNote;
684   - private Boolean search;
685   - private String language;
686   - private Boolean isFigure;
687   - private String aiImage;
688   -
689   - // TODO @新:这个可以用 lombok 简化么?
690   -
691   - public Builder query(String query) {
692   - this.query = query;
693   - return this;
694   - }
695   -
696   - public Builder file(MultipartFile file) {
697   - this.file = file;
698   - return this;
699   - }
700   -
701   - public Builder fileUrl(String fileUrl) {
702   - this.fileUrl = fileUrl;
703   - return this;
704   - }
705   -
706   - public Builder fileName(String fileName) {
707   - this.fileName = fileName;
708   - return this;
709   - }
710   -
711   - public Builder templateId(String templateId) {
712   - this.templateId = templateId;
713   - return this;
714   - }
715   -
716   - public Builder businessId(String businessId) {
717   - this.businessId = businessId;
718   - return this;
719   - }
720   -
721   - public Builder author(String author) {
722   - this.author = author;
723   - return this;
724   - }
725   -
726   - public Builder isCardNote(Boolean isCardNote) {
727   - this.isCardNote = isCardNote;
728   - return this;
729   - }
730   -
731   - public Builder search(Boolean search) {
732   - this.search = search;
733   - return this;
734   - }
735   -
736   - public Builder language(String language) {
737   - this.language = language;
738   - return this;
739   - }
740   -
741   - public Builder isFigure(Boolean isFigure) {
742   - this.isFigure = isFigure;
743   - return this;
744   - }
745   -
746   - public Builder aiImage(String aiImage) {
747   - this.aiImage = aiImage;
748   - return this;
749   - }
750   -
751   - public CreatePptRequest build() {
752   - // 验证参数
753   - if (query == null && file == null && fileUrl == null) {
754   - throw new IllegalArgumentException("query、file、fileUrl必填其一");
755   - }
756   - if ((file != null || fileUrl != null) && fileName == null) {
757   - throw new IllegalArgumentException("如果传file或者fileUrl,fileName必填");
758   - }
759   - return new CreatePptRequest(
760   - query, file, fileUrl, fileName, templateId, businessId, author,
761   - isCardNote, search, language, isFigure, aiImage
762   - );
763   - }
764   - }
765 522 }
766 523  
767 524 }
768 525 \ No newline at end of file
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/ppt/wdd/WenDuoDuoPptApiTests.java
... ... @@ -16,10 +16,14 @@ import java.util.Objects;
16 16 */
17 17 public class WenDuoDuoPptApiTests {
18 18  
19   - private final WenDuoDuoPptApi wenDuoDuoPptApi = new WenDuoDuoPptApi("https://docmee.cn");
  19 + private final WenDuoDuoPptApi wenDuoDuoPptApi;
20 20  
21 21 private final String token = ""; // API Token
22 22  
  23 + {
  24 + wenDuoDuoPptApi = new WenDuoDuoPptApi(token);
  25 + }
  26 +
23 27 @Test
24 28 @Disabled
25 29 public void testCreateApiToken() {
... ... @@ -38,7 +42,7 @@ public class WenDuoDuoPptApiTests {
38 42 @Test
39 43 @Disabled
40 44 public void testCreateTask() {
41   - WenDuoDuoPptApi.ApiResponse apiResponse = wenDuoDuoPptApi.createTask(token, 1, "dify 介绍", null);
  45 + WenDuoDuoPptApi.ApiResponse apiResponse = wenDuoDuoPptApi.createTask(1, "dify 介绍", null);
42 46 System.out.println(apiResponse);
43 47 }
44 48  
... ... @@ -49,7 +53,7 @@ public class WenDuoDuoPptApiTests {
49 53 WenDuoDuoPptApi.CreateOutlineRequest request = new WenDuoDuoPptApi.CreateOutlineRequest(
50 54 "1901539019628613632", "medium", null, null, null, null);
51 55 // 调用
52   - Flux<Map<String, Object>> flux = wenDuoDuoPptApi.createOutline(token, request);
  56 + Flux<Map<String, Object>> flux = wenDuoDuoPptApi.createOutline(request);
53 57 StringBuffer contentBuffer = new StringBuffer();
54 58 flux.doOnNext(chunk -> {
55 59 contentBuffer.append(chunk.get("text"));
... ... @@ -72,7 +76,7 @@ public class WenDuoDuoPptApiTests {
72 76 WenDuoDuoPptApi.UpdateOutlineRequest request = new WenDuoDuoPptApi.UpdateOutlineRequest(
73 77 "1901539019628613632", TEST_OUT_LINE_CONTENT, "精简一点,三个章节即可");
74 78 // 调用
75   - Flux<Map<String, Object>> flux = wenDuoDuoPptApi.updateOutline(token, request);
  79 + Flux<Map<String, Object>> flux = wenDuoDuoPptApi.updateOutline(request);
76 80 StringBuffer contentBuffer = new StringBuffer();
77 81 flux.doOnNext(chunk -> {
78 82 contentBuffer.append(chunk.get("text"));
... ... @@ -98,7 +102,7 @@ public class WenDuoDuoPptApiTests {
98 102 1, null, null, null);
99 103 WenDuoDuoPptApi.TemplateQueryRequest request = new WenDuoDuoPptApi.TemplateQueryRequest(1, 10, filter);
100 104 // 调用
101   - WenDuoDuoPptApi.PagePptTemplateInfo pptTemplatePage = wenDuoDuoPptApi.getTemplatePage(token, request);
  105 + WenDuoDuoPptApi.PagePptTemplateInfo pptTemplatePage = wenDuoDuoPptApi.getTemplatePage(request);
102 106 // 打印结果
103 107 System.out.println(pptTemplatePage);
104 108 }
... ... @@ -110,9 +114,9 @@ public class WenDuoDuoPptApiTests {
110 114 @Disabled
111 115 public void testGeneratePptx() {
112 116 // 准备参数
113   - WenDuoDuoPptApi.CreatePptRequest request = new WenDuoDuoPptApi.CreatePptRequest("1901539019628613632", "1805081814809960448", TEST_OUT_LINE_CONTENT);
  117 + WenDuoDuoPptApi.PptCreateRequest request = new WenDuoDuoPptApi.PptCreateRequest("1901539019628613632", "1805081814809960448", TEST_OUT_LINE_CONTENT);
114 118 // 调用
115   - WenDuoDuoPptApi.PptInfo pptInfo = wenDuoDuoPptApi.create(token, request);
  119 + WenDuoDuoPptApi.PptInfo pptInfo = wenDuoDuoPptApi.create(request);
116 120 // 打印结果
117 121 System.out.println(pptInfo);
118 122 }
... ... @@ -309,6 +313,7 @@ public class WenDuoDuoPptApiTests {
309 313 #### 7.2.2 合作共赢
310 314 期待与更多的企业和机构合作,共同推动AI技术的应用。
311 315 #### 7.2.3 共创未来
312   - 让我们一起用AI技术改变世界,共创美好未来。""";
  316 + 让我们一起用AI技术改变世界,共创美好未来。
  317 + """;
313 318  
314 319 }
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/ppt/xunfei/XunFeiPptApiTests.java
... ... @@ -18,10 +18,10 @@ import java.io.File;
18 18 public class XunFeiPptApiTests {
19 19  
20 20 // 讯飞 API 配置信息,实际使用时请替换为您的应用信息
21   - private static final String APP_ID = "";
22   - private static final String API_SECRET = "";
  21 + private static final String APP_ID = "6c8ac023";
  22 + private static final String API_SECRET = "Y2RjM2Q1MWJjZTdkYmFiODc0OGE5NmRk";
23 23  
24   - private final XunFeiPptApi xunfeiPptApi = new XunFeiPptApi(XunFeiPptApi.BASE_URL, APP_ID, API_SECRET);
  24 + private final XunFeiPptApi xunfeiPptApi = new XunFeiPptApi(APP_ID, API_SECRET);
25 25  
26 26 /**
27 27 * 获取 PPT 模板列表
... ... @@ -75,6 +75,7 @@ public class XunFeiPptApiTests {
75 75  
76 76 /**
77 77 * 创建大纲(通过文本)
  78 + *
78 79 * @return 创建大纲响应
79 80 */
80 81 private XunFeiPptApi.CreateResponse getCreateResponse() {
... ... @@ -160,7 +161,7 @@ public class XunFeiPptApiTests {
160 161 @Disabled
161 162 public void testPollCheckProgress() throws InterruptedException {
162 163 // 准备参数 - 使用之前创建 PP T时返回的 sid
163   - String sid = "fa36e926f2ed434987fcb4c1f0776ffb"; // 替换为实际的sid
  164 + String sid = "1690ef6ee0344e72b5c5434f403b8eaa"; // 替换为实际的sid
164 165  
165 166 // 最大轮询次数
166 167 int maxPolls = 20;
... ...