jeecg-module-system/jeecg-system-biz/pom.xml
@@ -41,6 +41,12 @@ <version>2.5.1</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>3.3.2</version> </dependency> <!-- AI大模型管理 --> <dependency> <groupId>org.jeecgframework.boot</groupId> jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/config/AsyncConfig.java
New file @@ -0,0 +1,36 @@ package org.jeecg.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import java.util.concurrent.Executor; import java.util.concurrent.ThreadPoolExecutor; @Configuration @EnableAsync // 开启全局异步支持 public class AsyncConfig { /** * 自定义文案生成异步线程池 */ @Bean(name = "copywritingAsyncExecutor") public Executor copywritingAsyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); // 核心线程数 executor.setCorePoolSize(5); // 最大线程数 executor.setMaxPoolSize(20); // 队列容量 executor.setQueueCapacity(100); // 线程名称前缀 executor.setThreadNamePrefix("CopyGenerate-Async-"); // 线程空闲时间(秒) executor.setKeepAliveSeconds(60); // 拒绝策略:当线程池满时,由调用线程处理(避免任务丢失) executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); // 初始化 executor.initialize(); return executor; } } jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/demo/contract/controller/ContractController.java
@@ -276,7 +276,7 @@ BeanUtils.copyProperties(contractPage, contract); contract.setContractCode(getFirstLetter(contract.getContractName())); contractService.saveMain(contract, contractPage.getContractFileList(),contractPage.getSemanticWordList()); addContract(contract,contractPage.getSemanticWordList()); // addContract(contract,contractPage.getSemanticWordList()); @@ -745,7 +745,7 @@ contractFile.setContractId(one1.getId()); contractFileService.save(contractFile); } addContract(contract,contract.getSemanticWordObjs()); // addContract(contract,contract.getSemanticWordObjs()); return Result.OK("添加成功!"); } jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/demo/contract/controller/ContractParam.java
New file @@ -0,0 +1,67 @@ package org.jeecg.modules.demo.contract.controller; public class ContractParam { private String contractId; private String fileName; private String youshang; private String wenti; private String copywritingRequirements; private String auditor; private String auditorName; public String getContractId() { return contractId; } public void setContractId(String contractId) { this.contractId = contractId; } public String getFileName() { return fileName; } public void setFileName(String fileName) { this.fileName = fileName; } public String getYoushang() { return youshang; } public void setYoushang(String youshang) { this.youshang = youshang; } public String getWenti() { return wenti; } public void setWenti(String wenti) { this.wenti = wenti; } public String getCopywritingRequirements() { return copywritingRequirements; } public void setCopywritingRequirements(String copywritingRequirements) { this.copywritingRequirements = copywritingRequirements; } public String getAuditor() { return auditor; } public void setAuditor(String auditor) { this.auditor = auditor; } public String getAuditorName() { return auditorName; } public void setAuditorName(String auditorName) { this.auditorName = auditorName; } } jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/demo/contract/controller/ExcelImportController.java
New file @@ -0,0 +1,153 @@ package org.jeecg.modules.demo.contract.controller; import com.alibaba.excel.EasyExcel; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.toolkit.SerializationUtils; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import jakarta.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; import org.apache.commons.beanutils.BeanUtils; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.jeecg.common.api.vo.Result; import org.jeecg.modules.demo.contract.entity.ExcelDataDTO; import org.jeecg.modules.demo.contract.entity.SemanticWord; import org.jeecg.modules.demo.contract.service.ISemanticWordService; import org.jeecg.modules.demo.copywriting.controller.CopywritingAsyncService; import org.jeecg.modules.listener.ExcelDataListener; import org.jeecg.modules.system.entity.SysUser; import org.jeecg.modules.system.service.ISysUserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.List; /** * 适配新Excel列的导入接口控制器 */ @RestController @RequestMapping("/api/excel") @Slf4j public class ExcelImportController { @Autowired private ISysUserService sysUserService; @Autowired private ISemanticWordService semanticWordService; /** * Excel文件导入接口(适配大类、品牌等五列) * @return 导入结果 */ @RequiresPermissions("contract:contract:importContract") @PostMapping("/importContract") public Result<String> importExcel(@RequestPart SemanticWord importParam, @RequestPart MultipartFile file, HttpServletRequest req) { // 1. 文件基础校验 if (file.isEmpty()) { return Result.error("上传的文件不能为空"); } String fileName = file.getOriginalFilename(); if (fileName == null || (!fileName.endsWith(".xlsx") && !fileName.endsWith(".xls"))) { return Result.error("仅支持上传Excel文件(.xlsx/.xls)"); } // 2. 解析Excel文件 ExcelDataListener listener = new ExcelDataListener(); try { EasyExcel.read(file.getInputStream(), ExcelDataDTO.class, listener) .sheet() // 读取第一个sheet(如需指定sheet,可传sheet名称/索引,如.sheet("数据sheet")) .doRead(); // 3. 获取解析后的数据,可执行业务逻辑 List<ExcelDataDTO> dataList = listener.getDataList(); dataList.remove(0); List<SemanticWord> semanticWords = new ArrayList<>(); int i = 0; for (ExcelDataDTO data : dataList) { LambdaQueryWrapper<SemanticWord> sem = new LambdaQueryWrapper<>(); sem.eq(SemanticWord::getContractId, importParam.getContractId()); sem.eq(SemanticWord::getWord, data.getLeakWord()); sem.eq(SemanticWord::getOutWord, data.getSemanticQuestion()); long count = semanticWordService.count(sem); if (count > 0) { i ++; } else { SemanticWord copy = new SemanticWord(); BeanUtils.copyProperties(copy, importParam); copy.setContractId(importParam.getContractId()); copy.setBrand(data.getBrand()); copy.setCategoryOne(data.getCategory()); copy.setWord(data.getLeakWord()); copy.setOutWord(data.getSemanticQuestion()); QueryWrapper qw = new QueryWrapper<SysUser>(); qw.eq("realName", data.getCreator()); Page result = (Page) sysUserService.queryPageList(req, qw, 2, 1).getResult(); if (result.getTotal() != 1) { return Result.error("未查询到用户:" + data.getCreator()); } String userId = ((SysUser)(result).getRecords().get(0)).getId(); copy.setChanger(userId); semanticWords.add(copy); } } semanticWordService.saveBatch(semanticWords); // 示例:调用Service保存数据(替换为你的实际业务逻辑) // excelDataService.saveBatch(dataList); return Result.ok("Excel导入成功!共导入" + (dataList.size() - i) + "条数据,重复" + i + "条数据"); } catch (IOException e) { log.error("文件读取失败", e); return Result.error("文件读取失败:" + e.getMessage()); } catch (RuntimeException e) { log.error("数据解析失败", e); return Result.error("数据解析失败:" + e.getMessage()); } catch (InvocationTargetException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); } } @Autowired private CopywritingAsyncService copywritingAsyncService; @RequiresPermissions("contract:contract:batchGenerateCopy") @RequestMapping(value = "/batchGenerateCopy", method = RequestMethod.POST) public Result<?> batchGenerateCopy(@RequestBody ContractParam cp) { if (cp.getContractId().isEmpty()) { return Result.error("文案编号不能为空"); } else if (cp.getFileName().isEmpty()) { return Result.error("文件不能为空"); } else if (cp.getYoushang().isEmpty()) { return Result.error("友商不能为空"); } else if (cp.getWenti().isEmpty()) { return Result.error("问题描述不能为空"); } else if (cp.getCopywritingRequirements().isEmpty()) { return Result.error("文案要求不能为空"); } else if (cp.getAuditor().isEmpty() || cp.getAuditorName().isEmpty()) { return Result.error("用户不能为空"); } QueryWrapper<SemanticWord> semanticWordQueryWrapper = new QueryWrapper<>(); semanticWordQueryWrapper.eq("contract_id", cp.getContractId()); List<SemanticWord> list = semanticWordService.list(semanticWordQueryWrapper); if (list.isEmpty()) { return Result.error("该合同不存在语义词"); } else { // 调用异步方法执行实际生成逻辑 copywritingAsyncService.asyncBatchGenerateCopy( cp.getContractId(), list, cp.getFileName(), cp.getYoushang(), cp.getWenti(), cp.getCopywritingRequirements(),cp.getAuditor(), cp.getAuditorName() ); // 立即返回响应,不等待生成完成 return Result.OK("开始生成,任务已提交至后台处理"); } } } jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/demo/contract/entity/ExcelDataDTO.java
New file @@ -0,0 +1,26 @@ package org.jeecg.modules.demo.contract.entity; import com.alibaba.excel.annotation.ExcelProperty; import lombok.Data; /** * 适配新Excel列的映射实体类 */ @Data public class ExcelDataDTO { // value:Excel列名,index:列索引(从0开始,对应Excel的A、B、C、D、E列) @ExcelProperty(value = "大类", index = 0) private String category; // 大类 @ExcelProperty(value = "品牌(仅比价/省钱类)", index = 1) private String brand; // 品牌(仅比价/省钱类) @ExcelProperty(value = "语义问法", index = 2) private String semanticQuestion; // 语义问法 @ExcelProperty(value = "漏出词", index = 3) private String leakWord; // 漏出词 @ExcelProperty(value = "创作人员", index = 4) private String creator; // 创作人员 } jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/demo/contract/entity/SemanticWord.java
@@ -9,6 +9,7 @@ import org.springframework.format.annotation.DateTimeFormat; import org.jeecgframework.poi.excel.annotation.Excel; import io.swagger.v3.oas.annotations.media.Schema; import org.springframework.web.multipart.MultipartFile; import java.util.List; @@ -165,4 +166,12 @@ @Excel(name = "掉词记录数", width = 15) @Schema(description = "掉词记录数") private java.lang.String count; @Excel(name = "大类", width = 15) @Schema(description = "大类") private java.lang.String categoryOne; @Excel(name = "品牌", width = 15) @Schema(description = "品牌") private java.lang.String brand; } jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/demo/copywriting/controller/CopywritingAsyncService.java
New file @@ -0,0 +1,96 @@ package org.jeecg.modules.demo.copywriting.controller; import cn.hutool.core.date.DateUtil; import org.jeecg.common.api.vo.Result; import org.jeecg.modules.demo.contract.entity.SemanticWord; import org.jeecg.modules.demo.copywriting.entity.CopyGenerateTask; import org.jeecg.modules.demo.copywriting.entity.Copywriting; import org.jeecg.modules.demo.copywriting.service.ICopywritingService; import org.jeecg.modules.demo.copywriting.service.impl.CopyGenerateTaskServiceImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.UUID; @Service public class CopywritingAsyncService { @Autowired private CopyGenerateTaskServiceImpl taskService; // 新增的任务状态服务 // 其他依赖... @Autowired private CopywritingController copywritingController; @Autowired public ICopywritingService copywritingService; @Async("copywritingAsyncExecutor") public void asyncBatchGenerateCopy(String contractId, List<SemanticWord> semanticWords, String fileName, String youshang, String wenti, String wenanyaoqiu, String auditor, String auditorName) { // 1. 生成唯一任务ID,创建待处理任务 String taskId = UUID.randomUUID().toString().replace("-", ""); CopyGenerateTask task = new CopyGenerateTask(); task.setId(taskId); task.setContractId(contractId); task.setStatus("PENDING"); taskService.save(task); try { // 2. 更新为处理中 task.setStatus("PROCESSING"); taskService.updateById(task); // 3. 执行实际的文案生成逻辑(你的核心业务) System.out.println("开始处理合同[" + contractId + "]的文案生成"); for (SemanticWord semanticWord : semanticWords) { Copywriting copywriting = new Copywriting(); Result title = copywritingController.getResult(semanticWord.getOutWord(), semanticWord.getWord(), DateUtil.format(new Date(), new SimpleDateFormat("yyyy-MM-dd")), DateUtil.format(new Date(), new SimpleDateFormat("yyyy-MM-dd")), "e9ca23d68d884d4ebb19d07889727dae"); if (title.isSuccess()) { copywriting.setTitle(title.getResult().toString()); } copywriting.setStatus("1"); copywriting.setWordId(semanticWord.getId()); Result text = copywritingController.getResult( fileName, wenanyaoqiu, semanticWord.getOutWord(), youshang, wenti, "e9ca23d68d884d4ebb19d07889727dae"); if (text.isSuccess()) { copywriting.setText(text.getResult().toString()); } copywriting.setAuditer(auditor); copywriting.setOutStatus("1"); copywriting.setCreateBy(auditorName); copywritingService.save(copywriting); } // 假设生成成功,这里可以记录生成结果 task.setStatus("SUCCESS"); task.setMessage("文案生成完成,共生成" + semanticWords.size() + "条"); task.setFinishTime(new Date()); System.out.println("完成处理合同[" + contractId + "]的文案生成"); } catch (Exception e) { // 4. 异常时更新为失败状态,记录原因 task.setStatus("FAIL"); task.setMessage("生成失败:" + e.getMessage()); task.setFinishTime(new Date()); e.printStackTrace(); } finally { // 5. 最终更新任务状态 taskService.updateById(task); } } } jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/demo/copywriting/controller/CopywritingController.java
@@ -13,6 +13,7 @@ import opennlp.tools.dictionary.serializer.Entry; import org.jeecg.modules.system.entity.SysUser; import org.jeecg.modules.system.service.ISysUserService; import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.ByteArrayResource; import org.springframework.http.*; @@ -44,6 +45,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.*; @@ -66,9 +68,10 @@ @RestController @RequestMapping("/copywriting/copywriting") @Slf4j @EnableAsync public class CopywritingController extends JeecgController<Copywriting, ICopywritingService> { @Autowired private ICopywritingService copywritingService; public ICopywritingService copywritingService; @Autowired private ISemanticWordService semanticWordService;; @Autowired @@ -272,6 +275,10 @@ @RequestParam String youshang, @RequestParam String wenti, @RequestParam String user) { return getResult(jianli, wenanyaoqiu, louchu, youshang, wenti, user); } public Result<?> getResult(String jianli, String wenanyaoqiu, String louchu, String youshang, String wenti, String user) { if (jianli == null || jianli.equals("")) { return Result.error("请选择文件"); } @@ -410,6 +417,8 @@ return Result.error("生成文案异常:" + e.getMessage()); } } /** * 新增的生成标题接口方法 */ @@ -422,6 +431,10 @@ @RequestParam String endTime, @RequestParam String user) { // 保留user参数,用于接口鉴权/归属 return getResult(louchu, yuyici, startTime, endTime, user); } public static Result<?> getResult(String louchu, String yuyici, String startTime, String endTime, String user) { // 2. 配置固定参数(和原有方法保持一致,可根据实际情况调整) String workflowUrl = "http://14.103.174.44/v1/workflows/run"; // 标题生成的工作流地址,若和文案不同需修改 String authToken = "app-F09iyl3p5448JoKufR2CRpWG"; jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/demo/copywriting/entity/CopyGenerateTask.java
New file @@ -0,0 +1,85 @@ package org.jeecg.modules.demo.copywriting.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import java.io.Serializable; import java.util.Date; /** * 文案生成任务状态表 * * @TableName copy_generate_task */ @Data @TableName(value = "copy_generate_task") public class CopyGenerateTask implements Serializable { /** * 任务ID */ @TableId(type = IdType.ASSIGN_ID) // 如果ID是手动生成,可改为IdType.INPUT private String id; /** * 合同ID */ @TableField(value = "contract_id") private String contractId; /** * 状态:PENDING-待处理、PROCESSING-处理中、SUCCESS-成功、FAIL-失败 */ @TableField(value = "status") private String status; /** * 结果信息/失败原因 */ @TableField(value = "message") private String message; /** * 创建时间 */ @TableField(value = "create_time") private Date createTime; /** * 完成时间 */ @TableField(value = "finish_time") private Date finishTime; /** * 序列化版本号(MyBatis-Plus 实体类建议添加) */ @TableField(exist = false) private static final long serialVersionUID = 1L; // 推荐:添加状态枚举(提升代码可读性和规范性) public enum TaskStatus { PENDING("PENDING", "待处理"), PROCESSING("PROCESSING", "处理中"), SUCCESS("SUCCESS", "成功"), FAIL("FAIL", "失败"); private final String code; private final String desc; TaskStatus(String code, String desc) { this.code = code; this.desc = desc; } public String getCode() { return code; } public String getDesc() { return desc; } } } jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/demo/copywriting/mapper/CopyGenerateTaskMapper.java
New file @@ -0,0 +1,15 @@ package org.jeecg.modules.demo.copywriting.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.jeecg.modules.demo.copywriting.entity.CopyGenerateTask; import org.jeecg.modules.demo.copywriting.entity.Copywriting; /** * @Description: 文案 * @Author: jeecg-boot * @Date: 2025-10-13 * @Version: V1.0 */ public interface CopyGenerateTaskMapper extends BaseMapper<CopyGenerateTask> { } jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/demo/copywriting/mapper/xml/CopyGenerateTaskMapper.xml
New file @@ -0,0 +1,5 @@ <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="org.jeecg.modules.demo.copywriting.mapper.CopyGenerateTaskMapper"> </mapper> jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/demo/copywriting/service/ICopyGenerateTaskService.java
New file @@ -0,0 +1,15 @@ package org.jeecg.modules.demo.copywriting.service; import com.baomidou.mybatisplus.extension.service.IService; import org.jeecg.modules.demo.copywriting.entity.CopyGenerateTask; import org.jeecg.modules.demo.copywriting.entity.Copywriting; /** * @Description: 文案 * @Author: jeecg-boot * @Date: 2025-10-13 * @Version: V1.0 */ public interface ICopyGenerateTaskService extends IService<CopyGenerateTask> { } jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/demo/copywriting/service/impl/CopyGenerateTaskServiceImpl.java
New file @@ -0,0 +1,21 @@ package org.jeecg.modules.demo.copywriting.service.impl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.jeecg.modules.demo.copywriting.entity.CopyGenerateTask; import org.jeecg.modules.demo.copywriting.entity.Copywriting; import org.jeecg.modules.demo.copywriting.mapper.CopyGenerateTaskMapper; import org.jeecg.modules.demo.copywriting.mapper.CopywritingMapper; import org.jeecg.modules.demo.copywriting.service.ICopyGenerateTaskService; import org.jeecg.modules.demo.copywriting.service.ICopywritingService; import org.springframework.stereotype.Service; /** * @Description: 文案 * @Author: jeecg-boot * @Date: 2025-10-13 * @Version: V1.0 */ @Service public class CopyGenerateTaskServiceImpl extends ServiceImpl<CopyGenerateTaskMapper, CopyGenerateTask> implements ICopyGenerateTaskService { } jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/demo/semanticword/controller/SemanticWordController.java
@@ -86,7 +86,9 @@ }else { queryWrapper = QueryGenerator.initQueryWrapper(semanticWord, req.getParameterMap(),customeRuleMap); } // if (!semanticWord.getStatus().isEmpty()) { // queryWrapper.eq("status",semanticWord.getStatus()); // } if (semanticWord.getUser() != null && !semanticWord.getUser().equals("admin")) { queryWrapper.eq("create_by", semanticWord.getUser()); } jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/listener/ExcelDataListener.java
New file @@ -0,0 +1,50 @@ package org.jeecg.modules.listener; import com.alibaba.excel.context.AnalysisContext; import com.alibaba.excel.event.AnalysisEventListener; import lombok.extern.slf4j.Slf4j; import org.jeecg.modules.demo.contract.entity.ExcelDataDTO; import java.util.ArrayList; import java.util.List; /** * 适配新Excel列的读取监听器 */ @Slf4j public class ExcelDataListener extends AnalysisEventListener<ExcelDataDTO> { // 存储解析后的所有数据 private List<ExcelDataDTO> dataList = new ArrayList<>(); /** * 每解析一行数据调用此方法,可添加数据校验 */ @Override public void invoke(ExcelDataDTO data, AnalysisContext context) { log.info("解析到一行数据:{}", data); // 核心字段非空校验(可根据业务调整) if (data.getCategory() == null || data.getCategory().trim().isEmpty()) { throw new RuntimeException("第" + context.readRowHolder().getRowIndex() + "行:大类不能为空"); } if (data.getSemanticQuestion() == null || data.getSemanticQuestion().trim().isEmpty()) { throw new RuntimeException("第" + context.readRowHolder().getRowIndex() + "行:语义问法不能为空"); } dataList.add(data); } /** * 所有数据解析完成后调用 */ @Override public void doAfterAllAnalysed(AnalysisContext context) { log.info("Excel解析完成,共解析{}条有效数据", dataList.size()); // 此处可添加批量保存到数据库的逻辑(如调用Service) // 示例:excelDataService.batchSave(dataList); } // 获取解析后的数据列表 public List<ExcelDataDTO> getDataList() { return dataList; } }