作者 徐宝林

工具类迁移

@@ -209,6 +209,31 @@ @@ -209,6 +209,31 @@
209 <optional>true</optional> 209 <optional>true</optional>
210 </dependency> 210 </dependency>
211 211
  212 + <!--okhttp-->
  213 + <dependency>
  214 + <groupId>com.squareup.okhttp3</groupId>
  215 + <artifactId>okhttp</artifactId>
  216 + <version>4.9.3</version>
  217 + </dependency>
  218 +
  219 + <dependency>
  220 + <groupId>org.apache.tika</groupId>
  221 + <artifactId>tika-core</artifactId>
  222 + <version>1.28.5</version>
  223 + </dependency>
  224 + <dependency>
  225 + <groupId>org.apache.tika</groupId>
  226 + <artifactId>tika-parsers</artifactId>
  227 + <version>1.28.5</version>
  228 + </dependency>
  229 +
  230 + <dependency>
  231 + <groupId>com.itextpdf</groupId>
  232 + <artifactId>itext7-core</artifactId>
  233 + <version>7.2.5</version>
  234 + <type>pom</type>
  235 + </dependency>
  236 +
212 <!-- Test --> 237 <!-- Test -->
213 <dependency> 238 <dependency>
214 <groupId>org.springframework.boot</groupId> 239 <groupId>org.springframework.boot</groupId>
  1 +package com.aigeo.article.controller;
  2 +
  3 +import com.aigeo.article.service.SearchService;
  4 +import com.aigeo.common.Result;
  5 +import io.swagger.v3.oas.annotations.tags.Tag;
  6 +import org.springframework.beans.factory.annotation.Autowired;
  7 +import org.springframework.web.bind.annotation.PostMapping;
  8 +import org.springframework.web.bind.annotation.RequestBody;
  9 +import org.springframework.web.bind.annotation.RequestMapping;
  10 +import org.springframework.web.bind.annotation.RestController;
  11 +
  12 +@RestController
  13 +@RequestMapping("/api/search")
  14 +@Tag(name = "搜索", description = "搜索功能相关接口")
  15 +public class SearchController {
  16 +
  17 + @Autowired
  18 + private SearchService searchService;
  19 +
  20 + @PostMapping("/keywords")
  21 + public Result<String> searchKeywords(@RequestBody String keywords) {
  22 + searchService.search(keywords);
  23 + return Result.success("");
  24 + }
  25 +}
  1 +package com.aigeo.article.service;
  2 +
  3 +import com.aigeo.common.Result;
  4 +
  5 +public interface SearchService {
  6 +
  7 + Result<String> search(String keyword);
  8 +
  9 +}
  1 +package com.aigeo.article.service.impl;
  2 +
  3 +import com.aigeo.article.service.SearchService;
  4 +import com.aigeo.common.Result;
  5 +import org.springframework.stereotype.Service;
  6 +
  7 +@Service
  8 +public class SearchServiceImpl implements SearchService {
  9 +
  10 + @Override
  11 + public Result<String> search(String keyword) {
  12 + return null;
  13 + }
  14 +}
@@ -30,34 +30,34 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { @@ -30,34 +30,34 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
30 protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) 30 protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
31 throws ServletException, IOException { 31 throws ServletException, IOException {
32 32
33 - final String requestTokenHeader = request.getHeader("Authorization");  
34 -  
35 - String username = null;  
36 - String jwtToken = null;  
37 -  
38 - if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) {  
39 - jwtToken = requestTokenHeader.substring(7);  
40 - try {  
41 - username = jwtUtil.getUsernameFromToken(jwtToken);  
42 - } catch (Exception e) {  
43 - logger.error("Unable to get JWT Token", e);  
44 - }  
45 - }  
46 -  
47 - if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {  
48 - UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);  
49 -  
50 - // 创建一个临时User对象用于验证token  
51 - com.aigeo.company.entity.User tempUser = new com.aigeo.company.entity.User();  
52 - tempUser.setUsername(username);  
53 -  
54 - if (jwtUtil.validateToken(jwtToken, tempUser)) {  
55 - UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(  
56 - userDetails, null, userDetails.getAuthorities());  
57 - authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));  
58 - SecurityContextHolder.getContext().setAuthentication(authToken);  
59 - }  
60 - } 33 +// final String requestTokenHeader = request.getHeader("Authorization");
  34 +//
  35 +// String username = null;
  36 +// String jwtToken = null;
  37 +//
  38 +// if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) {
  39 +// jwtToken = requestTokenHeader.substring(7);
  40 +// try {
  41 +// username = jwtUtil.getUsernameFromToken(jwtToken);
  42 +// } catch (Exception e) {
  43 +// logger.error("Unable to get JWT Token", e);
  44 +// }
  45 +// }
  46 +//
  47 +// if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
  48 +// UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
  49 +//
  50 +// // 创建一个临时User对象用于验证token
  51 +// com.aigeo.company.entity.User tempUser = new com.aigeo.company.entity.User();
  52 +// tempUser.setUsername(username);
  53 +//
  54 +// if (jwtUtil.validateToken(jwtToken, tempUser)) {
  55 +// UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
  56 +// userDetails, null, userDetails.getAuthorities());
  57 +// authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
  58 +// SecurityContextHolder.getContext().setAuthentication(authToken);
  59 +// }
  60 +// }
61 chain.doFilter(request, response); 61 chain.doFilter(request, response);
62 } 62 }
63 } 63 }
  1 +package com.aigeo.entity;
  2 +
  3 +import com.aigeo.util.article.PlatformTypeEnum;
  4 +import lombok.Data;
  5 +
  6 +@Data
  7 +public class CreateArticleDTO {
  8 +
  9 + private String blogId;
  10 + private String title;
  11 + private Author author;
  12 + private String handle;
  13 + private String body;
  14 + private Integer companyId;
  15 + private String summary;
  16 + private Boolean isPublished;
  17 + private String publishDate;
  18 + private String[] tags;
  19 + private String imageAltText;
  20 + private String imageUrl;
  21 +
  22 + private PlatformTypeEnum platform;
  23 +
  24 + @Data
  25 + public static class Author {
  26 + private String name;
  27 + }
  28 +}
  1 +package com.aigeo.entity;
  2 +
  3 +import lombok.Data;
  4 +
  5 +import java.util.List;
  6 +
  7 +@Data
  8 +public class DifyDatasetDocuments {
  9 + private Integer code;
  10 + private List<Document> data;
  11 + private Integer total;
  12 + private Integer page;
  13 + private Integer limit;
  14 + private Boolean has_more;
  15 +}
  1 +package com.aigeo.entity;
  2 +
  3 +import lombok.Data;
  4 +
  5 +@Data
  6 +public class DifyRequestDTO {
  7 + private Object inputs;
  8 + private String query;
  9 + private String response_mode;
  10 + private String conversation_id;
  11 + private String user;
  12 + private FileInfos[] files;
  13 +
  14 + @Data
  15 + public static class FileInfos{
  16 + private String type;
  17 + private String transfer_method;
  18 + private String url;
  19 + }
  20 +}
  1 +package com.aigeo.entity;
  2 +
  3 +import lombok.Data;
  4 +
  5 +@Data
  6 +public class DifyResponse {
  7 + /**
  8 + * 任务 ID,用于请求跟踪和下方的停止响应接口
  9 + */
  10 + private String task_id;
  11 + /**
  12 + * 消息唯一 ID
  13 + */
  14 + private String message_id;
  15 + /**
  16 + * 返回文本块内容
  17 + */
  18 + private String answer;
  19 + /**
  20 + * 返回文本块事件
  21 + */
  22 + private String event;
  23 + /**
  24 + * 会话 ID
  25 + */
  26 + private String conversation_id;
  27 + /**
  28 + * 创建时间戳
  29 + */
  30 + private Long created_at;
  31 + /**
  32 + * 每一轮Agent迭代都会有一个唯一的id
  33 + */
  34 + private String id;
  35 +
  36 + /**
  37 + * agent_thought在消息中的位置
  38 + */
  39 + private Integer position;
  40 +
  41 + /**
  42 + * agent的思考内容
  43 + */
  44 + private String thought;
  45 +
  46 + /**
  47 + * 工具调用的返回结果
  48 + */
  49 + private String observation;
  50 +
  51 + /**
  52 + * 使用的工具列表,以 ; 分割多个工具
  53 + */
  54 + private String tool;
  55 +
  56 + private Object tool_labels;
  57 +
  58 + /**
  59 + * 工具的输入,JSON格式的字符串
  60 + */
  61 + private String tool_input;
  62 +
  63 + /**
  64 + * 错误消息
  65 + */
  66 + private String message;
  67 +
  68 + /**
  69 + * 当前 agent_thought 关联的文件ID
  70 + */
  71 + private DifyRequestDTO.FileInfos message_files;
  72 +
  73 + private Integer code;
  74 +}
  1 +package com.aigeo.entity;
  2 +
  3 +import lombok.Data;
  4 +
  5 +import java.util.List;
  6 +
  7 +@Data
  8 +public class DifyUploadFileToDatasetDTO {
  9 + private String indexing_technique;
  10 +
  11 +
  12 + private PrecessRule process_rule;
  13 +
  14 + @Data
  15 + public static class PrecessRule {
  16 + private String mode;
  17 + private Rules rules;
  18 +
  19 + @Data
  20 + public static class Rules {
  21 + private List<PreProcessingRules> pre_processing_rules;
  22 + private Segmentation segmentation;
  23 +
  24 + @Data
  25 + public static class Segmentation {
  26 + private String separator;
  27 + private Integer max_tokens;
  28 + }
  29 +
  30 + @Data
  31 + public static class PreProcessingRules {
  32 + private String id;
  33 + private String enabled;
  34 + }
  35 + }
  36 + }
  37 +}
  1 +package com.aigeo.entity;
  2 +
  3 +import lombok.Data;
  4 +
  5 +@Data
  6 +public class Document {
  7 + private String id;
  8 + private String position;
  9 + private String data_source_type;
  10 + private String data_source_info;
  11 + private String dataset_process_rule_id;
  12 + private String name;
  13 + private String created_from;
  14 + private String created_by;
  15 + private String created_at;
  16 + private String tokens;
  17 + private String indexing_status;
  18 + private String error;
  19 + private String enabled;
  20 + private String disabled_at;
  21 + private String disabled_by;
  22 + private String archived;
  23 + private String display_status;
  24 + private String word_count;
  25 + private String hit_count;
  26 + private String doc_form;
  27 +}
  1 +package com.aigeo.entity;
  2 +
  3 +import lombok.Data;
  4 +
  5 +@Data
  6 +public class DocumentUploadRes {
  7 + private Document document;
  8 + private String batch;
  9 + private Integer code;
  10 +}
  1 +package com.aigeo.entity;
  2 +
  3 +import com.aigeo.util.article.PlatformTypeEnum;
  4 +import lombok.Data;
  5 +
  6 +@Data
  7 +public class UpdateArticleDTO {
  8 + private String id;
  9 + private String title;
  10 + private String handle;
  11 + private String body;
  12 + private String summary;
  13 + private String[] tags;
  14 + private String imageAltText;
  15 + private String imageUrl;
  16 + private Integer companyId;
  17 +
  18 + private PlatformTypeEnum platform;
  19 +}
  1 +package com.aigeo.socket;
  2 +
  3 +import com.alibaba.fastjson2.JSONException;
  4 +import com.alibaba.fastjson2.JSONObject;
  5 +import com.fasterxml.jackson.databind.ObjectMapper;
  6 +import jakarta.websocket.*;
  7 +import jakarta.websocket.server.ServerEndpoint;
  8 +import org.springframework.stereotype.Component;
  9 +
  10 +import java.io.IOException;
  11 +import java.util.concurrent.ConcurrentHashMap;
  12 +
  13 +@ServerEndpoint("/ws/chat/{userId}")
  14 +@Component
  15 +public class WebSocketAiServer {
  16 +
  17 +// public static AiSerivce aiSerivce;
  18 +
  19 +// TokenInfo tokenInfo = TokenInfo.getIntance();
  20 +
  21 + private static final ConcurrentHashMap<String, Session> sessionMap = new ConcurrentHashMap<>();
  22 +
  23 +
  24 + @OnOpen
  25 + public void onOpen(Session session) {
  26 + sessionMap.put(session.getId(),session);
  27 + System.out.println("New connection: " + session.getId());
  28 + }
  29 +
  30 + @OnClose
  31 + public void onClose(Session session) {
  32 + sessionMap.remove(session.getId());
  33 + System.out.println("Connection closed: " + session.getId());
  34 + }
  35 +
  36 + @OnMessage
  37 + public void onMessage(String message, Session session) {
  38 +// SocketDTO socketDTO = JSONObject.parseObject(message, SocketDTO.class);
  39 +// socketDTO.setId(session.getId());
  40 +// // 解析消息
  41 +// try {
  42 +// // 调用 reply 方法处理消息
  43 +// aiSerivce.chat(socketDTO);
  44 +// } catch (JSONException e) {
  45 +// e.printStackTrace();
  46 +// // 发送错误信息给客户端
  47 +// DifyResponse difyResponse = new DifyResponse();
  48 +// difyResponse.setCode(500);
  49 +// sendMessage(session.getId(), difyResponse);
  50 +// } catch (Exception e) {
  51 +// e.printStackTrace();
  52 +// // 发送错误信息给客户端
  53 +// DifyResponse difyResponse = new DifyResponse();
  54 +// difyResponse.setCode(500);
  55 +// sendMessage(session.getId(), difyResponse);
  56 +// }
  57 + }
  58 +
  59 + @OnError
  60 + public void onError(Session session, Throwable throwable) {
  61 + System.out.println("Error occurred: " + throwable.getMessage());
  62 + }
  63 +
  64 + public static void sendMessage(String sessionId, Object message) {
  65 + Session session = sessionMap.get(sessionId);
  66 + if (session != null && session.isOpen()) {
  67 + try {
  68 + ObjectMapper objectMapper = new ObjectMapper();
  69 + String jsonMessage;
  70 + try {
  71 + jsonMessage = objectMapper.writeValueAsString(message);
  72 + } catch (Exception e) {
  73 + e.printStackTrace();
  74 + jsonMessage = "Error: Failed to serialize message";
  75 + }
  76 + session.getBasicRemote().sendText(jsonMessage);
  77 + } catch (IOException e) {
  78 + e.printStackTrace();
  79 + }
  80 + }
  81 + }
  82 +
  83 + public static void sendMessageToAll(Object message) {
  84 + ObjectMapper objectMapper = new ObjectMapper();
  85 + String jsonMessage;
  86 + try {
  87 + jsonMessage = objectMapper.writeValueAsString(message);
  88 + } catch (Exception e) {
  89 + e.printStackTrace();
  90 + jsonMessage = "Error: Failed to serialize message";
  91 + }
  92 +
  93 + // 遍历 sessionMap 的所有 Session
  94 + for (Session session : sessionMap.values()) {
  95 + if (session.isOpen()) {
  96 + try {
  97 + session.getBasicRemote().sendText(jsonMessage);
  98 + } catch (IOException e) {
  99 + e.printStackTrace();
  100 + }
  101 + }
  102 + }
  103 + }
  104 +}
  105 +
  1 +package com.aigeo.util;
  2 +
  3 +import com.aigeo.entity.*;
  4 +import com.aigeo.socket.WebSocketAiServer;
  5 +import com.alibaba.fastjson2.JSON;
  6 +import com.alibaba.fastjson2.JSONObject;
  7 +import okhttp3.*;
  8 +import okio.BufferedSource;
  9 +import org.apache.tika.exception.TikaException;
  10 +import org.springframework.web.multipart.MultipartFile;
  11 +
  12 +import java.io.IOException;
  13 +import java.util.ArrayList;
  14 +import java.util.HashMap;
  15 +
  16 +/**
  17 + * <b>Dify AI工具类.</b>
  18 + * <p>
  19 + * 文件详情: 如需修改请联系开发人员<br>
  20 + * 版权所有: 深圳市科飞时速网络技术有限公司(0755-88843705)<br>
  21 + * 技术支持: info@21gmail.com<br>
  22 + * 开始时间: 2025/08/19 16:36<br>
  23 + * 最后修改: 2025/08/19 16:36<br>
  24 + *
  25 + * @author : 温志锋
  26 + * @version : 3.0
  27 + */
  28 +public class DifyAiUtil {
  29 +
  30 + //测试知识库ID:8b3c4cdd-01a3-453b-8e12-bc7c0d5268db
  31 + private static final String DIFY_BASE_URL="http://193.112.177.8:8088/v1";
  32 + private static final String API_KEY="app-sxppRrqW8OMd6Ak07iprjn2t";
  33 + private static final String DATASET_KEY="dataset-2nhuEkzhNshtvZ6L6Ut3G5hC";
  34 +
  35 + private static final String DIFY_CHAT_URL="/chat-messages";
  36 + private static final String DATASET_ENDPOINT="/datasets/";
  37 + private static final String DATASET_DOCUMENT_ENDPOINT="/document/create-by-text";
  38 + private static final String DATASET_UPLOADFILE_ENDPOINT="/document/create-by-file";
  39 +
  40 + private static final MediaType JSON_TYPE = MediaType.get("application/json; charset=utf-8");
  41 +
  42 + private static final OkHttpClient client = new OkHttpClient();
  43 +
  44 + /**
  45 + * 回复
  46 + * @param message 用户消息
  47 + * @param sessionId 会话ID
  48 + */
  49 + public static void reply(String message,String sessionId){
  50 + DifyRequestDTO difyRequestDTO = JSONObject.parseObject(message, DifyRequestDTO.class);
  51 + difyRequestDTO.setInputs(new Object());
  52 + difyRequestDTO.setUser("anhssd");
  53 + difyRequestDTO.setResponse_mode("streaming");
  54 +
  55 + String jsonBody = JSON.toJSONString(difyRequestDTO);
  56 +
  57 + // 创建请求体
  58 + RequestBody requestBody = RequestBody.create(
  59 + jsonBody,
  60 + JSON_TYPE
  61 + );
  62 +
  63 + // 创建请求
  64 + Request request = new Request.Builder()
  65 + .url(DIFY_BASE_URL+DIFY_CHAT_URL)
  66 + .addHeader("Authorization", "Bearer " + API_KEY)
  67 + .addHeader("Content-Type", "application/json")
  68 + .post(requestBody)
  69 + .build();
  70 +
  71 + // 发送请求并处理流式响应
  72 + client.newCall(request).enqueue(new Callback() {
  73 + @Override
  74 + public void onFailure(Call call, IOException e) {
  75 + e.printStackTrace();
  76 + }
  77 +
  78 + @Override
  79 + public void onResponse(Call call, Response response) throws IOException {
  80 + if (!response.isSuccessful()) {
  81 + System.err.println("请求失败: " + response.code() + " - " + response.body().string());
  82 + return;
  83 + }
  84 +
  85 + processStream(response,sessionId);
  86 + }
  87 + });
  88 + }
  89 +
  90 + /**
  91 + * 创建知识库
  92 + * @param datasetName 知识库名称
  93 + * @param description 知识库描述
  94 + */
  95 + public static void createDataset(String datasetName, String description) {
  96 + HashMap<String, Object> map = new HashMap<>();
  97 + map.put("name", datasetName);
  98 + map.put("description", description);
  99 + map.put("permission", "all_team_members");
  100 +
  101 + String jsonBody = JSON.toJSONString(map);
  102 +
  103 + RequestBody requestBody = RequestBody.create(jsonBody, JSON_TYPE);
  104 + // 创建请求
  105 + Request request = new Request.Builder()
  106 + .url(DIFY_BASE_URL + DATASET_ENDPOINT)
  107 + .addHeader("Authorization", "Bearer " + DATASET_KEY)
  108 + .addHeader("Content-Type", "application/json")
  109 + .post(requestBody)
  110 + .build();
  111 +
  112 + // 发送请求
  113 + client.newCall(request).enqueue(new Callback() {
  114 + @Override
  115 + public void onFailure(Call call, IOException e) {
  116 + e.printStackTrace();
  117 + }
  118 +
  119 + @Override
  120 + public void onResponse(Call call, Response response) throws IOException {
  121 + if (!response.isSuccessful()) {
  122 + System.err.println("请求失败: " + response.code() + " - " + response.body().string());
  123 + return;
  124 + }
  125 +
  126 + System.out.println("知识库创建成功: " + response.body().string());
  127 + }
  128 + });
  129 + }
  130 +
  131 + /**
  132 + * 获取知识库列表
  133 + */
  134 + public static void getDatasetList() {
  135 + // 创建请求
  136 + Request request = new Request.Builder()
  137 + .url(DIFY_BASE_URL + DATASET_ENDPOINT)
  138 + .addHeader("Authorization", "Bearer " + DATASET_KEY)
  139 + .build();
  140 +
  141 + try {
  142 + // 发送请求并获取响应
  143 + Response response = client.newCall(request).execute();
  144 +
  145 + if (!response.isSuccessful()) {
  146 + System.err.println("请求失败: " + response.code() + " - " + response.body().string());
  147 + return;
  148 + }
  149 +
  150 + if (response.body() != null) {
  151 + System.out.println("数据集列表: " + response.body().string());
  152 + }
  153 + } catch (IOException e) {
  154 + System.err.println("获取数据集列表失败: " + e.getMessage());
  155 + e.printStackTrace();
  156 + }
  157 + }
  158 +
  159 + /**
  160 + * 上传文件到知识库
  161 + * @param datasetId 知识库ID
  162 + * @param file 文件
  163 + * @param fileName 文件名
  164 + * @throws IOException IO异常
  165 + */
  166 + public static DocumentUploadRes uploadFileToDataset(String datasetId, MultipartFile file, String fileName) throws IOException, TikaException {
  167 + // 构建URL
  168 + String uploadUrl = DIFY_BASE_URL + DATASET_ENDPOINT + datasetId + DATASET_UPLOADFILE_ENDPOINT;
  169 + DifyUploadFileToDatasetDTO datasetDTO = new DifyUploadFileToDatasetDTO();
  170 + datasetDTO.setIndexing_technique("high_quality");
  171 + DifyUploadFileToDatasetDTO.PrecessRule precessRule = new DifyUploadFileToDatasetDTO.PrecessRule();
  172 + precessRule.setMode("custom");
  173 + DifyUploadFileToDatasetDTO.PrecessRule.Rules rules = new DifyUploadFileToDatasetDTO.PrecessRule.Rules();
  174 + DifyUploadFileToDatasetDTO.PrecessRule.Rules.Segmentation segmentation = new DifyUploadFileToDatasetDTO.PrecessRule.Rules.Segmentation();
  175 + segmentation.setSeparator("###");
  176 + segmentation.setMax_tokens(500);
  177 + DifyUploadFileToDatasetDTO.PrecessRule.Rules.PreProcessingRules processingRules = new DifyUploadFileToDatasetDTO.PrecessRule.Rules.PreProcessingRules();
  178 + processingRules.setId("remove_extra_spaces");
  179 + processingRules.setEnabled("true");
  180 + DifyUploadFileToDatasetDTO.PrecessRule.Rules.PreProcessingRules processingRules1 = new DifyUploadFileToDatasetDTO.PrecessRule.Rules.PreProcessingRules();
  181 + processingRules1.setId("remove_urls_emails");
  182 + processingRules1.setEnabled("true");
  183 + ArrayList<DifyUploadFileToDatasetDTO.PrecessRule.Rules.PreProcessingRules> list = new ArrayList<>();
  184 + list.add(processingRules);
  185 + list.add(processingRules1);
  186 + rules.setPre_processing_rules(list);
  187 + rules.setSegmentation(segmentation);
  188 + precessRule.setRules(rules);
  189 + datasetDTO.setProcess_rule(precessRule);
  190 + String jsonString = JSONObject.toJSONString(datasetDTO);
  191 + //System.out.println("Request JSON: " + jsonString);
  192 + // 关键修改:使用正确的媒体类型 application/json
  193 + RequestBody dataBody = RequestBody.create(
  194 + jsonString,
  195 + MediaType.parse("text/plain")
  196 + );
  197 +
  198 + RequestBody requestBody = new MultipartBody.Builder()
  199 + .setType(MultipartBody.FORM)
  200 + // 修改点:使用 application/json 类型传递JSON数据
  201 + .addFormDataPart("data", null, dataBody)
  202 + .addFormDataPart("file", fileName,
  203 + RequestBody.create(file.getBytes(), MediaType.parse("application/octet-stream")))
  204 + .build();
  205 + // 创建请求
  206 + Request request = new Request.Builder()
  207 + .url(uploadUrl)
  208 + .addHeader("Authorization", "Bearer " + DATASET_KEY)
  209 + .post(requestBody)
  210 + .build();
  211 +
  212 + try (Response response = client.newCall(request).execute()) {
  213 + if (!response.isSuccessful()) {
  214 + String errorBody = response.body() != null ? response.body().string() : "null";
  215 + System.err.println("上传文件请求失败: " + response.code() + " - " + errorBody);
  216 + DocumentUploadRes documentUploadRes = new DocumentUploadRes();
  217 + documentUploadRes.setCode(response.code());
  218 + return documentUploadRes;
  219 + }
  220 + if (response.body() != null) {
  221 + String responseBody = response.body().string();
  222 + return JSONObject.parseObject(responseBody, DocumentUploadRes.class); // 直接返回结果
  223 + } else {
  224 + DocumentUploadRes documentUploadRes = new DocumentUploadRes();
  225 + documentUploadRes.setCode(400);
  226 + return documentUploadRes;
  227 + }
  228 + }
  229 + }
  230 +
  231 + /**
  232 + * 上传知识库内容并创建文档至知识库
  233 + * @param datasetId 知识库ID
  234 + * @param documentName 文档名称
  235 + * @param text 文本内容
  236 + */
  237 + public static void createDocumentByText(String datasetId, String documentName, String text) throws IOException {
  238 + // 构建URL
  239 + String url = DIFY_BASE_URL + DATASET_ENDPOINT + datasetId + DATASET_DOCUMENT_ENDPOINT;
  240 +
  241 + // 构建请求数据
  242 + JSONObject requestData = new JSONObject();
  243 + requestData.put("name", documentName);
  244 + requestData.put("text", text);
  245 + requestData.put("indexing_technique", "high_quality");
  246 +
  247 + // 构建处理规则
  248 + JSONObject processRule = new JSONObject();
  249 + processRule.put("mode", "automatic");
  250 + requestData.put("process_rule", processRule);
  251 +
  252 + String jsonBody = requestData.toJSONString();
  253 +
  254 + // 创建请求体
  255 + RequestBody requestBody = RequestBody.create(jsonBody, JSON_TYPE);
  256 +
  257 + // 创建请求
  258 + Request request = new Request.Builder()
  259 + .url(url)
  260 + .addHeader("Authorization", "Bearer " + DATASET_KEY)
  261 + .addHeader("Content-Type", "application/json")
  262 + .post(requestBody)
  263 + .build();
  264 +
  265 + // 发送请求
  266 + client.newCall(request).enqueue(new Callback() {
  267 + @Override
  268 + public void onFailure(Call call, IOException e) {
  269 + System.err.println("创建文档失败: " + e.getMessage());
  270 + e.printStackTrace();
  271 + }
  272 +
  273 + @Override
  274 + public void onResponse(Call call, Response response) throws IOException {
  275 + if (!response.isSuccessful()) {
  276 + String errorBody = response.body() != null ? response.body().string() : "null";
  277 + System.err.println("创建文档请求失败: " + response.code() + " - " + errorBody);
  278 + return;
  279 + }
  280 +
  281 + if (response.body() != null) {
  282 + System.out.println("文档创建成功: " + response.body().string());
  283 + }
  284 + }
  285 + });
  286 + }
  287 +
  288 + /**
  289 + * 获取指定数据集中特定文档的详细信息
  290 + * @param datasetId 数据集ID
  291 + * @param documentId 文档ID
  292 + * @throws IOException IO异常
  293 + */
  294 + public static void getDocumentById(String datasetId, String documentId) throws IOException {
  295 + // 构建URL
  296 + String url = DIFY_BASE_URL + DATASET_ENDPOINT + datasetId + "/documents/" + documentId;
  297 +
  298 + // 创建请求
  299 + Request request = new Request.Builder()
  300 + .url(url)
  301 + .addHeader("Authorization", "Bearer " + DATASET_KEY)
  302 + .get() // 明确指定GET方法
  303 + .build();
  304 +
  305 + // 发送请求
  306 + client.newCall(request).enqueue(new Callback() {
  307 + @Override
  308 + public void onFailure(Call call, IOException e) {
  309 + System.err.println("获取文档失败: " + e.getMessage());
  310 + e.printStackTrace();
  311 + }
  312 +
  313 + @Override
  314 + public void onResponse(Call call, Response response) throws IOException {
  315 + if (!response.isSuccessful()) {
  316 + String errorBody = response.body() != null ? response.body().string() : "null";
  317 + System.err.println("获取文档请求失败: " + response.code() + " - " + errorBody);
  318 + return;
  319 + }
  320 +
  321 + if (response.body() != null) {
  322 + System.out.println("文档详情: " + response.body().string());
  323 + }
  324 + }
  325 + });
  326 + }
  327 +
  328 + /**
  329 + * 获取指定知识库中的文档列表
  330 + * @param datasetId 数据集ID
  331 + * @param page 页码(从1开始)
  332 + * @param limit 每页返回条数
  333 + * @param keyword 搜索关键词
  334 + * @return 响应内容
  335 + * @throws IOException IO异常
  336 + */
  337 + public static DifyDatasetDocuments getDatasetDocuments(String datasetId, Integer page, Integer limit, String keyword) throws IOException {
  338 + // 构建基础URL
  339 + String url = DIFY_BASE_URL + DATASET_ENDPOINT + datasetId + "/documents";
  340 +
  341 + // 构建查询参数
  342 + HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder();
  343 +
  344 + // 添加可选参数
  345 + if (page != null && page > 0) {
  346 + urlBuilder.addQueryParameter("page", String.valueOf(page));
  347 + }
  348 +
  349 + if (limit != null && limit > 0) {
  350 + urlBuilder.addQueryParameter("limit", String.valueOf(limit));
  351 + }
  352 +
  353 + if (keyword != null && !keyword.trim().isEmpty()) {
  354 + urlBuilder.addQueryParameter("keyword", keyword);
  355 + }
  356 +
  357 + // 构建最终URL
  358 + String finalUrl = urlBuilder.build().toString();
  359 +
  360 + // 创建请求
  361 + Request request = new Request.Builder()
  362 + .url(finalUrl)
  363 + .addHeader("Authorization", "Bearer " + DATASET_KEY)
  364 + .build();
  365 +
  366 + // 发送请求并处理响应
  367 + try (Response response = client.newCall(request).execute()) {
  368 + if (!response.isSuccessful()) {
  369 + DifyDatasetDocuments documents = new DifyDatasetDocuments();
  370 + documents.setCode(500);
  371 + return documents;
  372 + }
  373 +
  374 + if (response.body() != null) {
  375 + DifyDatasetDocuments documents = JSONObject.parseObject(response.body().string(), DifyDatasetDocuments.class);
  376 + documents.setCode(200);
  377 + return documents;
  378 + } else {
  379 + DifyDatasetDocuments documents = new DifyDatasetDocuments();
  380 + documents.setCode(500);
  381 + return documents;
  382 + }
  383 + } catch (IOException e) {
  384 + System.err.println("获取文档列表失败: " + e.getMessage());
  385 + throw e;
  386 + }
  387 + }
  388 +
  389 + /**
  390 + * 同步方式删除指定数据集中的特定文档
  391 + * @param datasetId 数据集ID
  392 + * @param documentId 文档ID
  393 + * @return 删除操作结果
  394 + * @throws IOException IO异常
  395 + */
  396 + public static boolean deleteDocumentSync(String datasetId, String documentId) throws IOException {
  397 + // 构建URL
  398 + String url = DIFY_BASE_URL + DATASET_ENDPOINT + datasetId + "/documents/" + documentId;
  399 +
  400 + // 创建DELETE请求
  401 + Request request = new Request.Builder()
  402 + .url(url)
  403 + .addHeader("Authorization", "Bearer " + DATASET_KEY)
  404 + .delete()
  405 + .build();
  406 +
  407 + // 发送请求并处理响应
  408 + try (Response response = client.newCall(request).execute()) {
  409 + if (!response.isSuccessful()) {
  410 + String errorBody = response.body() != null ? response.body().string() : "null";
  411 + System.err.println("删除文档请求失败: " + response.code() + " - " + errorBody);
  412 + return false;
  413 + }
  414 +
  415 + System.out.println("文档删除成功: " + response.code());
  416 + return true;
  417 + } catch (IOException e) {
  418 + System.err.println("删除文档失败: " + e.getMessage());
  419 + throw e;
  420 + }
  421 + }
  422 +
  423 +
  424 + private static void processStream(Response response,String sessionId) throws IOException {
  425 + try (BufferedSource source = response.body().source()) {
  426 + StringBuilder eventBuffer = new StringBuilder();
  427 +
  428 + while (!source.exhausted()) {
  429 + String line = source.readUtf8Line();
  430 + if (line == null) break;
  431 +
  432 + if (line.startsWith("data:")) {
  433 + eventBuffer.setLength(0);
  434 + eventBuffer.append(line.substring(5).trim());
  435 + } else if (line.isEmpty() && eventBuffer.length() > 0) {
  436 + handleEvent(eventBuffer.toString(),sessionId);
  437 + eventBuffer.setLength(0);
  438 + }
  439 + }
  440 + }
  441 + }
  442 +
  443 + private static void handleEvent(String eventData,String sessionId) {
  444 + try {
  445 + DifyResponse event = JSON.parseObject(eventData, DifyResponse.class);
  446 +
  447 + switch (event.getEvent()) {
  448 + case "agent_thought":
  449 + //System.out.println("[思考] " + event.getThought());
  450 + break;
  451 + case "agent_message":
  452 + System.out.print(event.getAnswer());
  453 + event.setCode(200);
  454 + WebSocketAiServer.sendMessage(sessionId,event);
  455 + break;
  456 + case "message_end":
  457 + event.setCode(203);
  458 + System.out.println("\n[对话结束]");
  459 + WebSocketAiServer.sendMessage(sessionId,event);
  460 + break;
  461 +
  462 + case "error":
  463 + event.setCode(500);
  464 + System.err.println("[错误] " + event.getMessage());
  465 + WebSocketAiServer.sendMessage(sessionId,event);
  466 + break;
  467 + default:
  468 + System.out.println("[事件] " + event.getEvent());
  469 + }
  470 + } catch (Exception e) {
  471 + System.err.println("解析错误: " + eventData);
  472 + e.printStackTrace();
  473 + }
  474 + }
  475 +}
  1 +package com.aigeo.util;
  2 +
  3 +import org.apache.poi.util.Units;
  4 +import org.apache.poi.xwpf.usermodel.ParagraphAlignment;
  5 +import org.apache.poi.xwpf.usermodel.XWPFDocument;
  6 +import org.apache.poi.xwpf.usermodel.XWPFParagraph;
  7 +import org.apache.poi.xwpf.usermodel.XWPFRun;
  8 +import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTInd;
  9 +import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTPPr;
  10 +import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSpacing;
  11 +import org.openxmlformats.schemas.wordprocessingml.x2006.main.STLineSpacingRule;
  12 +
  13 +import java.io.FileOutputStream;
  14 +import java.io.InputStream;
  15 +import java.math.BigInteger;
  16 +import java.net.URL;
  17 +import java.util.regex.Matcher;
  18 +import java.util.regex.Pattern;
  19 +
  20 +public class DocumentCreator {
  21 + public static String create(String content) {
  22 + try {
  23 + //生成一个随机文件名称
  24 + long l = System.currentTimeMillis();
  25 + String fileName = "D:/LsYwxProject/YwxErpApi/yiwaixiaoerp/YwxErp/upload/aidocx/" + l + ".docx";
  26 + createFormattedDocx(content, fileName);
  27 + System.out.println("文档创建成功:自然的诗篇.docx");
  28 + return fileName;
  29 + } catch (Exception e) {
  30 + System.err.println("创建文档时出错: " + e.getMessage());
  31 + e.printStackTrace();
  32 + return null;
  33 + }
  34 + }
  35 +
  36 + public static void createFormattedDocx(String content, String fileName) throws Exception {
  37 + // 创建新文档
  38 + XWPFDocument document = new XWPFDocument();
  39 +
  40 + // 按换行符分割内容
  41 + String[] parts = content.split("\n\n");
  42 +
  43 + if (parts.length == 0) return;
  44 +
  45 + // 处理标题(第一部分)
  46 + String title = parts[0];
  47 + // 移除标题前的Markdown标记
  48 + if (title.startsWith("# ")) {
  49 + title = title.substring(2);
  50 + }
  51 + createHeading(document, title, 1);
  52 +
  53 + // 处理其余部分
  54 + for (int i = 1; i < parts.length; i++) {
  55 + String part = parts[i];
  56 +
  57 + // 检查是否是图片标记
  58 + if (part.startsWith("![")) {
  59 + String imageUrl = extractImageUrl(part);
  60 + if (imageUrl != null) {
  61 + // 下载并插入图片
  62 + insertImage(document, imageUrl);
  63 + }
  64 + } else {
  65 + // 处理普通文本段落
  66 + createFormattedParagraph(document, part);
  67 + }
  68 + }
  69 +
  70 + // 保存文件
  71 + try (FileOutputStream out = new FileOutputStream(fileName)) {
  72 + document.write(out);
  73 + }
  74 + }
  75 +
  76 + // 从Markdown图片标记中提取URL
  77 + private static String extractImageUrl(String markdown) {
  78 + // 正则表达式匹配Markdown图片语法
  79 + Pattern pattern = Pattern.compile("!\\[.*?\\]\\((.*?)\\)");
  80 + Matcher matcher = pattern.matcher(markdown);
  81 + if (matcher.find()) {
  82 + return matcher.group(1);
  83 + }
  84 + return null;
  85 + }
  86 +
  87 + // 下载并插入图片
  88 + private static void insertImage(XWPFDocument doc, String imageUrl) throws Exception {
  89 + try {
  90 + // 创建新段落用于放置图片
  91 + XWPFParagraph para = doc.createParagraph();
  92 + para.setAlignment(ParagraphAlignment.CENTER);
  93 + XWPFRun run = para.createRun();
  94 +
  95 + // 下载图片
  96 + URL url = new URL(imageUrl);
  97 + try (InputStream is = url.openStream()) {
  98 + // 插入图片(宽度15厘米,高度按比例自动计算)
  99 + run.addPicture(is,
  100 + XWPFDocument.PICTURE_TYPE_JPEG,
  101 + "image.jpg",
  102 + Units.toEMU(400), // 宽度15厘米
  103 + Units.toEMU(200)); // 高度10厘米(实际会按比例调整)
  104 + }
  105 +
  106 + // 添加图片标题(可选)
  107 + //XWPFParagraph caption = doc.createParagraph();
  108 + //caption.setAlignment(ParagraphAlignment.CENTER);
  109 + //XWPFRun captionRun = caption.createRun();
  110 + //captionRun.setText("自然的画卷");
  111 + //captionRun.setFontSize(10);
  112 + //captionRun.setFontFamily("宋体");
  113 + //captionRun.setItalic(true);
  114 +
  115 + } catch (Exception e) {
  116 + System.err.println("无法插入图片: " + e.getMessage());
  117 + // 创建错误信息段落
  118 + XWPFParagraph errorPara = doc.createParagraph();
  119 + XWPFRun errorRun = errorPara.createRun();
  120 + errorRun.setText("[图片加载失败: " + imageUrl + "]");
  121 + errorRun.setColor("FF0000");
  122 + }
  123 + }
  124 +
  125 + // 创建标题方法
  126 + private static void createHeading(XWPFDocument doc, String text, int level) {
  127 + XWPFParagraph heading = doc.createParagraph();
  128 + heading.setStyle("Heading" + level);
  129 +
  130 + XWPFRun run = heading.createRun();
  131 + run.setText(text);
  132 + run.setBold(true);
  133 + run.setFontSize(18);
  134 + run.setFontFamily("楷体");
  135 +
  136 + // 设置标题居中
  137 + heading.setAlignment(ParagraphAlignment.CENTER);
  138 +
  139 + // 设置段后间距
  140 + CTPPr ppr = heading.getCTP().getPPr();
  141 + if (ppr == null) ppr = heading.getCTP().addNewPPr();
  142 + CTSpacing spacing = ppr.isSetSpacing() ? ppr.getSpacing() : ppr.addNewSpacing();
  143 + spacing.setAfter(BigInteger.valueOf(400)); // 20磅间距
  144 + }
  145 +
  146 + // 创建格式化的正文段落
  147 + private static void createFormattedParagraph(XWPFDocument doc, String text) {
  148 + XWPFParagraph para = doc.createParagraph();
  149 + XWPFRun run = para.createRun();
  150 + run.setText(text);
  151 + run.setFontSize(12);
  152 + run.setFontFamily("宋体");
  153 +
  154 + // 设置首行缩进和行距
  155 + CTPPr ppr = para.getCTP().getPPr();
  156 + if (ppr == null) ppr = para.getCTP().addNewPPr();
  157 +
  158 + // 首行缩进2字符 (1字符=400)
  159 + CTInd ind = ppr.isSetInd() ? ppr.getInd() : ppr.addNewInd();
  160 + ind.setFirstLine(BigInteger.valueOf(800));
  161 +
  162 + // 设置行距 (1.5倍行距)
  163 + CTSpacing spacing = ppr.isSetSpacing() ? ppr.getSpacing() : ppr.addNewSpacing();
  164 + spacing.setLineRule(STLineSpacingRule.AUTO);
  165 + spacing.setLine(BigInteger.valueOf(360)); // 1.5倍行距 = 1.5*240=360
  166 +
  167 + // 设置段后间距
  168 + spacing.setAfter(BigInteger.valueOf(100)); // 5磅段落间距
  169 + }
  170 +
  171 + // 下载并插入图片
  172 + //private static void insertImage(XWPFDocument doc, String imageUrl) throws Exception {
  173 + // try {
  174 + // // 创建新段落用于放置图片
  175 + // XWPFParagraph para = doc.createParagraph();
  176 + // para.setAlignment(ParagraphAlignment.CENTER);
  177 + // XWPFRun run = para.createRun();
  178 + //
  179 + // // 下载图片并获取原始尺寸
  180 + // URL url = new URL(imageUrl);
  181 + // try (InputStream is = url.openStream()) {
  182 + // // 将输入流转换为字节数组,以便多次使用
  183 + // byte[] imageBytes = IOUtils.toByteArray(is);
  184 + // ByteArrayInputStream bis = new ByteArrayInputStream(imageBytes);
  185 + //
  186 + // // 获取图片尺寸
  187 + // BufferedImage bufferedImage = ImageIO.read(bis);
  188 + // if (bufferedImage != null) {
  189 + // int originalWidth = bufferedImage.getWidth();
  190 + // int originalHeight = bufferedImage.getHeight();
  191 + //
  192 + // // 重置bis以供图片插入使用
  193 + // bis.reset();
  194 + // bis = new ByteArrayInputStream(imageBytes);
  195 + //
  196 + // // 插入图片(使用原始宽高,转换为EMU单位)
  197 + // run.addPicture(new ByteArrayInputStream(imageBytes),
  198 + // XWPFDocument.PICTURE_TYPE_JPEG,
  199 + // "image.jpg",
  200 + // Units.toEMU(400), // 宽度15厘米
  201 + // Units.toEMU(200)); // 高度10厘米
  202 + // } else {
  203 + // // 如果无法获取尺寸,使用默认尺寸
  204 + // run.addPicture(new ByteArrayInputStream(imageBytes),
  205 + // XWPFDocument.PICTURE_TYPE_JPEG,
  206 + // "image.jpg",
  207 + // Units.toEMU(400), // 宽度15厘米
  208 + // Units.toEMU(200)); // 高度10厘米
  209 + // }
  210 + // }
  211 + //
  212 + // } catch (Exception e) {
  213 + // System.err.println("无法插入图片: " + e.getMessage());
  214 + // // 创建错误信息段落
  215 + // XWPFParagraph errorPara = doc.createParagraph();
  216 + // XWPFRun errorRun = errorPara.createRun();
  217 + // errorRun.setText("[图片加载失败: " + imageUrl + "]");
  218 + // errorRun.setColor("FF0000");
  219 + // }
  220 + //}
  221 +}
  1 +package com.aigeo.util;
  2 +
  3 +import org.apache.tika.Tika;
  4 +import org.apache.tika.exception.TikaException;
  5 +
  6 +import java.io.IOException;
  7 +import java.io.InputStream;
  8 +import java.util.regex.Pattern;
  9 +
  10 +public class DocumentParse {
  11 + private final Tika tika;
  12 +
  13 + // 定义有意义字符的正则表达式
  14 + private static final Pattern MEANINGFUL_CHAR_PATTERN = Pattern.compile("[\\s\\u4e00-\\u9fa5a-zA-Z0-9¥$€£¢₹₽₩₪₨₦₡₫₴₵₸₺₼₾₿.,;:'\"!?()-]*");
  15 +
  16 + // 字符限制
  17 + private static final int CHINESE_CHAR_LIMIT = 30000;
  18 + private static final int ENGLISH_CHAR_LIMIT = 100000;
  19 +
  20 + // 中文比例阈值
  21 + private static final double CHINESE_THRESHOLD = 0.4;
  22 +
  23 + public DocumentParse() {
  24 + this.tika = new Tika();
  25 + }
  26 +
  27 +// public String parse(InputStream inputStream) throws TikaException, IOException, DocumentParseException {
  28 +// // 使用Tika解析文档内容
  29 +// String rawContent = this.tika.parseToString(inputStream);
  30 +//
  31 +// // 过滤特殊字符,只保留有意义的字符
  32 +// String filteredContent = filterMeaningfulCharacters(rawContent);
  33 +//
  34 +// // 检查字符数是否超过限制
  35 +// validateContentLength(filteredContent);
  36 +//
  37 +// return filteredContent;
  38 +// }
  39 +
  40 + /**
  41 + * 过滤特殊字符,只保留有意义的字符
  42 + * @param content 原始内容
  43 + * @return 过滤后的内容
  44 + */
  45 + private String filterMeaningfulCharacters(String content) {
  46 + if (content == null || content.isEmpty()) {
  47 + return "";
  48 + }
  49 + // 使用正则表达式匹配有意义的字符
  50 + StringBuilder result = new StringBuilder();
  51 + java.util.regex.Matcher matcher = MEANINGFUL_CHAR_PATTERN.matcher(content);
  52 +
  53 + while (matcher.find()) {
  54 + result.append(matcher.group());
  55 + }
  56 +
  57 + return result.toString();
  58 + }
  59 +
  60 + /**
  61 + * 验证内容长度是否超过限制
  62 + * @param content 过滤后的内容
  63 + * @throws DocumentParseException 当内容超过限制时抛出异常
  64 + */
  65 +// private void validateContentLength(String content) throws DocumentParseException {
  66 +// if (content == null || content.isEmpty()) {
  67 +// return;
  68 +// }
  69 +//
  70 +// // 计算中文字符比例
  71 +// double chineseRatio = calculateChineseRatio(content);
  72 +//
  73 +// if (chineseRatio > CHINESE_THRESHOLD) {
  74 +// // 中文文档处理
  75 +// if (content.length() > CHINESE_CHAR_LIMIT) {
  76 +// throw new DocumentParseException("您的文档字符超过限制,请缩减文档再次上传");
  77 +// }
  78 +// } else {
  79 +// // 英文文档处理
  80 +// if (content.length() > ENGLISH_CHAR_LIMIT) {
  81 +// throw new DocumentParseException("您的文档字符超过限制,请缩减文档再次上传");
  82 +// }
  83 +// }
  84 +// }
  85 +
  86 + /**
  87 + * 计算中文字符在总字符中的比例
  88 + * @param content 文档内容
  89 + * @return 中文字符比例
  90 + */
  91 + private double calculateChineseRatio(String content) {
  92 + if (content == null || content.isEmpty()) {
  93 + return 0.0;
  94 + }
  95 +
  96 + int totalChars = content.length();
  97 + int chineseChars = 0;
  98 +
  99 + for (char c : content.toCharArray()) {
  100 + // 判断是否为中文字符(基本汉字范围)
  101 + if (c >= 0x4e00 && c <= 0x9fa5) {
  102 + chineseChars++;
  103 + }
  104 + }
  105 +
  106 + return (double) chineseChars / totalChars;
  107 + }
  108 +}
  109 +
  1 +package com.aigeo.util;
  2 +
  3 +import java.io.ByteArrayOutputStream;
  4 +import java.io.FileInputStream;
  5 +import java.io.IOException;
  6 +import java.io.InputStream;
  7 +import java.util.ArrayList;
  8 +import java.util.List;
  9 +
  10 +public class FtpUtil {
  11 +
  12 + // FTP服务器配置参数
  13 + private static final String SERVER = "193.112.177.8"; // FTP服务器地址
  14 + private static final int PORT = 21; // FTP服务器端口,默认21
  15 + private static final String USERNAME = "user1"; // FTP用户名
  16 + private static final String PASSWORD = "sz123321"; // FTP密码
  17 +
  18 + // 本地文件路径及远程存储路径
  19 + private static final String LOCAL_FILE_PATH = "C:\\Users\\Lenovo\\Desktop\\sql.txt";
  20 + private static final String REMOTE_FILE_PATH = "/1/sql.txt";
  21 +
  22 +// public static void upload(FtpDTO ftpDTO) {
  23 +// FTPClient ftpClient = new FTPClient();
  24 +// try {
  25 +// // 1. 连接FTP服务器
  26 +// System.out.println("正在连接到FTP服务器 " + ftpDTO.getFtpHost() + "...");
  27 +// ftpClient.connect(ftpDTO.getFtpHost(), PORT);
  28 +// int replyCode = ftpClient.getReplyCode();
  29 +// if (!FTPReply.isPositiveCompletion(replyCode)) {
  30 +// System.err.println("FTP服务器拒绝连接,响应代码:" + replyCode);
  31 +// return;
  32 +// }
  33 +// System.out.println("连接成功,准备登录...");
  34 +//
  35 +// // 2. 登录FTP服务器
  36 +// boolean success = ftpClient.login(ftpDTO.getUser(), ftpDTO.getPassword());
  37 +// if (!success) {
  38 +// System.err.println("FTP登录失败,请检查用户名和密码。");
  39 +// return;
  40 +// }
  41 +// System.out.println("登录成功!");
  42 +//
  43 +// // 3. 设置FTP客户端工作模式
  44 +// // 进入被动模式,以适应防火墙/NAT环境
  45 +// ftpClient.enterLocalPassiveMode();
  46 +// // 设置文件传输类型为二进制文件,确保上传文件内容正确
  47 +// ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
  48 +//
  49 +// // 4. 上传文件
  50 +// System.out.println("开始上传文件:" + ftpDTO.getSourcePath());
  51 +// // 使用try-with-resources确保InputStream自动关闭
  52 +// try (InputStream inputStream = new FileInputStream(ftpDTO.getSourcePath())) {
  53 +// boolean uploadSuccess = ftpClient.storeFile(ftpDTO.getTargetPath(), inputStream);
  54 +// if (uploadSuccess) {
  55 +// System.out.println("文件上传成功!保存到:" + ftpDTO.getTargetPath());
  56 +// } else {
  57 +// System.err.println("文件上传失败,请检查文件路径和网络连接。");
  58 +// }
  59 +// }
  60 +//
  61 +// // 5. 登出并断开FTP连接
  62 +// ftpClient.logout();
  63 +// System.out.println("已登出FTP服务器。");
  64 +//
  65 +// } catch (IOException ex) {
  66 +// System.err.println("发生异常:" + ex.getMessage());
  67 +// ex.printStackTrace();
  68 +// } finally {
  69 +// // 6. 确保FTP连接被断开
  70 +// if (ftpClient.isConnected()) {
  71 +// try {
  72 +// ftpClient.disconnect();
  73 +// System.out.println("FTP连接已关闭。");
  74 +// } catch (IOException ex) {
  75 +// System.err.println("关闭FTP连接时发生异常:" + ex.getMessage());
  76 +// ex.printStackTrace();
  77 +// }
  78 +// }
  79 +// }
  80 +// }
  81 +
  82 +// public static List<String> listFileNames(ApiConfig apiConfig, String directoryPath) {
  83 +// FTPClient ftpClient = new FTPClient();
  84 +// List<String> fileNames = new ArrayList<>();
  85 +//
  86 +// try {
  87 +// // 连接并登录FTP服务器
  88 +// ftpClient.connect(apiConfig.getApiUrl(), PORT);
  89 +// int replyCode = ftpClient.getReplyCode();
  90 +// if (!FTPReply.isPositiveCompletion(replyCode)) {
  91 +// return null;
  92 +// }
  93 +//
  94 +// boolean success = ftpClient.login(apiConfig.getApiId(), apiConfig.getApiKey());
  95 +// if (!success) {
  96 +// return null;
  97 +// }
  98 +//
  99 +// // 进入被动模式
  100 +// ftpClient.enterLocalPassiveMode();
  101 +//
  102 +// // 获取文件列表
  103 +// FTPFile[] files;
  104 +// if (directoryPath != null && !directoryPath.isEmpty()) {
  105 +// files = ftpClient.listFiles(directoryPath);
  106 +// } else {
  107 +// files = ftpClient.listFiles();
  108 +// }
  109 +//
  110 +// // 提取文件名称
  111 +// for (FTPFile file : files) {
  112 +// fileNames.add(file.getName());
  113 +// }
  114 +//
  115 +// return fileNames;
  116 +//
  117 +// } catch (IOException ex) {
  118 +// return null;
  119 +// } finally {
  120 +// // 确保连接被正确关闭
  121 +// if (ftpClient.isConnected()) {
  122 +// try {
  123 +// ftpClient.logout();
  124 +// ftpClient.disconnect();
  125 +// } catch (IOException ex) {
  126 +// System.err.println("关闭FTP连接时发生异常:" + ex.getMessage());
  127 +// ex.printStackTrace();
  128 +// }
  129 +// }
  130 +// }
  131 +// }
  132 +
  133 + /**
  134 + * 获取FTP服务器当前目录下的文件列表
  135 + * @return FTPFile数组,包含目录中的文件和子目录信息
  136 + */
  137 +// public static FTPFile[] getlistFiles() {
  138 +// return listFiles(null);
  139 +// }
  140 +
  141 + /**
  142 + * 打印指定目录下的文件列表信息
  143 + * @param directoryPath 目录路径
  144 + */
  145 +// public static void printFileList(String directoryPath) {
  146 +// FTPFile[] files = listFiles(directoryPath);
  147 +//
  148 +// if (files.length == 0) {
  149 +// System.out.println("目录为空或获取失败");
  150 +// return;
  151 +// }
  152 +//
  153 +// System.out.println("目录 " + (directoryPath != null ? directoryPath : "当前目录") + " 中的文件列表:");
  154 +// for (FTPFile file : files) {
  155 +// String fileType = file.isDirectory() ? "目录" : "文件";
  156 +// System.out.printf("%-10s %-20s %10d bytes%n",
  157 +// fileType, file.getName(), file.getSize());
  158 +// }
  159 +// }
  160 +
  161 +
  162 +// public static byte[] downloadFile(ApiConfig apiConfig, String targetPath) {
  163 +// FTPClient ftpClient = new FTPClient();
  164 +// try {
  165 +// // 1. 连接FTP服务器
  166 +// System.out.println("正在连接到FTP服务器 " + apiConfig.getApiUrl() + "...");
  167 +// ftpClient.connect(apiConfig.getApiUrl(), PORT);
  168 +// int replyCode = ftpClient.getReplyCode();
  169 +// if (!FTPReply.isPositiveCompletion(replyCode)) {
  170 +// System.err.println("FTP服务器拒绝连接,响应代码:" + replyCode);
  171 +// return null;
  172 +// }
  173 +// System.out.println("连接成功,准备登录...");
  174 +//
  175 +// // 2. 登录FTP服务器
  176 +// boolean success = ftpClient.login(apiConfig.getApiId(), apiConfig.getApiKey());
  177 +// if (!success) {
  178 +// System.err.println("FTP登录失败,请检查用户名和密码。");
  179 +// return null;
  180 +// }
  181 +// System.out.println("登录成功!");
  182 +//
  183 +// // 3. 设置FTP客户端工作模式
  184 +// // 进入被动模式,以适应防火墙/NAT环境
  185 +// ftpClient.enterLocalPassiveMode();
  186 +// // 设置文件传输类型为二进制文件,确保下载文件内容正确
  187 +// ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
  188 +//
  189 +// // 4. 下载文件
  190 +// System.out.println("开始下载文件:" + targetPath);
  191 +// ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
  192 +// boolean downloadSuccess = ftpClient.retrieveFile(targetPath, outputStream);
  193 +// if (downloadSuccess) {
  194 +// System.out.println("文件下载成功!文件大小:" + outputStream.size() + " 字节");
  195 +// return outputStream.toByteArray();
  196 +// } else {
  197 +// System.err.println("文件下载失败,请检查文件路径和网络连接。");
  198 +// return null;
  199 +// }
  200 +//
  201 +// } catch (IOException ex) {
  202 +// System.err.println("下载文件时发生异常:" + ex.getMessage());
  203 +// ex.printStackTrace();
  204 +// return null;
  205 +// } finally {
  206 +// // 5. 确保FTP连接被正确关闭
  207 +// if (ftpClient.isConnected()) {
  208 +// try {
  209 +// ftpClient.logout();
  210 +// ftpClient.disconnect();
  211 +// System.out.println("FTP连接已关闭。");
  212 +// } catch (IOException ex) {
  213 +// System.err.println("关闭FTP连接时发生异常:" + ex.getMessage());
  214 +// ex.printStackTrace();
  215 +// }
  216 +// }
  217 +// }
  218 +// }
  219 +}
  1 +package com.aigeo.util;
  2 +
  3 +import okhttp3.HttpUrl;
  4 +import okhttp3.OkHttpClient;
  5 +import okhttp3.Request;
  6 +import okhttp3.Response;
  7 +
  8 +import java.io.IOException;
  9 +
  10 +public class GoogleUtil {
  11 +
  12 + private static final String API_KEY = "";
  13 + public static void search(){
  14 + OkHttpClient client = new OkHttpClient();
  15 +
  16 + HttpUrl.Builder urlBuilder = HttpUrl.get("https://www.searchapi.io/api/v1/search").newBuilder();
  17 + urlBuilder.addQueryParameter("engine", "google");
  18 + urlBuilder.addQueryParameter("q", "chatgpt");
  19 + urlBuilder.addQueryParameter("api_key", API_KEY);
  20 +
  21 + Request request = new Request.Builder()
  22 + .url(urlBuilder.build())
  23 + .build();
  24 +
  25 + Response response = null;
  26 + try {
  27 + response = client.newCall(request).execute();
  28 + System.out.println(response.body().string());
  29 + } catch (IOException e) {
  30 + throw new RuntimeException(e);
  31 + }
  32 +
  33 +
  34 + }
  35 +
  36 +}
  1 +package com.aigeo.util;
  2 +
  3 +import okhttp3.*;
  4 +import org.apache.commons.codec.digest.DigestUtils;
  5 +
  6 +import java.io.IOException;
  7 +import java.util.HashMap;
  8 +import java.util.Map;
  9 +
  10 +public class PbootUtil {
  11 + private static final String BASE_URL = "http://web.szcxybz.com/admin.php/content/";
  12 + private static final String API_APP_ID = "admin";
  13 + private static final String API_SECRET_KEY = "123456";
  14 + private static final OkHttpClient httpClient = new OkHttpClient();
  15 + private static final String MEDIA_TYPE_JSON = "application/json; charset=utf-8";
  16 + private static final String MEDIA_TYPE_FORM = "application/x-www-form-urlencoded; charset=utf-8";
  17 +
  18 + /**
  19 + * 通用API请求方法
  20 + * @param url 请求路径
  21 + * @param method 请求方法 (GET/POST)
  22 + * @param params 请求参数
  23 + * @return 响应结果
  24 + */
  25 + public static String apiRequest(String url, String method, Map<String, Object> params) throws IOException {
  26 + // 获取当前时间戳
  27 + String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
  28 +
  29 + // 生成签名
  30 + String signature = generateSignature(API_APP_ID, API_SECRET_KEY, timestamp);
  31 +
  32 + // 构造请求参数
  33 + Map<String, Object> requestParams = new HashMap<>();
  34 + requestParams.put("appid", API_APP_ID);
  35 + requestParams.put("timestamp", timestamp);
  36 + requestParams.put("signature", signature);
  37 +
  38 + // 添加传入的参数
  39 + if (params != null) {
  40 + requestParams.putAll(params);
  41 + }
  42 +
  43 + if ("POST".equalsIgnoreCase(method)) {
  44 + return postRequest(url, requestParams);
  45 + } else {
  46 + return getRequest(url, requestParams);
  47 + }
  48 + }
  49 +
  50 + /**
  51 + * 发送POST请求
  52 + * @param url 请求路径
  53 + * @param params 请求参数
  54 + * @return 响应结果
  55 + */
  56 + private static String postRequest(String url, Map<String, Object> params) throws IOException {
  57 + // 构建表单数据而不是JSON
  58 + FormBody.Builder formBuilder = new FormBody.Builder();
  59 + for (Map.Entry<String, Object> entry : params.entrySet()) {
  60 + formBuilder.add(entry.getKey(), String.valueOf(entry.getValue()));
  61 + }
  62 +
  63 + RequestBody body = formBuilder.build();
  64 +
  65 + Request request = new Request.Builder()
  66 + .url(BASE_URL + url)
  67 + .post(body)
  68 + .addHeader("Content-Type", MEDIA_TYPE_FORM)
  69 + .build();
  70 +
  71 + try (Response response = httpClient.newCall(request).execute()) {
  72 + if (response.isSuccessful()) {
  73 + return response.body().string();
  74 + } else {
  75 + throw new IOException("请求失败: " + response.code() + " " + response.message());
  76 + }
  77 + }
  78 + }
  79 +
  80 + /**
  81 + * 发送GET请求
  82 + * @param url 请求路径
  83 + * @param params 请求参数
  84 + * @return 响应结果
  85 + */
  86 + private static String getRequest(String url, Map<String, Object> params) throws IOException {
  87 + // 构建带参数的URL
  88 + String fullUrl = buildUrlWithParams(BASE_URL + url, params);
  89 +
  90 + Request request = new Request.Builder()
  91 + .url(fullUrl)
  92 + .get()
  93 + .addHeader("Content-Type", MEDIA_TYPE_JSON)
  94 + .build();
  95 +
  96 + try (Response response = httpClient.newCall(request).execute()) {
  97 + if (response.isSuccessful()) {
  98 + return response.body().string();
  99 + } else {
  100 + throw new IOException("请求失败: " + response.code() + " " + response.message());
  101 + }
  102 + }
  103 + }
  104 +
  105 + /**
  106 + * 构建带查询参数的URL
  107 + * @param baseUrl 基础URL
  108 + * @param params 查询参数
  109 + * @return 完整URL
  110 + */
  111 + private static String buildUrlWithParams(String baseUrl, Map<String, Object> params) {
  112 + if (params == null || params.isEmpty()) {
  113 + return baseUrl;
  114 + }
  115 +
  116 + StringBuilder urlBuilder = new StringBuilder(baseUrl);
  117 + urlBuilder.append("?");
  118 +
  119 + boolean first = true;
  120 + for (Map.Entry<String, Object> entry : params.entrySet()) {
  121 + if (!first) {
  122 + urlBuilder.append("&");
  123 + }
  124 + urlBuilder.append(entry.getKey())
  125 + .append("=")
  126 + .append(entry.getValue());
  127 + first = false;
  128 + }
  129 +
  130 + return urlBuilder.toString();
  131 + }
  132 +
  133 + /**
  134 + * 生成PbootCMS API签名(双层MD5加密)
  135 + * @param appid 应用ID
  136 + * @param secret 密钥
  137 + * @param timestamp 时间戳
  138 + * @return 签名字符串
  139 + */
  140 + private static String generateSignature(String appid, String secret, String timestamp) {
  141 + // 构造签名字符串: appid + secret + timestamp
  142 + String sign = appid + secret + timestamp;
  143 +
  144 + // 双层MD5加密
  145 + String firstMd5 = DigestUtils.md5Hex(sign);
  146 + return DigestUtils.md5Hex(firstMd5);
  147 + }
  148 +
  149 + // 使用示例
  150 + //public static void main(String[] args) {
  151 + // try {
  152 + // // GET请求示例
  153 + // //String getResult = apiRequest("site", "GET", null);
  154 + // //System.out.println("GET结果: " + getResult);
  155 + //
  156 + // // POST请求示例
  157 + // Map<String, Object> postParams = new HashMap<>();
  158 + // postParams.put("contacts", "测试文章标题");
  159 + // postParams.put("name", "nimhjcws");
  160 + // postParams.put("tel", "14523687549");
  161 + // postParams.put("mobile", "12345678914");
  162 + // postParams.put("content", ";lsdjfpolasndlwiojnkldoifdfnddfd");
  163 + //
  164 + // String postResult = apiRequest("add", "POST", postParams);
  165 + // System.out.println("POST结果: " + postResult);
  166 + //
  167 + // } catch (IOException e) {
  168 + // e.printStackTrace();
  169 + // }
  170 + //}
  171 +}
  1 +package com.aigeo.util;
  2 +
  3 +import com.itextpdf.io.image.ImageData;
  4 +import com.itextpdf.io.image.ImageDataFactory;
  5 +import com.itextpdf.kernel.colors.ColorConstants;
  6 +import com.itextpdf.kernel.font.PdfFont;
  7 +import com.itextpdf.kernel.font.PdfFontFactory;
  8 +import com.itextpdf.kernel.pdf.PdfDocument;
  9 +import com.itextpdf.kernel.pdf.PdfWriter;
  10 +import com.itextpdf.layout.Document;
  11 +import com.itextpdf.layout.element.Image;
  12 +import com.itextpdf.layout.element.Paragraph;
  13 +import com.itextpdf.layout.properties.TextAlignment;
  14 +import com.itextpdf.layout.properties.UnitValue;
  15 +
  16 +import java.io.File;
  17 +import java.io.FileOutputStream;
  18 +import java.net.URL;
  19 +import java.util.regex.Matcher;
  20 +import java.util.regex.Pattern;
  21 +
  22 +public class PdfCreateUtil {
  23 + public static String create(String content) {
  24 + try {
  25 + long l = System.currentTimeMillis();
  26 + String fileName = "D:/LsYwxProject/YwxErpApi/yiwaixiaoerp/YwxErp/upload/aidocx/" + l + ".pdf";
  27 + createFormattedPdfWithIText(content, fileName);
  28 + System.out.println("文档创建成功:自然的诗篇.pdf");
  29 + return fileName;
  30 + } catch (Exception e) {
  31 + System.err.println("创建文档时出错: " + e.getMessage());
  32 + e.printStackTrace();
  33 + }
  34 + return "success";
  35 + }
  36 +
  37 + public static void createFormattedPdfWithIText(String content, String fileName) throws Exception {
  38 + // 确保输出目录存在
  39 + File outputFile = new File(fileName);
  40 + File parentDir = outputFile.getParentFile();
  41 + if (parentDir != null && !parentDir.exists()) {
  42 + parentDir.mkdirs();
  43 + }
  44 +
  45 + // 创建PDF文档
  46 + PdfWriter writer = new PdfWriter(new FileOutputStream(outputFile));
  47 + PdfDocument pdf = new PdfDocument(writer);
  48 + Document document = new Document(pdf);
  49 +
  50 + // 尝试设置中文字体
  51 + try {
  52 + PdfFont chineseFont = PdfFontFactory.createFont("STSong-Light", "UniGB-UCS2-H");
  53 + document.setFont(chineseFont);
  54 + System.out.println("中文字体设置成功");
  55 + } catch (Exception e) {
  56 + System.err.println("无法设置中文字体,将使用默认字体: " + e.getMessage());
  57 + }
  58 +
  59 + try {
  60 + // 按段落分割内容(两个换行符)
  61 + String[] paragraphs = content.split("\n");
  62 +
  63 + System.out.println("检测到 " + paragraphs.length + " 个段落");
  64 +
  65 + // 遍历所有段落
  66 + for (int i = 0; i < paragraphs.length; i++) {
  67 + String paragraph = paragraphs[i].trim();
  68 + System.out.println("处理第 " + (i+1) + " 段: " + (paragraph.length() > 50 ? paragraph.substring(0, 50) + "..." : paragraph));
  69 +
  70 + if (paragraph.isEmpty()) {
  71 + continue;
  72 + }
  73 +
  74 + // 检查是否是标题
  75 + if (paragraph.startsWith("# ")) {
  76 + // 处理以 # 开头的标题
  77 + String title = paragraph.substring(2).trim(); // 移除 "# "
  78 + System.out.println("发现标题: " + title);
  79 + //.setTextAlignment(TextAlignment.CENTER)
  80 + Paragraph titlePara = new Paragraph(title)
  81 + .setFontSize(18)
  82 + .setBold()
  83 + .setMarginBottom(20);
  84 + document.add(titlePara);
  85 + }
  86 + else if (paragraph.startsWith("**") && paragraph.endsWith("**")) {
  87 + // 处理以 ** 开头和结尾的标题
  88 + String title = paragraph.substring(2, paragraph.length() - 2).trim(); // 移除开头和结尾的 **
  89 + System.out.println("发现标题: " + title);
  90 +
  91 + Paragraph titlePara = new Paragraph(title)
  92 + .setFontSize(18)
  93 + .setBold()
  94 +
  95 + .setMarginBottom(20);
  96 + document.add(titlePara);
  97 + }
  98 + // 检查是否是图片
  99 + else if (paragraph.startsWith("![")) {
  100 + String imageUrl = extractImageUrl(paragraph);
  101 + if (imageUrl != null) {
  102 + System.out.println("发现图片,URL: " + imageUrl);
  103 + try {
  104 + ImageData imageData = ImageDataFactory.create(new URL(imageUrl));
  105 + Image image = new Image(imageData);
  106 + image.setAutoScale(true);
  107 + image.setMaxWidth(UnitValue.createPointValue(400));
  108 + image.setTextAlignment(TextAlignment.CENTER);
  109 + document.add(image);
  110 + } catch (Exception e) {
  111 + System.err.println("无法加载图片: " + e.getMessage());
  112 + Paragraph errorPara = new Paragraph("[图片加载失败: " + imageUrl + "]")
  113 + .setFontColor(ColorConstants.RED);
  114 + document.add(errorPara);
  115 + }
  116 + }
  117 + }
  118 + // 普通文本段落
  119 + else {
  120 + System.out.println("发现文本段落,长度: " + paragraph.length());
  121 + if (!paragraph.isEmpty()) {
  122 + Paragraph textPara = new Paragraph(paragraph)
  123 + .setFontSize(12)
  124 + .setMarginBottom(10)
  125 + .setFirstLineIndent(20);
  126 + document.add(textPara);
  127 + }
  128 + }
  129 + }
  130 +
  131 + System.out.println("所有内容处理完成");
  132 + } finally {
  133 + document.close();
  134 + System.out.println("PDF文档已关闭");
  135 + }
  136 + }
  137 +
  138 +
  139 + private static String extractImageUrl(String markdown) {
  140 + Pattern pattern = Pattern.compile("!\\[.*?\\]\\((.*?)\\)");
  141 + Matcher matcher = pattern.matcher(markdown);
  142 + if (matcher.find()) {
  143 + return matcher.group(1);
  144 + }
  145 + return null;
  146 + }
  147 +}
  148 +
  1 +package com.aigeo.util.article;
  2 +
  3 +import org.springframework.beans.factory.annotation.Autowired;
  4 +import org.springframework.stereotype.Component;
  5 +
  6 +// 文章服务门面类
  7 +@Component
  8 +public class ArticleOperationFacade {
  9 +
  10 + @Autowired
  11 + private ArticleServiceFactory articleServiceFactory;
  12 +
  13 + public void publishArticle(PlatformTypeEnum platformType, Object articleData, Integer companyId) {
  14 + ArticlePlatformService service = articleServiceFactory.getArticleService(platformType);
  15 +// return service.publishArticle(articleData, companyId);
  16 + }
  17 +
  18 + public void updateArticle(PlatformTypeEnum platformType, Object articleData, Integer companyId) {
  19 + ArticlePlatformService service = articleServiceFactory.getArticleService(platformType);
  20 +// return service.updateArticle(articleData, companyId);
  21 + }
  22 +
  23 + public void deleteArticle(PlatformTypeEnum platformType, String articleId, Integer companyId) {
  24 + ArticlePlatformService service = articleServiceFactory.getArticleService(platformType);
  25 +// return service.deleteArticle(articleId, companyId);
  26 + }
  27 +}
  1 +package com.aigeo.util.article;
  2 +
  3 +
  4 +public interface ArticlePlatformService {
  5 + void publishArticle(Object articleData, Integer companyId);
  6 + void updateArticle(Object articleData, Integer companyId);
  7 + void deleteArticle(String articleId, Integer companyId);
  8 +
  9 + String getPlatform();
  10 +}
  1 +package com.aigeo.util.article;
  2 +
  3 +import org.springframework.beans.factory.annotation.Autowired;
  4 +import org.springframework.stereotype.Component;
  5 +
  6 +import java.util.Map;
  7 +import java.util.concurrent.ConcurrentHashMap;
  8 +
  9 +@Component
  10 +public class ArticleServiceFactory {
  11 +
  12 + private Map<String, ArticlePlatformService> platformServices;
  13 +
  14 + private final Map<String, ArticlePlatformService> serviceMap = new ConcurrentHashMap<>();
  15 +
  16 + @Autowired
  17 + public void setPlatformServices(Map<String, ArticlePlatformService> platformServices) {
  18 + this.platformServices = platformServices;
  19 + initServiceMap();
  20 + }
  21 +
  22 + private void initServiceMap() {
  23 + for (ArticlePlatformService service : platformServices.values()) {
  24 + serviceMap.put(service.getPlatform(), service);
  25 + }
  26 + }
  27 +
  28 + public ArticlePlatformService getArticleService(PlatformTypeEnum platformType) {
  29 + ArticlePlatformService service = serviceMap.get(platformType.getPlatform());
  30 + if (service == null) {
  31 + throw new IllegalArgumentException("不支持的平台类型: " + platformType);
  32 + }
  33 + return service;
  34 + }
  35 +
  36 + public ArticlePlatformService getArticleService(String platform) {
  37 + ArticlePlatformService service = serviceMap.get(platform);
  38 + if (service == null) {
  39 + throw new IllegalArgumentException("不支持的平台类型: " + platform);
  40 + }
  41 + return service;
  42 + }
  43 +}
  1 +package com.aigeo.util.article;
  2 +
  3 +public enum PlatformTypeEnum {
  4 + SHOPIFY("shopify"),
  5 + WORDPRESS("wordpress");
  6 +
  7 + private final String platform;
  8 +
  9 + PlatformTypeEnum(String platform) {
  10 + this.platform = platform;
  11 + }
  12 +
  13 + public String getPlatform() {
  14 + return platform;
  15 + }
  16 +}
  1 +package com.aigeo.util.article;
  2 +
  3 +import com.aigeo.entity.CreateArticleDTO;
  4 +import com.aigeo.entity.UpdateArticleDTO;
  5 +import com.alibaba.fastjson2.JSONObject;
  6 +import okhttp3.OkHttpClient;
  7 +import okhttp3.Request;
  8 +import okhttp3.Response;
  9 +import org.apache.commons.lang3.StringUtils;
  10 +import org.springframework.beans.factory.annotation.Value;
  11 +import org.springframework.stereotype.Component;
  12 +
  13 +import javax.annotation.PostConstruct;
  14 +import java.io.IOException;
  15 +
  16 +@Component
  17 +public class Shopify implements ArticlePlatformService {
  18 + private static final OkHttpClient okHttpClient = new OkHttpClient();
  19 +
  20 +// @Value("${openapi.url}")
  21 + private String api_url;
  22 +
  23 + private static String staticApiUrl;
  24 +
  25 + @PostConstruct
  26 + public void init() {
  27 + staticApiUrl = api_url;
  28 + }
  29 +
  30 + @Override
  31 + public void publishArticle(Object articleData, Integer companyId) {
  32 + // 实现Shopify发布文章逻辑
  33 +// if (!(articleData instanceof CreateArticleDTO)) {
  34 +// return BaseResponse.error(ReturnCodeEnum.ERROR, "参数类型错误");
  35 +// }
  36 +
  37 + CreateArticleDTO createArticleDTO = (CreateArticleDTO) articleData;
  38 +
  39 +// // 参数校验
  40 +// if (StringUtils.isBlank(createArticleDTO.getBlogId())) {
  41 +// return BaseResponse.error(ReturnCodeEnum.ERROR, "博客ID不能为空");
  42 +// }
  43 +// if (StringUtils.isBlank(createArticleDTO.getTitle())) {
  44 +// return BaseResponse.error(ReturnCodeEnum.ERROR, "标题不能为空");
  45 +// }
  46 +// if (createArticleDTO.getAuthor() == null || StringUtils.isBlank(createArticleDTO.getAuthor().getName())) {
  47 +// return BaseResponse.error(ReturnCodeEnum.ERROR, "作者信息不能为空");
  48 +// }
  49 +// if (StringUtils.isBlank(createArticleDTO.getBody())) {
  50 +// return BaseResponse.error(ReturnCodeEnum.ERROR, "文章内容不能为空");
  51 +// }
  52 +
  53 + // 构建请求体
  54 + JSONObject requestBody = new JSONObject();
  55 + requestBody.put("blogId", createArticleDTO.getBlogId());
  56 + requestBody.put("title", createArticleDTO.getTitle());
  57 +
  58 + // 构建author对象
  59 + JSONObject authorObject = new JSONObject();
  60 + authorObject.put("name", createArticleDTO.getAuthor().getName());
  61 + requestBody.put("author", authorObject);
  62 +
  63 + requestBody.put("handle", generateUniqueHandle(createArticleDTO.getTitle()));
  64 + requestBody.put("body", createArticleDTO.getBody());
  65 + requestBody.put("companyId", companyId);
  66 +
  67 + // 构建请求
  68 + Request request = new Request.Builder()
  69 + .url("http:127.0.0.1:8009" + "/shopify/createArticle")
  70 + .post(okhttp3.RequestBody.create(
  71 + requestBody.toJSONString(),
  72 + okhttp3.MediaType.parse("application/json; charset=utf-8")
  73 + ))
  74 + .build();
  75 +
  76 + // 发送请求
  77 + try (Response response = okHttpClient.newCall(request).execute()) {
  78 + if (response.isSuccessful()) {
  79 + String responseBody = response.body().string();
  80 + JSONObject jsonResponse = JSONObject.parseObject(responseBody);
  81 + int code = jsonResponse.getIntValue("code");
  82 + String message = jsonResponse.getString("message");
  83 +// if (code != 200) {
  84 +// return BaseResponse.error(ReturnCodeEnum.ERROR, message);
  85 +// } else {
  86 +// return BaseResponse.success(jsonResponse.get("data"));
  87 +// }
  88 + } else {
  89 +// return BaseResponse.error(ReturnCodeEnum.SYSTEM_ERROR);
  90 + }
  91 + } catch (IOException e) {
  92 + e.printStackTrace();
  93 +// return BaseResponse.error(ReturnCodeEnum.SYSTEM_ERROR);
  94 + }
  95 + }
  96 +
  97 + @Override
  98 + public void updateArticle(Object articleData, Integer companyId) {
  99 +// if (!(articleData instanceof UpdateArticleDTO)) {
  100 +// return BaseResponse.error(ReturnCodeEnum.ERROR, "参数类型错误");
  101 +// }
  102 +
  103 + UpdateArticleDTO updateArticleDTO = (UpdateArticleDTO) articleData;
  104 +
  105 + // 参数校验
  106 +// if (StringUtils.isBlank(updateArticleDTO.getId())) {
  107 +// return BaseResponse.error(ReturnCodeEnum.ERROR, "文章ID不能为空");
  108 +// }
  109 +// if (StringUtils.isBlank(updateArticleDTO.getTitle())) {
  110 +// return BaseResponse.error(ReturnCodeEnum.ERROR, "标题不能为空");
  111 +// }
  112 +// if (StringUtils.isBlank(updateArticleDTO.getBody())) {
  113 +// return BaseResponse.error(ReturnCodeEnum.ERROR, "文章内容不能为空");
  114 +// }
  115 +
  116 + // 构建请求体
  117 + JSONObject requestBody = new JSONObject();
  118 + requestBody.put("id", updateArticleDTO.getId());
  119 + requestBody.put("title", updateArticleDTO.getTitle());
  120 + requestBody.put("handle", updateArticleDTO.getHandle() != null ?
  121 + updateArticleDTO.getHandle() : generateUniqueHandle(updateArticleDTO.getTitle()));
  122 + requestBody.put("body", updateArticleDTO.getBody());
  123 + requestBody.put("companyId", companyId);
  124 +
  125 + // 可选字段
  126 + if (StringUtils.isNotBlank(updateArticleDTO.getSummary())) {
  127 + requestBody.put("summary", updateArticleDTO.getSummary());
  128 + }
  129 +
  130 + if (updateArticleDTO.getTags() != null && updateArticleDTO.getTags().length > 0) {
  131 + requestBody.put("tags", updateArticleDTO.getTags());
  132 + }
  133 +
  134 + if (StringUtils.isNotBlank(updateArticleDTO.getImageAltText())) {
  135 + requestBody.put("imageAltText", updateArticleDTO.getImageAltText());
  136 + }
  137 +
  138 + if (StringUtils.isNotBlank(updateArticleDTO.getImageUrl())) {
  139 + requestBody.put("imageUrl", updateArticleDTO.getImageUrl());
  140 + }
  141 +
  142 + // 构建请求
  143 + Request request = new Request.Builder()
  144 + .url(staticApiUrl + "/shopify/updateArticle")
  145 + .post(okhttp3.RequestBody.create(
  146 + requestBody.toJSONString(),
  147 + okhttp3.MediaType.parse("application/json; charset=utf-8")
  148 + ))
  149 + .build();
  150 +
  151 + // 发送请求
  152 + try (Response response = okHttpClient.newCall(request).execute()) {
  153 + if (response.isSuccessful()) {
  154 + String responseBody = response.body().string();
  155 + JSONObject jsonResponse = JSONObject.parseObject(responseBody);
  156 + int code = jsonResponse.getIntValue("code");
  157 + String message = jsonResponse.getString("message");
  158 +// if (code != 200) {
  159 +// return BaseResponse.error(ReturnCodeEnum.ERROR, message);
  160 +// } else {
  161 +// return BaseResponse.success(jsonResponse.get("data"));
  162 +// }
  163 + } else {
  164 +// return BaseResponse.error(ReturnCodeEnum.SYSTEM_ERROR);
  165 + }
  166 + } catch (IOException e) {
  167 + e.printStackTrace();
  168 +// return BaseResponse.error(ReturnCodeEnum.SYSTEM_ERROR);
  169 + }
  170 + }
  171 +
  172 + @Override
  173 + public void deleteArticle(String articleId, Integer companyId) {
  174 + // 参数校验
  175 +// if (StringUtils.isBlank(articleId)) {
  176 +// return BaseResponse.error(ReturnCodeEnum.ERROR, "文章ID不能为空");
  177 +// }
  178 +
  179 + // 构建带参数的URL
  180 + String deleteUrl = staticApiUrl + "/shopify/deleteArticle?articleId=" + articleId;
  181 +
  182 + // 构建DELETE请求
  183 + Request request = new Request.Builder()
  184 + .url(deleteUrl)
  185 + .delete()
  186 + .build();
  187 +
  188 + // 发送请求
  189 + try (Response response = okHttpClient.newCall(request).execute()) {
  190 + if (response.isSuccessful()) {
  191 + String responseBody = response.body().string();
  192 + JSONObject jsonResponse = JSONObject.parseObject(responseBody);
  193 + int code = jsonResponse.getIntValue("code");
  194 + String message = jsonResponse.getString("message");
  195 +// if (code != 200) {
  196 +// return BaseResponse.error(ReturnCodeEnum.ERROR, message);
  197 +// } else {
  198 +// return BaseResponse.success(jsonResponse.get("data"));
  199 +// }
  200 + } else {
  201 +// return BaseResponse.error(ReturnCodeEnum.SYSTEM_ERROR);
  202 + }
  203 + } catch (IOException e) {
  204 + e.printStackTrace();
  205 +// return BaseResponse.error(ReturnCodeEnum.SYSTEM_ERROR);
  206 + }
  207 + }
  208 +
  209 + @Override
  210 + public String getPlatform() {
  211 + return "shopify";
  212 + }
  213 +
  214 + private static String generateUniqueHandle(String title) {
  215 + // 创建URL友好的handle
  216 + String cleanTitle = title.toLowerCase()
  217 + .replaceAll("[^a-zA-Z0-9\\u4e00-\\u9fa5\\s-]", "") // 保留中文字符
  218 + .replaceAll("\\s+", "-")
  219 + .replaceAll("-+", "-")
  220 + .replaceAll("^-|-$", "");
  221 +
  222 + // 限制长度并确保唯一性
  223 + if (cleanTitle.length() > 40) {
  224 + cleanTitle = cleanTitle.substring(0, 40);
  225 + }
  226 +
  227 + // 添加时间戳确保唯一性
  228 + String timestamp = String.valueOf(System.currentTimeMillis() % 100000);
  229 + return cleanTitle + "-" + timestamp;
  230 + }
  231 +}
  1 +// 修改 WordPress.java 文件,实现 deleteArticle 方法
  2 +package com.aigeo.util.article;
  3 +
  4 +import com.aigeo.entity.CreateArticleDTO;
  5 +import com.aigeo.entity.UpdateArticleDTO;
  6 +import com.alibaba.fastjson2.JSONObject;
  7 +import okhttp3.*;
  8 +import org.springframework.stereotype.Component;
  9 +
  10 +import java.io.IOException;
  11 +import java.util.concurrent.TimeUnit;
  12 +
  13 +@Component
  14 +public class WordPress implements ArticlePlatformService {
  15 +
  16 + private static final OkHttpClient okHttpClient = new OkHttpClient();
  17 +
  18 + private static final String BASE_URL = "https://web1.szcxybz.com/wp-json/wp/v2/posts";
  19 + private static final String TOKEN_URL = "https://web1.szcxybz.com/wp-json/jwt-auth/v1/token";
  20 +
  21 + private static String cachedToken = null;
  22 + private static long tokenExpiryTime = 0;
  23 + private static final long TOKEN_REFRESH_BUFFER = TimeUnit.MINUTES.toMillis(5);// 5分钟缓冲
  24 +
  25 + private static final OkHttpClient httpClient = new OkHttpClient();
  26 + private static final String MEDIA_TYPE = "application/json; charset=utf-8";
  27 +
  28 + private static final String USERNAME = "admin";
  29 +
  30 + private static final String PASSWORD = "123456";
  31 +
  32 + @Override
  33 + public void publishArticle(Object articleData, Integer companyId) {
  34 + // 获取有效的 token
  35 + String token = null;
  36 + try {
  37 + token = getValidToken();
  38 + } catch (IOException e) {
  39 + e.printStackTrace();
  40 +// return BaseResponse.error(ReturnCodeEnum.SYSTEM_ERROR);
  41 + }
  42 +// if (token == null) {
  43 +// System.out.println("无法获取有效的认证 token");
  44 +// return BaseResponse.error(ReturnCodeEnum.SYSTEM_ERROR);
  45 +// }
  46 + CreateArticleDTO createArticleDTO = (CreateArticleDTO) articleData;
  47 + String styledContent = createArticleDTO.getBody().replace("<style>", "<!-- Style block will be removed by WordPress -->")
  48 + .replace("</style>", "");
  49 + JSONObject contentObject = new JSONObject();
  50 + contentObject.put("raw", styledContent);
  51 + JSONObject jsonBody = new JSONObject();
  52 + jsonBody.put("title", createArticleDTO.getTitle());
  53 + jsonBody.put("content", contentObject);
  54 + jsonBody.put("status", "publish");
  55 + //jsonBody.put("author", author);
  56 +
  57 + RequestBody body = RequestBody.create(
  58 + jsonBody.toString(),
  59 + MediaType.get(MEDIA_TYPE)
  60 + );
  61 +
  62 + Request request = new Request.Builder()
  63 + .url(BASE_URL)
  64 + .post(body)
  65 + .addHeader("Authorization", "Bearer " + token)
  66 + .addHeader("Content-Type", "application/json")
  67 + .build();
  68 +
  69 + try (Response response = okHttpClient.newCall(request).execute()) {
  70 + if (response.isSuccessful()) {
  71 +// System.out.println("响应内容: " + response.body().string());
  72 + String res = response.body().string();
  73 +// return BaseResponse.success(res);
  74 + } else {
  75 + System.out.println("发布失败: " + response.code());
  76 + System.out.println("错误信息: " + response.body().string());
  77 +// return BaseResponse.error(ReturnCodeEnum.ERROR,"发布失败");
  78 + }
  79 + } catch (IOException e) {
  80 + e.printStackTrace();
  81 +// return BaseResponse.error(ReturnCodeEnum.SYSTEM_ERROR);
  82 + }
  83 + }
  84 +
  85 + @Override
  86 + public void updateArticle(Object articleData, Integer companyId) {
  87 + // 检查参数类型
  88 +// if (!(articleData instanceof UpdateArticleDTO)) {
  89 +// return BaseResponse.error(ReturnCodeEnum.ERROR, "参数类型错误");
  90 +// }
  91 + UpdateArticleDTO updateArticleDTO = (UpdateArticleDTO) articleData;
  92 + // 参数校验
  93 +// if (updateArticleDTO.getId() == null || updateArticleDTO.getId().isEmpty()) {
  94 +// return BaseResponse.error(ReturnCodeEnum.ERROR, "文章ID不能为空");
  95 +// }
  96 + // 获取有效的 token
  97 + String token = null;
  98 + try {
  99 + token = getValidToken();
  100 + } catch (IOException e) {
  101 + e.printStackTrace();
  102 +// return BaseResponse.error(ReturnCodeEnum.SYSTEM_ERROR);
  103 + }
  104 +// if (token == null) {
  105 +// System.out.println("无法获取有效的认证 token");
  106 +// return BaseResponse.error(ReturnCodeEnum.SYSTEM_ERROR);
  107 +// }
  108 + // 构建更新文章的URL
  109 + String updateUrl = BASE_URL + "/" + updateArticleDTO.getId();
  110 +
  111 + // 构建请求体
  112 + JSONObject jsonBody = new JSONObject();
  113 +
  114 + // 只添加非空字段进行更新
  115 + if (updateArticleDTO.getTitle() != null && !updateArticleDTO.getTitle().isEmpty()) {
  116 + jsonBody.put("title", updateArticleDTO.getTitle());
  117 + }
  118 +
  119 + if (updateArticleDTO.getBody() != null && !updateArticleDTO.getBody().isEmpty()) {
  120 + String styledContent = updateArticleDTO.getBody().replace("<style>", "<!-- Style block will be removed by WordPress -->")
  121 + .replace("</style>", "");
  122 + JSONObject contentObject = new JSONObject();
  123 + contentObject.put("raw", styledContent);
  124 + jsonBody.put("content", contentObject);
  125 + }
  126 +
  127 + // 可选字段
  128 + if (updateArticleDTO.getSummary() != null) {
  129 + jsonBody.put("excerpt", updateArticleDTO.getSummary());
  130 + }
  131 +
  132 + // 添加状态字段(如果需要)
  133 + jsonBody.put("status", "publish");
  134 +
  135 + RequestBody body = RequestBody.create(
  136 + jsonBody.toString(),
  137 + MediaType.get(MEDIA_TYPE)
  138 + );
  139 +
  140 + // 构建PUT请求
  141 + Request request = new Request.Builder()
  142 + .url(updateUrl)
  143 + .put(body) // 使用PUT方法更新文章
  144 + .addHeader("Authorization", "Bearer " + token)
  145 + .addHeader("Content-Type", "application/json")
  146 + .build();
  147 +
  148 + try (Response response = okHttpClient.newCall(request).execute()) {
  149 + if (response.isSuccessful()) {
  150 + String res = response.body().string();
  151 +// return BaseResponse.success(res);
  152 + } else {
  153 + System.out.println("更新文章失败: " + response.code());
  154 + System.out.println("错误信息: " + response.body().string());
  155 +// return BaseResponse.error(ReturnCodeEnum.ERROR, "更新文章失败,状态码: " + response.code());
  156 + }
  157 + } catch (IOException e) {
  158 + e.printStackTrace();
  159 +// return BaseResponse.error(ReturnCodeEnum.SYSTEM_ERROR);
  160 + }
  161 + }
  162 +
  163 + @Override
  164 + public void deleteArticle(String articleId, Integer companyId) {
  165 + // 获取有效的 token
  166 + String token = null;
  167 + try {
  168 + token = getValidToken();
  169 + } catch (IOException e) {
  170 + e.printStackTrace();
  171 +// return BaseResponse.error(ReturnCodeEnum.SYSTEM_ERROR);
  172 + }
  173 + if (token == null) {
  174 + System.out.println("无法获取有效的认证 token");
  175 +// return BaseResponse.error(ReturnCodeEnum.SYSTEM_ERROR);
  176 + }
  177 +
  178 + // 构建删除文章的URL,将文章ID附加到基础URL后面
  179 + String deleteUrl = BASE_URL + "/" + articleId;
  180 +
  181 + // 构建DELETE请求
  182 + Request request = new Request.Builder()
  183 + .url(deleteUrl)
  184 + .delete()
  185 + .addHeader("Authorization", "Bearer " + token)
  186 + .addHeader("Content-Type", "application/json")
  187 + .build();
  188 +
  189 + try (Response response = okHttpClient.newCall(request).execute()) {
  190 + if (response.isSuccessful()) {
  191 + String res = response.body().string();
  192 +// return BaseResponse.success(res);
  193 + } else {
  194 + System.out.println("删除文章失败: " + response.code());
  195 + System.out.println("错误信息: " + response.body().string());
  196 +// return BaseResponse.error(ReturnCodeEnum.ERROR, "删除文章失败,状态码: " + response.code());
  197 + }
  198 + } catch (IOException e) {
  199 + e.printStackTrace();
  200 +// return BaseResponse.error(ReturnCodeEnum.SYSTEM_ERROR);
  201 + }
  202 + }
  203 +
  204 + @Override
  205 + public String getPlatform() {
  206 + return "wordpress";
  207 + }
  208 +
  209 + private static String getValidToken() throws IOException {
  210 + // 检查是否有缓存的 token 且未过期
  211 + if (cachedToken != null && System.currentTimeMillis() < (tokenExpiryTime - TOKEN_REFRESH_BUFFER)) {
  212 + return cachedToken;
  213 + }
  214 + System.out.println("token过期,重新获取token");
  215 + // 获取新 token
  216 + String newToken = fetchNewToken();
  217 + if (newToken != null) {
  218 + cachedToken = newToken;
  219 + // 假设 token 有效期为 24 小时,实际应根据返回的过期时间设置
  220 + tokenExpiryTime = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(2);
  221 + }
  222 +
  223 + return newToken;
  224 + }
  225 +
  226 + // 获取新的 JWT token
  227 + private static String fetchNewToken() throws IOException {
  228 + JSONObject loginData = new JSONObject();
  229 + loginData.put("username", USERNAME);
  230 + loginData.put("password", PASSWORD);
  231 +
  232 + RequestBody body = RequestBody.create(
  233 + loginData.toString(),
  234 + MediaType.get(MEDIA_TYPE)
  235 + );
  236 +
  237 + Request request = new Request.Builder()
  238 + .url(TOKEN_URL)
  239 + .post(body)
  240 + .addHeader("Content-Type", "application/json")
  241 + .build();
  242 +
  243 + try (Response response = httpClient.newCall(request).execute()) {
  244 + if (response.isSuccessful()) {
  245 + String responseBody = response.body().string();
  246 + JSONObject tokenResponse = JSONObject.parseObject(responseBody);
  247 +
  248 + // 实际项目中应该从响应中获取真正的过期时间
  249 + // tokenResponse.getLong("expires_in") 或类似字段
  250 +
  251 + return tokenResponse.getString("token");
  252 + } else {
  253 + System.out.println("获取 token 失败: " + response.code());
  254 + System.out.println("错误信息: " + response.body().string());
  255 + return null;
  256 + }
  257 + }
  258 + }
  259 +}