refactor(phase1): 完成包名重构 (org.example → com.superbiz.agent)

Task 4.1: 包名统一重构
- 重命名 41 个 Java 文件的包名
- 更新所有 import 语句
- 恢复枚举类(FaultCategory、DiagnosisStatus、SourceType)
- 更新测试类的 import

重构范围:
- domain/entity: 3 个实体类
- domain/model: 2 个数据类
- domain/enums: 3 个枚举类
- repository: 3 个接口
- service/session: 2 个类(接口 + 实现)
- config: 9 个配置类
- controller: 2 个控制器
- agent/tool: 4 个工具类
- client: 1 个客户端
- Main.java: 主类

验证结果:
- 编译成功,无错误
- 所有测试通过 (27/27)
  - ApiDocumentRepositoryTest: 7/7 ✅
  - CaseLibraryRepositoryTest: 6/6 ✅
  - DiagnosisRecordRepositoryTest: 6/6 ✅
  - RedisSessionManagerTest: 8/8 ✅

Progress: 21/33 tasks completed (64%)
This commit is contained in:
zhuyongxin
2026-06-23 14:56:04 +08:00
parent 8bd758dbaf
commit c3a232540a
59 changed files with 103 additions and 104 deletions
@@ -0,0 +1,40 @@
package com.superbiz.agent.config;
import okhttp3.OkHttpClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
import org.springframework.web.client.RestClient;
import java.time.Duration;
/**
* DashScope API 配置
* 用于配置超时时间等参数
*/
@Configuration
public class DashScopeConfig {
@Value("${spring.ai.dashscope.chat.options.timeout:180000}")
private long timeout;
/**
* 配置 RestClient.Builder,设置超时时间
* Spring AI 会自动使用这个 Bean
*/
@Bean
public RestClient.Builder restClientBuilder() {
// 创建自定义的 OkHttpClient,设置超时时间
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.connectTimeout(Duration.ofMillis(timeout))
.readTimeout(Duration.ofMillis(timeout))
.writeTimeout(Duration.ofMillis(timeout))
.callTimeout(Duration.ofMillis(timeout))
.build();
// 创建 RestClient.Builder 并配置 OkHttpClient
return RestClient.builder()
.requestFactory(new OkHttp3ClientHttpRequestFactory(okHttpClient));
}
}
@@ -0,0 +1,52 @@
package com.superbiz.agent.config;
import lombok.Getter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* 文档分片配置
*/
@Getter
@Configuration
@ConfigurationProperties(prefix = "document.chunk")
public class DocumentChunkConfig {
/**
* 每个分片的最大字符数(保留向后兼容)
*/
private int maxSize = 800;
/**
* 分片之间的重叠字符数
*/
private int overlap = 100;
/**
* 每个分片的最大 token 数(中文~1:1,英文~0.25:1)
* 替代 maxSize 作为切割触发器
*/
private int maxTokens = 500;
/**
* 硬上限 token 数 = maxTokens × 1.2
* 仅在不可中断上下文(列表、代码块)内触发
*/
private int maxTokensHard = 600;
public void setMaxSize(int maxSize) {
this.maxSize = maxSize;
}
public void setOverlap(int overlap) {
this.overlap = overlap;
}
public void setMaxTokens(int maxTokens) {
this.maxTokens = maxTokens;
}
public void setMaxTokensHard(int maxTokensHard) {
this.maxTokensHard = maxTokensHard;
}
}
@@ -0,0 +1,22 @@
package com.superbiz.agent.config;
import lombok.Getter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Getter
@Configuration
@ConfigurationProperties(prefix = "file.upload")
public class FileUploadConfig {
private String path;
private String allowedExtensions;
public void setPath(String path) {
this.path = path;
}
public void setAllowedExtensions(String allowedExtensions) {
this.allowedExtensions = allowedExtensions;
}
}
@@ -0,0 +1,51 @@
package com.superbiz.agent.config;
import io.milvus.client.MilvusServiceClient;
import com.superbiz.agent.client.MilvusClientFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import jakarta.annotation.PreDestroy;
/**
* Milvus 配置类
* 负责创建和管理 MilvusServiceClient Bean
*/
@Configuration
public class MilvusConfig {
private static final Logger logger = LoggerFactory.getLogger(MilvusConfig.class);
@Autowired
private MilvusClientFactory milvusClientFactory;
private MilvusServiceClient milvusClient;
/**
* 创建 MilvusServiceClient Bean
*
* @return MilvusServiceClient 实例
*/
@Bean
public MilvusServiceClient milvusServiceClient() {
logger.info("正在初始化 Milvus 客户端...");
milvusClient = milvusClientFactory.createClient();
logger.info("Milvus 客户端初始化完成");
return milvusClient;
}
/**
* 应用关闭时清理资源
*/
@PreDestroy
public void cleanup() {
if (milvusClient != null) {
logger.info("正在关闭 Milvus 客户端连接...");
milvusClient.close();
logger.info("Milvus 客户端连接已关闭");
}
}
}
@@ -0,0 +1,95 @@
package com.superbiz.agent.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties(prefix = "milvus")
public class MilvusProperties {
private String host = "localhost";
private Integer port = 19530;
private String username = "";
private String password = "";
private String database = "default";
private Long timeout = 10000L;
private String token = "";
private boolean secure = false;
private int vectorDim = 1024;
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public Integer getPort() {
return port;
}
public void setPort(Integer port) {
this.port = port;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getDatabase() {
return database;
}
public void setDatabase(String database) {
this.database = database;
}
public Long getTimeout() {
return timeout;
}
public void setTimeout(Long timeout) {
this.timeout = timeout;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public boolean isSecure() {
return secure;
}
public void setSecure(boolean secure) {
this.secure = secure;
}
public int getVectorDim() {
return vectorDim;
}
public void setVectorDim(int vectorDim) {
this.vectorDim = vectorDim;
}
public String getAddress() {
return host + ":" + port;
}
}
@@ -0,0 +1,91 @@
package com.superbiz.agent.config;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
/**
* 模型路由配置 — 由 yml 驱动,不硬编码模型名。
* <p>
* 配置示例:
* <pre>{@code
* model-routing:
* chat: deepseek
* embedding: siliconflow
* }</pre>
* <p>
* 匹配优先级:Bean 名 > 类名(均不区分大小写)。
* 切换模型只改 yml + pom + 对应 api-key,Java 代码不动。
*/
@Configuration
public class ModelRoutingConfig {
private static final Logger log = LoggerFactory.getLogger(ModelRoutingConfig.class);
@Value("${model-routing.chat:deepseek}")
private String chatKeyword;
@Value("${model-routing.embedding:siliconflow}")
private String embeddingKeyword;
@Bean
@Primary
public ChatModel chatModel(List<ChatModel> chatModels) {
log.info("Chat 路由: keyword='{}', 可用: {}", chatKeyword,
chatModels.stream().map(c -> c.getClass().getSimpleName()).toList());
for (ChatModel cm : chatModels) {
if (matches(cm.getClass(), chatKeyword)) {
log.info(" → 选中 {}", cm.getClass().getSimpleName());
return cm;
}
}
log.warn(" → 未匹配, 回退到 {}", chatModels.get(0).getClass().getSimpleName());
return chatModels.get(0);
}
@Bean
@Primary
public EmbeddingModel embeddingModel(Map<String, EmbeddingModel> embeddingBeans) {
log.info("Embedding 路由: keyword='{}', 可用: {}", embeddingKeyword, embeddingBeans.keySet());
// 先按 Bean 名匹配
for (Map.Entry<String, EmbeddingModel> entry : embeddingBeans.entrySet()) {
if (containsIgnoreCase(entry.getKey(), embeddingKeyword)) {
log.info(" → Bean 名匹配: {} → {}", entry.getKey(),
entry.getValue().getClass().getSimpleName());
return entry.getValue();
}
}
// 再按类名匹配
for (EmbeddingModel em : embeddingBeans.values()) {
if (matches(em.getClass(), embeddingKeyword)) {
log.info(" → 类名匹配: {}", em.getClass().getSimpleName());
return em;
}
}
var first = embeddingBeans.values().iterator().next();
log.warn(" → 未匹配, 回退到 {}", first.getClass().getSimpleName());
return first;
}
private boolean matches(Class<?> clazz, String keyword) {
return containsIgnoreCase(clazz.getName(), keyword)
|| containsIgnoreCase(clazz.getSimpleName(), keyword);
}
private boolean containsIgnoreCase(String text, String keyword) {
return text.toLowerCase().contains(keyword.toLowerCase());
}
}
@@ -0,0 +1,55 @@
package com.superbiz.agent.config;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* Redis 会话配置
*/
@Configuration
public class SessionConfiguration {
/**
* 配置 RedisTemplate
* 使用 JSON 序列化存储会话对象
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
// 配置 ObjectMapper 支持 Java 8 时间类型
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
// 启用类型信息,支持多态反序列化
objectMapper.activateDefaultTyping(
LaissezFaireSubTypeValidator.instance,
ObjectMapper.DefaultTyping.NON_FINAL,
JsonTypeInfo.As.PROPERTY
);
// 使用 JSON 序列化器
GenericJackson2JsonRedisSerializer jsonSerializer =
new GenericJackson2JsonRedisSerializer(objectMapper);
// Key 使用 String 序列化
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
// Value 使用 JSON 序列化
template.setValueSerializer(jsonSerializer);
template.setHashValueSerializer(jsonSerializer);
template.afterPropertiesSet();
return template;
}
}
@@ -0,0 +1,54 @@
package com.superbiz.agent.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.document.MetadataMode;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.openai.OpenAiEmbeddingModel;
import org.springframework.ai.openai.OpenAiEmbeddingOptions;
import org.springframework.ai.openai.api.OpenAiApi;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestClient;
import org.springframework.web.reactive.function.client.WebClient;
/**
* SiliconFlow Embedding 配置(BGE-M3, OpenAI 兼容协议, 1024维)
* <p>
* Chat 走 DeepSeek、Embedding 走 SiliconFlow,两者都是 OpenAI 兼容但地址不同,
* 因此单独为 SiliconFlow 创建 OpenAiApi + EmbeddingModel Bean。
*/
@Configuration
public class SiliconFlowEmbeddingConfig {
private static final Logger log = LoggerFactory.getLogger(SiliconFlowEmbeddingConfig.class);
@Value("${siliconflow.api-key}")
private String apiKey;
@Value("${siliconflow.base-url}")
private String baseUrl;
@Value("${siliconflow.embedding.model}")
private String model;
@Bean
public OpenAiApi siliconFlowApi(RestClient.Builder restClientBuilder, WebClient.Builder webClientBuilder) {
log.info("创建 SiliconFlow OpenAiApi: {}", baseUrl);
return OpenAiApi.builder()
.baseUrl(baseUrl)
.apiKey(apiKey)
.restClientBuilder(restClientBuilder)
.build();
}
@Bean
public EmbeddingModel siliconFlowEmbeddingModel(OpenAiApi siliconFlowApi) {
log.info("创建 SiliconFlow EmbeddingModel, model: {}", model);
return new OpenAiEmbeddingModel(siliconFlowApi, MetadataMode.EMBED,
OpenAiEmbeddingOptions.builder()
.model(model)
.build());
}
}
@@ -0,0 +1,38 @@
package com.superbiz.agent.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.nio.charset.StandardCharsets;
import java.util.List;
/**
* Web MVC 配置
* 解决中文乱码问题
*/
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
// 添加 UTF-8 字符串转换器
StringHttpMessageConverter stringConverter = new StringHttpMessageConverter(StandardCharsets.UTF_8);
stringConverter.setWriteAcceptCharset(false); // 不设置 Accept-Charset
converters.add(0, stringConverter);
// 添加 Jackson JSON 转换器,确保 UTF-8 编码
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
jsonConverter.setDefaultCharset(StandardCharsets.UTF_8);
converters.add(1, jsonConverter);
}
@Bean
public ObjectMapper objectMapper() {
return new ObjectMapper();
}
}
@@ -0,0 +1,30 @@
package com.superbiz.agent.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* Web MVC 配置
* 配置跨域和静态资源
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.maxAge(3600);
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 配置静态资源映射
registry.addResourceHandler("/**")
.addResourceLocations("classpath:/static/");
}
}