Merge branch 'dev54'
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -20,6 +20,7 @@ CLAUDE.md
|
||||
*.iws
|
||||
.agents/
|
||||
.history/
|
||||
.trae/
|
||||
logs/
|
||||
|
||||
# ==========================================
|
||||
|
||||
5
pom.xml
5
pom.xml
@@ -84,6 +84,11 @@
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk18on</artifactId>
|
||||
<version>1.78.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>s3</artifactId>
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
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.request.SaveAgentConfigRequest;
|
||||
import com.labelsys.backend.dto.response.AgentConfigListResponse;
|
||||
import com.labelsys.backend.entity.AnnotationAgentConfig;
|
||||
import com.labelsys.backend.enums.UserPosition;
|
||||
import com.labelsys.backend.service.AnnotationAgentConfigService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@Tag(name = "Agent配置管理")
|
||||
@RestController
|
||||
@RequestMapping("/api/agent-configs")
|
||||
@RequiredArgsConstructor
|
||||
public class AnnotationAgentConfigController {
|
||||
|
||||
private final AnnotationAgentConfigService annotationAgentConfigService;
|
||||
|
||||
@Operation(summary = "保存Agent配置")
|
||||
@PostMapping
|
||||
@RequirePosition(UserPosition.ADMIN) // 仅限ADMIN及以上岗位访问
|
||||
public Result<AgentConfigListResponse> save(@Valid @RequestBody SaveAgentConfigRequest request) {
|
||||
return Result.success(
|
||||
annotationAgentConfigService.saveAgentConfigs(UserContext.requireUser(), request));
|
||||
}
|
||||
|
||||
@Operation(summary = "获取公司Agent对应配置列表:模型配置和提示词配置")
|
||||
@GetMapping
|
||||
@RequirePosition(UserPosition.ADMIN) // 仅限ADMIN及以上岗位访问
|
||||
public Result<AgentConfigListResponse> list() {
|
||||
var user = UserContext.requireUser();
|
||||
return Result.success(
|
||||
annotationAgentConfigService.getAgentConfigsForCompany(user.companyId()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,20 @@
|
||||
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.request.UpdateSysConfigRequest;
|
||||
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;
|
||||
@@ -9,21 +24,6 @@ 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")
|
||||
@@ -33,16 +33,16 @@ public class SysConfigController {
|
||||
private final SysConfigService sysConfigService;
|
||||
|
||||
@Operation(summary = "创建系统配置")
|
||||
// @RequirePosition(UserPosition.ADMIN)
|
||||
@PostMapping
|
||||
@RequirePosition(UserPosition.ADMIN) // 仅限ADMIN及以上岗位访问
|
||||
public Result<SysConfigResponse> create(@Valid @RequestBody SaveSysConfigRequest request) {
|
||||
return Result
|
||||
.success(sysConfigService.toResponse(sysConfigService.saveConfig(UserContext.requireUser(), request)));
|
||||
}
|
||||
|
||||
@Operation(summary = "更新系统配置")
|
||||
// @RequirePosition(UserPosition.ADMIN)
|
||||
@PutMapping("/{id}")
|
||||
@RequirePosition(UserPosition.ADMIN) // 仅限ADMIN及以上岗位访问
|
||||
public Result<SysConfigResponse> update(
|
||||
@Parameter(description = "配置ID", example = "191000000000000501") @PathVariable Long id,
|
||||
@Valid @RequestBody UpdateSysConfigRequest request) {
|
||||
@@ -52,14 +52,16 @@ public class SysConfigController {
|
||||
|
||||
@Operation(summary = "分页查询系统配置")
|
||||
@GetMapping
|
||||
@RequirePosition(UserPosition.ADMIN) // 仅限ADMIN及以上岗位访问
|
||||
public Result<PageResult<SysConfigResponse>> page(@ParameterObject SysConfigPageQuery query) {
|
||||
return Result.success(sysConfigService.pageConfigs(UserContext.requireUser(), query));
|
||||
}
|
||||
|
||||
@Operation(summary = "查询系统配置详情")
|
||||
@Operation(summary = "查询配置详情")
|
||||
@GetMapping("/{id}")
|
||||
public Result<SysConfigResponse>
|
||||
detail(@Parameter(description = "配置ID", example = "191000000000000501") @PathVariable Long id) {
|
||||
return Result.success(sysConfigService.getConfig(UserContext.requireUser(), id));
|
||||
@RequirePosition(UserPosition.ADMIN) // 仅限ADMIN及以上岗位访问
|
||||
public Result<SysConfigResponse> detail(
|
||||
@Parameter(description = "配置ID", example = "191000000000000501") @PathVariable Long id) {
|
||||
return Result.success(sysConfigService.getConfigDetail(UserContext.requireUser(), id));
|
||||
}
|
||||
}
|
||||
|
||||
20
src/main/java/com/labelsys/backend/dto/LlmConfigModel.java
Normal file
20
src/main/java/com/labelsys/backend/dto/LlmConfigModel.java
Normal file
@@ -0,0 +1,20 @@
|
||||
package com.labelsys.backend.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "大模型配置模型")
|
||||
@Data
|
||||
public class LlmConfigModel {
|
||||
@Schema(description = "多模态类型枚举")
|
||||
private String llmType;
|
||||
|
||||
@Schema(description = "模型名称")
|
||||
private String modelName;
|
||||
|
||||
@Schema(description = "模型调用地址")
|
||||
private String modelUrl;
|
||||
|
||||
@Schema(description = "API密钥,加密存储")
|
||||
private String apiKey;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.labelsys.backend.dto.request;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Schema(description = "保存Agent配置请求: 请求体中agentConfigs为map结构,key为AgentType枚举值,值为用户选择的模型配置ID和提示词配置ID")
|
||||
@Data
|
||||
public class SaveAgentConfigRequest {
|
||||
|
||||
@Schema(description = "Agent配置映射,key为AgentType枚举类型code,value为配置信息。支持的Agent类型:RedTeamAgent(红黑对抗)、AnalyzerAgent(分析)、IndustryClassifierAgent(行业识别)、HallucinationDetectorAgent(幻觉检测)、ReviewerAgent(审查)、regenerator(再次生成)")
|
||||
@NotNull(message = "Agent配置不能为空")
|
||||
private Map<String, AgentConfigItem> agentConfigs;
|
||||
|
||||
@Schema(description = "Agent配置项")
|
||||
@Data
|
||||
public static class AgentConfigItem {
|
||||
|
||||
@Schema(description = "模型配置ID", example = "191000000000000501")
|
||||
@NotNull(message = "模型配置ID不能为空")
|
||||
private Long modelConfigId;
|
||||
|
||||
@Schema(description = "提示词配置ID", example = "191000000000000502")
|
||||
private Long promptConfigId;
|
||||
}
|
||||
}
|
||||
@@ -5,9 +5,10 @@ import jakarta.validation.constraints.NotBlank;
|
||||
|
||||
@Schema(description = "保存系统配置请求")
|
||||
public record SaveSysConfigRequest(
|
||||
@Schema(description = "配置类型", example = "MODEL") @NotBlank(message = "配置类型不能为空") String configType,
|
||||
@Schema(description = "配置类型: MODEL-大模型配置、PROMPT-提示词配置、SYSTEM-其他配置项", example = "MODEL") @NotBlank(message = "配置类型不能为空") String configType,
|
||||
@Schema(description = "配置名称", example = "qwen-plus-extract") @NotBlank(message = "配置名称不能为空") String configName,
|
||||
@Schema(description = "配置值",
|
||||
@Schema(description = "配置值, 当configType为model时候,configValue为大模型配置的json字符串,configType为其他值时,configValue为普通字符串",
|
||||
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) {}
|
||||
@Schema(description = "配置状态", example = "ENABLED") String status) {
|
||||
}
|
||||
|
||||
@@ -6,9 +6,9 @@ import jakarta.validation.constraints.NotBlank;
|
||||
@Schema(description = "更新系统配置请求")
|
||||
public record UpdateSysConfigRequest(
|
||||
|
||||
@Schema(description = "配置类型", example = "MODEL") String configType,
|
||||
@Schema(description = "配置类型: MODEL-大模型配置、PROMPT-提示词配置、SYSTEM-其他配置项\", example = \"MODEL\"", example = "MODEL") String configType,
|
||||
@Schema(description = "配置名称", example = "qwen-plus-extract") String configName,
|
||||
@Schema(description = "配置值",
|
||||
@Schema(description = "配置值, 当configType为model时候,configValue为大模型配置的json字符串,configType为其他值时,configValue为普通字符串",
|
||||
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) {}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.labelsys.backend.dto.response;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "Agent配置信息响应")
|
||||
@Data
|
||||
@Builder
|
||||
public class AgentConfigInfoResponse {
|
||||
|
||||
@Schema(description = "配置ID")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "Agent类型。支持的Agent类型:RedTeamAgent(红黑对抗)、AnalyzerAgent(分析)、IndustryClassifierAgent(行业识别)、HallucinationDetectorAgent(幻觉检测)、ReviewerAgent(审查)、regenerator(再次生成)")
|
||||
private String agentType;
|
||||
|
||||
@Schema(description = "模型配置信息")
|
||||
private ModelConfigInfo modelConfig;
|
||||
|
||||
@Schema(description = "Prompt配置信息")
|
||||
private PromptConfigInfo promptConfig;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
private java.time.LocalDateTime createdAt;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@Schema(description = "模型配置信息")
|
||||
public static class ModelConfigInfo {
|
||||
@Schema(description = "模型配置ID")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "模型名称")
|
||||
private String modelName;
|
||||
|
||||
@Schema(description = "模型URL")
|
||||
private String modelUrl;
|
||||
|
||||
@Schema(description = "API密钥")
|
||||
private String apiKey;
|
||||
|
||||
@Schema(description = "LLM类型")
|
||||
private String llmType;
|
||||
}
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@Schema(description = "Prompt配置信息")
|
||||
public static class PromptConfigInfo {
|
||||
@Schema(description = "Prompt配置ID")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "Prompt文本")
|
||||
private String promptText;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.labelsys.backend.dto.response;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Schema(description = "Agent配置列表响应:返回结果agentConfigs为公司Agent配置Map,key为AgentType枚举值(固定六个Agent类型key), 值为Agent配置对象")
|
||||
@Data
|
||||
@Builder
|
||||
public class AgentConfigListResponse {
|
||||
|
||||
@Schema(description = "Agent配置映射,key为AgentType枚举类型code,value为Agent配置信息。AgentType类型code:RedTeamAgent(红黑对抗)、AnalyzerAgent(分析)、IndustryClassifierAgent(行业识别)、HallucinationDetectorAgent(幻觉检测)、ReviewerAgent(审查)、regenerator(再次生成)")
|
||||
private Map<String, AgentConfigInfoResponse> agentConfigs;
|
||||
|
||||
@Schema(description = "公司所有Prompt配置列表")
|
||||
private List<AgentConfigInfoResponse.PromptConfigInfo> promptConfigs;
|
||||
|
||||
@Schema(description = "公司所有Model配置列表")
|
||||
private List<AgentConfigInfoResponse.ModelConfigInfo> modelConfigs;
|
||||
}
|
||||
@@ -1,14 +1,17 @@
|
||||
package com.labelsys.backend.dto.response;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "系统配置响应")
|
||||
public record SysConfigResponse(
|
||||
@Schema(description = "配置ID", example = "191000000000000501") Long id,
|
||||
@Schema(description = "配置类型", example = "MODEL") String configType,
|
||||
@Schema(description = "配置类型: MODEL-大模型配置、PROMPT-提示词配置、SYSTEM-其他配置项", 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\"}") String configValue,
|
||||
@Schema(description = "配置值, 当configType为model时候,configValue为大模型配置的json字符串,configType为其他值时,configValue为普通字符串",
|
||||
example = "{\"modelName\":\"qwen-plus\",\"modelUrl\":\"https://dashscope.aliyuncs.com/compatible-mode/v1\",\"apiKey\":\"sk-demo1234\"}") String configValue,
|
||||
@Schema(description = "配置状态", example = "ENABLED") String status,
|
||||
@Schema(description = "创建人ID", example = "191000000000000021") Long creatorId,
|
||||
@Schema(description = "创建时间", example = "2026-04-27T09:50:00") LocalDateTime createdAt,
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
package com.labelsys.backend.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.labelsys.backend.util.SM4Util;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@TableName("annotation_agent_configs")
|
||||
public class AnnotationAgentConfig {
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
@TableField("company_id")
|
||||
private Long companyId; // 所属公司ID,来源于annotation_task.company_id
|
||||
|
||||
@TableField("agent_type")
|
||||
private String agentType; // Agent角色: extract(抽取/Analyzer+Regenerator) / verify(校验/Reviewer) / industry(行业识别) / hallucination(幻觉检测)
|
||||
|
||||
@TableField("model_config_id")
|
||||
private Long modelConfigId; // 模型配置来源ID,外键引用sys_config.id,任务启动时从sys_config(config_type=MODEL, status=ENABLED)获取
|
||||
|
||||
@TableField("model_name")
|
||||
private String modelName; // 模型名称快照,如: qwen-max / glm-4 / claude-3-opus
|
||||
|
||||
@TableField("model_url")
|
||||
private String modelUrl; // 模型调用地址快照
|
||||
|
||||
@TableField("model_api_key")
|
||||
private String modelApiKey; // 模型调用密钥快照(需要加密存储)
|
||||
|
||||
@TableField("prompt_config_id")
|
||||
private Long promptConfigId; // Prompt配置来源ID,外键引用sys_config.id,任务启动时从sys_config(config_type=PROMPT, status=ENABLED)获取
|
||||
|
||||
@TableField("prompt_text")
|
||||
private String promptText; // Prompt文本快照,任务执行期间实际使用的提示词
|
||||
|
||||
@TableField(value = "config_snapshot", typeHandler = JacksonTypeHandler.class)
|
||||
private JsonNode configSnapshot; // 完整配置快照JSON,包含temperature、max_tokens等运行时参数
|
||||
|
||||
@TableField("created_at")
|
||||
private LocalDateTime createdAt; // 快照创建时间
|
||||
|
||||
@TableField("llm_type")
|
||||
private String llmType; // LLM类型,如:text, audio, video, image等,默认为'text'
|
||||
|
||||
// Getter和Setter方法
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Long getCompanyId() {
|
||||
return companyId;
|
||||
}
|
||||
|
||||
public void setCompanyId(Long companyId) {
|
||||
this.companyId = companyId;
|
||||
}
|
||||
|
||||
public String getAgentType() {
|
||||
return agentType;
|
||||
}
|
||||
|
||||
public void setAgentType(String agentType) {
|
||||
this.agentType = agentType;
|
||||
}
|
||||
|
||||
public Long getModelConfigId() {
|
||||
return modelConfigId;
|
||||
}
|
||||
|
||||
public void setModelConfigId(Long modelConfigId) {
|
||||
this.modelConfigId = modelConfigId;
|
||||
}
|
||||
|
||||
public String getModelName() {
|
||||
return modelName;
|
||||
}
|
||||
|
||||
public void setModelName(String modelName) {
|
||||
this.modelName = modelName;
|
||||
}
|
||||
|
||||
public String getModelUrl() {
|
||||
return modelUrl;
|
||||
}
|
||||
|
||||
public void setModelUrl(String modelUrl) {
|
||||
this.modelUrl = modelUrl;
|
||||
}
|
||||
|
||||
public String getModelApiKey() {
|
||||
if (this.modelApiKey != null) {
|
||||
return SM4Util.decryptSafe(this.modelApiKey); // 解密返回
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setModelApiKey(String modelApiKey) {
|
||||
if (modelApiKey != null) {
|
||||
this.modelApiKey = SM4Util.encrypt(modelApiKey); // 加密存储
|
||||
}
|
||||
}
|
||||
|
||||
public Long getPromptConfigId() {
|
||||
return promptConfigId;
|
||||
}
|
||||
|
||||
public void setPromptConfigId(Long promptConfigId) {
|
||||
this.promptConfigId = promptConfigId;
|
||||
}
|
||||
|
||||
public String getPromptText() {
|
||||
return promptText;
|
||||
}
|
||||
|
||||
public void setPromptText(String promptText) {
|
||||
this.promptText = promptText;
|
||||
}
|
||||
|
||||
public JsonNode getConfigSnapshot() {
|
||||
return configSnapshot;
|
||||
}
|
||||
|
||||
public void setConfigSnapshot(JsonNode configSnapshot) {
|
||||
this.configSnapshot = configSnapshot;
|
||||
}
|
||||
|
||||
public LocalDateTime getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public void setCreatedAt(LocalDateTime createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
public String getLlmType() {
|
||||
return llmType;
|
||||
}
|
||||
|
||||
public void setLlmType(String llmType) {
|
||||
this.llmType = llmType;
|
||||
}
|
||||
}
|
||||
43
src/main/java/com/labelsys/backend/enums/AgentType.java
Normal file
43
src/main/java/com/labelsys/backend/enums/AgentType.java
Normal file
@@ -0,0 +1,43 @@
|
||||
package com.labelsys.backend.enums;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
@Schema(description = "Agent类型枚举")
|
||||
public enum AgentType {
|
||||
@Schema(description = "红黑对抗")
|
||||
RED_TEAM("RedTeamAgent"),
|
||||
|
||||
@Schema(description = "分析")
|
||||
ANALYZER("AnalyzerAgent"),
|
||||
|
||||
@Schema(description = "行业识别")
|
||||
INDUSTRY_CLASSIFIER("IndustryClassifierAgent"),
|
||||
|
||||
@Schema(description = "幻觉检测")
|
||||
HALLUCINATION_DETECTOR("HallucinationDetectorAgent"),
|
||||
|
||||
@Schema(description = "审查")
|
||||
REVIEWER("ReviewerAgent"),
|
||||
|
||||
@Schema(description = "再次生成")
|
||||
REGENERATOR("regenerator");
|
||||
|
||||
private final String code;
|
||||
|
||||
AgentType(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public static boolean isValid(String value) {
|
||||
for (AgentType type : AgentType.values()) {
|
||||
if (type.name().equalsIgnoreCase(value) || type.getCode().equals(value)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
37
src/main/java/com/labelsys/backend/enums/MultiModalType.java
Normal file
37
src/main/java/com/labelsys/backend/enums/MultiModalType.java
Normal file
@@ -0,0 +1,37 @@
|
||||
package com.labelsys.backend.enums;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
@Schema(description = "多模态类型枚举")
|
||||
public enum MultiModalType {
|
||||
@Schema(description = "文本")
|
||||
TEXT("text"),
|
||||
|
||||
@Schema(description = "音频")
|
||||
AUDIO("audio"),
|
||||
|
||||
@Schema(description = "视频")
|
||||
VIDEO("video"),
|
||||
|
||||
@Schema(description = "图片")
|
||||
IMAGE("image");
|
||||
|
||||
private final String code;
|
||||
|
||||
MultiModalType(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public static boolean isValid(String value) {
|
||||
for (MultiModalType type : MultiModalType.values()) {
|
||||
if (type.name().equalsIgnoreCase(value) || type.getCode().equals(value)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.labelsys.backend.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.labelsys.backend.entity.AnnotationAgentConfig;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface AnnotationAgentConfigMapper extends BaseMapper<AnnotationAgentConfig> {
|
||||
// 基于BaseMapper,已提供基本的CRUD操作
|
||||
// 如需特殊查询,可在此添加自定义方法
|
||||
}
|
||||
@@ -0,0 +1,299 @@
|
||||
package com.labelsys.backend.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.labelsys.backend.common.ResultCode;
|
||||
import com.labelsys.backend.common.exception.BusinessException;
|
||||
import com.labelsys.backend.context.LoginUser;
|
||||
import com.labelsys.backend.dto.LlmConfigModel;
|
||||
import com.labelsys.backend.dto.request.SaveAgentConfigRequest;
|
||||
import com.labelsys.backend.dto.response.AgentConfigInfoResponse;
|
||||
import com.labelsys.backend.dto.response.AgentConfigListResponse;
|
||||
import com.labelsys.backend.entity.AnnotationAgentConfig;
|
||||
import com.labelsys.backend.entity.SysConfig;
|
||||
import com.labelsys.backend.enums.AgentType;
|
||||
import com.labelsys.backend.mapper.AnnotationAgentConfigMapper;
|
||||
import com.labelsys.backend.util.SM4Util;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@Transactional
|
||||
@RequiredArgsConstructor
|
||||
public class AnnotationAgentConfigService {
|
||||
|
||||
private final AnnotationAgentConfigMapper agentConfigMapper;
|
||||
|
||||
private final SysConfigService sysConfigService;
|
||||
|
||||
// 保存多个Agent配置
|
||||
// 保存多个Agent配置
|
||||
public AgentConfigListResponse saveAgentConfigs(LoginUser user, SaveAgentConfigRequest request) {
|
||||
// 遍历所有Agent配置项
|
||||
for (Map.Entry<String, SaveAgentConfigRequest.AgentConfigItem> entry : request.getAgentConfigs().entrySet()) {
|
||||
String agentType = entry.getKey();
|
||||
SaveAgentConfigRequest.AgentConfigItem configItem = entry.getValue();
|
||||
|
||||
// 验证Agent类型是否有效
|
||||
if (!AgentType.isValid(agentType)) {
|
||||
throw new BusinessException(ResultCode.BAD_REQUEST, "无效的Agent类型: " + agentType);
|
||||
}
|
||||
|
||||
// 验证模型配置是否存在且属于该公司
|
||||
SysConfig modelConfig = sysConfigService.getById(configItem.getModelConfigId());
|
||||
if (modelConfig == null || modelConfig.getCompanyId() == null || !modelConfig.getCompanyId()
|
||||
.equals(user.companyId())) {
|
||||
throw new BusinessException(ResultCode.BAD_REQUEST, "模型配置不存在或不属于当前公司");
|
||||
}
|
||||
|
||||
// 验证提示词配置是否存在且属于该公司(如果提供了的话)
|
||||
if (configItem.getPromptConfigId() != null) {
|
||||
SysConfig promptConfig = sysConfigService.getById(configItem.getPromptConfigId());
|
||||
if (promptConfig == null || promptConfig.getCompanyId() == null || !promptConfig.getCompanyId()
|
||||
.equals(user.companyId())) {
|
||||
throw new BusinessException(ResultCode.BAD_REQUEST, "提示词配置不存在或不属于当前公司");
|
||||
}
|
||||
}
|
||||
|
||||
// 从模型配置中提取API密钥
|
||||
String apiKey = extractApiKeyFromConfig(modelConfig.getConfigValue());
|
||||
|
||||
// 检查是否已存在配置
|
||||
LambdaQueryWrapper<AnnotationAgentConfig> wrapper = Wrappers.<AnnotationAgentConfig>lambdaQuery()
|
||||
.eq(AnnotationAgentConfig::getCompanyId, user.companyId())
|
||||
.eq(AnnotationAgentConfig::getAgentType, agentType);
|
||||
|
||||
AnnotationAgentConfig existing = agentConfigMapper.selectOne(wrapper);
|
||||
if (existing != null) {
|
||||
// 更新现有配置
|
||||
existing.setModelConfigId(configItem.getModelConfigId());
|
||||
existing.setPromptConfigId(configItem.getPromptConfigId());
|
||||
// 从模型配置中获取其他字段
|
||||
existing.setModelName(modelConfig.getConfigName());
|
||||
existing.setModelUrl(extractModelUrlFromConfig(modelConfig.getConfigValue()));
|
||||
existing.setModelApiKey(apiKey); // 保存API密钥
|
||||
existing.setLlmType(extractLlmTypeFromConfig(modelConfig.getConfigValue()));
|
||||
existing.setCreatedAt(LocalDateTime.now());
|
||||
|
||||
agentConfigMapper.updateById(existing);
|
||||
} else {
|
||||
// 创建新配置
|
||||
AnnotationAgentConfig config = AnnotationAgentConfig.builder()
|
||||
.companyId(user.companyId())
|
||||
.agentType(agentType)
|
||||
.modelConfigId(configItem.getModelConfigId())
|
||||
.modelName(modelConfig.getConfigName())
|
||||
.modelUrl(extractModelUrlFromConfig(modelConfig.getConfigValue()))
|
||||
.modelApiKey(apiKey) // 保存API密钥
|
||||
.promptConfigId(configItem.getPromptConfigId())
|
||||
.promptText(getPromptText(configItem.getPromptConfigId()))
|
||||
.llmType(extractLlmTypeFromConfig(modelConfig.getConfigValue()))
|
||||
.createdAt(LocalDateTime.now())
|
||||
.build();
|
||||
|
||||
agentConfigMapper.insert(config);
|
||||
}
|
||||
}
|
||||
|
||||
// 返回更新后的完整配置列表
|
||||
return getAgentConfigsForCompany(user.companyId());
|
||||
}
|
||||
|
||||
// 从模型配置值中提取API密钥
|
||||
private String extractApiKeyFromConfig(String configValue) {
|
||||
try {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
LlmConfigModel llmConfig = objectMapper.readValue(configValue, LlmConfigModel.class);
|
||||
return llmConfig != null ? llmConfig.getApiKey() : null;
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 从模型配置值中提取模型URL
|
||||
private String extractModelUrlFromConfig(String configValue) {
|
||||
try {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
LlmConfigModel llmConfig = objectMapper.readValue(configValue, LlmConfigModel.class);
|
||||
return llmConfig != null ? llmConfig.getModelUrl() : null;
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 从模型配置值中提取LLM类型
|
||||
private String extractLlmTypeFromConfig(String configValue) {
|
||||
try {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
LlmConfigModel llmConfig = objectMapper.readValue(configValue, LlmConfigModel.class);
|
||||
return llmConfig != null ? llmConfig.getLlmType() : null;
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取提示词文本
|
||||
private String getPromptText(Long promptConfigId) {
|
||||
if (promptConfigId == null) {
|
||||
return null;
|
||||
}
|
||||
SysConfig promptConfig = sysConfigService.getById(promptConfigId);
|
||||
return promptConfig != null ? promptConfig.getConfigValue() : null;
|
||||
}
|
||||
|
||||
// 获取特定Agent类型的配置实体
|
||||
public AnnotationAgentConfig getAgentConfigEntity(Long companyId, String agentType) {
|
||||
LambdaQueryWrapper<AnnotationAgentConfig> wrapper = Wrappers.<AnnotationAgentConfig>lambdaQuery()
|
||||
.eq(AnnotationAgentConfig::getCompanyId, companyId)
|
||||
.eq(AnnotationAgentConfig::getAgentType, agentType);
|
||||
|
||||
return agentConfigMapper.selectOne(wrapper);
|
||||
}
|
||||
|
||||
// 获取公司所有Agent配置的完整信息
|
||||
public AgentConfigListResponse getAgentConfigsForCompany(Long companyId) {
|
||||
// 获取所有固定Agent类型
|
||||
Map<String, AgentConfigInfoResponse> agentConfigs = new HashMap<>();
|
||||
|
||||
// 获取该公司所有的Prompt配置
|
||||
List<AgentConfigInfoResponse.PromptConfigInfo> promptConfigs = getCompanyPromptConfigs(companyId);
|
||||
|
||||
// 获取该公司所有的Model配置
|
||||
List<AgentConfigInfoResponse.ModelConfigInfo> modelConfigs = getCompanyModelConfigs(companyId);
|
||||
|
||||
// 遍历所有Agent类型
|
||||
for (AgentType agentType : AgentType.values()) {
|
||||
String agentTypeCode = agentType.getCode();
|
||||
|
||||
// 查询该Agent类型的配置
|
||||
AnnotationAgentConfig agentConfig = getAgentConfigByType(companyId, agentTypeCode);
|
||||
|
||||
if (agentConfig != null) {
|
||||
// 如果存在配置,构建完整配置信息
|
||||
AgentConfigInfoResponse.AgentConfigInfoResponseBuilder builder = AgentConfigInfoResponse.builder()
|
||||
.id(agentConfig.getId())
|
||||
.agentType(agentConfig.getAgentType())
|
||||
.createdAt(agentConfig.getCreatedAt());
|
||||
|
||||
// 设置模型配置信息
|
||||
if (agentConfig.getModelConfigId() != null) {
|
||||
SysConfig modelConfig = sysConfigService.getById(agentConfig.getModelConfigId());
|
||||
if (modelConfig != null) {
|
||||
AgentConfigInfoResponse.ModelConfigInfo modelConfigInfo = AgentConfigInfoResponse.ModelConfigInfo.builder()
|
||||
.id(modelConfig.getId())
|
||||
.modelName(modelConfig.getConfigName())
|
||||
.build();
|
||||
|
||||
// 解析模型配置值
|
||||
try {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
LlmConfigModel llmConfig = objectMapper.readValue(modelConfig.getConfigValue(),
|
||||
LlmConfigModel.class);
|
||||
if (llmConfig != null) {
|
||||
modelConfigInfo.setModelUrl(llmConfig.getModelUrl());
|
||||
modelConfigInfo.setApiKey(SM4Util.decryptSafe(llmConfig.getApiKey()));
|
||||
modelConfigInfo.setLlmType(llmConfig.getLlmType());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 解析失败,忽略
|
||||
}
|
||||
|
||||
builder.modelConfig(modelConfigInfo);
|
||||
}
|
||||
}
|
||||
|
||||
// 设置Prompt配置信息
|
||||
if (agentConfig.getPromptConfigId() != null) {
|
||||
SysConfig promptConfig = sysConfigService.getById(agentConfig.getPromptConfigId());
|
||||
if (promptConfig != null) {
|
||||
AgentConfigInfoResponse.PromptConfigInfo promptConfigInfo = AgentConfigInfoResponse.PromptConfigInfo.builder()
|
||||
.id(promptConfig.getId())
|
||||
.promptText(promptConfig.getConfigValue())
|
||||
.build();
|
||||
builder.promptConfig(promptConfigInfo);
|
||||
}
|
||||
}
|
||||
|
||||
agentConfigs.put(agentTypeCode, builder.build());
|
||||
} else {
|
||||
// 如果没有配置,返回空的配置信息
|
||||
agentConfigs.put(agentTypeCode, AgentConfigInfoResponse.builder()
|
||||
.agentType(agentTypeCode)
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
return AgentConfigListResponse.builder()
|
||||
.agentConfigs(agentConfigs)
|
||||
.promptConfigs(promptConfigs)
|
||||
.modelConfigs(modelConfigs)
|
||||
.build();
|
||||
}
|
||||
|
||||
// 获取公司所有Prompt配置
|
||||
private List<AgentConfigInfoResponse.PromptConfigInfo> getCompanyPromptConfigs(Long companyId) {
|
||||
List<SysConfig> configs = sysConfigService.getCompanyConfigsByType(companyId, "PROMPT");
|
||||
return configs.stream().map(config -> AgentConfigInfoResponse.PromptConfigInfo.builder()
|
||||
.id(config.getId())
|
||||
.promptText(config.getConfigValue())
|
||||
.build()).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
// 获取公司所有Model配置
|
||||
private List<AgentConfigInfoResponse.ModelConfigInfo> getCompanyModelConfigs(Long companyId) {
|
||||
List<SysConfig> configs = sysConfigService.getCompanyConfigsByType(companyId, "MODEL");
|
||||
return configs.stream().map(config -> {
|
||||
AgentConfigInfoResponse.ModelConfigInfo.ModelConfigInfoBuilder builder =
|
||||
AgentConfigInfoResponse.ModelConfigInfo.builder()
|
||||
.id(config.getId())
|
||||
.modelName(config.getConfigName());
|
||||
|
||||
// 解析模型配置值
|
||||
try {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
LlmConfigModel llmConfig = objectMapper.readValue(config.getConfigValue(), LlmConfigModel.class);
|
||||
if (llmConfig != null) {
|
||||
builder.modelUrl(llmConfig.getModelUrl())
|
||||
.apiKey(SM4Util.decryptSafe(llmConfig.getApiKey()))
|
||||
.llmType(llmConfig.getLlmType());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 解析失败,忽略
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
// 根据类型获取Agent配置
|
||||
private AnnotationAgentConfig getAgentConfigByType(Long companyId, String agentType) {
|
||||
LambdaQueryWrapper<AnnotationAgentConfig> wrapper = Wrappers.<AnnotationAgentConfig>lambdaQuery()
|
||||
.eq(AnnotationAgentConfig::getCompanyId, companyId)
|
||||
.eq(AnnotationAgentConfig::getAgentType, agentType);
|
||||
|
||||
return agentConfigMapper.selectOne(wrapper);
|
||||
}
|
||||
|
||||
// private AgentConfigDetailResponse toDetailResponse(AnnotationAgentConfig config) {
|
||||
// return AgentConfigDetailResponse.builder()
|
||||
// .id(config.getId())
|
||||
// .agentType(config.getAgentType())
|
||||
// .llmType(config.getLlmType())
|
||||
// .modelName(config.getModelName())
|
||||
// .modelUrl(config.getModelUrl())
|
||||
// .promptText(config.getPromptText())
|
||||
// .configSnapshot(config.getConfigSnapshot())
|
||||
// .build();
|
||||
// }
|
||||
}
|
||||
@@ -266,7 +266,8 @@ public class AnnotationResultService {
|
||||
List<QaContent.QaRecord> updatedQaRecords = qaContent.records().stream()
|
||||
.map(record -> {
|
||||
String mergedAnswer = request.mergedAnswers().get(record.id());
|
||||
String reviewComment = request.reviewComments() != null ? request.reviewComments().get(record.id()) : null;
|
||||
String reviewComment =
|
||||
request.reviewComments() != null ? request.reviewComments().get(record.id()) : null;
|
||||
if (mergedAnswer != null || reviewComment != null) {
|
||||
return new QaContent.QaRecord(
|
||||
record.id(),
|
||||
|
||||
@@ -2,9 +2,12 @@ package com.labelsys.backend.service;
|
||||
|
||||
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;
|
||||
import com.labelsys.backend.common.exception.BusinessException;
|
||||
import com.labelsys.backend.context.LoginUser;
|
||||
import com.labelsys.backend.dto.LlmConfigModel;
|
||||
import com.labelsys.backend.dto.common.PageResult;
|
||||
import com.labelsys.backend.dto.request.SaveSysConfigRequest;
|
||||
import com.labelsys.backend.dto.request.SysConfigPageQuery;
|
||||
@@ -12,9 +15,9 @@ import com.labelsys.backend.dto.request.UpdateSysConfigRequest;
|
||||
import com.labelsys.backend.dto.response.SysConfigResponse;
|
||||
import com.labelsys.backend.entity.SysConfig;
|
||||
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 com.labelsys.backend.util.SM4Util;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -29,7 +32,7 @@ import java.util.List;
|
||||
public class SysConfigService {
|
||||
|
||||
private final SysConfigMapper sysConfigMapper;
|
||||
private final DataPermissionService dataPermissionService;
|
||||
//private final DataPermissionService dataPermissionService;
|
||||
|
||||
@Transactional
|
||||
public SysConfig saveConfig(LoginUser currentUser, SaveSysConfigRequest request) {
|
||||
@@ -40,12 +43,19 @@ public class SysConfigService {
|
||||
if (existing != null) {
|
||||
throw new BusinessException(ResultCode.CONFLICT, "配置名称已存在");
|
||||
}
|
||||
|
||||
String processedConfigValue = request.configValue();
|
||||
// 如果是model类型,对apiKey进行加密处理
|
||||
if (ConfigType.MODEL.name().equalsIgnoreCase(request.configType())) {
|
||||
processedConfigValue = processModelConfigValue(processedConfigValue, true);
|
||||
}
|
||||
|
||||
SysConfig config = SysConfig.builder()
|
||||
.id(IdGenerator.nextId())
|
||||
.companyId(currentUser.companyId())
|
||||
.configType(request.configType())
|
||||
.configName(request.configName())
|
||||
.configValue(request.configValue())
|
||||
.configValue(processedConfigValue)
|
||||
.status(request.status())
|
||||
.creatorId(currentUser.userId())
|
||||
.creatorRole(currentUser.role().name())
|
||||
@@ -77,8 +87,13 @@ public class SysConfigService {
|
||||
existing.setConfigType(request.configType());
|
||||
}
|
||||
if (StringUtils.hasText(request.configValue())) {
|
||||
// 如果是model类型,对apiKey进行加密处理
|
||||
if (ConfigType.MODEL.name().equalsIgnoreCase(request.configType())) {
|
||||
existing.setConfigValue(processModelConfigValue(request.configValue(), true));
|
||||
} else {
|
||||
existing.setConfigValue(request.configValue());
|
||||
}
|
||||
}
|
||||
if (StringUtils.hasText(request.status())) {
|
||||
existing.setStatus(request.status());
|
||||
}
|
||||
@@ -95,27 +110,10 @@ public class SysConfigService {
|
||||
}
|
||||
}
|
||||
|
||||
public SysConfigResponse getConfig(LoginUser currentUser, Long configId) {
|
||||
try {
|
||||
SysConfig config = getConfigEntity(currentUser, configId);
|
||||
if (!dataPermissionService.canAccessCreator(currentUser, config.getCreatorId(),
|
||||
UserRole.valueOf(config.getCreatorRole()))) {
|
||||
throw new BusinessException(ResultCode.FORBIDDEN, "无权访问配置");
|
||||
}
|
||||
return toResponse(config);
|
||||
} catch (BusinessException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.error("getConfig failed, companyId={}, userId={}, configId={}, error={}",
|
||||
currentUser.companyId(), currentUser.userId(), configId, e.getMessage(), e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public PageResult<SysConfigResponse> pageConfigs(LoginUser currentUser, SysConfigPageQuery query) {
|
||||
try {
|
||||
List<String> allowedRoles = dataPermissionService.getAllowedRoles(currentUser);
|
||||
boolean shouldFilterByUserId = dataPermissionService.shouldFilterByUserId(currentUser);
|
||||
// List<String> allowedRoles = dataPermissionService.getAllowedRoles(currentUser);
|
||||
// boolean shouldFilterByUserId = dataPermissionService.shouldFilterByUserId(currentUser);
|
||||
|
||||
LambdaQueryWrapper<SysConfig> wrapper = new LambdaQueryWrapper<SysConfig>()
|
||||
.eq(SysConfig::getCompanyId, currentUser.companyId())
|
||||
@@ -123,11 +121,11 @@ public class SysConfigService {
|
||||
.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);
|
||||
}
|
||||
// if (shouldFilterByUserId) {
|
||||
// wrapper.eq(SysConfig::getCreatorId, currentUser.userId());
|
||||
// } else if (!allowedRoles.isEmpty()) {
|
||||
// wrapper.in(SysConfig::getCreatorRole, allowedRoles);
|
||||
// }
|
||||
|
||||
wrapper.orderByDesc(SysConfig::getCreatedAt);
|
||||
|
||||
@@ -173,4 +171,84 @@ public class SysConfigService {
|
||||
throw new BusinessException(ResultCode.BAD_REQUEST, "配置类型非法");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理模型配置值,加密或解密API密钥
|
||||
*
|
||||
* @param configValue 配置值
|
||||
* @param encrypt true表示加密,false表示解密
|
||||
* @return 处理后的配置值
|
||||
*/
|
||||
private String processModelConfigValue(String configValue, boolean encrypt) {
|
||||
try {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
LlmConfigModel configModel = objectMapper.readValue(configValue, LlmConfigModel.class);
|
||||
|
||||
if (configModel.getApiKey() != null && !configModel.getApiKey().isEmpty()) {
|
||||
if (encrypt) {
|
||||
// 加密API密钥
|
||||
configModel.setApiKey(SM4Util.encrypt(configModel.getApiKey()));
|
||||
} else {
|
||||
// 解密API密钥
|
||||
configModel.setApiKey(SM4Util.decryptSafe(configModel.getApiKey()));
|
||||
}
|
||||
}
|
||||
|
||||
return objectMapper.writeValueAsString(configModel);
|
||||
} catch (JsonProcessingException e) {
|
||||
log.error("处理模型配置值失败: {}", e.getMessage(), e);
|
||||
throw new BusinessException(ResultCode.BAD_REQUEST, "模型配置格式错误");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID获取配置实体
|
||||
*/
|
||||
public SysConfig getById(Long configId) {
|
||||
return sysConfigMapper.selectById(configId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取配置详情(根据配置ID查询数据库判断类型并返回SysConfigResponse格式)
|
||||
*/
|
||||
public SysConfigResponse getConfigDetail(LoginUser currentUser, Long configId) {
|
||||
SysConfig config = getConfigEntity(currentUser, configId);
|
||||
|
||||
// 如果是模型配置,我们需要在返回前处理API密钥解密
|
||||
if (ConfigType.MODEL.name().equalsIgnoreCase(config.getConfigType())) {
|
||||
if (config.getConfigValue() != null) {
|
||||
try {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
LlmConfigModel model = objectMapper.readValue(config.getConfigValue(), LlmConfigModel.class);
|
||||
if (model != null && model.getApiKey() != null) {
|
||||
// 解密API密钥并更新配置值
|
||||
String decryptedApiKey = SM4Util.decryptSafe(model.getApiKey());
|
||||
model.setApiKey(decryptedApiKey);
|
||||
|
||||
// 将更新后的对象转回JSON字符串,但这次API密钥是解密的
|
||||
String updatedConfigValue = objectMapper.writeValueAsString(model);
|
||||
config.setConfigValue(updatedConfigValue);
|
||||
}
|
||||
} catch (JsonProcessingException e) {
|
||||
log.error("解析模型配置失败: {}", e.getMessage(), e);
|
||||
throw new BusinessException(ResultCode.BAD_REQUEST, "模型配置格式错误");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 总是返回SysConfigResponse对象
|
||||
return toResponse(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取公司特定类型的配置实体列表
|
||||
*/
|
||||
public List<SysConfig> getCompanyConfigsByType(Long companyId, String configType) {
|
||||
LambdaQueryWrapper<SysConfig> wrapper =
|
||||
new LambdaQueryWrapper<SysConfig>()
|
||||
.eq(SysConfig::getCompanyId, companyId)
|
||||
.eq(SysConfig::getConfigType, configType);
|
||||
|
||||
return sysConfigMapper.selectList(wrapper);
|
||||
}
|
||||
}
|
||||
255
src/main/java/com/labelsys/backend/util/SM4Util.java
Normal file
255
src/main/java/com/labelsys/backend/util/SM4Util.java
Normal file
@@ -0,0 +1,255 @@
|
||||
package com.labelsys.backend.util;
|
||||
|
||||
import org.bouncycastle.crypto.engines.SM4Engine;
|
||||
import org.bouncycastle.crypto.modes.CBCBlockCipher;
|
||||
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
|
||||
import org.bouncycastle.crypto.params.KeyParameter;
|
||||
import org.bouncycastle.crypto.params.ParametersWithIV;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.Security;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* SM4 国密加解密工具类
|
||||
* SM4-CBC 模式,密钥/IV 从环境变量读取(hex编码,32字符)
|
||||
*/
|
||||
public class SM4Util {
|
||||
|
||||
private static final String ALGORITHM = "SM4";
|
||||
private static final String TRANSFORMATION = "SM4/CBC/NoPadding";
|
||||
private static final int BLOCK_SIZE = 16;
|
||||
|
||||
private static final String SM4_SECRET_KEY = "a5c9b98d8042ff99a5befedd1bcdc78a";
|
||||
private static final String SM4_IV = "83e3d80962fea60369fb7ebaac9b2285";
|
||||
|
||||
static {
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
}
|
||||
|
||||
// ---- 密钥 / IV ----
|
||||
|
||||
private static byte[] getKey() {
|
||||
//String hex = System.getenv("SM4_SECRET_KEY");
|
||||
if (SM4_SECRET_KEY == null || SM4_SECRET_KEY.isEmpty())
|
||||
throw new SM4CryptoException("环境变量 SM4_SECRET_KEY 未配置");
|
||||
byte[] key = hexToBytes(SM4_SECRET_KEY);
|
||||
if (key.length != 16)
|
||||
throw new SM4CryptoException("SM4 密钥长度必须为16字节,当前为 " + key.length + " 字节");
|
||||
return key;
|
||||
}
|
||||
|
||||
private static byte[] getIV() {
|
||||
//String hex = System.getenv("SM4_IV");
|
||||
if (SM4_IV != null && !SM4_IV.isEmpty()) {
|
||||
byte[] iv = hexToBytes(SM4_IV);
|
||||
if (iv.length != 16)
|
||||
throw new SM4CryptoException("SM4 IV 长度必须为16字节,当前为 " + iv.length + " 字节");
|
||||
return iv;
|
||||
}
|
||||
return new byte[16]; // 默认全零 IV
|
||||
}
|
||||
|
||||
// ---- PKCS7 填充 ----
|
||||
|
||||
private static byte[] pad(byte[] data) {
|
||||
int n = BLOCK_SIZE - (data.length % BLOCK_SIZE);
|
||||
byte[] out = Arrays.copyOf(data, data.length + n);
|
||||
Arrays.fill(out, data.length, out.length, (byte) n);
|
||||
return out;
|
||||
}
|
||||
|
||||
private static byte[] unpad(byte[] data) {
|
||||
if (data.length == 0)
|
||||
throw new SM4CryptoException("数据为空,无法去填充");
|
||||
int n = data[data.length - 1] & 0xFF;
|
||||
if (n < 1 || n > 16)
|
||||
throw new SM4CryptoException("无效的填充长度: " + n);
|
||||
for (int i = data.length - n; i < data.length; i++) {
|
||||
if ((data[i] & 0xFF) != n)
|
||||
throw new SM4CryptoException("填充校验失败");
|
||||
}
|
||||
return Arrays.copyOf(data, data.length - n);
|
||||
}
|
||||
|
||||
// ---- 加解密方法 ----
|
||||
|
||||
/**
|
||||
* SM4-CBC 加密,返回 hex 密文
|
||||
*/
|
||||
public static String encrypt(String plainText, String key, String iv) {
|
||||
try {
|
||||
if (key == null || key.length() != 32) {
|
||||
throw new IllegalArgumentException("SM4密钥长度必须为32位十六进制字符");
|
||||
}
|
||||
if (iv == null || iv.length() != 32) {
|
||||
throw new IllegalArgumentException("SM4 IV长度必须为32位十六进制字符");
|
||||
}
|
||||
|
||||
byte[] keyBytes = hexToBytes(key);
|
||||
byte[] ivBytes = hexToBytes(iv);
|
||||
byte[] plainBytes = plainText.getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
// 手动进行PKCS7填充,与Python版本保持一致
|
||||
byte[] paddedData = pkcs7Pad(plainBytes, 16);
|
||||
|
||||
SM4Engine engine = new SM4Engine();
|
||||
CBCBlockCipher blockCipher = new CBCBlockCipher(engine);
|
||||
PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(blockCipher);
|
||||
|
||||
KeyParameter keyParameter = new KeyParameter(keyBytes);
|
||||
ParametersWithIV parametersWithIV = new ParametersWithIV(keyParameter, ivBytes);
|
||||
cipher.init(true, parametersWithIV);
|
||||
|
||||
byte[] encrypted = new byte[cipher.getOutputSize(paddedData.length)];
|
||||
int len = cipher.processBytes(paddedData, 0, paddedData.length, encrypted, 0);
|
||||
len += cipher.doFinal(encrypted, len);
|
||||
|
||||
byte[] result = new byte[len];
|
||||
System.arraycopy(encrypted, 0, result, 0, len);
|
||||
return bytesToHex(result);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("SM4加密失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SM4-CBC 解密,传入 hex 密文
|
||||
*/
|
||||
public static String decrypt(String cipherText, String key, String iv) {
|
||||
try {
|
||||
if (key == null || key.length() != 32) {
|
||||
throw new IllegalArgumentException("SM4密钥长度必须为32位十六进制字符");
|
||||
}
|
||||
if (iv == null || iv.length() != 32) {
|
||||
throw new IllegalArgumentException("SM4 IV长度必须为32位十六进制字符");
|
||||
}
|
||||
|
||||
byte[] keyBytes = hexToBytes(key);
|
||||
byte[] ivBytes = hexToBytes(iv);
|
||||
byte[] cipherBytes = hexToBytes(cipherText);
|
||||
|
||||
SM4Engine engine = new SM4Engine();
|
||||
CBCBlockCipher blockCipher = new CBCBlockCipher(engine);
|
||||
PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(blockCipher);
|
||||
|
||||
KeyParameter keyParameter = new KeyParameter(keyBytes);
|
||||
ParametersWithIV parametersWithIV = new ParametersWithIV(keyParameter, ivBytes);
|
||||
cipher.init(false, parametersWithIV);
|
||||
|
||||
byte[] decrypted = new byte[cipher.getOutputSize(cipherBytes.length)];
|
||||
int len = cipher.processBytes(cipherBytes, 0, cipherBytes.length, decrypted, 0);
|
||||
len += cipher.doFinal(decrypted, len);
|
||||
|
||||
// 手动去PKCS7填充,与Python版本保持一致
|
||||
byte[] unpadded = pkcs7Unpad(Arrays.copyOf(decrypted, len));
|
||||
|
||||
return new String(unpadded, StandardCharsets.UTF_8);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("SM4解密失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用默认密钥和IV进行加密
|
||||
*/
|
||||
public static String encrypt(String plainText) {
|
||||
return encrypt(plainText, SM4_SECRET_KEY, SM4_IV);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用默认密钥和IV进行解密
|
||||
*/
|
||||
public static String decrypt(String cipherText) {
|
||||
return decrypt(cipherText, SM4_SECRET_KEY, SM4_IV);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解密,非有效密文时原样返回(兼容未加密旧数据)
|
||||
*/
|
||||
public static String decryptSafe(String value) {
|
||||
try {
|
||||
return decrypt(value);
|
||||
} catch (Exception e) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
// ---- PKCS7 填充/去填充方法 ----
|
||||
|
||||
private static byte[] pkcs7Pad(byte[] data, int blockSize) {
|
||||
int paddingLen = blockSize - (data.length % blockSize);
|
||||
byte[] paddedData = new byte[data.length + paddingLen];
|
||||
System.arraycopy(data, 0, paddedData, 0, data.length);
|
||||
for (int i = 0; i < paddingLen; i++) {
|
||||
paddedData[data.length + i] = (byte) paddingLen;
|
||||
}
|
||||
return paddedData;
|
||||
}
|
||||
|
||||
private static byte[] pkcs7Unpad(byte[] data) {
|
||||
if (data == null || data.length == 0) {
|
||||
throw new RuntimeException("数据为空,无法去填充");
|
||||
}
|
||||
int padLen = data[data.length - 1] & 0xFF; // 转换为无符号整数
|
||||
if (padLen < 1 || padLen > 16) {
|
||||
throw new RuntimeException("无效的填充长度: " + padLen);
|
||||
}
|
||||
if (data.length < padLen) {
|
||||
throw new RuntimeException("数据长度小于填充长度");
|
||||
}
|
||||
// 检查填充是否正确
|
||||
for (int i = 0; i < padLen; i++) {
|
||||
if (data[data.length - padLen + i] != (byte) padLen) {
|
||||
throw new RuntimeException("填充校验失败");
|
||||
}
|
||||
}
|
||||
byte[] result = new byte[data.length - padLen];
|
||||
System.arraycopy(data, 0, result, 0, data.length - padLen);
|
||||
return result;
|
||||
}
|
||||
|
||||
// ---- Hex 工具 ----
|
||||
|
||||
private static String bytesToHex(byte[] b) {
|
||||
StringBuilder sb = new StringBuilder(b.length * 2);
|
||||
for (byte v : b)
|
||||
sb.append(String.format("%02x", v & 0xFF));
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static byte[] hexToBytes(String hex) {
|
||||
byte[] out = new byte[hex.length() / 2];
|
||||
for (int i = 0; i < out.length; i++)
|
||||
out[i] = (byte) ((Character.digit(hex.charAt(2 * i), 16) << 4)
|
||||
+ Character.digit(hex.charAt(2 * i + 1), 16));
|
||||
return out;
|
||||
}
|
||||
|
||||
// ---- 异常 ----
|
||||
|
||||
public static class SM4CryptoException extends RuntimeException {
|
||||
public SM4CryptoException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
public SM4CryptoException(String msg, Throwable cause) {
|
||||
super(msg, cause);
|
||||
}
|
||||
}
|
||||
|
||||
static void main() {
|
||||
String key = "a5c9b98d8042ff99a5befedd1bcdc78a";
|
||||
String iv = "83e3d80962fea60369fb7ebaac9b2285";
|
||||
|
||||
String plainText = "extract-api-key-demo";
|
||||
|
||||
String encrypted = SM4Util.encrypt(plainText);
|
||||
String decrypted = SM4Util.decrypt(encrypted);
|
||||
|
||||
System.out.println("原文: " + plainText);
|
||||
System.out.println("密文: " + encrypted);
|
||||
System.out.println("解密: " + decrypted);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user