From 9e6ae1eca900e62ba9a8feb59f505e5b39291da2 Mon Sep 17 00:00:00 2001 From: wangyu <823267011@qq.com> Date: Fri, 12 Jun 2026 14:35:27 +0800 Subject: [PATCH] =?UTF-8?q?AI=E5=AE=A1=E6=A0=B8=E6=94=AF=E6=8C=81=E5=BE=AE?= =?UTF-8?q?=E8=B0=83/=E8=87=AA=E5=AE=9A=E4=B9=89=E5=AE=A1=E6=A0=B8?= =?UTF-8?q?=E9=87=8D=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增审核可选择审核重点(字典驱动,合同/简历各一套)并填写附加要求自由文本, 两者合并为 requirements 随请求提交,后端追加进系统提示词,让模型按需聚焦 - 审核项存字典 oa_ai_review_item_contract / oa_ai_review_item_resume, 用户可在系统管理→字典管理自行增删审核项(无需改代码),各预置10项 - oa_ai_review 增加 requirements 列(已应用到生产库),落库留痕;详情页展示 - 前后端贯通:analyze / analyzeStream 均新增 requirements 参数 Co-Authored-By: Claude Fable 5 --- .../oa/controller/OaAiReviewController.java | 13 +++-- .../java/com/ruoyi/oa/domain/OaAiReview.java | 3 + .../com/ruoyi/oa/domain/vo/OaAiReviewVo.java | 1 + .../ruoyi/oa/service/IOaAiReviewService.java | 11 ++-- .../service/impl/OaAiReviewServiceImpl.java | 20 +++++-- ruoyi-ui/src/views/oa/aiReview/add.vue | 58 +++++++++++++++++++ ruoyi-ui/src/views/oa/aiReview/detail.vue | 3 + sql/oa_ai_review.sql | 33 ++++++++++- 8 files changed, 125 insertions(+), 17 deletions(-) diff --git a/ruoyi-oa/src/main/java/com/ruoyi/oa/controller/OaAiReviewController.java b/ruoyi-oa/src/main/java/com/ruoyi/oa/controller/OaAiReviewController.java index b424c0e..1ae8396 100644 --- a/ruoyi-oa/src/main/java/com/ruoyi/oa/controller/OaAiReviewController.java +++ b/ruoyi-oa/src/main/java/com/ruoyi/oa/controller/OaAiReviewController.java @@ -37,8 +37,9 @@ public class OaAiReviewController extends BaseController { @PostMapping(value = "/analyze", consumes = "multipart/form-data") public R analyze(@RequestParam("file") MultipartFile file, @RequestParam("reviewType") String reviewType, - @RequestParam(value = "position", required = false) String position) { - return R.ok("审核完成", service.analyze(file, reviewType, position)); + @RequestParam(value = "position", required = false) String position, + @RequestParam(value = "requirements", required = false) String requirements) { + return R.ok("审核完成", service.analyze(file, reviewType, position, requirements)); } /** @@ -48,8 +49,9 @@ public class OaAiReviewController extends BaseController { @PostMapping(value = "/analyzeStream", consumes = "multipart/form-data", produces = "text/event-stream;charset=UTF-8") public SseEmitter analyzeStream(@RequestParam("file") MultipartFile file, @RequestParam("reviewType") String reviewType, - @RequestParam(value = "position", required = false) String position) { - return service.analyzeStream(file, reviewType, position); + @RequestParam(value = "position", required = false) String position, + @RequestParam(value = "requirements", required = false) String requirements) { + return service.analyzeStream(file, reviewType, position, requirements); } @GetMapping("/list") @@ -57,7 +59,8 @@ public class OaAiReviewController extends BaseController { return service.queryPageList(bo, pageQuery); } - @GetMapping("/{id}") + // 限定为数字,避免 /analyzeStream 等子路径被 {id} 误匹配(否则 POST 会得到 405) + @GetMapping("/{id:\\d+}") public R getInfo(@NotNull @PathVariable Long id) { return R.ok(service.queryById(id)); } diff --git a/ruoyi-oa/src/main/java/com/ruoyi/oa/domain/OaAiReview.java b/ruoyi-oa/src/main/java/com/ruoyi/oa/domain/OaAiReview.java index a11215b..041ff1f 100644 --- a/ruoyi-oa/src/main/java/com/ruoyi/oa/domain/OaAiReview.java +++ b/ruoyi-oa/src/main/java/com/ruoyi/oa/domain/OaAiReview.java @@ -35,6 +35,9 @@ public class OaAiReview extends BaseEntity { /** 简历审核的目标岗位 */ private String position; + /** 本次审核的附加要求/审核重点 */ + private String requirements; + /** 简历匹配度评分 0-100(合同为空) */ private Integer matchScore; diff --git a/ruoyi-oa/src/main/java/com/ruoyi/oa/domain/vo/OaAiReviewVo.java b/ruoyi-oa/src/main/java/com/ruoyi/oa/domain/vo/OaAiReviewVo.java index 6c81d32..a47ca42 100644 --- a/ruoyi-oa/src/main/java/com/ruoyi/oa/domain/vo/OaAiReviewVo.java +++ b/ruoyi-oa/src/main/java/com/ruoyi/oa/domain/vo/OaAiReviewVo.java @@ -20,6 +20,7 @@ public class OaAiReviewVo implements Serializable { private Long ossId; private String fileUrl; private String position; + private String requirements; private Integer matchScore; private String riskLevel; private String summary; diff --git a/ruoyi-oa/src/main/java/com/ruoyi/oa/service/IOaAiReviewService.java b/ruoyi-oa/src/main/java/com/ruoyi/oa/service/IOaAiReviewService.java index dbd02bc..f5dae31 100644 --- a/ruoyi-oa/src/main/java/com/ruoyi/oa/service/IOaAiReviewService.java +++ b/ruoyi-oa/src/main/java/com/ruoyi/oa/service/IOaAiReviewService.java @@ -14,16 +14,17 @@ public interface IOaAiReviewService { /** * 上传合同/简历并进行 AI 审核,落库并返回结果 * - * @param file PDF / Word 文件 - * @param reviewType contract / resume - * @param position 简历审核的目标岗位(合同可空) + * @param file PDF / Word 文件 + * @param reviewType contract / resume + * @param position 简历审核的目标岗位(合同可空) + * @param requirements 附加审核要求/重点(可空) */ - OaAiReviewVo analyze(MultipartFile file, String reviewType, String position); + OaAiReviewVo analyze(MultipartFile file, String reviewType, String position, String requirements); /** * 流式审核:边生成边推送(SSE),结束后落库 */ - SseEmitter analyzeStream(MultipartFile file, String reviewType, String position); + SseEmitter analyzeStream(MultipartFile file, String reviewType, String position, String requirements); TableDataInfo queryPageList(OaAiReviewBo bo, PageQuery pageQuery); diff --git a/ruoyi-oa/src/main/java/com/ruoyi/oa/service/impl/OaAiReviewServiceImpl.java b/ruoyi-oa/src/main/java/com/ruoyi/oa/service/impl/OaAiReviewServiceImpl.java index 559cec3..ec508bc 100644 --- a/ruoyi-oa/src/main/java/com/ruoyi/oa/service/impl/OaAiReviewServiceImpl.java +++ b/ruoyi-oa/src/main/java/com/ruoyi/oa/service/impl/OaAiReviewServiceImpl.java @@ -57,8 +57,8 @@ public class OaAiReviewServiceImpl implements IOaAiReviewService { private static final Pattern RISK_PATTERN = Pattern.compile("风险评级[::\\s]*([高中低])"); @Override - public OaAiReviewVo analyze(MultipartFile file, String reviewType, String position) { - Prepared p = prepareSync(file, reviewType, position); + public OaAiReviewVo analyze(MultipartFile file, String reviewType, String position, String requirements) { + Prepared p = prepareSync(file, reviewType, position, requirements); buildPrompt(p); String result = p.images != null ? miMoClient.chatMultimodal(p.system, p.userText, p.images) @@ -70,11 +70,11 @@ public class OaAiReviewServiceImpl implements IOaAiReviewService { } @Override - public SseEmitter analyzeStream(MultipartFile file, String reviewType, String position) { + public SseEmitter analyzeStream(MultipartFile file, String reviewType, String position, String requirements) { // 只在同步阶段做轻量校验 + 读取字节(multipart 必须在请求线程内消费); // 文档解析、渲染、大模型调用全部放到异步线程,任何异常都以 SSE error 事件返回, // 避免在返回 SseEmitter 之前抛异常被全局处理器包成 JSON(前端按流读取会“静默失败”)。 - Prepared p = prepareSync(file, reviewType, position); + Prepared p = prepareSync(file, reviewType, position, requirements); String username = currentUsername(); long timeoutMs = ((miMoProps.getTimeout() == null ? 180 : miMoProps.getTimeout()) + 60) * 1000L; SseEmitter emitter = new SseEmitter(timeoutMs); @@ -129,7 +129,7 @@ public class OaAiReviewServiceImpl implements IOaAiReviewService { } /** 同步阶段:校验 + 读取字节(不解析,快) */ - private Prepared prepareSync(MultipartFile file, String reviewType, String position) { + private Prepared prepareSync(MultipartFile file, String reviewType, String position, String requirements) { if (file == null || file.isEmpty()) { throw new ServiceException("请上传文件"); } @@ -153,6 +153,7 @@ public class OaAiReviewServiceImpl implements IOaAiReviewService { Prepared p = new Prepared(); p.reviewType = reviewType; p.position = position; + p.requirements = StringUtils.trimToNull(requirements); p.fileName = fileName; p.bytes = bytes; return p; @@ -160,7 +161,12 @@ public class OaAiReviewServiceImpl implements IOaAiReviewService { /** 解析文档 + 构建提示词(耗时/可能抛异常的部分) */ private void buildPrompt(Prepared p) { - p.system = "contract".equals(p.reviewType) ? contractSystemPrompt() : resumeSystemPrompt(p.position); + String base = "contract".equals(p.reviewType) ? contractSystemPrompt() : resumeSystemPrompt(p.position); + if (StringUtils.isNotBlank(p.requirements)) { + base += "\n\n【本次额外审核要求 / 重点】请在上述固定结构基础上,务必额外重点关注并逐条回应以下要求:\n" + + p.requirements; + } + p.system = base; String text = truncate(DocumentParseUtil.extractText(p.fileName, p.bytes)); if (StringUtils.length(text) >= MIN_TEXT_LEN) { p.userText = "contract".equals(p.reviewType) @@ -189,6 +195,7 @@ public class OaAiReviewServiceImpl implements IOaAiReviewService { entity.setFileUrl(oss.getUrl()); } entity.setPosition(p.position); + entity.setRequirements(p.requirements); entity.setResultMd(result); entity.setSummary(buildSummary(result)); entity.setModel(miMoProps.getModel()); @@ -242,6 +249,7 @@ public class OaAiReviewServiceImpl implements IOaAiReviewService { private static class Prepared { String reviewType; String position; + String requirements; String fileName; byte[] bytes; String system; diff --git a/ruoyi-ui/src/views/oa/aiReview/add.vue b/ruoyi-ui/src/views/oa/aiReview/add.vue index 8455cc6..c4de4f6 100644 --- a/ruoyi-ui/src/views/oa/aiReview/add.vue +++ b/ruoyi-ui/src/views/oa/aiReview/add.vue @@ -30,6 +30,23 @@ : '评估候选人,分析与目标岗位的匹配度、优势、短板与面试建议。' }} 支持 PDF / Word(.doc/.docx),≤ 20MB。 + + +
+
+ 审核重点 + + {{ it.label }} + + 可在「系统管理→字典管理」增删审核项 +
+
+ 附加要求 + +
+
@@ -96,6 +113,7 @@