Commit 234f9289 authored by 林洋洋's avatar 林洋洋

添加使用工具接口

parent 725a962d
...@@ -119,7 +119,12 @@ ...@@ -119,7 +119,12 @@
<dependency> <dependency>
<groupId>org.apache.poi</groupId> <groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId> <artifactId>poi-ooxml</artifactId>
<version>5.2.5</version> <version>5.2.2</version>
</dependency>
<dependency>
<groupId>com.deepoove</groupId>
<artifactId>poi-tl</artifactId>
<version>1.12.1</version>
</dependency> </dependency>
</dependencies> </dependencies>
<repositories> <repositories>
......
...@@ -169,24 +169,10 @@ public class ChatController { ...@@ -169,24 +169,10 @@ public class ChatController {
Message userMessage = new UserMessage("问题:" + message + "\n回答要求:请使用markdown格式输出"); Message userMessage = new UserMessage("问题:" + message + "\n回答要求:请使用markdown格式输出");
Prompt prompt = new Prompt(List.of(systemMessage, userMessage)); Prompt prompt = new Prompt(List.of(systemMessage, userMessage));
// ChatResponse chatResponse = openAiChatClient.prompt(prompt)
// .advisors(messageChatMemoryAdvisor)
// .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, conversationId))
// .tools(new ExcelTools())
// // 关闭内部自动执行
// .options(
// ToolCallingChatOptions.builder()
// .internalToolExecutionEnabled(false) // ← 关键
// .build()
// )
// .call().chatResponse();
return openAiChatClient.prompt(prompt) return openAiChatClient.prompt(prompt)
.advisors(messageChatMemoryAdvisor) .advisors(messageChatMemoryAdvisor)
.advisors(a -> a.param(ChatMemory.CONVERSATION_ID, conversationId)) .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, conversationId))
.tools(new ExcelTools()) .tools(excelTools)
.call() .call()
.content(); .content();
......
package com.ask.tools; package com.ask.tools;
import cn.hutool.json.JSONObject;
import com.ask.utils.ConvertUtils;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.tool.annotation.Tool; import org.springframework.ai.tool.annotation.Tool;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.sql.Date;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.format.SignStyle;
import java.time.temporal.ChronoField;
import java.util.HashMap;
import java.util.Map;
@Component @Component
@Slf4j @Slf4j
public class ExcelTools { public class ExcelTools {
@Tool(description = "Get the current date and time in the user's timezone") @Autowired
String getCurrentDateTime() { private ConvertUtils convertUtils;
log.info("调用getCurrentDateTime");
return LocalDateTime.now().atZone(LocaleContextHolder.getTimeZone().toZoneId()).toString(); private String formatLocalDate(LocalDate date) {
// 创建自定义格式器:2位年份.单/双位月.单/双位日
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
// 年份取后两位(2025 -> 25)
.appendValueReduced(ChronoField.YEAR, 2, 2, 2000)
.appendLiteral('.')
// 月份不补零(1-12)
.appendValue(ChronoField.MONTH_OF_YEAR, 1, 2, SignStyle.NEVER)
.appendLiteral('.')
// 日期不补零(1-31)
.appendValue(ChronoField.DAY_OF_MONTH, 1, 2, SignStyle.NEVER)
.toFormatter();
return date.format(formatter);
}
@Tool(description = "Get the production records word")
public String getProductionFile(String dateStr) {
log.info("getProductionFile{}", dateStr);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-M-d");
LocalDate date = LocalDate.parse(dateStr, formatter);
String dateFileStr = formatLocalDate(date);
// 动态生成文件名
String fileName = String.format("科环集团电力运营日报(%s).docx", dateFileStr);
Map<String, Object> paramMap = new HashMap<String, Object>();
paramMap.put("year", date.getYear());
paramMap.put("month", date.getMonthValue());
paramMap.put("day", date.getDayOfMonth());
String bucketName = "report/production";
boolean result = convertUtils.fillWordLoop(fileName, "科环集团电力运营日报模板.docx", paramMap,bucketName);
JSONObject jsonObject =new JSONObject();
if(result){
jsonObject.set("success",true);
jsonObject.set("fileName",fileName);
jsonObject.set("filePath","/admin/"+bucketName+"/"+fileName);
}else{
jsonObject.set("success",false);
jsonObject.set("fileName","");
jsonObject.set("filePath","");
}
return jsonObject.toString();
} }
} }
package com.ask.utils;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.ask.common.core.FileTemplate;
import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.config.Configure;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.stereotype.Component;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.Map;
@Component
@Slf4j
public class ConvertUtils {
@Autowired
private ResourceLoader resourceLoader;
@Autowired
private FileTemplate fileTemplate;
/**
* 填充word并上传到本地OSS
*
* @param templatePath 模板路径
* @param paramMap 参数映射
* @return {@link String} 返回OSS中的文件路径
*/
private boolean fillWord(String fileName,String templatePath, Map<String, Object> paramMap,String bucketName) {
// 载入Word文档
if(fileTemplate.isFileExists(bucketName,fileName)) {
return true;
}
Resource resource = resourceLoader.getResource("classpath:" + templatePath);
// OSS中的文件路径
try (InputStream resourceStream =resource.getInputStream();
XWPFTemplate template = XWPFTemplate.compile(resourceStream).render(paramMap);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
template.write(byteArrayOutputStream);
byteArrayOutputStream.flush();
// 直接使用字节数组上传,避免额外的ByteArrayInputStream
byte[] content = byteArrayOutputStream.toByteArray();
fileTemplate.putObject(bucketName, fileName, new ByteArrayInputStream(content), "");
} catch (IOException e) {
// 添加具体异常处理逻辑
log.error("文档生成或上传失败: {}", e.getMessage(), e);
return false;
} catch (Exception e) {
return false;
}
return true;
}
/**
* 填充单词循环
* 填充
*
* @param templatePath 模板路径
* @param paramMap 参数映射
* @return {@link String}
*/
public boolean fillWordLoop(String fileName, String templatePath, Map<String, Object> paramMap, String bucketName) {
return fillWord(fileName,templatePath, paramMap,bucketName);
}
}
package com.ask.utils;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
@Component
public class SpringContextUtils implements ApplicationContextAware {
/**
* 上下文对象实例
*/
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringContextUtils.applicationContext = applicationContext;
}
/**
* 获取applicationContext
*
* @return
*/
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
/**
* 获取HttpServletRequest
*/
public static HttpServletRequest getHttpServletRequest() {
return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
}
public static String getDomain(){
HttpServletRequest request = getHttpServletRequest();
StringBuffer url = request.getRequestURL();
return url.delete(url.length() - request.getRequestURI().length(), url.length()).toString();
}
public static String getOrigin(){
HttpServletRequest request = getHttpServletRequest();
return request.getHeader("Origin");
}
/**
* 通过name获取 Bean.
*
* @param name
* @return
*/
public static Object getBean(String name) {
return getApplicationContext().getBean(name);
}
/**
* 通过class获取Bean.
*
* @param clazz
* @param <T>
* @return
*/
public static <T> T getBean(Class<T> clazz) {
return getApplicationContext().getBean(clazz);
}
/**
* 通过name,以及Clazz返回指定的Bean
*
* @param name
* @param clazz
* @param <T>
* @return
*/
public static <T> T getBean(String name, Class<T> clazz) {
return getApplicationContext().getBean(name, clazz);
}
}
package com.ask.utils;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.apache.poi.ooxml.POIXMLDocument;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableCell;
import org.apache.poi.xwpf.usermodel.XWPFTableRow;
public class WordUtil {
/**
* 根据模板生成新word文档
* 对表格是进行替换以及插入
* @param inputUrl 模板存放地址
* @param outputUrl 新文档存放地址
* @param textMap 需要替换的信息集合
* @param tableList 需要插入的表格信息集合
* @return 成功返回true,失败返回false
*/
public static boolean changWord(String inputUrl, String outputUrl,
Map<String, String> textMap, List<String[]> tableList) {
//模板转换默认成功
boolean changeFlag = true;
try {
//获取docx解析对象
XWPFDocument document = new XWPFDocument(POIXMLDocument.openPackage(inputUrl));
//解析替换文本段落对象
WordUtil.changeText(document, textMap);
//解析替换表格对象
WordUtil.changeTable(document, textMap, tableList);
//生成新的word
File file = new File(outputUrl);
FileOutputStream stream = new FileOutputStream(file);
document.write(stream);
stream.close();
} catch (IOException e) {
e.printStackTrace();
changeFlag = false;
}
return changeFlag;
}
/**
* 替换段落文本
* @param document docx解析对象
* @param textMap 需要替换的信息集合
*/
public static void changeText(XWPFDocument document, Map<String, String> textMap){
//获取段落集合
List<XWPFParagraph> paragraphs = document.getParagraphs();
for (XWPFParagraph paragraph : paragraphs) {
//判断此段落时候需要进行替换
String text = paragraph.getText();
System.out.println(text);
if(checkText(text)){
List<XWPFRun> runs = paragraph.getRuns();
for (XWPFRun run : runs) {
//替换模板原来位置
run.setText(changeValue(run.toString(), textMap),0);
}
}
}
}
/**
* 替换表格对象方法
* @param document docx解析对象
* @param textMap 需要替换的信息集合
* @param tableList 需要插入的表格信息集合
*/
public static void changeTable(XWPFDocument document, Map<String, String> textMap,
List<String[]> tableList){
//获取表格对象集合
List<XWPFTable> tables = document.getTables();
//获取第一个表格 根据实际模板情况 决定去第几个word中的表格
XWPFTable table = tables.get(0);
//表格中的内容替换
List<XWPFTableRow> rows = table.getRows();
eachTable(rows, textMap);//遍历表格,并替换模板
//向特定位置 行插入数据
insertTable(table, tableList);
}
/**
* 遍历表格
* @param rows 表格行对象
* @param textMap 需要替换的信息集合
*/
public static void eachTable(List<XWPFTableRow> rows ,Map<String, String> textMap){
for (XWPFTableRow row : rows) {
List<XWPFTableCell> cells = row.getTableCells();
for (XWPFTableCell cell : cells) {
//判断单元格是否需要替换
if(checkText(cell.getText())){
List<XWPFParagraph> paragraphs = cell.getParagraphs();
for (XWPFParagraph paragraph : paragraphs) {
List<XWPFRun> runs = paragraph.getRuns();
for (XWPFRun run : runs) {
run.setText(changeValue(run.toString(), textMap),0);
}
}
}
}
}
}
/**
* 为表格插入数据,行数不够添加新行
* @param table 需要插入数据的表格
* @param tableList 插入数据集合
*/
public static void insertTable(XWPFTable table, List<String[]> tableList){
//创建行,根据需要插入的数据添加新行,不处理表头
//判断需要插入的数量
if (tableList.size() > 5) {//我的模板预留了五行
for (int i=0;i<tableList.size()-4;i++) {
insertRow(table,10,14);
}
}
//遍历表格插入数据
List<XWPFTableRow> rows = table.getRows();
for(int i = 9; i < tableList.size() + 9; i++){
XWPFTableRow newRow = table.getRow(i);
List<XWPFTableCell> cells = newRow.getTableCells();
for(int j = 0; j < cells.size(); j++){
XWPFTableCell cell = cells.get(j);
cell.setText(tableList.get(i-9)[j]);
}
}
}
/**
* 判断文本中时候包含$
* @param text 文本
* @return 包含返回true,不包含返回false
*/
public static boolean checkText(String text){
boolean check = false;
if(text.indexOf("$")!= -1){
check = true;
}
return check;
}
/**
* insertRow 在word表格中指定位置插入一行,并将某一行的样式复制到新增行
* @param copyrowIndex 需要复制的行位置
* @param newrowIndex 需要新增一行的位置
* */
public static void insertRow(XWPFTable table, int copyrowIndex, int newrowIndex) {
// 在表格中指定的位置新增一行
XWPFTableRow targetRow = table.insertNewTableRow(newrowIndex);
// 获取需要复制行对象
XWPFTableRow copyRow = table.getRow(copyrowIndex);
//复制行对象
targetRow.getCtRow().setTrPr(copyRow.getCtRow().getTrPr());
//或许需要复制的行的列
List<XWPFTableCell> copyCells = copyRow.getTableCells();
//复制列对象
XWPFTableCell targetCell = null;
for (int i = 0; i < copyCells.size(); i++) {
XWPFTableCell copyCell = copyCells.get(i);
targetCell = targetRow.addNewTableCell();
targetCell.getCTTc().setTcPr(copyCell.getCTTc().getTcPr());
if (copyCell.getParagraphs() != null && copyCell.getParagraphs().size() > 0) {
targetCell.getParagraphs().get(0).getCTP().setPPr(copyCell.getParagraphs().get(0).getCTP().getPPr());
if (copyCell.getParagraphs().get(0).getRuns() != null
&& copyCell.getParagraphs().get(0).getRuns().size() > 0) {
XWPFRun cellR = targetCell.getParagraphs().get(0).createRun();
cellR.setBold(copyCell.getParagraphs().get(0).getRuns().get(0).isBold());
}
}
}
}
/**
* 匹配传入信息集合与模板
* @param value 模板需要替换的区域
* @param textMap 传入信息集合
* @return 模板需要替换区域信息集合对应值
*/
public static String changeValue(String value, Map<String, String> textMap){
Set<Entry<String, String>> textSets = textMap.entrySet();
for (Entry<String, String> textSet : textSets) {
//匹配模板与替换值 格式${key}
String key = "${"+textSet.getKey()+"}";
if(value.indexOf(key)!= -1){
value = textSet.getValue();
}
}
//模板未匹配到区域替换为空
if(checkText(value)){
value = "";
}
return value;
}
public static void main(String[] args) {
//模板文件地址
String inputUrl = "D:/科环集团电力运营日报模板.docx";
//新生产的模板文件
String outputUrl = "D:/科环集团电力运营日报模板1.docx";
Map<String, String> testMap = new HashMap<String, String>();
testMap.put("year", "2025");
testMap.put("month", "1");
testMap.put("day", "1");
List<String[]> testList = new ArrayList<String[]>();
// testList.add(new String[]{"1","Q1","W1","E1","R1","T1"});
// testList.add(new String[]{"2","Q2","W2","E2","R2","T2"});
// testList.add(new String[]{"3","Q3","W3","E3","R3","T3"});
// testList.add(new String[]{"4","Q4","W4","E4","R4","T4"});
// testList.add(new String[]{"5","Q5","W5","E5","R5","T5"});
// testList.add(new String[]{"6","Q6","W6","E6","R6","T6"});
// testList.add(new String[]{"7","Q7","W7","E7","R7","T7"});
WordUtil.changWord(inputUrl, outputUrl, testMap, testList);
}
}
...@@ -18,6 +18,11 @@ public interface FileTemplate extends InitializingBean { ...@@ -18,6 +18,11 @@ public interface FileTemplate extends InitializingBean {
* @param bucketName bucket名称 * @param bucketName bucket名称
*/ */
void createBucket(String bucketName); void createBucket(String bucketName);
/**
* 创建bucket
* @param bucketName bucket名称
*/
boolean isFileExists(String bucketName,String fileName);
/** /**
* 获取全部bucket * 获取全部bucket
......
...@@ -31,6 +31,12 @@ public class LocalFileTemplate implements FileTemplate { ...@@ -31,6 +31,12 @@ public class LocalFileTemplate implements FileTemplate {
FileUtil.mkdir(basePath + FileUtil.FILE_SEPARATOR + bucketName); FileUtil.mkdir(basePath + FileUtil.FILE_SEPARATOR + bucketName);
} }
@Override
public boolean isFileExists(String bucketName, String fileName) {
String dir = basePath + FileUtil.FILE_SEPARATOR + bucketName+FileUtil.FILE_SEPARATOR+fileName;
return FileUtil.exist(dir);
}
/** /**
* 获取全部bucket * 获取全部bucket
* <p> * <p>
......
...@@ -64,7 +64,9 @@ ...@@ -64,7 +64,9 @@
<resources> <resources>
<resource> <resource>
<directory>src/main/resources</directory> <directory>src/main/resources</directory>
<filtering>true</filtering> <includes>
<include>**/*</include>
</includes>
</resource> </resource>
</resources> </resources>
<plugins> <plugins>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment