ruoyi-admin/src/main/resources/application-dev.yml | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
ruoyi-admin/src/main/resources/application-prod.yml | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/domain/PdfFileContentResult.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/PdfImageExtractService.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/impl/PdfImageExtractServiceImpl.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/utils/ZipUtils.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/controller/knowledge/KnowledgeController.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
ruoyi-admin/src/main/resources/application-dev.yml
@@ -94,3 +94,8 @@ # è ¾è®¯ä¸ç¨ sdkAppId: pdf: extract: service: url: http://localhost:8080 ruoyi-admin/src/main/resources/application-prod.yml
@@ -172,3 +172,8 @@ signName: æµè¯ # è ¾è®¯ä¸ç¨ sdkAppId: pdf: extract: service: url: http://localhost:8080 ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/domain/PdfFileContentResult.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,30 @@ package org.ruoyi.domain; /** * æä»¶å å®¹ç»æå°è£ ç±» */ public class PdfFileContentResult { private String filename; private String content; public PdfFileContentResult(String filename, String content) { this.filename = filename; this.content = content; } public String getFilename() { return filename; } public void setFilename(String filename) { this.filename = filename; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } } ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/PdfImageExtractService.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,41 @@ package org.ruoyi.service; import java.io.IOException; import java.util.List; import org.ruoyi.domain.PdfFileContentResult; import org.springframework.web.multipart.MultipartFile; /** * PDFå¾çæåæå¡æ¥å£ */ public interface PdfImageExtractService { /** * ä»PDFæä»¶ä¸æåå¾ç * * @param pdfFile PDFæä»¶ * @param imageFormat è¾åºå¾çæ ¼å¼ (png, jpeg, gif) * @param allowDuplicates æ¯å¦å 许éå¤å¾ç * @return å 嫿åå¾ççZIPæä»¶çåèæ°ç» * @throws IOException 妿æä»¶å¤çè¿ç¨ä¸åçé误 */ byte[] extractImages(MultipartFile pdfFile, String imageFormat, boolean allowDuplicates) throws IOException; /** * å¤çæä»¶å 容 * * @param unzip Base64ç¼ç çå¾çæ°ç» * @return æä»¶å å®¹ç»æå表 * @throws IOException 妿APIè°ç¨è¿ç¨ä¸åçé误 */ List<PdfFileContentResult> dealFileContent(String[] unzip) throws IOException; /** * æåPDFä¸çå¾çå¹¶è°ç¨gpt-4o-mini,è¯å«å¾çå 容并è¿å * @param file * @return * @throws IOException */ List<PdfFileContentResult> extractImages(MultipartFile file) throws IOException; } ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/impl/PdfImageExtractServiceImpl.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,144 @@ package org.ruoyi.service.impl; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; import lombok.extern.slf4j.Slf4j; import okhttp3.MediaType; import okhttp3.MultipartBody; import okhttp3.OkHttpClient; import okhttp3.OkHttpClient.Builder; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; import org.ruoyi.common.core.domain.R; import org.ruoyi.domain.PdfFileContentResult; import org.ruoyi.service.PdfImageExtractService; import org.ruoyi.utils.ZipUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; /** * PDFå¾çæåæå¡å®ç°ç±» */ @Service @Slf4j public class PdfImageExtractServiceImpl implements PdfImageExtractService { @Value("${pdf.extract.service.url}") private String serviceUrl; @Value("${pdf.extract.ai-api.url}") private String aiApiUrl; @Value("${pdf.extract.ai-api.key}") private String aiApiKey ; private final OkHttpClient client = new Builder() .connectTimeout(100, TimeUnit.SECONDS) .readTimeout(150, TimeUnit.SECONDS) .writeTimeout(150, TimeUnit.SECONDS) .callTimeout(300, TimeUnit.SECONDS) .build(); private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); @Override public byte[] extractImages(MultipartFile pdfFile, String imageFormat, boolean allowDuplicates) throws IOException { // æå»ºmultipartè¯·æ± RequestBody requestBody = new MultipartBody.Builder() .setType(MultipartBody.FORM) .addFormDataPart("fileInput", pdfFile.getOriginalFilename(), RequestBody.create(MediaType.parse("application/pdf"), pdfFile.getBytes())) .addFormDataPart("format", imageFormat) .addFormDataPart("allowDuplicates", String.valueOf(allowDuplicates)) .build(); // åå»ºè¯·æ± Request request = new Request.Builder() .url(serviceUrl + "/api/v1/misc/extract-images") .post(requestBody) .build(); // æ§è¡è¯·æ± try (Response response = client.newCall(request).execute()) { if (!response.isSuccessful()) { throw new IOException("请æ±å¤±è´¥: " + response.code()); } return response.body().bytes(); } } /** * å¤çæä»¶å 容 * * @param unzip Base64ç¼ç çå¾çæ°ç» * @return æä»¶å å®¹ç»æå表 * @throws IOException 妿APIè°ç¨è¿ç¨ä¸åçé误 */ @Override public List<PdfFileContentResult> dealFileContent(String[] unzip) throws IOException { List<PdfFileContentResult> results = new ArrayList<>(); int i = 0; for (String base64Image : unzip) { // æå»ºè¯·æ±JSON String requestJson = String.format("{" + "\"model\": \"gpt-4o\"," + "\"stream\": false," + "\"messages\": [{" + "\"role\": \"user\"," + "\"content\": [{" + "\"type\": \"text\"," + "\"text\": \"è¿å¼ å¾çæä»ä¹\"" + "}, {" + "\"type\": \"image_url\"," + "\"image_url\": {" + "\"url\": \"%s\"" + "}}" + "]}]," + "\"max_tokens\": 400" + "}", base64Image); // åå»ºè¯·æ± Request request = new Request.Builder() .url(aiApiUrl) .addHeader("Authorization", "Bearer " + aiApiKey) .post(RequestBody.create(JSON, requestJson)) .build(); // æ§è¡è¯·æ± try { log.info("=============call=" + ++i); Response response = client.newCall(request).execute(); log.info("=============response=" + response); if (!response.isSuccessful()) { throw new IOException("API请æ±å¤±è´¥: " + response.code() + response.toString()); } String responseBody = response.body().string(); log.info("=============responseBody=" + responseBody); // ä½¿ç¨æä»¶åï¼è¿é使ç¨base64çå10个å符ä½ä¸ºæ è¯ï¼åAPIè¿åå 容åå»ºç»æå¯¹è±¡ String filename = base64Image.substring(0, Math.min(base64Image.length(), 10)); results.add(new PdfFileContentResult(filename, responseBody)); } catch (Exception e) { log.error(e.getMessage()); throw new RuntimeException(e); } } return results; } @Override public List<PdfFileContentResult> extractImages(MultipartFile file) throws IOException { String format = "png"; boolean allowDuplicates = true; // è·åZIPæ°æ® byte[] zipData = this.extractImages(file, format, allowDuplicates); // è§£åæä»¶å¹¶è¯å«å¾çå 容并è¿å String[] unzip = ZipUtils.unzipForBase64(zipData); //è§£æå¾çå 容 return this.dealFileContent(unzip); } } ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/utils/ZipUtils.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,95 @@ package org.ruoyi.utils; import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Base64; import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; /** * ZIPæä»¶å¤çå·¥å ·ç±» */ public class ZipUtils { /** * è§£åZIPæä»¶å°æå®ç®å½ * * @param zipData ZIPæä»¶çåèæ°ç» * @param destDir ç®æ ç®å½ * @return è§£ååçæä»¶è·¯å¾å表 * @throws IOException å¦æè§£åè¿ç¨ä¸åçé误 */ public static String[] unzip(byte[] zipData, String destDir) throws IOException { File destDirFile = new File(destDir); if (!destDirFile.exists()) { destDirFile.mkdirs(); } List<String> extractedPaths = new ArrayList<>(); try (ByteArrayInputStream bis = new ByteArrayInputStream(zipData); ZipInputStream zis = new ZipInputStream(bis)) { ZipEntry zipEntry; while ((zipEntry = zis.getNextEntry()) != null) { String filePath = destDir + File.separator + zipEntry.getName(); if (!zipEntry.isDirectory()) { extractFile(zis, filePath); extractedPaths.add(filePath); } else { new File(filePath).mkdirs(); } zis.closeEntry(); } } return extractedPaths.toArray(new String[0]); } private static void extractFile(ZipInputStream zis, String filePath) throws IOException { try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(filePath))) { byte[] buffer = new byte[4096]; int read; while ((read = zis.read(buffer)) != -1) { bos.write(buffer, 0, read); } } } /** * è§£åZIPæä»¶å¹¶è¿åæä»¶å 容çBase64ç¼ç å符串æ°ç» * * @param zipData ZIPæä»¶çåèæ°ç» * @return Base64ç¼ç çæä»¶å 容æ°ç» * @throws IOException å¦æè§£åè¿ç¨ä¸åçé误 */ public static String[] unzipForBase64(byte[] zipData) throws IOException { List<String> base64Contents = new ArrayList<>(); try (ByteArrayInputStream bis = new ByteArrayInputStream(zipData); ZipInputStream zis = new ZipInputStream(bis)) { ZipEntry zipEntry; while ((zipEntry = zis.getNextEntry()) != null) { if (!zipEntry.isDirectory()) { // 读åæä»¶å 容å°å å ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buffer = new byte[4096]; int read; while ((read = zis.read(buffer)) != -1) { baos.write(buffer, 0, read); } // å°æä»¶å 容转æ¢ä¸ºBase64å符串 String base64Content = Base64.getEncoder().encodeToString(baos.toByteArray()); base64Contents.add(base64Content); } zis.closeEntry(); } } return base64Contents.toArray(new String[0]); } } ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/controller/knowledge/KnowledgeController.java
@@ -1,9 +1,12 @@ package org.ruoyi.chat.controller.knowledge; import cn.dev33.satoken.stp.StpUtil; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import java.io.IOException; import lombok.RequiredArgsConstructor; import org.ruoyi.common.core.domain.R; import org.ruoyi.common.core.validate.AddGroup; @@ -14,6 +17,7 @@ import org.ruoyi.common.web.core.BaseController; import org.ruoyi.core.page.PageQuery; import org.ruoyi.core.page.TableDataInfo; import org.ruoyi.domain.PdfFileContentResult; import org.ruoyi.domain.bo.KnowledgeAttachBo; import org.ruoyi.domain.bo.KnowledgeFragmentBo; import org.ruoyi.domain.bo.KnowledgeInfoBo; @@ -24,6 +28,7 @@ import org.ruoyi.service.IKnowledgeAttachService; import org.ruoyi.service.IKnowledgeFragmentService; import org.ruoyi.service.IKnowledgeInfoService; import org.ruoyi.service.PdfImageExtractService; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; @@ -41,117 +46,135 @@ @RequestMapping("/knowledge") public class KnowledgeController extends BaseController { private final IKnowledgeInfoService knowledgeInfoService; private final IKnowledgeInfoService knowledgeInfoService; private final IKnowledgeAttachService attachService; private final IKnowledgeAttachService attachService; private final IKnowledgeFragmentService fragmentService; private final IKnowledgeFragmentService fragmentService; /** * æ ¹æ®ç¨æ·ä¿¡æ¯æ¥è¯¢æ¬å°ç¥è¯åº */ @GetMapping("/list") public TableDataInfo<KnowledgeInfoVo> list(KnowledgeInfoBo bo, PageQuery pageQuery) { if (!StpUtil.isLogin()) { throw new SecurityException("请å å»ç»å½!"); } bo.setUid(LoginHelper.getUserId()); return knowledgeInfoService.queryPageList(bo, pageQuery); private final PdfImageExtractService pdfImageExtractService; /** * æ ¹æ®ç¨æ·ä¿¡æ¯æ¥è¯¢æ¬å°ç¥è¯åº */ @GetMapping("/list") public TableDataInfo<KnowledgeInfoVo> list(KnowledgeInfoBo bo, PageQuery pageQuery) { if (!StpUtil.isLogin()) { throw new SecurityException("请å å»ç»å½!"); } bo.setUid(LoginHelper.getUserId()); return knowledgeInfoService.queryPageList(bo, pageQuery); } /** * æ°å¢ç¥è¯åº */ @Log(title = "ç¥è¯åº", businessType = BusinessType.INSERT) @PostMapping("/save") public R<Void> save(@Validated(AddGroup.class) @RequestBody KnowledgeInfoBo bo) { knowledgeInfoService.saveOne(bo); return R.ok(); } /** * æ°å¢ç¥è¯åº */ @Log(title = "ç¥è¯åº", businessType = BusinessType.INSERT) @PostMapping("/save") public R<Void> save(@Validated(AddGroup.class) @RequestBody KnowledgeInfoBo bo) { knowledgeInfoService.saveOne(bo); return R.ok(); } /** * å é¤ç¥è¯åº */ @PostMapping("/remove/{id}") public R<String> remove(@PathVariable String id) { knowledgeInfoService.removeKnowledge(id); return R.ok("å é¤ç¥è¯åºæå!"); } /** * å é¤ç¥è¯åº */ @PostMapping("/remove/{id}") public R<String> remove(@PathVariable String id) { knowledgeInfoService.removeKnowledge(id); return R.ok("å é¤ç¥è¯åºæå!"); } /** * ä¿®æ¹ç¥è¯åº */ @Log(title = "ç¥è¯åº", businessType = BusinessType.UPDATE) @PostMapping("/edit") public R<Void> edit(@RequestBody KnowledgeInfoBo bo) { return toAjax(knowledgeInfoService.updateByBo(bo)); } /** * ä¿®æ¹ç¥è¯åº */ @Log(title = "ç¥è¯åº", businessType = BusinessType.UPDATE) @PostMapping("/edit") public R<Void> edit(@RequestBody KnowledgeInfoBo bo) { return toAjax(knowledgeInfoService.updateByBo(bo)); } /** * 导åºç¥è¯åºå表 */ @Log(title = "ç¥è¯åº", businessType = BusinessType.EXPORT) @PostMapping("/export") public void export(KnowledgeInfoBo bo, HttpServletResponse response) { List<KnowledgeInfoVo> list = knowledgeInfoService.queryList(bo); ExcelUtil.exportExcel(list, "ç¥è¯åº", KnowledgeInfoVo.class, response); } /** * 导åºç¥è¯åºå表 */ @Log(title = "ç¥è¯åº", businessType = BusinessType.EXPORT) @PostMapping("/export") public void export(KnowledgeInfoBo bo, HttpServletResponse response) { List<KnowledgeInfoVo> list = knowledgeInfoService.queryList(bo); ExcelUtil.exportExcel(list, "ç¥è¯åº", KnowledgeInfoVo.class, response); } /** * æ¥è¯¢ç¥è¯éä»¶ä¿¡æ¯ */ @GetMapping("/detail/{kid}") public TableDataInfo<KnowledgeAttachVo> attach(KnowledgeAttachBo bo, PageQuery pageQuery, @PathVariable String kid) { bo.setKid(kid); return attachService.queryPageList(bo, pageQuery); } /** * æ¥è¯¢ç¥è¯éä»¶ä¿¡æ¯ */ @GetMapping("/detail/{kid}") public TableDataInfo<KnowledgeAttachVo> attach(KnowledgeAttachBo bo, PageQuery pageQuery, @PathVariable String kid) { bo.setKid(kid); return attachService.queryPageList(bo, pageQuery); } /** * ä¸ä¼ ç¥è¯åºéä»¶ */ @PostMapping(value = "/attach/upload") public R<String> upload(KnowledgeInfoUploadBo bo) { knowledgeInfoService.upload(bo); return R.ok("ä¸ä¼ ç¥è¯åºéä»¶æå!"); } /** * ä¸ä¼ ç¥è¯åºéä»¶ */ @PostMapping(value = "/attach/upload") public R<String> upload(KnowledgeInfoUploadBo bo) { knowledgeInfoService.upload(bo); return R.ok("ä¸ä¼ ç¥è¯åºéä»¶æå!"); } /** * è·åç¥è¯åºé件详ç»ä¿¡æ¯ * * @param id ä¸»é® */ @GetMapping("attach/info/{id}") public R<KnowledgeAttachVo> getAttachInfo(@NotNull(message = "主é®ä¸è½ä¸ºç©º") @PathVariable Long id) { return R.ok(attachService.queryById(id)); } /** * è·åç¥è¯åºé件详ç»ä¿¡æ¯ * * @param id ä¸»é® */ @GetMapping("attach/info/{id}") public R<KnowledgeAttachVo> getAttachInfo(@NotNull(message = "主é®ä¸è½ä¸ºç©º") @PathVariable Long id) { return R.ok(attachService.queryById(id)); } /** * å é¤ç¥è¯åºéä»¶ */ @PostMapping("attach/remove/{kid}") public R<Void> removeAttach(@NotEmpty(message = "主é®ä¸è½ä¸ºç©º") @PathVariable String kid) { attachService.removeKnowledgeAttach(kid); return R.ok(); } /** * å é¤ç¥è¯åºéä»¶ */ @PostMapping("attach/remove/{kid}") public R<Void> removeAttach(@NotEmpty(message = "主é®ä¸è½ä¸ºç©º") @PathVariable String kid) { attachService.removeKnowledgeAttach(kid); return R.ok(); } /** * æ¥è¯¢ç¥è¯ç段 */ @GetMapping("/fragment/list/{docId}") public TableDataInfo<KnowledgeFragmentVo> fragmentList(KnowledgeFragmentBo bo, PageQuery pageQuery, @PathVariable String docId) { bo.setDocId(docId); return fragmentService.queryPageList(bo, pageQuery); } /** * æ¥è¯¢ç¥è¯ç段 */ @GetMapping("/fragment/list/{docId}") public TableDataInfo<KnowledgeFragmentVo> fragmentList(KnowledgeFragmentBo bo, PageQuery pageQuery, @PathVariable String docId) { bo.setDocId(docId); return fragmentService.queryPageList(bo, pageQuery); } /** * ä¸ä¼ æä»¶ç¿»è¯ */ @PostMapping("/translationByFile") @ResponseBody public String translationByFile(@RequestParam("file") MultipartFile file, String targetLanguage) { return attachService.translationByFile(file, targetLanguage); } /** * ä¸ä¼ æä»¶ç¿»è¯ */ @PostMapping("/translationByFile") @ResponseBody public String translationByFile(@RequestParam("file") MultipartFile file, String targetLanguage) { return attachService.translationByFile(file, targetLanguage); } /** * æåPDFä¸çå¾çå¹¶è°ç¨gpt-4o-mini,è¯å«å¾çå 容并è¿å * * @param file PDFæä»¶ * @return ä¿åçæä»¶è·¯å¾ä¿¡æ¯ */ @PostMapping("/extract-images") @Operation(summary = "æåPDFä¸çå¾çå¹¶è°ç¨å¤§æ¨¡å,è¯å«å¾çå 容并è¿å", description = "æåPDFä¸çå¾çå¹¶è°ç¨gpt-4o-mini,è¯å«å¾çå 容并è¿å") public R<List<PdfFileContentResult>> extractImages( @RequestPart("file") MultipartFile file ) throws IOException { return R.ok(pdfImageExtractService.extractImages(file)); } }