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,6 +8,7 @@ import org.springframework.core.ParameterizedTypeReference;
8 import org.springframework.http.HttpRequest; 8 import org.springframework.http.HttpRequest;
9 import org.springframework.http.HttpStatusCode; 9 import org.springframework.http.HttpStatusCode;
10 import org.springframework.http.MediaType; 10 import org.springframework.http.MediaType;
  11 +import org.springframework.util.Assert;
11 import org.springframework.util.LinkedMultiValueMap; 12 import org.springframework.util.LinkedMultiValueMap;
12 import org.springframework.util.MultiValueMap; 13 import org.springframework.util.MultiValueMap;
13 import org.springframework.web.multipart.MultipartFile; 14 import org.springframework.web.multipart.MultipartFile;
@@ -35,6 +36,7 @@ import java.util.function.Predicate; @@ -35,6 +36,7 @@ import java.util.function.Predicate;
35 public class WenDuoDuoPptApi { 36 public class WenDuoDuoPptApi {
36 37
37 public static final String BASE_URL = "https://docmee.cn"; 38 public static final String BASE_URL = "https://docmee.cn";
  39 + public static final String TOKEN_NAME = "token";
38 40
39 private final WebClient webClient; 41 private final WebClient webClient;
40 42
@@ -43,17 +45,24 @@ public class WenDuoDuoPptApi { @@ -43,17 +45,24 @@ public class WenDuoDuoPptApi {
43 private final Function<Object, Function<ClientResponse, Mono<? extends Throwable>>> EXCEPTION_FUNCTION = 45 private final Function<Object, Function<ClientResponse, Mono<? extends Throwable>>> EXCEPTION_FUNCTION =
44 reqParam -> response -> response.bodyToMono(String.class).handle((responseBody, sink) -> { 46 reqParam -> response -> response.bodyToMono(String.class).handle((responseBody, sink) -> {
45 HttpRequest request = response.request(); 47 HttpRequest request = response.request();
46 - log.error("[wdd-api] 调用失败!请求方式:[{}],请求地址:[{}],请求参数:[{}],响应数据: [{}]", 48 + log.error("[WenDuoDuoPptApi] 调用失败!请求方式:[{}],请求地址:[{}],请求参数:[{}],响应数据: [{}]",
47 request.getMethod(), request.getURI(), reqParam, responseBody); 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 this.webClient = WebClient.builder() 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 .build(); 66 .build();
58 } 67 }
59 68
@@ -90,7 +99,7 @@ public class WenDuoDuoPptApi { @@ -90,7 +99,7 @@ public class WenDuoDuoPptApi {
90 * @return 任务ID 99 * @return 任务ID
91 * @see <a href="https://docmee.cn/open-platform/api#%E5%88%9B%E5%BB%BA%E4%BB%BB%E5%8A%A1">创建任务</a> 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 MultiValueMap<String, Object> formData = new LinkedMultiValueMap<>(); 103 MultiValueMap<String, Object> formData = new LinkedMultiValueMap<>();
95 formData.add("type", type); 104 formData.add("type", type);
96 if (content != null) { 105 if (content != null) {
@@ -103,7 +112,6 @@ public class WenDuoDuoPptApi { @@ -103,7 +112,6 @@ public class WenDuoDuoPptApi {
103 } 112 }
104 return this.webClient.post() 113 return this.webClient.post()
105 .uri("/api/ppt/v2/createTask") 114 .uri("/api/ppt/v2/createTask")
106 - .header("token", token)  
107 .contentType(MediaType.MULTIPART_FORM_DATA) 115 .contentType(MediaType.MULTIPART_FORM_DATA)
108 .body(BodyInserters.fromMultipartData(formData)) 116 .body(BodyInserters.fromMultipartData(formData))
109 .retrieve() 117 .retrieve()
@@ -146,10 +154,9 @@ public class WenDuoDuoPptApi { @@ -146,10 +154,9 @@ public class WenDuoDuoPptApi {
146 * @param request 请求体 154 * @param request 请求体
147 * @return 模板列表 155 * @return 模板列表
148 */ 156 */
149 - public PagePptTemplateInfo getTemplatePage(String token, TemplateQueryRequest request) { 157 + public PagePptTemplateInfo getTemplatePage(TemplateQueryRequest request) {
150 return this.webClient.post() 158 return this.webClient.post()
151 .uri("/api/ppt/templates") 159 .uri("/api/ppt/templates")
152 - .header("token", token)  
153 .bodyValue(request) 160 .bodyValue(request)
154 .retrieve() 161 .retrieve()
155 .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) 162 .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request))
@@ -163,10 +170,9 @@ public class WenDuoDuoPptApi { @@ -163,10 +170,9 @@ public class WenDuoDuoPptApi {
163 * 170 *
164 * @return 大纲内容流 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 return this.webClient.post() 174 return this.webClient.post()
168 .uri("/api/ppt/v2/generateContent") 175 .uri("/api/ppt/v2/generateContent")
169 - .header("token", token)  
170 .body(Mono.just(request), CreateOutlineRequest.class) 176 .body(Mono.just(request), CreateOutlineRequest.class)
171 .retrieve() 177 .retrieve()
172 .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) 178 .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request))
@@ -180,10 +186,9 @@ public class WenDuoDuoPptApi { @@ -180,10 +186,9 @@ public class WenDuoDuoPptApi {
180 * @param request 请求体 186 * @param request 请求体
181 * @return 大纲内容流 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 return this.webClient.post() 190 return this.webClient.post()
185 .uri("/api/ppt/v2/updateContent") 191 .uri("/api/ppt/v2/updateContent")
186 - .header("token", token)  
187 .body(Mono.just(request), UpdateOutlineRequest.class) 192 .body(Mono.just(request), UpdateOutlineRequest.class)
188 .retrieve() 193 .retrieve()
189 .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) 194 .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request))
@@ -196,11 +201,10 @@ public class WenDuoDuoPptApi { @@ -196,11 +201,10 @@ public class WenDuoDuoPptApi {
196 * 201 *
197 * @return PPT信息 202 * @return PPT信息
198 */ 203 */
199 - public PptInfo create(String token, CreatePptRequest request) { 204 + public PptInfo create(PptCreateRequest request) {
200 return this.webClient.post() 205 return this.webClient.post()
201 .uri("/api/ppt/v2/generatePptx") 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 .retrieve() 208 .retrieve()
205 .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) 209 .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request))
206 .bodyToMono(ApiResponse.class) 210 .bodyToMono(ApiResponse.class)
@@ -214,7 +218,9 @@ public class WenDuoDuoPptApi { @@ -214,7 +218,9 @@ public class WenDuoDuoPptApi {
214 .block(); 218 .block();
215 } 219 }
216 220
217 - 221 + /**
  222 + * 创建Token请求参数
  223 + */
218 @JsonInclude(value = JsonInclude.Include.NON_NULL) 224 @JsonInclude(value = JsonInclude.Include.NON_NULL)
219 public record CreateTokenRequest( 225 public record CreateTokenRequest(
220 String apiKey, 226 String apiKey,
@@ -276,18 +282,19 @@ public class WenDuoDuoPptApi { @@ -276,18 +282,19 @@ public class WenDuoDuoPptApi {
276 } 282 }
277 283
278 /** 284 /**
279 - * 生成 PPT 请求 285 + * 生成 PPT 请求参数
280 */ 286 */
281 - // TODO @新:要不按照 PptCreateRequest 这样的风格  
282 @JsonInclude(value = JsonInclude.Include.NON_NULL) 287 @JsonInclude(value = JsonInclude.Include.NON_NULL)
283 - public record CreatePptRequest( 288 + public record PptCreateRequest(
284 String id, 289 String id,
285 String templateId, 290 String templateId,
286 String markdown 291 String markdown
287 ) { 292 ) {
288 } 293 }
289 294
290 - // TODO @新:要不写下类注释 295 + /**
  296 + * PPT 信息
  297 + */
291 @JsonInclude(value = JsonInclude.Include.NON_NULL) 298 @JsonInclude(value = JsonInclude.Include.NON_NULL)
292 public record PptInfo( 299 public record PptInfo(
293 String id, 300 String id,
@@ -309,7 +316,9 @@ public class WenDuoDuoPptApi { @@ -309,7 +316,9 @@ public class WenDuoDuoPptApi {
309 ) { 316 ) {
310 } 317 }
311 318
312 - // TODO @新:要不写下类注释 319 + /**
  320 + * 模板查询请求参数
  321 + */
313 @JsonInclude(value = JsonInclude.Include.NON_NULL) 322 @JsonInclude(value = JsonInclude.Include.NON_NULL)
314 public record TemplateQueryRequest( 323 public record TemplateQueryRequest(
315 int page, 324 int page,
@@ -317,6 +326,9 @@ public class WenDuoDuoPptApi { @@ -317,6 +326,9 @@ public class WenDuoDuoPptApi {
317 Filter filters 326 Filter filters
318 ) { 327 ) {
319 328
  329 + /**
  330 + * 模板查询过滤条件
  331 + */
320 @JsonInclude(value = JsonInclude.Include.NON_NULL) 332 @JsonInclude(value = JsonInclude.Include.NON_NULL)
321 public record Filter( 333 public record Filter(
322 int type, 334 int type,
@@ -328,7 +340,9 @@ public class WenDuoDuoPptApi { @@ -328,7 +340,9 @@ public class WenDuoDuoPptApi {
328 340
329 } 341 }
330 342
331 - // TODO @新:要不写下类注释 343 + /**
  344 + * PPT模板分页信息
  345 + */
332 @JsonInclude(value = JsonInclude.Include.NON_NULL) 346 @JsonInclude(value = JsonInclude.Include.NON_NULL)
333 public record PagePptTemplateInfo( 347 public record PagePptTemplateInfo(
334 List<PptTemplateInfo> data, 348 List<PptTemplateInfo> data,
@@ -336,8 +350,9 @@ public class WenDuoDuoPptApi { @@ -336,8 +350,9 @@ public class WenDuoDuoPptApi {
336 ) { 350 ) {
337 } 351 }
338 352
339 -  
340 - // TODO @新:要不写下类注释 353 + /**
  354 + * PPT模板信息
  355 + */
341 @JsonInclude(value = JsonInclude.Include.NON_NULL) 356 @JsonInclude(value = JsonInclude.Include.NON_NULL)
342 public record PptTemplateInfo( 357 public record PptTemplateInfo(
343 String id, 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 package cn.iocoder.yudao.framework.ai.core.model.xinghuo.api; 1 package cn.iocoder.yudao.framework.ai.core.model.xinghuo.api;
2 2
3 import cn.hutool.core.util.ObjUtil; 3 import cn.hutool.core.util.ObjUtil;
  4 +import cn.hutool.crypto.SecureUtil;
  5 +import cn.hutool.crypto.digest.HmacAlgorithm;
4 import cn.iocoder.yudao.framework.common.util.json.JsonUtils; 6 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
5 import com.fasterxml.jackson.annotation.JsonInclude; 7 import com.fasterxml.jackson.annotation.JsonInclude;
  8 +import lombok.Builder;
6 import lombok.extern.slf4j.Slf4j; 9 import lombok.extern.slf4j.Slf4j;
7 import org.springframework.core.io.ByteArrayResource; 10 import org.springframework.core.io.ByteArrayResource;
8 import org.springframework.http.HttpStatusCode; 11 import org.springframework.http.HttpStatusCode;
9 import org.springframework.http.MediaType; 12 import org.springframework.http.MediaType;
10 import org.springframework.util.LinkedMultiValueMap; 13 import org.springframework.util.LinkedMultiValueMap;
11 import org.springframework.util.MultiValueMap; 14 import org.springframework.util.MultiValueMap;
  15 +import org.springframework.util.StringUtils;
12 import org.springframework.web.multipart.MultipartFile; 16 import org.springframework.web.multipart.MultipartFile;
13 import org.springframework.web.reactive.function.BodyInserters; 17 import org.springframework.web.reactive.function.BodyInserters;
14 import org.springframework.web.reactive.function.client.ClientResponse; 18 import org.springframework.web.reactive.function.client.ClientResponse;
15 import org.springframework.web.reactive.function.client.WebClient; 19 import org.springframework.web.reactive.function.client.WebClient;
16 import reactor.core.publisher.Mono; 20 import reactor.core.publisher.Mono;
17 21
18 -import javax.crypto.Mac;  
19 -import javax.crypto.spec.SecretKeySpec;  
20 import java.io.IOException; 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 import java.util.HashMap; 23 import java.util.HashMap;
27 import java.util.List; 24 import java.util.List;
28 import java.util.Map; 25 import java.util.Map;
29 import java.util.function.Function; 26 import java.util.function.Function;
30 import java.util.function.Predicate; 27 import java.util.function.Predicate;
31 28
32 -  
33 -  
34 /** 29 /**
35 * 讯飞智能 PPT 生成 API 30 * 讯飞智能 PPT 生成 API
36 * 31 *
@@ -41,6 +36,9 @@ import java.util.function.Predicate; @@ -41,6 +36,9 @@ import java.util.function.Predicate;
41 public class XunFeiPptApi { 36 public class XunFeiPptApi {
42 37
43 public static final String BASE_URL = "https://zwapi.xfyun.cn/api/ppt/v2"; 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 private final WebClient webClient; 43 private final WebClient webClient;
46 private final String appId; 44 private final String appId;
@@ -50,18 +48,21 @@ public class XunFeiPptApi { @@ -50,18 +48,21 @@ public class XunFeiPptApi {
50 48
51 private final Function<Object, Function<ClientResponse, Mono<? extends Throwable>>> EXCEPTION_FUNCTION = 49 private final Function<Object, Function<ClientResponse, Mono<? extends Throwable>>> EXCEPTION_FUNCTION =
52 reqParam -> response -> response.bodyToMono(String.class).handle((responseBody, sink) -> { 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 this.appId = appId; 56 this.appId = appId;
64 this.apiSecret = apiSecret; 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,7 +74,7 @@ public class XunFeiPptApi {
73 long timestamp = System.currentTimeMillis() / 1000; 74 long timestamp = System.currentTimeMillis() / 1000;
74 String ts = String.valueOf(timestamp); 75 String ts = String.valueOf(timestamp);
75 String signature = generateSignature(appId, apiSecret, timestamp); 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,43 +86,8 @@ public class XunFeiPptApi {
85 * @return 签名 86 * @return 签名
86 */ 87 */
87 private String generateSignature(String appId, String apiSecret, long timestamp) { 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,9 +104,8 @@ public class XunFeiPptApi {
138 requestBody.put("pageSize", ObjUtil.defaultIfNull(pageSize, 20)); 104 requestBody.put("pageSize", ObjUtil.defaultIfNull(pageSize, 20));
139 return this.webClient.post() 105 return this.webClient.post()
140 .uri("/template/list") 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 .contentType(MediaType.APPLICATION_JSON) 109 .contentType(MediaType.APPLICATION_JSON)
145 .bodyValue(requestBody) 110 .bodyValue(requestBody)
146 .retrieve() 111 .retrieve()
@@ -161,9 +126,8 @@ public class XunFeiPptApi { @@ -161,9 +126,8 @@ public class XunFeiPptApi {
161 formData.add("query", query); 126 formData.add("query", query);
162 return this.webClient.post() 127 return this.webClient.post()
163 .uri("/createOutline") 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 .contentType(MediaType.MULTIPART_FORM_DATA) 131 .contentType(MediaType.MULTIPART_FORM_DATA)
168 .body(BodyInserters.fromMultipartData(formData)) 132 .body(BodyInserters.fromMultipartData(formData))
169 .retrieve() 133 .retrieve()
@@ -207,12 +171,11 @@ public class XunFeiPptApi { @@ -207,12 +171,11 @@ public class XunFeiPptApi {
207 */ 171 */
208 public CreateResponse create(CreatePptRequest request) { 172 public CreateResponse create(CreatePptRequest request) {
209 SignatureInfo signInfo = getSignature(); 173 SignatureInfo signInfo = getSignature();
210 - MultiValueMap<String, Object> formData = buildCreateFormData(request); 174 + MultiValueMap<String, Object> formData = buildCreatePptFormData(request);
211 return this.webClient.post() 175 return this.webClient.post()
212 .uri("/create") 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 .contentType(MediaType.MULTIPART_FORM_DATA) 179 .contentType(MediaType.MULTIPART_FORM_DATA)
217 .body(BodyInserters.fromMultipartData(formData)) 180 .body(BodyInserters.fromMultipartData(formData))
218 .retrieve() 181 .retrieve()
@@ -247,9 +210,8 @@ public class XunFeiPptApi { @@ -247,9 +210,8 @@ public class XunFeiPptApi {
247 SignatureInfo signInfo = getSignature(); 210 SignatureInfo signInfo = getSignature();
248 return this.webClient.post() 211 return this.webClient.post()
249 .uri("/createPptByOutline") 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 .contentType(MediaType.APPLICATION_JSON) 215 .contentType(MediaType.APPLICATION_JSON)
254 .bodyValue(request) 216 .bodyValue(request)
255 .retrieve() 217 .retrieve()
@@ -271,9 +233,8 @@ public class XunFeiPptApi { @@ -271,9 +233,8 @@ public class XunFeiPptApi {
271 .path("/progress") 233 .path("/progress")
272 .queryParam("sid", sid) 234 .queryParam("sid", sid)
273 .build()) 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 .retrieve() 238 .retrieve()
278 .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(sid)) 239 .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(sid))
279 .bodyToMono(ProgressResponse.class) 240 .bodyToMono(ProgressResponse.class)
@@ -285,7 +246,6 @@ public class XunFeiPptApi { @@ -285,7 +246,6 @@ public class XunFeiPptApi {
285 */ 246 */
286 @JsonInclude(value = JsonInclude.Include.NON_NULL) 247 @JsonInclude(value = JsonInclude.Include.NON_NULL)
287 private record SignatureInfo( 248 private record SignatureInfo(
288 - String appId,  
289 String timestamp, 249 String timestamp,
290 String signature 250 String signature
291 ) { 251 ) {
@@ -461,6 +421,7 @@ public class XunFeiPptApi { @@ -461,6 +421,7 @@ public class XunFeiPptApi {
461 * 通过大纲创建 PPT 请求参数 421 * 通过大纲创建 PPT 请求参数
462 */ 422 */
463 @JsonInclude(value = JsonInclude.Include.NON_NULL) 423 @JsonInclude(value = JsonInclude.Include.NON_NULL)
  424 + @Builder
464 public record CreatePptByOutlineRequest( 425 public record CreatePptByOutlineRequest(
465 String query, // 用户生成PPT要求(最多8000字) 426 String query, // 用户生成PPT要求(最多8000字)
466 String outlineSid, // 已生成大纲后,响应返回的请求大纲唯一id 427 String outlineSid, // 已生成大纲后,响应返回的请求大纲唯一id
@@ -476,123 +437,18 @@ public class XunFeiPptApi { @@ -476,123 +437,18 @@ public class XunFeiPptApi {
476 Boolean isFigure, // 是否自动配图 437 Boolean isFigure, // 是否自动配图
477 String aiImage // ai配图类型:normal、advanced 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 * 构建创建 PPT 的表单数据 444 * 构建创建 PPT 的表单数据
586 * 445 *
587 * @param request 请求参数 446 * @param request 请求参数
588 * @return 表单数据 447 * @return 表单数据
589 */ 448 */
590 - private MultiValueMap<String, Object> buildCreateFormData(CreatePptRequest request) { 449 + private MultiValueMap<String, Object> buildCreatePptFormData(CreatePptRequest request) {
591 MultiValueMap<String, Object> formData = new LinkedMultiValueMap<>(); 450 MultiValueMap<String, Object> formData = new LinkedMultiValueMap<>();
592 - // 添加请求参数  
593 - if (request.query() != null) {  
594 - formData.add("query", request.query());  
595 - } 451 +
596 if (request.file() != null) { 452 if (request.file() != null) {
597 try { 453 try {
598 formData.add("file", new ByteArrayResource(request.file().getBytes()) { 454 formData.add("file", new ByteArrayResource(request.file().getBytes()) {
@@ -602,48 +458,52 @@ public class XunFeiPptApi { @@ -602,48 +458,52 @@ public class XunFeiPptApi {
602 } 458 }
603 }); 459 });
604 } catch (IOException e) { 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 * 直接生成PPT请求参数 503 * 直接生成PPT请求参数
645 */ 504 */
646 @JsonInclude(value = JsonInclude.Include.NON_NULL) 505 @JsonInclude(value = JsonInclude.Include.NON_NULL)
  506 + @Builder
647 public record CreatePptRequest( 507 public record CreatePptRequest(
648 String query, // 用户生成PPT要求(最多8000字) 508 String query, // 用户生成PPT要求(最多8000字)
649 MultipartFile file, // 上传文件 509 MultipartFile file, // 上传文件
@@ -659,109 +519,6 @@ public class XunFeiPptApi { @@ -659,109 +519,6 @@ public class XunFeiPptApi {
659 String aiImage // ai配图类型:normal、advanced 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 \ No newline at end of file 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,10 +16,14 @@ import java.util.Objects;
16 */ 16 */
17 public class WenDuoDuoPptApiTests { 17 public class WenDuoDuoPptApiTests {
18 18
19 - private final WenDuoDuoPptApi wenDuoDuoPptApi = new WenDuoDuoPptApi("https://docmee.cn"); 19 + private final WenDuoDuoPptApi wenDuoDuoPptApi;
20 20
21 private final String token = ""; // API Token 21 private final String token = ""; // API Token
22 22
  23 + {
  24 + wenDuoDuoPptApi = new WenDuoDuoPptApi(token);
  25 + }
  26 +
23 @Test 27 @Test
24 @Disabled 28 @Disabled
25 public void testCreateApiToken() { 29 public void testCreateApiToken() {
@@ -38,7 +42,7 @@ public class WenDuoDuoPptApiTests { @@ -38,7 +42,7 @@ public class WenDuoDuoPptApiTests {
38 @Test 42 @Test
39 @Disabled 43 @Disabled
40 public void testCreateTask() { 44 public void testCreateTask() {
41 - WenDuoDuoPptApi.ApiResponse apiResponse = wenDuoDuoPptApi.createTask(token, 1, "dify 介绍", null); 45 + WenDuoDuoPptApi.ApiResponse apiResponse = wenDuoDuoPptApi.createTask(1, "dify 介绍", null);
42 System.out.println(apiResponse); 46 System.out.println(apiResponse);
43 } 47 }
44 48
@@ -49,7 +53,7 @@ public class WenDuoDuoPptApiTests { @@ -49,7 +53,7 @@ public class WenDuoDuoPptApiTests {
49 WenDuoDuoPptApi.CreateOutlineRequest request = new WenDuoDuoPptApi.CreateOutlineRequest( 53 WenDuoDuoPptApi.CreateOutlineRequest request = new WenDuoDuoPptApi.CreateOutlineRequest(
50 "1901539019628613632", "medium", null, null, null, null); 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 StringBuffer contentBuffer = new StringBuffer(); 57 StringBuffer contentBuffer = new StringBuffer();
54 flux.doOnNext(chunk -> { 58 flux.doOnNext(chunk -> {
55 contentBuffer.append(chunk.get("text")); 59 contentBuffer.append(chunk.get("text"));
@@ -72,7 +76,7 @@ public class WenDuoDuoPptApiTests { @@ -72,7 +76,7 @@ public class WenDuoDuoPptApiTests {
72 WenDuoDuoPptApi.UpdateOutlineRequest request = new WenDuoDuoPptApi.UpdateOutlineRequest( 76 WenDuoDuoPptApi.UpdateOutlineRequest request = new WenDuoDuoPptApi.UpdateOutlineRequest(
73 "1901539019628613632", TEST_OUT_LINE_CONTENT, "精简一点,三个章节即可"); 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 StringBuffer contentBuffer = new StringBuffer(); 80 StringBuffer contentBuffer = new StringBuffer();
77 flux.doOnNext(chunk -> { 81 flux.doOnNext(chunk -> {
78 contentBuffer.append(chunk.get("text")); 82 contentBuffer.append(chunk.get("text"));
@@ -98,7 +102,7 @@ public class WenDuoDuoPptApiTests { @@ -98,7 +102,7 @@ public class WenDuoDuoPptApiTests {
98 1, null, null, null); 102 1, null, null, null);
99 WenDuoDuoPptApi.TemplateQueryRequest request = new WenDuoDuoPptApi.TemplateQueryRequest(1, 10, filter); 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 System.out.println(pptTemplatePage); 107 System.out.println(pptTemplatePage);
104 } 108 }
@@ -110,9 +114,9 @@ public class WenDuoDuoPptApiTests { @@ -110,9 +114,9 @@ public class WenDuoDuoPptApiTests {
110 @Disabled 114 @Disabled
111 public void testGeneratePptx() { 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 System.out.println(pptInfo); 121 System.out.println(pptInfo);
118 } 122 }
@@ -309,6 +313,7 @@ public class WenDuoDuoPptApiTests { @@ -309,6 +313,7 @@ public class WenDuoDuoPptApiTests {
309 #### 7.2.2 合作共赢 313 #### 7.2.2 合作共赢
310 期待与更多的企业和机构合作,共同推动AI技术的应用。 314 期待与更多的企业和机构合作,共同推动AI技术的应用。
311 #### 7.2.3 共创未来 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,10 +18,10 @@ import java.io.File;
18 public class XunFeiPptApiTests { 18 public class XunFeiPptApiTests {
19 19
20 // 讯飞 API 配置信息,实际使用时请替换为您的应用信息 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 * 获取 PPT 模板列表 27 * 获取 PPT 模板列表
@@ -75,6 +75,7 @@ public class XunFeiPptApiTests { @@ -75,6 +75,7 @@ public class XunFeiPptApiTests {
75 75
76 /** 76 /**
77 * 创建大纲(通过文本) 77 * 创建大纲(通过文本)
  78 + *
78 * @return 创建大纲响应 79 * @return 创建大纲响应
79 */ 80 */
80 private XunFeiPptApi.CreateResponse getCreateResponse() { 81 private XunFeiPptApi.CreateResponse getCreateResponse() {
@@ -160,7 +161,7 @@ public class XunFeiPptApiTests { @@ -160,7 +161,7 @@ public class XunFeiPptApiTests {
160 @Disabled 161 @Disabled
161 public void testPollCheckProgress() throws InterruptedException { 162 public void testPollCheckProgress() throws InterruptedException {
162 // 准备参数 - 使用之前创建 PP T时返回的 sid 163 // 准备参数 - 使用之前创建 PP T时返回的 sid
163 - String sid = "fa36e926f2ed434987fcb4c1f0776ffb"; // 替换为实际的sid 164 + String sid = "1690ef6ee0344e72b5c5434f403b8eaa"; // 替换为实际的sid
164 165
165 // 最大轮询次数 166 // 最大轮询次数
166 int maxPolls = 20; 167 int maxPolls = 20;