- 工单编号改为业务格式 TK-yyMMdd-NNN
- 类型断言加 comma-ok 防 panic
- priority 用指针区分未传/P0
- json.Marshal 错误处理
- 提取 ParseID 公共函数消除重复
- HTTP client 包级别复用
- LIKE 查询特殊字符转义
- interface{} → any
- auth 中间件用 dto.Fail 统一响应
162 lines
4.3 KiB
Go
162 lines
4.3 KiB
Go
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
|
||
}
|