项目一:意图识别技术与实战(案例:智能政务热线意图分类与工单自动分发系统)
项目原型界面
政务热线智能处理系统 v2.0
==================================================[实时通话监控面板]
通话号码: 138****1234 通话时长: 02:45 状态: ████████ 分析中[实时分析结果]
├── 主要意图: 市政设施报修 (置信度: 92.3%)
├── 次要意图: 进度查询 (置信度: 45.1%)
└── 关键实体:├── 问题类型: 路灯故障├── 详细地址: 人民路与解放大道交叉口└── 紧急程度: 高[自动工单生成]
工单类型: 市政设施维修单
责任部门: 市路灯管理处
紧急级别: ⚡ 紧急 (24小时内处理)
预计处理时间: 2024-01-15 18:00前[操作面板]
1. ✅ 确认生成工单 2. ✏️ 手动调整 3. ❌ 转人工处理
4. 📊 查看类似案例 5. ⚙️ 模型置信度设置输入选择 [1-5]:
关键配置参数
# config/training_config.yaml
training:batch_size: 32learning_rate: 2e-5num_epochs: 50warmup_ratio: 0.1weight_decay: 0.01max_grad_norm: 1.0data:max_seq_length: 256num_intents: 215 # 政务意图类别数num_entities: 25 # 实体类型数intent_threshold: 0.7 # 意图判断阈值model:hidden_size: 768lstm_hidden: 256dropout_rate: 0.3attention_heads: 8deployment:model_path: "/models/intent_detection/v1.0/"api_port: 8001max_concurrent_requests: 1000timeout: 30
核心代码实现
- 深度学习框架PyTorch
import torch #PyTorch深度学习框架
import torch.nn as nn #PyTorch神经网络模块
from transformers import BertModel, BertTokenizer #用于BERT模型
import torch.nn.functional as F #PyTorch的功能型函数,如激活函数class GovernmentIntentModel(nn.Module):"""政企意图识别模型-企业级实现BERT+BILSTM+Attention+CRF架构参数:意图类别数,实体类别数,BERT模型隐藏层大小,LSTM隐藏层大小"""def __init__(self,num_intents,num_entities,hidden_size=768,lstm_hidden=265):super(GovernmentIntentModel, self).__init__()#BERT基础模型-使用中文预训练模型self.bert = BertModel.from_pretrained('hfl/chinese-roberta-wwm-ext')self.bert_dropout = nn.Dropout()#企业级建议的dropout率#BiLSTM层,捕获序列上下文信息self.bilstm = nn.LSTM(input_size = hidden_size,hidden_size = lstm_hidden,num_layers = 2, #两层LSTM,增强表示能力batch_first = True,bidirectional = True,#双向LSTM,前后文信息都考虑dropout = 0.2)#Attention机制,关注关键词语self.attention = nn.MultiheadAttention(embed_dim = lstm_hidden * 2,#双向LSTM,双向拼接num_heads = 8,dropout = 0.1,batch_first = True)#意图分类头,多标签分类self.intent_classifier = nn.Sequential(nn.Linear(lstm_hidden * 2,512),nn.ReLU(),nn.Dropout(0.3),nn.Linear(512,num_intents),nn.Sigmoid()#多标签使用sigmoid激活函数)#实体识别头,BIO标注self.entity_classifier = nn.Sequential(nn.Linear(lstm_hidden * 2,256),nn.ReLU(),nn.Dropout(0.2),nn.Linear(256,num_entities))#CRF层,提高实体识别连贯性self.crf = CRFLayer(num_entities)def forward():"""前向传播:输入文本——>BERT编码——>LiLSTM——>注意力加权——>双任务输出——>意图分类、实体识别Args:input_ids:tokenized inputattention_mask:注意力掩码intent_labels:意图标签(训练时使用)entity_labels:实体标签(训练时使用)"""#BERT编码获取基础表示bert_out = self.bert(input_ids = input_ids,attention_mask = attention_mask,token_type_ids = token_type_ids,return_dict = True)sequence_output = bert_output.last_hidden_state # [batch_size, seq_len, hidden_size]sequenct_output = self.bert_dropout(sequence_output)#BiLSTM处理,捕获序列依赖lstm_out,_ = self.bilstm(sequence_output) # [batch_size, seq_len, lstm_hidden*2]#Attention加权,注意力机制聚焦关键信息attened_out,attention_weights = self.attention(lstm_out,lstm_output,lstm_output,key_padding_mask = ~attention_mask.bool() #转换mask格式)# 意图分类,使用[CLS] token或池化,双任务并行处理intent_logits = self.intent_classifier(attended_output[:, 0, :]) #使用第一个token#实体识别entity_logits = self.entity_classifier(attended_output)#训练时计算损失losses = {}if intent_labels is not None:intent_loss = F.binary_cross_entropy(intent_logits,intent_labels)losses['intent_loss'] = intent_lossif entity_labels is not None:entity_loss = self.crf(entity_logits,entity_labels,attention_mask)losses['entity_loss'] = entity_lossreturn {'intent_logits': intent_logits,'entity_logits': entity_logits,'losses': losses,'attention_weights': attention_weights}class CRFLayer(nn.Module):"""简化版CRF层实现"""def __init__(self,num_tags):super(CRFLayer,self).__init__()self.num_tags = num_tagsself.transitions = nn.Parameter(torck.randn(num_tags,num_tags))def forward(self,logits,tags,mask):#简化实现,实际企业级应用使用完整的CRF实现batch_size,seq_len,num_tags = logits.size()#计算真实路径分数total_score = torch.zeros(batch_size).to(logits.device)#计算所有路径分数all_scores = torch.zero(batch_size).to(logits.device)#返回负对数似然损失loss = (all_scores - total_score).mean()return loss
场景演示:
# 输入: "我想咨询企业注册流程和所需材料"
# 意图输出: [咨询: 0.95, 查询: 0.87]
# 实体输出: [O, O, O, B-业务类型, I-业务类型, O, O, O]
=======================================================
Java开发
技术栈: Java 11、Spring Boot、Spring MVC、Apache OpenNLP/DeepLearning4j(NLP模型)、MySQL(存储意图数据)、Maven、Docker。
- 安装Java 11、Maven、Docker等开发环境。
- 搭建Spring Boot项目骨架,集成Spring MVC、Spring Security等。
- 配置MySQL和Redis连接。
- 收集和标注训练数据。
- 使用Apache OpenNLP或DeepLearning4j训练意图分类模型。
- 评估模型性能,调整参数。
- 实现意图识别服务,加载模型并进行实时预测。
- 开发RESTful API,提供意图识别接口。
- 实现认证和授权机制(OAuth2)。
- 集成日志和监控(如Spring Boot Actuator、ELK栈)。
- 进行单元测试和集成测试,确保功能正确性和性能要求。
- 使用JMeter进行压力测试,优化性能。
- 使用Docker容器化应用。
- 部署到Kubernetes集群,配置自动扩缩容。
- 设置持续集成/持续部署(CI/CD)流程。
组件设计:
API网关层: 使用Spring Cloud Gateway处理请求路由、认证和限流。
意图识别服务: 核心业务逻辑,加载预训练模型进行实时推理。
模型训练模块: 离线运行,使用Java ML库训练和更新意图模型。
数据存储层: MySQL存储意图标签、训练数据和日志;Redis缓存高频意图数据。
数据流:
用户请求 → API网关 → 意图识别服务(模型推理) → 返回意图标签 → 日志存储。
意图识别系统
├── 前端界面 (Thymeleaf + Bootstrap)
├── RESTful API (Spring Boot)
├── 业务逻辑层 (Spring Service)
│ ├── 意图识别服务
│ ├── 模型训练服务
│ └── 数据管理服务
├── 数据访问层 (Spring Data JPA)
├── 机器学习层 (Deeplearning4j)
└── 数据库 (MySQL + Redis)
一、API使用示例
系统访问:
Web界面:http://localhost:8080/intent/web/demo
API文档: http://localhost:8080/intent/swagger-ui.html
健康检查:http://localhost:8080/intent/actuator/health
意图识别
curl -X POST "http://localhost:8080/intent/api/v1/intent/recognize" \-H "Content-Type: application/json" \-d '{"text": "这个产品有什么功能","domain": "default","confidenceThreshold": 0.6}'
批量识别
curl -X POST "http://localhost:8080/intent/api/v1/intent/recognize/batch" \-H "Content-Type: application/json" \-d '{"texts": ["这个产品多少钱","我要退货","如何使用这个功能"],"domain": "default"}'
训练模型
curl -X POST "http://localhost:8080/intent/api/v1/intent/train" \-H "Content-Type: application/json" \-d '{"domain": "default","epochs": 100,"learningRate": 0.01}'
依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.company</groupId><artifactId>intent-recognition</artifactId><version>1.0.0</version><packaging>jar</packaging><name>Intent Recognition System</name><description>Enterprise-level intent recognition system</description><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.0</version><relativePath/></parent><properties><java.version>11</java.version><dl4j.version>1.0.0-M2.1</dl4j.version><nd4j.version>1.0.0-M2.1</nd4j.version></properties><dependencies><!-- Spring Boot Starters --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency><!-- Database --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><!-- Machine Learning --><dependency><groupId>org.deeplearning4j</groupId><artifactId>deeplearning4j-core</artifactId><version>${dl4j.version}</version></dependency><dependency><groupId>org.nd4j</groupId><artifactId>nd4j-native-platform</artifactId><version>${nd4j.version}</version></dependency><dependency><groupId>org.datavec</groupId><artifactId>datavec-api</artifactId><version>${dl4j.version}</version></dependency><!-- NLP Utilities --><dependency><groupId>edu.stanford.nlp</groupId><artifactId>stanford-corenlp</artifactId><version>4.5.0</version></dependency><!-- Utilities --><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId></dependency><dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>31.1-jre</version></dependency><!-- Test --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
</project>
数据传输对象DTO
/*** 意图识别请求DTO*/
@Data
@NoArgsConstructor
public class IntentRequest {@NotBlank(message = "文本内容不能为空")private String text;private String sessionId;private String domain = "default";private Boolean enableCache = true;private Double confidenceThreshold = 0.6;private Map<String, Object> context = new HashMap<>();public IntentRequest(String text) {this.text = text;}public IntentRequest(String text, String domain) {this.text = text;this.domain = domain;}
}/*** 批量意图识别请求DTO*/
@Data
@NoArgsConstructor
public class BatchIntentRequest {@NotBlank(message = "文本列表不能为空")private java.util.List<String> texts;private String domain = "default";private Boolean enableCache = true;public BatchIntentRequest(java.util.List<String> texts) {this.texts = texts;}
}/*** 训练请求DTO*/
@Data
@NoArgsConstructor
public class TrainingRequest {@NotBlank(message = "领域不能为空")private String domain;private String version;private Integer epochs = 100;private Double learningRate = 0.01;private Integer batchSize = 32;private Boolean useExistingData = true;private java.util.List<TrainingExample> additionalExamples;@Data@NoArgsConstructorpublic static class TrainingExample {private String text;private String intentLabel;}
}
/*** 意图识别响应DTO*/
@Data
@NoArgsConstructor
public class IntentResponse {private Boolean success = true;private String text;private String intent;private Double confidence;private List<IntentCandidate> candidates;private Map<String, Object> entities;private String modelVersion;private Long processingTimeMs;private LocalDateTime timestamp;private String errorMessage;@Data@NoArgsConstructorpublic static class IntentCandidate {private String intent;private Double confidence;public IntentCandidate(String intent, Double confidence) {this.intent = intent;this.confidence = confidence;}}public static IntentResponse error(String errorMessage) {IntentResponse response = new IntentResponse();response.setSuccess(false);response.setErrorMessage(errorMessage);response.setTimestamp(LocalDateTime.now());return response;}public IntentResponse(String text, String intent, Double confidence) {this.text = text;this.intent = intent;this.confidence = confidence;this.timestamp = LocalDateTime.now();}
}/*** 批量意图识别响应DTO*/
@Data
@NoArgsConstructor
public class BatchIntentResponse {private Boolean success = true;private List<IntentResponse> results;private Integer totalCount;private Integer successCount;private Long totalProcessingTimeMs;private String errorMessage;public static BatchIntentResponse error(String errorMessage) {BatchIntentResponse response = new BatchIntentResponse();response.setSuccess(false);response.setErrorMessage(errorMessage);return response;}
}/*** 训练响应DTO*/
@Data
@NoArgsConstructor
public class TrainingResponse {private Boolean success = true;private String jobId;private String domain;private String version;private String status;private String message;private LocalDateTime startTime;private LocalDateTime estimatedCompletionTime;private String errorMessage;public static TrainingResponse error(String errorMessage) {TrainingResponse response = new TrainingResponse();response.setSuccess(false);response.setErrorMessage(errorMessage);return response;}
}
模型实体
/*** 模型版本实体 - 管理模型训练版本*/
@Entity
@Table(name = "model_version")
@Data
@NoArgsConstructor
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class ModelVersion {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)@EqualsAndHashCode.Includeprivate Long id;@NotBlank(message = "版本号不能为空")@Column(nullable = false, unique = true, length = 50)private String version;@NotBlank(message = "领域不能为空")@Column(nullable = false, length = 50)private String domain;@Column(length = 500)private String modelPath;@Column(length = 500)private String vectorizerPath;@Column(precision = 5, scale = 4)private Double accuracy;@Column(precision = 5, scale = 4)private Double precision;@Column(precision = 5, scale = 4)private Double recall;@Column(precision = 5, scale = 4)private Double f1Score;@Column(nullable = false)private Integer trainingSize;@Column(nullable = false)private Integer labelCount;@Column(nullable = false)private Boolean isActive = false;@Column(length = 20)private String status; // TRAINING, SUCCESS, FAILED@Column(columnDefinition = "TEXT")private String trainingLog;@CreationTimestamp@Column(updatable = false)private LocalDateTime createdAt;private LocalDateTime completedAt;@Versionprivate Long versionNumber;public ModelVersion(String version, String domain) {this.version = version;this.domain = domain;this.status = "TRAINING";}
}
/*** 意图标签实体 - 管理意图分类体系*/
@Entity
@Table(name = "intent_label", uniqueConstraints = {@UniqueConstraint(columnNames = {"name", "domain"})
})
@Data
@NoArgsConstructor
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class IntentLabel {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)@EqualsAndHashCode.Includeprivate Long id;@NotBlank(message = "标签名称不能为空")@Column(nullable = false, length = 100)private String name;@Column(length = 200)private String description;@NotBlank(message = "领域不能为空")@Column(nullable = false, length = 50)private String domain = "default";@Column(nullable = false)private Boolean enabled = true;@Column(nullable = false)private Integer trainingCount = 0;@Column(length = 20)private String color; // 用于UI显示的颜色@ElementCollection@CollectionTable(name = "intent_label_synonyms", joinColumns = @JoinColumn(name = "intent_label_id"))@Column(name = "synonym")private List<String> synonyms = new ArrayList<>();@CreationTimestamp@Column(updatable = false)private LocalDateTime createdAt;@Versionprivate Long version;public IntentLabel(String name, String domain) {this.name = name;this.domain = domain;}public void incrementTrainingCount() {this.trainingCount = (this.trainingCount == null) ? 1 : this.trainingCount + 1;}
}
/*** 意图数据实体 - 存储训练数据和预测结果*/
@Entity
@Table(name = "intent_data", indexes = {@Index(name = "idx_text", columnList = "text"),@Index(name = "idx_intent_label", columnList = "intentLabel"),@Index(name = "idx_created_at", columnList = "createdAt")
})
@Data
@NoArgsConstructor
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class IntentData {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)@EqualsAndHashCode.Includeprivate Long id;@NotBlank(message = "文本内容不能为空")@Column(columnDefinition = "TEXT", nullable = false)private String text;@NotBlank(message = "意图标签不能为空")@Column(nullable = false, length = 100)private String intentLabel;@Column(precision = 5, scale = 4)private Double confidence;@Column(length = 50)private String source; // 数据来源: MANUAL, IMPORT, API等@Column(length = 50)private String domain; // 领域: CUSTOMER_SERVICE, ECOMMERCE等@Column(nullable = false)private Boolean isTrainingData = true;@ElementCollection@CollectionTable(name = "intent_data_metadata", joinColumns = @JoinColumn(name = "intent_data_id"))@MapKeyColumn(name = "meta_key")@Column(name = "meta_value")private Map<String, String> metadata = new HashMap<>();@CreationTimestamp@Column(updatable = false)private LocalDateTime createdAt;@UpdateTimestampprivate LocalDateTime updatedAt;@Versionprivate Long version;public IntentData(String text, String intentLabel) {this.text = text;this.intentLabel = intentLabel;}public IntentData(String text, String intentLabel, Double confidence) {this.text = text;this.intentLabel = intentLabel;this.confidence = confidence;}
}
application.yml
server:port: 8080servlet:context-path: /intentspring:datasource:url: jdbc:mysql://localhost:3306/intent_recognition?useSSL=false&serverTimezone=UTCusername: rootpassword: passworddriver-class-name: com.mysql.cj.jdbc.Driverjpa:hibernate:ddl-auto: updateshow-sql: trueproperties:hibernate:dialect: org.hibernate.dialect.MySQL8Dialectformat_sql: trueredis:host: localhostport: 6379password: database: 0timeout: 2000mslettuce:pool:max-active: 8max-wait: -1msmax-idle: 8min-idle: 0thymeleaf:cache: falseprefix: classpath:/templates/suffix: .html# Application Configuration
app:model:storage-path: ./models/vectorizer-path: ./models/vectorizer/batch-size: 32epochs: 100learning-rate: 0.01cache:intent-results-ttl: 3600 # 1 hourmodel-ttl: 86400 # 24 hours# Logging
logging:level:com.company.intent: DEBUGorg.springframework.web: INFOorg.hibernate: WARNfile:name: logs/intent-recognition.logpattern:file: "%d{yyyy-MM-dd HH:mm:ss} - %logger{36} - %msg%n"# Management Endpoints
management:endpoints:web:exposure:include: health,info,metrics,prometheusendpoint:health:show-details: always
二、Web界面控制器
/*** 意图识别REST API控制器*/
@RestController
@RequestMapping("/api/v1/intent")
@Validated
@Slf4j
public class IntentRecognitionController {@Autowiredprivate IntentRecognitionService intentRecognitionService;@Autowiredprivate ModelTrainingService modelTrainingService;/**识别单个文本意图*/@PostMapping("/recognize")public ResponseEntity<IntentResponse> recognizeIntent(@Valid @RequestBody IntentRequest request){log.info("Intent recognition request: {}", request.getText());IntentResponse response = intentRecognitionService.recognizeIntent(request.getText(),request.getDomain(),request.getConfidenceThreshold(),request.getEnableCache());return ResponseEntity.ok(response)}/**批量识别意图*/@PostMapping("/recognize/batch")public ResponseEntity<BatchIntentResponse> batchRecognizeIntent(@Valid @RequestBody BatchIntentRequest request){BatchIntentResponse response = intentRecognitionService.batchRecognize(request.getTexts(),request.getDomain(),request.getEnableCache());return ResponseEntity.ok(response);}/**训练新模型*/@PostMapping("/train")public ResponseEntity<TrainingResponse> trainModel(@Valid @RequestBody TrainingRequest request){log.info("Model training request for domain: {}", request.getDomain());try{CompletableFuture<Void> traingFuture = modelTrainingService.trainModelAsync(request.getDomain(),request.getVersion(),request.getEpochs(),request.getLearningRate()).thenAccept(modelVersion -> {log.info("Model training completed: {}", modelVersion.getVersion());});TrainingResponse response = new TrainingResponse();response.setSuccess(true);response.setJobId(trainingFuture.toString()); // 简化实现,实际应该生成唯一IDresponse.setDomain(request.getDomain());response.setVersion(request.getVersion());response.setStatus("STARTED");response.setMessage("模型训练任务已启动");response.setStartTime(java.time.LocalDateTime.now());return ResponseEntity.ok(response);}catch(Exception e){log.error("Failed to start model training", e);return ResponseEntity.badRequest().body(TrainingResponse.error("训练任务启动失败: " + e.getMessage()));}}/*** 健康检查接口*/@GetMapping("/health")public ResponseEntity<HealthResponse> healthCheck() {HealthResponse response = new HealthResponse();response.setStatus("UP");response.setTimestamp(java.time.LocalDateTime.now());response.setVersion("1.0.0");return ResponseEntity.ok(response);}/*** 健康检查响应DTO*/@Data@NoArgsConstructorpublic static class HealthResponse {private String status;private java.time.LocalDateTime timestamp;private String version;private java.util.Map<String, Object> details = new java.util.HashMap<>();}
}
/*** Web界面控制器 - 提供用户友好的Web界面*/
@Controller
@RequestMapping("/web")
@Slf4j
public class WebInterfaceController {@Autowiredprivate IntentRecognitionService intentRecognitionService;@Autowiredprivate IntentDataService intentDataService;@Autowiredprivate IntentLabelService intentLabelService;/**首页-意图识别演示*/@GetMapping({"","/","/demo"})public String demoPage(Model model){model.addAttribute("intentRequest",new IntentRequest());model.addAttribute("recentPredictions",intentDataService.findRecentPredictions(10));return "demo";}/**处理意图识别请求*/@PostMapping("/recognize")public String recognizeIntent(@ModelAttribute IntentRequest request,Model model,RedirectAttributes redirectAttributes){try{IntentResponse response = intentRecognitionService.recognizeIntent(request.getText(),request.getDomain(),request.getConfidenceThreshold(),true);model.addAttribute("intentRequest", request);model.addAttribute("intentResponse", response);model.addAttribute("success", true);}catch(Exception e){log.error("Intent recognition failed in web interface", e);model.addAttribute("error", "识别失败: " + e.getMessage());model.addAttribute("success", false);}model.addAttribute("recentPredictions", intentDataService.findRecentPredictions(10));return "demo";}/**数据管理页面*/@GetMapping("/data")public String dataManagementPage(@RequestParam(defaultValue = "0") int page,@RequestParam(defaultValue = "20") int size,@RequestParam(required = false) String domain,@RequestParam(required = false) Boolean isTrainingData,Model model){Pageable pageable = PageRequest.of(page,size,Sort.by("createdAt").descending());Page<IntentData> dataPage;if(domain != null && isTrainingData != null){dataPage = intentDataService.findByDomainAndIsTrainingData(domain, isTrainingData, pageable);}else if(domain != null){dataPage = intentDataService.findByDomain(domain, pageable);}else if(isTrainingData != null){dataPage = intentDataService.findByIsTrainingData(isTrainingData, pageable);}else{dataPage = intentDataService.findAll(pageable);}List<String> domains = intentDataService.findAllDomains();List<IntentLabel> labels = intentLabelService.findAll();model.addAttribute("dataPage", dataPage);model.addAttribute("domains", domains);model.addAttribute("labels", labels);model.addAttribute("currentDomain", domain);model.addAttribute("currentIsTrainingData", isTrainingData);return "data-management";}/**添加训练数据*/@PostMapping("/data/add")public String addTrainingData(@RequestParam String text,@RequestParam String intentLabel,@RequestParam String domain,RedirectAttributes redirectAttributes){try {IntentData data = new IntentData(text, intentLabel);data.setDomain(domain);data.setIsTrainingData(true);data.setSource("MANUAL");intentDataService.save(data);redirectAttributes.addFlashAttribute("success", "训练数据添加成功");} catch (Exception e) {log.error("Failed to add training data", e);redirectAttributes.addFlashAttribute("error", "添加失败: " + e.getMessage());}return "redirect:/web/data";}/*** 删除数据*/@PostMapping("/data/delete/{id}")public String deleteData(@PathVariable Long id, RedirectAttributes redirectAttributes) {try {intentDataService.deleteById(id);redirectAttributes.addFlashAttribute("success", "数据删除成功");} catch (Exception e) {log.error("Failed to delete data", e);redirectAttributes.addFlashAttribute("error", "删除失败: " + e.getMessage());}return "redirect:/web/data";}/*** 标签管理页面*/@GetMapping("/labels")public String labelManagementPage(Model model) {List<IntentLabel> labels = intentLabelService.findAll();List<String> domains = intentLabelService.findAllDomains();model.addAttribute("labels", labels);model.addAttribute("domains", domains);model.addAttribute("newLabel", new IntentLabel());return "label-management";}/*** 添加新标签*/@PostMapping("/labels/add")public String addLabel(@ModelAttribute IntentLabel label,RedirectAttributes redirectAttributes) {try {intentLabelService.save(label);redirectAttributes.addFlashAttribute("success", "标签添加成功");} catch (Exception e) {log.error("Failed to add label", e);redirectAttributes.addFlashAttribute("error", "添加失败: " + e.getMessage());}return "redirect:/web/labels";}/*** 更新标签状态*/@PostMapping("/labels/toggle/{id}")public String toggleLabel(@PathVariable Long id, RedirectAttributes redirectAttributes) {try {intentLabelService.toggleEnabled(id);redirectAttributes.addFlashAttribute("success", "标签状态更新成功");} catch (Exception e) {log.error("Failed to toggle label", e);redirectAttributes.addFlashAttribute("error", "更新失败: " + e.getMessage());}return "redirect:/web/labels";}/**系统监控页面*/@GetMapping("/monitoring")public String monitoringPage(Model model){//获取系统统计信息long totalDataCount = intentDataService.count();long trainingDataCount = intentDataService.countTrainingData();long labelCount = intentLabelService.count();long domainCount = intentLabelService.countDomains();// 获取各领域数据分布List<Object[]> domainDistribution = intentDataService.getDomainDistribution();List<Object[]> labelDistribution = intentDataService.getLabelDistribution();model.addAttribute("totalDataCount", totalDataCount);model.addAttribute("trainingDataCount", trainingDataCount);model.addAttribute("labelCount", labelCount);model.addAttribute("domainCount", domainCount);model.addAttribute("domainDistribution", domainDistribution);model.addAttribute("labelDistribution", labelDistribution);return "monitoring";}
}
三、Service业务层
文本预处理服务
@Service
@Slf4j
public class TextPreprocessingService{private static final Pattern PATTERN_PUNCTUATION = Pattern.compile("[\\p{P}&&[^#]]");private static final Pattern PATTERN_EXTRA_SPACES = Pattern.compile("\\s+");private static final Pattern PATTERN_NUMBERS = Pattern.compile("\\d+");private static final Pattern PATTERN_SPECIAL_CHARS = Pattern.compile("[^\\w\\s\\u4e00-\\u9fff]");// 停用词列表private static final Set<String> STOP_WORDS = Set.of("的", "了", "在", "是", "我", "有", "和", "就", "不", "人", "都", "一", "一个", "上", "也", "很", "到", "说", "要", "去","你", "会", "着", "没有", "看", "好", "自己", "这", "那", "他", "她", "它", "我们", "你们", "他们", "这个", "那个","the", "a", "an", "and", "or", "but", "in", "on", "at", "to", "for", "of", "with", "by", "as", "is", "are", "was","were", "be", "been", "have", "has", "had", "do", "does", "did", "will", "would", "could", "should", "can", "may","might", "must");/**文本预处理主方法*/public String preprocessText(String text){if(StringUtils.isBlank(text)){return "";}String processed = text;//1.转换为小写processed = processed.toLowerCase();//2.移除特殊字符但保留中文和基本标点processed = PATTERN_SPECIAL_AHARS.matcher(processed).replaceAll(" ");//3.标准化文本,处理重音字符等processed = Normalizer.normalize(processed,Normalizer.Form.NFKC);//4.移除标点符号processed = PATTERN_PUNCTUATION.matcher(processed).replaceAll(" ");// 5. 处理数字(可选:替换为特殊标记或移除)processed = PATTERN_NUMBERS.matcher(processed).replaceAll(" NUM ");// 6. 移除多余空格processed = PATTERN_EXTRA_SPACES.matcher(processed).replaceAll(" ").trim();// 7. 移除停用词processed = removeStopWords(processed);log.debug("Text preprocessing: '{}' -> '{}'", text, processed);return processed;}//移除停用词private String removeStopWords(String text){return Arrays.stream().filter(word -> !STOP_WORDS.contains(word) && word.length() > 1).collect(Collectors.joining(" "));}/**分词处理,简单空格分词,实际项目中可集成中文分词器*/public List<String> tokenize(String text){if(StringUtils.isBlank(text)){return Collections.emptyList();}String processed = preprocessText(text);return Arrays.stream(processed.split("\\s+")).filter(token -> token.length() > 0).collect(Collectors.toList());}/**提取文本特征(词袋模型)*/public Map<String,Integer> extractBagOfWordsFeatures(String text){List<String> tokens = tokenize(text);Map<String,Integer> features = new HashMap<>();for(String token:tokens){features.put(token,features.getOrDefault(token,0) + 1)}return features;}/**计算文本相似度(基于Jaccard相似度)*/public double calculateSimilarity(String text1,String text2){Set<String> tokens1 = new HashSet<>(tokenize(text1));Set<String> tokens2 = new HashSet<>(tokenize(text2));if (tokens1.isEmpty() && tokens2.isEmpty()) {return 1.0;}if (tokens1.isEmpty() || tokens2.isEmpty()) {return 0.0;}Set<String> intersection = new HashSet<>(tokens1);intersection.retainAll(tokens2);Set<String> union = new HashSet<>(tokens1);union.addAll(tokens2);return (double) intersection.size() / union.size();}/*** 文本标准化(用于比较)*/public String normalizeForComparison(String text) {if (StringUtils.isBlank(text)) {return "";}return preprocessText(text).replaceAll("\\s+", " ").trim().toLowerCase();}
}
意图识别服务
@Service
@Slf4j
public class IntentRecognitionService{@Autowiredprivate TextPreprocessingService textPreprocessingService;@Autowriedprivate ModelService modelService;@Autowiredprivate IntentLabelService intentLabelService;@Autowiredprivate IntentDataService intentSataService;@Autowiredprivate RedisTemplate<String,Object> redisTemplate;@Value("${app.cache.intent-results-ttl:3600}")private int cacheTtl;/**识别单个文本的意图*/public IntentResponse recognizeIntent(String text,String domain,Double confidenceThreshold,Boolean enableCache){long startTime = System.currentTimeMillis();try{// 参数验证if (text == null || text.trim().isEmpty()) {return IntentResponse.error("文本内容不能为空");}if (domain == null) {domain = "default";}if (confidenceThreshold == null) {confidenceThreshold = 0.6;}if (enableCache == null) {enableCache = true;}//检查缓存String cacheKey = generateCacheKey(text,domain);if(enableCache){IntentResponse cacheResult = getCacheResult(cacheKey);if(cacheResult != null){cachedResult.setProcessingTimeMs(System.currentTimeMillis() - startTime);log.debug("Cache hit for text: {}", text);return cachedResult;}}//文本预处理String processedText = textPreprocessingService.preprocessText(text);//获取当前活跃模型MultiLayerNetwork model = modelService.getActiveModel(domain);if (model == null) {return IntentResponse.error("领域 '" + domain + "' 没有可用的模型");}//特征提取和向量化INDArray features = extractFeatures(processedText,domain);if(features == null){return IntentResponse.error("特征提取失败");}//模型预测INDArray output = model.output(features);double[] probalilities = output.toDoubleVector();//获取标签列表List<IntentLabel> labels = intentLabelService.getEnabledLabelsByDomain(domain);if (labels.isEmpty()) {return IntentResponse.error("领域 '" + domain + "' 没有可用的意图标签");}// 构建预测结果List<IntentResponse.IntentCandidate> candidates = new ArrayList<>();String predictedIntent = null;Double maxConfidence = 0.0;for (int i = 0; i < probabilities.length && i < labels.size(); i++) {double confidence = probabilities[i];String intent = labels.get(i).getName();candidates.add(new IntentResponse.IntentCandidate(intent, confidence));if (confidence > maxConfidence) {maxConfidence = confidence;predictedIntent = intent;}}// 按置信度排序候选结果candidates.sort((a, b) -> Double.compare(b.getConfidence(), a.getConfidence()));// 构建响应IntentResponse response = new IntentResponse();response.setText(text);response.setIntent(predictedIntent);response.setConfidence(maxConfidence);response.setCandidates(candidates);response.setModelVersion(modelService.getActiveModelVersion(domain));response.setProcessingTimeMs(System.currentTimeMillis() - startTime);response.setTimestamp(LocalDateTime.now());// 检查置信度阈值if (maxConfidence < confidenceThreshold) {response.setIntent("unknown");log.debug("Low confidence prediction: {} (confidence: {})", predictedIntent, maxConfidence);}// 缓存结果if (enableCache) {cacheResult(cacheKey, response);}// 保存预测记录(异步)savePredictionRecord(text, predictedIntent, maxConfidence, domain);log.info("Intent recognition completed: '{}' -> '{}' (confidence: {})", text, predictedIntent, maxConfidence);return response;}catch(Exception e){log.error("Intent recognition failed for text: {}", text, e);return IntentResponse.error("意图识别失败: " + e.getMessage());}}/**批量意图识别*/public BatchIntentResponse batchRecognize(List<String> texts, String domain, Boolean enableCache){long startTime = System.currentTimeMillis();BatchIntentResponse response = new BatchIntentResponse();List<IntentResponse> results = new ArrayList<>();int successCount = 0;for (String text : texts) {IntentResponse intentResponse = recognizeIntent(text, domain, 0.6, enableCache);results.add(intentResponse);if (intentResponse.getSuccess()) {successCount++;}}response.setResults(results);response.setTotalCount(texts.size());response.setSuccessCount(successCount);response.setTotalProcessingTimeMs(System.currentTimeMillis() - startTime);return response;}/*** 提取特征向量*/private INDArray extractFeatures(String text, String domain) {try {// 使用TF-IDF向量化器(实际项目中应该使用训练时相同的向量化器)Map<String, Double> tfidfFeatures = modelService.transformText(text, domain);if (tfidfFeatures == null || tfidfFeatures.isEmpty()) {return null;}// 转换为INDArrayint featureSize = modelService.getFeatureSize(domain);if (featureSize <= 0) {return null;}INDArray features = Nd4j.zeros(1, featureSize);Map<String, Integer> featureIndexMap = modelService.getFeatureIndexMap(domain);for (Map.Entry<String, Double> entry : tfidfFeatures.entrySet()) {Integer index = featureIndexMap.get(entry.getKey());if (index != null && index < featureSize) {features.putScalar(0, index, entry.getValue());}}return features;} catch (Exception e) {log.error("Feature extraction failed for text: {}", text, e);return null;}}/*** 生成缓存键*/private String generateCacheKey(String text, String domain) {String normalizedText = textPreprocessingService.normalizeForComparison(text);return String.format("intent:%s:%s", domain, normalizedText.hashCode());}/*** 从缓存获取结果*/@SuppressWarnings("unchecked")private IntentResponse getCachedResult(String cacheKey) {try {return (IntentResponse) redisTemplate.opsForValue().get(cacheKey);} catch (Exception e) {log.warn("Redis cache access failed for key: {}", cacheKey, e);return null;}}/*** 缓存结果*/private void cacheResult(String cacheKey, IntentResponse response) {try {// 创建副本以避免修改原始对象IntentResponse cachedResponse = new IntentResponse();cachedResponse.setSuccess(response.getSuccess());cachedResponse.setText(response.getText());cachedResponse.setIntent(response.getIntent());cachedResponse.setConfidence(response.getConfidence());cachedResponse.setCandidates(response.getCandidates());cachedResponse.setModelVersion(response.getModelVersion());cachedResponse.setProcessingTimeMs(response.getProcessingTimeMs());cachedResponse.setTimestamp(response.getTimestamp());redisTemplate.opsForValue().set(cacheKey, cachedResponse, cacheTtl, TimeUnit.SECONDS);} catch (Exception e) {log.warn("Redis cache store failed for key: {}", cacheKey, e);}}/*** 保存预测记录*/private void savePredictionRecord(String text, String intent, Double confidence, String domain) {try {IntentData record = new IntentData(text, intent, confidence);record.setIsTrainingData(false);record.setDomain(domain);record.setSource("API_PREDICTION");intentDataService.save(record);} catch (Exception e) {log.warn("Failed to save prediction record for text: {}", text, e);}}
}
模型训练服务
- import org.deeplearning4j.nn.conf.MultiLayerConfiguration;
- import org.deeplearning4j.nn.conf.NeuralNetConfiguration;
- import org.deeplearning4j.nn.conf.layers.DenseLayer;
- import org.deeplearning4j.nn.conf.layers.OutputLayer;
- import org.deeplearning4j.nn.multilayer.MultiLayerNetwork;
- import org.deeplearning4j.nn.weights.WeightInit;
- import org.deeplearning4j.optimize.listeners.ScoreIterationListener;
- import org.nd4j.linalg.activations.Activation;
- import org.nd4j.linalg.dataset.DataSet;
- import org.nd4j.linalg.learning.config.Adam;
- import org.nd4j.linalg.lossfunctions.LossFunctions;
/*** 模型训练服务 - 负责模型训练和更新*/
@Service
@Slf4j
public class ModelTrainingService {@Autowiredprivate IntentDataService intentDataService;@Autowiredprivate IntentLabelService intentLabelService;@Autowiredprivate ModelService modelService;@Autowiredprivate TextVectorizationService textVectorizationService;@Value("${app.model.storage-path:./models/}")private String modelStoragePath;@Value("${app.model.batch-size:32}")private int batchSize;@Value("${app.model.epochs:100}")private int epochs;@Value("${app.model.learning-rate:0.01}")private double learningRate;/*** 异步训练模型*/@Asyncpublic CompletableFuture<ModelVersion> trainModelAsync(String domain, String version, Integer customEpochs, Double customLearningRate) {return CompletableFuture.completedFuture(trainModel(domain, version, customEpochs, customLearningRate));}/*** 训练模型主方法*/public ModelVersion trainModel(String domain, String version, Integer customEpochs, Double customLearningRate) {log.info("Starting model training for domain: {}, version: {}", domain, version);ModelVersion modelVersion = new ModelVersion(version != null ? version : generateVersion(), domain);try {modelVersion = modelService.saveModelVersion(modelVersion);// 1. 准备训练数据List<IntentData> trainingData = intentDataService.getTrainingDataByDomain(domain);if (trainingData.isEmpty()) {throw new IllegalArgumentException("领域 '" + domain + "' 没有训练数据");}List<IntentLabel> labels = intentLabelService.getEnabledLabelsByDomain(domain);if (labels.isEmpty()) {throw new IllegalArgumentException("领域 '" + domain + "' 没有可用的意图标签");}modelVersion.setTrainingSize(trainingData.size());modelVersion.setLabelCount(labels.size());modelService.saveModelVersion(modelVersion);// 2. 文本向量化log.info("Starting text vectorization...");TextVectorizationService.VectorizationResult vectorizationResult = textVectorizationService.vectorizeTrainingData(trainingData, labels, domain);// 3. 构建神经网络模型log.info("Building neural network model...");MultiLayerNetwork model = buildModel(vectorizationResult.getFeatureSize(),labels.size(),customEpochs != null ? customEpochs : epochs,customLearningRate != null ? customLearningRate : learningRate);// 4. 训练模型log.info("Starting model training with {} samples...", trainingData.size());trainModelInternal(model, vectorizationResult, customEpochs != null ? customEpochs : epochs);// 5. 评估模型log.info("Evaluating model...");Map<String, Double> evaluationMetrics = evaluateModel(model, vectorizationResult, labels);modelVersion.setAccuracy(evaluationMetrics.get("accuracy"));modelVersion.setPrecision(evaluationMetrics.get("precision"));modelVersion.setRecall(evaluationMetrics.get("recall"));modelVersion.setF1Score(evaluationMetrics.get("f1Score"));// 6. 保存模型和向量化器log.info("Saving model and vectorizer...");String modelPath = saveModel(model, domain, version);String vectorizerPath = textVectorizationService.saveVectorizer(domain, version);modelVersion.setModelPath(modelPath);modelVersion.setVectorizerPath(vectorizerPath);modelVersion.setStatus("SUCCESS");modelVersion.setCompletedAt(LocalDateTime.now());// 7. 激活新模型modelService.activateModelVersion(domain, modelVersion);log.info("Model training completed successfully for domain: {}, accuracy: {}", domain, evaluationMetrics.get("accuracy"));} catch (Exception e) {log.error("Model training failed for domain: {}", domain, e);modelVersion.setStatus("FAILED");modelVersion.setTrainingLog("Training failed: " + e.getMessage());modelVersion.setCompletedAt(LocalDateTime.now());}return modelService.saveModelVersion(modelVersion);}/*** 构建神经网络模型*/private MultiLayerNetwork buildModel(int inputSize, int outputSize, int epochs, double learningRate) {log.info("Building model with inputSize: {}, outputSize: {}, learningRate: {}", inputSize, outputSize, learningRate);MultiLayerConfiguration config = new NeuralNetConfiguration.Builder().seed(123).weightInit(WeightInit.XAVIER).updater(new Adam(learningRate)).list().layer(new DenseLayer.Builder().nIn(inputSize).nOut(128).activation(Activation.RELU).build()).layer(new DenseLayer.Builder().nIn(128).nOut(64).activation(Activation.RELU).build()).layer(new OutputLayer.Builder(LossFunctions.LossFunction.NEGATIVELOGLIKELIHOOD).nIn(64).nOut(outputSize).activation(Activation.SOFTMAX).build()).build();MultiLayerNetwork model = new MultiLayerNetwork(config);model.init();model.setListeners(new ScoreIterationListener(100));return model;}/*** 内部训练方法*/private void trainModelInternal(MultiLayerNetwork model, TextVectorizationService.VectorizationResult vectorizationResult,int epochs) {List<DataSet> trainingData = vectorizationResult.getTrainingData();for (int epoch = 0; epoch < epochs; epoch++) {double totalScore = 0;int batchCount = 0;// 分批训练for (int i = 0; i < trainingData.size(); i += batchSize) {int end = Math.min(i + batchSize, trainingData.size());List<DataSet> batch = trainingData.subList(i, end);// 合并批次数据DataSet batchDataSet = DataSet.merge(batch);// 训练模型model.fit(batchDataSet);totalScore += model.score();batchCount++;}double averageScore = totalScore / batchCount;if (epoch % 10 == 0) {log.info("Epoch {} completed, average score: {}", epoch, averageScore);}}}/*** 评估模型性能*/private Map<String, Double> evaluateModel(MultiLayerNetwork model, TextVectorizationService.VectorizationResult vectorizationResult,List<IntentLabel> labels) {List<DataSet> testData = vectorizationResult.getTestData();if (testData.isEmpty()) {return createDefaultMetrics();}int correctPredictions = 0;int totalPredictions = 0;Map<String, Integer> truePositives = new HashMap<>();Map<String, Integer> falsePositives = new HashMap<>();Map<String, Integer> falseNegatives = new HashMap<>();// 初始化指标for (IntentLabel label : labels) {truePositives.put(label.getName(), 0);falsePositives.put(label.getName(), 0);falseNegatives.put(label.getName(), 0);}// 计算预测结果for (DataSet dataSet : testData) {INDArray features = dataSet.getFeatures();INDArray labelsArray = dataSet.getLabels();INDArray predictions = model.output(features);int predictedClass = predictions.argMax(1).getInt(0);int actualClass = labelsArray.argMax(1).getInt(0);String predictedLabel = labels.get(predictedClass).getName();String actualLabel = labels.get(actualClass).getName();if (predictedClass == actualClass) {correctPredictions++;truePositives.put(actualLabel, truePositives.get(actualLabel) + 1);} else {falsePositives.put(predictedLabel, falsePositives.get(predictedLabel) + 1);falseNegatives.put(actualLabel, falseNegatives.get(actualLabel) + 1);}totalPredictions++;}// 计算指标double accuracy = (double) correctPredictions / totalPredictions;// 计算宏平均精确率、召回率和F1分数double totalPrecision = 0.0;double totalRecall = 0.0;int labelCount = 0;for (IntentLabel label : labels) {String labelName = label.getName();int tp = truePositives.get(labelName);int fp = falsePositives.get(labelName);int fn = falseNegatives.get(labelName);double precision = (tp + fp) > 0 ? (double) tp / (tp + fp) : 0.0;double recall = (tp + fn) > 0 ? (double) tp / (tp + fn) : 0.0;totalPrecision += precision;totalRecall += recall;labelCount++;}double macroPrecision = totalPrecision / labelCount;double macroRecall = totalRecall / labelCount;double f1Score = (macroPrecision + macroRecall) > 0 ? 2 * macroPrecision * macroRecall / (macroPrecision + macroRecall) : 0.0;Map<String, Double> metrics = new HashMap<>();metrics.put("accuracy", accuracy);metrics.put("precision", macroPrecision);metrics.put("recall", macroRecall);metrics.put("f1Score", f1Score);log.info("Model evaluation - Accuracy: {:.4f}, Precision: {:.4f}, Recall: {:.4f}, F1: {:.4f}", accuracy, macroPrecision, macroRecall, f1Score);return metrics;}/*** 保存模型到文件*/private String saveModel(MultiLayerNetwork model, String domain, String version) {try {String fileName = String.format("intent_model_%s_%s.zip", domain, version);String filePath = modelStoragePath + fileName;File modelFile = new File(filePath);modelFile.getParentFile().mkdirs();model.save(modelFile, true);log.info("Model saved to: {}", filePath);return filePath;} catch (Exception e) {throw new RuntimeException("Failed to save model", e);}}/*** 生成版本号*/private String generateVersion() {return "v" + LocalDateTime.now().format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"));}/*** 创建默认评估指标(当没有测试数据时)*/private Map<String, Double> createDefaultMetrics() {Map<String, Double> metrics = new HashMap<>();metrics.put("accuracy", 0.0);metrics.put("precision", 0.0);metrics.put("recall", 0.0);metrics.put("f1Score", 0.0);return metrics;}
}
四、数据访问层
意图数据仓库
@Repository
public interface IntentDataRepository extends JpaRepository<IntentData, Long>{/*** 根据意图标签查找数据*/List<IntentData> findByIntentLabel(String intentLabel);/*** 根据领域查找数据*/List<IntentData> findByDomain(String domain);/*** 根据是否是训练数据查找*/Page<IntentData> findByIsTrainingData(Boolean isTrainingData, Pageable pageable);/*** 根据领域和是否是训练数据查找*/List<IntentData> findByDomainAndIsTrainingData(String domain, Boolean isTrainingData);/*** 查找相似文本(模糊匹配)*/@Query("SELECT id FROM IntentData id WHERE id.text LIKE %:text%")List<IntentData> findSimilarTexts(@Param("text") String text);/*** 统计各意图标签的数据量*/@Query("SELECT id.intentLabel, COUNT(id) FROM IntentData id WHERE id.isTrainingData = true GROUP BY id.intentLabel")List<Object[]> countByIntentLabel();/*** 根据创建时间范围查找*/List<IntentData> findByCreatedAtBetween(LocalDateTime start, LocalDateTime end);/*** 批量更新数据领域*/@Modifying@Query("UPDATE IntentData SET domain = :domain WHERE id IN :ids")int updateDomainByIds(@Param("domain") String domain, @Param("ids") List<Long> ids);/*** 查找置信度高于阈值的数据*/@Query("SELECT id FROM IntentData id WHERE id.confidence >= :confidenceThreshold")List<IntentData> findByHighConfidence(@Param("confidenceThreshold") Double confidenceThreshold);/*** 检查文本是否已存在*/Optional<IntentData> findByText(String text);
}
意图标签仓库
@Repository
public interface IntentLabelRepository extends JpaRepository<IntentLabel, Long> {/*** 根据名称和领域查找*/Optional<IntentLabel> findByNameAndDomain(String name, String domain);/*** 根据领域查找所有启用的标签*/List<IntentLabel> findByDomainAndEnabledTrue(String domain);/*** 根据领域查找所有标签*/List<IntentLabel> findByDomain(String domain);/*** 检查标签名称在领域中是否存在*/boolean existsByNameAndDomain(String name, String domain);/*** 根据领域统计标签数量*/@Query("SELECT COUNT(il) FROM IntentLabel il WHERE il.domain = :domain AND il.enabled = true")long countEnabledByDomain(@Param("domain") String domain);/*** 批量更新标签启用状态*/@Modifying@Query("UPDATE IntentLabel SET enabled = :enabled WHERE id IN :ids")int updateEnabledStatus(@Param("enabled") Boolean enabled, @Param("ids") List<Long> ids);/*** 查找包含同义词的标签*/@Query("SELECT il FROM IntentLabel il JOIN il.synonyms s WHERE s = :synonym AND il.domain = :domain")List<IntentLabel> findBySynonymAndDomain(@Param("synonym") String synonym, @Param("domain") String domain);
}
启动类
package com.company.intent;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.scheduling.annotation.EnableAsync;/*** 意图识别系统启动类*/
@SpringBootApplication
@EnableCaching
@EnableAsync
public class IntentRecognitionApplication {public static void main(String[] args) {SpringApplication.run(IntentRecognitionApplication.class, args);}
}
部署
Dockerfile
FROM openjdk:11-jre-slim# 安装系统依赖
RUN apt-get update && apt-get install -y \curl \gnupg \&& rm -rf /var/lib/apt/lists/*# 创建应用目录
WORKDIR /app# 创建非root用户
RUN groupadd -r appuser && useradd -r -g appuser appuser# 复制JAR文件
COPY target/intent-recognition-1.0.0.jar app.jar# 创建日志目录
RUN mkdir -p /app/logs && chown -R appuser:appuser /app# 切换用户
USER appuser# 暴露端口
EXPOSE 8080# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \CMD curl -f http://localhost:8080/intent/actuator/health || exit 1# 启动应用
ENTRYPOINT ["java", "-jar", "app.jar"]
docker-compose.yml
version: '3.8'services:intent-recognition:build: .ports:- "8080:8080"environment:- SPRING_PROFILES_ACTIVE=prod- SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/intent_recognition- SPRING_REDIS_HOST=redisdepends_on:- mysql- redisvolumes:- ./models:/app/models- ./logs:/app/logsnetworks:- intent-networkmysql:image: mysql:8.0environment:- MYSQL_ROOT_PASSWORD=rootpassword- MYSQL_DATABASE=intent_recognition- MYSQL_USER=intent_user- MYSQL_PASSWORD=intent_passwordvolumes:- mysql_data:/var/lib/mysqlnetworks:- intent-networkredis:image: redis:7-alpinecommand: redis-server --appendonly yesvolumes:- redis_data:/datanetworks:- intent-networkvolumes:mysql_data:redis_data:networks:intent-network:driver: bridge
# 使用Docker快速启动
# 克隆项目
git clone <repository-url>
cd intent-recognition# 构建项目
mvn clean package# 启动所有服务
docker-compose up -d# 查看日志
docker-compose logs -f intent-recognition
# 手动启动
# 创建数据库
mysql -u root -p -e "CREATE DATABASE intent_recognition;"# 构建项目
mvn clean package# 启动应用
java -jar target/intent-recognition-1.0.0.jar
