fix(aihr): protect model and knowledge admin APIs

This commit is contained in:
2026-07-14 05:18:54 +08:00
parent cd4e930832
commit 0c941ba578
4 changed files with 37 additions and 1 deletions
@@ -1,5 +1,7 @@
package org.dromara.aihr.controller;
import cn.dev33.satoken.annotation.SaCheckRole;
import cn.dev33.satoken.annotation.SaMode;
import lombok.RequiredArgsConstructor;
import org.dromara.aihr.domain.AihrModelDto.ChatRequest;
import org.dromara.aihr.domain.AihrModelDto.ChatResponse;
@@ -10,6 +12,7 @@ import org.dromara.aihr.domain.AihrModelDto.ProviderRequest;
import org.dromara.aihr.domain.AihrModelDto.ProviderResponse;
import org.dromara.aihr.domain.AihrModelDto.ProviderStatusRequest;
import org.dromara.aihr.service.AihrModelSeedService;
import org.dromara.common.core.constant.TenantConstants;
import org.dromara.common.core.domain.R;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
@@ -30,54 +33,65 @@ import java.util.List;
@RequestMapping("/api/aihr/model")
public class AihrModelController {
private static final String HR_OPERATOR_ROLE = "hr_operator";
private final AihrModelSeedService modelSeedService;
@SaCheckRole(value = {TenantConstants.SUPER_ADMIN_ROLE_KEY, HR_OPERATOR_ROLE}, mode = SaMode.OR)
@GetMapping("/providers")
public R<List<ProviderResponse>> providers() {
return R.ok(modelSeedService.providers());
}
@SaCheckRole(value = {TenantConstants.SUPER_ADMIN_ROLE_KEY, HR_OPERATOR_ROLE}, mode = SaMode.OR)
@PostMapping("/providers")
public R<Void> createProvider(@RequestBody ProviderRequest request) {
modelSeedService.createProvider(request);
return R.ok();
}
@SaCheckRole(value = {TenantConstants.SUPER_ADMIN_ROLE_KEY, HR_OPERATOR_ROLE}, mode = SaMode.OR)
@PutMapping("/providers/{providerCode}")
public R<Void> updateProvider(@PathVariable String providerCode, @RequestBody ProviderRequest request) {
modelSeedService.updateProvider(providerCode, request);
return R.ok();
}
@SaCheckRole(value = {TenantConstants.SUPER_ADMIN_ROLE_KEY, HR_OPERATOR_ROLE}, mode = SaMode.OR)
@PatchMapping("/providers/{providerCode}/status")
public R<Void> updateProviderStatus(@PathVariable String providerCode, @RequestBody ProviderStatusRequest request) {
modelSeedService.updateProviderStatus(providerCode, request);
return R.ok();
}
@SaCheckRole(value = {TenantConstants.SUPER_ADMIN_ROLE_KEY, HR_OPERATOR_ROLE}, mode = SaMode.OR)
@GetMapping("/configs")
public R<List<ConfigResponse>> configs() {
return R.ok(modelSeedService.configs());
}
@SaCheckRole(value = {TenantConstants.SUPER_ADMIN_ROLE_KEY, HR_OPERATOR_ROLE}, mode = SaMode.OR)
@PostMapping("/configs")
public R<Void> createConfig(@RequestBody ConfigRequest request) {
modelSeedService.createConfig(request);
return R.ok();
}
@SaCheckRole(value = {TenantConstants.SUPER_ADMIN_ROLE_KEY, HR_OPERATOR_ROLE}, mode = SaMode.OR)
@PutMapping("/configs/{id}")
public R<Void> updateConfig(@PathVariable Long id, @RequestBody ConfigRequest request) {
modelSeedService.updateConfig(id, request);
return R.ok();
}
@SaCheckRole(value = {TenantConstants.SUPER_ADMIN_ROLE_KEY, HR_OPERATOR_ROLE}, mode = SaMode.OR)
@PatchMapping("/configs/{id}/enabled")
public R<Void> updateConfigEnabled(@PathVariable Long id, @RequestBody ConfigEnabledRequest request) {
modelSeedService.updateConfigEnabled(id, request);
return R.ok();
}
@SaCheckRole(value = {TenantConstants.SUPER_ADMIN_ROLE_KEY, HR_OPERATOR_ROLE}, mode = SaMode.OR)
@PostMapping("/chat")
public R<ChatResponse> chat(@RequestBody(required = false) ChatRequest request) {
return R.ok(modelSeedService.chat(request));
@@ -1,5 +1,7 @@
package org.dromara.aihr.controller;
import cn.dev33.satoken.annotation.SaCheckRole;
import cn.dev33.satoken.annotation.SaMode;
import lombok.RequiredArgsConstructor;
import org.dromara.aihr.domain.AihrSopDto.AnswerFeedbackItemResponse;
import org.dromara.aihr.domain.AihrSopDto.AnswerFeedbackRequest;
@@ -23,6 +25,7 @@ import org.dromara.aihr.domain.AihrSopDto.VectorIndexStatusResponse;
import org.dromara.aihr.domain.AihrSopDto.VectorizeResponse;
import org.dromara.aihr.service.AihrSopSeedService;
import org.dromara.aihr.service.AihrUploadQueueService;
import org.dromara.common.core.constant.TenantConstants;
import org.dromara.common.core.domain.R;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@@ -44,9 +47,12 @@ import java.util.List;
@RequestMapping("/api/knowledge")
public class AihrSopController {
private static final String HR_OPERATOR_ROLE = "hr_operator";
private final AihrSopSeedService sopSeedService;
private final AihrUploadQueueService uploadQueueService;
@SaCheckRole(value = {TenantConstants.SUPER_ADMIN_ROLE_KEY, HR_OPERATOR_ROLE}, mode = SaMode.OR)
@PostMapping("/doc/upload-async")
public R<UploadEnqueueResponse> uploadAsync(@RequestPart("file") MultipartFile file,
@RequestParam(value = "category", required = false) String category,
@@ -54,12 +60,14 @@ public class AihrSopController {
return R.ok(uploadQueueService.enqueue(file, category, batchId));
}
@SaCheckRole(value = {TenantConstants.SUPER_ADMIN_ROLE_KEY, HR_OPERATOR_ROLE}, mode = SaMode.OR)
@GetMapping("/doc/upload-items")
public R<List<UploadItemResponse>> uploadItems(@RequestParam(value = "batchId", required = false) String batchId,
@RequestParam(value = "limit", required = false, defaultValue = "50") int limit) {
return R.ok(uploadQueueService.items(batchId, limit));
}
@SaCheckRole(value = {TenantConstants.SUPER_ADMIN_ROLE_KEY, HR_OPERATOR_ROLE}, mode = SaMode.OR)
@PostMapping("/doc/upload-items/{id}/retry")
public R<UploadItemResponse> retryUploadItem(@PathVariable Long id) {
return R.ok(uploadQueueService.retry(id));
@@ -81,18 +89,21 @@ public class AihrSopController {
return R.ok(sopSeedService.answerFeedback(request));
}
@SaCheckRole(value = {TenantConstants.SUPER_ADMIN_ROLE_KEY, HR_OPERATOR_ROLE}, mode = SaMode.OR)
@GetMapping("/answer-feedback")
public R<List<AnswerFeedbackItemResponse>> answerFeedbackList(@RequestParam(value = "status", required = false, defaultValue = "待复核") String status,
@RequestParam(value = "limit", required = false, defaultValue = "50") int limit) {
return R.ok(sopSeedService.answerFeedbackList(status, limit));
}
@SaCheckRole(value = {TenantConstants.SUPER_ADMIN_ROLE_KEY, HR_OPERATOR_ROLE}, mode = SaMode.OR)
@PostMapping("/answer-feedback/{id}/review")
public R<AnswerFeedbackReviewResponse> reviewAnswerFeedback(@PathVariable Long id) {
AnswerFeedbackReviewResponse response = sopSeedService.reviewAnswerFeedback(id);
return response == null ? R.fail("答案反馈记录不存在") : R.ok(response);
}
@SaCheckRole(value = {TenantConstants.SUPER_ADMIN_ROLE_KEY, HR_OPERATOR_ROLE}, mode = SaMode.OR)
@PostMapping("/reviews/{id}")
public R<SopReviewResponse> review(@PathVariable Long id, @RequestBody(required = false) SopReviewRequest request) {
SopReviewResponse response = sopSeedService.reviewSearch(id, request);
@@ -104,46 +115,55 @@ public class AihrSopController {
return R.ok(sopSeedService.summaryCard(request));
}
@SaCheckRole(value = {TenantConstants.SUPER_ADMIN_ROLE_KEY, HR_OPERATOR_ROLE}, mode = SaMode.OR)
@PostMapping("/doc/upload")
public R<UploadResponse> uploadDoc(@RequestPart("file") MultipartFile file, @RequestParam("category") String category) {
return R.ok(sopSeedService.uploadDoc(file, category));
}
@SaCheckRole(value = {TenantConstants.SUPER_ADMIN_ROLE_KEY, HR_OPERATOR_ROLE}, mode = SaMode.OR)
@PostMapping("/doc/import-local")
public R<LocalImportResponse> importLocalDocs(@RequestBody LocalImportRequest request) {
return R.ok(sopSeedService.importLocalDocs(request));
}
@SaCheckRole(value = {TenantConstants.SUPER_ADMIN_ROLE_KEY, HR_OPERATOR_ROLE}, mode = SaMode.OR)
@PostMapping("/doc/import-local-task")
public R<LocalImportTaskResponse> startLocalImportTask(@RequestBody LocalImportRequest request) {
return R.ok(sopSeedService.startLocalImportTask(request));
}
@SaCheckRole(value = {TenantConstants.SUPER_ADMIN_ROLE_KEY, HR_OPERATOR_ROLE}, mode = SaMode.OR)
@PostMapping("/doc/vectorize-missing")
public R<VectorizeResponse> vectorizeMissing() {
return R.ok(sopSeedService.vectorizeMissing());
}
@SaCheckRole(value = {TenantConstants.SUPER_ADMIN_ROLE_KEY, HR_OPERATOR_ROLE}, mode = SaMode.OR)
@GetMapping("/doc/vector-index-status")
public R<VectorIndexStatusResponse> vectorIndexStatus() {
return R.ok(sopSeedService.vectorIndexStatus());
}
@SaCheckRole(value = {TenantConstants.SUPER_ADMIN_ROLE_KEY, HR_OPERATOR_ROLE}, mode = SaMode.OR)
@PostMapping("/doc/rebuild-vector-index")
public R<VectorizeResponse> rebuildVectorIndex() {
return R.ok(sopSeedService.rebuildVectorIndex());
}
@SaCheckRole(value = {TenantConstants.SUPER_ADMIN_ROLE_KEY, HR_OPERATOR_ROLE}, mode = SaMode.OR)
@GetMapping("/doc/import-tasks")
public R<List<LocalImportTaskResponse>> localImportTasks() {
return R.ok(sopSeedService.localImportTasks());
}
@SaCheckRole(value = {TenantConstants.SUPER_ADMIN_ROLE_KEY, HR_OPERATOR_ROLE}, mode = SaMode.OR)
@PostMapping("/doc/import-tasks/{id}/cancel")
public R<LocalImportTaskResponse> cancelLocalImportTask(@PathVariable Long id) {
return R.ok(sopSeedService.cancelLocalImportTask(id));
}
@SaCheckRole(value = {TenantConstants.SUPER_ADMIN_ROLE_KEY, HR_OPERATOR_ROLE}, mode = SaMode.OR)
@GetMapping("/processing/overview")
public R<ProcessingOverviewResponse> processingOverview() {
return R.ok(sopSeedService.processingOverview());
+1 -1
View File
@@ -40,7 +40,7 @@
| 智能错题本 | 3.1、4.4、TechSpec P1 | 已补员工本人错题本和主管 `/api/aihr/mobile/practice/team` 内的 `mistakes` 聚合:按当前项目权限统计场景、归因、次数、影响人数、平均分和最近发生时间;员工端可跳转待训练 retry assignment,retry 已完成时改为显示“已完成”,不再重复引导进入已完成任务;不新增独立错题事实表 | 继续用正式组织快照和试点窗口验证主管只看到本项目数据;后续可在真实错题量上来后补趋势/导出,不把本地演示样本当正式指标 |
| 案例学习/视频详情 | 3.1、4.6、矩阵第35行 | `AihrCaseService` 已能从 `aihr_case_record` 恢复转写案例,且新增按项目范围校验的 `GET /api/knowledge/case/records/{caseId}`,员工端可查看持久化转写、摘要和状态;但仍没有稳定 media URL、学习点、主管点评和案例视频资产 | 先由业务提供样片或正式媒体 URL,并确定案例详情、学习点、主管点评字段,再扩持久化契约和权限;没有真实媒体 URL 时不造播放按钮 |
| 案例库质量与数量 | L1-B、4.6/4.7 | 上传/整理/筛选路径已有;“至少20条脱敏案例”和每周每项目一条需要正式数据证明,当前也没有可用于一期样片验收的真实视频资产 | 先按项目窗口采集并脱敏检查,再补案例统计/学习详情导出;不使用空壳视频或演示 seed 冒充 |
| RAG 可用率与知识治理 | 4.7、L1-C、G3 | 搜索、引用、反馈、知识缺口和人工评审入口已有;员工反馈保存同一次搜索的 `reviewId`,SOP 评审记录和搜索响应现在保留答案生成 `promptVersion`;住宅核心流程覆盖率与可用率仍需正式评审数据,不能宣称完成逐问答全链路追溯 | 后续由业务确认模型/检索批次的正式版本口径,再补检索批次与模型版本;当前先用 `reviewId + promptVersion + query + fragment` 做可追溯统计,不伪造正式可用率 |
| RAG 可用率与知识治理 | 4.7、L1-C、G3 | 搜索、引用、反馈、知识缺口和人工评审入口已有;员工反馈保存同一次搜索的 `reviewId`,SOP 评审记录和搜索响应现在保留答案生成 `promptVersion`;知识库上传、导入、向量重建、评审队列和模型配置管理接口已收紧为 `superadmin/hr_operator`;住宅核心流程覆盖率与可用率仍需正式评审数据,不能宣称完成逐问答全链路追溯 | 后续由业务确认模型/检索批次的正式版本口径,再补检索批次与模型版本;当前先用 `reviewId + promptVersion + query + fragment` 做可追溯统计,不伪造正式可用率 |
| 方言语音与多模态训练验收 | 4.3.2、4.3.3、L1-C | ASR/TTS、音频 OSS 留痕和文本降级链路已有;但当前没有四川话/粤语等方言供应商配置、样本集和准确率/响应时延证据,也没有把图像/视频训练输入纳入一期验收口径 | 先确定一期方言厂商和可接受指标,用真实短音频做至少一条方言路径回归;图像/视频输入另按场景与合规边界评审,不用通用文本链路冒充完成 |
### P2/P3:明确暂缓,不应混入当前修复
+2
View File
@@ -386,6 +386,8 @@ contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service
contains backend/script/sql/aihr_practice_mysql8.sql "aihr_practice_calibration"
contains frontend/src/api/aihr/practice.ts '/api/train/practice/scenarios/${id}/enabled'
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrPracticeController.java "/scenarios/{id}/enabled"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrModelController.java "@SaCheckRole"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrSopController.java "@SaCheckRole"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/domain/AihrSopDto.java "String position"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrSopController.java "/position-sop"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrSopSeedService.java 'firstNonBlank(position, "生活顾问")'