fix(aihr): protect model and knowledge admin APIs
This commit is contained in:
+14
@@ -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));
|
||||
|
||||
+20
@@ -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());
|
||||
|
||||
@@ -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:明确暂缓,不应混入当前修复
|
||||
|
||||
@@ -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, "生活顾问")'
|
||||
|
||||
Reference in New Issue
Block a user