| | |
| | | package org.jeecg.modules.demo.copywriting.controller; |
| | | |
| | | import java.util.Arrays; |
| | | import java.util.HashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.io.File; |
| | | import java.io.FileInputStream; |
| | | import java.util.*; |
| | | import java.util.regex.Pattern; |
| | | import java.util.stream.Collectors; |
| | | import java.io.IOException; |
| | | import java.io.UnsupportedEncodingException; |
| | | import java.net.URLDecoder; |
| | | |
| | | import com.alibaba.fastjson2.JSONObject; |
| | | import opennlp.tools.dictionary.serializer.Entry; |
| | | import org.jeecg.modules.system.entity.SysUser; |
| | | import org.jeecg.modules.system.service.ISysUserService; |
| | | import org.springframework.beans.factory.annotation.Value; |
| | | import org.springframework.core.io.ByteArrayResource; |
| | | import org.springframework.http.*; |
| | | import com.baomidou.mybatisplus.core.toolkit.StringUtils; |
| | | import com.github.yulichang.wrapper.MPJLambdaWrapper; |
| | | import jakarta.servlet.http.HttpServletRequest; |
| | |
| | | import org.jeecgframework.poi.excel.view.JeecgEntityExcelView; |
| | | import org.jeecg.common.system.base.controller.JeecgController; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.http.HttpStatus; |
| | | import org.springframework.http.ResponseEntity; |
| | | import org.springframework.http.client.SimpleClientHttpRequestFactory; |
| | | import org.springframework.util.LinkedMultiValueMap; |
| | | import org.springframework.util.MultiValueMap; |
| | | import org.springframework.web.bind.annotation.*; |
| | | import org.springframework.web.client.RestTemplate; |
| | | import org.springframework.web.multipart.MultipartFile; |
| | | import org.springframework.web.multipart.MultipartHttpServletRequest; |
| | | import org.springframework.web.servlet.ModelAndView; |
| | |
| | | @Autowired |
| | | private ICopywritingService copywritingService; |
| | | @Autowired |
| | | private ISemanticWordService semanticWordService; |
| | | private ISemanticWordService semanticWordService;; |
| | | @Autowired |
| | | private ISysUserService sysUserService; |
| | | |
| | | /** |
| | | * 分页列表查询 |
| | |
| | | return Result.OK(pageList); |
| | | } |
| | | |
| | | |
| | | @Operation(summary="文案-查询发送门户文章总量") |
| | | @GetMapping(value = "/count") |
| | | public Result<IPage<Copywriting>> count(Copywriting copywriting, |
| | | @RequestParam(name="role", defaultValue="无") String role, |
| | | @RequestParam(name="user", defaultValue="无") String user, |
| | | HttpServletRequest req) { |
| | | |
| | | QueryWrapper<Copywriting> queryWrapper = QueryGenerator.initQueryWrapper(copywriting, req.getParameterMap()); |
| | | |
| | | if (StringUtils.isNotBlank(copywriting.getTitleLike())) { |
| | | queryWrapper.like("title", copywriting.getTitleLike()); |
| | | } |
| | | if (StringUtils.isNotBlank(copywriting.getTitleLike()) && StringUtils.isNotBlank(copywriting.getWordLike())) { |
| | | queryWrapper.or(); |
| | | } |
| | | if (StringUtils.isNotBlank(copywriting.getWordLike())){ |
| | | queryWrapper.exists("SELECT 1 FROM semantic_word WHERE semantic_word.id = copywriting.word_id " + |
| | | "AND semantic_word.word LIKE '%" + copywriting.getWordLike() + "%'"); |
| | | |
| | | } |
| | | |
| | | if (!user.equals("无")){ |
| | | QueryWrapper qw = new QueryWrapper<SysUser>(); |
| | | qw.eq("id", user); |
| | | String userName = ((SysUser)((Page) sysUserService.queryPageList(req, qw, 1, 1).getResult()).getRecords().get(0)).getUsername(); |
| | | queryWrapper.eq("create_by", userName); |
| | | } |
| | | long count = copywritingService.count(queryWrapper); |
| | | return Result.OK(count+""); |
| | | } |
| | | |
| | | |
| | | |
| | | @Operation(summary="文案-查询发送门户文章总量") |
| | | @GetMapping(value = "/upAvgTime") |
| | | public Result<IPage<Copywriting>> upAvgTime(Copywriting copywriting, |
| | | HttpServletRequest req) { |
| | | return Result.OK("5"); |
| | | } |
| | | |
| | | /** |
| | | * 添加 |
| | | * |
| | |
| | | @RequiresPermissions("copywriting:copywriting:add") |
| | | @PostMapping(value = "/add") |
| | | public Result<String> add(@RequestBody Copywriting copywriting) { |
| | | if (copywriting.getOutStatus() == null) { |
| | | copywriting.setOutStatus("1"); |
| | | } |
| | | copywritingService.save(copywriting); |
| | | |
| | | return Result.OK("添加成功!"); |
| | |
| | | return super.exportXls(request, copywriting, Copywriting.class, "文案"); |
| | | } |
| | | |
| | | |
| | | @Value("${jeecg.path.upload}") |
| | | private String uploadPath; |
| | | /** |
| | | * 通过excel导入数据 |
| | | * |
| | |
| | | return super.importExcel(request, response, Copywriting.class); |
| | | } |
| | | |
| | | |
| | | @RequiresPermissions("copywriting:copywriting:aiCreateCopyWriting") |
| | | @RequestMapping(value = "/aiCreateCopyWriting", method = RequestMethod.POST) |
| | | public Result<?> aiCreateCopyWriting( |
| | | @RequestParam("jianli") String jianli, |
| | | @RequestParam String wenanyaoqiu, |
| | | @RequestParam String louchu, |
| | | @RequestParam String youshang, |
| | | @RequestParam String wenti, |
| | | @RequestParam String user) { |
| | | if (jianli == null || jianli.equals("")) { |
| | | return Result.error("请选择文件"); |
| | | } |
| | | // 配置信息 |
| | | String serverFileRoot = uploadPath; |
| | | String workflowUrl = "http://14.103.174.44/v1/workflows/run"; |
| | | String fileUploadUrl = "http://14.103.174.44/v1/files/upload"; // 文件上传接口(假设) |
| | | String authToken = "app-J1Tqytg0ZetcrVTF2fVHHY8B"; |
| | | String userId = user; |
| | | String appId = "cf85fe4d-b76b-4c4c-801a-1336c880d473"; |
| | | |
| | | try { |
| | | // 步骤1:上传简历文件,获取 upload_file_id 列表 |
| | | List<String> jianliFileList = Arrays.asList(jianli.split(",")); |
| | | List<String> jianliFileIds = new ArrayList<>(); |
| | | for (String fileName : jianliFileList) { |
| | | // 修正:只过滤路径遍历字符,保留合法的 / |
| | | String safeFileName = File.separator + fileName.trim() |
| | | // 过滤 ../ 和 ./ 序列(防止访问上级目录) |
| | | .replaceAll("\\.\\./", "") |
| | | .replaceAll("\\./", ""); |
| | | |
| | | // 进一步安全校验:确保拼接后的文件路径在服务器根目录下(核心安全措施) |
| | | File file = new File(serverFileRoot + safeFileName); |
| | | String canonicalPath = file.getCanonicalPath(); // 获取标准化路径(自动解析 ../ 等) |
| | | String canonicalRootPath = new File(serverFileRoot).getCanonicalPath(); |
| | | |
| | | // 校验:如果文件路径不在服务器根目录下,视为非法请求 |
| | | if (!canonicalPath.startsWith(canonicalRootPath)) { |
| | | throw new RuntimeException("非法文件访问:" + fileName); |
| | | } |
| | | |
| | | // 再判断文件是否存在 |
| | | if (!file.exists() || !file.isFile()) { |
| | | return Result.error("服务器不存在文件:" + safeFileName + ",路径:" + file.getAbsolutePath()); |
| | | } |
| | | // 调用文件上传接口,获取 upload_file_id |
| | | String fileId = uploadFileToServer(file, fileUploadUrl, authToken); |
| | | jianliFileIds.add(fileId); |
| | | } |
| | | |
| | | // 步骤2:构建 inputs 参数(按接口要求格式) |
| | | Map<String, Object> inputs = new HashMap<>(); |
| | | |
| | | // 处理简历文件(jianli 是 variable_name,对应接口的 {variable_name}) |
| | | if (!jianliFileIds.isEmpty()) { |
| | | // 若支持多个文件,可能需要数组形式;单个文件则直接放对象 |
| | | List<Map<String, String>> jianliFiles = new ArrayList<>(); |
| | | for (String fileId : jianliFileIds) { |
| | | Map<String, String> fileInfo = new HashMap<>(); |
| | | fileInfo.put("transfer_method", "local_file"); // 固定值,接口要求 |
| | | fileInfo.put("upload_file_id", fileId); // 上传文件返回的ID |
| | | fileInfo.put("type", "document"); // 文件类型,如 document/image 等(按接口要求) |
| | | jianliFiles.add(fileInfo); |
| | | } |
| | | inputs.put("jianli", jianliFiles); // jianli 对应 {variable_name} |
| | | } else { |
| | | inputs.put("jianli", new ArrayList<>()); // 空文件列表 |
| | | } |
| | | |
| | | // 添加其他文本参数 |
| | | inputs.put("wenanyaoqiu", wenanyaoqiu); |
| | | inputs.put("louchu", louchu); |
| | | inputs.put("youshang", youshang); |
| | | inputs.put("wenti", wenti); |
| | | |
| | | // 添加系统参数 |
| | | inputs.put("sys.user_id", userId); |
| | | inputs.put("sys.app_id", appId); |
| | | |
| | | // 步骤3:构建完整请求体 |
| | | Map<String, Object> requestBody = new HashMap<>(); |
| | | requestBody.put("inputs", inputs); // 顶层 inputs 容器 |
| | | requestBody.put("user", user); // 顶层 inputs 容器 |
| | | |
| | | SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); |
| | | // 连接超时:建立TCP连接的超时时间,单位毫秒(建议设5秒) |
| | | factory.setConnectTimeout(5000); |
| | | // 读取超时:等待服务端响应数据的超时时间,单位毫秒(根据接口耗时调整,这里设30秒) |
| | | factory.setReadTimeout(100000); |
| | | // 步骤4:调用工作流接口 |
| | | RestTemplate restTemplate = new RestTemplate(factory); |
| | | HttpHeaders headers = new HttpHeaders(); |
| | | headers.setContentType(MediaType.APPLICATION_JSON); |
| | | headers.set("Authorization", "Bearer " + authToken); |
| | | |
| | | HttpEntity<Map<String, Object>> requestEntity = new HttpEntity<>(requestBody, headers); |
| | | ResponseEntity<String> response = restTemplate.postForEntity(workflowUrl, requestEntity, String.class); |
| | | |
| | | if (response.getStatusCode() == HttpStatus.OK) { |
| | | if (JSONObject.parseObject(response.getBody()).getJSONObject("data").get("outputs") == null) { |
| | | return Result.error(JSONObject.parseObject(response.getBody()).getJSONObject("data").getString("error")); |
| | | } |
| | | else{ |
| | | String test = JSONObject.parseObject(response.getBody()).getJSONObject("data").getJSONObject("outputs").getString("http"); |
| | | |
| | | // ========== 中间操作:MD转HTML格式文本(保留所有回车,新增####四级标题处理) ========== |
| | | // 1. 定义MD语法正则(补充####四级标题,其他保留) |
| | | Pattern h1Pattern = Pattern.compile("^# (.*)", Pattern.MULTILINE); |
| | | Pattern h2Pattern = Pattern.compile("^## (.*)", Pattern.MULTILINE); |
| | | Pattern h3Pattern = Pattern.compile("^### (.*)", Pattern.MULTILINE); |
| | | Pattern h4Pattern = Pattern.compile("^#### (.*)", Pattern.MULTILINE); // 新增:四级标题正则 |
| | | Pattern ulStartPattern = Pattern.compile("^- (.*)", Pattern.MULTILINE); |
| | | Pattern boldPattern = Pattern.compile("\\*\\*(.*?)\\*\\*"); |
| | | Pattern hrPattern = Pattern.compile("^---+$", Pattern.MULTILINE); |
| | | |
| | | // 2. 逐步替换MD语法为HTML标签(保留所有换行,新增四级标题转换) |
| | | test = h1Pattern.matcher(test).replaceAll("<h1>$1</h1>"); // 一级标题→<h1> |
| | | test = h2Pattern.matcher(test).replaceAll("<h2>$1</h2>"); // 二级标题→<h2> |
| | | test = h3Pattern.matcher(test).replaceAll("<h3>$1</h3>"); // 三级标题→<h3> |
| | | test = h4Pattern.matcher(test).replaceAll("<h4>$1</h4>"); // 新增:四级标题→<h4> |
| | | test = boldPattern.matcher(test).replaceAll("<strong>$1</strong>"); // 加粗→<strong> |
| | | test = hrPattern.matcher(test).replaceAll("<hr/>"); // 分隔线→<hr/> |
| | | |
| | | // 处理无序列表(先标记列表项,再包裹<ul>) |
| | | test = ulStartPattern.matcher(test).replaceAll("<li>$1</li>"); |
| | | test = test.replaceAll("(<li>.*?</li>\\r?\\n?)+", "<ul>$0</ul>"); |
| | | |
| | | // 保留原有回车(将\n转为HTML换行<br>,同时保留原始换行符) |
| | | test = test.replaceAll("\\r?\\n", "<br/>\n"); |
| | | |
| | | // ========== 转换结束,test为HTML格式且处理了####四级标题 ========== |
| | | |
| | | return Result.OK("文案生成成功", test); |
| | | } |
| | | |
| | | } else { |
| | | return Result.error("工作流接口异常,状态码:" + response.getStatusCodeValue()); |
| | | } |
| | | |
| | | } catch (NullPointerException e) { |
| | | e.printStackTrace(); |
| | | return Result.error("不支持的文件格式:"+jianli.split("\\.")[jianli.split("\\.").length-1]); |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | return Result.error("生成文案异常:" + e.getMessage()); |
| | | } |
| | | } |
| | | /** |
| | | * 新增的生成标题接口方法 |
| | | */ |
| | | @RequiresPermissions("copywriting:copywriting:aiCreateTitle") |
| | | @RequestMapping(value = "/aiCreateTitle", method = RequestMethod.POST) |
| | | public Result<?> aiCreateTitle( |
| | | @RequestParam String louchu, |
| | | @RequestParam String yuyici, |
| | | @RequestParam String startTime, |
| | | @RequestParam String endTime, |
| | | @RequestParam String user) { // 保留user参数,用于接口鉴权/归属 |
| | | |
| | | // 2. 配置固定参数(和原有方法保持一致,可根据实际情况调整) |
| | | String workflowUrl = "http://14.103.174.44/v1/workflows/run"; // 标题生成的工作流地址,若和文案不同需修改 |
| | | String authToken = "app-F09iyl3p5448JoKufR2CRpWG"; |
| | | String appId = "cf85fe4d-b76b-4c4c-801a-1336c880d473"; |
| | | |
| | | try { |
| | | // 3. 构建inputs参数(适配标题生成接口的参数格式) |
| | | Map<String, Object> inputs = new HashMap<>(); |
| | | // 添加业务参数 |
| | | inputs.put("louchu", louchu); // 露出 |
| | | inputs.put("yuyici", yuyici); // 语气词 |
| | | inputs.put("startTime", startTime); // 开始时间 |
| | | inputs.put("endTime", endTime); // 结束时间 |
| | | |
| | | // 添加系统参数(和原有方法一致) |
| | | inputs.put("sys.user_id", user); |
| | | inputs.put("sys.app_id", appId); |
| | | |
| | | // 4. 构建完整请求体 |
| | | Map<String, Object> requestBody = new HashMap<>(); |
| | | requestBody.put("inputs", inputs); // 顶层inputs容器 |
| | | requestBody.put("user", user); // 顶层user参数(保持和原有接口一致) |
| | | |
| | | // 5. 调用工作流接口 |
| | | RestTemplate restTemplate = new RestTemplate(); |
| | | HttpHeaders headers = new HttpHeaders(); |
| | | headers.setContentType(MediaType.APPLICATION_JSON); |
| | | headers.set("Authorization", "Bearer " + authToken); |
| | | HttpEntity<Map<String, Object>> requestEntity = new HttpEntity<>(requestBody, headers); |
| | | ResponseEntity<String> response = restTemplate.postForEntity(workflowUrl, requestEntity, String.class); |
| | | |
| | | // 6. 处理接口返回结果 |
| | | if (response.getStatusCode() == HttpStatus.OK) { |
| | | JSONObject responseJson = JSONObject.parseObject(response.getBody()); |
| | | JSONObject dataJson = responseJson.getJSONObject("data"); |
| | | |
| | | // 校验返回结果结构 |
| | | if (dataJson == null) { |
| | | return Result.error("接口返回数据格式异常:无data字段"); |
| | | } |
| | | if (dataJson.get("outputs") == null) { |
| | | String errorMsg = dataJson.getString("error") != null ? dataJson.getString("error") : "标题生成失败,无具体错误信息"; |
| | | return Result.error(errorMsg); |
| | | } |
| | | |
| | | // 提取标题结果(假设outputs里的key是"title",需根据实际接口返回调整) |
| | | String title = dataJson.getJSONObject("outputs").getString("title"); |
| | | if (title != null) { |
| | | return Result.OK("标题生成成功", title); |
| | | } else { |
| | | try { |
| | | return Result.OK(JSONObject.parseObject(dataJson.get("outputs").toString()).get("llm")); |
| | | } catch (Exception e ){ |
| | | |
| | | } |
| | | |
| | | } |
| | | } else { |
| | | return Result.error("工作流接口调用异常,状态码:" + response.getStatusCodeValue()); |
| | | } |
| | | |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | return Result.error("生成标题异常:" + e.getMessage()); |
| | | } |
| | | return Result.error(""); |
| | | } |
| | | |
| | | /** |
| | | * 调用文件上传接口,获取 upload_file_id(修正版) |
| | | */ |
| | | private String uploadFileToServer(File file, String uploadUrl, String authToken) throws Exception { |
| | | // 构建文件上传请求(multipart/form-data 格式) |
| | | HttpHeaders headers = new HttpHeaders(); |
| | | headers.setContentType(MediaType.MULTIPART_FORM_DATA); |
| | | headers.set("Authorization", "Bearer " + authToken); |
| | | |
| | | // 读取文件内容 |
| | | byte[] fileBytes = new byte[(int) file.length()]; |
| | | try (FileInputStream fis = new FileInputStream(file)) { |
| | | fis.read(fileBytes); |
| | | } |
| | | |
| | | // 构建多部分请求体 |
| | | MultiValueMap<String, Object> parts = new LinkedMultiValueMap<>(); |
| | | parts.add("file", new ByteArrayResource(fileBytes) { |
| | | @Override |
| | | public String getFilename() { |
| | | return file.getName(); // 必须设置文件名 |
| | | } |
| | | }); |
| | | |
| | | HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(parts, headers); |
| | | RestTemplate restTemplate = new RestTemplate(); |
| | | ResponseEntity<Map> response = restTemplate.postForEntity(uploadUrl, requestEntity, Map.class); |
| | | |
| | | // 修正:只要状态码是200 OK,且包含id字段,就是上传成功 |
| | | if ((response.getStatusCode() == HttpStatus.OK || response.getStatusCode() == HttpStatus.CREATED) && response.getBody() != null) { |
| | | Object fileId = response.getBody().get("id"); |
| | | if (fileId != null) { |
| | | return fileId.toString(); // 成功返回upload_file_id |
| | | } else { |
| | | throw new RuntimeException("文件上传成功,但未返回id字段,响应:" + response.getBody()); |
| | | } |
| | | } else { |
| | | throw new RuntimeException("文件上传失败,状态码:" + response.getStatusCode() + ",响应:" + response.getBody()); |
| | | } |
| | | } |
| | | } |