Compare commits

...

8 Commits

47 changed files with 3586 additions and 279 deletions

61
doc/sql/update_260308.sql Normal file
View File

@@ -0,0 +1,61 @@
ALTER TABLE `cms_demo`.`pwl_project`
ADD COLUMN `person_name` varchar(50) NULL COMMENT '针对用户名称' AFTER `company_id`,
ADD COLUMN `position` varchar(50) NULL COMMENT '用户职务' AFTER `person_name`;
CREATE TABLE `audit_evidence` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`case_index` varchar(255) DEFAULT NULL COMMENT '案件编号',
`project_id` int(11) NOT NULL COMMENT '项目编号',
`project_name` varchar(255) DEFAULT NULL COMMENT '项目名称',
`content_type` int(10) unsigned NOT NULL COMMENT '内容类型1到11',
`audited_target` varchar(255) DEFAULT NULL COMMENT '被审计单位或个人',
`audit_matter_type` varchar(50) DEFAULT NULL COMMENT '审计事项类型',
`audit_matter` varchar(100) DEFAULT NULL COMMENT '审计事项描述',
`summary_title` varchar(255) DEFAULT NULL COMMENT '核心问题标题',
`audit_record` varchar(2000) DEFAULT NULL COMMENT '客观的审计核查事实记录',
`audit_finding` varchar(255) DEFAULT NULL COMMENT '审计发现的具体问题',
`evidence_basis` varchar(255) DEFAULT NULL COMMENT '定性依据',
`handling` varchar(255) DEFAULT NULL COMMENT '拟采取的处理措施',
`suggestion` varchar(255) DEFAULT NULL COMMENT '改进或整改建议',
`attachment` varchar(255) DEFAULT NULL COMMENT '随附的证明材料',
`auditors` varchar(50) DEFAULT NULL COMMENT '审计人员姓名',
`compile_date` varchar(20) DEFAULT NULL COMMENT '编制日期',
`provider_opinion` varchar(255) DEFAULT NULL COMMENT '证据提供单位或个人意见',
`provider_date` varchar(20) DEFAULT NULL COMMENT '证据提供日期',
`attachment_pages` int(11) DEFAULT NULL COMMENT '附件页数',
`feedback_deadline` varchar(100) DEFAULT NULL COMMENT '反馈期限',
`data` longtext COMMENT '取证单数据',
`user_id` int(11) DEFAULT NULL COMMENT '用户ID',
`username` varchar(30) DEFAULT NULL COMMENT '用户名',
`status` int(11) DEFAULT NULL COMMENT '状态, 0正常, 1冻结',
`deleted` int(11) DEFAULT NULL COMMENT '是否删除, 0否, 1是',
`tenant_id` int(11) DEFAULT NULL COMMENT '租户id',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '修改时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COMMENT='审计取证单表';
-- 审计报告表
CREATE TABLE IF NOT EXISTS `audit_report` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '主键 ID',
`project_id` INT(11) DEFAULT NULL COMMENT '项目编号',
`project_name` VARCHAR(255) DEFAULT NULL COMMENT '项目名称',
`case_index` VARCHAR(100) DEFAULT NULL COMMENT '案引号',
`audited_target` VARCHAR(255) DEFAULT NULL COMMENT '被审计单位',
`report_content` LONGTEXT COMMENT '报告内容JSON 格式,包含 sections 和 previewHtml',
`preview_html` LONGTEXT COMMENT '报告预览 HTML',
`section_count` INT(11) DEFAULT NULL COMMENT '章节数量',
`user_id` INT(11) DEFAULT NULL COMMENT '用户 ID',
`username` VARCHAR(100) DEFAULT NULL COMMENT '用户名',
`status` INT(1) DEFAULT 0 COMMENT '状态0 正常1 冻结',
`deleted` INT(1) DEFAULT 0 COMMENT '是否删除0 否1 是',
`tenant_id` INT(11) DEFAULT NULL COMMENT '租户 id',
`form_commit` INT(11) DEFAULT NULL COMMENT '表单提交标识,用于判断记录是否存在',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
PRIMARY KEY (`id`),
KEY `idx_project_id` (`project_id`),
KEY `idx_form_commit` (`form_commit`),
KEY `idx_project_form` (`project_id`, `form_commit`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='审计报告表';

BIN
doc/尾页.docx Normal file

Binary file not shown.

View File

@@ -16,14 +16,16 @@ import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
/**
* AI审计历史记录表控制器
*
* @author yc
*/
@Tag(name = "AI审计历史记录表管理")
@RestController
@RequestMapping("/api/ai/history")
@Slf4j
public class AiHistoryController extends BaseController {
@Resource
private AiHistoryService aiHistoryService;
@@ -116,7 +118,7 @@ public class AiHistoryController extends BaseController {
}
//@PreAuthorize("hasAuthority('ai:aiHistory:remove')")
@Operation(summary = "批量删除AI审计历史记录表")
@Operation(summary = "批量删除 AI审计历史记录表")
@DeleteMapping("/batch")
public ApiResult<?> removeBatch(@RequestBody List<Long> ids) {
if (aiHistoryService.removeByIds(ids)) {
@@ -124,4 +126,32 @@ public class AiHistoryController extends BaseController {
}
return fail("删除失败");
}
/**
* 根据接口名称和项目 ID 查询最新的历史记录
*/
@Operation(summary = "查询最新的历史记录")
@GetMapping("/latest")
public ApiResult<AiHistory> getLatestHistory(
@RequestParam String interfaceName,
@RequestParam Long projectId) {
try {
log.info("===== 查询最新历史记录 =====");
log.info("interfaceName: {}", interfaceName);
log.info("projectId: {}", projectId);
AiHistory latestHistory = aiHistoryService.getLatestHistory(interfaceName, projectId);
if (latestHistory != null) {
log.info("查询到历史记录 - id: {}, interfaceName: {}", latestHistory.getId(), latestHistory.getInterfaceName());
} else {
log.warn("未查询到历史记录 - interfaceName: {}, projectId: {}", interfaceName, projectId);
}
return success(latestHistory);
} catch (Exception e) {
log.error("查询最新历史记录异常", e);
return fail("查询最新历史记录异常:" + e.getMessage(), null);
}
}
}

View File

@@ -12,6 +12,7 @@ import com.gxwebsoft.common.core.web.ApiResult;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@@ -26,19 +27,17 @@ import java.util.stream.Collectors;
* 审计内容1控制器 - 八项规定对比分析
*/
@Slf4j
@RequiredArgsConstructor
@Tag(name = "审计内容1-八项规定")
@RestController
@RequestMapping("/api/ai/auditContent1")
public class AuditContent1Controller extends BaseAuditContentController {
@Autowired
private AuditContent1LeaderListService auditContent1LeaderListService;
private final AuditContent1LeaderListService auditContent1LeaderListService;
@Autowired
private AuditContent1ExpenseService auditContent1ExpenseService;
private final AuditContent1ExpenseService auditContent1ExpenseService;
@Autowired
private AuditContent1EightRegService auditContent1EightRegService;
private final AuditContent1EightRegService auditContent1EightRegService;
/**
* 生成领导班子名单数据

View File

@@ -1,19 +1,27 @@
package com.gxwebsoft.ai.controller;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.gxwebsoft.ai.dto.AuditEvidenceRequest;
import com.gxwebsoft.ai.entity.AuditEvidence;
import com.gxwebsoft.ai.param.AuditEvidenceParam;
import com.gxwebsoft.ai.service.AuditEvidenceService;
import com.gxwebsoft.common.core.web.ApiResult;
import com.gxwebsoft.common.core.web.BaseController;
import com.gxwebsoft.common.core.web.PageParam;
import com.gxwebsoft.common.core.web.PageResult;
import com.gxwebsoft.common.system.entity.User;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
@@ -40,39 +48,106 @@ import cn.afterturn.easypoi.word.WordExportUtil;
*/
@Slf4j
@Tag(name = "审计取证单")
@RequiredArgsConstructor
@RestController
@RequestMapping("/api/ai/auditEvidence")
public class AuditEvidenceController extends BaseController {
@Autowired
private AuditEvidenceService auditEvidenceService;
@Autowired
private TemplateConfig templateConfig;
private final AuditEvidenceService auditEvidenceService;
private final TemplateConfig templateConfig;
/**
* 生成审计取证单
*/
@Operation(summary = "生成审计取证单")
@PostMapping("/generate")
public ApiResult<?> generateAuditEvidence(@RequestBody AuditEvidenceRequest request) {
public ApiResult<AuditEvidence> generateAuditEvidence(@RequestBody AuditEvidenceRequest request) {
try {
final User loginUser = getLoginUser();
request.setUserName(loginUser.getUsername());
final User loginUser = getLoginUser();
request.setUserName(loginUser.getUsername());
log.info("接收到审计取证单生成请求 - 用户: {}, 项目: {}", request.getUserName(), request.getProjectName());
JSONObject result = auditEvidenceService.generateAuditEvidence(request);
return auditEvidenceService.generateAuditEvidence(request);
/*JSONObject result = auditEvidenceService.generateAuditEvidence(request);
if (Boolean.TRUE.equals(result.getBoolean("success"))) {
return success(result);
} else {
return fail(result.getString("error") != null ? result.getString("error") : "生成审计取证单失败");
}
}*/
} catch (Exception e) {
log.error("生成审计取证单异常", e);
return fail("生成审计取证单异常: " + e.getMessage());
return fail("生成审计取证单异常: " + e.getMessage(), null);
}
}
/**
* 分页查询审计取证单记录
*/
@Operation(summary = "分页查询审计取证单记录")
@PostMapping("/page")
public ApiResult<PageResult<AuditEvidence>> getAuditEvidencePage(
@Parameter(description = "页码,默认为 1") @RequestParam(defaultValue = "1") Integer current,
@Parameter(description = "页面大小,默认为 10") @RequestParam(defaultValue = "10") Integer size,
@RequestBody(required = false) AuditEvidenceParam param) {
try {
// 创建分页对象
param.setPage(current.longValue());
param.setLimit(size.longValue());
PageParam<AuditEvidence, AuditEvidenceParam> page =
new PageParam<>(param);
// 调用服务层分页查询方法
PageResult<AuditEvidence> resultPage =
auditEvidenceService.getAuditEvidencePage(page, param);
return success(resultPage);
} catch (Exception e) {
log.error("查询审计取证单分页记录失败", e);
return fail("查询审计取证单分页记录失败:" + e.getMessage(), null);
}
}
/**
* 保存审计取证单到数据库
*/
@Operation(summary = "保存审计取证单")
@PostMapping("/save")
public ApiResult<AuditEvidence> saveAuditEvidence(@RequestBody AuditEvidenceRequest request) {
try {
final User loginUser = getLoginUser();
request.setUserName(loginUser.getUsername());
request.setUserId(loginUser.getUserId());
request.setTenantId(loginUser.getTenantId());
log.info("接收到审计取证单保存请求 - 用户:{}, 项目:{}", request.getUserName(), request.getProjectName());
return auditEvidenceService.saveAuditEvidence(request);
} catch (Exception e) {
log.error("保存审计取证单异常", e);
return fail("保存审计取证单异常:" + e.getMessage(), null);
}
}
/**
* 保存审计取证单到数据库
*/
@Operation(summary = "批量查询取证单接口")
@PostMapping("/batchQuery")
public ApiResult<List<AuditEvidence>> batchQueryEvidence(@RequestBody Map<String, List<Long>> request) {
List<Long> evidenceIds = request.get("evidenceIds");
if (evidenceIds == null || evidenceIds.isEmpty()) {
return success(new ArrayList<>());
}
LambdaQueryWrapper<AuditEvidence> wrapper = new LambdaQueryWrapper<>();
wrapper.in(AuditEvidence::getId, evidenceIds)
.eq(AuditEvidence::getDeleted, 0);
List<AuditEvidence> evidences = auditEvidenceService.list(wrapper);
return success(evidences);
}
/**
* 下载审计取证单Word文档
*/

View File

@@ -16,6 +16,7 @@ import com.gxwebsoft.pwl.service.PwlProjectLibraryService;
import com.gxwebsoft.ai.service.AiCloudDocService;
import com.gxwebsoft.ai.service.AiCloudFileService;
import com.gxwebsoft.ai.service.KnowledgeBaseService;
import com.gxwebsoft.ai.service.impl.AbstractAuditContentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
@@ -70,21 +71,23 @@ public abstract class BaseAuditContentController extends BaseController {
}
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());
// 如果有docList/fileList计算去重的fileIds并设置到ThreadLocal
if (hasUploadedFiles(request)) {
Set<Integer> docIds = request.getDocList().stream().flatMap(docId -> aiCloudDocService.getSelfAndChildren(docId).stream()).map(AiCloudDoc::getId).collect(Collectors.toSet());
List<AiCloudFile> relatedFiles = getRelatedFiles(docIds, request.getFileList());
List<String> fileIds = relatedFiles.stream().map(AiCloudFile::getFileId).distinct().collect(Collectors.toList());
Set<String> mainKbIds = Arrays.stream(request.getKbIds().split(",")).map(String::trim).filter(StrUtil::isNotBlank).collect(Collectors.toSet());
AbstractAuditContentService.setRequestFileIds(mainKbIds, fileIds);
}
// 生成数据(使用原来的默认知识库)
GenerateParams params = new GenerateParams(request.getKbIds(), libraryKbIds, request.getProjectLibrary(), loginUser.getUsername(), request.getHistory(), request.getSuggestion());
JSONObject result = generateFunction.apply(params);
@@ -100,7 +103,7 @@ public abstract class BaseAuditContentController extends BaseController {
log.error("生成表格数据失败,接口: {}", interfaceName, e);
return fail("生成表格数据失败: " + e.getMessage());
} finally {
cleanupTempKnowledgeBase(kbIdTmp);
AbstractAuditContentService.clearRequestFileIds();
}
}
@@ -199,7 +202,9 @@ public abstract class BaseAuditContentController extends BaseController {
* 检查是否有上传的文件
*/
protected boolean hasUploadedFiles(AuditContentRequest request) {
return !request.getDocList().isEmpty() || !request.getFileList().isEmpty();
List<Integer> docList = request.getDocList();
List<Integer> fileList = request.getFileList();
return (docList != null && !docList.isEmpty()) || (fileList != null && !fileList.isEmpty());
}
/**

View File

@@ -1,13 +1,19 @@
package com.gxwebsoft.ai.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
public class AuditEvidenceRequest {
// 基础信息
private String caseIndex; // 案引号
private Integer projectId; // 项目名称
private String projectName; // 项目名称
@Schema(description = "内容类型1到11")
private Integer contentType;
private String auditedTarget; // 被审计单位或个人
@Schema(description = "审计事项对应AuditMatterTypeEnum")
private String auditMatterType;
private String auditMatter; // 审计事项
private String summaryTitle; // 标题
private String auditRecord; // 审计记录
@@ -32,4 +38,6 @@ public class AuditEvidenceRequest {
// 用户信息
private String userName;
private Integer userId;
private Integer tenantId;
}

View File

@@ -0,0 +1,26 @@
package com.gxwebsoft.ai.dto;
import java.util.List;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* AI 生成默认话术请求 DTO
*/
@Schema(description = "AI 生成默认话术请求")
@Data
public class AuditReportDefaultTextRequest {
@Schema(description = "项目 ID", required = true)
private Integer projectId;
@Schema(description = "章节类型编码1-11", required = true)
private Integer formCommit;
@Schema(description = "章节标题")
private String chapterTitle;
@Schema(description = "选中的取证单 ID 列表")
private List<Integer> evidenceIds;
}

View File

@@ -0,0 +1,39 @@
package com.gxwebsoft.ai.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "审计报告生成请求")
@Data
public class AuditReportGenRequest {
// 基础信息
private Integer projectId; // 项目名称
@Schema(description = "内容类型1到11")
private Integer contentType;
private String auditedTarget; // 被审计单位或个人
private String summaryTitle; // 标题
private String auditRecord; // 审计记录
private String auditFinding; // 审计发现
private String evidenceBasis; // 定性依据
private String handling; // 处理
private String suggestion; // 建议
private String attachment; // 附件
private String auditors; // 审计人员
private String compileDate; // 编制日期
private String providerOpinion; // 证据提供单位意见
private String providerDate; // 证据提供日期
private String attachmentPages; // 附件页数
private String feedbackDeadline; // 反馈期限
// 导出取证单使用
private String pageIndex;
private String pageTotal;
// 历史内容(用于工作流生成)
private String history;
// 用户信息
private String userName;
private Integer userId;
private Integer tenantId;
}

View File

@@ -0,0 +1,36 @@
package com.gxwebsoft.ai.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 审计报告保存请求 DTO
*/
@Data
@Schema(description = "审计报告保存请求")
public class AuditReportSaveRequest {
@Schema(description = "项目编号")
private Integer projectId;
@Schema(description = "项目名称")
private String projectName;
@Schema(description = "案引号")
private String caseIndex;
@Schema(description = "被审计单位")
private String auditedTarget;
@Schema(description = "报告内容JSON 格式)")
private String reportContent;
@Schema(description = "报告预览 HTML")
private String previewHtml;
@Schema(description = "章节数量")
private Integer sectionCount;
@Schema(description = "表单提交标识")
private Integer formCommit;
}

View File

@@ -0,0 +1,114 @@
package com.gxwebsoft.ai.entity;
import com.baomidou.mybatisplus.annotation.*;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 审计取证单表
*
* @author yc
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Schema(name = "AuditEvidence对象", description = "审计取证单表")
@TableName("audit_evidence")
public class AuditEvidence implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "主键ID")
@TableId(value = "id", type = IdType.AUTO)
private Long id;
@Schema(description = "案件编号")
private String caseIndex;
@Schema(description = "项目编号")
private Integer projectId;
@Schema(description = "项目名称")
private String projectName;
@Schema(description = "内容类型1到11")
private Integer contentType;
@Schema(description = "被审计单位或个人")
private String auditedTarget;
@Schema(description = "审计事项类型对应AuditMatterTypeEnum")
private String auditMatterType;
@Schema(description = "审计事项描述")
private String auditMatter;
@Schema(description = "核心问题标题")
private String summaryTitle;
@Schema(description = "客观的审计核查事实记录")
private String auditRecord;
@Schema(description = "审计发现的具体问题")
private String auditFinding;
@Schema(description = "定性依据")
private String evidenceBasis;
@Schema(description = "拟采取的处理措施")
private String handling;
@Schema(description = "改进或整改建议")
private String suggestion;
@Schema(description = "随附的证明材料")
private String attachment;
@Schema(description = "审计人员姓名")
private String auditors;
@Schema(description = "编制日期")
private String compileDate;
@Schema(description = "证据提供单位或个人意见")
private String providerOpinion;
@Schema(description = "证据提供日期")
private String providerDate;
@Schema(description = "附件页数")
private Integer attachmentPages;
@Schema(description = "反馈期限")
private String feedbackDeadline;
@Schema(description = "取证单数据")
private String data;
@Schema(description = "用户ID")
private Integer userId;
@Schema(description = "用户名")
private String username;
@Schema(description = "状态, 0正常, 1冻结")
private Integer status;
@Schema(description = "是否删除, 0否, 1是")
@TableLogic
private Integer deleted;
@Schema(description = "租户id")
private Integer tenantId;
@Schema(description = "创建时间")
private LocalDateTime createTime;
@Schema(description = "修改时间")
private LocalDateTime updateTime;
@TableField(exist = false)
private Boolean success;
}

View File

@@ -0,0 +1,75 @@
package com.gxwebsoft.ai.entity;
import com.baomidou.mybatisplus.annotation.*;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 审计报告表
*
* @author yc
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Schema(name = "AuditReport 对象", description = "审计报告表")
@TableName("audit_report")
public class AuditReport implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "主键 ID")
@TableId(value = "id", type = IdType.AUTO)
private Long id;
@Schema(description = "项目编号")
private Integer projectId;
@Schema(description = "项目名称")
private String projectName;
@Schema(description = "案引号")
private String caseIndex;
@Schema(description = "被审计单位")
private String auditedTarget;
@Schema(description = "报告内容JSON 格式,包含 sections 和 previewHtml")
private String reportContent;
@Schema(description = "报告预览 HTML")
private String previewHtml;
@Schema(description = "章节数量")
private Integer sectionCount;
@Schema(description = "用户 ID")
private Integer userId;
@Schema(description = "用户名")
private String username;
@Schema(description = "状态0 正常1 冻结")
private Integer status;
@Schema(description = "是否删除0 否1 是")
@TableLogic
private Integer deleted;
@Schema(description = "租户 id")
private Integer tenantId;
@Schema(description = "创建时间")
private LocalDateTime createTime;
@Schema(description = "修改时间")
private LocalDateTime updateTime;
@Schema(description = "表单提交标识,用于判断记录是否存在")
private Integer formCommit;
@TableField(exist = false)
private Boolean success;
}

View File

@@ -0,0 +1,57 @@
package com.gxwebsoft.ai.enums;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Getter;
@AllArgsConstructor
@Getter
@Schema(description = "审计表类型")
public enum AuditMatterTypeEnum {
EIGHT_REGULATION(1,"eightReg", "八项规定"),
EXPENSE(1,"expense", "决策支出表"),
LEADER_LIST(1,"leaderList", "领导班子名单"),
STRATEGY(2,"strategyAudit", "单位发展战略执行"),
DECISION_TABLE(3,"decisionTable", "重大经济决策调查表"),
TRIPLE_ONE(3,"tripleOne", "三重一大"),
TARGET(4,"target", "目标责任制完成情况表"),
BUDGET_EXECUTION(5,"budgetExecution", "预算执行情况审计"),
BUDGET_MANAGE(5,"budgetManage", "预算管理审计"),
STATE_ASSETS(6,"assets", "国有资产管理审计"),
INVESTMENT_SITUATION(7,"investmentSituation", "重大投资情况"),
INTERNAL_CONTROL(8,"internalControl", "内部控制测试表"),
PERSONNEL_ESTABLISHMENT(9,"personnel", "人员编制管理审计"),
PARTY_CONDUCT(10,"partyConduct", "党风廉政建设责任制审计"),
HISTORY(11,"history", "历史审计问题整改");
private final Integer value;
private final String code;
private final String desc;
public static AuditMatterTypeEnum getByCode(String code) {
for (AuditMatterTypeEnum type : AuditMatterTypeEnum.values()) {
if (type.getCode().equals(code)) {
return type;
}
}
return null;
}
public static AuditMatterTypeEnum getByValue(Integer value) {
for (AuditMatterTypeEnum type : AuditMatterTypeEnum.values()) {
if (type.getValue().equals(value)) {
return type;
}
}
return null;
}
public static AuditMatterTypeEnum getByDesc(String desc) {
for (AuditMatterTypeEnum type : AuditMatterTypeEnum.values()) {
if (type.getDesc().equals(desc)) {
return type;
}
}
return null;
}
}

View File

@@ -1,5 +1,8 @@
package com.gxwebsoft.ai.enums;
import lombok.Getter;
@Getter
public enum AuditReportEnum {
// 基础信息
@@ -24,11 +27,12 @@ public enum AuditReportEnum {
INTEGRITY_COMPLIANCE(56, "五、审计内容和重点及审计方法-(六)在经济活动中落实有关党风廉政建设责任和遵守廉洁从业规定情况"),
PREV_AUDIT_ISSUES(57, "五、审计内容和重点及审计方法-(七)对以往审计中发现问题的整改情况"),
OTHER_MATTERS(58, "五、审计内容和重点及审计方法-(八)其他需要审计的事项"),
// 重要风险的识别及应对
RISK_IDENTIFY(61, "六、重要风险的识别及应对-(一)重要风险的识别"),
RISK_RESPONSE(62, "六、重要风险的识别及应对-(二)风险的应对策略"),
// 其他部分
TECHNIQUES(70, "七、审计技术方法"),
SCHEDULE(80, "八、工作步骤与时间安排"),
@@ -42,14 +46,6 @@ public enum AuditReportEnum {
this.desc = desc;
}
public Integer getCode() {
return code;
}
public String getDesc() {
return desc;
}
public String getCodeStr() {
return String.format("%02d", code);
}

View File

@@ -0,0 +1,35 @@
package com.gxwebsoft.ai.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.gxwebsoft.ai.entity.AuditEvidence;
import com.gxwebsoft.ai.param.AuditEvidenceParam;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 审计取证单表Mapper
*
* @author yc
*/
public interface AuditEvidenceMapper extends BaseMapper<AuditEvidence> {
/**
* 分页查询
*
* @param page 分页对象
* @param param 查询参数
* @return IPage<AuditEvidence>
*/
IPage<AuditEvidence> selectPageRel(@Param("page") IPage<AuditEvidence> page,
@Param("param") AuditEvidenceParam param);
/**
* 查询全部
*
* @param param 查询参数
* @return List<AuditEvidence>
*/
List<AuditEvidence> selectListRel(@Param("param") AuditEvidenceParam param);
}

View File

@@ -0,0 +1,12 @@
package com.gxwebsoft.ai.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.gxwebsoft.ai.entity.AuditReport;
import org.apache.ibatis.annotations.Mapper;
/**
* 审计报告 Mapper 接口
*/
@Mapper
public interface AuditReportMapper extends BaseMapper<AuditReport> {
}

View File

@@ -0,0 +1,100 @@
<?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="com.gxwebsoft.ai.mapper.AuditEvidenceMapper">
<!-- 关联查询sql -->
<sql id="selectSql">
SELECT a.*
FROM audit_evidence a
<where>
<if test="param.id != null">
AND a.id = #{param.id}
</if>
<if test="param.caseIndex != null">
AND a.case_index = #{param.caseIndex}
</if>
<if test="param.projectName != null">
AND a.project_name LIKE CONCAT('%', #{param.projectName}, '%')
</if>
<if test="param.auditedTarget != null">
AND a.audited_target LIKE CONCAT('%', #{param.auditedTarget}, '%')
</if>
<if test="param.auditMatter != null and param.auditMatter != ''">
AND a.audit_matter LIKE CONCAT('%', #{param.auditMatter}, '%')
</if>
<if test="param.summaryTitle != null and param.summaryTitle != ''">
AND a.summary_title LIKE CONCAT('%', #{param.summaryTitle}, '%')
</if>
<if test="param.contentType != null">
AND a.content_type = #{param.contentType}
</if>
<if test="param.userId != null">
AND a.user_id = #{param.userId}
</if>
<if test="param.username != null">
AND a.username LIKE CONCAT('%', #{param.username}, '%')
</if>
<if test="param.status != null">
AND a.status = #{param.status}
</if>
<if test="param.deleted != null">
AND a.deleted = #{param.deleted}
</if>
<if test="param.deleted == null">
AND a.deleted = 0
</if>
<if test="param.tenantId != null">
AND a.tenant_id = #{param.tenantId}
</if>
<if test="param.startTime != null">
AND a.create_time &gt;= #{param.startTime}
</if>
<if test="param.endTime != null">
AND a.create_time &lt;= #{param.endTime}
</if>
</where>
</sql>
<!-- 分页查询 -->
<select id="selectPageRel" resultType="com.gxwebsoft.ai.entity.AuditEvidence">
<include refid="selectSql"></include>
ORDER BY a.create_time DESC
</select>
<!-- 查询全部 -->
<select id="selectListRel" resultType="com.gxwebsoft.ai.entity.AuditEvidence">
SELECT a.*
FROM audit_evidence a
<where>
<if test="param.id != null">
AND a.id = #{param.id}
</if>
<if test="param.caseIndex != null">
AND a.case_index = #{param.caseIndex}
</if>
<if test="param.projectName != null">
AND a.project_name LIKE CONCAT('%', #{param.projectName}, '%')
</if>
<if test="param.status != null">
AND a.status = #{param.status}
</if>
<if test="param.deleted != null">
AND a.deleted = #{param.deleted}
</if>
<if test="param.deleted == null">
AND a.deleted = 0
</if>
<if test="param.tenantId != null">
AND a.tenant_id = #{param.tenantId}
</if>
<if test="param.startTime != null">
AND a.create_time &gt;= #{param.startTime}
</if>
<if test="param.endTime != null">
AND a.create_time &lt;= #{param.endTime}
</if>
</where>
ORDER BY a.create_time DESC
</select>
</mapper>

View File

@@ -0,0 +1,65 @@
package com.gxwebsoft.ai.param;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import com.gxwebsoft.common.core.web.BaseParam;
import java.time.LocalDateTime;
/**
* 审计取证单查询参数
*
* @author yc
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Schema(name = "AuditEvidenceParam对象", description = "审计取证单查询参数")
public class AuditEvidenceParam extends BaseParam {
@Schema(description = "主键ID")
private Long id;
@Schema(description = "案件编号")
private String caseIndex;
@Schema(description = "项目编号")
private String projectId;
@Schema(description = "项目名称")
private String projectName;
@Schema(description = "被审计单位或个人")
private String auditedTarget;
@Schema(description = "审计事项类型")
private String auditMatterType;
@Schema(description = "审计事项描述")
private String auditMatter;
@Schema(description = "核心问题标题")
private String summaryTitle;
@Schema(description = "内容类型1到11")
private Integer contentType;
@Schema(description = "用户ID")
private Integer userId;
@Schema(description = "用户名")
private String username;
@Schema(description = "删除状态, 0未删除, 1已删除")
private Integer deleted;
@Schema(description = "状态, 0正常, 1冻结")
private Integer status;
@Schema(description = "开始时间")
private LocalDateTime startTime;
@Schema(description = "结束时间")
private LocalDateTime endTime;
}

View File

@@ -51,13 +51,22 @@ public interface AiHistoryService extends IService<AiHistory> {
/**
* 保存历史记录
*
* @param projectId 项目ID
* @param projectId 项目 ID
* @param requestHash 请求哈希值
* @param interfaceName 接口名称
* @param requestData 请求数据
* @param responseData 响应数据
* @param userId 用户ID
* @param userId 用户 ID
* @param username 用户名
*/
void saveHistory(Long projectId, String requestHash, String interfaceName, String requestData, String responseData, Integer userId, String username, Integer tenantId);
/**
* 根据接口名称和项目 ID 查询最新的历史记录
*
* @param interfaceName 接口名称
* @param projectId 项目 ID
* @return AiHistory
*/
AiHistory getLatestHistory(String interfaceName, Long projectId);
}

View File

@@ -1,11 +1,26 @@
package com.gxwebsoft.ai.service;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.extension.service.IService;
import com.gxwebsoft.ai.dto.AuditEvidenceRequest;
import com.gxwebsoft.ai.entity.AuditEvidence;
import com.gxwebsoft.ai.param.AuditEvidenceParam;
import com.gxwebsoft.common.core.web.ApiResult;
import com.gxwebsoft.common.core.web.PageParam;
import com.gxwebsoft.common.core.web.PageResult;
public interface AuditEvidenceService {
public interface AuditEvidenceService extends IService<AuditEvidence> {
/**
* 生成审计取证单
*/
JSONObject generateAuditEvidence(AuditEvidenceRequest request);
ApiResult<AuditEvidence> generateAuditEvidence(AuditEvidenceRequest request);
/**
* 分页查询审计取证单记录
*/
PageResult<AuditEvidence> getAuditEvidencePage(PageParam<AuditEvidence, AuditEvidenceParam> page, AuditEvidenceParam param);
/**
* 保存审计取证单到数据库(新增或更新)
*/
ApiResult<AuditEvidence> saveAuditEvidence(AuditEvidenceRequest request);
}

View File

@@ -1,6 +1,14 @@
package com.gxwebsoft.ai.service;
import com.alibaba.fastjson.JSONObject;
import com.gxwebsoft.ai.dto.AuditReportSaveRequest;
import com.gxwebsoft.ai.entity.AuditEvidence;
import com.gxwebsoft.ai.entity.AuditReport;
import com.gxwebsoft.common.core.web.ApiResult;
import com.gxwebsoft.pwl.entity.PwlProject;
import java.util.List;
import java.util.Map;
public interface AuditReportService {
@@ -11,4 +19,29 @@ public interface AuditReportService {
String kbId, String libraryIds,
String analysisLibrary, String projectLibrary,
String userName);
/**
* 保存审计报告到数据库(新增或更新)
*/
ApiResult<AuditReport> saveAuditReport(AuditReportSaveRequest request);
/**
* 根据 projectId 和 formCommit 查询审计报告
*/
ApiResult<AuditReport> queryAuditReport(Integer projectId, Integer formCommit);
/**
* 根据项目 ID 查询所有审计报告和取证单数据
* @param project 项目
* @return Map包含 reports (AuditReport 列表) 和 evidences (AuditEvidence 列表)
*/
Map<String, Object> queryAuditDataByProjectId(PwlProject project, List<Long> selectedEvidenceIds);
/**
* 根据项目 ID 查询审计报告和取证单数据,并对勾选的取证单进行 AI 分析
* @param project 项目
* @param selectedEvidenceIds 勾选的取证单 ID 列表(可为 null
* @return 包含 reports、evidences、evaluate、suggestion 的 Map
*/
Map<String, Object> queryAuditDataByProjectIdWithAnalysis(PwlProject project, List<Long> selectedEvidenceIds);
}

View File

@@ -16,6 +16,7 @@ import com.gxwebsoft.ai.factory.KnowledgeBaseClientFactory;
import com.gxwebsoft.ai.service.AiCloudFileService;
import com.gxwebsoft.ai.util.AiCloudKnowledgeBaseUtil;
import com.gxwebsoft.common.core.context.TenantContext;
import com.gxwebsoft.oa.service.OaCompanyService;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
@@ -41,11 +42,35 @@ public abstract class AbstractAuditContentService {
@Autowired
protected AiCloudFileService aiCloudFileService;
@Autowired
protected OaCompanyService oaCompanyService;
protected static final String DIFY_WORKFLOW_URL = "http://1.14.159.185:8180/v1/workflows/run";
// 用于同步的锁对象池
private static final Map<String, Object> kbLocks = new ConcurrentHashMap<>();
// 当前请求的主知识库ID集合用于判断是否需要按文件过滤
private static final ThreadLocal<Set<String>> requestMainKbIds = new ThreadLocal<>();
// 当前请求需要过滤的文件ID列表
private static final ThreadLocal<List<String>> requestFileIds = new ThreadLocal<>();
/**
* 设置当前请求的文件ID过滤条件
*/
public static void setRequestFileIds(Set<String> mainKbIds, List<String> fileIds) {
requestMainKbIds.set(mainKbIds);
requestFileIds.set(fileIds);
}
/**
* 清除当前请求的文件ID过滤条件
*/
public static void clearRequestFileIds() {
requestMainKbIds.remove();
requestFileIds.remove();
}
/**
* 调用工作流通用方法
*/
@@ -147,8 +172,6 @@ public abstract class AbstractAuditContentService {
return null;
}
/**
* 构建工作流请求通用方法
*/
@@ -176,12 +199,25 @@ public abstract class AbstractAuditContentService {
* 查询知识库通用方法
*/
protected List<String> queryKnowledgeBase(String kbId, List<String> queries, int topK) {
// 递归获取所有父级单位的kbId
Set<String> allKbIds = TenantContext.callIgnoreTenant(() -> oaCompanyService.getAllParentKbIds(kbId));
Object lock = kbLocks.computeIfAbsent(kbId, k -> new Object());
synchronized (lock) {
try {
// 1. 收集所有节点和文档ID
List<RetrieveResponseBodyDataNodes> allNodes = collectKnowledgeNodes(kbId, queries, topK);
// 获取当前请求的fileIds仅对主知识库生效
List<String> fileIds = null;
Set<String> mainKbIds = requestMainKbIds.get();
if (mainKbIds != null && mainKbIds.contains(kbId)) {
fileIds = requestFileIds.get();
}
// 1. 收集所有节点和文档ID包含所有父级kbId
List<RetrieveResponseBodyDataNodes> allNodes = new ArrayList<>();
for (String currentKbId : allKbIds) {
allNodes.addAll(collectKnowledgeNodes(currentKbId, queries, topK, fileIds));
}
if (allNodes.isEmpty()) {
return new ArrayList<>();
}
@@ -202,14 +238,24 @@ public abstract class AbstractAuditContentService {
/**
* 收集知识库节点
*/
private List<RetrieveResponseBodyDataNodes> collectKnowledgeNodes(String kbId, List<String> queries, int topK) {
private List<RetrieveResponseBodyDataNodes> collectKnowledgeNodes(String kbId, List<String> queries, int topK, List<String> fileIds) {
List<RetrieveResponseBodyDataNodes> allNodes = new ArrayList<>();
String workspaceId = config.getWorkspaceId();
try {
Client client = clientFactory.createClient();
for (String query : queries) {
try {
RetrieveResponse resp = AiCloudKnowledgeBaseUtil.retrieveIndex(client, workspaceId, kbId, query);
RetrieveResponse resp;
if (fileIds != null && !fileIds.isEmpty()) {
// fileId格式 file_xxxx_yyyy知识库tag保存的是xxxx部分
List<String> tags = fileIds.stream()
.map(id -> StrUtil.subBetween(id, "_", "_"))
.filter(StrUtil::isNotBlank)
.collect(Collectors.toList());
resp = AiCloudKnowledgeBaseUtil.retrieveIndex(client, workspaceId, kbId, query, tags);
} else {
resp = AiCloudKnowledgeBaseUtil.retrieveIndex(client, workspaceId, kbId, query);
}
List<RetrieveResponseBodyDataNodes> nodes = Optional.ofNullable(resp)
.map(RetrieveResponse::getBody)
.map(RetrieveResponseBody::getData)

View File

@@ -72,4 +72,17 @@ public class AiHistoryServiceImpl extends ServiceImpl<AiHistoryMapper, AiHistory
save(history);
}
@Override
public AiHistory getLatestHistory(String interfaceName, Long projectId) {
// 使用 LambdaQueryWrapper 查询最新的记录
LambdaQueryWrapper<AiHistory> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(AiHistory::getInterfaceName, interfaceName)
.eq(AiHistory::getProjectId, projectId)
.eq(AiHistory::getDeleted, 0)
.orderByDesc(AiHistory::getCreateTime)
.last("LIMIT 1");
return getOne(wrapper);
}
}

View File

@@ -261,6 +261,9 @@ public class AuditContent10PartyConductServiceImpl extends AbstractAuditContentS
context.append(knowledge).append("\n"));
context.append("\n");
}
context.append("## 重要要求\n");
context.append("1. 除了本次说明中提供的数据知识等依据,不能从其他地方读取数据,不能凭空捏造数据\n");
return context.toString();
}

View File

@@ -169,16 +169,7 @@ public class AuditContent11HistoryServiceImpl extends AbstractAuditContentServic
context.append("8. **整改责任人**:负责整改的责任人姓名和职务\n");
context.append("9. **备注**:其他需要说明的情况\n");
context.append("10. **工作底稿索引**:相关审计报告、整改报告等文件名称,必须是实际存在的完整文件名\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");
context.append("6. 对于历史遗留问题,需特别注明是否为非任期问题\n");
context.append("7. 对于制度完善情况,需说明审计后是否建立了相关制度机制\n\n");
// 3. 历史内容
if (StrUtil.isNotBlank(history)) {
context.append("## 历史生成内容\n");
@@ -223,6 +214,16 @@ public class AuditContent11HistoryServiceImpl extends AbstractAuditContentServic
knowledgeSources.get("regulations").forEach(knowledge ->
context.append(knowledge).append("\n"));
}
context.append("**生成要求:**\n");
context.append("1. 基于知识库内容,尽可能全面地生成历次审计发现的问题和整改情况\n");
context.append("2. 每个问题应包含完整的审计年度、类型、问题描述、整改要求、措施、完成情况等\n");
context.append("3. 重点关注整改措施的落实情况和实际效果\n");
context.append("4. 工作底稿索引必须准确对应实际文件名称,确保能在文件夹中搜索到\n");
context.append("5. 整改完成情况需根据实际情况判断,证据充分的才能判定为'已完成'\n");
context.append("6. 对于历史遗留问题,需特别注明是否为非任期问题\n");
context.append("7. 对于制度完善情况,需说明审计后是否建立了相关制度机制\n\n");
context.append("8. 除了本次说明中提供的数据知识等依据,不能从其他地方读取数据,不能凭空捏造数据\n");
return context.toString();
}

View File

@@ -253,6 +253,56 @@ public class AuditContent1EightRegServiceImpl extends AbstractAuditContentServic
context.append("4. **不得合并计算**:不得将多笔凭证、多次活动合并计算平均值\n");
context.append("5. **逐笔描述**:每笔凭证的检查结果应作为独立记录\n");
}
// 3. 参考数据(从常量类中获取)
context.append("## 参考数据\n");
context.append("### 政策内容\n").append(AuditContent1EightRegConstants.POLICY_CONTENTS.get(category)).append("\n\n");
context.append("### 实施细则\n").append(AuditContent1EightRegConstants.IMPLEMENTATION_DETAILS.get(category)).append("\n\n");
// 4. 审计建议 - 新增部分
String auditSuggestion = AuditContent1EightRegConstants.AUDIT_SUGGESTIONS.get(category);
if (StrUtil.isNotBlank(auditSuggestion)) {
context.append("## 审计建议\n");
context.append("如发现被审计单位在").append(category).append("方面存在不符合中央八项规定的情况,可参考以下审计建议:\n\n");
context.append(auditSuggestion).append("\n\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"));
}
// 2. 数据格式要求
context.append("\n## 数据格式要求\n");
@@ -274,56 +324,7 @@ public class AuditContent1EightRegServiceImpl extends AbstractAuditContentServic
context.append("3. 测试结果判定需严格,对于制度不一致、执行不到位、证据不充分的情况必须判定为不通过\n");
// context.append("4. title字段必须按照八项规定顺序输出一、二、三...八\n\n");
context.append("4. **title字段必须为\"").append(POLICY_TITLE_MAP.get(category)).append("\",不得生成其他标题**\n\n");
// 3. 参考数据(从常量类中获取)
context.append("## 参考数据\n");
context.append("### 政策内容\n").append(AuditContent1EightRegConstants.POLICY_CONTENTS.get(category)).append("\n\n");
context.append("### 实施细则\n").append(AuditContent1EightRegConstants.IMPLEMENTATION_DETAILS.get(category)).append("\n\n");
// 4. 审计建议 - 新增部分
String auditSuggestion = AuditContent1EightRegConstants.AUDIT_SUGGESTIONS.get(category);
if (StrUtil.isNotBlank(auditSuggestion)) {
context.append("## 审计建议\n");
context.append("如发现被审计单位在").append(category).append("方面存在不符合中央八项规定的情况,可参考以下审计建议:\n\n");
context.append(auditSuggestion).append("\n\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"));
}
context.append("5. 除了本次说明中提供的数据知识等依据,不能从其他地方读取数据,不能凭空捏造数据\n");
return context.toString();
}

View File

@@ -213,13 +213,6 @@ public class AuditContent1ExpenseServiceImpl extends AbstractAuditContentService
context.append("3. 关注是否存在未经预算批准变相公款消费的情况\n");
context.append("4. 检查报销手续是否完善,是否存在超支使用\n\n");
// 2. 特别提醒
context.append("## 特别提醒\n");
context.append("1. 必须准确识别支出类型:只提取\"公务接待\"\"出国\"\"公车运行维护\"\"会议培训费\"四类支出\n");
context.append("2. 不要将《工程造价和概(预)算执行情况表》等工程类报表误识别为\"三公经费\"报表\n");
// context.append("3. 工作底稿索引必须使用实际存在的完整文件名称作为FileId\n\n");
context.append("3. 工作底稿索引必须使用实际存在的完整文件名||FileUrl\n\n");
// 3. 历史内容
if (StrUtil.isNotBlank(history)) {
context.append("## 历史生成内容参考\n");
@@ -255,6 +248,14 @@ public class AuditContent1ExpenseServiceImpl extends AbstractAuditContentService
knowledgeSources.get("auditCase").forEach(knowledge ->
context.append(knowledge).append("\n"));
}
// 2. 特别提醒
context.append("## 特别提醒\n");
context.append("1. 必须准确识别支出类型:只提取\"公务接待\"\"出国\"\"公车运行维护\"\"会议培训费\"四类支出\n");
context.append("2. 不要将《工程造价和概(预)算执行情况表》等工程类报表误识别为\"三公经费\"报表\n");
// context.append("3. 工作底稿索引必须使用实际存在的完整文件名称作为FileId\n\n");
context.append("3. 工作底稿索引必须使用实际存在的完整文件名||FileUrl\n\n");
context.append("4. 除了本次说明中提供的数据知识等依据,不能从其他地方读取数据,不能凭空捏造数据\n");
log.debug("构建的知识上下文长度: {}", context.length());
return context.toString();

View File

@@ -216,13 +216,6 @@ public class AuditContent1LeaderListServiceImpl extends AbstractAuditContentServ
context.append("## 数据格式要求\n");
context.append("需要生成完整详细的领导班子名单数据:\n\n");
context.append(AuditContent1LeaderListConstants.DATA_FORMAT_REQUIREMENT);
context.append("\n\n重要要求\n");
context.append("1. 必须根据知识库内容生成尽可能多、尽可能完整的领导班子成员信息\n");
context.append("2. 工作底稿索引必须准确对应实际文件名称,避免使用附表或章节标题\n");
context.append("3. 任职期间格式统一为YYYY.MM-YYYY.MM 或 YYYY.MM-至今\n");
context.append("4. 主要工作责任要具体明确,不能笼统描述\n");
context.append("5. 部门信息要具体,如:董事会、经理层、财务部、生产部等\n");
context.append("6. 备注信息可以包含任命方式、试用期等特殊说明\n\n");
// 3. 历史内容
if (StrUtil.isNotBlank(history)) {
@@ -261,6 +254,15 @@ public class AuditContent1LeaderListServiceImpl extends AbstractAuditContentServ
knowledgeSources.get("auditCase").forEach(knowledge ->
context.append(knowledge).append("\n"));
}
context.append("\n\n重要要求\n");
context.append("1. 必须根据知识库内容生成尽可能多、尽可能完整的领导班子成员信息\n");
context.append("2. 工作底稿索引必须准确对应实际文件名称,避免使用附表或章节标题\n");
context.append("3. 任职期间格式统一为YYYY.MM-YYYY.MM 或 YYYY.MM-至今\n");
context.append("4. 主要工作责任要具体明确,不能笼统描述\n");
context.append("5. 部门信息要具体,如:董事会、经理层、财务部、生产部等\n");
context.append("6. 备注信息可以包含任命方式、试用期等特殊说明\n\n");
context.append("7. 除了本次说明中提供的数据知识等依据,不能从其他地方读取数据,不能凭空捏造数据\n");
log.debug("构建的知识上下文长度: {}", context.length());
return context.toString();

View File

@@ -230,6 +230,9 @@ public class AuditContent2StrategyServiceImpl extends AbstractAuditContentServic
context.append("## 用户建议\n");
context.append(suggestion).append("\n");
}
context.append("\n\n重要要求\n");
context.append("1. 除了本次说明中提供的数据知识等依据,不能从其他地方读取数据,不能凭空捏造数据\n");
// 记录上下文长度用于监控
log.debug("分类 {} 构建的知识上下文长度: {} 字符", category, context.length());

View File

@@ -235,6 +235,9 @@ public class AuditContent3DecisionServiceImpl extends AbstractAuditContentServic
if (StrUtil.isNotBlank(suggestion)) {
context.append("## 用户优化建议\n").append(suggestion).append("\n");
}
context.append("\n\n重要要求\n");
context.append("1. 除了本次说明中提供的数据知识等依据,不能从其他地方读取数据,不能凭空捏造数据\n");
return context.toString();
}

View File

@@ -196,17 +196,6 @@ public class AuditContent3TripleServiceImpl extends AbstractAuditContentService
context.append("- testResult审计测试的结果通过/不通过),严格判断,从严掌握通过标准\n");
// context.append("- workPaperIndex相关《参考文件名FileId》必须是实际存在的完整文件FileId不能使用附表标题确保能在文件夹中搜索到\n");
context.append("- workPaperIndex相关[\"实际存在的完整文件名||FileUrl\"],必须是实际存在的完整文件名,不能使用附表标题,确保能在文件夹中搜索到\n");
context.append("\n注意\n");
context.append("1. 请根据知识库内容尽可能全面地生成所有相关制度规定和检查点\n");
context.append("2. 公司执行情况分析需包含:查阅了哪些文件、发现了什么内容、与制度的差异点、分析判断过程\n");
context.append("3. 工作底稿索引必须准确对应实际文件名称,避免使用附表或章节标题\n");
context.append("4. 测试结果判定需严格,对于制度不一致、执行不到位、证据不充分的情况必须判定为不通过\n");
context.append("5. 审计检查的证据主要按公司制度为主,政策内容和集团制度为次要\n");
context.append("6. 审计检查的证据增加判断公司制度是否与政策内容和集团制度相符\n");
context.append("7. 历史问题整改分析:需核查历史问题是否已整改,结合最新时间材料(如最新会议纪要)分析当前是否存在相同问题\n");
context.append("8. 项目上会核查:涉及三重一大的项目必须核查是否按规定上会,检查有无会议纪要作为证据\n");
context.append("9. 制度权限关系:明确分析公司制度与集团制度的关联关系,权限设置必须遵循公司权限≤集团权限的原则\n");
context.append("10. 层级关系识别:注意识别文件中的上级单位信息,分析制度执行是否符合层级管理要求\n\n");
// 3. 参考数据(从常量类中获取)
context.append("## 参考数据\n");
@@ -250,7 +239,19 @@ public class AuditContent3TripleServiceImpl extends AbstractAuditContentService
knowledgeSources.get("auditCase").forEach(knowledge ->
context.append(knowledge).append("\n"));
}
context.append("\n注意\n");
context.append("1. 请根据知识库内容尽可能全面地生成所有相关制度规定和检查点\n");
context.append("2. 公司执行情况分析需包含:查阅了哪些文件、发现了什么内容、与制度的差异点、分析判断过程\n");
context.append("3. 工作底稿索引必须准确对应实际文件名称,避免使用附表或章节标题\n");
context.append("4. 测试结果判定需严格,对于制度不一致、执行不到位、证据不充分的情况必须判定为不通过\n");
context.append("5. 审计检查的证据主要按公司制度为主,政策内容和集团制度为次要\n");
context.append("6. 审计检查的证据增加判断公司制度是否与政策内容和集团制度相符\n");
context.append("7. 历史问题整改分析:需核查历史问题是否已整改,结合最新时间材料(如最新会议纪要)分析当前是否存在相同问题\n");
context.append("8. 项目上会核查:涉及三重一大的项目必须核查是否按规定上会,检查有无会议纪要作为证据\n");
context.append("9. 制度权限关系:明确分析公司制度与集团制度的关联关系,权限设置必须遵循公司权限≤集团权限的原则\n");
context.append("10. 层级关系识别:注意识别文件中的上级单位信息,分析制度执行是否符合层级管理要求\n\n");
context.append("11. 除了本次说明中提供的数据知识等依据,不能从其他地方读取数据,不能凭空捏造数据\n");
return context.toString();
}

View File

@@ -173,23 +173,7 @@ public class AuditContent4TargetServiceImpl extends AbstractAuditContentService
// 8. 审计证据要求
context.append("## 审计证据要求\n");
context.append(AuditContent4TargetConstants.AUDIT_EVIDENCE_REQUIREMENTS).append("\n\n");
// 9. 特别提醒(新增关键优化点)
context.append("## 特别提醒\n");
context.append("1. 审计证据必须包含具体的查阅过程:查阅了哪些文件、文件中发现了什么内容\n");
// context.append("2. workPaperIndex必须填写实际存在的完整文件FileId确保能在文件夹中搜索到\n");
context.append("2. workPaperIndex必须填写实际存在的完整文件名||FileUrl确保能在文件夹中搜索到\n");
context.append("3. 完成情况判定必须基于充分证据:只有证据充分且完全符合要求才能判定为已完成\n");
context.append("4. 对于执行不到位、效果不佳、证据不充分的情况必须在备注中说明\n");
context.append("5. 尽可能识别知识库中所有相关年度,生成对应的审计记录\n");
context.append("6. 如果知识库中没有单位自定目标文件,请根据企业职责和上级目标合理推断单位自定计划内容\n");
context.append("7. 不能填写简单的\"\",要提供有意义的描述\n");
context.append("8. 对于正在进行的年度,完成情况应填写\"年度未结束无法评估\"\n");
context.append("9. 如果知识库中有多个目标责任文件,每个文件都应生成独立的审计记录\n");
context.append("10. 同一年度可能有不同类型的多个目标责任,应尽可能多地生成审计记录\n");
context.append("11. 不能填写简单的\"\",要提供有意义的描述\n");
context.append("12. 对于正在进行的年度,完成情况应填写\"无法评估\"\n\n");
// 10. 法规和案例参考
if (!knowledgeSources.get("regulation").isEmpty()) {
context.append("## 法律法规参考\n");
@@ -216,7 +200,25 @@ public class AuditContent4TargetServiceImpl extends AbstractAuditContentService
context.append("## 用户建议\n");
context.append(suggestion).append("\n\n");
}
// 9. 特别提醒(新增关键优化点)
context.append("## 特别提醒\n");
context.append("1. 审计证据必须包含具体的查阅过程:查阅了哪些文件、文件中发现了什么内容\n");
// context.append("2. workPaperIndex必须填写实际存在的完整文件FileId确保能在文件夹中搜索到\n");
context.append("2. workPaperIndex必须填写实际存在的完整文件名||FileUrl确保能在文件夹中搜索到\n");
context.append("3. 完成情况判定必须基于充分证据:只有证据充分且完全符合要求才能判定为已完成\n");
context.append("4. 对于执行不到位、效果不佳、证据不充分的情况必须在备注中说明\n");
context.append("5. 尽可能识别知识库中所有相关年度,生成对应的审计记录\n");
// context.append("6. 如果知识库中没有单位自定目标文件,请根据企业职责和上级目标合理推断单位自定计划内容\n");
context.append("6. 不能填写简单的\"\",要提供有意义的描述\n");
context.append("7. 对于正在进行的年度,完成情况应填写\"年度未结束无法评估\"\n");
context.append("8. 如果知识库中有多个目标责任文件,每个文件都应生成独立的审计记录\n");
context.append("9. 同一年度可能有不同类型的多个目标责任,应尽可能多地生成审计记录\n");
context.append("10. 除了本次说明中提供的数据知识等依据,不能从其他地方读取数据,不能凭空捏造数据\n");
// context.append("11. 不能填写简单的\"无\",要提供有意义的描述\n");
// context.append("12. 对于正在进行的年度,完成情况应填写\"无法评估\"\n\n");
log.debug("目标责任制审计知识上下文长度: {} 字符", context.length());
return context.toString();

View File

@@ -232,18 +232,6 @@ public class AuditContent5BudgetExecutionServiceImpl extends AbstractAuditConten
context.append("## 审计证据要求\n");
context.append(AuditContent5BudgetExecutionConstants.AUDIT_EVIDENCE_REQUIREMENTS).append("\n\n");
// 10. 特别提醒 - 强调全面识别
context.append("## 特别提醒\n");
context.append("1. 必须全面分析预算执行全过程,包括执行进度、执行效果、执行合规性\n");
context.append("2. 重点关注预算执行率、资金到位率、预算执行偏差等关键指标\n");
context.append("3. 金额字段应填写具体数值,如\"1,000,000.00\",不能填写简单的\"\"\"\"\n");
// context.append("4. workPaperIndex必须填写实际存在的完整文件FileId\n");
context.append("4. workPaperIndex必须填写实际存在的完整文件名||FileUrl\n");
context.append("5. 对于无数据的字段,可填写\"-\"或留空,但不能填写\"\"\n");
context.append("6. 基于预算执行全流程进行审计分析,包括月度、季度、年度执行情况\n");
context.append("7. 重点关注预算执行偏差原因分析和整改措施\n");
context.append("8. 尽可能多地生成审计记录,覆盖所有预算项目和执行环节\n\n");
// 11. 审计项目参考
context.append("## 审计项目参考(常见类型)\n");
AuditContent5BudgetExecutionConstants.AUDIT_PROJECT_TYPES.forEach(project ->
@@ -276,6 +264,19 @@ public class AuditContent5BudgetExecutionServiceImpl extends AbstractAuditConten
context.append("## 用户建议\n");
context.append(suggestion).append("\n\n");
}
// 10. 特别提醒 - 强调全面识别
context.append("## 特别提醒\n");
context.append("1. 必须全面分析预算执行全过程,包括执行进度、执行效果、执行合规性\n");
context.append("2. 重点关注预算执行率、资金到位率、预算执行偏差等关键指标\n");
context.append("3. 金额字段应填写具体数值,如\"1,000,000.00\",不能填写简单的\"\"\"\"\n");
// context.append("4. workPaperIndex必须填写实际存在的完整文件FileId\n");
context.append("4. workPaperIndex必须填写实际存在的完整文件名||FileUrl\n");
context.append("5. 对于无数据的字段,可填写\"-\"或留空,但不能填写\"\"\n");
context.append("6. 基于预算执行全流程进行审计分析,包括月度、季度、年度执行情况\n");
context.append("7. 重点关注预算执行偏差原因分析和整改措施\n");
context.append("8. 尽可能多地生成审计记录,覆盖所有预算项目和执行环节\n\n");
context.append("9. 除了本次说明中提供的数据知识等依据,不能从其他地方读取数据,不能凭空捏造数据\n");
log.debug("预算执行情况审计知识上下文长度: {} 字符", context.length());

View File

@@ -232,17 +232,6 @@ public class AuditContent5BudgetManageServiceImpl extends AbstractAuditContentSe
context.append("## 审计证据要求\n");
context.append(AuditContent5BudgetManageConstants.AUDIT_EVIDENCE_REQUIREMENTS).append("\n\n");
// 10. 特别提醒 - 强调全面识别
context.append("## 特别提醒\n");
context.append("1. 必须全面识别知识库中所有预算科目,包括:基本支出、项目支出、人员经费、公用经费等\n");
context.append("2. 每个独立的预算科目都要生成独立的审计记录,不限制数量,尽可能多地生成\n");
context.append("3. 金额字段应填写具体数值,如\"1,000,000.00\",不能填写简单的\"\"\"\"\n");
// context.append("4. workPaperIndex必须填写实际存在的完整文件FileId\n");
context.append("4. workPaperIndex必须填写实际存在的完整文件名||FileUrl\n");
context.append("5. 对于无数据的字段,可填写\"-\"或留空,但不能填写\"\"\n");
context.append("6. 基于预算编制、调整、执行的全流程进行审计分析\n");
context.append("7. 重点关注预算调整的合规性和预算执行的真实性\n\n");
// 11. 预算科目参考
context.append("## 预算科目参考(常见类型)\n");
AuditContent5BudgetManageConstants.BUDGET_SUBJECT_TYPES.forEach(subject ->
@@ -275,6 +264,18 @@ public class AuditContent5BudgetManageServiceImpl extends AbstractAuditContentSe
context.append("## 用户建议\n");
context.append(suggestion).append("\n\n");
}
// 10. 特别提醒 - 强调全面识别
context.append("## 特别提醒\n");
context.append("1. 必须全面识别知识库中所有预算科目,包括:基本支出、项目支出、人员经费、公用经费等\n");
context.append("2. 每个独立的预算科目都要生成独立的审计记录,不限制数量,尽可能多地生成\n");
context.append("3. 金额字段应填写具体数值,如\"1,000,000.00\",不能填写简单的\"\"\"\"\n");
// context.append("4. workPaperIndex必须填写实际存在的完整文件FileId\n");
context.append("4. workPaperIndex必须填写实际存在的完整文件名||FileUrl\n");
context.append("5. 对于无数据的字段,可填写\"-\"或留空,但不能填写\"\"\n");
context.append("6. 基于预算编制、调整、执行的全流程进行审计分析\n");
context.append("7. 重点关注预算调整的合规性和预算执行的真实性\n\n");
context.append("8. 除了本次说明中提供的数据知识等依据,不能从其他地方读取数据,不能凭空捏造数据\n");
log.debug("预算管理审计知识上下文长度: {} 字符", context.length());

View File

@@ -187,17 +187,6 @@ public class AuditContent6StateAssetsServiceImpl extends AbstractAuditContentSer
context.append("## 审计证据要求\n");
context.append(AuditContent6StateAssetsConstants.AUDIT_EVIDENCE_REQUIREMENTS).append("\n\n");
// 9. 特别提醒 - 强调全面识别
context.append("## 特别提醒\n");
context.append("1. 必须全面识别知识库中所有国有资产,包括:房屋、土地、车辆、机械设备、办公设备、电子设备、家具、无形资产等\n");
context.append("2. 每个独立的资产都要生成独立的审计记录,不限制数量,尽可能多地生成\n");
context.append("3. 即使资产信息不完整,也要基于现有信息生成审计记录\n");
// context.append("4. workPaperIndex必须填写实际存在的完整文件FileId\n");
context.append("4. workPaperIndex必须填写实际存在的完整文件名||FileUrl\n");
context.append("5. 对于未出租资产,承租方、合同金额等字段填写\"未出租\"\n");
context.append("6. 备注中应详细说明资产状况、使用情况、合规性评价\n");
context.append("7. 不能填写简单的\"\",要提供有意义的描述\n\n");
// 10. 法规和案例参考
if (!knowledgeSources.get("regulation").isEmpty()) {
context.append("## 法律法规参考\n");
@@ -224,6 +213,18 @@ public class AuditContent6StateAssetsServiceImpl extends AbstractAuditContentSer
context.append("## 用户建议\n");
context.append(suggestion).append("\n\n");
}
// 9. 特别提醒 - 强调全面识别
context.append("## 特别提醒\n");
context.append("1. 必须全面识别知识库中所有国有资产,包括:房屋、土地、车辆、机械设备、办公设备、电子设备、家具、无形资产等\n");
context.append("2. 每个独立的资产都要生成独立的审计记录,不限制数量,尽可能多地生成\n");
context.append("3. 即使资产信息不完整,也要基于现有信息生成审计记录\n");
// context.append("4. workPaperIndex必须填写实际存在的完整文件FileId\n");
context.append("4. workPaperIndex必须填写实际存在的完整文件名||FileUrl\n");
context.append("5. 对于未出租资产,承租方、合同金额等字段填写\"未出租\"\n");
context.append("6. 备注中应详细说明资产状况、使用情况、合规性评价\n");
context.append("7. 不能填写简单的\"\",要提供有意义的描述\n\n");
context.append("8. 除了本次说明中提供的数据知识等依据,不能从其他地方读取数据,不能凭空捏造数据\n");
log.debug("国资管理审计知识上下文长度: {} 字符", context.length());

View File

@@ -293,7 +293,9 @@ public class AuditContent7InvestmentServiceImpl extends AbstractAuditContentServ
knowledgeSources.get("financial").forEach(knowledge ->
context.append(knowledge).append("\n"));
}
context.append("## 特别提醒\n");
context.append("1. 除了本次说明中提供的数据知识等依据,不能从其他地方读取数据,不能凭空捏造数据\n");
return context.toString();
}

View File

@@ -243,13 +243,6 @@ public class AuditContent8InternalControlServiceImpl extends AbstractAuditConten
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");
@@ -292,7 +285,14 @@ public class AuditContent8InternalControlServiceImpl extends AbstractAuditConten
knowledgeSources.get("auditCase").forEach(knowledge ->
context.append(knowledge).append("\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");
context.append("6. 除了本次说明中提供的数据知识等依据,不能从其他地方读取数据,不能凭空捏造数据\n");
return context.toString();
}

View File

@@ -65,7 +65,7 @@ public class AuditContent9PersonnelServiceImpl extends AbstractAuditContentServi
} catch (Exception e) {
log.error("生成人员编制管理审计表失败", e);
return buildErrorResponse("生成失败: " + e.getMessage());
return buildErrorResponse("生成人员编制管理审计失败: " + e.getMessage());
}
}
@@ -269,14 +269,6 @@ public class AuditContent9PersonnelServiceImpl extends AbstractAuditContentServi
context.append("## 生成结果要求\n");
context.append(subContent.getGenerationResult()).append("\n\n");
context.append("## 重要要求\n");
context.append("1. 必须使用具体单位名称,禁止使用'XX单位'等模糊词汇\n");
context.append("2. 审计记录必须具体,包含文件名称、数据、人员等详细信息\n");
context.append("3. 重点关注问题发现,提供具体证据和建议\n");
context.append("4. **如果在上传资料中找不到相应证据/凭证,不要直接判定违反规定,应说明'未找到相关材料,无法判定'**\n");
context.append("5. **除了审计证据中列出的资料清单,还需主动查找上传材料中其他涉及审计内容和目标的材料**\n");
context.append("6. **合同与主体公司不相关时,不应判定在主体公司责任范围内,需明确区分责任主体**\n\n");
context.append("## 审计判断原则\n");
context.append("1. **证据不足原则**:当缺乏关键证据时,不做出违规判定\n");
context.append("2. **主动查找原则**:不局限于给定清单,主动识别所有相关材料\n");
@@ -346,6 +338,15 @@ public class AuditContent9PersonnelServiceImpl extends AbstractAuditContentServi
knowledgeSources.get("auditCase").forEach(info ->
context.append(info).append("\n"));
}
context.append("## 重要要求\n");
context.append("1. 必须使用具体单位名称,禁止使用'XX单位'等模糊词汇\n");
context.append("2. 审计记录必须具体,包含文件名称、数据、人员等详细信息\n");
context.append("3. 重点关注问题发现,提供具体证据和建议\n");
context.append("4. **如果在上传资料中找不到相应证据/凭证,不要直接判定违反规定,应说明'未找到相关材料,无法判定'**\n");
context.append("5. **除了审计证据中列出的资料清单,还需主动查找上传材料中其他涉及审计内容和目标的材料**\n");
context.append("6. **合同与主体公司不相关时,不应判定在主体公司责任范围内,需明确区分责任主体**\n\n");
context.append("7. 除了本次说明中提供的数据知识等依据,不能从其他地方读取数据,不能凭空捏造数据\n");
return context.toString();
}

View File

@@ -1,27 +1,35 @@
package com.gxwebsoft.ai.service.impl;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.gxwebsoft.ai.dto.AuditEvidenceRequest;
import com.gxwebsoft.ai.entity.AuditEvidence;
import com.gxwebsoft.ai.enums.AuditMatterTypeEnum;
import com.gxwebsoft.ai.mapper.AuditEvidenceMapper;
import com.gxwebsoft.ai.param.AuditEvidenceParam;
import com.gxwebsoft.ai.service.AuditEvidenceService;
import com.gxwebsoft.common.core.Constants;
import com.gxwebsoft.common.core.web.ApiResult;
import com.gxwebsoft.common.core.web.PageParam;
import com.gxwebsoft.common.core.web.PageResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import java.util.Date;
@Slf4j
@Service
public class AuditEvidenceServiceImpl implements AuditEvidenceService {
public class AuditEvidenceServiceImpl extends ServiceImpl<AuditEvidenceMapper, AuditEvidence> implements AuditEvidenceService {
// Dify工作流配置
private static final String DIFY_WORKFLOW_URL = "http://1.14.159.185:8180/v1/workflows/run";
private static final String DIFY_WORKFLOW_TOKEN = "Bearer app-DEhvF537rQfy6MvH9KeBKUVj";
@Override
public JSONObject generateAuditEvidence(AuditEvidenceRequest request) {
public ApiResult<AuditEvidence> generateAuditEvidence(AuditEvidenceRequest request) {
log.info("开始生成审计取证单 - 用户: {}, 项目: {}", request.getUserName(), request.getProjectName());
long startTime = System.currentTimeMillis();
@@ -33,21 +41,24 @@ public class AuditEvidenceServiceImpl implements AuditEvidenceService {
// 2. 构建工作流请求
JSONObject requestBody = buildWorkflowRequest(knowledgeContext, request.getUserName());
// 3. 调用Dify工作流
JSONObject result = callWorkflow(requestBody, "审计取证单");
// 3. 调用 Dify 工作流
AuditEvidence result = callWorkflow(requestBody);
// 4. 添加处理时间等信息
result.put("success", true);
result.put("processing_time", (System.currentTimeMillis() - startTime) + "ms");
result.put("generated_time", new Date().toString());
// 4. 保存到数据库
// saveToDatabase(result, request);
log.info("审计取证单生成成功 - 处理时间: {}ms", (System.currentTimeMillis() - startTime));
return result;
// 5. 添加处理时间等信息
if (result != null) {
result.setSuccess(true);
log.info("审计取证单生成成功 - ID: {}, 处理时间:{}ms", result.getId(), (System.currentTimeMillis() - startTime));
}
return new ApiResult<>(Constants.RESULT_OK_CODE, Constants.RESULT_OK_MSG, result);
} catch (Exception e) {
log.error("生成审计取证单失败", e);
return buildErrorResponse("生成审计取证单失败: " + e.getMessage());
return new ApiResult<>(1, "生成审计取证单失败: " + e.getMessage());
// return buildErrorResponse("生成审计取证单失败: " + e.getMessage());
}
}
@@ -153,7 +164,7 @@ public class AuditEvidenceServiceImpl implements AuditEvidenceService {
/**
* 调用工作流
*/
private JSONObject callWorkflow(JSONObject requestBody, String workflowName) {
/*private JSONObject callWorkflow(JSONObject requestBody, String workflowName) {
try {
log.info("调用{}工作流,请求体长度: {}", workflowName, requestBody.toString().length());
@@ -189,16 +200,260 @@ public class AuditEvidenceServiceImpl implements AuditEvidenceService {
log.error("调用{}工作流失败", workflowName, e);
throw new RuntimeException("调用" + workflowName + "工作流失败: " + e.getMessage(), e);
}
}*/
private AuditEvidence callWorkflow(JSONObject requestBody) {
try {
log.info("调用审计取证单工作流,请求体长度: {}", requestBody.toString().length());
String result = HttpUtil.createPost(DIFY_WORKFLOW_URL)
.header("Authorization", DIFY_WORKFLOW_TOKEN)
.header("Content-Type", "application/json")
.body(requestBody.toString())
.timeout(5 * 60 * 1000) // 5分钟
.execute()
.body();
log.info("审计取证单工作流返回结果长度: {}", result.length());
JSONObject jsonResponse = JSONObject.parseObject(result);
// 提取输出文本
String outputText = jsonResponse.getJSONObject("data")
.getJSONObject("outputs")
.getString("result");
if (StrUtil.isBlank(outputText)) {
log.warn("审计取证单工作流返回结果为空");
return null;
}
// 解析为JSON对象
AuditEvidence auditEvidence = JSONObject.parseObject(outputText, AuditEvidence.class);
log.info("成功解析审计取证单工作流返回数据");
return auditEvidence;
} catch (Exception e) {
log.error("调用审计取证单工作流失败", e);
throw new RuntimeException("调用审计取证单工作流失败: " + e.getMessage(), e);
}
}
/**
* 构建错误响应
* 保存审计取证单到数据库
*/
private JSONObject buildErrorResponse(String errorMessage) {
JSONObject result = new JSONObject();
result.put("success", false);
result.put("error", errorMessage);
result.put("timestamp", System.currentTimeMillis());
return result;
/*private void saveToDatabase(AuditEvidence auditEvidence, AuditEvidenceRequest request) {
try {
// AuditEvidence auditEvidence = new AuditEvidence();
// 基本信息
auditEvidence.setAuditMatterType(request.getAuditMaterType());
auditEvidence.setContentType(request.getContentType());
auditEvidence.setCaseIndex(request.getCaseIndex());
auditEvidence.setProjectId(request.getProjectId());
auditEvidence.setProjectName(request.getProjectName());
auditEvidence.setData(request.getHistory());
// auditEvidence.setAuditedTarget(result.getString("auditedTarget"));
// auditEvidence.setAuditMatter(result.getString("auditMatter"));
// auditEvidence.setSummaryTitle(result.getString("summaryTitle"));
// 审计内容
// auditEvidence.setAuditRecord(result.getString("auditRecord"));
// auditEvidence.setAuditFinding(result.getString("auditFinding"));
// auditEvidence.setEvidenceBasis(result.getString("evidenceBasis"));
// auditEvidence.setHandling(result.getString("handling"));
// auditEvidence.setSuggestion(result.getString("suggestion"));
// auditEvidence.setAttachment(result.getString("attachment"));
// 时间和人员信息
// auditEvidence.setAuditors(result.getString("auditors"));
// auditEvidence.setCompileDate(result.getString("compileDate"));
// auditEvidence.setProviderOpinion(result.getString("providerOpinion"));
// auditEvidence.setProviderDate(result.getString("providerDate"));
// auditEvidence.setAttachmentPages(result.getString("attachmentPages"));
// auditEvidence.setFeedbackDeadline(result.getString("feedbackDeadline"));
// 用户信息
auditEvidence.setUserId(request.getUserId());
auditEvidence.setUsername(request.getUserName());
auditEvidence.setTenantId(request.getTenantId());
// 状态信息
auditEvidence.setStatus(0); // 正常状态
auditEvidence.setDeleted(0); // 未删除
// 保存到数据库
baseMapper.insert(auditEvidence);
// log.info("审计取证单保存成功 - ID: {}", auditEvidence.getId());
// return auditEvidence;
} catch (Exception e) {
log.error("保存审计取证单到数据库失败", e);
throw new RuntimeException("保存审计取证单到数据库失败: " + e.getMessage(), e);
}
}*/
@Override
public PageResult<AuditEvidence> getAuditEvidencePage(PageParam<AuditEvidence, AuditEvidenceParam> page, AuditEvidenceParam param) {
log.info("查询审计取证单分页列表 - 页码: {}, 页面大小: {}", page.getCurrent(), page.getSize());
try {
// 调用Mapper的分页查询方法
IPage<AuditEvidence> list = baseMapper.selectPageRel(page, param);
log.info("审计取证单分页查询成功 - 总记录数: {}, 当前页记录数: {}",
page.getTotal(), list.getTotal());
// 返回分页结果
return new PageResult<>(list.getRecords(), page.getTotal());
} catch (Exception e) {
log.error("查询审计取证单分页列表失败", e);
throw new RuntimeException("查询审计取证单分页列表失败:" + e.getMessage(), e);
}
}
@Override
public ApiResult<AuditEvidence> saveAuditEvidence(AuditEvidenceRequest request) {
log.info("开始保存审计取证单 - 用户:{}, 项目:{}", request.getUserName(), request.getProjectName());
try {
// 1. 验证并转换 auditMatterType
if (StrUtil.isBlank(request.getAuditMatterType())) {
return new ApiResult<>(1, "审计事项类型不能为空");
}
AuditMatterTypeEnum matterType = AuditMatterTypeEnum.getByCode(request.getAuditMatterType());
if (matterType == null) {
return new ApiResult<>(1, "无效的审计事项类型:" + request.getAuditMatterType());
}
// 2. 检查是否已存在(根据 projectId + auditMatterType
if (request.getProjectId() == null) {
return new ApiResult<>(1, "项目编号不能为空");
}
LambdaQueryWrapper<AuditEvidence> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(AuditEvidence::getProjectId, request.getProjectId())
.eq(AuditEvidence::getAuditMatterType, request.getAuditMatterType())
.eq(AuditEvidence::getDeleted, 0);
AuditEvidence existingEvidence = baseMapper.selectOne(queryWrapper);
AuditEvidence auditEvidence;
if (existingEvidence != null) {
// 3. 更新现有记录
log.info("发现已存在的记录,执行更新操作 - ID: {}", existingEvidence.getId());
auditEvidence = existingEvidence;
// 更新字段
auditEvidence.setCaseIndex(request.getCaseIndex());
auditEvidence.setProjectName(request.getProjectName());
auditEvidence.setContentType(request.getContentType());
// 审计内容
auditEvidence.setSummaryTitle(request.getSummaryTitle());
auditEvidence.setAuditRecord(request.getAuditRecord());
auditEvidence.setAuditFinding(request.getAuditFinding());
auditEvidence.setEvidenceBasis(request.getEvidenceBasis());
auditEvidence.setHandling(request.getHandling());
auditEvidence.setSuggestion(request.getSuggestion());
auditEvidence.setAttachment(request.getAttachment());
// 时间和人员信息
auditEvidence.setAuditors(request.getAuditors());
auditEvidence.setCompileDate(request.getCompileDate());
auditEvidence.setProviderOpinion(request.getProviderOpinion());
auditEvidence.setProviderDate(request.getProviderDate());
// 处理附件页数(转换为 Integer
if (request.getAttachmentPages() != null && !request.getAttachmentPages().isEmpty()) {
try {
auditEvidence.setAttachmentPages(Integer.parseInt(request.getAttachmentPages()));
} catch (NumberFormatException e) {
log.warn("附件页数转换失败,使用默认值 0", e);
auditEvidence.setAttachmentPages(0);
}
} else {
auditEvidence.setAttachmentPages(0);
}
auditEvidence.setFeedbackDeadline(request.getFeedbackDeadline());
// 将原始数据保存为 JSON 字符串
auditEvidence.setData(com.alibaba.fastjson.JSONObject.toJSONString(request));
// 更新数据库
baseMapper.updateById(auditEvidence);
log.info("审计取证单更新成功 - ID: {}", auditEvidence.getId());
} else {
// 4. 新增记录
log.info("未发现已存在的记录,执行新增操作");
auditEvidence = new AuditEvidence();
// 基本信息
auditEvidence.setCaseIndex(request.getCaseIndex());
auditEvidence.setProjectId(request.getProjectId());
auditEvidence.setProjectName(request.getProjectName());
auditEvidence.setContentType(request.getContentType());
auditEvidence.setAuditMatterType(request.getAuditMatterType());
auditEvidence.setAuditMatter(request.getAuditMatter());
// 审计内容
auditEvidence.setSummaryTitle(request.getSummaryTitle());
auditEvidence.setAuditRecord(request.getAuditRecord());
auditEvidence.setAuditFinding(request.getAuditFinding());
auditEvidence.setEvidenceBasis(request.getEvidenceBasis());
auditEvidence.setHandling(request.getHandling());
auditEvidence.setSuggestion(request.getSuggestion());
auditEvidence.setAttachment(request.getAttachment());
// 时间和人员信息
auditEvidence.setAuditors(request.getAuditors());
auditEvidence.setCompileDate(request.getCompileDate());
auditEvidence.setProviderOpinion(request.getProviderOpinion());
auditEvidence.setProviderDate(request.getProviderDate());
// 处理附件页数(转换为 Integer
if (request.getAttachmentPages() != null && !request.getAttachmentPages().isEmpty()) {
try {
auditEvidence.setAttachmentPages(Integer.parseInt(request.getAttachmentPages()));
} catch (NumberFormatException e) {
log.warn("附件页数转换失败,使用默认值 0", e);
auditEvidence.setAttachmentPages(0);
}
} else {
auditEvidence.setAttachmentPages(0);
}
auditEvidence.setFeedbackDeadline(request.getFeedbackDeadline());
// 将原始数据保存为 JSON 字符串
auditEvidence.setData(com.alibaba.fastjson.JSONObject.toJSONString(request));
// 用户信息
auditEvidence.setUserId(request.getUserId());
auditEvidence.setUsername(request.getUserName());
auditEvidence.setTenantId(request.getTenantId());
// 状态信息
auditEvidence.setStatus(0); // 正常状态
auditEvidence.setDeleted(0); // 未删除
// 保存到数据库
baseMapper.insert(auditEvidence);
log.info("审计取证单保存成功 - ID: {}", auditEvidence.getId());
}
return new ApiResult<>(Constants.RESULT_OK_CODE, Constants.RESULT_OK_MSG, auditEvidence);
} catch (Exception e) {
log.error("保存审计取证单到数据库失败", e);
return new ApiResult<>(1, "保存审计取证单失败:" + e.getMessage());
}
}
}

View File

@@ -1,5 +1,6 @@
package com.gxwebsoft.ai.service.impl;
import cn.hutool.core.collection.CollectionUtil;
import com.aliyun.bailian20231229.Client;
import com.aliyun.bailian20231229.models.RetrieveResponse;
import com.aliyun.bailian20231229.models.RetrieveResponseBody.RetrieveResponseBodyDataNodes;
@@ -10,6 +11,7 @@ import com.gxwebsoft.ai.config.KnowledgeBaseConfig;
import com.gxwebsoft.ai.factory.KnowledgeBaseClientFactory;
import com.gxwebsoft.ai.service.AuditReportService;
import com.gxwebsoft.ai.util.AiCloudKnowledgeBaseUtil;
import com.gxwebsoft.pwl.entity.PwlProject;
import com.gxwebsoft.pwl.entity.PwlProjectLibrary;
import com.gxwebsoft.pwl.service.PwlProjectLibraryService;
@@ -19,11 +21,29 @@ import cn.hutool.http.HttpUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.gxwebsoft.ai.dto.AuditReportSaveRequest;
import com.gxwebsoft.ai.entity.AuditEvidence;
import com.gxwebsoft.ai.entity.AuditReport;
import com.gxwebsoft.ai.mapper.AuditEvidenceMapper;
import com.gxwebsoft.ai.mapper.AuditReportMapper;
import com.gxwebsoft.common.core.Constants;
import com.gxwebsoft.common.core.web.ApiResult;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
public class AuditReportServiceImpl implements AuditReportService {
public class AuditReportServiceImpl extends ServiceImpl<AuditReportMapper, AuditReport> implements AuditReportService {
@Autowired
private KnowledgeBaseClientFactory clientFactory;
@@ -33,6 +53,9 @@ public class AuditReportServiceImpl implements AuditReportService {
@Autowired
private PwlProjectLibraryService pwlProjectLibraryService;
@Autowired
private AuditEvidenceMapper auditEvidenceMapper;
// 工作流配置
private static final String QUESTION_GENERATION_WORKFLOW_URL = "http://1.14.159.185:8180/v1/workflows/run";
@@ -41,6 +64,12 @@ public class AuditReportServiceImpl implements AuditReportService {
private static final String QUESTION_ANALYSIS_WORKFLOW_URL = "http://1.14.159.185:8180/v1/workflows/run";
private static final String QUESTION_ANALYSIS_TOKEN = "Bearer app-5CQWPxctsPape0HePWA3cP4Y";
private static final String QUOTA_STR = "1.《中华人民共和国审计法》;\n" +
"2.《中华人民共和国注册会计师法》;\n" +
"3.《中国注册会计师执业准则》;\n" +
"4.《党政主要领导干部和国有企事业单位主要领导人员经济责任审计规定》中办发201945号以下简称“两办《规定》”\n" +
"5.《企业国有资本保值增值结果确认暂行办法》国资委令第9号";
@Override
public JSONObject generateCompleteAuditReport(String auditContent, String fileName,
String kbId, String libraryIds,
@@ -105,9 +134,8 @@ public class AuditReportServiceImpl implements AuditReportService {
String knowledgeContext = buildKnowledgeContext(auditContent, regulations);
JSONObject requestBody = buildQuestionGenerationRequest(knowledgeContext, userName);
JSONArray response = callQuestionGenerationWorkflow(requestBody);
return response;
return callQuestionGenerationWorkflow(requestBody);
}
/**
@@ -344,4 +372,353 @@ public class AuditReportServiceImpl implements AuditReportService {
String[] keywords = {"制度", "办法", "规定", "流程", "标准", "规范", "要求"};
return String.join(" ", keywords);
}
@Override
public ApiResult<AuditReport> saveAuditReport(AuditReportSaveRequest request) {
log.info("开始保存审计报告 - 用户:{}, 项目:{}", request.getAuditedTarget(), request.getProjectName());
try {
// 1. 验证必填字段
if (request.getProjectId() == null) {
return new ApiResult<>(1, "项目编号不能为空");
}
if (request.getFormCommit() == null) {
return new ApiResult<>(1, "表单提交标识不能为空");
}
// 2. 检查是否已存在根据projectId + formCommit
LambdaQueryWrapper<AuditReport> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(AuditReport::getProjectId, request.getProjectId())
.eq(AuditReport::getFormCommit, request.getFormCommit())
.eq(AuditReport::getDeleted, 0);
AuditReport existingReport = baseMapper.selectOne(queryWrapper);
AuditReport auditReport;
if (existingReport != null) {
// 3. 更新现有记录
log.info("发现已存在的记录,执行更新操作 - ID: {}", existingReport.getId());
auditReport = existingReport;
// 更新字段
auditReport.setProjectName(request.getProjectName());
auditReport.setCaseIndex(request.getCaseIndex());
auditReport.setAuditedTarget(request.getAuditedTarget());
auditReport.setReportContent(request.getReportContent());
auditReport.setPreviewHtml(request.getPreviewHtml());
auditReport.setSectionCount(request.getSectionCount());
// 更新数据库
baseMapper.updateById(auditReport);
log.info("审计报告更新成功 - ID: {}", auditReport.getId());
} else {
// 4. 新增记录
log.info("未发现已存在的记录,执行新增操作");
auditReport = new AuditReport();
// 基本信息
auditReport.setProjectId(request.getProjectId());
auditReport.setProjectName(request.getProjectName());
auditReport.setCaseIndex(request.getCaseIndex());
auditReport.setAuditedTarget(request.getAuditedTarget());
auditReport.setReportContent(request.getReportContent());
auditReport.setPreviewHtml(request.getPreviewHtml());
auditReport.setSectionCount(request.getSectionCount());
auditReport.setFormCommit(request.getFormCommit());
// 保存到数据库
baseMapper.insert(auditReport);
log.info("审计报告保存成功 - ID: {}", auditReport.getId());
}
// 设置 success 标志
auditReport.setSuccess(true);
return new ApiResult<>(Constants.RESULT_OK_CODE, Constants.RESULT_OK_MSG, auditReport);
} catch (Exception e) {
log.error("保存审计报告到数据库失败", e);
return new ApiResult<>(1, "保存审计报告失败:" + e.getMessage());
}
}
@Override
public ApiResult<AuditReport> queryAuditReport(Integer projectId, Integer formCommit) {
log.info("查询审计报告 - 项目 ID: {}, formCommit: {}", projectId, formCommit);
try {
if (projectId == null || formCommit == null) {
return new ApiResult<>(1, "项目编号和表单提交标识不能为空");
}
// 根据projectId + formCommit 查询
LambdaQueryWrapper<AuditReport> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(AuditReport::getProjectId, projectId)
.eq(AuditReport::getFormCommit, formCommit)
.eq(AuditReport::getDeleted, 0);
AuditReport auditReport = baseMapper.selectOne(queryWrapper);
if (auditReport != null) {
log.info("查询到审计报告 - ID: {}", auditReport.getId());
auditReport.setSuccess(true);
return new ApiResult<>(Constants.RESULT_OK_CODE, Constants.RESULT_OK_MSG, auditReport);
} else {
log.info("未查询到审计报告");
return new ApiResult<>(Constants.RESULT_OK_CODE, "未查询到记录", null);
}
} catch (Exception e) {
log.error("查询审计报告失败", e);
return new ApiResult<>(1, "查询审计报告失败:" + e.getMessage());
}
}
@Override
public Map<String, Object> queryAuditDataByProjectId(PwlProject project, List<Long> selectedEvidenceIds) {
// 调用新方法,不传入 selectedEvidenceIds兼容旧接口
return queryAuditDataByProjectIdWithAnalysis(project, selectedEvidenceIds);
}
/**
* 根据项目 ID 查询审计报告和取证单数据,并对勾选的取证单进行 AI 分析
* @param project 项目 ID
* @param selectedEvidenceIds 勾选的取证单 ID 列表(可为 null
* @return 包含 reports、evidences、evaluate、suggestion 的 Map
*/
@Override
public Map<String, Object> queryAuditDataByProjectIdWithAnalysis(PwlProject project, List<Long> selectedEvidenceIds) {
Integer projectId = project.getId();
log.info("=== 开始查询审计数据 ===");
log.info("项目 ID: {}, 勾选取证单 IDs: {}", projectId, selectedEvidenceIds);
Map<String, Object> result = new HashMap<>();
// 添加章节顺序配置,供前端使用
List<Map<String, Object>> sectionOrder = new ArrayList<>();
sectionOrder.add(createSectionInfo(1, "基本情况", Arrays.asList(41, 42, 43)));
sectionOrder.add(createSectionInfo(2, "总体评价", null));
sectionOrder.add(createSectionInfo(3, String.format("%s主要业绩", project.getPersonName()), null));
sectionOrder.add(createSectionInfo(4, "履行经济责任的主要情况及审计发现的问题与责任认定", Arrays.asList(51, 52, 53, 54, 55, 56, 57)));
sectionOrder.add(createSectionInfo(5, "审计建议", null));
sectionOrder.add(createSectionInfo(6, "其他事项说明", null));
result.put("sectionOrder", sectionOrder);
try {
if (projectId == null) {
log.warn("项目 ID 为 null返回空结果");
result.put("reports", new ArrayList<>());
result.put("evidences", new ArrayList<>());
result.put("evaluate", "");
result.put("suggestion", "");
return result;
}
// 1. 查询审计报告数据
log.info("正在查询审计报告...");
List<AuditReport> reports = new ArrayList<>();
AuditReport report10 = new AuditReport();
report10.setFormCommit(10);
report10.setProjectId(projectId);
report10.setReportContent(QUOTA_STR);
reports.add(report10);
LambdaQueryWrapper<AuditReport> reportWrapper = new LambdaQueryWrapper<>();
reportWrapper.eq(AuditReport::getProjectId, projectId)
.eq(AuditReport::getDeleted, 0)
.in(AuditReport::getFormCommit, Arrays.asList(41, 42, 43))
.orderByAsc(AuditReport::getFormCommit);
List<AuditReport> reportList = baseMapper.selectList(reportWrapper);
if(CollectionUtil.isNotEmpty(reportList)){
reports.addAll(reportList);
}
log.info("查询到 {} 条审计报告记录", reports.size());
// 2. 查询审计取证单数据 - 查询该项目下所有的取证单
log.info("正在查询审计取证单...");
LambdaQueryWrapper<AuditEvidence> evidenceWrapper = new LambdaQueryWrapper<>();
evidenceWrapper.eq(AuditEvidence::getProjectId, projectId)
.eq(AuditEvidence::getDeleted, 0)
.orderByAsc(AuditEvidence::getId);
List<AuditEvidence> evidences = auditEvidenceMapper.selectList(evidenceWrapper);
log.info("查询到 {} 条审计取证单记录", evidences.size());
result.put("reports", reports);
result.put("evidences", evidences);
// 3. 获取项目信息(用于获取项目名称)
String projectName = project.getName() != null ? project.getName() : "";
result.put("projectName", projectName); // 添加项目名称到返回结果
log.info("项目名称:{}", projectName);
// 生成审计引言
String auditIntro = generateAuditIntro(project);
result.put("auditIntro", auditIntro); // 添加审计引言到返回结果
// 生成其他事项说明
String otherMatters = generateOtherMatters(project);
result.put("otherMatters", otherMatters); // 添加其他事项说明到返回结果
// 4. 如果传入了勾选的取证单 ID进行字段叠加
if (selectedEvidenceIds != null && !selectedEvidenceIds.isEmpty()) {
log.info("开始对 {} 条勾选的取证单进行字段叠加", selectedEvidenceIds.size());
// LambdaQueryWrapper<AuditEvidence> evidenceWrapper = new LambdaQueryWrapper<>();
// evidenceWrapper.eq(AuditEvidence::getProjectId, projectId)
// .eq(AuditEvidence::getDeleted, 0)
// .orderByAsc(AuditEvidence::getId);
// 筛选出勾选的取证单
List<AuditEvidence> selectedEvidences = evidences.stream()
.filter(e -> selectedEvidenceIds.contains(e.getId()))
.collect(Collectors.toList());
// List<AuditEvidence> selectedEvidences = auditEvidenceMapper.selectList(evidenceWrapper);
log.info("实际筛选出 {} 条取证单进行叠加", selectedEvidences.size());
if (!selectedEvidences.isEmpty()) {
// 叠加字段内容
String evaluate = concatenateField(selectedEvidences, "auditFinding");
String suggestion = concatenateField(selectedEvidences, "suggestion");
result.put("evaluate", evaluate);
result.put("suggestion", suggestion);
log.info("字段叠加完成,评价字数:{},建议字数:{}",
evaluate.length(),
suggestion.length());
} else {
log.info("没有筛选到勾选的取证单,返回空的评价和建议");
result.put("evaluate", "");
result.put("suggestion", "");
}
} else {
// 没有勾选,返回空值
log.info("未传入勾选的取证单 ID返回空的评价和建议");
result.put("evaluate", "");
result.put("suggestion", "");
}
log.info("=== 审计数据查询完成 ===");
return result;
} catch (Exception e) {
log.error("根据项目 ID 查询审计数据失败,异常信息:{}", e.getMessage(), e);
result.put("reports", new ArrayList<>());
result.put("evidences", new ArrayList<>());
result.put("evaluate", "");
result.put("suggestion", "");
return result;
}
}
/**
* 生成审计引言段落
* @param project 项目信息
* @return 审计引言文本
*/
private String generateAuditIntro(PwlProject project) {
if (project == null) {
return "我们接受委托,自 20XX 年 XX 月 XX 日至 20XX 年 XX 月 XX 日,对 XX 同志自 20XX 年 XX 月 XX 日至 20XX 年 XX 月 XX 日担任公司董事长(具体职务)期间的经济责任履行情况进行了就地审计。公司及 XX 同志的责任是提供与本次审计相关的全部资料,并保证其真实、完整。我们的责任是在实施审计工作的基础上发表审计意见。审计工作为发表意见提供了合理基础。现将审计情况报告如下:";
}
String intro = "我们接受%s委托自 20XX 年 XX 月 XX 日至 20XX 年 XX 月 XX 日,对%s自 20XX 年 XX 月 XX 日至 20XX 年 XX 月 XX 日担任%s %s期间的经济责任履行情况进行了就地审计。%s及%s责任是提供与本次审计相关的全部资料并保证其真实、完整。我们的责任是在实施审计工作的基础上发表审计意见。审计工作为发表意见提供了合理基础。现将审计情况报告如下";
return String.format(intro, project.getName(), project.getPersonName(), project.getName(), project.getPosition(), project.getName(), project.getPersonName());
}
/**
* 生成其他事项说明内容
* @param project 项目信息
* @return 其他事项说明文本
*/
private String generateOtherMatters(PwlProject project) {
return String.format("(一)本报告依据委托方及 %s 提供的审计相关资料出具,审计意见受所提供资料真实性及完整性的影响。" +
// 第二段
"(二)本报告仅供委托方 %s 了解 %s 的经济责任履行情况之用,未经本所书面同意不得用作其他用途,因使用不当造成的后果,与执行本次专项审计业务的注册会计师及会计师事务所无关。", project.getName(), project.getName(), project.getPersonName());
}
/**
* 创建章节信息
* @param order 章节顺序
* @param title 章节标题
* @param formCommits 表单提交标识列表
* @return 章节信息Map
*/
private Map<String, Object> createSectionInfo(int order, String title, List<Integer> formCommits) {
Map<String, Object> sectionInfo = new HashMap<>();
sectionInfo.put("order", order);
sectionInfo.put("title", title);
sectionInfo.put("formCommits", formCommits);
return sectionInfo;
}
private String concatenateField(List<AuditEvidence> selectedEvidences, String fieldName) {
log.info("开始叠加字段:{}", fieldName);
StringBuilder result = new StringBuilder();
for (int i = 0; i < selectedEvidences.size(); i++) {
AuditEvidence evidence = selectedEvidences.get(i);
String fieldValue = null;
if ("auditFinding".equals(fieldName)) {
fieldValue = evidence.getAuditFinding();
} else if ("suggestion".equals(fieldName)) {
fieldValue = evidence.getSuggestion();
}
log.info("取证单 ID={}, 序号={}, 字段={}, 值长度={}",
evidence.getId(), i+1, fieldName, fieldValue != null ? fieldValue.length() : 0);
if (StrUtil.isNotBlank(fieldValue)) {
// 添加序号和审计事项作为标题
String auditMatter = evidence.getAuditMatter();
String numText = getChineseNumber(i + 1);
if (StrUtil.isNotBlank(auditMatter)) {
result.append("(").append(numText).append(") ").append(auditMatter).append("\n");
} else {
result.append("(").append(numText).append(") 事项").append(i + 1).append("\n");
}
// 将内容按换行符分割,每段都首行缩进
String[] paragraphs = fieldValue.split("\\n+");
for (String paragraph : paragraphs) {
if (StrUtil.isNotBlank(paragraph.trim())) {
result.append(" ").append(paragraph.trim()).append("\n");
}
}
// 如果不是最后一条,添加空行分隔
if (i < selectedEvidences.size() - 1) {
result.append("\n");
}
} else {
log.warn("取证单 ID={}, 字段 {} 为空或空字符串", evidence.getId(), fieldName);
}
}
log.info("字段 {} 叠加完成,总长度={}", fieldName, result.length());
return result.toString();
}
/**
* 获取中文序号(一、二、三...十)
* @param number 数字1-10
* @return 中文序号字符串
*/
private String getChineseNumber(int number) {
String[] chineseNumbers = {"", "", "", "", "", "", "", "", "", "", ""};
if (number >= 1 && number <= 10) {
return chineseNumbers[number];
} else if (number > 10 && number <= 20) {
return "" + chineseNumbers[number - 10];
} else {
// 超过 20 的数字,直接返回阿拉伯数字
return String.valueOf(number);
}
}
}

View File

@@ -0,0 +1,25 @@
package com.gxwebsoft.common.core.config;
import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Tomcat 配置类,用于解决 URL 中包含特殊字符的问题
*/
@Configuration
public class TomcatConfig {
/**
* 允许 URL 中包含特殊字符(如中文、括号等)
* 解决错误Invalid character found in the request target
*/
@Bean
public TomcatConnectorCustomizer tomcatConnectorCustomizer() {
return connector -> {
// 允许 [] 等特殊字符
connector.setProperty("relaxedPathChars", "[]|{}^<>\"");
connector.setProperty("relaxedQueryChars", "[]|{}^<>\"\\`");
};
}
}

View File

@@ -39,6 +39,12 @@ public class OaCompany implements Serializable {
@Schema(description = "企业标识")
private String companyCode;
@Schema(description = "企业编号")
private String companyNo;
@Schema(description = "上级单位/集团公司/归属单位")
private String parentCompany;
@Schema(description = "类型 10企业 20政府单位")
private String companyType;

View File

@@ -6,6 +6,7 @@ import com.gxwebsoft.oa.entity.OaCompany;
import com.gxwebsoft.oa.param.OaCompanyParam;
import java.util.List;
import java.util.Set;
/**
* 企业信息Service
@@ -61,4 +62,12 @@ public interface OaCompanyService extends IService<OaCompany> {
*/
boolean removeCompanyKnowledgeBase(Integer companyId);
/**
* 递归获取所有父级单位的kbId包含当前单位
*
* @param kbId 知识库ID
* @return 所有父级单位的kbId集合
*/
Set<String> getAllParentKbIds(String kbId);
}

View File

@@ -28,7 +28,9 @@ import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
@@ -189,4 +191,43 @@ public class OaCompanyServiceImpl extends ServiceImpl<OaCompanyMapper, OaCompany
}
return ret;
}
@Override
public Set<String> getAllParentKbIds(String kbId) {
Set<String> kbIds = new LinkedHashSet<>();
kbIds.add(kbId);
// 根据kbId查找OaCompany
OaCompany company = getOne(new LambdaQueryWrapper<OaCompany>().eq(OaCompany::getKbId, kbId));
if (company != null && StrUtil.isNotBlank(company.getParentCompany())) {
// 递归查找父级
collectParentKbIds(company.getParentCompany(), kbIds);
}
return kbIds;
}
/**
* 递归收集父级kbId
*/
private void collectParentKbIds(String parentCompanyId, Set<String> kbIds) {
if (StrUtil.isBlank(parentCompanyId)) {
return;
}
// 查找父级公司
OaCompany parentCompany = getById(Integer.valueOf(parentCompanyId));
if (parentCompany != null) {
// 添加父级kbId
if (StrUtil.isNotBlank(parentCompany.getKbId())) {
kbIds.add(parentCompany.getKbId());
}
// 继续递归查找父级的父级
if (StrUtil.isNotBlank(parentCompany.getParentCompany())) {
collectParentKbIds(parentCompany.getParentCompany(), kbIds);
}
}
}
}

View File

@@ -41,6 +41,12 @@ public class PwlProject implements Serializable {
@Schema(description = "项目标识")
private String code;
@Schema(description = "针对用户名称")
private String personName;
@Schema(description = "职务")
private String position;
@Schema(description = "案引号")
private String caseIndex;

View File

@@ -235,3 +235,9 @@ payment:
notify-url: "https://cms-api.websoft.top/api/shop/shop-order/notify"
# 生产环境是否启用环境感知
environment-aware: false
# AI 审计报告模板配置
ai:
template:
word-template-path: classpath:templates/audit_report_template.docx
evidence-template-path: classpath:templates/audit_evidence_template.docx