Compare commits

...

4 Commits

Author SHA1 Message Date
wh
942c68989c Merge commit 'ca81514d4373039356d1684f93ae24c9b2182c79' 2026-04-28 20:14:39 +08:00
wh
ca81514d43 任务管理优化 2026-04-28 20:14:14 +08:00
wh
7e35cf146e Merge commit '343df65c698d579335d9c7c47160b79ae0c14f8a' 2026-04-28 12:15:39 +08:00
wh
343df65c69 系统资源管理优化 2026-04-28 12:15:10 +08:00
21 changed files with 443 additions and 335 deletions

View File

@@ -40,10 +40,8 @@ public class AnnotationTaskController {
@Operation(summary = "更新标注任务")
@PutMapping("/{id}")
public Result<AnnotationTaskResponse> update(
@Parameter(description = "任务ID", example = "191000000000000301")
@PathVariable Long id,
@Valid @RequestBody UpdateAnnotationTaskRequest request
) {
@Parameter(description = "任务ID", example = "191000000000000301") @PathVariable Long id,
@Valid @RequestBody UpdateAnnotationTaskRequest request) {
return Result.success(annotationTaskService.updateTask(UserContext.requireUser(), id, request));
}
@@ -56,18 +54,14 @@ public class AnnotationTaskController {
@Operation(summary = "查询标注任务详情")
@GetMapping("/{id}")
public Result<AnnotationTaskResponse> detail(
@Parameter(description = "任务ID", example = "191000000000000301")
@PathVariable Long id
) {
@Parameter(description = "任务ID", example = "191000000000000301") @PathVariable Long id) {
return Result.success(annotationTaskService.getTask(UserContext.requireUser(), id));
}
@Operation(summary = "删除标注任务")
@DeleteMapping("/{id}")
public Result<Void> delete(
@Parameter(description = "任务ID", example = "191000000000000301")
@PathVariable Long id
) {
@Parameter(description = "任务ID", example = "191000000000000301") @PathVariable Long id) {
annotationTaskService.deleteTask(UserContext.requireUser(), id);
return Result.success();
}

View File

@@ -1,19 +1,5 @@
package com.labelsys.backend.controller;
import com.labelsys.backend.annotation.RequirePosition;
import com.labelsys.backend.common.Result;
import com.labelsys.backend.context.UserContext;
import com.labelsys.backend.dto.common.PageResult;
import com.labelsys.backend.dto.request.SaveSysConfigRequest;
import com.labelsys.backend.dto.request.SysConfigPageQuery;
import com.labelsys.backend.dto.response.SysConfigResponse;
import com.labelsys.backend.enums.UserPosition;
import com.labelsys.backend.service.SysConfigService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springdoc.core.annotations.ParameterObject;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@@ -23,6 +9,21 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.labelsys.backend.common.Result;
import com.labelsys.backend.context.UserContext;
import com.labelsys.backend.dto.common.PageResult;
import com.labelsys.backend.dto.request.SaveSysConfigRequest;
import com.labelsys.backend.dto.request.SysConfigPageQuery;
import com.labelsys.backend.dto.request.UpdateSysConfigRequest;
import com.labelsys.backend.dto.response.SysConfigResponse;
import com.labelsys.backend.service.SysConfigService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
@Tag(name = "系统配置管理")
@RestController
@RequestMapping("/api/sys-configs")
@@ -32,21 +33,21 @@ public class SysConfigController {
private final SysConfigService sysConfigService;
@Operation(summary = "创建系统配置")
@RequirePosition(UserPosition.ADMIN)
// @RequirePosition(UserPosition.ADMIN)
@PostMapping
public Result<SysConfigResponse> create(@Valid @RequestBody SaveSysConfigRequest request) {
return Result.success(sysConfigService.toResponse(sysConfigService.saveConfig(UserContext.requireUser(), request)));
return Result
.success(sysConfigService.toResponse(sysConfigService.saveConfig(UserContext.requireUser(), request)));
}
@Operation(summary = "更新系统配置")
@RequirePosition(UserPosition.ADMIN)
// @RequirePosition(UserPosition.ADMIN)
@PutMapping("/{id}")
public Result<SysConfigResponse> update(
@Parameter(description = "配置ID", example = "191000000000000501")
@PathVariable Long id,
@Valid @RequestBody SaveSysConfigRequest request
) {
return Result.success(sysConfigService.toResponse(sysConfigService.updateConfig(UserContext.requireUser(), id, request)));
@Parameter(description = "配置ID", example = "191000000000000501") @PathVariable Long id,
@Valid @RequestBody UpdateSysConfigRequest request) {
return Result.success(
sysConfigService.toResponse(sysConfigService.updateConfig(UserContext.requireUser(), id, request)));
}
@Operation(summary = "分页查询系统配置")
@@ -57,10 +58,8 @@ public class SysConfigController {
@Operation(summary = "查询系统配置详情")
@GetMapping("/{id}")
public Result<SysConfigResponse> detail(
@Parameter(description = "配置ID", example = "191000000000000501")
@PathVariable Long id
) {
public Result<SysConfigResponse>
detail(@Parameter(description = "配置ID", example = "191000000000000501") @PathVariable Long id) {
return Result.success(sysConfigService.getConfig(UserContext.requireUser(), id));
}
}

View File

@@ -1,11 +1,13 @@
package com.labelsys.backend.dto.request;
import com.labelsys.backend.enums.TaskType;
import io.swagger.v3.oas.annotations.media.Schema;
@Schema(description = "标注任务分页查询请求")
public record AnnotationTaskPageQuery(
@Schema(description = "关键字", example = "运输") String keyword,
@Schema(description = "任务类型", example = "EXTRACT_QA") String taskType,
@Schema(description = "任务类型", example = "EXTRACT_QA") TaskType taskType,
@Schema(description = "任务状态", example = "PENDING") String taskStatus,
@Schema(description = "资源ID", example = "191000000000000101") Long resourceId,
@Schema(description = "是否已删除", example = "false") Boolean isDeleted,

View File

@@ -1,20 +1,24 @@
package com.labelsys.backend.dto.request;
import java.util.List;
import com.labelsys.backend.enums.IndustryType;
import com.labelsys.backend.enums.TaskType;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import java.util.List;
import jakarta.validation.constraints.NotNull;
@Schema(description = "创建标注任务请求")
public record CreateAnnotationTaskRequest(
@Schema(description = "任务名称", example = "运输文档问答抽取任务") @NotBlank(message = "任务名称不能为空") String taskName,
@Schema(description = "行业类型", example = "transport") String industryType,
@Schema(description = "任务类型", example = "EXTRACT_QA") String taskType,
@Schema(description = "资源ID列表", example = "[191000000000000101,191000000000000102]") @NotEmpty(message = "资源列表不能为空") List<Long> resourceIds,
@Schema(description = "抽取模型配置", example = "{\"mode\":\"SELECT\",\"selectedConfigName\":\"qwen-plus-extract\"}") @Valid TaskModelConfigRequest extractModel,
@Schema(description = "校验模型配置", example = "{\"mode\":\"MANUAL\",\"manualConfig\":{\"modelName\":\"qwen-max\",\"modelUrl\":\"https://dashscope.aliyuncs.com/compatible-mode/v1\",\"apiKey\":\"sk-demo5678\"}}") @Valid TaskModelConfigRequest verifyModel,
@Schema(description = "抽取提示词配置", example = "{\"selectedConfigName\":\"qa-extract-v1\"}") @Valid PromptConfigOptionRequest extractPrompt,
@Schema(description = "校验提示词配置", example = "{\"promptText\":\"请对抽取结果进行逐项校验,并输出差异说明。\"}") @Valid PromptConfigOptionRequest verifyPrompt
) {
@Schema(description = "任务名称", example = "运输文档问答抽取任务") @NotBlank(message = "任务名称不能为空") String taskName,
@Schema(description = "行业类型", defaultValue = "TRANSPORT", example = "TRANSPORT") @NotNull(message = "行业类型不能为空") IndustryType industryType,
@Schema(description = "任务类型", defaultValue = "EXTRACT_QA", example = "EXTRACT_QA") @NotNull(message = "任务类型不能为空") TaskType taskType,
@Schema(description = "资源ID列表", example = "[191000000000000101,191000000000000102]") @NotEmpty(message = "资源列表不能为空") List<Long> resourceIds,
@Schema(description = "抽取模型配置", example = "{\"mode\":\"SELECT\",\"selectedConfigName\":\"qwen-plus-extract\"}") @Valid TaskModelConfigRequest extractModel,
@Schema(description = "校验模型配置", example = "{\"mode\":\"MANUAL\",\"manualConfig\":{\"modelName\":\"qwen-max\",\"modelUrl\":\"https://dashscope.aliyuncs.com/compatible-mode/v1\",\"apiKey\":\"sk-demo5678\"}}") @Valid TaskModelConfigRequest verifyModel,
@Schema(description = "抽取提示词配置", example = "{\"selectedConfigName\":\"qa-extract-v1\"}") @Valid PromptConfigOptionRequest extractPrompt,
@Schema(description = "校验提示词配置", example = "{\"promptText\":\"请对抽取结果进行逐项校验,并输出差异说明。\"}") @Valid PromptConfigOptionRequest verifyPrompt) {
}

View File

@@ -5,6 +5,7 @@ import jakarta.validation.constraints.NotBlank;
@Schema(description = "创建系统工程师管理员请求")
public record CreateSystemEngineerAdminRequest(
@Schema(description = "手机号", example = "13800138002") @NotBlank(message = "不能为空") String phone,
@Schema(description = "用户名,前端展示用,可为空", example = "system-engineer") String username,
@Schema(description = "真实姓名", example = "系统工程师") @NotBlank(message = "不能为空") String realName) {}
@Schema(description = "手机号", example = "13800138002") @NotBlank(message = "不能为空") String phone,
@Schema(description = "用户名,前端展示用,可为空", example = "system-engineer") String username,
@Schema(description = "真实姓名", example = "系统工程师") @NotBlank(message = "不能为空") String realName) {
}

View File

@@ -7,7 +7,7 @@ import jakarta.validation.constraints.NotBlank;
public record SaveSysConfigRequest(
@Schema(description = "配置类型", example = "MODEL") @NotBlank(message = "配置类型不能为空") String configType,
@Schema(description = "配置名称", example = "qwen-plus-extract") @NotBlank(message = "配置名称不能为空") String configName,
@Schema(description = "配置值", example = "{\"modelName\":\"qwen-plus\",\"modelUrl\":\"https://dashscope.aliyuncs.com/compatible-mode/v1\",\"apiKey\":\"sk-demo1234\"}") @NotBlank(message = "配置值不能为空") String configValue,
@Schema(description = "配置状态", example = "ENABLED") @NotBlank(message = "配置状态不能为空") String status
) {
}
@Schema(description = "配置值",
example = "{\"modelName\":\"qwen-plus\",\"modelUrl\":\"https://dashscope.aliyuncs.com/compatible-mode/v1\",\"apiKey\":\"sk-demo1234\"}") @NotBlank(
message = "配置值不能为空") String configValue,
@Schema(description = "配置状态", example = "ENABLED") String status) {}

View File

@@ -3,11 +3,8 @@ package com.labelsys.backend.dto.request;
import io.swagger.v3.oas.annotations.media.Schema;
@Schema(description = "系统配置分页查询请求")
public record SysConfigPageQuery(
@Schema(description = "配置类型", example = "MODEL") String configType,
public record SysConfigPageQuery(@Schema(description = "配置类型", example = "MODEL") String configType,
@Schema(description = "配置名称", example = "qwen-plus-extract") String configName,
@Schema(description = "配置状态", example = "ENABLED") String status,
@Schema(description = "页码", example = "1") Integer pageNo,
@Schema(description = "每页数量", example = "10") Integer pageSize
) {
}
@Schema(description = "每页数量", example = "10") Integer pageSize) {}

View File

@@ -1,13 +1,16 @@
package com.labelsys.backend.dto.request;
import com.labelsys.backend.enums.ConfigMode;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
@Schema(description = "任务模型配置请求")
public record TaskModelConfigRequest(
@Schema(description = "配置模式SELECT 或 MANUAL", example = "SELECT") @NotBlank(message = "配置模式不能为空") String mode,
@Schema(description = "已选择的配置名称", example = "qwen-plus-extract") String selectedConfigName,
@Schema(description = "手动录入的模型配置", example = "{\"modelName\":\"qwen-plus\",\"modelUrl\":\"https://dashscope.aliyuncs.com/compatible-mode/v1\",\"apiKey\":\"sk-demo1234\"}") @Valid ManualModelConfigRequest manualConfig
) {
@Schema(description = "配置模式SELECT 或 MANUAL", example = "SELECT") @NotNull(message = "配置模式不能为空") ConfigMode mode,
@Schema(description = "已选择的配置名称", example = "qwen-plus-extract") String selectedConfigName,
@Schema(description = "手动录入的模型配置", example = "{\"modelName\":\"qwen-plus\",\"modelUrl\":\"https://dashscope.aliyuncs.com/compatible-mode/v1\",\"apiKey\":\"sk-demo1234\"}") @Valid ManualModelConfigRequest manualConfig) {
}

View File

@@ -1,18 +1,20 @@
package com.labelsys.backend.dto.request;
import java.util.List;
import com.labelsys.backend.enums.IndustryType;
import com.labelsys.backend.enums.TaskType;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import java.util.List;
@Schema(description = "更新标注任务请求")
public record UpdateAnnotationTaskRequest(
@Schema(description = "行业类型", example = "transport") String industryType,
@Schema(description = "任务类型", example = "EXTRACT_QA") String taskType,
@Schema(description = "资源ID列表", example = "[191000000000000101,191000000000000102]") @NotEmpty(message = "资源列表不能为空") List<Long> resourceIds,
@Schema(description = "抽取模型配置", example = "{\"mode\":\"SELECT\",\"selectedConfigName\":\"qwen-plus-extract\"}") @Valid TaskModelConfigRequest extractModel,
@Schema(description = "校验模型配置", example = "{\"mode\":\"MANUAL\",\"manualConfig\":{\"modelName\":\"qwen-max\",\"modelUrl\":\"https://dashscope.aliyuncs.com/compatible-mode/v1\",\"apiKey\":\"sk-demo5678\"}}") @Valid TaskModelConfigRequest verifyModel,
@Schema(description = "抽取提示词配置", example = "{\"selectedConfigName\":\"qa-extract-v1\"}") @Valid PromptConfigOptionRequest extractPrompt,
@Schema(description = "校验提示词配置", example = "{\"promptText\":\"请对抽取结果进行逐项校验,并输出差异说明。\"}") @Valid PromptConfigOptionRequest verifyPrompt
) {
@Schema(description = "行业类型", example = "TRANSPORT") IndustryType industryType,
@Schema(description = "任务类型", example = "EXTRACT_QA") TaskType taskType,
@Schema(description = "资源ID列表", example = "[191000000000000101,191000000000000102]") List<Long> resourceIds,
@Schema(description = "抽取模型配置", example = "{\"mode\":\"SELECT\",\"selectedConfigName\":\"qwen-plus-extract\"}") @Valid TaskModelConfigRequest extractModel,
@Schema(description = "校验模型配置", example = "{\"mode\":\"MANUAL\",\"manualConfig\":{\"modelName\":\"qwen-max\",\"modelUrl\":\"https://dashscope.aliyuncs.com/compatible-mode/v1\",\"apiKey\":\"sk-demo5678\"}}") @Valid TaskModelConfigRequest verifyModel,
@Schema(description = "抽取提示词配置", example = "{\"selectedConfigName\":\"qa-extract-v1\"}") @Valid PromptConfigOptionRequest extractPrompt,
@Schema(description = "校验提示词配置", example = "{\"promptText\":\"请对抽取结果进行逐项校验,并输出差异说明。\"}") @Valid PromptConfigOptionRequest verifyPrompt) {
}

View File

@@ -0,0 +1,14 @@
package com.labelsys.backend.dto.request;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
@Schema(description = "更新系统配置请求")
public record UpdateSysConfigRequest(
@Schema(description = "配置类型", example = "MODEL") String configType,
@Schema(description = "配置名称", example = "qwen-plus-extract") String configName,
@Schema(description = "配置值",
example = "{\"modelName\":\"qwen-plus\",\"modelUrl\":\"https://dashscope.aliyuncs.com/compatible-mode/v1\",\"apiKey\":\"sk-demo1234\"}") @NotBlank(
message = "配置值不能为空") String configValue,
@Schema(description = "配置状态", example = "ENABLED") String status) {}

View File

@@ -4,12 +4,15 @@ import io.swagger.v3.oas.annotations.media.Schema;
import java.time.LocalDateTime;
import java.util.List;
import com.labelsys.backend.enums.IndustryType;
import com.labelsys.backend.enums.TaskType;
@Schema(description = "标注任务响应")
public record AnnotationTaskResponse(
@Schema(description = "任务ID", example = "191000000000000301") Long id,
@Schema(description = "任务名称", example = "运输文档问答抽取任务") String taskName,
@Schema(description = "行业类型:默认值transport暂不显示", example = "transport") String industryType,
@Schema(description = "任务类型:暂不显示", example = "EXTRACT_QA") String taskType,
@Schema(description = "行业类型:默认值transport暂不显示", example = "transport") IndustryType industryType,
@Schema(description = "任务类型:暂不显示", example = "EXTRACT_QA") TaskType taskType,
@Schema(description = "任务状态", example = "PENDING") String taskStatus,
@Schema(description = "资源ID列表", example = "[191000000000000101,191000000000000102]") List<Long> resourceIds,
@Schema(description = "抽取模型配置", example = "{\"configId\":191000000000000511,\"configName\":\"qwen-plus-extract\",\"modelName\":\"qwen-plus\",\"modelUrl\":\"https://dashscope.aliyuncs.com/compatible-mode/v1\",\"maskedApiKey\":\"****1234\"}") TaskModelConfigResponse extractModel,

View File

@@ -3,6 +3,8 @@ package com.labelsys.backend.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.labelsys.backend.enums.IndustryType;
import com.labelsys.backend.enums.TaskType;
import com.labelsys.backend.enums.UserRole;
import java.time.LocalDateTime;
import lombok.AllArgsConstructor;
@@ -22,8 +24,8 @@ public class AnnotationTask {
private Long creatorId;
private UserRole creatorRole;
private String taskName;
private String industryType;
private String taskType;
private IndustryType industryType;
private TaskType taskType;
private Long extractModelConfigId;
private String extractModelName;
private String extractModelUrl;

View File

@@ -23,6 +23,7 @@ public class SysConfig {
private String configValue;
private String status;
private Long creatorId;
private String creatorRole;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}

View File

@@ -0,0 +1,12 @@
package com.labelsys.backend.enums;
import io.swagger.v3.oas.annotations.media.Schema;
@Schema(description = "模型配置模式枚举值SELECT:选择已有模型配置、MANUAL手动配置新模型")
public enum ConfigMode {
@Schema(description = "从已有配置中选择")
SELECT,
@Schema(description = "手动录入配置")
MANUAL
}

View File

@@ -0,0 +1,21 @@
package com.labelsys.backend.enums;
import io.swagger.v3.oas.annotations.media.Schema;
@Schema(description = "行业类型")
public enum IndustryType {
@Schema(description = "交通运输")
TRANSPORT,
@Schema(description = "电力")
ELECTRICITY,
@Schema(description = "金融")
FINANCE,
@Schema(description = "医疗")
MEDICAL,
@Schema(description = "教育")
EDUCATION
}

View File

@@ -0,0 +1,12 @@
package com.labelsys.backend.enums;
import io.swagger.v3.oas.annotations.media.Schema;
@Schema(description = "标注任务类型")
public enum TaskType {
@Schema(description = "抽取问答对")
EXTRACT_QA,
@Schema(description = "大模型微调")
FINE_TUNE
}

View File

@@ -1,5 +1,9 @@
package com.labelsys.backend.service;
import java.util.List;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.labelsys.backend.common.ResultCode;
@@ -14,11 +18,9 @@ import com.labelsys.backend.entity.SourceResource;
import com.labelsys.backend.enums.RuntimeResultStatus;
import com.labelsys.backend.mapper.AnnotationResultMapper;
import com.labelsys.backend.mapper.SourceResourceMapper;
import com.labelsys.backend.service.DataPermissionService;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Slf4j
@Service
@@ -33,11 +35,12 @@ public class AnnotationResultService {
List<String> allowedRoles = dataPermissionService.getAllowedRoles(currentUser);
boolean shouldFilterByUserId = dataPermissionService.shouldFilterByUserId(currentUser);
LambdaQueryWrapper<AnnotationResult> wrapper = new LambdaQueryWrapper<AnnotationResult>()
.eq(AnnotationResult::getCompanyId, currentUser.companyId())
.eq(query.taskId() != null, AnnotationResult::getTaskId, query.taskId())
.eq(query.resourceId() != null, AnnotationResult::getResourceId, query.resourceId())
.eq(query.requiresManualReview() != null, AnnotationResult::getRequiresManualReview, query.requiresManualReview());
LambdaQueryWrapper<AnnotationResult> wrapper =
new LambdaQueryWrapper<AnnotationResult>().eq(AnnotationResult::getCompanyId, currentUser.companyId())
.eq(query.taskId() != null, AnnotationResult::getTaskId, query.taskId())
.eq(query.resourceId() != null, AnnotationResult::getResourceId, query.resourceId())
.eq(query.requiresManualReview() != null, AnnotationResult::getRequiresManualReview,
query.requiresManualReview());
if (shouldFilterByUserId) {
wrapper.eq(AnnotationResult::getCreatorId, currentUser.userId());
@@ -50,19 +53,19 @@ public class AnnotationResultService {
Page<AnnotationResult> page = new Page<>(query.pageNo(), query.pageSize());
Page<AnnotationResult> resultPage = annotationResultMapper.selectPage(page, wrapper);
List<AnnotationResultResponse> records = resultPage.getRecords().stream()
.map(this::toResponse)
List<AnnotationResultResponse> records = resultPage.getRecords().stream().map(this::toResponse)
.filter(response -> query.runtimeStatus() == null || query.runtimeStatus().equals(response.runtimeStatus()))
.toList();
return new PageResult<>(records, resultPage.getTotal(), (int) resultPage.getCurrent(), (int) resultPage.getSize());
return new PageResult<>(records, resultPage.getTotal(), (int)resultPage.getCurrent(),
(int)resultPage.getSize());
}
public AnnotationResultResponse getResult(LoginUser currentUser, Long resultId) {
AnnotationResult result = annotationResultMapper.findActiveByIdAndCompanyId(resultId, currentUser.companyId());
if (result == null) {
log.warn("Result not found or cross-tenant access attempt: resultId={}, companyId={}, userId={}",
resultId, currentUser.companyId(), currentUser.userId());
log.warn("Result not found or cross-tenant access attempt: resultId={}, companyId={}, userId={}", resultId,
currentUser.companyId(), currentUser.userId());
throw new BusinessException(ResultCode.NOT_FOUND, "结果不存在");
}
return toResponse(result);
@@ -71,34 +74,20 @@ public class AnnotationResultService {
public AnnotationResultCompareResponse compareResult(LoginUser currentUser, Long resultId) {
AnnotationResult result = annotationResultMapper.findActiveByIdAndCompanyId(resultId, currentUser.companyId());
if (result == null) {
log.warn("Result not found or cross-tenant access attempt: resultId={}, companyId={}, userId={}",
resultId, currentUser.companyId(), currentUser.userId());
log.warn("Result not found or cross-tenant access attempt: resultId={}, companyId={}, userId={}", resultId,
currentUser.companyId(), currentUser.userId());
throw new BusinessException(ResultCode.NOT_FOUND, "结果不存在");
}
SourceResource resource = sourceResourceMapper.selectById(result.getResourceId());
return new AnnotationResultCompareResponse(
result.getId(),
result.getTaskId(),
result.getResourceId(),
result.getQaContentJson(),
result.getDiffSummary(),
result.getQaContentStorageMode(),
result.getQaContentFilePath(),
resource == null ? null : resource.getFilePath());
return new AnnotationResultCompareResponse(result.getId(), result.getTaskId(), result.getResourceId(),
result.getQaContentJson(), result.getDiffSummary(), result.getQaContentStorageMode(),
result.getQaContentFilePath(), resource == null ? null : resource.getFilePath());
}
private AnnotationResultResponse toResponse(AnnotationResult result) {
return new AnnotationResultResponse(
result.getId(),
result.getTaskId(),
result.getResourceId(),
deriveStatus(result),
result.getRequiresManualReview(),
result.getIsDeleted(),
result.getQaContentStorageMode(),
result.getReviewComment(),
result.getReviewedAt(),
result.getCreatedAt());
return new AnnotationResultResponse(result.getId(), result.getTaskId(), result.getResourceId(),
deriveStatus(result), result.getRequiresManualReview(), result.getIsDeleted(),
result.getQaContentStorageMode(), result.getReviewComment(), result.getReviewedAt(), result.getCreatedAt());
}
private String deriveStatus(AnnotationResult result) {

View File

@@ -25,11 +25,15 @@ import com.labelsys.backend.dto.response.TaskPromptConfigResponse;
import com.labelsys.backend.entity.AnnotationTask;
import com.labelsys.backend.entity.AnnotationTaskResource;
import com.labelsys.backend.entity.SourceResource;
import com.labelsys.backend.entity.SysConfig;
import com.labelsys.backend.enums.IndustryType;
import com.labelsys.backend.enums.SourceStatus;
import com.labelsys.backend.enums.TaskStatus;
import com.labelsys.backend.enums.TaskType;
import com.labelsys.backend.mapper.AnnotationTaskMapper;
import com.labelsys.backend.mapper.AnnotationTaskResourceMapper;
import com.labelsys.backend.mapper.SourceResourceMapper;
import com.labelsys.backend.mapper.SysConfigMapper;
import com.labelsys.backend.util.IdGenerator;
import lombok.RequiredArgsConstructor;
@@ -43,44 +47,36 @@ public class AnnotationTaskService {
private final AnnotationTaskMapper annotationTaskMapper;
private final AnnotationTaskResourceMapper annotationTaskResourceMapper;
private final SourceResourceMapper sourceResourceMapper;
private final SysConfigMapper sysConfigMapper;
private final SysConfigService sysConfigService;
private final DataPermissionService dataPermissionService;
@Transactional
public AnnotationTaskResponse createTask(LoginUser currentUser, CreateAnnotationTaskRequest request) {
List<SourceResource> resources = loadAndValidateResources(currentUser, request.resourceIds());
SysConfigService.ResolvedModelConfig extractModel = sysConfigService.resolveModelConfig(currentUser, request.extractModel());
SysConfigService.ResolvedModelConfig verifyModel = sysConfigService.resolveModelConfig(currentUser, request.verifyModel());
SysConfigService.ResolvedPromptConfig extractPrompt = sysConfigService.resolvePromptConfig(currentUser, request.extractPrompt());
SysConfigService.ResolvedPromptConfig verifyPrompt = sysConfigService.resolvePromptConfig(currentUser, request.verifyPrompt());
SysConfigService.ResolvedModelConfig extractModel = sysConfigService.resolveModelConfig(currentUser,
request.extractModel());
SysConfigService.ResolvedModelConfig verifyModel = sysConfigService.resolveModelConfig(currentUser,
request.verifyModel());
SysConfigService.ResolvedPromptConfig extractPrompt = sysConfigService.resolvePromptConfig(currentUser,
request.extractPrompt());
SysConfigService.ResolvedPromptConfig verifyPrompt = sysConfigService.resolvePromptConfig(currentUser,
request.verifyPrompt());
AnnotationTask task = AnnotationTask.builder()
.id(IdGenerator.nextId())
.companyId(currentUser.companyId())
.creatorId(currentUser.userId())
.creatorRole(currentUser.role())
.taskName(request.taskName())
.industryType(defaultIndustryType(request.industryType()))
.taskType(defaultTaskType(request.taskType()))
.extractModelConfigId(extractModel.configId())
.extractModelName(extractModel.modelName())
.extractModelUrl(extractModel.modelUrl())
.extractModelApiKey(extractModel.apiKey())
.verifyModelConfigId(verifyModel.configId())
.verifyModelName(verifyModel.modelName())
.verifyModelUrl(verifyModel.modelUrl())
.verifyModelApiKey(verifyModel.apiKey())
.extractPromptConfigId(extractPrompt.configId())
.extractPrompt(extractPrompt.promptText())
.verifyPromptConfigId(verifyPrompt.configId())
.verifyPrompt(verifyPrompt.promptText())
.taskStatus(TaskStatus.PENDING.name())
.isDeleted(false)
.build();
AnnotationTask task = AnnotationTask.builder().id(IdGenerator.nextId()).companyId(currentUser.companyId())
.creatorId(currentUser.userId()).creatorRole(currentUser.role()).taskName(request.taskName())
.industryType(defaultIndustryType(request.industryType())).taskType(defaultTaskType(request.taskType()))
.extractModelConfigId(extractModel.configId()).extractModelName(extractModel.modelName())
.extractModelUrl(extractModel.modelUrl()).extractModelApiKey(extractModel.apiKey())
.verifyModelConfigId(verifyModel.configId()).verifyModelName(verifyModel.modelName())
.verifyModelUrl(verifyModel.modelUrl()).verifyModelApiKey(verifyModel.apiKey())
.extractPromptConfigId(extractPrompt.configId()).extractPrompt(extractPrompt.promptText())
.verifyPromptConfigId(verifyPrompt.configId()).verifyPrompt(verifyPrompt.promptText())
.taskStatus(TaskStatus.PENDING.name()).isDeleted(false).build();
annotationTaskMapper.insert(task);
saveTaskBindings(task.getId(), currentUser.companyId(), resources);
log.info("created annotation task, companyId={}, userId={}, taskId={}, resourceCount={}",
currentUser.companyId(), currentUser.userId(), task.getId(), resources.size());
currentUser.companyId(), currentUser.userId(), task.getId(), resources.size());
return buildTaskResponse(task, resourceIds(resources), extractModel, verifyModel, extractPrompt, verifyPrompt);
}
@@ -92,42 +88,77 @@ public class AnnotationTaskService {
}
assertTaskPermission(currentUser, task);
List<Long> currentResourceIds = normalizeIds(annotationTaskResourceMapper.listResourceIdsByTaskId(taskId));
List<Long> targetResourceIds = normalizeIds(request.resourceIds());
boolean resourcesChanged = !currentResourceIds.equals(targetResourceIds);
if (TaskStatus.RUNNING.name().equals(task.getTaskStatus()) && resourcesChanged) {
throw new BusinessException(ResultCode.CONFLICT, "运行中的任务不允许修改资源");
boolean resourcesChanged = false;
List<SourceResource> resources = null;
if (request.resourceIds() != null && !request.resourceIds().isEmpty()) {
List<Long> currentResourceIds = normalizeIds(annotationTaskResourceMapper.listResourceIdsByTaskId(taskId));
List<Long> targetResourceIds = normalizeIds(request.resourceIds());
resourcesChanged = !currentResourceIds.equals(targetResourceIds);
if (TaskStatus.RUNNING.name().equals(task.getTaskStatus()) && resourcesChanged) {
throw new BusinessException(ResultCode.CONFLICT, "运行中的任务不允许修改资源");
}
resources = loadAndValidateResources(currentUser, request.resourceIds());
}
List<SourceResource> resources = loadAndValidateResources(currentUser, request.resourceIds());
SysConfigService.ResolvedModelConfig extractModel = sysConfigService.resolveModelConfig(currentUser, request.extractModel());
SysConfigService.ResolvedModelConfig verifyModel = sysConfigService.resolveModelConfig(currentUser, request.verifyModel());
SysConfigService.ResolvedPromptConfig extractPrompt = sysConfigService.resolvePromptConfig(currentUser, request.extractPrompt());
SysConfigService.ResolvedPromptConfig verifyPrompt = sysConfigService.resolvePromptConfig(currentUser, request.verifyPrompt());
SysConfigService.ResolvedModelConfig extractModel = null;
SysConfigService.ResolvedModelConfig verifyModel = null;
SysConfigService.ResolvedPromptConfig extractPrompt = null;
SysConfigService.ResolvedPromptConfig verifyPrompt = null;
if (request.extractModel() != null) {
extractModel = sysConfigService.resolveModelConfig(currentUser, request.extractModel());
task.setExtractModelConfigId(extractModel.configId());
task.setExtractModelName(extractModel.modelName());
task.setExtractModelUrl(extractModel.modelUrl());
task.setExtractModelApiKey(extractModel.apiKey());
}
if (request.verifyModel() != null) {
verifyModel = sysConfigService.resolveModelConfig(currentUser, request.verifyModel());
task.setVerifyModelConfigId(verifyModel.configId());
task.setVerifyModelName(verifyModel.modelName());
task.setVerifyModelUrl(verifyModel.modelUrl());
task.setVerifyModelApiKey(verifyModel.apiKey());
}
if (request.extractPrompt() != null) {
extractPrompt = sysConfigService.resolvePromptConfig(currentUser, request.extractPrompt());
task.setExtractPromptConfigId(extractPrompt.configId());
task.setExtractPrompt(extractPrompt.promptText());
}
if (request.verifyPrompt() != null) {
verifyPrompt = sysConfigService.resolvePromptConfig(currentUser, request.verifyPrompt());
task.setVerifyPromptConfigId(verifyPrompt.configId());
task.setVerifyPrompt(verifyPrompt.promptText());
}
if (request.industryType() != null) {
task.setIndustryType(request.industryType());
}
if (request.taskType() != null) {
task.setTaskType(request.taskType());
}
task.setIndustryType(defaultIndustryType(request.industryType()));
task.setTaskType(defaultTaskType(request.taskType()));
task.setExtractModelConfigId(extractModel.configId());
task.setExtractModelName(extractModel.modelName());
task.setExtractModelUrl(extractModel.modelUrl());
task.setExtractModelApiKey(extractModel.apiKey());
task.setVerifyModelConfigId(verifyModel.configId());
task.setVerifyModelName(verifyModel.modelName());
task.setVerifyModelUrl(verifyModel.modelUrl());
task.setVerifyModelApiKey(verifyModel.apiKey());
task.setExtractPromptConfigId(extractPrompt.configId());
task.setExtractPrompt(extractPrompt.promptText());
task.setVerifyPromptConfigId(verifyPrompt.configId());
task.setVerifyPrompt(verifyPrompt.promptText());
annotationTaskMapper.updateById(task);
if (resourcesChanged) {
if (resourcesChanged && resources != null) {
annotationTaskResourceMapper.deleteByTaskId(taskId);
saveTaskBindings(taskId, currentUser.companyId(), resources);
}
log.info("updated annotation task, companyId={}, userId={}, taskId={}, resourcesChanged={}",
currentUser.companyId(), currentUser.userId(), taskId, resourcesChanged);
return buildTaskResponse(task, resourceIds(resources), extractModel, verifyModel, extractPrompt, verifyPrompt);
currentUser.companyId(), currentUser.userId(), taskId, resourcesChanged);
List<Long> finalResourceIds = resources != null ? resourceIds(resources)
: normalizeIds(annotationTaskResourceMapper.listResourceIdsByTaskId(taskId));
if (extractModel == null || verifyModel == null || extractPrompt == null || verifyPrompt == null) {
return buildTaskResponse(task, finalResourceIds);
}
return buildTaskResponse(task, finalResourceIds, extractModel, verifyModel, extractPrompt, verifyPrompt);
}
public AnnotationTaskResponse getTask(LoginUser currentUser, Long taskId) {
@@ -144,11 +175,11 @@ public class AnnotationTaskService {
boolean shouldFilterByUserId = dataPermissionService.shouldFilterByUserId(currentUser);
LambdaQueryWrapper<AnnotationTask> wrapper = new LambdaQueryWrapper<AnnotationTask>()
.eq(AnnotationTask::getCompanyId, currentUser.companyId())
.eq(StringUtils.hasText(query.taskType()), AnnotationTask::getTaskType, query.taskType())
.eq(StringUtils.hasText(query.taskStatus()), AnnotationTask::getTaskStatus, query.taskStatus())
.eq(query.isDeleted() != null, AnnotationTask::getIsDeleted, query.isDeleted())
.like(StringUtils.hasText(query.keyword()), AnnotationTask::getTaskName, query.keyword());
.eq(AnnotationTask::getCompanyId, currentUser.companyId())
.eq(query.taskType() != null, AnnotationTask::getTaskType, query.taskType())
.eq(StringUtils.hasText(query.taskStatus()), AnnotationTask::getTaskStatus, query.taskStatus())
.eq(query.isDeleted() != null, AnnotationTask::getIsDeleted, query.isDeleted())
.like(StringUtils.hasText(query.keyword()), AnnotationTask::getTaskName, query.keyword());
if (shouldFilterByUserId) {
wrapper.eq(AnnotationTask::getCreatorId, currentUser.userId());
@@ -162,13 +193,15 @@ public class AnnotationTaskService {
Page<AnnotationTask> resultPage = annotationTaskMapper.selectPage(page, wrapper);
List<AnnotationTaskResponse> records = resultPage.getRecords().stream()
.filter(task -> query.resourceId() == null ||
annotationTaskResourceMapper.listResourceIdsByTaskId(task.getId()).contains(query.resourceId()))
.map(task -> buildTaskResponse(task,
normalizeIds(annotationTaskResourceMapper.listResourceIdsByTaskId(task.getId()))))
.toList();
.filter(task -> query.resourceId() == null
|| annotationTaskResourceMapper.listResourceIdsByTaskId(task.getId())
.contains(query.resourceId()))
.map(task -> buildTaskResponse(task,
normalizeIds(annotationTaskResourceMapper.listResourceIdsByTaskId(task.getId()))))
.toList();
return new PageResult<>(records, resultPage.getTotal(), (int) resultPage.getCurrent(), (int) resultPage.getSize());
return new PageResult<>(records, resultPage.getTotal(), (int) resultPage.getCurrent(),
(int) resultPage.getSize());
}
@Transactional
@@ -183,8 +216,8 @@ public class AnnotationTaskService {
}
task.setIsDeleted(true);
annotationTaskMapper.updateById(task);
log.info("deleted annotation task logically, companyId={}, userId={}, taskId={}",
currentUser.companyId(), currentUser.userId(), taskId);
log.info("deleted annotation task logically, companyId={}, userId={}, taskId={}", currentUser.companyId(),
currentUser.userId(), taskId);
}
private List<SourceResource> loadAndValidateResources(LoginUser currentUser, List<Long> resourceIds) {
@@ -192,12 +225,14 @@ public class AnnotationTaskService {
throw new BusinessException(ResultCode.BAD_REQUEST, "任务资源不能为空");
}
List<Long> normalizedIds = normalizeIds(resourceIds);
List<SourceResource> resources = sourceResourceMapper.selectByCompanyIdAndIds(currentUser.companyId(), normalizedIds);
List<SourceResource> resources = sourceResourceMapper.selectByCompanyIdAndIds(currentUser.companyId(),
normalizedIds);
if (resources.size() != normalizedIds.size()) {
throw new BusinessException(ResultCode.BAD_REQUEST, "存在无效资源");
}
for (SourceResource resource : resources) {
if (!dataPermissionService.canAccessCreator(currentUser, resource.getCreatorId(), resource.getCreatorRole())) {
if (!dataPermissionService.canAccessCreator(currentUser, resource.getCreatorId(),
resource.getCreatorRole())) {
throw new BusinessException(ResultCode.FORBIDDEN, "无权访问资源");
}
if (!SourceStatus.READY.name().equals(resource.getSourceStatus())) {
@@ -210,52 +245,39 @@ public class AnnotationTaskService {
private void saveTaskBindings(Long taskId, Long companyId, List<SourceResource> resources) {
for (SourceResource resource : resources) {
annotationTaskResourceMapper.insert(AnnotationTaskResource.builder()
.id(IdGenerator.nextId())
.companyId(companyId)
.taskId(taskId)
.resourceId(resource.getId())
.build());
annotationTaskResourceMapper.insert(AnnotationTaskResource.builder().id(IdGenerator.nextId())
.companyId(companyId).taskId(taskId).resourceId(resource.getId()).build());
}
}
private AnnotationTaskResponse buildTaskResponse(AnnotationTask task,
List<Long> resourceIds,
SysConfigService.ResolvedModelConfig extractModel,
SysConfigService.ResolvedModelConfig verifyModel,
SysConfigService.ResolvedPromptConfig extractPrompt,
SysConfigService.ResolvedPromptConfig verifyPrompt) {
return new AnnotationTaskResponse(
task.getId(),
task.getTaskName(),
task.getIndustryType(),
task.getTaskType(),
task.getTaskStatus(),
resourceIds,
sysConfigService.toResponse(extractModel),
sysConfigService.toResponse(verifyModel),
sysConfigService.toResponse(extractPrompt),
sysConfigService.toResponse(verifyPrompt),
task.getCreatedAt(),
task.getUpdatedAt());
private AnnotationTaskResponse buildTaskResponse(AnnotationTask task, List<Long> resourceIds,
SysConfigService.ResolvedModelConfig extractModel, SysConfigService.ResolvedModelConfig verifyModel,
SysConfigService.ResolvedPromptConfig extractPrompt, SysConfigService.ResolvedPromptConfig verifyPrompt) {
return new AnnotationTaskResponse(task.getId(), task.getTaskName(), task.getIndustryType(), task.getTaskType(),
task.getTaskStatus(), resourceIds, sysConfigService.toResponse(extractModel),
sysConfigService.toResponse(verifyModel), sysConfigService.toResponse(extractPrompt),
sysConfigService.toResponse(verifyPrompt), task.getCreatedAt(), task.getUpdatedAt());
}
private AnnotationTaskResponse buildTaskResponse(AnnotationTask task, List<Long> resourceIds) {
return new AnnotationTaskResponse(
task.getId(),
task.getTaskName(),
task.getIndustryType(),
task.getTaskType(),
task.getTaskStatus(),
resourceIds,
new TaskModelConfigResponse(task.getExtractModelConfigId(), null, task.getExtractModelName(),
task.getExtractModelUrl(), maskSecret(task.getExtractModelApiKey())),
new TaskModelConfigResponse(task.getVerifyModelConfigId(), null, task.getVerifyModelName(),
task.getVerifyModelUrl(), maskSecret(task.getVerifyModelApiKey())),
new TaskPromptConfigResponse(task.getExtractPromptConfigId(), null, task.getExtractPrompt()),
new TaskPromptConfigResponse(task.getVerifyPromptConfigId(), null, task.getVerifyPrompt()),
task.getCreatedAt(),
task.getUpdatedAt());
String extractModelConfigName = resolveConfigName(task.getExtractModelConfigId());
String verifyModelConfigName = resolveConfigName(task.getVerifyModelConfigId());
String extractPromptConfigName = resolveConfigName(task.getExtractPromptConfigId());
String verifyPromptConfigName = resolveConfigName(task.getVerifyPromptConfigId());
return new AnnotationTaskResponse(task.getId(), task.getTaskName(), task.getIndustryType(), task.getTaskType(),
task.getTaskStatus(), resourceIds,
new TaskModelConfigResponse(task.getExtractModelConfigId(), extractModelConfigName,
task.getExtractModelName(),
task.getExtractModelUrl(), maskSecret(task.getExtractModelApiKey())),
new TaskModelConfigResponse(task.getVerifyModelConfigId(), verifyModelConfigName,
task.getVerifyModelName(),
task.getVerifyModelUrl(), maskSecret(task.getVerifyModelApiKey())),
new TaskPromptConfigResponse(task.getExtractPromptConfigId(), extractPromptConfigName,
task.getExtractPrompt()),
new TaskPromptConfigResponse(task.getVerifyPromptConfigId(), verifyPromptConfigName,
task.getVerifyPrompt()),
task.getCreatedAt(), task.getUpdatedAt());
}
private List<Long> resourceIds(List<SourceResource> resources) {
@@ -275,12 +297,12 @@ public class AnnotationTaskService {
}
}
private String defaultIndustryType(String industryType) {
return StringUtils.hasText(industryType) ? industryType : "transport";
private IndustryType defaultIndustryType(IndustryType industryType) {
return industryType != null ? industryType : IndustryType.TRANSPORT;
}
private String defaultTaskType(String taskType) {
return StringUtils.hasText(taskType) ? taskType : "EXTRACT_QA";
private TaskType defaultTaskType(TaskType taskType) {
return taskType != null ? taskType : TaskType.EXTRACT_QA;
}
private String maskSecret(String secret) {
@@ -292,4 +314,12 @@ public class AnnotationTaskService {
}
return "****" + secret.substring(secret.length() - 4);
}
private String resolveConfigName(Long configId) {
if (configId == null) {
return null;
}
SysConfig config = sysConfigMapper.selectById(configId);
return config != null ? config.getConfigName() : null;
}
}

View File

@@ -1,6 +1,14 @@
package com.labelsys.backend.service;
import java.util.List;
import java.util.Map;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.labelsys.backend.common.ResultCode;
@@ -12,21 +20,19 @@ import com.labelsys.backend.dto.request.PromptConfigOptionRequest;
import com.labelsys.backend.dto.request.SaveSysConfigRequest;
import com.labelsys.backend.dto.request.SysConfigPageQuery;
import com.labelsys.backend.dto.request.TaskModelConfigRequest;
import com.labelsys.backend.dto.request.UpdateSysConfigRequest;
import com.labelsys.backend.dto.response.SysConfigResponse;
import com.labelsys.backend.dto.response.TaskModelConfigResponse;
import com.labelsys.backend.dto.response.TaskPromptConfigResponse;
import com.labelsys.backend.entity.SysConfig;
import com.labelsys.backend.enums.ConfigMode;
import com.labelsys.backend.enums.ConfigType;
import com.labelsys.backend.enums.UserRole;
import com.labelsys.backend.mapper.SysConfigMapper;
import com.labelsys.backend.util.IdGenerator;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
@Slf4j
@Service
@@ -35,74 +41,100 @@ public class SysConfigService {
private final SysConfigMapper sysConfigMapper;
private final ObjectMapper objectMapper;
private final DataPermissionService dataPermissionService;
@Transactional
public SysConfig saveConfig(LoginUser currentUser, SaveSysConfigRequest request) {
validateConfigType(request.configType());
SysConfig existing = sysConfigMapper.findByCompanyIdAndConfigName(currentUser.companyId(), request.configName());
SysConfig existing = sysConfigMapper.findByCompanyIdAndConfigName(currentUser.companyId(),
request.configName());
if (existing != null) {
throw new BusinessException(ResultCode.CONFLICT, "配置名称已存在");
}
SysConfig config = SysConfig.builder()
.id(IdGenerator.nextId())
.companyId(currentUser.companyId())
.configType(request.configType())
.configName(request.configName())
.configValue(request.configValue())
.status(request.status())
.creatorId(currentUser.userId())
.build();
SysConfig config = SysConfig.builder().id(IdGenerator.nextId()).companyId(currentUser.companyId())
.configType(request.configType()).configName(request.configName()).configValue(request.configValue())
.status(request.status()).creatorId(currentUser.userId()).creatorRole(currentUser.role().name())
.build();
sysConfigMapper.insert(config);
log.info("saved sys config, companyId={}, userId={}, configName={}, configType={}",
currentUser.companyId(), currentUser.userId(), request.configName(), request.configType());
log.info("saved sys config, companyId={}, userId={}, userRole={}, configName={}, configType={}",
currentUser.companyId(), currentUser.userId(), currentUser.role().name(), request.configName(),
request.configType());
return config;
}
@Transactional
public SysConfig updateConfig(LoginUser currentUser, Long configId, SaveSysConfigRequest request) {
public SysConfig updateConfig(LoginUser currentUser, Long configId, UpdateSysConfigRequest request) {
validateConfigType(request.configType());
SysConfig existing = getConfigEntity(currentUser, configId);
SysConfig duplicate = sysConfigMapper.findByCompanyIdAndConfigName(currentUser.companyId(), request.configName());
if (duplicate != null && !duplicate.getId().equals(configId)) {
throw new BusinessException(ResultCode.CONFLICT, "配置名称已存在");
// SysConfig duplicate =
// sysConfigMapper.findByCompanyIdAndConfigName(currentUser.companyId(),
// request.configName());
// if (duplicate != null && !duplicate.getId().equals(configId)) {
// throw new BusinessException(ResultCode.CONFLICT, "配置名称已存在");
// }
if (StringUtils.hasText(request.configName())) {
existing.setConfigName(request.configName());
}
if (StringUtils.hasText(request.configType())) {
existing.setConfigType(request.configType());
}
if (StringUtils.hasText(request.configValue())) {
existing.setConfigValue(request.configValue());
}
if (StringUtils.hasText(request.status())) {
existing.setStatus(request.status());
}
existing.setConfigType(request.configType());
existing.setConfigName(request.configName());
existing.setConfigValue(request.configValue());
existing.setStatus(request.status());
sysConfigMapper.updateById(existing);
log.info("updated sys config, companyId={}, userId={}, configId={}",
currentUser.companyId(), currentUser.userId(), configId);
log.info("updated sys config, companyId={}, userId={}, configId={}", currentUser.companyId(),
currentUser.userId(), configId);
return existing;
}
public SysConfigResponse getConfig(LoginUser currentUser, Long configId) {
return toResponse(getConfigEntity(currentUser, configId));
SysConfig config = getConfigEntity(currentUser, configId);
if (!dataPermissionService.canAccessCreator(currentUser, config.getCreatorId(),
UserRole.valueOf(config.getCreatorRole()))) {
throw new BusinessException(ResultCode.FORBIDDEN, "无权访问配置");
}
return toResponse(config);
}
public PageResult<SysConfigResponse> pageConfigs(LoginUser currentUser, SysConfigPageQuery query) {
List<String> allowedRoles = dataPermissionService.getAllowedRoles(currentUser);
boolean shouldFilterByUserId = dataPermissionService.shouldFilterByUserId(currentUser);
LambdaQueryWrapper<SysConfig> wrapper = new LambdaQueryWrapper<SysConfig>()
.eq(SysConfig::getCompanyId, currentUser.companyId())
.eq(StringUtils.hasText(query.configType()), SysConfig::getConfigType, query.configType())
.eq(StringUtils.hasText(query.status()), SysConfig::getStatus, query.status())
.like(StringUtils.hasText(query.configName()), SysConfig::getConfigName, query.configName())
.orderByDesc(SysConfig::getCreatedAt);
List<SysConfigResponse> records = sysConfigMapper.selectList(wrapper).stream()
.sorted(Comparator.comparing(SysConfig::getCreatedAt, Comparator.nullsLast(Comparator.naturalOrder())).reversed())
.map(this::toResponse)
.toList();
return paginate(records, query.pageNo(), query.pageSize());
.eq(SysConfig::getCompanyId, currentUser.companyId())
.eq(StringUtils.hasText(query.configType()), SysConfig::getConfigType, query.configType())
.eq(StringUtils.hasText(query.status()), SysConfig::getStatus, query.status())
.like(StringUtils.hasText(query.configName()), SysConfig::getConfigName, query.configName());
if (shouldFilterByUserId) {
wrapper.eq(SysConfig::getCreatorId, currentUser.userId());
} else if (!allowedRoles.isEmpty()) {
wrapper.in(SysConfig::getCreatorRole, allowedRoles);
}
wrapper.orderByDesc(SysConfig::getCreatedAt);
Page<SysConfig> page = new Page<>(query.pageNo(), query.pageSize());
Page<SysConfig> resultPage = sysConfigMapper.selectPage(page, wrapper);
List<SysConfigResponse> records = resultPage.getRecords().stream().map(this::toResponse).toList();
return new PageResult<>(records, resultPage.getTotal(), (int) resultPage.getCurrent(),
(int) resultPage.getSize());
}
@Transactional
public ResolvedModelConfig resolveModelConfig(LoginUser currentUser, TaskModelConfigRequest request) {
if (request == null || !StringUtils.hasText(request.mode())) {
if (request == null || request.mode() == null) {
throw new BusinessException(ResultCode.BAD_REQUEST, "模型配置不能为空");
}
if ("SELECT".equalsIgnoreCase(request.mode())) {
if (request.mode() == ConfigMode.SELECT) {
return resolveSelectedModel(currentUser, request.selectedConfigName());
}
if ("MANUAL".equalsIgnoreCase(request.mode())) {
if (request.mode() == ConfigMode.MANUAL) {
return resolveManualModel(currentUser, request.manualConfig());
}
throw new BusinessException(ResultCode.BAD_REQUEST, "不支持的模型配置模式");
@@ -113,8 +145,8 @@ public class SysConfigService {
throw new BusinessException(ResultCode.BAD_REQUEST, "提示词配置不能为空");
}
if (StringUtils.hasText(request.selectedConfigName())) {
SysConfig config = sysConfigMapper.findByCompanyIdAndConfigNameAndType(
currentUser.companyId(), request.selectedConfigName(), ConfigType.PROMPT.name());
SysConfig config = sysConfigMapper.findByCompanyIdAndConfigNameAndType(currentUser.companyId(),
request.selectedConfigName(), ConfigType.PROMPT.name());
if (config == null) {
throw new BusinessException(ResultCode.NOT_FOUND, "提示词配置不存在");
}
@@ -127,12 +159,8 @@ public class SysConfigService {
}
public TaskModelConfigResponse toResponse(ResolvedModelConfig config) {
return new TaskModelConfigResponse(
config.configId(),
config.configName(),
config.modelName(),
config.modelUrl(),
maskSecret(config.apiKey()));
return new TaskModelConfigResponse(config.configId(), config.configName(), config.modelName(),
config.modelUrl(), maskSecret(config.apiKey()));
}
public TaskPromptConfigResponse toResponse(ResolvedPromptConfig config) {
@@ -140,60 +168,48 @@ public class SysConfigService {
}
public SysConfigResponse toResponse(SysConfig config) {
return new SysConfigResponse(
config.getId(),
config.getConfigType(),
config.getConfigName(),
config.getConfigValue(),
config.getStatus(),
config.getCreatorId(),
config.getCreatedAt(),
config.getUpdatedAt());
return new SysConfigResponse(config.getId(), config.getConfigType(), config.getConfigName(),
config.getConfigValue(), config.getStatus(), config.getCreatorId(), config.getCreatedAt(),
config.getUpdatedAt());
}
private ResolvedModelConfig resolveSelectedModel(LoginUser currentUser, String configName) {
if (!StringUtils.hasText(configName)) {
throw new BusinessException(ResultCode.BAD_REQUEST, "模型配置名称不能为空");
}
SysConfig config = sysConfigMapper.findByCompanyIdAndConfigNameAndType(
currentUser.companyId(), configName, ConfigType.MODEL.name());
SysConfig config = sysConfigMapper.findByCompanyIdAndConfigNameAndType(currentUser.companyId(), configName,
ConfigType.MODEL.name());
if (config == null) {
throw new BusinessException(ResultCode.NOT_FOUND, "模型配置不存在");
}
ModelConfigValue configValue = parseModelConfig(config.getConfigValue());
return new ResolvedModelConfig(config.getId(), config.getConfigName(),
configValue.modelName(), configValue.modelUrl(), configValue.apiKey());
return new ResolvedModelConfig(config.getId(), config.getConfigName(), configValue.modelName(),
configValue.modelUrl(), configValue.apiKey());
}
private ResolvedModelConfig resolveManualModel(LoginUser currentUser, ManualModelConfigRequest request) {
if (request == null || !StringUtils.hasText(request.modelName())
|| !StringUtils.hasText(request.modelUrl()) || !StringUtils.hasText(request.apiKey())) {
if (request == null || !StringUtils.hasText(request.modelName()) || !StringUtils.hasText(request.modelUrl())
|| !StringUtils.hasText(request.apiKey())) {
throw new BusinessException(ResultCode.BAD_REQUEST, "手动模型配置不完整");
}
SysConfig existing = sysConfigMapper.findByCompanyIdAndConfigName(currentUser.companyId(), request.modelName());
if (existing == null) {
String configValue = writeModelConfig(request);
SysConfig config = SysConfig.builder()
.id(IdGenerator.nextId())
.companyId(currentUser.companyId())
.configType(ConfigType.MODEL.name())
.configName(request.modelName())
.configValue(configValue)
.status("ENABLED")
.creatorId(currentUser.userId())
.build();
SysConfig config = SysConfig.builder().id(IdGenerator.nextId()).companyId(currentUser.companyId())
.configType(ConfigType.MODEL.name()).configName(request.modelName()).configValue(configValue)
.status("ENABLED").creatorId(currentUser.userId()).build();
sysConfigMapper.insert(config);
log.info("auto created model config, companyId={}, userId={}, configName={}",
currentUser.companyId(), currentUser.userId(), request.modelName());
return new ResolvedModelConfig(config.getId(), config.getConfigName(),
request.modelName(), request.modelUrl(), request.apiKey());
log.info("auto created model config, companyId={}, userId={}, configName={}", currentUser.companyId(),
currentUser.userId(), request.modelName());
return new ResolvedModelConfig(config.getId(), config.getConfigName(), request.modelName(),
request.modelUrl(), request.apiKey());
}
if (!ConfigType.MODEL.name().equals(existing.getConfigType())) {
throw new BusinessException(ResultCode.CONFLICT, "同名配置已被其他类型占用");
}
ModelConfigValue configValue = parseModelConfig(existing.getConfigValue());
return new ResolvedModelConfig(existing.getId(), existing.getConfigName(),
configValue.modelName(), configValue.modelUrl(), configValue.apiKey());
return new ResolvedModelConfig(existing.getId(), existing.getConfigName(), configValue.modelName(),
configValue.modelUrl(), configValue.apiKey());
}
private SysConfig getConfigEntity(LoginUser currentUser, Long configId) {
@@ -220,10 +236,9 @@ public class SysConfigService {
private String writeModelConfig(ManualModelConfigRequest request) {
try {
return objectMapper.writeValueAsString(Map.of(
"modelName", request.modelName(),
"modelUrl", request.modelUrl(),
"apiKey", request.apiKey()));
return objectMapper.writeValueAsString(
Map.of("modelName", request.modelName(), "modelUrl", request.modelUrl(), "apiKey",
request.apiKey()));
} catch (JsonProcessingException ex) {
throw new BusinessException(ResultCode.BAD_REQUEST, "模型配置值生成失败");
}
@@ -239,18 +254,23 @@ public class SysConfigService {
return "****" + secret.substring(secret.length() - 4);
}
private <T> PageResult<T> paginate(List<T> records, Integer pageNo, Integer pageSize) {
int actualPageNo = pageNo == null || pageNo < 1 ? 1 : pageNo;
int actualPageSize = pageSize == null || pageSize < 1 ? 10 : pageSize;
int fromIndex = Math.min((actualPageNo - 1) * actualPageSize, records.size());
int toIndex = Math.min(fromIndex + actualPageSize, records.size());
return new PageResult<>(records.subList(fromIndex, toIndex), (long) records.size(), actualPageNo, actualPageSize);
}
// private <T> PageResult<T> paginate(List<T> records, Integer pageNo, Integer
// pageSize) {
// int actualPageNo = pageNo == null || pageNo < 1 ? 1 : pageNo;
// int actualPageSize = pageSize == null || pageSize < 1 ? 10 : pageSize;
// int fromIndex = Math.min((actualPageNo - 1) * actualPageSize,
// records.size());
// int toIndex = Math.min(fromIndex + actualPageSize, records.size());
// return new PageResult<>(records.subList(fromIndex, toIndex),
// (long)records.size(), actualPageNo,
// actualPageSize);
// }
private record ModelConfigValue(String modelName, String modelUrl, String apiKey) {
}
public record ResolvedModelConfig(Long configId, String configName, String modelName, String modelUrl, String apiKey) {
public record ResolvedModelConfig(Long configId, String configName, String modelName, String modelUrl,
String apiKey) {
}
public record ResolvedPromptConfig(Long configId, String configName, String promptText) {

View File

@@ -33,26 +33,26 @@ INSERT INTO sys_menu (id, company_id, menu_code, menu_name, path, visible_positi
ON CONFLICT DO NOTHING;
INSERT INTO sys_config (
id, company_id, config_type, config_name, config_value, status, creator_id
id, company_id, config_type, config_name, config_value, status, creator_id, creator_role
) VALUES
(401, 2, 'MODEL', 'qwen-max',
'{"modelUrl":"https://api.example.com/extract","apiKey":"extract-api-key-demo"}', 'ENABLED', 2),
'{"modelUrl":"https://api.example.com/extract","apiKey":"extract-api-key-demo"}', 'ENABLED', 2, 'ENGINEER'),
(402, 2, 'MODEL', 'glm-4.5',
'{"modelUrl":"https://api.example.com/verify","apiKey":"verify-api-key-demo"}', 'ENABLED', 2),
'{"modelUrl":"https://api.example.com/verify","apiKey":"verify-api-key-demo"}', 'ENABLED', 2, 'ENGINEER'),
(403, 2, 'PROMPT', 'extractPrompt',
'请根据输入内容提取结构化问答对。', 'ENABLED', 2),
'请根据输入内容提取结构化问答对。', 'ENABLED', 2,'ENGINEER'),
(404, 2, 'PROMPT', 'verifyPrompt',
'请核验抽取结果是否准确,并给出修正答案。', 'ENABLED', 2),
'请核验抽取结果是否准确,并给出修正答案。', 'ENABLED', 2,'ENGINEER'),
(406, 2, 'MODEL', 'qwen-vl-max',
'{"modelUrl":"https://api.example.com/extract-vl","apiKey":"extract-vl-api-key-demo"}', 'ENABLED', 2),
'{"modelUrl":"https://api.example.com/extract-vl","apiKey":"extract-vl-api-key-demo"}', 'ENABLED', 2,'ENGINEER'),
(407, 2, 'MODEL', 'glm-4.5v',
'{"modelUrl":"https://api.example.com/verify-vl","apiKey":"verify-vl-api-key-demo"}', 'ENABLED', 2),
'{"modelUrl":"https://api.example.com/verify-vl","apiKey":"verify-vl-api-key-demo"}', 'ENABLED', 2,'ENGINEER'),
(408, 2, 'PROMPT', 'imageExtractPrompt',
'请根据输入图片内容提取结构化问答对。', 'ENABLED', 2),
'请根据输入图片内容提取结构化问答对。', 'ENABLED', 2,'ENGINEER'),
(409, 2, 'PROMPT', 'imageVerifyPrompt',
'请核验图片问答结果是否准确。', 'ENABLED', 2),
'请核验图片问答结果是否准确。', 'ENABLED', 2,'ENGINEER'),
(405, 2, 'SYSTEM', 'storageProvider',
'{"provider":"rustfs","defaultBucket":"source-data"}', 'ENABLED', 2)
'{"provider":"rustfs","defaultBucket":"source-data"}', 'ENABLED', 2,'ENGINEER')
ON CONFLICT DO NOTHING;
INSERT INTO source_resource (

View File

@@ -98,6 +98,7 @@ CREATE TABLE IF NOT EXISTS sys_config (
config_value TEXT NOT NULL,
status VARCHAR(32) NOT NULL DEFAULT 'ENABLED',
creator_id BIGINT NOT NULL,
creator_role VARCHAR(50) NOT NULL DEFAULT 'EMPLOYEE',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT uq_sys_config_company_name UNIQUE (company_id, config_name),
@@ -113,6 +114,7 @@ COMMENT ON COLUMN sys_config.config_name IS '配置名称。MODEL 类型时存
COMMENT ON COLUMN sys_config.config_value IS '配置值。MODEL 类型建议保存 JSON至少包含 modelName、modelUrl、apiKeyPROMPT 类型保存提示词文本SYSTEM 类型预留后续扩展。';
COMMENT ON COLUMN sys_config.status IS '配置状态,默认 ENABLED。';
COMMENT ON COLUMN sys_config.creator_id IS '创建人用户ID。';
COMMENT ON COLUMN sys_config.creator_role IS '创建人角色.默认 EMPLOYEE。';
COMMENT ON COLUMN sys_config.created_at IS '创建时间。';
COMMENT ON COLUMN sys_config.updated_at IS '更新时间。';