From a2a75c91b752535ca848f90ba32a11cef9e63282 Mon Sep 17 00:00:00 2001 From: HuangHai <10402852@qq.com> Date: Fri, 13 Jun 2025 16:08:55 +0800 Subject: [PATCH] 'commit' --- .../com/dsideal/base/Tools/AiGenerate.java | 95 ++- .../com/dsideal/base/Tools/AiPptDemo1.java | 57 ++ .../dsideal/base/Tools/Util/HttpUtils.java | 665 ++++++++++++++++++ .../com/dsideal/base/Tools/Util/PptAIKit.java | 292 ++++++++ ...源对比分析报告_20250613_154039.docx | Bin 3870 -> 0 bytes ...源对比分析报告_20250613_154215.docx | Bin 3721 -> 0 bytes 6 files changed, 1103 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/dsideal/base/Tools/AiPptDemo1.java create mode 100644 src/main/java/com/dsideal/base/Tools/Util/HttpUtils.java create mode 100644 src/main/java/com/dsideal/base/Tools/Util/PptAIKit.java delete mode 100644 教育资源对比分析报告_20250613_154039.docx delete mode 100644 教育资源对比分析报告_20250613_154215.docx diff --git a/src/main/java/com/dsideal/base/Tools/AiGenerate.java b/src/main/java/com/dsideal/base/Tools/AiGenerate.java index 3ba0f1f9..eb49dead 100644 --- a/src/main/java/com/dsideal/base/Tools/AiGenerate.java +++ b/src/main/java/com/dsideal/base/Tools/AiGenerate.java @@ -1,10 +1,13 @@ package com.dsideal.base.Tools; +import com.alibaba.fastjson.JSONObject; import com.dsideal.base.BaseApplication; import com.dsideal.base.DataEase.Model.DataEaseModel; import com.dsideal.base.Plugin.YamlProp; import com.dsideal.base.Tools.Util.CallDeepSeek; import com.dsideal.base.Tools.Util.LocalMysqlConnectUtil; +import com.dsideal.base.Tools.Util.PptAIKit; +import com.dsideal.base.Tools.Util.HttpUtils; import com.jfinal.plugin.activerecord.Db; import com.jfinal.plugin.activerecord.Record; import cn.hutool.json.JSONUtil; @@ -13,17 +16,16 @@ import org.apache.poi.xwpf.usermodel.XWPFParagraph; import org.apache.poi.xwpf.usermodel.XWPFRun; import org.apache.poi.xwpf.usermodel.ParagraphAlignment; +import java.io.File; import java.io.FileOutputStream; -import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; -public class AiGenerate { +public class AiGenerate extends PptAIKit { public static DataEaseModel dm = new DataEaseModel(); public static void main(String[] args) { - //加载配置文件 String configFile = "application.yaml"; BaseApplication.PropKit = new YamlProp(configFile); @@ -33,7 +35,7 @@ public class AiGenerate { String sql = "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'dataease' AND TABLE_NAME LIKE 'excel_报告-教育资源配置发展预测%';"; List tableList = Db.find(sql); - String[] biJiao = new String[]{"文山州", "昆明市"}; + String[] biJiao = new String[]{"文山州", "昆明市"}; StringBuilder dataContent = new StringBuilder(); // 收集两个州的数据 @@ -62,6 +64,9 @@ public class AiGenerate { // 构建分析提示词 String analysisPrompt = createAnalysisPrompt(dataContent.toString()); + + // 构建PPT生成提示词 + String pptPrompt = createPptPrompt(dataContent.toString(), biJiao); // 生成输出文件路径 String timestamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); @@ -82,12 +87,18 @@ public class AiGenerate { @Override public void onComplete(String response) { - System.out.println("\n\n=== 分析完成 ==="); + System.out.println("\n\n=== Word文档分析完成 ==="); try { // 使用Apache POI生成Word文档 generateWordDocument(fullResponse.toString(), outputPath, biJiao); - System.out.println("分析报告已保存到: " + outputPath); + System.out.println("Word分析报告已保存到: " + outputPath); + + // 生成PPT + // 生成PPT + System.out.println("\n\n=== 开始生成PPT ==="); + generatePptPresentation(biJiao, fullResponse.toString()); + } catch (Exception e) { System.err.println("保存文件时出错: " + e.getMessage()); e.printStackTrace(); @@ -101,6 +112,78 @@ public class AiGenerate { }); } + /** + * 创建PPT生成提示词 + */ + private static String createPptPrompt(String dataContent, String[] cities) { + return "请基于以下教育资源配置数据,为" + String.join("与", cities) + + "教育资源配置对比分析创建一个专业的PPT大纲。\n\n" + + "PPT应包含以下主要部分:\n" + + "1. 封面 - 标题和基本信息\n" + + "2. 目录 - 分析框架\n" + + "3. 数据概览 - 关键指标对比\n" + + "4. 详细分析 - 各维度深入对比\n" + + "5. 问题识别 - 存在的主要问题\n" + + "6. 建议方案 - 改进措施\n" + + "7. 总结 - 结论和展望\n\n" + + "请确保内容简洁明了,适合PPT展示。\n\n" + + "=== 原始数据 ===\n" + dataContent; + } + + /** + * 生成PPT演示文稿 + */ + private static void generatePptPresentation(String[] cities, String analysisContent) { + try { + // 第三方用户ID(数据隔离) + String uid = "education_analysis_" + System.currentTimeMillis(); + String subject = String.join("与", cities) + "教育资源配置对比分析"; + + // 创建 api token + String apiToken = PptAIKit.createApiToken(uid, null); + System.out.println("PPT API token: " + apiToken); + + // 生成大纲 + System.out.println("\n\n========== 正在生成PPT大纲 =========="); + String outline = PptAIKit.generateOutline(apiToken, subject, null, null); + System.out.println("PPT大纲生成完成"); + + // 生成大纲内容 + System.out.println("\n\n========== 正在生成PPT内容 =========="); + String markdown = PptAIKit.generateContent(apiToken, outline, null, null); + System.out.println("PPT内容生成完成"); + + // 随机选择一个模板 + System.out.println("\n\n========== 选择PPT模板 =========="); + String templateId = PptAIKit.randomOneTemplateId(apiToken); + System.out.println("选择的模板ID: " + templateId); + + // 生成PPT + System.out.println("\n\n========== 正在生成PPT文件 =========="); + JSONObject pptInfo = PptAIKit.generatePptx(apiToken, templateId, markdown, false); + String pptId = pptInfo.getString("id"); + System.out.println("PPT ID: " + pptId); + System.out.println("PPT主题:" + pptInfo.getString("subject")); + System.out.println("PPT封面:" + pptInfo.getString("coverUrl") + "?token=" + apiToken); + + // 下载PPT到当前目录 + System.out.println("\n\n========== 正在下载PPT =========="); + JSONObject result = PptAIKit.downloadPptx(apiToken, pptId); + String url = result.getString("fileUrl"); + System.out.println("PPT下载链接:" + url); + + String timestamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); + // 修改这里:使用完整路径 + String savePath = System.getProperty("user.dir") + File.separator + "教育资源对比分析PPT_" + timestamp + ".pptx"; + HttpUtils.download(url, new File(savePath)); + System.out.println("PPT下载完成,保存路径:" + savePath); + + } catch (Exception e) { + System.err.println("生成PPT时出错: " + e.getMessage()); + e.printStackTrace(); + } + } + /** * 创建分析提示词 */ diff --git a/src/main/java/com/dsideal/base/Tools/AiPptDemo1.java b/src/main/java/com/dsideal/base/Tools/AiPptDemo1.java new file mode 100644 index 00000000..f0a1b681 --- /dev/null +++ b/src/main/java/com/dsideal/base/Tools/AiPptDemo1.java @@ -0,0 +1,57 @@ +package com.dsideal.base.Tools; + +import com.alibaba.fastjson.JSONObject; +import com.dsideal.base.Tools.Util.HttpUtils; +import com.dsideal.base.Tools.Util.PptAIKit; + +import javax.swing.filechooser.FileSystemView; +import java.io.File; + +/** + * 同步流式生成 PPT + * + * @author veasion + * @date 2024/7/12 + */ +public class AiPptDemo1 extends PptAIKit { + + public static void main(String[] args) throws Exception { + // 第三方用户ID(数据隔离) + String uid = "test"; + String subject = "AI未来的发展"; + + // 创建 api token (有效期2小时,建议缓存到redis,同一个 uid 创建时之前的 token 会在10秒内失效) + String apiToken = PptAIKit.createApiToken(uid, null); + System.out.println("api token: " + apiToken); + + // 生成大纲 + System.out.println("\n\n========== 正在生成大纲 =========="); + String outline = PptAIKit.generateOutline(apiToken, subject, null, null); + + // 生成大纲内容 + System.out.println("\n\n========== 正在生成大纲内容 =========="); + String markdown = PptAIKit.generateContent(apiToken, outline, null, null); + + // 随机一个模板 + System.out.println("\n\n========== 随机选择模板 =========="); + String templateId = PptAIKit.randomOneTemplateId(apiToken); + System.out.println(templateId); + + // 生成PPT + System.out.println("\n\n========== 正在生成PPT =========="); + JSONObject pptInfo = PptAIKit.generatePptx(apiToken, templateId, markdown, false); + String pptId = pptInfo.getString("id"); + System.out.println("pptId: " + pptId); + System.out.println("ppt主题:" + pptInfo.getString("subject")); + System.out.println("ppt封面:" + pptInfo.getString("coverUrl") + "?token=" + apiToken); + + // 下载PPT到桌面 + System.out.println("\n\n========== 正在下载PPT =========="); + JSONObject result = PptAIKit.downloadPptx(apiToken, pptId); + String url = result.getString("fileUrl"); + System.out.println("ppt链接:" + url); + String savePath = FileSystemView.getFileSystemView().getHomeDirectory().getAbsolutePath() + File.separator + pptId + ".pptx"; + HttpUtils.download(url, new File(savePath)); + System.out.println("ppt下载完成,保存路径:" + savePath); + } +} diff --git a/src/main/java/com/dsideal/base/Tools/Util/HttpUtils.java b/src/main/java/com/dsideal/base/Tools/Util/HttpUtils.java new file mode 100644 index 00000000..a162eeea --- /dev/null +++ b/src/main/java/com/dsideal/base/Tools/Util/HttpUtils.java @@ -0,0 +1,665 @@ +package com.dsideal.base.Tools.Util; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import org.apache.commons.io.IOUtils; +import org.apache.http.Header; +import org.apache.http.HttpEntity; +import org.apache.http.HttpEntityEnclosingRequest; +import org.apache.http.client.HttpClient; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.HttpDelete; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPatch; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpPut; +import org.apache.http.client.methods.HttpRequestBase; +import org.apache.http.config.Registry; +import org.apache.http.config.RegistryBuilder; +import org.apache.http.conn.socket.ConnectionSocketFactory; +import org.apache.http.conn.socket.PlainConnectionSocketFactory; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.entity.ByteArrayEntity; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.FileEntity; +import org.apache.http.entity.InputStreamEntity; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.X509TrustManager; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Serializable; +import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; +import java.net.SocketException; +import java.net.URL; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.security.SecureRandom; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * HttpUtils + * + * @author veasion + * @date 2021/9/13 + */ +public class HttpUtils { + + public static final String CHARSET_DEFAULT = "UTF-8"; + public static final String CONTENT_TYPE = "Content-Type"; + public static final String CONTENT_TYPE_JSON = "application/json"; + public static final String CONTENT_TYPE_FORM_DATA = "application/x-www-form-urlencoded"; + private static final PoolingHttpClientConnectionManager CONNECTION_MANAGER; + private static final int MAX_CONNECT_TIMEOUT = 8000; + private static final int MAX_SOCKET_TIMEOUT = 90000; + + static { + CONNECTION_MANAGER = new PoolingHttpClientConnectionManager(getDefaultRegistry()); + CONNECTION_MANAGER.setMaxTotal(500); + CONNECTION_MANAGER.setDefaultMaxPerRoute(50); + CONNECTION_MANAGER.setValidateAfterInactivity(2000); + } + + public static HttpResponse get(String url) { + return get(url, null); + } + + public static HttpResponse get(String url, Map params) { + String urlLinks = getUrlLinks(params); + if (urlLinks != null) { + if (url.contains("?")) { + url = url + "&" + urlLinks; + } else { + url = url + "?" + urlLinks; + } + } + return request(HttpRequest.build(url, "GET")); + } + + public static void download(String url, File destFile) throws Exception { + HttpRequest request = HttpRequest.build(url, "GET"); + request.setResponseHandler(entity -> { + try { + if (!destFile.getParentFile().exists()) { + destFile.getParentFile().mkdirs(); + } + try (FileOutputStream fs = new FileOutputStream(destFile)) { + entity.writeTo(fs); + } + return destFile; + } catch (Exception e) { + return e; + } + }); + HttpResponse response = request(request); + if (response.getResponse() instanceof Exception) { + throw (Exception) response.getResponse(); + } + } + + public static HttpResponse postJson(String url, String bodyJson) { + HttpRequest request = HttpRequest.build(url, "POST").setBody(bodyJson); + if (request.getHeaders() == null) { + request.setHeaders(Collections.singletonMap(CONTENT_TYPE, CONTENT_TYPE_JSON)); + } else { + request.getHeaders().put(CONTENT_TYPE, CONTENT_TYPE_JSON); + } + return request(request); + } + + public static HttpResponse postForm(String url, Map params) { + String urlLinks = getUrlLinks(params); + HttpRequest request = HttpRequest.build(url, "POST").setBody(urlLinks != null ? urlLinks : ""); + if (request.getHeaders() == null) { + request.setHeaders(Collections.singletonMap(CONTENT_TYPE, CONTENT_TYPE_FORM_DATA)); + } else { + request.getHeaders().put(CONTENT_TYPE, CONTENT_TYPE_FORM_DATA); + } + return request(request); + } + + public static HttpResponse requestWithEventStream(ExecutorService executorService, long firstReadTimeout, HttpRequest request, Consumer dataConsumer) throws ExecutionException, InterruptedException, TimeoutException { + return requestWithEventStream(executorService, firstReadTimeout, request, dataConsumer, null); + } + + public static HttpResponse requestWithEventStream(ExecutorService executorService, long firstReadTimeout, HttpRequest request, Consumer dataConsumer, Consumer> futureConsumer) throws ExecutionException, InterruptedException, TimeoutException { + // status: 0 start 1 run 2 timeout + AtomicInteger status = new AtomicInteger(0); + Future submit = executorService.submit(() -> { + if (request.getMaxSocketTimeout() == null) { + request.setMaxSocketTimeout(30_000); + } + request.setResponseHandler(entity -> { + StringBuilder sb = new StringBuilder(); + try { + try (InputStream is = entity.getContent()) { + try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) { + String line; + while ((line = bufferedReader.readLine()) != null) { + if (status.get() == 2) { + throw new TimeoutException(); + } + status.set(1); + sb.append(line).append("\n"); + if (line.startsWith("data:")) { + dataConsumer.accept(line.substring(line.startsWith("data: ") ? 6 : 5)); + } + } + } + } + } catch (Exception e) { + throw new RuntimeException("eventStream 请求异常", e); + } + return sb.toString(); + }); + return request(request); + }); + if (futureConsumer != null) { + futureConsumer.accept(submit); + } + try { + HttpResponse httpResponse = submit.get(firstReadTimeout, TimeUnit.MILLISECONDS); + if (httpResponse != null) { + return httpResponse; + } + } catch (TimeoutException e) { + if (status.get() == 0) { + status.set(2); + submit.cancel(true); + throw e; + } + } + return submit.get(); + } + + public static HttpResponse requestWithEventStream(HttpRequest request, Consumer dataConsumer) { + if (request.getMaxSocketTimeout() == null) { + request.setMaxSocketTimeout(30_000); + } + request.setResponseHandler(entity -> { + StringBuilder sb = new StringBuilder(); + try { + try (InputStream is = entity.getContent()) { + try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) { + String line; + while ((line = bufferedReader.readLine()) != null) { + sb.append(line).append("\n"); + if (line.startsWith("data:")) { + dataConsumer.accept(line.substring(line.startsWith("data: ") ? 6 : 5)); + } + } + } + } + } catch (Exception e) { + throw new RuntimeException("eventStream 请求异常", e); + } + return sb.toString(); + }); + return request(request); + } + + public static int getUrlHttpStatus(String _url) { + HttpURLConnection urlConnection = null; + try { + URL url = new URL(_url); + urlConnection = (HttpURLConnection) url.openConnection(); + urlConnection.connect(); + return urlConnection.getResponseCode(); + } catch (Exception ignored) { + return -1; + } finally { + if (urlConnection != null) { + urlConnection.disconnect(); + } + } + } + + public static String getUrlLinks(Map params) { + if (params == null || params.isEmpty()) { + return null; + } + StringBuilder sb = new StringBuilder(); + try { + String[] sortedKeys = params.keySet().toArray(new String[0]); + Arrays.sort(sortedKeys); + for (String key : sortedKeys) { + if (key == null || key.isEmpty()) { + continue; + } + Object value = params.get(key); + sb.append(key).append("="); + if (value != null) { + sb.append(URLEncoder.encode(value.toString(), "UTF-8")); + } + sb.append("&"); + } + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + if (sb.length() > 0) { + sb.setLength(sb.length() - 1); + } + return sb.toString(); + } + + @SuppressWarnings("unchecked") + public static Map parseUrlLinks(String params) { + Map result = new LinkedHashMap<>(); + if (params == null || params.isEmpty()) { + return result; + } + for (String param : params.split("&")) { + String[] split = param.split("="); + String key = split[0]; + String value = ""; + if (split.length > 1) { + value = split[1]; + } + if (result.containsKey(key)) { + Object o = result.get(key); + if (o instanceof List) { + ((List) o).add(value); + } else { + List list = new ArrayList<>(); + list.add(o); + list.add(value); + result.put(key, list); + } + } else { + result.put(key, value); + } + } + return result; + } + + /** + * 通用接口请求 + */ + public static HttpResponse request(HttpRequest request) { + return request(request, 0); + } + + private static HttpResponse request(HttpRequest request, int retryCount) { + HttpRequestBase requestBase = toRequest(request); + Map headers = request.getHeaders(); + ContentType contentType = null; + if (headers != null && !headers.isEmpty()) { + for (Map.Entry entry : headers.entrySet()) { + String key = entry.getKey(); + if (key == null || key.isEmpty()) { + continue; + } + String value = entry.getValue(); + if (CONTENT_TYPE.equalsIgnoreCase(key) && value != null) { + contentType = ContentType.parse(value); + } + requestBase.setHeader(key, value); + } + } + + // body + setBodyEntity(requestBase, contentType, request.getBody()); + + try { + HttpClient client = getHttpClient(request, requestBase); + org.apache.http.HttpResponse response; + long startTime = System.currentTimeMillis(); + response = client.execute(requestBase); + HttpResponse httpResponse = new HttpResponse(); + httpResponse.setReqTime(System.currentTimeMillis() - startTime); + httpResponse.setStatus(response.getStatusLine().getStatusCode()); + Header[] allHeaders = response.getAllHeaders(); + if (allHeaders != null && allHeaders.length > 0) { + httpResponse.setHeaders(new HashMap<>()); + for (Header header : allHeaders) { + httpResponse.getHeaders().put(header.getName(), header.getValue()); + } + } + HttpEntity entity = response.getEntity(); + if (request.responseHandler != null) { + httpResponse.setResponse(request.responseHandler.apply(entity)); + } else { + String charset = null; + if (entity.getContentType() != null && entity.getContentType().getValue() != null) { + contentType = ContentType.parse(entity.getContentType().getValue()); + if (contentType.getCharset() != null) { + charset = contentType.getCharset().name(); + } + } + if (charset == null) { + charset = CHARSET_DEFAULT; + } + httpResponse.setResponse(IOUtils.toString(entity.getContent(), charset)); + } + return httpResponse; + } catch (Exception e) { + requestBase.abort(); + if (request.getMaxRetryCount() > retryCount) { + return request(request, retryCount + 1); + } else if (e instanceof SocketException && "Connection reset".equals(e.getMessage()) && retryCount == 0 && request.getMaxRetryCount() == 0) { + // 遇到 Connection reset 默认重试一次 + return request(request, retryCount + 1); + } else { + throw new RuntimeException("请求异常", e); + } + } finally { + requestBase.releaseConnection(); + } + } + + private static void setBodyEntity(HttpRequestBase requestBase, ContentType contentType, Object body) { + if (body != null && requestBase instanceof HttpEntityEnclosingRequest) { + HttpEntityEnclosingRequest entityRequest = (HttpEntityEnclosingRequest) requestBase; + if (body instanceof HttpEntity) { + entityRequest.setEntity((HttpEntity) body); + } else if (body instanceof String) { + entityRequest.setEntity(getStringEntity((String) body, contentType)); + } else if (body instanceof byte[]) { + entityRequest.setEntity(new ByteArrayEntity((byte[]) body, contentType)); + } else if (body instanceof File) { + entityRequest.setEntity(new FileEntity((File) body, contentType)); + } else if (body instanceof InputStream) { + entityRequest.setEntity(new InputStreamEntity((InputStream) body, contentType)); + } else if (ContentType.APPLICATION_JSON.equals(contentType)) { + entityRequest.setEntity(getStringEntity(JSON.toJSONString(body), contentType)); + } else { + entityRequest.setEntity(getStringEntity(body.toString(), contentType)); + } + } + } + + private static StringEntity getStringEntity(String body, ContentType contentType) { + if (contentType != null && contentType.getCharset() != null) { + return new StringEntity(body, contentType); + } else { + return new StringEntity(body, CHARSET_DEFAULT); + } + } + + public static HttpRequestBase toRequest(HttpRequest request) { + String url = request.getUrl(); + String method = request.getMethod(); + if (url == null || url.isEmpty()) { + throw new RuntimeException("url不能为空"); + } + if (method == null || method.isEmpty()) { + method = "GET"; + } + switch (method.toUpperCase()) { + case "GET": + return new HttpGet(url); + case "POST": + return new HttpPost(url); + case "PUT": + return new HttpPut(url); + case "PATCH": + return new HttpPatch(url); + case "DELETE": + return new HttpDelete(url); + default: + throw new RuntimeException("不支持的请求方式:" + method); + } + } + + private static HttpClient getHttpClient(HttpRequest req, HttpRequestBase request) { + RequestConfig.Builder customReqConf = RequestConfig.custom(); + if (req.getMaxSocketTimeout() != null) { + customReqConf.setSocketTimeout(req.getMaxSocketTimeout()); + } else { + customReqConf.setSocketTimeout(MAX_SOCKET_TIMEOUT); + } + customReqConf.setConnectTimeout(MAX_CONNECT_TIMEOUT); + customReqConf.setConnectionRequestTimeout(MAX_CONNECT_TIMEOUT); + if (req.getRequestConfigConsumer() != null) { + req.getRequestConfigConsumer().accept(customReqConf); + } + request.setConfig(customReqConf.build()); + return HttpClients.custom().setConnectionManager(CONNECTION_MANAGER).build(); + } + + private static Registry getDefaultRegistry() { + try { + // ssl: TLS / TLSv1.2 / TLSv1.3 + SSLContext context = SSLContext.getInstance("TLS"); + context.init(null, new X509TrustManager[]{new X509TrustManager() { + public void checkClientTrusted(X509Certificate[] x509Certificates, String s) { + } + + public void checkServerTrusted(X509Certificate[] x509Certificates, String s) { + } + + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + }}, new SecureRandom()); + return RegistryBuilder.create() + .register("http", PlainConnectionSocketFactory.getSocketFactory()) + .register("https", new SSLConnectionSocketFactory(context)) + .build(); + } catch (Exception e) { + return RegistryBuilder.create() + .register("http", PlainConnectionSocketFactory.getSocketFactory()) + .register("https", SSLConnectionSocketFactory.getSocketFactory()) + .build(); + } + } + + public static String toParamLinks(Map params, boolean encode) { + List keys = new ArrayList<>(params.keySet()); + Collections.sort(keys); + StringBuilder sb = new StringBuilder(); + try { + for (String key : keys) { + if (key == null || "".equals(key)) { + continue; + } + Object value = params.get(key); + if (value == null || "".equals(value)) { + continue; + } + if (encode) { + sb.append(key).append("=").append(URLEncoder.encode(value.toString(), "UTF-8")).append("&"); + } else { + sb.append(key).append("=").append(value).append("&"); + } + } + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("编码失败", e); + } + if (sb.length() > 0) { + sb.setLength(sb.length() - 1); + } + return sb.toString(); + } + + public static class HttpRequest implements Serializable { + + private String url; + private String method; + private Map headers; + private Object body; + private int maxRetryCount = 0; + private Integer maxSocketTimeout; + private Consumer requestConfigConsumer; + private Function responseHandler; + + public static HttpRequest build(String url, String method) { + HttpRequest request = new HttpRequest(); + request.url = url; + request.method = method; + return request; + } + + public static HttpRequest get(String url) { + return build(url, "GET"); + } + + public static HttpRequest postJson(String url) { + return build(url, "POST").setContentType(CONTENT_TYPE_JSON); + } + + public static HttpRequest postFormData(String url) { + return build(url, "POST").setContentType(CONTENT_TYPE_FORM_DATA); + } + + public String getUrl() { + return url; + } + + public HttpRequest setUrl(String url) { + this.url = url; + return this; + } + + public String getMethod() { + return method; + } + + public HttpRequest setMethod(String method) { + this.method = method; + return this; + } + + public Map getHeaders() { + return headers; + } + + public HttpRequest setHeaders(Map headers) { + this.headers = headers; + return this; + } + + public HttpRequest setContentType(String contentType) { + if (this.headers == null) { + this.headers = new HashMap<>(); + } + this.headers.put(CONTENT_TYPE, contentType); + return this; + } + + public HttpRequest addHeaders(String key, Object value) { + if (headers == null) { + headers = new HashMap<>(); + } + headers.put(key, value != null ? value.toString() : null); + return this; + } + + public Object getBody() { + return body; + } + + public HttpRequest setBody(Object body) { + this.body = body; + return this; + } + + public void setMaxRetryCount(int maxRetryCount) { + this.maxRetryCount = maxRetryCount; + } + + public int getMaxRetryCount() { + return maxRetryCount; + } + + public HttpRequest setMaxSocketTimeout(Integer maxSocketTimeout) { + this.maxSocketTimeout = maxSocketTimeout; + return this; + } + + public Integer getMaxSocketTimeout() { + return maxSocketTimeout; + } + + public Consumer getRequestConfigConsumer() { + return requestConfigConsumer; + } + + public void setRequestConfigConsumer(Consumer requestConfigConsumer) { + this.requestConfigConsumer = requestConfigConsumer; + } + + public Function getResponseHandler() { + return responseHandler; + } + + public HttpRequest setResponseHandler(Function responseHandler) { + this.responseHandler = responseHandler; + return this; + } + } + + public static class HttpResponse implements Serializable { + + private int status; + private long reqTime; + private Object response; + private Map headers; + + public int getStatus() { + return status; + } + + public void setStatus(int status) { + this.status = status; + } + + public long getReqTime() { + return reqTime; + } + + public void setReqTime(long reqTime) { + this.reqTime = reqTime; + } + + public Object getResponse() { + return response; + } + + public String getResponseToString() { + if (response == null) { + return null; + } + return response instanceof String ? (String) response : String.valueOf(response); + } + + public JSONObject getResponseToJson() { + return JSON.parseObject(getResponseToString()); + } + + public void setResponse(Object response) { + this.response = response; + } + + public Map getHeaders() { + return headers; + } + + public void setHeaders(Map headers) { + this.headers = headers; + } + } + +} \ No newline at end of file diff --git a/src/main/java/com/dsideal/base/Tools/Util/PptAIKit.java b/src/main/java/com/dsideal/base/Tools/Util/PptAIKit.java new file mode 100644 index 00000000..2265e5f0 --- /dev/null +++ b/src/main/java/com/dsideal/base/Tools/Util/PptAIKit.java @@ -0,0 +1,292 @@ +package com.dsideal.base.Tools.Util; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.mime.MultipartEntityBuilder; + +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * Api + * + * @author luozhuowei + * @date 2024/7/15 + */ +public class PptAIKit { + protected static String BASE_URL = "https://open.docmee.cn"; + protected static String apiKey = "ak_uKoKrRF63333E2lcBq"; + + public static String createApiToken(String uid, Integer limit) { + String url = BASE_URL + "/api/user/createApiToken"; + JSONObject body = new JSONObject(); + body.put("uid", uid); + body.put("limit", limit); + HttpUtils.HttpRequest httpRequest = HttpUtils.HttpRequest.postJson(url); + httpRequest.setBody(body.toJSONString()); + httpRequest.addHeaders("Api-Key", apiKey); + httpRequest.addHeaders("Content-Type", "application/json"); + HttpUtils.HttpResponse response = HttpUtils.request(httpRequest); + if (response.getStatus() != 200) { + System.out.println(response.getResponseToString()); + throw new RuntimeException("创建apiToken失败,httpStatus=" + response.getStatus()); + } + JSONObject result = response.getResponseToJson(); + if (result.getIntValue("code") != 0) { + throw new RuntimeException("创建apiToken异常," + result.getString("message")); + } + return result.getJSONObject("data").getString("token"); + } + + public static String parseFileData(String apiToken, File file, String content, String fileUrl) { + String url = BASE_URL + "/api/ppt/parseFileData"; + HttpUtils.HttpRequest httpRequest = new HttpUtils.HttpRequest(); + httpRequest.setUrl(url); + httpRequest.setMethod("POST"); + httpRequest.addHeaders("token", apiToken); + MultipartEntityBuilder multipartEntity = MultipartEntityBuilder.create(); + multipartEntity.setCharset(StandardCharsets.UTF_8); + if (file != null) { + multipartEntity.addBinaryBody("file", file); + } + if (content != null) { + multipartEntity.addTextBody("content", content, ContentType.create("text/plain", StandardCharsets.UTF_8)); + } + if (fileUrl != null) { + multipartEntity.addTextBody("fileUrl", fileUrl, ContentType.create("text/plain", StandardCharsets.UTF_8)); + } + httpRequest.setBody(multipartEntity.build()); + HttpUtils.HttpResponse response = HttpUtils.request(httpRequest); + if (response.getStatus() != 200) { + throw new RuntimeException("解析文件或内容失败,httpStatus=" + response.getStatus()); + } + JSONObject result = response.getResponseToJson(); + if (result.getIntValue("code") != 0) { + throw new RuntimeException("解析文件或内容异常," + result.getString("message")); + } + return result.getJSONObject("data").getString("dataUrl"); + } + + public static String generateOutline(String apiToken, String subject, String dataUrl, String prompt) { + String url = BASE_URL + "/api/ppt/generateOutline"; + JSONObject body = new JSONObject(); + body.put("subject", subject); + body.put("dataUrl", dataUrl); + body.put("prompt", prompt); + HttpUtils.HttpRequest httpRequest = HttpUtils.HttpRequest.postJson(url); + httpRequest.setBody(body.toJSONString()); + httpRequest.addHeaders("token", apiToken); + StringBuilder sb = new StringBuilder(); + HttpUtils.HttpResponse response = HttpUtils.requestWithEventStream(httpRequest, data -> { + if (data == null || data.isEmpty()) { + return; + } + JSONObject json = JSONObject.parseObject(data); + if (Objects.equals(json.getInteger("status"), -1)) { + throw new RuntimeException(json.getString("error")); + } + String text = json.getString("text"); + sb.append(text); + // 打印输出 + System.out.print(text); + }); + if (response.getStatus() != 200) { + throw new RuntimeException("生成大纲失败,httpStatus=" + response.getStatus()); + } + if (response.getHeaders().getOrDefault("Content-Type", response.getHeaders().get("content-type")).contains("application/json")) { + JSONObject result = response.getResponseToJson(); + throw new RuntimeException("生成大纲失败:" + result.getString("message")); + } + return sb.toString(); + } + + public static String generateContent(String apiToken, String outlineMarkdown, String dataUrl, String prompt) { + String url = BASE_URL + "/api/ppt/generateContent"; + JSONObject body = new JSONObject(); + body.put("outlineMarkdown", outlineMarkdown); + body.put("dataUrl", dataUrl); + body.put("prompt", prompt); + HttpUtils.HttpRequest httpRequest = HttpUtils.HttpRequest.postJson(url); + httpRequest.setBody(body.toJSONString()); + httpRequest.addHeaders("token", apiToken); + StringBuilder sb = new StringBuilder(); + HttpUtils.HttpResponse response = HttpUtils.requestWithEventStream(httpRequest, data -> { + if (data == null || data.isEmpty()) { + return; + } + JSONObject json = JSONObject.parseObject(data); + if (Objects.equals(json.getInteger("status"), -1)) { + throw new RuntimeException(json.getString("error")); + } + String text = json.getString("text"); + sb.append(text); + // 打印输出 + System.out.print(text); + }); + if (response.getStatus() != 200) { + throw new RuntimeException("生成大纲内容失败,httpStatus=" + response.getStatus()); + } + if (response.getHeaders().getOrDefault("Content-Type", response.getHeaders().get("content-type")).contains("application/json")) { + JSONObject result = response.getResponseToJson(); + throw new RuntimeException("生成大纲内容失败:" + result.getString("message")); + } + return sb.toString(); + } + + public static Map asyncGenerateContent(String apiToken, String outlineMarkdown, String dataUrl, String templateId, String prompt) { + String url = BASE_URL + "/api/ppt/generateContent"; + JSONObject body = new JSONObject(); + body.put("asyncGenPptx", true); + body.put("templateId", templateId); + body.put("outlineMarkdown", outlineMarkdown); + body.put("dataUrl", dataUrl); + body.put("prompt", prompt); + HttpUtils.HttpRequest httpRequest = HttpUtils.HttpRequest.postJson(url); + httpRequest.setBody(body.toJSONString()); + httpRequest.addHeaders("token", apiToken); + Map pptInfo = new HashMap<>(); + StringBuilder sb = new StringBuilder(); + HttpUtils.HttpResponse response = HttpUtils.requestWithEventStream(httpRequest, data -> { + if (data == null || data.isEmpty()) { + return; + } + JSONObject json = JSONObject.parseObject(data); + if (Objects.equals(json.getInteger("status"), -1)) { + throw new RuntimeException(json.getString("error")); + } + if (json.getString("pptId") != null) { + pptInfo.put("id", json.getString("pptId")); + } + String text = json.getString("text"); + sb.append(text); + // 打印输出 + System.out.print(text); + }); + if (response.getStatus() != 200) { + throw new RuntimeException("生成大纲内容失败,httpStatus=" + response.getStatus()); + } + if (response.getHeaders().getOrDefault("Content-Type", response.getHeaders().get("content-type")).contains("application/json")) { + JSONObject result = response.getResponseToJson(); + throw new RuntimeException("生成大纲内容失败:" + result.getString("message")); + } + pptInfo.put("markdown", sb.toString()); + return pptInfo; + } + + public static String randomOneTemplateId(String apiToken) { + String url = BASE_URL + "/api/ppt/randomTemplates"; + JSONObject body = new JSONObject(); + body.put("size", 1); + JSONObject filters = new JSONObject(); + filters.put("type", 1); + body.put("filters", filters); + HttpUtils.HttpRequest httpRequest = HttpUtils.HttpRequest.postJson(url); + httpRequest.setBody(body.toJSONString()); + httpRequest.addHeaders("token", apiToken); + HttpUtils.HttpResponse response = HttpUtils.request(httpRequest); + if (response.getStatus() != 200) { + throw new RuntimeException("获取模板失败,httpStatus=" + response.getStatus()); + } + JSONObject result = response.getResponseToJson(); + if (result.getIntValue("code") != 0) { + throw new RuntimeException("获取模板异常," + result.getString("message")); + } + JSONArray data = result.getJSONArray("data"); + JSONObject template = data.getJSONObject(0); + return template.getString("id"); + } + + public static JSONObject generatePptx(String apiToken, String templateId, String markdown, boolean pptxProperty) { + String url = BASE_URL + "/api/ppt/generatePptx"; + JSONObject body = new JSONObject(); + body.put("templateId", templateId); + body.put("outlineContentMarkdown", markdown); + body.put("pptxProperty", pptxProperty); + HttpUtils.HttpRequest httpRequest = HttpUtils.HttpRequest.postJson(url); + httpRequest.setBody(body.toJSONString()); + httpRequest.addHeaders("token", apiToken); + HttpUtils.HttpResponse response = HttpUtils.request(httpRequest); + if (response.getStatus() != 200) { + throw new RuntimeException("生成PPT失败,httpStatus=" + response.getStatus()); + } + JSONObject result = response.getResponseToJson(); + if (result.getIntValue("code") != 0) { + throw new RuntimeException("生成PPT异常," + result.getString("message")); + } + return result.getJSONObject("data").getJSONObject("pptInfo"); + } + + public static JSONObject downloadPptx(String apiToken, String id) { + String url = BASE_URL + "/api/ppt/downloadPptx"; + JSONObject body = new JSONObject(); + body.put("id", id); + HttpUtils.HttpRequest httpRequest = HttpUtils.HttpRequest.postJson(url); + httpRequest.setBody(body.toJSONString()); + httpRequest.addHeaders("token", apiToken); + HttpUtils.HttpResponse response = HttpUtils.request(httpRequest); + if (response.getStatus() != 200) { + throw new RuntimeException("下载PPT失败,httpStatus=" + response.getStatus()); + } + JSONObject result = response.getResponseToJson(); + if (result.getIntValue("code") != 0) { + throw new RuntimeException("下载PPT异常," + result.getString("message")); + } + return result.getJSONObject("data"); + } + + public static JSONObject directGeneratePptx(String apiToken, boolean stream, String templateId, String subject, String dataUrl, String prompt, boolean pptxProperty) { + String url = BASE_URL + "/api/ppt/directGeneratePptx"; + JSONObject body = new JSONObject(); + body.put("stream", stream); + body.put("templateId", templateId); + body.put("subject", subject); + body.put("dataUrl", dataUrl); + body.put("prompt", prompt); + body.put("pptxProperty", pptxProperty); + HttpUtils.HttpRequest httpRequest = HttpUtils.HttpRequest.postJson(url); + httpRequest.setBody(body.toJSONString()); + httpRequest.addHeaders("token", apiToken); + if (stream) { + // 流式生成 + JSONObject[] pptInfo = new JSONObject[1]; + HttpUtils.HttpResponse response = HttpUtils.requestWithEventStream(httpRequest, data -> { + if (data == null || data.isEmpty()) { + return; + } + JSONObject json = JSONObject.parseObject(data); + if (Objects.equals(json.getInteger("status"), -1)) { + throw new RuntimeException(json.getString("error")); + } + if (Objects.equals(json.getInteger("status"), 4) && json.containsKey("result")) { + pptInfo[0] = json.getJSONObject("result"); + } + String text = json.getString("text"); + // 打印输出 + System.out.print(text); + }); + if (response.getStatus() != 200) { + throw new RuntimeException("生成PPT失败,httpStatus=" + response.getStatus()); + } + if (response.getHeaders().getOrDefault("Content-Type", response.getHeaders().get("content-type")).contains("application/json")) { + JSONObject result = response.getResponseToJson(); + throw new RuntimeException("生成PPT失败:" + result.getString("message")); + } + return pptInfo[0]; + } else { + // 非流式生成 + HttpUtils.HttpResponse response = HttpUtils.request(httpRequest); + if (response.getStatus() != 200) { + throw new RuntimeException("生成PPT失败,httpStatus=" + response.getStatus()); + } + JSONObject result = response.getResponseToJson(); + if (result.getIntValue("code") != 0) { + throw new RuntimeException("生成PPT异常," + result.getString("message")); + } + return result.getJSONObject("data").getJSONObject("pptInfo"); + } + } +} diff --git a/教育资源对比分析报告_20250613_154039.docx b/教育资源对比分析报告_20250613_154039.docx deleted file mode 100644 index 967cfc64bf493dcde3ff4312e801a8c74ab7ee1d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3870 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 Zrqcw1>rj!-8vsB}x>`y2YMniK`xkcoXFUJ_ diff --git a/教育资源对比分析报告_20250613_154215.docx b/教育资源对比分析报告_20250613_154215.docx deleted file mode 100644 index dddcd01ee2ed7aee4d0ee44aab6e5625bd197f9d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3721 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 Wq)$!0ZvX%-`DrEN`;@#10QeW!w