From 4c022d3c2101a3da4dd70b753d61b1d15b446b8b Mon Sep 17 00:00:00 2001 From: yuance <182865460@qq.com> Date: Thu, 11 Dec 2025 11:37:33 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=AE=A1=E8=AE=A1=E5=86=85?= =?UTF-8?q?=E5=AE=B98-=E5=86=85=E9=83=A8=E6=8E=A7=E5=88=B6=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...AuditContent8InternalControlConstants.java | 195 +++++++++++ .../controller/AuditContent8Controller.java | 299 ++++++++++++++++ .../export/InternalControlExportEntity.java | 40 +++ .../AuditContent8InternalControlService.java | 13 + .../impl/AbstractAuditContentService.java | 56 +-- ...ditContent8InternalControlServiceImpl.java | 326 ++++++++++++++++++ 6 files changed, 905 insertions(+), 24 deletions(-) create mode 100644 src/main/java/com/gxwebsoft/ai/constants/AuditContent8InternalControlConstants.java create mode 100644 src/main/java/com/gxwebsoft/ai/controller/AuditContent8Controller.java create mode 100644 src/main/java/com/gxwebsoft/ai/dto/export/InternalControlExportEntity.java create mode 100644 src/main/java/com/gxwebsoft/ai/service/AuditContent8InternalControlService.java create mode 100644 src/main/java/com/gxwebsoft/ai/service/impl/AuditContent8InternalControlServiceImpl.java diff --git a/src/main/java/com/gxwebsoft/ai/constants/AuditContent8InternalControlConstants.java b/src/main/java/com/gxwebsoft/ai/constants/AuditContent8InternalControlConstants.java new file mode 100644 index 0000000..d3c71a5 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/constants/AuditContent8InternalControlConstants.java @@ -0,0 +1,195 @@ +package com.gxwebsoft.ai.constants; + +import java.util.*; + +/** + * 审计内容8 - 单位层面财务管理内部控制测试常量类 + */ +public class AuditContent8InternalControlConstants { + + // 16个控制环节 + public static final String[] CONTROL_LINKS = { + "有关部门或岗位财务管理职能划分", + "财务管理岗位设置及职责划分", + "财务管理岗位人员配备", + "财务管理关键岗位人员轮岗", + "财务管理事项授权审批(核)", + "财务管理重大事项决定", + "会计核算与文档管理", + "财务电子信息管理", + "重要财务管理事项的信息沟通", + "财务管理风险评估", + "财务管理风险应对", + "内部审计与监督检查", + "反舞弊", + "财务管理内部控制评价", + "财务管理内部控制缺陷跟踪与改进", + "财务管理内部控制实施情况纳入考核体系" + }; + + // 控制要求 + public static final Map CONTROL_REQUIREMENTS = new HashMap<>(); + static { + CONTROL_REQUIREMENTS.put(CONTROL_LINKS[0], "财务管理决策、执行与监督职能相互分离"); + CONTROL_REQUIREMENTS.put(CONTROL_LINKS[1], "财务管理不相容职务相互分离并相互制约"); + CONTROL_REQUIREMENTS.put(CONTROL_LINKS[2], "财务管理岗位人员配备符合规定与岗位要求"); + CONTROL_REQUIREMENTS.put(CONTROL_LINKS[3], "关键岗位实行定期轮岗,不具备轮岗条件的实施其他替代措施"); + CONTROL_REQUIREMENTS.put(CONTROL_LINKS[4], "全部财务管理业务实施授权审批(核)"); + CONTROL_REQUIREMENTS.put(CONTROL_LINKS[5], "财务管理重大事项均由单位领导层集体研究决定"); + CONTROL_REQUIREMENTS.put(CONTROL_LINKS[6], "会计核算及其档案管理规范,会计信息准确"); + CONTROL_REQUIREMENTS.put(CONTROL_LINKS[7], "信息系统开发、运行和维护以及用户管理与数据备份规范,信息安全"); + CONTROL_REQUIREMENTS.put(CONTROL_LINKS[8], "对于重要财务管理事项均进行信息沟通"); + CONTROL_REQUIREMENTS.put(CONTROL_LINKS[9], "对于财务管理风险能够进行识别、评估与预防"); + CONTROL_REQUIREMENTS.put(CONTROL_LINKS[10], "对于出现的财务管理风险能够应对与控制"); + CONTROL_REQUIREMENTS.put(CONTROL_LINKS[11], "对于发生的财务管理业务进行定期与不定期的审计与监督检查"); + CONTROL_REQUIREMENTS.put(CONTROL_LINKS[12], "及时察觉与处置财务舞弊事件"); + CONTROL_REQUIREMENTS.put(CONTROL_LINKS[13], "对于财务管理内部控制实施评价,并取得成效"); + CONTROL_REQUIREMENTS.put(CONTROL_LINKS[14], "跟踪发现的财务管理内部控制缺陷,及时进行改进"); + CONTROL_REQUIREMENTS.put(CONTROL_LINKS[15], "在单位考核体系中包含财务管理内部控制实施情况内容"); + } + + // 控制活动描述 + public static final Map CONTROL_ACTIVITIES = new HashMap<>(); + static { + CONTROL_ACTIVITIES.put(CONTROL_LINKS[0], "按照决策、执行与监督职能相互分离的要求确定有关部门或岗位的财务管理职能,由财务管理部门提出意见,报经分管领导审核,经单位领导层集体审批后实施"); + CONTROL_ACTIVITIES.put(CONTROL_LINKS[1], "按照不相容职务相互分离的要求设置财务管理岗位,所有岗位具有明确的职责,由财务管理部门提出意见,报经分管领导审核,经单位领导层集体审批后实施"); + CONTROL_ACTIVITIES.put(CONTROL_LINKS[2], "按照有关规定与岗位要求配备财务管理人员,各岗位均有明确的任职要求,由财务管理部门提出意见,报经分管领导审核,经单位领导层集体审批后实施"); + CONTROL_ACTIVITIES.put(CONTROL_LINKS[3], "对于财务管理部门负责人与其他关键岗位人员按照规定进行轮岗、相关人员实行回避,不具备轮岗条件的实施其他替代措施,由财务管理部门提出意见,报经分管领导审核,经单位领导层集体审批后实施"); + CONTROL_ACTIVITIES.put(CONTROL_LINKS[4], "对于财务管理业务授权审批的范围、权限、程序与责任具有明确的规定,由财务管理部门提出意见,报经分管领导审核,经单位领导层集体审批后实施"); + CONTROL_ACTIVITIES.put(CONTROL_LINKS[5], "对于单位部门预算、大额资金使用、大宗设备采购或重大服务购买、基本建设等重大财务事项均有明确的决定或会签制度规定,由财务管理部门提出意见,报经分管领导审核,经单位领导层集体审批后实施"); + CONTROL_ACTIVITIES.put(CONTROL_LINKS[6], "对于会计凭证、会计账簿和会计报告处理程序以及会计档案管理具有明确的规定,由财务管理部门提出意见,报经分管领导审核,经单位领导层集体审批后实施"); + CONTROL_ACTIVITIES.put(CONTROL_LINKS[7], "对于财务管理信息系统的开发、运行和维护以及用户管理与系统数据备份,信息系统安全保密和泄密责任追究具有明确的规定,由财务管理部门提出意见,报经分管领导审核,经单位领导层集体审批后实施"); + CONTROL_ACTIVITIES.put(CONTROL_LINKS[8], "对于财务管理重要事项建立信息沟通制度,由财务管理部门提出意见,报经分管领导审核,经单位领导层集体审批后实施"); + CONTROL_ACTIVITIES.put(CONTROL_LINKS[9], "对于财务管理风险具有明确工作内容与要求的评估预防体系与工作机制,由财务管理部门提出意见,报经分管领导审核,经单位领导层集体审批后实施"); + CONTROL_ACTIVITIES.put(CONTROL_LINKS[10], "对于出现的财务管理风险具有应对机制与控制措施,由财务管理部门提出意见,报经分管领导审核,经单位领导层集体审批后实施"); + CONTROL_ACTIVITIES.put(CONTROL_LINKS[11], "设有独立的内审部门或审计岗位并有明确的职责要求,对内部审计与财务管理监督检查具有明确的规定,由审计管理部门提出意见,报经分管领导审核,经单位领导层集体审批后实施"); + CONTROL_ACTIVITIES.put(CONTROL_LINKS[12], "对于财务管理舞弊具有举报投诉制度和举报人保护制度以及全体员工被告知制度,对于举报投诉的财务管理舞弊事件与问题的处置具有明确的规定,由财务管理部门提出意见,报经分管领导审核,经单位领导层集体审批后实施"); + CONTROL_ACTIVITIES.put(CONTROL_LINKS[13], "设立或指定财务管理内部控制评价的职能部门与岗位,对于财务管理内部控制评价工作的计划、要求具有明确的规定,由财务管理内部控制评价部门提出意见,报经分管领导审核,经单位领导层集体审批后实施"); + CONTROL_ACTIVITIES.put(CONTROL_LINKS[14], "对于财务管理内部控制缺陷具有跟踪与改进制度,对于整改的实施与后续的检查具有明确的规定,由财务管理内部控制评价部门提出意见,报经分管领导审核,经单位领导层集体审批后实施"); + CONTROL_ACTIVITIES.put(CONTROL_LINKS[15], "对于财务管理内部控体系建设与执行在单位的考核制度中具有明确的考核规定,由财务管理内部控制评价部门提出意见,报经分管领导审核,经单位领导层集体审批后实施"); + } + + // 控制职责岗位 + public static final Map CONTROL_POSITIONS = new HashMap<>(); + static { + CONTROL_POSITIONS.put(CONTROL_LINKS[0], "财务管理部门负责人、分管领导"); + CONTROL_POSITIONS.put(CONTROL_LINKS[1], "财务管理部门负责人、分管领导"); + CONTROL_POSITIONS.put(CONTROL_LINKS[2], "财务管理部门负责人、分管领导"); + CONTROL_POSITIONS.put(CONTROL_LINKS[3], "财务管理部门负责人、分管领导"); + CONTROL_POSITIONS.put(CONTROL_LINKS[4], "财务管理部门负责人、分管领导"); + CONTROL_POSITIONS.put(CONTROL_LINKS[5], "财务管理部门负责人、分管领导"); + CONTROL_POSITIONS.put(CONTROL_LINKS[6], "财务管理部门负责人、分管领导"); + CONTROL_POSITIONS.put(CONTROL_LINKS[7], "财务管理部门负责人、分管领导"); + CONTROL_POSITIONS.put(CONTROL_LINKS[8], "财务管理部门负责人、分管领导"); + CONTROL_POSITIONS.put(CONTROL_LINKS[9], "财务管理部门负责人、分管领导"); + CONTROL_POSITIONS.put(CONTROL_LINKS[10], "财务管理部门负责人、分管领导"); + CONTROL_POSITIONS.put(CONTROL_LINKS[11], "审计管理部门负责人、分管领导"); + CONTROL_POSITIONS.put(CONTROL_LINKS[12], "财务管理部门负责人、分管领导"); + CONTROL_POSITIONS.put(CONTROL_LINKS[13], "财务管理内部控制评价部门负责人、分管领导"); + CONTROL_POSITIONS.put(CONTROL_LINKS[14], "财务管理内部控制评价部门负责人、分管领导"); + CONTROL_POSITIONS.put(CONTROL_LINKS[15], "财务管理内部控制评价部门负责人、分管领导"); + } + + // 测试步骤 + public static final Map> TEST_STEPS = new HashMap<>(); + static { + TEST_STEPS.put(CONTROL_LINKS[0], Arrays.asList( + "(1)是否有制度规定", + "(2)单位有关部门或岗位财务管理职能设置是否符合相互分离规定", + "(3)通过观察、询问以及查阅有关工作文档等方式检查单位财务管理组织体系中的决策、执行与监督职能是否实现了分离" + )); + TEST_STEPS.put(CONTROL_LINKS[1], Arrays.asList( + "(1)是否有制度规定", + "(2)单位财务管理岗位设置与职责划分是否符合不相容岗位相互分离规定", + "(3)通过观察、询问以及查阅有关工作文档等方式检查财务管理岗位不相容职务是否实现了分离" + )); + TEST_STEPS.put(CONTROL_LINKS[2], Arrays.asList( + "(1)是否有制度规定", + "(2)财务管理岗位配备的人员是否符合任职资质要求", + "(3)重要财务管理岗位人员的专业背景、工作经验及有关执业资质是否能够胜任岗位工作需要" + )); + TEST_STEPS.put(CONTROL_LINKS[3], Arrays.asList( + "(1)是否有制度规定", + "(2)通过观察、询问以及查阅有关工作文档等方式检查财务管理关键岗位是否进行了轮岗或采取了其他替代措施" + )); + TEST_STEPS.put(CONTROL_LINKS[4], Arrays.asList( + "(1)是否有制度规定", + "(2)通过观察、询问以及查阅有关工作文档等方式检查单位财务管理业务流程中的各个审批(核)环节的操作是否符合授权审批(核)规定" + )); + TEST_STEPS.put(CONTROL_LINKS[5], Arrays.asList( + "(1)是否有制度规定", + "(2)通过观察、询问以及查阅有关工作文档等方式检查单位财务管理重大事项是否由领导层集体研究决定或会签,且研究决定或会签记录内容完整" + )); + TEST_STEPS.put(CONTROL_LINKS[6], Arrays.asList( + "(1)是否有制度规定", + "(2)通过观察、询问以及查阅有关工作文档等方式检查单位会计核算流程是否符合制度规定", + "(3)通过观察、询问以及查阅有关工作文档等方式检查单位是否有专职岗位负责会计档案保管,且档案文件保管情况良好" + )); + TEST_STEPS.put(CONTROL_LINKS[7], Arrays.asList( + "(1)是否有制度规定", + "(2)通过观察、询问以及查阅有关工作文档等方式检查单位是否按规定进行了信息系统的开发、运行和维护以及用户管理与数据备份" + )); + TEST_STEPS.put(CONTROL_LINKS[8], Arrays.asList( + "(1)是否有制度规定", + "(2)通过观察、询问以及查阅有关工作文档等方式检查单位财务管理有关重要事项是否通过会议、内部报告或会签等方式进行信息沟通" + )); + TEST_STEPS.put(CONTROL_LINKS[9], Arrays.asList( + "(1)是否有制度规定", + "(2)通过观察、询问以及查阅有关工作文档等方式检查单位是否进行了财务管理风险的识别、评估与预防工作" + )); + TEST_STEPS.put(CONTROL_LINKS[10], Arrays.asList( + "(1)是否有制度规定", + "(2)通过观察、询问以及查阅有关工作文档等方式检查单位是否对出现的财务风险进行了应对,并采取了控制措施" + )); + TEST_STEPS.put(CONTROL_LINKS[11], Arrays.asList( + "(1)是否有制度规定", + "(2)是否设置了独立的审计部门或审计岗位", + "(3)通过观察、询问以及查阅有关工作文档等方式检查单位内审部门是否定期或不定期对财务管理业务进行审计与监督检查", + "(4)通过观察、询问以及查阅有关工作文档等方式检查单位内审部门能否及时发现财务管理中的问题,督促整改" + )); + TEST_STEPS.put(CONTROL_LINKS[12], Arrays.asList( + "(1)是否有制度规定", + "(2)通过观察、询问以及查阅有关工作文档等方式检查单位员工是否知晓财务管理舞弊的举报投诉制度和举报人保护制度", + "(3)通过观察、询问以及查阅有关工作文档等方式检查单位对于举报投诉的财务管理舞弊事件与问题是否及时进行了处置" + )); + TEST_STEPS.put(CONTROL_LINKS[13], Arrays.asList( + "(1)是否有制度规定", + "(2)是否设立或指定有财务管理内部控制评价的职能部门与岗位", + "(3)通过观察、询问以及查阅有关工作文档等方式检查单位是否有财务管理内部控制评价工作计划与工作要求", + "(4)通过观察、询问以及查阅有关工作文档等方式检查单位是否按计划开展了财务管理内部控制评价工作,促进了单位财务管理内部控制体系的建立健全" + )); + TEST_STEPS.put(CONTROL_LINKS[14], Arrays.asList( + "(1)是否有制度规定", + "(2)通过观察、询问以及查阅有关工作文档等方式检查单位对于发现的内部控制缺陷是否及时通报与跟踪", + "(3)通过观察、询问以及查阅有关工作文档等方式检查单位对于发现的缺陷是否及时进行了整改" + )); + TEST_STEPS.put(CONTROL_LINKS[15], Arrays.asList( + "(1)是否有制度规定", + "(2)通过观察、询问以及查阅有关工作文档等方式检查单位的考核制度中是否将财务管理内部控制实施情况列入了考核内容中", + "(3)通过观察、询问以及查阅有关工作文档等方式检查单位在实际进行考核中对财务管理内部控制实施情况的考核内容" + )); + } + + // 关键词权重 + public static final Map KEYWORD_WEIGHTS = new HashMap<>(); + static { + KEYWORD_WEIGHTS.put("内部控制", 10); + KEYWORD_WEIGHTS.put("财务管理", 9); + KEYWORD_WEIGHTS.put("不相容职务分离", 9); + KEYWORD_WEIGHTS.put("授权审批", 8); + KEYWORD_WEIGHTS.put("集体决策", 8); + KEYWORD_WEIGHTS.put("会计档案", 7); + KEYWORD_WEIGHTS.put("信息系统", 7); + KEYWORD_WEIGHTS.put("风险识别", 8); + KEYWORD_WEIGHTS.put("风险评估", 8); + KEYWORD_WEIGHTS.put("内部审计", 9); + KEYWORD_WEIGHTS.put("反舞弊", 9); + KEYWORD_WEIGHTS.put("内部控制评价", 8); + KEYWORD_WEIGHTS.put("岗位轮岗", 7); + KEYWORD_WEIGHTS.put("考核体系", 7); + } + + private AuditContent8InternalControlConstants() { + // 防止实例化 + } +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/controller/AuditContent8Controller.java b/src/main/java/com/gxwebsoft/ai/controller/AuditContent8Controller.java new file mode 100644 index 0000000..4e501b7 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/controller/AuditContent8Controller.java @@ -0,0 +1,299 @@ +package com.gxwebsoft.ai.controller; + +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.gxwebsoft.ai.dto.AuditContentRequest; +import com.gxwebsoft.ai.dto.export.InternalControlExportEntity; +import com.gxwebsoft.ai.entity.AiCloudDoc; +import com.gxwebsoft.ai.entity.AiCloudFile; +import com.gxwebsoft.ai.service.AiHistoryService; +import com.gxwebsoft.ai.utils.ExcelExportTool; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.pwl.entity.PwlProjectLibrary; +import com.gxwebsoft.pwl.service.PwlProjectLibraryService; +import com.gxwebsoft.ai.service.AiCloudDocService; +import com.gxwebsoft.ai.service.AiCloudFileService; +import com.gxwebsoft.ai.service.AuditContent8InternalControlService; +import com.gxwebsoft.ai.service.KnowledgeBaseService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.digest.DigestUtil; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * 审计内容8控制器 - 单位层面财务管理内部控制测试表 + */ +@Slf4j +@Tag(name = "审计内容8-内部控制测试") +@RestController +@RequestMapping("/api/ai/auditContent8") +public class AuditContent8Controller extends BaseController { + + @Autowired + private AuditContent8InternalControlService auditContent8InternalControlService; + + @Autowired + private AiCloudDocService aiCloudDocService; + + @Autowired + private AiCloudFileService aiCloudFileService; + + @Autowired + private KnowledgeBaseService knowledgeBaseService; + + @Autowired + private PwlProjectLibraryService pwlProjectLibraryService; + + @Autowired + private AiHistoryService aiHistoryService; + + // 历史记录有效期(分钟) + private static final int HISTORY_EXPIRE_MINUTES = 10; + + /** + * 生成单位层面财务管理内部控制测试表数据 + */ + @Operation(summary = "生成单位层面财务管理内部控制测试表") + @PostMapping("/generateInternalControlTable") + public ApiResult generateInternalControlTable(@RequestBody AuditContentRequest request, HttpServletRequest servletRequest) { + return generateTableData(request, servletRequest.getRequestURI(), + (params) -> auditContent8InternalControlService.generateInternalControlTableData( + params.knowledgeBaseId, params.libraryKbIds, params.projectLibrary, + params.username, params.history, params.suggestion + )); + } + + /** + * 通用的表格数据生成方法 + */ + private ApiResult generateTableData(AuditContentRequest request, String interfaceName, Function generateFunction) { + final User loginUser = getLoginUser(); + String requestHistory = request.getHistory(); + request.setHistory(""); + + // 检查历史记录 + String requestHash = generateRequestHash(request, interfaceName); + var history = aiHistoryService.getValidHistory(requestHash, interfaceName, HISTORY_EXPIRE_MINUTES); + if (history != null) { + log.info("返回历史数据,请求哈希: {}", requestHash); + return success(JSONObject.parseObject(history.getResponseData())); + } + request.setHistory(requestHistory); + + String kbIdTmp = ""; + String libraryKbIds = ""; + + try { + // 创建临时知识库(如果需要) + if (hasUploadedFiles(request)) { + kbIdTmp = createTempKnowledgeBase(request); + } + + // 查询项目库信息 + libraryKbIds = getLibraryKbIds(request.getLibraryIds()); + + // 生成数据 + String knowledgeBaseId = getKnowledgeBaseId(kbIdTmp, request.getKbIds()); + GenerateParams params = new GenerateParams(knowledgeBaseId, libraryKbIds, request.getProjectLibrary(), loginUser.getUsername(), request.getHistory(), request.getSuggestion()); + + JSONObject result = generateFunction.apply(params); + + if(result.getBoolean("success")) { + // 保存到历史记录 + saveToHistory(request, interfaceName, requestHash, result, loginUser); + } + + return success(result); + } catch (Exception e) { + log.error("生成表格数据失败,接口: {}", interfaceName, e); + return fail("生成表格数据失败: " + e.getMessage()); + } finally { + cleanupTempKnowledgeBase(kbIdTmp); + } + } + + /** + * 生成请求哈希 + */ + private String generateRequestHash(AuditContentRequest request, String interfaceName) { + String requestJson = JSONObject.toJSONString(request); + return DigestUtil.md5Hex(interfaceName + ":" + requestJson); + } + + /** + * 保存到历史记录 + */ + private void saveToHistory(AuditContentRequest request, String interfaceName, String requestHash, JSONObject result, User loginUser) { + try { + aiHistoryService.saveHistory(requestHash, interfaceName, JSONObject.toJSONString(request), result.toJSONString(), loginUser.getUserId(), loginUser.getUsername(), loginUser.getTenantId()); + } catch (Exception e) { + log.warn("保存历史记录失败", e); + } + } + + /** + * 检查是否有上传的文件 + */ + private boolean hasUploadedFiles(AuditContentRequest request) { + return !request.getDocList().isEmpty() || !request.getFileList().isEmpty(); + } + + /** + * 获取知识库ID + */ + private String getKnowledgeBaseId(String tempKbId, String requestKbIds) { + return StrUtil.isNotBlank(tempKbId) ? tempKbId : requestKbIds; + } + + /** + * 获取项目库KB IDs + */ + private String getLibraryKbIds(String libraryIds) { + if (StrUtil.isBlank(libraryIds)) { + return ""; + } + List idList = StrUtil.split(libraryIds, ','); + List ret = pwlProjectLibraryService.list(new LambdaQueryWrapper().in(PwlProjectLibrary::getId, idList)); + return ret.stream().map(PwlProjectLibrary::getKbId).filter(StrUtil::isNotBlank).collect(Collectors.joining(",")); + } + + /** + * 创建临时知识库并提交文档 + */ + private String createTempKnowledgeBase(AuditContentRequest request) { + String kbIdTmp = knowledgeBaseService.createKnowledgeBaseTemp(); + // 收集文档ID + Set docIds = request.getDocList().stream().flatMap(docId -> aiCloudDocService.getSelfAndChildren(docId).stream()).map(AiCloudDoc::getId).collect(Collectors.toSet()); + // 查询相关文件 + List fileList = getRelatedFiles(docIds, request.getFileList()); + // 提取文件ID并提交到知识库 + Set kbFileIds = fileList.stream().map(AiCloudFile::getFileId).collect(Collectors.toSet()); + if (!kbFileIds.isEmpty()) { + knowledgeBaseService.submitDocuments(kbIdTmp, new ArrayList<>(kbFileIds)); + } + return kbIdTmp; + } + + /** + * 获取相关文件列表 + */ + private List getRelatedFiles(Set docIds, List fileList) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper() + .in(!docIds.isEmpty(), AiCloudFile::getDocId, docIds) + .or(!fileList.isEmpty()) + .in(!fileList.isEmpty(), AiCloudFile::getId, fileList); + return aiCloudFileService.list(queryWrapper); + } + + /** + * 清理临时知识库 + */ + private void cleanupTempKnowledgeBase(String kbId) { + if (StrUtil.isNotBlank(kbId)) { + try { + knowledgeBaseService.deleteIndex(kbId); + } catch (Exception e) { + log.warn("删除临时知识库失败: {}", kbId, e); + } + } + } + + /** + * 导出单位层面财务管理内部控制测试表到Excel + */ + @Operation(summary = "导出单位层面财务管理内部控制测试表到Excel") + @PostMapping("/exportInternalControlTable") + public void exportInternalControlTable(@RequestBody Map request, HttpServletResponse response) { + exportToExcel(request, response, "单位层面财务管理内部控制测试表", + this::convertToInternalControlEntityList, InternalControlExportEntity.class); + } + + /** + * 通用的Excel导出方法 + */ + private void exportToExcel(Map request, HttpServletResponse response, + String sheetName, Function>, List> converter, + Class entityClass) { + List> dataList = (List>) request.get("data"); + String companyName = (String) request.get("companyName"); + + List exportData = converter.apply(dataList); + + String fileName = sheetName + "_" + (companyName != null ? companyName : "未知公司"); + String title = companyName != null ? companyName + " - " + sheetName : sheetName; + + ExcelExportTool.exportExcel(exportData, entityClass, fileName, sheetName, title, response); + } + + /** + * 参数包装类 + */ + private static class GenerateParams { + final String knowledgeBaseId; + final String libraryKbIds; + final String projectLibrary; + final String username; + final String history; + final String suggestion; + + GenerateParams(String knowledgeBaseId, String libraryKbIds, String projectLibrary, String username, String history, String suggestion) { + this.knowledgeBaseId = knowledgeBaseId; + this.libraryKbIds = libraryKbIds; + this.projectLibrary = projectLibrary; + this.username = username; + this.history = history; + this.suggestion = suggestion; + } + } + + // ========== 数据转换方法 ========== + + private List convertToInternalControlEntityList(List> originalData) { + return originalData.stream().map(this::convertToInternalControlEntity).collect(Collectors.toList()); + } + + private InternalControlExportEntity convertToInternalControlEntity(Map item) { + InternalControlExportEntity entity = new InternalControlExportEntity(); + entity.setIndex(getStringValue(item, "index")); + entity.setControlLink(getStringValue(item, "controlLink")); + entity.setControlRequirement(getStringValue(item, "controlRequirement")); + entity.setControlActivity(getStringValue(item, "controlActivity")); + entity.setControlPosition(getStringValue(item, "controlPosition")); + entity.setTestSteps(getStringValue(item, "testSteps")); + entity.setCheckEvidence(getStringValue(item, "checkEvidence")); + entity.setTestResult(getStringValue(item, "testResult")); + entity.setWorkPaperIndex(formatWorkPaperIndex(item.get("workPaperIndex"))); + return entity; + } + + private String getStringValue(Map map, String key) { + Object value = map.get(key); + return value != null ? value.toString() : ""; + } + + private String formatWorkPaperIndex(Object workPaperIndex) { + if (workPaperIndex == null) { + return ""; + } + + if (workPaperIndex instanceof List) { + List list = (List) workPaperIndex; + return String.join(", ", list.stream() + .map(Object::toString) + .collect(Collectors.toList())); + } + + return workPaperIndex.toString(); + } +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/dto/export/InternalControlExportEntity.java b/src/main/java/com/gxwebsoft/ai/dto/export/InternalControlExportEntity.java new file mode 100644 index 0000000..1987abd --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/dto/export/InternalControlExportEntity.java @@ -0,0 +1,40 @@ +package com.gxwebsoft.ai.dto.export; + +import cn.afterturn.easypoi.excel.annotation.Excel; +import cn.afterturn.easypoi.excel.annotation.ExcelTarget; +import lombok.Data; + +/** + * 单位层面财务管理内部控制测试表导出实体 + */ +@Data +@ExcelTarget("InternalControlExportEntity") +public class InternalControlExportEntity { + + @Excel(name = "序号", orderNum = "1", width = 8) + private String index; + + @Excel(name = "控制环节", orderNum = "2", width = 25) + private String controlLink; + + @Excel(name = "控制要求", orderNum = "3", width = 25) + private String controlRequirement; + + @Excel(name = "控制活动描述", orderNum = "4", width = 30) + private String controlActivity; + + @Excel(name = "控制职责岗位", orderNum = "5", width = 20) + private String controlPosition; + + @Excel(name = "测试步骤", orderNum = "6", width = 30) + private String testSteps; + + @Excel(name = "检查的证据及测试内容", orderNum = "7", width = 40) + private String checkEvidence; + + @Excel(name = "测试结果", orderNum = "8", width = 12) + private String testResult; + + @Excel(name = "工作底稿索引", orderNum = "9", width = 25) + private String workPaperIndex; +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/service/AuditContent8InternalControlService.java b/src/main/java/com/gxwebsoft/ai/service/AuditContent8InternalControlService.java new file mode 100644 index 0000000..75a0169 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/service/AuditContent8InternalControlService.java @@ -0,0 +1,13 @@ +package com.gxwebsoft.ai.service; + +import com.alibaba.fastjson.JSONObject; + +public interface AuditContent8InternalControlService { + + /** + * 生成单位层面财务管理内部控制测试表数据 + */ + JSONObject generateInternalControlTableData( + String kbIds, String libraryKbIds, String projectLibrary, + String userName, String history, String suggestion); +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/service/impl/AbstractAuditContentService.java b/src/main/java/com/gxwebsoft/ai/service/impl/AbstractAuditContentService.java index f047bc2..a84ffc8 100644 --- a/src/main/java/com/gxwebsoft/ai/service/impl/AbstractAuditContentService.java +++ b/src/main/java/com/gxwebsoft/ai/service/impl/AbstractAuditContentService.java @@ -20,6 +20,7 @@ import org.springframework.beans.factory.annotation.Autowired; import java.util.*; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; @Slf4j public abstract class AbstractAuditContentService { @@ -32,6 +33,9 @@ public abstract class AbstractAuditContentService { protected static final String DIFY_WORKFLOW_URL = "http://1.14.159.185:8180/v1/workflows/run"; + // 用于同步的锁对象池 + private static final Map kbLocks = new ConcurrentHashMap<>(); + /** * 调用工作流通用方法 */ @@ -99,36 +103,40 @@ public abstract class AbstractAuditContentService { /** * 查询知识库通用方法 + * 同一个kbId,全局线程情况下,只支持一个,不支持异步多并发,并按队列顺序执行 */ protected List queryKnowledgeBase(String kbId, List queries, int topK) { Set results = new LinkedHashSet<>(); String workspaceId = config.getWorkspaceId(); - try { - Client client = clientFactory.createClient(); - - for (String query : queries) { - try { - RetrieveResponse resp = KnowledgeBaseUtil.retrieveIndex(client, workspaceId, kbId, query); - - Optional.ofNullable(resp) - .map(RetrieveResponse::getBody) - .map(RetrieveResponseBody::getData) - .map(RetrieveResponseBodyData::getNodes) - .orElse(Collections.emptyList()) - .stream() - .limit(topK) - .forEach(node -> processKnowledgeNode(node, results)); - - } catch (Exception e) { - log.warn("查询知识库失败 - kbId: {}, query: {}", kbId, query, e); - } - } - - } catch (Exception e) { - log.error("创建知识库客户端失败", e); - } + // 获取或创建该kbId对应的锁对象 + Object lock = kbLocks.computeIfAbsent(kbId, k -> new Object()); + // 使用synchronized确保同一个kbId的查询串行执行 + synchronized (lock) { + try { + Client client = clientFactory.createClient(); + for (String query : queries) { + try { + RetrieveResponse resp = KnowledgeBaseUtil.retrieveIndex(client, workspaceId, kbId, query); + + Optional.ofNullable(resp) + .map(RetrieveResponse::getBody) + .map(RetrieveResponseBody::getData) + .map(RetrieveResponseBodyData::getNodes) + .orElse(Collections.emptyList()) + .stream() + .limit(topK) + .forEach(node -> processKnowledgeNode(node, results)); + + } catch (Exception e) { + log.warn("查询知识库失败 - kbId: {}, query: {}", kbId, query, e); + } + } + } catch (Exception e) { + log.error("创建知识库客户端失败", e); + } + } return new ArrayList<>(results); } diff --git a/src/main/java/com/gxwebsoft/ai/service/impl/AuditContent8InternalControlServiceImpl.java b/src/main/java/com/gxwebsoft/ai/service/impl/AuditContent8InternalControlServiceImpl.java new file mode 100644 index 0000000..ef2a14d --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/service/impl/AuditContent8InternalControlServiceImpl.java @@ -0,0 +1,326 @@ +package com.gxwebsoft.ai.service.impl; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.gxwebsoft.ai.constants.AuditContent8InternalControlConstants; +import com.gxwebsoft.ai.service.AuditContent8InternalControlService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +import cn.hutool.core.util.StrUtil; + +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +@Slf4j +@Service +public class AuditContent8InternalControlServiceImpl extends AbstractAuditContentService implements AuditContent8InternalControlService { + + // 工作流配置 + private static final String DIFY_WORKFLOW_TOKEN = "Bearer app-R1GN6zKkrfBYVMtwz8c6pyRM"; + + // 审计测试方法 + private static final List TEST_METHODS = Arrays.asList( + "符合性测试", "穿行测试", "抽样测试", "观察询问", "文档查阅" + ); + + @Override + public JSONObject generateInternalControlTableData(String kbIds, String libraryKbIds, String projectLibrary, String userName, String history, String suggestion) { + log.info("开始生成单位层面财务管理内部控制测试表数据 - 用户: {}, kbIds: {}, libraryIds: {}, projectLibrary: {}", userName, kbIds, libraryKbIds, projectLibrary); + + long startTime = System.currentTimeMillis(); + + try { + // 异步并行处理每个控制环节 + Map> futures = processCategoriesAsync( + Arrays.asList(AuditContent8InternalControlConstants.CONTROL_LINKS), + controlLink -> generateControlLinkDataAsync(controlLink, kbIds, libraryKbIds, projectLibrary, userName, history, suggestion) + ); + + // 等待所有异步任务完成 + CompletableFuture.allOf(futures.values().toArray(new CompletableFuture[0])).join(); + + // 合并所有分类的结果 + JSONArray allData = mergeCategoryResults(Arrays.asList(AuditContent8InternalControlConstants.CONTROL_LINKS), futures); + + log.info("单位层面财务管理内部控制测试表生成成功 - 记录数: {}, 处理时间: {}ms", allData.size(), (System.currentTimeMillis() - startTime)); + + return buildSuccessResponse(allData, startTime, "internal_control_audit"); + + } catch (Exception e) { + log.error("生成单位层面财务管理内部控制测试表失败", e); + return buildErrorResponse("生成单位层面财务管理内部控制测试表失败: " + e.getMessage()); + } + } + + /** + * 异步生成单个控制环节的数据 + */ + @Async + public CompletableFuture generateControlLinkDataAsync(String controlLink, String kbIds, String libraryKbIds, String projectLibrary, String userName, String history, String suggestion) { + return CompletableFuture.supplyAsync(() -> { + try { + log.info("开始生成控制环节 {} 的数据", controlLink); + + // 1. 为当前控制环节召回相关知识 + Map> knowledgeSources = retrieveKnowledgeForControlLink( + controlLink, kbIds, libraryKbIds, projectLibrary + ); + + // 2. 构建完整的知识上下文 + String knowledgeContext = buildCompleteKnowledgeContext( + controlLink, knowledgeSources, history, suggestion + ); + + // 3. 调用工作流生成数据 + JSONObject requestBody = buildWorkflowRequest(knowledgeContext, userName); + JSONArray controlLinkData = callWorkflow(DIFY_WORKFLOW_URL, DIFY_WORKFLOW_TOKEN, requestBody, "内部控制-" + controlLink); + + log.info("控制环节 {} 数据生成完成,生成 {} 条记录", controlLink, controlLinkData.size()); + return controlLinkData; + + } catch (Exception e) { + log.error("生成控制环节 {} 数据失败", controlLink, e); + return new JSONArray(); + } + }); + } + + /** + * 为单个控制环节检索相关知识 + */ + private Map> retrieveKnowledgeForControlLink(String controlLink, String kbIds, String libraryKbIds, String projectLibrary) { + Map> knowledgeSources = new HashMap<>(); + knowledgeSources.put("enterprise", new ArrayList<>()); + knowledgeSources.put("regulation", new ArrayList<>()); + knowledgeSources.put("auditCase", new ArrayList<>()); + + // 构建当前控制环节的查询词 + List controlQueries = buildControlLinkQueries(controlLink); + + // 企业单位库检索 + if (StrUtil.isNotBlank(kbIds)) { + Arrays.stream(kbIds.split(",")) + .map(String::trim) + .filter(StrUtil::isNotBlank) + .forEach(kbId -> knowledgeSources.get("enterprise") + .addAll(queryKnowledgeBase(kbId, controlQueries, 120))); + } + + // 公共法律法规库检索 + if (StrUtil.isNotBlank(libraryKbIds)) { + Arrays.stream(libraryKbIds.split(",")) + .map(String::trim) + .filter(StrUtil::isNotBlank) + .forEach(libId -> knowledgeSources.get("regulation") + .addAll(queryKnowledgeBase(libId, controlQueries, 100))); + } + + // 审计案例库检索 + if (StrUtil.isNotBlank(projectLibrary)) { + knowledgeSources.get("auditCase").addAll( + queryKnowledgeBase(projectLibrary, controlQueries, 80)); + } + + // 智能去重和排序 + knowledgeSources.forEach((key, list) -> { + List processed = list.stream() + .distinct() + .sorted(this::internalControlComparator) + .limit(getLimitBySourceType(key)) + .collect(Collectors.toList()); + knowledgeSources.put(key, processed); + }); + + log.debug("控制环节 {} 知识检索完成 - 企业: {}条, 法规: {}条, 案例: {}条", + controlLink, + knowledgeSources.get("enterprise").size(), + knowledgeSources.get("regulation").size(), + knowledgeSources.get("auditCase").size()); + + return knowledgeSources; + } + + /** + * 构建控制环节特定的查询词 + */ + private List buildControlLinkQueries(String controlLink) { + // 根据控制环节构建相关查询词 + List queries = new ArrayList<>(); + + // 通用查询词 + queries.add(controlLink); + queries.add("内部控制 " + controlLink); + queries.add("财务管理 " + controlLink); + + // 根据特定控制环节添加相关关键词 + if (controlLink.contains("岗位")) { + queries.add("岗位职责 不相容职务分离"); + queries.add("岗位设置 职责划分"); + } + if (controlLink.contains("授权审批")) { + queries.add("授权审批流程 审批权限"); + queries.add("财务管理授权"); + } + if (controlLink.contains("重大事项")) { + queries.add("重大事项决策 集体决策"); + queries.add("三重一大 财务管理"); + } + if (controlLink.contains("会计")) { + queries.add("会计核算 会计档案"); + queries.add("会计凭证 会计账簿"); + } + if (controlLink.contains("信息系统")) { + queries.add("财务信息系统 数据备份"); + queries.add("信息安全 用户管理"); + } + if (controlLink.contains("风险")) { + queries.add("财务风险管理 风险识别"); + queries.add("风险评估 风险应对"); + } + if (controlLink.contains("内部审计")) { + queries.add("内审部门 审计监督"); + queries.add("审计检查 审计报告"); + } + if (controlLink.contains("反舞弊")) { + queries.add("财务舞弊 举报投诉"); + queries.add("舞弊防范 举报保护"); + } + + return queries; + } + + /** + * 构建完整的知识上下文 + */ + private String buildCompleteKnowledgeContext(String controlLink, Map> knowledgeSources, String history, String suggestion) { + StringBuilder context = new StringBuilder(); + + // 1. 单位层面财务管理内部控制测试要求 + context.append("## 单位层面财务管理内部控制测试要求 - ").append(controlLink).append("\n"); + context.append("请基于以下知识生成").append(controlLink).append("相关的单位层面财务管理内部控制测试数据:\n\n"); + context.append("1. 控制环节:").append(controlLink).append("\n"); + context.append("2. 控制要求:").append(AuditContent8InternalControlConstants.CONTROL_REQUIREMENTS.get(controlLink)).append("\n"); + context.append("3. 控制活动描述:").append(AuditContent8InternalControlConstants.CONTROL_ACTIVITIES.get(controlLink)).append("\n"); + context.append("4. 控制职责岗位:").append(AuditContent8InternalControlConstants.CONTROL_POSITIONS.get(controlLink)).append("\n"); + context.append("5. 测试步骤:\n"); + AuditContent8InternalControlConstants.TEST_STEPS.get(controlLink).forEach(step -> + context.append(" ").append(step).append("\n")); + context.append("\n"); + + context.append("## 审计测试要求\n"); + context.append("1. 基于测试步骤逐项检查,评估内部控制的有效性\n"); + context.append("2. 检查证据必须具体到查阅的文件名称、内容、检查过程和发现的问题\n"); + context.append("3. 测试结果必须基于充分证据严格判断:\n"); + context.append(" - 通过:所有测试步骤均得到有效执行且证据充分\n"); + context.append(" - 不通过:任一测试步骤未执行、执行不到位或证据不足\n"); + context.append("4. 工作底稿索引必须填写实际存在的完整文件名\n"); + context.append("5. 重点关注货币资金管理、财务报销、资产管理、物资采购等关键环节\n\n"); + + // 2. 数据格式要求 + context.append("## 数据格式要求\n"); + context.append("需要生成").append(controlLink).append("控制环节的内部控制测试数据:\n\n"); + + context.append("**重要:每个测试步骤必须单独生成一条记录**\n\n"); + + context.append("每条记录应包含以下字段:\n"); + context.append("- controlLink(控制环节):固定值,不要修改\n"); + context.append("- controlRequirement(控制要求):固定值,不要修改\n"); + context.append("- controlActivity(控制活动描述):固定值,不要修改\n"); + context.append("- controlPosition(控制职责岗位):固定值,不要修改\n"); + context.append("- testSteps(测试步骤):**只包含当前步骤内容**,如\"(1)是否有制度规定\"\n"); + context.append("- checkEvidence(检查证据):针对当前测试步骤的详细检查证据\n"); + context.append("- testResult(测试结果):基于当前测试步骤的检查证据严格判断,只有证据充分且完全符合要求才能判定为\"通过\",否则必须判定为\"不通过\"\n"); + context.append("- workPaperIndex(工作底稿索引):实际存在的完整文件名,确保能在文件夹中搜索到\n\n"); + + context.append("**生成规则:**\n"); + context.append("1. **每个测试步骤单独生成一条完整记录**\n"); + context.append("2. 如果测试步骤中有多个小点(如(1)、(2)、(3)),必须为每个小点生成独立记录\n"); + context.append("3. 每条记录的检查证据必须专门针对该测试步骤\n"); + context.append("4. 每条记录的测试结果必须基于该步骤的检查证据独立判断\n"); + context.append("5. 控制环节、控制要求、控制活动描述、控制职责岗位字段在所有相关记录中保持相同\n\n"); + + context.append("**注意:**\n"); + context.append("1. 检查证据必须详细描述:针对当前测试步骤查阅了哪些制度文件、检查了哪些业务凭证、访谈了哪些人员、发现了什么问题\n"); + context.append("2. 测试结果判定必须严格,对于制度不健全、执行不到位、证据不充分的情况必须判定为\"不通过\"\n"); + context.append("3. 工作底稿索引必须准确对应实际审计工作底稿文件名称,不能使用章节标题\n"); + context.append("4. 审计检查可采用:符合性测试、穿行测试、抽样测试、观察询问、文档查阅等方法\n"); + context.append("5. 重点关注内部控制制度的建立健全性和执行的有效性\n\n"); + + // 3. 审计测试方法参考 + context.append("## 审计测试方法参考\n"); + context.append("审计时可采用的测试方法:\n"); + TEST_METHODS.forEach(method -> context.append("- ").append(method).append("\n")); + context.append("\n"); + + // 4. 历史内容 + if (StrUtil.isNotBlank(history)) { + context.append("## 历史生成内容\n"); + context.append("以下是之前生成的内容,请基于此进行优化:\n"); + context.append(history).append("\n\n"); + } + + // 5. 用户建议 + if (StrUtil.isNotBlank(suggestion)) { + context.append("## 用户优化建议\n"); + context.append("请根据以下建议对生成内容进行调整:\n"); + context.append(suggestion).append("\n\n"); + } + + // 6. 企业单位知识 + if (!knowledgeSources.get("enterprise").isEmpty()) { + context.append("## 企业单位知识\n"); + knowledgeSources.get("enterprise").forEach(knowledge -> + context.append(knowledge).append("\n")); + context.append("\n"); + } + + // 7. 法律法规知识 + if (!knowledgeSources.get("regulation").isEmpty()) { + context.append("## 法律法规知识\n"); + knowledgeSources.get("regulation").forEach(knowledge -> + context.append(knowledge).append("\n")); + context.append("\n"); + } + + // 8. 审计案例知识 + if (!knowledgeSources.get("auditCase").isEmpty()) { + context.append("## 审计案例知识\n"); + knowledgeSources.get("auditCase").forEach(knowledge -> + context.append(knowledge).append("\n")); + } + + return context.toString(); + } + + /** + * 内部控制相关性比较器 + */ + private int internalControlComparator(String reg1, String reg2) { + int score1 = calculateInternalControlRelevanceScore(reg1); + int score2 = calculateInternalControlRelevanceScore(reg2); + return Integer.compare(score2, score1); + } + + /** + * 计算内部控制相关性分数 + */ + private int calculateInternalControlRelevanceScore(String content) { + return AuditContent8InternalControlConstants.KEYWORD_WEIGHTS.entrySet().stream() + .filter(entry -> content.contains(entry.getKey())) + .mapToInt(Map.Entry::getValue) + .sum(); + } + + private int getLimitBySourceType(String sourceType) { + switch (sourceType) { + case "enterprise": return 120; + case "regulation": return 100; + case "auditCase": return 80; + default: return 50; + } + } + +} \ No newline at end of file