chengf
2026-02-10 24bfd38bb9f89042028f757e05386cdd2558fc67
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/demo/copywriting/controller/CopywritingController.java
@@ -11,6 +11,11 @@
import com.alibaba.fastjson2.JSONObject;
import opennlp.tools.dictionary.serializer.Entry;
import org.jeecg.modules.demo.copywritingScheme.entity.CopywritingScheme;
import org.jeecg.modules.demo.copywritingScheme.service.ICopywritingSchemeService;
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.*;
@@ -42,6 +47,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.*;
@@ -64,12 +70,17 @@
@RestController
@RequestMapping("/copywriting/copywriting")
@Slf4j
@EnableAsync
public class CopywritingController extends JeecgController<Copywriting, ICopywritingService> {
   @Autowired
   private ICopywritingService copywritingService;
    @Autowired
    public ICopywritingService copywritingService;
     @Autowired
     private ISemanticWordService semanticWordService;;
     @Autowired
     private ISysUserService sysUserService;
     @Autowired
     private ICopywritingSchemeService copywritingSchemeService;
   /**
    * 分页列表查询
    *
@@ -97,8 +108,19 @@
        }
        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() + "%'");
                    "AND semantic_word.word LIKE '%" + copywriting.getWordLike() + "%'");
        }
        if (StringUtils.isNotBlank(copywriting.getContractId())) {
            // 1. 拼接关联 contact 表的 EXISTS 子查询,使用 contact.id 作为外键关联
            // 2. 使用 MyBatis-Plus 的参数占位符避免 SQL 注入,而不是直接字符串拼接
            String existsSql = "SELECT 1 FROM semantic_word " +
                    "LEFT JOIN contract ON semantic_word.contract_id = contract.id " + // 关联 contact 表(外键关联)
                    "WHERE semantic_word.id = copywriting.word_id " +
                    "AND contract.id = " + copywriting.getContractId(); // 使用 contact 表的 id 作为条件
            // 给 QueryWrapper 设置参数,避免 SQL 注入
            queryWrapper.exists(existsSql);
        }
        Page<Copywriting> page = new Page<Copywriting>(pageNo, pageSize);
      IPage<Copywriting> pageList = copywritingService.page(page, queryWrapper);
@@ -112,6 +134,8 @@
     @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());
@@ -126,6 +150,13 @@
             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+"");
@@ -253,74 +284,101 @@
     @RequiresPermissions("copywriting:copywriting:aiCreateCopyWriting")
     @RequestMapping(value = "/aiCreateCopyWriting", method = RequestMethod.POST)
     public Result<?> aiCreateCopyWriting(
             @RequestParam("jianli") String jianli,
             @RequestParam(name = "file", required = false) String jianli,
             @RequestParam(name = "url", required = false) String jianli2,
             @RequestParam String wenanyaoqiu,
             @RequestParam String louchu,
             @RequestParam String youshang,
             @RequestParam String wenti,
             @RequestParam String user) {
        if (jianli == null || jianli.equals("")) {
            return Result.error("请选择文件");
        }
             @RequestParam String user,
             @RequestParam String csId) {
         if (csId == null){
             csId = "2020795745607319553";
         }
         CopywritingScheme copywritingScheme = new CopywritingScheme();
         copywritingScheme.setId(csId);
         return getResult(jianli, jianli2, wenanyaoqiu, louchu, youshang, wenti, user, copywritingScheme);
     }
     public Result<?> getResult(String jianli, String jianli2, String wenanyaoqiu, String louchu, String youshang, String wenti, String user, CopywritingScheme cs) {
         QueryWrapper<CopywritingScheme> copywritingSchemeQueryWrapper = new QueryWrapper<>();
         if (cs.getId() == null){
             cs.setId("2020795745607319553");
         }
         copywritingSchemeQueryWrapper.eq("id", cs.getId());
         List<CopywritingScheme> list = copywritingSchemeService.list(copywritingSchemeQueryWrapper);
         if(list.size()==0) {
             return Result.error("方案不存在");
         } else {
             cs = list.get(0);
         }
         // 配置信息
         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 workflowUrl = cs.getWorkflowUrl();
         String fileUploadUrl = cs.getFileUploadUrl(); // 文件上传接口(假设)
         String authToken = cs.getAuthToken();
         String userId = user;
         String appId = "cf85fe4d-b76b-4c4c-801a-1336c880d473";
         String appId = cs.getAppId();
         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("\\./", "");
             Map<String, Object> inputs = new HashMap<>();
             if (jianli != null) {
                 // 步骤2:构建 inputs 参数(按接口要求格式)
                 List<String> jianliFileList = Arrays.asList(jianli.split(","));
                 if (jianliFileList == null || jianliFileList.isEmpty()) {
                     // 步骤1:上传简历文件,获取 upload_file_id 列表
                     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();
                         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 (!canonicalPath.startsWith(canonicalRootPath)) {
                             throw new RuntimeException("非法文件访问:" + fileName);
                         }
// 再判断文件是否存在
                 if (!file.exists() || !file.isFile()) {
                     return Result.error("服务器不存在文件:" + safeFileName + ",路径:" + file.getAbsolutePath());
                         if (!file.exists() || !file.isFile()) {
                             return Result.error("服务器不存在文件:" + safeFileName + ",路径:" + file.getAbsolutePath());
                         }
                         // 调用文件上传接口,获取 upload_file_id
                         String fileId = uploadFileToServer(file, fileUploadUrl, authToken);
                         jianliFileIds.add(fileId);
                     }
                     // 处理简历文件(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<>()); // 空文件列表
                     }
                 }
                 // 调用文件上传接口,获取 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<>()); // 空文件列表
             if (jianli2 != null) {
                 inputs.put("jianli2", jianli2);
             }
             // 添加其他文本参数
             inputs.put("wenanyaoqiu", wenanyaoqiu);
             inputs.put("benchmarkUrl", wenanyaoqiu);
             inputs.put("louchu", louchu);
             inputs.put("youshang", youshang);
             inputs.put("wenti", wenti);
@@ -338,7 +396,7 @@
// 连接超时:建立TCP连接的超时时间,单位毫秒(建议设5秒)
             factory.setConnectTimeout(5000);
// 读取超时:等待服务端响应数据的超时时间,单位毫秒(根据接口耗时调整,这里设30秒)
             factory.setReadTimeout(100000);
             factory.setReadTimeout(200000);
             // 步骤4:调用工作流接口
             RestTemplate restTemplate = new RestTemplate(factory);
             HttpHeaders headers = new HttpHeaders();
@@ -391,12 +449,14 @@
         } catch (NullPointerException e) {
             e.printStackTrace();
             return Result.error("不支持的文件格式:"+jianli.split("\\.")[jianli.split("\\.").length-1]);
             return Result.error("不支持的文件格式:" + jianli.split("\\.")[jianli.split("\\.").length - 1]);
         } catch (Exception e) {
             e.printStackTrace();
             return Result.error("生成文案异常:" + e.getMessage());
         }
     }
     /**
      * 新增的生成标题接口方法
      */
@@ -409,6 +469,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";