config(zxsq): 更新配置文件以支持个推推送、MongoDB存储和文件上传功能
- 添加了个推Unipush推送服务配置(appId、appKey、密钥等)
- 修改服务器端口从9005调整为9003
- 更新数据库连接配置,修改数据库名称和密码
- 将Redis配置移至data.redis下并调整数据库索引为0
- 添加MongoDB配置用于聊天记忆存储
- 更换安全令牌密钥为更复杂的密钥串
- 新增文件上传相关配置(临时目录、正式目录、域名等)
- 添加文件压缩、过期时间及访问限制配置
| | |
| | | @Component |
| | | public class ApproveTodoIntentExecutor { |
| | | |
| | | private static final Pattern APPROVE_ID_PATTERN = Pattern.compile("\\b[A-Za-z]*\\d{8,}\\b"); |
| | | private static final Pattern LIMIT_PATTERN = Pattern.compile("(前|最近)?(\\d{1,2})条"); |
| | | private static final Pattern APPROVE_ID_BY_LABEL_PATTERN = Pattern.compile("(流程编号|流程号|流程ID|审批编号|编号)\\s*[::]?\\s*([A-Za-z0-9_-]{2,64})"); |
| | | private static final Pattern APPROVE_ID_PATTERN = Pattern.compile("\\b[A-Za-z]*\\d{6,}[A-Za-z0-9_-]*\\b"); |
| | | private static final Pattern LIMIT_PATTERN = Pattern.compile("(前|最近)?\\s*(\\d{1,2})\\s*条"); |
| | | private static final Pattern DATE_PATTERN = Pattern.compile("(\\d{4}-\\d{2}-\\d{2})"); |
| | | private static final Pattern NUMBER_PATTERN = Pattern.compile("(\\d+(?:\\.\\d+)?)"); |
| | | private static final Pattern RECENT_RANGE_PATTERN = Pattern.compile("近\\d+(天|周|个月|月|年)"); |
| | |
| | | } |
| | | |
| | | String text = message.trim(); |
| | | String quickPromptResponse = tryExecuteQuickPrompt(memoryId, text); |
| | | if (StringUtils.hasText(quickPromptResponse)) { |
| | | return quickPromptResponse; |
| | | } |
| | | |
| | | String approveId = extractApproveId(text); |
| | | boolean hasApproveId = StringUtils.hasText(approveId) && !isPlaceholderApproveId(approveId); |
| | | String startDate = extractStartDate(text); |
| | | String endDate = extractEndDate(text); |
| | | String timeRange = extractTimeRange(text); |
| | | |
| | | if (isStatsIntent(text)) { |
| | | return approveTodoTools.getTodoStats( |
| | | memoryId, |
| | | extractStartDate(text), |
| | | extractEndDate(text), |
| | | extractTimeRange(text) |
| | | startDate, |
| | | endDate, |
| | | timeRange |
| | | ); |
| | | } |
| | | if (containsAny(text, "流转", "进度", "节点", "日志", "卡在", "卡到", "当前审批人", "处理记录")) { |
| | | return StringUtils.hasText(approveId) |
| | | return hasApproveId |
| | | ? approveTodoTools.getTodoProgress(memoryId, approveId) |
| | | : missingApproveId("todo_progress", "查询审批进度需要提供流程编号。"); |
| | | } |
| | | if (containsAny(text, "详情", "明细") && !containsAny(text, "列表")) { |
| | | return StringUtils.hasText(approveId) |
| | | return hasApproveId |
| | | ? approveTodoTools.getTodoDetail(memoryId, approveId) |
| | | : missingApproveId("todo_detail", "查询审批详情需要提供流程编号。"); |
| | | } |
| | | if (containsAny(text, "取消审核", "撤销审核", "回退审核", "撤销审批", "撤回审批") |
| | | || (containsAny(text, "撤销", "撤回") && containsAny(text, "审批操作", "审核操作"))) { |
| | | return StringUtils.hasText(approveId) |
| | | ? approveTodoTools.cancelReviewTodo(memoryId, approveId, firstNonBlank(extractTail(text, "原因"), extractTail(text, "备注"))) |
| | | return hasApproveId |
| | | ? approveTodoTools.cancelReviewTodo(memoryId, approveId, extractRemark(text)) |
| | | : missingApproveId("cancel_review_action", "取消审核需要提供流程编号。"); |
| | | } |
| | | if (containsAny(text, "删除", "移除")) { |
| | | return StringUtils.hasText(approveId) |
| | | return hasApproveId |
| | | ? approveTodoTools.deleteTodo(memoryId, approveId) |
| | | : missingApproveId("delete_action", "删除审批单需要提供流程编号。"); |
| | | } |
| | | if (containsAny(text, "驳回", "拒绝")) { |
| | | return StringUtils.hasText(approveId) |
| | | ? approveTodoTools.reviewTodo(memoryId, approveId, "reject", firstNonBlank(extractTail(text, "原因"), extractTail(text, "备注"))) |
| | | return hasApproveId |
| | | ? approveTodoTools.reviewTodo(memoryId, approveId, "reject", extractRemark(text)) |
| | | : missingApproveId("review_action", "驳回审批需要提供流程编号。"); |
| | | } |
| | | if (containsAny(text, "审核通过", "审批通过", "通过审批", "同意审批", "审批同意")) { |
| | | return StringUtils.hasText(approveId) |
| | | ? approveTodoTools.reviewTodo(memoryId, approveId, "approve", extractTail(text, "备注")) |
| | | return hasApproveId |
| | | ? approveTodoTools.reviewTodo(memoryId, approveId, "approve", extractRemark(text)) |
| | | : missingApproveId("review_action", "审批通过需要提供流程编号。"); |
| | | } |
| | | if (StringUtils.hasText(approveId) |
| | | if (hasApproveId |
| | | && containsAny(text, "通过", "同意") |
| | | && !containsAny(text, "未通过", "通过率", "审批通过率", "审核通过率")) { |
| | | return approveTodoTools.reviewTodo(memoryId, approveId, "approve", extractTail(text, "备注")); |
| | | return approveTodoTools.reviewTodo(memoryId, approveId, "approve", extractRemark(text)); |
| | | } |
| | | if (containsAny(text, "修改", "更新", "变更")) { |
| | | return StringUtils.hasText(approveId) |
| | | return hasApproveId |
| | | ? approveTodoTools.updateTodo( |
| | | memoryId, |
| | | approveId, |
| | |
| | | extractApproveType(text), |
| | | extractKeyword(text), |
| | | extractLimit(text), |
| | | extractScope(text)); |
| | | extractScope(text), |
| | | startDate, |
| | | endDate, |
| | | timeRange); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private String tryExecuteQuickPrompt(String memoryId, String text) { |
| | | String normalized = normalizeForMatch(text); |
| | | String approveId = extractApproveId(text); |
| | | boolean hasApproveId = StringUtils.hasText(approveId) && !isPlaceholderApproveId(approveId); |
| | | |
| | | if ("我当前有哪些审批待办需要处理".equals(normalized)) { |
| | | return approveTodoTools.listTodos(memoryId, "pending", null, null, 10, "approver", null, null, null); |
| | | } |
| | | if ("帮我列出今天新增的审批待办".equals(normalized)) { |
| | | return approveTodoTools.listTodos(memoryId, "all", null, null, 10, "related", null, null, "今天"); |
| | | } |
| | | if ("当前待我审批的单据按时间倒序列出来".equals(normalized)) { |
| | | return approveTodoTools.listTodos(memoryId, "pending", null, null, 10, "approver", null, null, null); |
| | | } |
| | | if ("我发起的审批里哪些还在处理中".equals(normalized)) { |
| | | return approveTodoTools.listTodos(memoryId, "processing", null, null, 10, "applicant", null, null, null); |
| | | } |
| | | if ("近7天我的审批待办统计情况怎么样".equals(normalized)) { |
| | | return approveTodoTools.getTodoStats(memoryId, null, null, "近7天"); |
| | | } |
| | | if ("本月我的审批中通过驳回处理中各有多少".equals(normalized)) { |
| | | return approveTodoTools.getTodoStats(memoryId, null, null, "本月"); |
| | | } |
| | | if ("近30天各类型审批数量分布是什么".equals(normalized)) { |
| | | return approveTodoTools.getTodoStats(memoryId, null, null, "近30天"); |
| | | } |
| | | |
| | | if (normalized.startsWith("查询流程编号") && normalized.contains("审批详情")) { |
| | | return hasApproveId |
| | | ? approveTodoTools.getTodoDetail(memoryId, approveId) |
| | | : missingApproveId("todo_detail", "查询审批详情需要提供真实流程编号。"); |
| | | } |
| | | if (normalized.startsWith("流程编号") |
| | | && normalized.contains("卡在哪个审批节点") |
| | | && normalized.contains("当前审批人是谁")) { |
| | | return hasApproveId |
| | | ? approveTodoTools.getTodoProgress(memoryId, approveId) |
| | | : missingApproveId("todo_progress", "查询审批进度需要提供真实流程编号。"); |
| | | } |
| | | if (normalized.startsWith("帮我查看流程编号") && normalized.contains("审批流转记录")) { |
| | | return hasApproveId |
| | | ? approveTodoTools.getTodoProgress(memoryId, approveId) |
| | | : missingApproveId("todo_progress", "查询审批流转记录需要提供真实流程编号。"); |
| | | } |
| | | if (normalized.startsWith("帮我审批通过流程编号")) { |
| | | return hasApproveId |
| | | ? approveTodoTools.reviewTodo(memoryId, approveId, "approve", extractRemark(text)) |
| | | : missingApproveId("review_action", "审批通过需要提供真实流程编号。"); |
| | | } |
| | | if (normalized.startsWith("帮我驳回流程编号")) { |
| | | return hasApproveId |
| | | ? approveTodoTools.reviewTodo(memoryId, approveId, "reject", extractRemark(text)) |
| | | : missingApproveId("review_action", "驳回审批需要提供真实流程编号。"); |
| | | } |
| | | if (normalized.startsWith("撤销我刚刚对流程编号") && normalized.contains("审批操作")) { |
| | | return hasApproveId |
| | | ? approveTodoTools.cancelReviewTodo(memoryId, approveId, extractRemark(text)) |
| | | : missingApproveId("cancel_review_action", "撤销审批操作需要提供真实流程编号。"); |
| | | } |
| | | if (normalized.startsWith("帮我修改流程编号") && normalized.contains("备注为")) { |
| | | return hasApproveId |
| | | ? approveTodoTools.updateTodo(memoryId, approveId, null, null, null, null, null, null, extractRemark(text)) |
| | | : missingApproveId("update_action", "修改审批单需要提供真实流程编号。"); |
| | | } |
| | | if (normalized.startsWith("删除我发起的流程编号")) { |
| | | return hasApproveId |
| | | ? approveTodoTools.deleteTodo(memoryId, approveId) |
| | | : missingApproveId("delete_action", "删除审批单需要提供真实流程编号。"); |
| | | } |
| | | return null; |
| | | } |
| | |
| | | } |
| | | |
| | | private String extractApproveId(String text) { |
| | | Matcher keywordMatcher = APPROVE_ID_BY_LABEL_PATTERN.matcher(text); |
| | | if (keywordMatcher.find()) { |
| | | return keywordMatcher.group(2); |
| | | } |
| | | Matcher matcher = APPROVE_ID_PATTERN.matcher(text); |
| | | return matcher.find() ? matcher.group() : null; |
| | | } |
| | |
| | | .replace("单据", "") |
| | | .replace("待办", "") |
| | | .replace("列表", "") |
| | | .replace("流程编号", "") |
| | | .replace("流程号", "") |
| | | .replace("前10条", "") |
| | | .replace("最近10条", "") |
| | | .trim(); |
| | |
| | | } |
| | | |
| | | private String extractValue(String text, String fieldName) { |
| | | Pattern pattern = Pattern.compile(fieldName + "(改为|修改为|是)[::]?[\\s]*([^,,。;;\\s]+)"); |
| | | Pattern pattern = Pattern.compile(fieldName + "(改为|修改为|为|是)[::]?[\\s]*([^,,。;;\\s]+)"); |
| | | Matcher matcher = pattern.matcher(text); |
| | | return matcher.find() ? matcher.group(2) : null; |
| | | } |
| | |
| | | if (!text.contains(fieldName)) { |
| | | return null; |
| | | } |
| | | Matcher matcher = Pattern.compile(fieldName + "(改为|修改为|是)[::]?[\\s]*(\\d{1,2})").matcher(text); |
| | | Matcher matcher = Pattern.compile(fieldName + "(改为|修改为|为|是)[::]?[\\s]*(\\d{1,2})").matcher(text); |
| | | return matcher.find() ? Integer.parseInt(matcher.group(2)) : null; |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | private String extractTail(String text, String key) { |
| | | Pattern quotedPattern = Pattern.compile(key + "(是|为)?[::]?[\\s]*[“\"]([^”\"]+)[”\"]"); |
| | | Matcher quotedMatcher = quotedPattern.matcher(text); |
| | | if (quotedMatcher.find()) { |
| | | return cleanContent(quotedMatcher.group(2)); |
| | | } |
| | | Pattern pattern = Pattern.compile(key + "(是|为)?[::]?[\\s]*(.+)"); |
| | | Matcher matcher = pattern.matcher(text); |
| | | return matcher.find() ? matcher.group(2).trim() : null; |
| | | return matcher.find() ? cleanContent(matcher.group(2)) : null; |
| | | } |
| | | |
| | | private String extractScope(String text) { |
| | | if (containsAny(text, "我发起", "我提交", "我申请", "申请人是我")) { |
| | | return "applicant"; |
| | | } |
| | | if (containsAny(text, "待我审批", "待我审核", "我处理", "我审批", "当前待我", "需要我处理")) { |
| | | if (containsAny(text, "待我审批", "待我审核", "我处理", "我审批", "当前待我", "需要我处理", "需要处理")) { |
| | | return "approver"; |
| | | } |
| | | return "related"; |
| | | } |
| | | |
| | | private String extractRemark(String text) { |
| | | return firstNonBlank(firstNonBlank(extractTail(text, "备注"), extractTail(text, "原因")), extractQuotedContent(text)); |
| | | } |
| | | |
| | | private String extractQuotedContent(String text) { |
| | | Matcher matcher = Pattern.compile("[“\"]([^”\"]+)[”\"]").matcher(text); |
| | | return matcher.find() ? cleanContent(matcher.group(1)) : null; |
| | | } |
| | | |
| | | private String normalizeForMatch(String text) { |
| | | if (!StringUtils.hasText(text)) { |
| | | return ""; |
| | | } |
| | | return text.replace(",", "") |
| | | .replace(",", "") |
| | | .replace("。", "") |
| | | .replace(".", "") |
| | | .replace("!", "") |
| | | .replace("!", "") |
| | | .replace("?", "") |
| | | .replace("?", "") |
| | | .replace(":", "") |
| | | .replace(":", "") |
| | | .replace(";", "") |
| | | .replace(";", "") |
| | | .replace("“", "") |
| | | .replace("”", "") |
| | | .replace("\"", "") |
| | | .replace(" ", "") |
| | | .trim(); |
| | | } |
| | | |
| | | private boolean isPlaceholderApproveId(String approveId) { |
| | | if (!StringUtils.hasText(approveId)) { |
| | | return true; |
| | | } |
| | | String value = approveId.trim(); |
| | | return "xxx".equalsIgnoreCase(value) |
| | | || value.matches("[xX]{2,}") |
| | | || "流程编号".equals(value) |
| | | || "编号".equals(value) |
| | | || value.contains("示例") |
| | | || value.contains("请输入"); |
| | | } |
| | | |
| | | private String cleanContent(String text) { |
| | | if (!StringUtils.hasText(text)) { |
| | | return null; |
| | | } |
| | | return text.trim() |
| | | .replace("“", "") |
| | | .replace("”", "") |
| | | .replace("\"", "") |
| | | .replace("。", "") |
| | | .replace(";", "") |
| | | .replace(";", "") |
| | | .trim(); |
| | | } |
| | | |
| | | private String firstNonBlank(String first, String second) { |
| | | return StringUtils.hasText(first) ? first : second; |
| | | } |
| | |
| | | return Flux.just(directResponse); |
| | | } |
| | | |
| | | if (isApproveTodoBusinessIntent(userMessage)) { |
| | | String noGuessResponse = "未识别到可执行的审批待办操作条件。为保证结果准确,当前不会推测或编造审批数据,请补充流程编号、时间范围或明确操作指令后再试。"; |
| | | mongoChatMemoryStore.appendMessages( |
| | | memoryId, |
| | | List.of(UserMessage.from(userMessage), AiMessage.from(noGuessResponse)) |
| | | ); |
| | | aiChatSessionService.refreshSessionStats(memoryId, loginUser); |
| | | return Flux.just(noGuessResponse); |
| | | } |
| | | |
| | | return approveTodoAgent.chat(memoryId, userMessage) |
| | | .doOnComplete(() -> aiChatSessionService.refreshSessionStats(memoryId, loginUser)) |
| | | .doOnError(ex -> aiChatSessionService.refreshSessionStats(memoryId, loginUser)); |
| | |
| | | aiSessionUserContext.remove(memoryId); |
| | | return toAjax(aiChatSessionService.deleteCurrentUserSession(memoryId, SecurityUtils.getLoginUser())); |
| | | } |
| | | |
| | | private boolean isApproveTodoBusinessIntent(String message) { |
| | | if (!StringUtils.hasText(message)) { |
| | | return false; |
| | | } |
| | | String text = message.trim(); |
| | | boolean hasDomainWord = containsAny(text, |
| | | "审批", "待办", "流程编号", "流程号", "审批流转", "审批节点", "当前审批人", "驳回", "通过", "撤销", "删除"); |
| | | boolean hasIntentWord = containsAny(text, |
| | | "查询", "查看", "列出", "统计", "分析", "分布", "通过", "驳回", "撤销", "删除", "修改", "有哪些", "卡在"); |
| | | return hasDomainWord && hasIntentWord; |
| | | } |
| | | |
| | | private boolean containsAny(String text, String... keywords) { |
| | | for (String keyword : keywords) { |
| | | if (text.contains(keyword)) { |
| | | return true; |
| | | } |
| | | } |
| | | return false; |
| | | } |
| | | } |
| | |
| | | @P(value = "审批类型编号,可不传", required = false) Integer approveType, |
| | | @P(value = "关键字,可匹配流程编号、标题、申请人、当前审批人", required = false) String keyword, |
| | | @P(value = "返回条数,默认10,最大20", required = false) Integer limit, |
| | | @P(value = "查询范围,可选值:related、applicant、approver;related 表示当前用户相关,applicant 表示我发起的,approver 表示待我处理的", required = false) String scope) { |
| | | @P(value = "查询范围,可选值:related、applicant、approver;related 表示当前用户相关,applicant 表示我发起的,approver 表示待我处理的", required = false) String scope, |
| | | @P(value = "开始日期 yyyy-MM-dd,可不传", required = false) String startDate, |
| | | @P(value = "结束日期 yyyy-MM-dd,可不传", required = false) String endDate, |
| | | @P(value = "时间范围描述,例如 今天、本月、近30天,可不传", required = false) String timeRange) { |
| | | |
| | | LoginUser loginUser = currentLoginUser(memoryId); |
| | | Long userId = loginUser.getUserId(); |
| | | Integer statusCode = parseStatus(status); |
| | | String normalizedScope = normalizeScope(scope); |
| | | boolean hasDateFilter = StringUtils.hasText(startDate) || StringUtils.hasText(endDate) || StringUtils.hasText(timeRange); |
| | | DateRange dateRange = hasDateFilter ? resolveDateRange(startDate, endDate, timeRange) : null; |
| | | |
| | | LambdaQueryWrapper<ApproveProcess> wrapper = new LambdaQueryWrapper<>(); |
| | | wrapper.eq(ApproveProcess::getApproveDelete, 0); |
| | |
| | | } |
| | | } |
| | | |
| | | if (dateRange != null) { |
| | | wrapper.ge(ApproveProcess::getCreateTime, dateRange.start().atStartOfDay()) |
| | | .lt(ApproveProcess::getCreateTime, dateRange.end().plusDays(1).atStartOfDay()); |
| | | } |
| | | |
| | | wrapper.orderByDesc(ApproveProcess::getCreateTime) |
| | | .last("limit " + normalizeLimit(limit)); |
| | | |
| | |
| | | "statusFilter", StringUtils.hasText(status) ? status : "all", |
| | | "approveType", approveType == null ? "" : approveType, |
| | | "keyword", keyword == null ? "" : keyword, |
| | | "scope", normalizedScope |
| | | "scope", normalizedScope, |
| | | "timeRange", dateRange == null ? "all" : dateRange.label(), |
| | | "startDate", dateRange == null ? "" : dateRange.start().toString(), |
| | | "endDate", dateRange == null ? "" : dateRange.end().toString() |
| | | ), |
| | | Map.of("columns", todoColumns(), "items", items), |
| | | Map.of()); |