Files
ticket-workbench/backend/internal/service/analysis_service.go
绝尘 e94f160782 修复: 代码审查4个必改项+4个建议改进
- 工单编号改为业务格式 TK-yyMMdd-NNN
- 类型断言加 comma-ok 防 panic
- priority 用指针区分未传/P0
- json.Marshal 错误处理
- 提取 ParseID 公共函数消除重复
- HTTP client 包级别复用
- LIKE 查询特殊字符转义
- interface{} → any
- auth 中间件用 dto.Fail 统一响应
2026-05-13 19:01:06 +08:00

162 lines
4.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package service
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"time"
"github.com/casehub/ticket-workbench/internal/model"
"gorm.io/gorm"
)
var httpClient = &http.Client{Timeout: 30 * time.Second}
type GLMRequest struct {
Model string `json:"model"`
Messages []Message `json:"messages"`
}
type Message struct {
Role string `json:"role"`
Content string `json:"content"`
}
type GLMResponse struct {
Choices []Choice `json:"choices"`
}
type Choice struct {
Message Message `json:"message"`
}
type AnalysisResult struct {
Category string `json:"category"`
Priority json.Number `json:"priority"`
Summary string `json:"summary"`
SuggestRole string `json:"suggest_role"`
}
func AnalyzeTicket(db *gorm.DB, ticketid int, apikey, baseURL, glmModel, title, content, contactname, contactphone string) (*model.TicketAiAnalysis, error) {
prompt := fmt.Sprintf(`你是一个客服工单分析助手。请分析以下工单内容返回JSON格式的分析结果
{
"category": "分类(refund/login/invoice/logistics/account/inquiry/other)",
"priority": "优先级(0=P0紧急,1=P1高,2=P2中,3=P3低)",
"summary": "一句话摘要",
"suggest_role": "建议处理团队(refund_team/tech_support/finance_team/logistics_team/customer_service)"
}
工单标题: %s
工单内容: %s
联系人: %s
联系电话: %s
请只返回JSON不要其他内容。`, title, content, contactname, contactphone)
reqBody := GLMRequest{
Model: glmModel,
Messages: []Message{
{Role: "system", Content: "你是一个专业的客服工单分析助手,擅长对工单进行分类、优先级判断和智能分派。"},
{Role: "user", Content: prompt},
},
}
jsonData, err := json.Marshal(reqBody)
if err != nil {
return nil, fmt.Errorf("构建请求失败: %w", err)
}
httpReq, err := http.NewRequest("POST", baseURL+"/chat/completions", bytes.NewBuffer(jsonData))
if err != nil {
return nil, err
}
httpReq.Header.Set("Content-Type", "application/json")
httpReq.Header.Set("Authorization", "Bearer "+apikey)
resp, err := httpClient.Do(httpReq)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
var glmResp GLMResponse
if err := json.Unmarshal(body, &glmResp); err != nil {
return nil, err
}
if len(glmResp.Choices) == 0 {
return nil, fmt.Errorf("AI返回为空")
}
aiContent := strings.TrimSpace(glmResp.Choices[0].Message.Content)
aiContent = strings.TrimPrefix(aiContent, "```json")
aiContent = strings.TrimPrefix(aiContent, "```")
aiContent = strings.TrimSuffix(aiContent, "```")
aiContent = strings.TrimSpace(aiContent)
var result AnalysisResult
if err := json.Unmarshal([]byte(aiContent), &result); err != nil {
return nil, fmt.Errorf("解析AI响应失败: %v, 原始内容: %s", err, aiContent)
}
priority := int16(2)
if n, err := result.Priority.Int64(); err == nil {
priority = int16(n)
}
analysis := &model.TicketAiAnalysis{
Ticketid: ticketid,
Category: result.Category,
Priority: priority,
Summary: result.Summary,
Suggestrole: result.SuggestRole,
Rawresponse: glmResp.Choices[0].Message.Content,
Confirmed: 0,
Createtime: time.Now(),
}
if err := db.Create(analysis).Error; err != nil {
return nil, err
}
AddOperationLog(db, ticketid, 0, "analyze", "AI分析: "+result.Summary)
return analysis, nil
}
func GetAnalysisByTicketID(db *gorm.DB, ticketid int) ([]model.TicketAiAnalysis, error) {
var analyses []model.TicketAiAnalysis
err := db.Where("ticketid = ?", ticketid).Order("createtime DESC").Find(&analyses).Error
return analyses, err
}
func ConfirmAnalysis(db *gorm.DB, analysisid int, category string, priority int16, summary string, confirmedby int) error {
updates := map[string]interface{}{
"confirmed": 1,
"confirmedby": confirmedby,
"confirmedat": time.Now(),
}
if category != "" {
updates["category"] = category
}
if summary != "" {
updates["summary"] = summary
}
if err := db.Model(&model.TicketAiAnalysis{}).Where("analysisid = ?", analysisid).Updates(updates).Error; err != nil {
return err
}
var analysis model.TicketAiAnalysis
db.Where("analysisid = ?", analysisid).First(&analysis)
AddOperationLog(db, analysis.Ticketid, confirmedby, "confirm_analysis", "确认AI分析结果")
return nil
}