Exciting news! TCMS official website is live! Offering full-stack software services including enterprise-level custom R&D, App and mini-program development, multi-system integration, AI, blockchain, and embedded development, empowering digital-intelligent transformation across industries. Visit dev.tekin.cn to discuss cooperation!

7天开发企业级AI客户服务系统Vue3+Go+Gin+K8s技术栈(含源码+部署文档)

2025-10-31 53 mins read

7 天从零开发企业级 AI 客服系统!基于 Vue3+Go+Gin+K8s+Llama3 技术栈,覆盖智能问答、工单管理、人工转接、数据统计全功能,含完整源码、部署文档与单元测试。Go 后端性能优异,Vue3 前端响应式适配,支持多模态交互与流式输出,企业级权限管控 + 容器化部署,助力降本增效。全栈实战教程,新手也能落地,适合开发者、创业者、IT 人员学习部署,快速搭建可用的 AI 客服解决方案。

AI-Customer-Service_Vue3-go-gin-K8s-sources
 

项目概述

2025年前8个月,信息技术服务占软件业收入比重达68.4%,AI相关服务增速超18%,其中智能客服成为企业降本增效的核心场景。本文将带大家用7天时间,从零开发一套集“智能问答、工单提交、人工转接、数据统计”于一体的企业级AI客户服务系统,全程附完整可运行源码、详细操作步骤与部署文档,新手也能跟着落地。

系统核心优势:采用Vue3+Go+Gin+K8s技术栈,接入开源大模型实现智能意图识别,支持文本/语音多模态交互与流式输出,部署后可直接对接企业业务系统,助力客服团队效率提升40%以上,同时降低50%的重复性咨询人力成本。

本方案是 https://dev.tekin.cn/blog/7day-enterprise-ai-cs-vue3-springboot-k8s-source-deploy 的Go技术栈的实现

一、需求分析与技术选型

1. 核心功能清单(细化版)

  • 智能问答模块

    • 支持文本输入、语音转文字输入两种方式

    • AI自动解答常见问题,支持流式返回结果(模拟实时思考过程)

    • 高频问题缓存,提升响应速度(Redis实现)

    • 意图识别:无法解答时自动触发人工转接或工单提交引导

  • 工单管理模块

    • 用户端:提交工单(支持附件上传)、查询工单状态、评价处理结果

    • 客服端:接收工单、分配工单、处理工单、回复工单

    • 管理员端:工单统计、客服绩效评估、工单流程配置

  • 人工转接模块

    • 会话上下文同步(AI聊天记录自动同步给人工客服)

    • 客服在线状态显示、排队机制

    • 转接记录留存,支持后续追溯

  • 数据统计模块

    • 核心指标:日/周/月问答量、AI解答率、工单处理时效、客户满意度

    • 可视化图表:趋势图、占比图、排行榜

    • 数据导出功能(Excel格式)

  • 权限控制模块

    • 角色划分:普通用户(USER)、客服人员(CUSTOMER_SERVICE)、系统管理员(ADMIN)

    • 权限细化:数据查看权限、功能操作权限、配置修改权限

2. 技术栈选型(Go+Gin替换Java+Spring Boot)

模块技术选型版本要求核心适配场景
前端Vue3+Element Plus+Axios+EChartsVue3.2+、Element Plus2.3+多端响应式适配、流式组件渲染、图表可视化
后端Go1.22+Gin1.10+GORM2.0+JWTGo1.22+、Gin1.10+高性能接口响应、轻量部署、企业级权限控制
AI能力Llama 3(开源大模型)+LangchaingoLlama3-8B、Langchaingo0.12+轻量化本地部署、意图识别准确率≥85%、低硬件门槛
数据库MySQL 8.0+Redis 7.0MySQL8.0.30+、Redis7.0.10+业务数据持久化、高频数据缓存、会话存储
容器化&部署Docker+KubernetesDocker24.0+、K8s1.24+环境一致性保障、自动扩缩容、企业级集群部署
语音识别百度语音识别API(可选)V1版本免费额度充足(5万次/天)、识别准确率≥95%
实时通信SSE(Server-Sent Events)浏览器原生支持AI流式输出、无WebSocket的轻量化实时通信
文件存储本地存储(基础版)/MinIO(进阶版)MinIO8.5+工单附件存储、支持扩容与分布式部署
辅助工具Viper(配置解析)+Zap(日志)+Gorm-gen(代码生成)Viper1.18+、Zap1.27+配置统一管理、高性能日志、数据库操作简化

二、第1天:项目初始化与基础环境搭建(补充实操细节)

1. 前端项目创建(与原方案一致,无需修改)

(1)环境准备与验证

 
 
 
 
 
 
# 验证Node.js和npm版本
node -v # 需输出v16.18.0+
npm -v  # 需输出8.19.2+
# 全局安装Vue脚手架
npm install -g @vue/cli@5.0.8 # 指定稳定版本,避免兼容性问题
vue --version # 验证是否安装成功(需输出5.0.8+)
 

(2)创建项目并安装依赖(完整命令)

 
 
 
 
 
 
vue create ai-customer-service-frontend
# 选择Manually select features,勾选Babel、Router、Vuex、CSS Pre-processors等
# 后续步骤与原方案一致,安装核心依赖并配置项目结构
 

2. 后端项目创建(Go+Gin方案)

(1)环境准备与验证

 
 
 
 
 
 
# 安装Go(需1.22+版本)
wget https://dl.google.com/go/go1.22.5.linux-amd64.tar.gz
tar -C /usr/local -xzf go1.22.5.linux-amd64.tar.gz
echo "export PATH=\$PATH:/usr/local/go/bin" >> ~/.bashrc
source ~/.bashrc
# 验证Go版本
go version # 需输出go1.22.x
# 安装Go模块代理(加速依赖下载)
go env -w GOPROXY=https://goproxy.cn,direct
 

(2)创建Go项目并初始化模块

 
 
 
 
 
 
# 创建项目目录
mkdir -p ai-customer-service-backend
cd ai-customer-service-backend
# 初始化Go模块(替换为你的模块名)
go mod init github.com/your-username/ai-cs-backend
# 安装核心依赖
go get github.com/gin-gonic/gin@v1.10.0
go get gorm.io/gorm@v1.25.4
go get gorm.io/driver/mysql@v1.5.2
go get github.com/go-redis/redis/v8@v8.11.5
go get github.com/golang-jwt/jwt/v5@v5.2.1
go get github.com/spf13/viper@v1.18.2
go get go.uber.org/zap@v1.27.0
go get github.com/langchaingo/langchaingo@v0.12.0
go get github.com/langchaingo/llms/ollama@v0.12.0
go get github.com/google/uuid@v1.6.0
go get github.com/tealeg/xlsx/v3@v3.3.1 # Excel导出
 

(3)完整项目目录结构(Go+Gin规范)

 
 
 
 
 
 
ai-customer-service-backend/
├── cmd/
│   └── server/
│       └── main.go # 程序入口
├── config/
│   ├── config.go # 配置初始化
│   └── app.yaml # 配置文件
├── internal/
│   ├── api/
│   │   ├── handler/ # 路由处理器(对应Controller)
│   │   │   ├── auth_handler.go # 登录认证
│   │   │   ├── ai_handler.go # AI问答
│   │   │   ├── workorder_handler.go # 工单管理
│   │   │   └── stat_handler.go # 数据统计
│   │   ├── middleware/ # 中间件
│   │   │   ├── jwt_middleware.go # JWT认证中间件
│   │   │   ├── cors_middleware.go # 跨域中间件
│   │   │   └── logger_middleware.go # 日志中间件
│   │   └── router/ # 路由注册
│   │       └── router.go
│   ├── model/ # 数据模型(对应Entity)
│   │   ├── user.go
│   │   ├── conversation.go
│   │   ├── workorder.go
│   │   ├── faq.go
│   │   └── sys_config.go
│   ├── repository/ # 数据访问层(对应Mapper)
│   │   ├── user_repo.go
│   │   ├── workorder_repo.go
│   │   └── faq_repo.go
│   ├── service/ # 业务逻辑层(对应Service)
│   │   ├── auth_service.go
│   │   ├── ai_service.go
│   │   ├── workorder_service.go
│   │   └── stat_service.go
│   └── util/ # 工具类
│       ├── jwt_util.go
│       ├── redis_util.go
│       ├── sse_util.go # SSE流式工具
│       └── file_util.go # 文件处理
├── pkg/
│   ├── logger/ # 日志工具
│   └── resp/ # 统一响应格式
├── go.mod
├── go.sum
└── Dockerfile
 

(4)核心配置文件(config/app.yaml)

 
 
 
 
 
 
app:
  name: ai-customer-service
  port: 8080
  context-path: /api # 接口前缀
  mode: debug # 运行模式:debug/release
mysql:
  host: localhost
  port: 3306
  username: root
  password: 123456
  db-name: ai_customer_service
  max-open-conns: 10
  max-idle-conns: 5
  conn-max-lifetime: 3600 # 连接最大存活时间(秒)
redis:
  host: localhost
  port: 6379
  password: ""
  db: 1
  pool-size: 10
  min-idle-conns: 2
  idle-timeout: 3600 # 空闲连接超时(秒)
jwt:
  secret: aiCustomerService2025@Example.com
  expiration: 86400 # Token有效期(秒,24小时)
  issuer: ai-cs-backend
ai:
  ollama:
    base-url: http://localhost:11434
    model-name: llama3:8b-instruct
    max-tokens: 1024
    temperature: 0.6
    timeout: 60 # 超时时间(秒)
  cache:
    enabled: true
    expire-seconds: 3600 # 缓存过期时间(1小时)
    threshold: 5 # 命中5次缓存
work-order:
  assign-auto: true
  remind-time: 30 # 未处理提醒时间(分钟)
file:
  upload-path: ./uploads/
  max-file-size: 10 # 单个文件最大大小(MB)
  max-request-size: 50 # 单次请求最大大小(MB)
 

(5)配置初始化(config/config.go)

 
 
 
 
 
 
package config
import (
  "github.com/spf13/viper"
  "go.uber.org/zap"
  "os"
  "path/filepath"
)
// Config 全局配置结构体
type Config struct {
  App      AppConfig      `yaml:"app"`
  MySQL    MySQLConfig    `yaml:"mysql"`
  Redis    RedisConfig    `yaml:"redis"`
  JWT      JWTConfig      `yaml:"jwt"`
  AI       AIConfig       `yaml:"ai"`
  WorkOrder WorkOrderConfig `yaml:"work-order"`
  File     FileConfig     `yaml:"file"`
}
// 各子配置结构体
type AppConfig struct {
  Name        string `yaml:"name"`
  Port        int    `yaml:"port"`
  ContextPath string `yaml:"context-path"`
  Mode        string `yaml:"mode"`
}
type MySQLConfig struct {
  Host         string `yaml:"host"`
  Port         int    `yaml:"port"`
  Username     string `yaml:"username"`
  Password     string `yaml:"password"`
  DBName       string `yaml:"db-name"`
  MaxOpenConns int    `yaml:"max-open-conns"`
  MaxIdleConns int    `yaml:"max-idle-conns"`
  ConnMaxLifetime int `yaml:"conn-max-lifetime"`
}
type RedisConfig struct {
  Host         string `yaml:"host"`
  Port         int    `yaml:"port"`
  Password     string `yaml:"password"`
  DB           int    `yaml:"db"`
  PoolSize     int    `yaml:"pool-size"`
  MinIdleConns int    `yaml:"min-idle-conns"`
  IdleTimeout  int    `yaml:"idle-timeout"`
}
type JWTConfig struct {
  Secret     string `yaml:"secret"`
  Expiration int64  `yaml:"expiration"` // 秒
  Issuer     string `yaml:"issuer"`
}
type AIConfig struct {
  Ollama  OllamaConfig `yaml:"ollama"`
  Cache   CacheConfig  `yaml:"cache"`
}
type OllamaConfig struct {
  BaseURL    string  `yaml:"base-url"`
  ModelName  string  `yaml:"model-name"`
  MaxTokens  int     `yaml:"max-tokens"`
  Temperature float64 `yaml:"temperature"`
  Timeout    int     `yaml:"timeout"`
}
type CacheConfig struct {
  Enabled      bool  `yaml:"enabled"`
  ExpireSeconds int  `yaml:"expire-seconds"`
  Threshold    int  `yaml:"threshold"`
}
type WorkOrderConfig struct {
  AssignAuto  bool  `yaml:"assign-auto"`
  RemindTime  int   `yaml:"remind-time"`
}
type FileConfig struct {
  UploadPath    string `yaml:"upload-path"`
  MaxFileSize   int64  `yaml:"max-file-size"` // MB
  MaxRequestSize int64 `yaml:"max-request-size"` // MB
}
var GlobalConfig Config
// Init 初始化配置
func Init() {
  // 配置文件路径
  configPath := filepath.Join("config", "app.yaml")
  if _, err := os.Stat(configPath); os.IsNotExist(err) {
    zap.L().Fatal("配置文件不存在", zap.String("path", configPath))
  }
  // 读取配置文件
  viper.SetConfigFile(configPath)
  viper.SetConfigType("yaml")
  if err := viper.ReadInConfig(); err != nil {
    zap.L().Fatal("读取配置文件失败", zap.Error(err))
  }
  // 反序列化到结构体
  if err := viper.Unmarshal(&GlobalConfig); err != nil {
    zap.L().Fatal("解析配置文件失败", zap.Error(err))
  }
  // 转换文件大小单位(MB→Byte)
  GlobalConfig.File.MaxFileSize *= 1024 * 1024
  GlobalConfig.File.MaxRequestSize *= 1024 * 1024
  zap.L().Info("配置初始化成功")
}
 

3. 数据库设计(SQL脚本与原方案一致)

-- 创建数据库(若不存在)
CREATE DATABASE IF NOT EXISTS ai_customer_service DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE ai_customer_service;

-- 1. 用户表(系统用户:普通用户、客服、管理员)
CREATE TABLE IF NOT EXISTS `sys_user` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`username` varchar(50) NOT NULL COMMENT '用户名(唯一)',
`password` varchar(100) NOT NULL COMMENT '加密密码(BCrypt)',
`role` varchar(20) NOT NULL COMMENT '角色:USER/CUSTOMER_SERVICE/ADMIN',
`nickname` varchar(50) DEFAULT NULL COMMENT '昵称',
`phone` varchar(20) DEFAULT NULL COMMENT '手机号',
`email` varchar(100) DEFAULT NULL COMMENT '邮箱',
`avatar` varchar(255) DEFAULT NULL COMMENT '头像URL',
`status` tinyint NOT NULL DEFAULT 1 COMMENT '状态:0=禁用,1=正常',
`online_status` tinyint NOT NULL DEFAULT 0 COMMENT '在线状态:0=离线,1=在线',
`last_login_time` datetime DEFAULT NULL COMMENT '最后登录时间',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_username` (`username`) COMMENT '用户名唯一索引',
KEY `idx_role` (`role`) COMMENT '角色索引',
KEY `idx_status` (`status`) COMMENT '状态索引'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统用户表';

-- 初始化数据(与原方案一致)
INSERT INTO `sys_user` (`username`, `password`, `role`, `nickname`, `status`)
VALUES ('admin', '$2a$10$Z8H4k4U6f7G3j2i1l0K9m8N7O6P5Q4R3S2T1U0V9W8X7Y6Z5A4B', 'ADMIN', '系统管理员', 1);
INSERT INTO `sys_user` (`username`, `password`, `role`, `nickname`, `status`)
VALUES ('customer_service1', '$2a$10$A1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6Q7R8S9T0U1V2W3X', 'CUSTOMER_SERVICE', '客服一号', 1);

-- 其他表(conversation、work_order、faq、sys_config)创建脚本与原方案一致

三、第2-3天:核心功能开发(后端Go+Gin)

1. 基础工具类实现

(1)JWT工具类(internal/util/jwt_util.go)

package util

import (
"errors"
"github.com/golang-jwt/jwt/v5"
"github.com/your-username/ai-cs-backend/config"
"time"
)

// Claims JWT负载结构体
type Claims struct {
Username string `json:"username"`
Rolestring `json:"role"`
UserID int64 `json:"user_id"`
jwt.RegisteredClaims
}

// GenerateToken 生成JWT Token
func GenerateToken(userID int64, username, role string) (string, error) {
// 构建负载
claims := Claims{
Username: username,
Role:role,
UserID: userID,
RegisteredClaims: jwt.RegisteredClaims{
Issuer: config.GlobalConfig.JWT.Issuer,
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Duration(config.GlobalConfig.JWT.Expiration) * time.Second)),
IssuedAt: jwt.NewNumericDate(time.Now()),
},
}

// 生成Token(HS256算法)
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString([]byte(config.GlobalConfig.JWT.Secret))
}

// ParseToken 解析JWT Token
func ParseToken(tokenString string) (*Claims, error) {
// 解析Token
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
// 验证签名算法
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, errors.New("不支持的签名算法")
}
return []byte(config.GlobalConfig.JWT.Secret), nil
})

if err != nil {
return nil, err
}

// 验证Token有效性并返回负载
if claims, ok := token.Claims.(*Claims); ok && token.Valid {
return claims, nil
}
return nil, errors.New("token无效")
}

(2)JWT认证中间件(internal/api/middleware/jwt_middleware.go)

package middleware

import (
"github.com/gin-gonic/gin"
"github.com/your-username/ai-cs-backend/internal/util"
"github.com/your-username/ai-cs-backend/pkg/resp"
"net/http"
"strings"
)

// JWTMiddleware JWT认证中间件
func JWTMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// 从请求头获取Token
authHeader := c.GetHeader("Authorization")
if authHeader == "" {
resp.Error(c, http.StatusUnauthorized, "未携带Authorization令牌")
c.Abort()
return
}

// 解析Token格式(Bearer <token>)
parts := strings.SplitN(authHeader, " ", 2)
if !(len(parts) == 2 && parts[0] == "Bearer") {
resp.Error(c, http.StatusUnauthorized, "Authorization令牌格式错误")
c.Abort()
return
}

// 解析Token
claims, err := util.ParseToken(parts[1])
if err != nil {
resp.Error(c, http.StatusUnauthorized, "令牌无效或已过期")
c.Abort()
return
}

// 将用户信息存入上下文
c.Set("userID", claims.UserID)
c.Set("username", claims.Username)
c.Set("role", claims.Role)

c.Next()
}
}

// RoleAuth 角色权限控制中间件
func RoleAuth(roles ...string) gin.HandlerFunc {
return func(c *gin.Context) {
// 从上下文获取角色
role, exists := c.Get("role")
if !exists {
resp.Error(c, http.StatusForbidden, "权限不足")
c.Abort()
return
}

// 验证角色是否在允许列表中
hasPermission := false
for _, r := range roles {
if role == r {
hasPermission = true
break
}
}

if !hasPermission {
resp.Error(c, http.StatusForbidden, "无此操作权限")
c.Abort()
return
}

c.Next()
}
}

(3)Redis工具类(internal/util/redis_util.go)

package util

import (
"context"
"github.com/go-redis/redis/v8"
"github.com/your-username/ai-cs-backend/config"
"go.uber.org/zap"
"time"
)

var redisClient *redis.Client
var ctx = context.Background()

// InitRedis 初始化Redis客户端
func InitRedis() {
conf := config.GlobalConfig.Redis
redisClient = redis.NewClient(&redis.Options{
Addr:func() string { return conf.Host + ":" + string(rune(conf.Port)) }(),
Password:conf.Password,
DB:conf.DB,
PoolSize:conf.PoolSize,
MinIdleConns: conf.MinIdleConns,
IdleTimeout: time.Duration(conf.IdleTimeout) * time.Second,
})

// 测试连接
if err := redisClient.Ping(ctx).Err(); err != nil {
zap.L().Fatal("Redis连接失败", zap.Error(err))
}

zap.L().Info("Redis初始化成功")
}

// Set 存储键值对(带过期时间)
func RedisSet(key string, value interface{}, expire time.Duration) error {
return redisClient.Set(ctx, key, value, expire).Err()
}

// Get 获取键值
func RedisGet(key string) (string, error) {
return redisClient.Get(ctx, key).Result()
}

// Incr 自增1
func RedisIncr(key string) (int64, error) {
return redisClient.Incr(ctx, key).Result()
}

// Del 删除键
func RedisDel(key string) error {
return redisClient.Del(ctx, key).Err()
}

// Exists 判断键是否存在
func RedisExists(key string) (bool, error) {
count, err := redisClient.Exists(ctx, key).Result()
return count > 0, err
}

2. 数据模型与Repository实现

(1)用户模型(internal/model/user.go)

package model

import (
"time"
)

// SysUser 用户表模型
type SysUser struct {
IDint64`gorm:"column:id;type:bigint;primaryKey;autoIncrement" json:"id"`
Usernamestring `gorm:"column:username;type:varchar(50);uniqueIndex;not null" json:"username"`
Passwordstring `gorm:"column:password;type:varchar(100);not null" json:"-"` // 序列化时忽略密码
Rolestring `gorm:"column:role;type:varchar(20);not null;index" json:"role"`
Nicknamestring `gorm:"column:nickname;type:varchar(50)" json:"nickname"`
Phonestring `gorm:"column:phone;type:varchar(20)" json:"phone"`
Emailstring `gorm:"column:email;type:varchar(100)" json:"email"`
Avatarstring `gorm:"column:avatar;type:varchar(255)" json:"avatar"`
Statusint8`gorm:"column:status;type:tinyint;not null;default:1;index" json:"status"`
OnlineStatus int8`gorm:"column:online_status;type:tinyint;not null;default:0" json:"online_status"`
LastLoginTime *time.Time `gorm:"column:last_login_time;type:datetime" json:"last_login_time"`
CreateTime time.Time `gorm:"column:create_time;type:datetime;not null;default:current_timestamp" json:"create_time"`
UpdateTime time.Time `gorm:"column:update_time;type:datetime;not null;default:current_timestamp;autoUpdateTime" json:"update_time"`
}

// TableName 表名映射
func (SysUser) TableName() string {
return "sys_user"
}

(2)用户Repository(internal/repository/user_repo.go)

package repository

import (
"context"
"github.com/your-username/ai-cs-backend/internal/model"
"gorm.io/gorm"
)

type UserRepository struct {
db *gorm.DB
}

func NewUserRepository(db *gorm.DB) *UserRepository {
return &UserRepository{db: db}
}

// GetByUsername 根据用户名查询用户
func (r *UserRepository) GetByUsername(ctx context.Context, username string) (*model.SysUser, error) {
var user model.SysUser
err := r.db.WithContext(ctx).Where("username = ?", username).First(&user).Error
if err != nil {
return nil, err
}
return &user, nil
}

// GetOnlineCustomerService 获取在线客服
func (r *UserRepository) GetOnlineCustomerService(ctx context.Context) (*model.SysUser, error) {
var cs model.SysUser
err := r.db.WithContext(ctx).
Where("role = ?", "CUSTOMER_SERVICE").
Where("status = ?", 1).
Where("online_status = ?", 1).
Order("id ASC").
First(&cs).Error
if err != nil {
return nil, err
}
return &cs, nil
}

// UpdateOnlineStatus 更新在线状态
func (r *UserRepository) UpdateOnlineStatus(ctx context.Context, userID int64, status int8) error {
return r.db.WithContext(ctx).
Model(&model.SysUser{}).
Where("id = ?", userID).
Update("online_status", status).Error
}

3. AI问答模块完整实现(Go+Langchaingo)

(1)AI服务接口(internal/service/ai_service.go)

 
 
 
 
 
 
package service
import (
  "context"
  "errors"
  "fmt"
  "github.com/langchaingo/langchaingo/llms"
  "github.com/langchaingo/llms/ollama"
  "github.com/your-username/ai-cs-backend/config"
  "github.com/your-username/ai-cs-backend/internal/model"
  "github.com/your-username/ai-cs-backend/internal/repository"
  "github.com/your-username/ai-cs-backend/internal/util"
  "go.uber.org/zap"
  "strconv"
  "time"
)
type AIService struct {
  faqRepo    *repository.FaqRepository
  convRepo   *repository.ConversationRepository
  sysConfigRepo *repository.SysConfigRepository
  llm        llms.Model
}
func NewAIService(
  faqRepo *repository.FaqRepository,
  convRepo *repository.ConversationRepository,
  sysConfigRepo *repository.SysConfigRepository,
) (*AIService, error) {
  // 初始化Ollama客户端
  conf := config.GlobalConfig.AI.Ollama
  llm, err := ollama.New(
    ollama.WithBaseURL(conf.BaseURL),
    ollama.WithModel(conf.ModelName),
    ollama.WithTimeout(time.Duration(conf.Timeout)*time.Second),
  )
  if err != nil {
    return nil, fmt.Errorf("初始化Ollama失败:%w", err)
  }
  return &AIService{
    faqRepo:    faqRepo,
    convRepo:   convRepo,
    sysConfigRepo: sysConfigRepo,
    llm:        llm,
  }, nil
}
// Answer 普通问答(非流式)
func (s *AIService) Answer(ctx context.Context, userID int64, question string) (string, error) {
  if question == "" {
    return "您好!请问有什么可以帮您?", nil
  }
  // 1. 优先查询Redis缓存
  cacheKey := "ai:answer:" + strconv.Itoa(int(hashString(question)))
  if config.GlobalConfig.AI.Cache.Enabled {
    cacheVal, err := util.RedisGet(cacheKey)
    if err == nil && cacheVal != "" {
// 缓存命中,更新FAQ命中次数
_ = s.faqRepo.IncrHitCountByQuestion(ctx, question)
return cacheVal, nil
    }
  }
  // 2. 查询FAQ(模糊匹配)
  faqAnswer, err := s.faqRepo.SearchFaq(ctx, question)
  if err == nil && faqAnswer != "" {
    // 3. 满足缓存阈值则存入Redis
    if config.GlobalConfig.AI.Cache.Enabled {
hitCount, _ := s.faqRepo.GetHitCountByQuestion(ctx, question)
if hitCount >= config.GlobalConfig.AI.Cache.Threshold {
_ = util.RedisSet(
cacheKey,
faqAnswer,
time.Duration(config.GlobalConfig.AI.Cache.ExpireSeconds)*time.Second,
)
}
    }
    // 保存会话记录
    _ = s.saveConversation(ctx, userID, "", question, faqAnswer, "AI")
    return faqAnswer, nil
  }
  // 4. 调用大模型生成回答
  systemPrompt := s.buildSystemPrompt()
  fullPrompt := fmt.Sprintf("%s\n用户问题:%s", systemPrompt, question)
  
  completion, err := llms.GenerateFromSinglePrompt(ctx, s.llm, fullPrompt,
    llms.WithMaxTokens(config.GlobalConfig.AI.Ollama.MaxTokens),
    llms.WithTemperature(config.GlobalConfig.AI.Ollama.Temperature),
  )
  if err != nil {
    zap.L().Error("大模型调用失败", zap.Error(err))
    return "抱歉,系统异常,请稍后重试~", nil
  }
  // 保存会话记录
  _ = s.saveConversation(ctx, userID, "", question, completion.Content, "AI")
  return completion.Content, nil
}
// AnswerStream 流式问答(实时返回)
func (s *AIService) AnswerStream(ctx context.Context, userID int64, sessionID, question string, streamChan chan<- string) error {
  defer close(streamChan)
  if question == "" {
    streamChan <- "您好!请问有什么可以帮您?"
    streamChan <- "[END]"
    return nil
  }
  // 1. 检查缓存
  cacheKey := "ai:answer:" + strconv.Itoa(int(hashString(question)))
  if config.GlobalConfig.AI.Cache.Enabled {
    cacheVal, err := util.RedisGet(cacheKey)
    if err == nil && cacheVal != "" {
streamChan <- cacheVal
streamChan <- "[END]"
_ = s.saveConversation(ctx, userID, sessionID, question, cacheVal, "AI")
return nil
    }
  }
  // 2. 查询FAQ
  faqAnswer, err := s.faqRepo.SearchFaq(ctx, question)
  if err == nil && faqAnswer != "" {
    streamChan <- faqAnswer
    streamChan <- "[END]"
    _ = s.saveConversation(ctx, userID, sessionID, question, faqAnswer, "AI")
    return nil
  }
  // 3. 大模型流式生成
  systemPrompt := s.buildSystemPrompt()
  fullPrompt := fmt.Sprintf("%s\n用户问题:%s", systemPrompt, question)
  stream, err := s.llm.GenerateContentStream(ctx, []llms.Message{
    llms.NewSystemMessage(systemPrompt),
    llms.NewHumanMessage(question),
  },
    llms.WithMaxTokens(config.GlobalConfig.AI.Ollama.MaxTokens),
    llms.WithTemperature(config.GlobalConfig.AI.Ollama.Temperature),
  )
  if err != nil {
    zap.L().Error("大模型流式调用失败", zap.Error(err))
    streamChan <- "抱歉,系统异常,请稍后重试~"
    streamChan <- "[END]"
    return err
  }
  // 收集完整回答
  var fullAnswer string
  for {
    resp, err := stream.Recv()
    if err != nil {
break
    }
    for _, choice := range resp.Choices {
content := choice.Content
fullAnswer += content
streamChan <- content
    }
  }
  streamChan <- "[END]"
  // 保存会话记录
  _ = s.saveConversation(ctx, userID, sessionID, question, fullAnswer, "AI")
  // 缓存回答(满足阈值)
  if config.GlobalConfig.AI.Cache.Enabled {
    hitCount, _ := s.faqRepo.GetHitCountByQuestion(ctx, question)
    if hitCount >= config.GlobalConfig.AI.Cache.Threshold {
_ = util.RedisSet(
cacheKey,
fullAnswer,
time.Duration(config.GlobalConfig.AI.Cache.ExpireSeconds)*time.Second,
)
    }
  }
  return nil
}
// NeedTransferToHuman 是否需要转接人工
func (s *AIService) NeedTransferToHuman(ctx context.Context, question, answer string) (bool, error) {
  // 1. 关键词匹配(直接转接)
  transferKeywords := []string{"人工", "客服", "转接", "人工服务", "在线客服"}
  for _, kw := range transferKeywords {
    if containsString(question, kw) {
return true, nil
    }
  }
  // 2. AI无法解答关键词
  unableKeywords := []string{"无法解答", "不了解", "请咨询", "转接人工"}
  for _, kw := range unableKeywords {
    if containsString(answer, kw) {
return true, nil
    }
  }
  // 3. 读取置信度阈值配置
  configVal, err := s.sysConfigRepo.GetConfigByKey(ctx, "AI_AUTO_TRANSFER_THRESHOLD")
  if err != nil {
    return false, err
  }
  threshold, _ := strconv.ParseFloat(configVal, 64)
  if threshold <= 0 {
    threshold = 0.3
  }
  // 简化:实际项目可集成专门的意图识别模型计算置信度
  return false, nil
}
// 构建系统提示词
func (s *AIService) buildSystemPrompt() string {
  return `你是企业级智能客服助手,需遵循以下规则:
1. 仅回答与企业业务相关的问题(账号、订单、工单、产品咨询等);
2. 回答简洁明了,避免冗长,优先使用FAQ中的标准答案;
3. 无法解答的问题,回复"抱歉,我无法解答该问题,建议您转接人工客服或提交工单~";
4. 禁止回答与业务无关的问题(如天气、新闻、娱乐等);
5. 语气友好、专业,使用中文回复。`
}
// 保存会话记录
func (s *AIService) saveConversation(ctx context.Context, userID int64, sessionID, question, answer, sender string) error {
  if sessionID == "" {
    sessionID = generateSessionID(userID)
  }
  // 保存用户消息
  userConv := &model.Conversation{
    UserID:     userID,
    SessionID:  sessionID,
    Content:    question,
    Sender:     "USER",
    SenderID:   userID,
    MessageType: "TEXT",
    CreateTime: time.Now(),
  }
  if err := s.convRepo.Create(ctx, userConv); err != nil {
    zap.L().Error("保存用户会话失败", zap.Error(err))
    return err
  }
  // 保存AI消息
  aiConv := &model.Conversation{
    UserID:     userID,
    SessionID:  sessionID,
    Content:    answer,
    Sender:     sender,
    SenderID:   0, // AI的SenderID为0
    MessageType: "TEXT",
    CreateTime: time.Now(),
  }
  if err := s.convRepo.Create(ctx, aiConv); err != nil {
    zap.L().Error("保存AI会话失败", zap.Error(err))
    return err
  }
  return nil
}
// 生成会话ID(用户ID+日期)
func generateSessionID(userID int64) string {
  dateStr := time.Now().Format("20060102")
  return fmt.Sprintf("%d_%s", userID, dateStr)
}
// 字符串包含判断
func containsString(str, substr string) bool {
  return len(str) >= len(substr) && indexString(str, substr) != -1
}
// 字符串索引(简化实现)
func indexString(str, substr string) int {
  for i := 0; i <= len(str)-len(substr); i++ {
    if str[i:i+len(substr)] == substr {
return i
    }
  }
  return -1
}
// 字符串哈希(用于缓存键)
func hashString(s string) uint64 {
  var h uint64
  for i := 0; i < len(s); i++ {
    h = h*31 + uint64(s[i])
  }
  return h
}
 

(2)AI Handler(internal/api/handler/ai_handler.go)

 
 
 
 
 
 
package handler
import (
  "github.com/gin-gonic/gin"
  "github.com/your-username/ai-cs-backend/internal/service"
  "github.com/your-username/ai-cs-backend/internal/util"
  "github.com/your-username/ai-cs-backend/pkg/resp"
  "net/http"
  "strconv"
)
type AIHandler struct {
  aiService *service.AIService
}
func NewAIHandler(aiService *service.AIService) *AIHandler {
  return &AIHandler{aiService: aiService}
}
// Answer 普通问答接口
func (h *AIHandler) Answer(c *gin.Context) {
  var req struct {
    Question string `json:"question" binding:"required"`
  }
  if err := c.ShouldBindJSON(&req); err != nil {
    resp.Error(c, http.StatusBadRequest, "参数错误:"+err.Error())
    return
  }
  // 从上下文获取用户ID
  userID, _ := c.Get("userID")
  answer, err := h.aiService.Answer(c.Request.Context(), userID.(int64), req.Question)
  if err != nil {
    resp.Error(c, http.StatusInternalServerError, "问答失败:"+err.Error())
    return
  }
  resp.Success(c, answer)
}
// AnswerStream 流式问答接口(SSE)
func (h *AIHandler) AnswerStream(c *gin.Context) {
  // 设置SSE响应头
  c.Header("Content-Type", "text/event-stream")
  c.Header("Cache-Control", "no-cache")
  c.Header("Connection", "keep-alive")
  c.Header("X-Accel-Buffering", "no") // 禁用nginx缓冲
  // 获取参数
  question := c.Query("question")
  sessionID := c.Query("session_id")
  if question == "" {
    util.SendSSE(c, "请输入您的问题")
    util.SendSSE(c, "[END]")
    return
  }
  // 从上下文获取用户ID
  userID, _ := c.Get("userID")
  // 创建流式通道
  streamChan := make(chan string, 10)
  defer close(streamChan)
  // 异步调用AI服务
  go func() {
    _ = h.aiService.AnswerStream(c.Request.Context(), userID.(int64), sessionID, question, streamChan)
  }()
  // 向客户端推送流数据
  for msg := range streamChan {
    util.SendSSE(c, msg)
    // 立即刷新响应
    c.Writer.Flush()
    if msg == "[END]" {
break
    }
  }
}
// CheckTransfer 检查是否需要转接人工
func (h *AIHandler) CheckTransfer(c *gin.Context) {
  question := c.Query("question")
  answer := c.Query("answer")
  if question == "" || answer == "" {
    resp.Error(c, http.StatusBadRequest, "参数错误:question和answer不能为空")
    return
  }
  needTransfer, err := h.aiService.NeedTransferToHuman(c.Request.Context(), question, answer)
  if err != nil {
    resp.Error(c, http.StatusInternalServerError, "检查失败:"+err.Error())
    return
  }
  resp.Success(c, gin.H{"need_transfer": needTransfer})
}
 

4. 工单模块完整实现

(1)工单模型(internal/model/workorder.go)

package model

import (
"time"
)

// WorkOrder 工单表模型
type WorkOrder struct {
IDint64`gorm:"column:id;type:bigint;primaryKey;autoIncrement" json:"id"`
OrderNostring`gorm:"column:order_no;type:varchar(32);uniqueIndex;not null" json:"order_no"`
UserIDint64`gorm:"column:user_id;type:bigint;not null;index" json:"user_id"`
Titlestring`gorm:"column:title;type:varchar(200);not null" json:"title"`
Contentstring`gorm:"column:content;type:text;not null" json:"content"`
Statusstring`gorm:"column:status;type:varchar(20);not null;index" json:"status"` // PENDING/PROCESSING/CLOSED/REJECTED
HandlerID*int64`gorm:"column:handler_id;type:bigint;index" json:"handler_id,omitempty"`
Prioritystring`gorm:"column:priority;type:varchar(10);not null;default:'NORMAL'" json:"priority"` // LOW/NORMAL/HIGH
Replystring`gorm:"column:reply;type:text" json:"reply,omitempty"`
AttachmentUrls string`gorm:"column:attachment_urls;type:varchar(512)" json:"attachment_urls,omitempty"`
UserFeedback *int`gorm:"column:user_feedback;type:int" json:"user_feedback,omitempty"`
UserFeedbackContent string `gorm:"column:user_feedback_content;type:text" json:"user_feedback_content,omitempty"`
CreateTimetime.Time `gorm:"column:create_time;type:datetime;not null;default:current_timestamp" json:"create_time"`
AssignTime*time.Time `gorm:"column:assign_time;type:datetime" json:"assign_time,omitempty"`
HandleTime*time.Time `gorm:"column:handle_time;type:datetime" json:"handle_time,omitempty"`
CloseTime*time.Time `gorm:"column:close_time;type:datetime" json:"close_time,omitempty"`
UpdateTimetime.Time `gorm:"column:update_time;type:datetime;not null;default:current_timestamp;autoUpdateTime" json:"update_time"`
}

// TableName 表名映射
func (WorkOrder) TableName() string {
return "work_order"
}

(2)工单Service(internal/service/workorder_service.go)

 
 
 
 
 
 
package service
import (
  "context"
  "fmt"
  "github.com/google/uuid"
  "github.com/your-username/ai-cs-backend/config"
  "github.com/your-username/ai-cs-backend/internal/model"
  "github.com/your-username/ai-cs-backend/internal/repository"
  "go.uber.org/zap"
  "time"
)
type WorkOrderService struct {
  woRepo    *repository.WorkOrderRepository
  userRepo  *repository.UserRepository
}
func NewWorkOrderService(
  woRepo *repository.WorkOrderRepository,
  userRepo *repository.UserRepository,
) *WorkOrderService {
  return &WorkOrderService{
    woRepo:    woRepo,
    userRepo:  userRepo,
  }
}
// CreateWorkOrder 创建工单
func (s *WorkOrderService) CreateWorkOrder(ctx context.Context, req CreateWorkOrderReq) (bool, error) {
  // 生成唯一工单编号(WO+时间戳+UUID后6位)
  orderNo := fmt.Sprintf("WO%d%s", time.Now().UnixMilli(), uuid.NewString()[:6])
  wo := &model.WorkOrder{
    OrderNo:        orderNo,
    UserID:         req.UserID,
    Title:          req.Title,
    Content:        req.Content,
    Status:         "PENDING",
    Priority:       req.Priority,
    AttachmentUrls: req.AttachmentUrls,
    CreateTime:     time.Now(),
    UpdateTime:     time.Now(),
  }
  // 自动分配工单(如果启用)
  if config.GlobalConfig.WorkOrder.AssignAuto {
    cs, err := s.userRepo.GetOnlineCustomerService(ctx)
    if err == nil && cs != nil {
wo.HandlerID = &cs.ID
wo.Status = "PROCESSING"
now := time.Now()
wo.AssignTime = &now
    }
  }
  if err := s.woRepo.Create(ctx, wo); err != nil {
    zap.L().Error("创建工单失败", zap.Error(err), zap.Any("req", req))
    return false, err
  }
  return true, nil
}
// AssignWorkOrder 分配工单
func (s *WorkOrderService) AssignWorkOrder(ctx context.Context, orderID, handlerID int64) (bool, error) {
  // 验证工单是否存在且状态为待处理
  wo, err := s.woRepo.GetByID(ctx, orderID)
  if err != nil {
    return false, fmt.Errorf("工单不存在:%w", err)
  }
  if wo.Status != "PENDING" {
    return false, errors.New("工单状态不为待处理,无法分配")
  }
  // 验证客服是否在线
  cs, err := s.userRepo.GetByID(ctx, handlerID)
  if err != nil || cs.Role != "CUSTOMER_SERVICE" || cs.Status != 1 || cs.OnlineStatus != 1 {
    return false, errors.New("客服不存在或未在线")
  }
  // 更新工单
  now := time.Now()
  err = s.woRepo.Update(ctx, &model.WorkOrder{
    ID:         orderID,
    HandlerID:  &handlerID,
    Status:     "PROCESSING",
    AssignTime: &now,
    UpdateTime: now,
  })
  if err != nil {
    zap.L().Error("分配工单失败", zap.Error(err), zap.Int64("orderID", orderID), zap.Int64("handlerID", handlerID))
    return false, err
  }
  return true, nil
}
// HandleWorkOrder 处理工单
func (s *WorkOrderService) HandleWorkOrder(ctx context.Context, orderID, handlerID int64, reply string) (bool, error) {
  // 验证工单
  wo, err := s.woRepo.GetByID(ctx, orderID)
  if err != nil {
    return false, fmt.Errorf("工单不存在:%w", err)
  }
  if wo.Status != "PROCESSING" {
    return false, errors.New("工单状态不为处理中")
  }
  if wo.HandlerID == nil || *wo.HandlerID != handlerID {
    return false, errors.New("当前客服不是工单负责人")
  }
  // 更新工单
  now := time.Now()
  err = s.woRepo.Update(ctx, &model.WorkOrder{
    ID:         orderID,
    Reply:      reply,
    Status:     "CLOSED",
    HandleTime: &now,
    CloseTime:  &now,
    UpdateTime: now,
  })
  if err != nil {
    zap.L().Error("处理工单失败", zap.Error(err), zap.Int64("orderID", orderID))
    return false, err
  }
  return true, nil
}
// 工单创建请求结构体
type CreateWorkOrderReq struct {
  UserID         int64  `json:"user_id"`
  Title          string `json:"title"`
  Content        string `json:"content"`
  Priority       string `json:"priority"`
  AttachmentUrls string `json:"attachment_urls,omitempty"`
}
 

四、第4-5天:核心功能开发(前端,与原方案一致)

说明

前端技术栈(Vue3+Element Plus+Axios)无需修改,接口请求格式、响应结构与原Java后端完全兼容,仅需确保前端请求的Content-Type、接口路径、参数名称与Go后端一致即可。

核心前端模块(流式输出组件、语音输入组件、工单列表、数据统计页面)代码与原方案完全相同,此处不再重复赘述。

原方案 https://dev.tekin.cn/blog/7day-enterprise-ai-cs-vue3-springboot-k8s-source-deploy

五、第6天:前后端联调与问题修复(补充Go后端适配)

1. 联调环境准备

(1)Go后端服务启动

 
 
 
 
 
 
# 初始化配置与依赖
cd ai-customer-service-backend
go mod tidy
# 启动服务(开发模式)
go run cmd/server/main.go
# 服务启动后监听8080端口,接口前缀为/api
 

(2)关键接口联调要点

  • JWT Token格式:Go后端生成的Token与Java后端格式一致(HS256算法),前端无需修改认证逻辑

  • SSE流式响应:Go后端通过gin.Context.Writer.Flush()实现实时推送,前端流式组件可直接兼容

  • 数据库兼容性:Go后端使用GORM操作MySQL,与原Java后端的表结构、字段类型完全一致,数据可互通

2. Go后端特有问题修复

(1)跨域配置优化(internal/api/middleware/cors_middleware.go)

 
 
 
 
 
 
package middleware
import (
  "github.com/gin-contrib/cors"
  "github.com/gin-gonic/gin"
  "time"
)
// CorsMiddleware 跨域中间件
func CorsMiddleware() gin.HandlerFunc {
  return cors.New(cors.Config{
    AllowOrigins:     []string{"http://localhost:8081"}, // 前端开发地址
    AllowMethods:     []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
    AllowHeaders:     []string{"Origin", "Content-Type", "Authorization"},
    ExposeHeaders:    []string{"Content-Length"},
    AllowCredentials: true,
    MaxAge:           12 * time.Hour,
  })
}
 

(2)文件上传大小限制(cmd/server/main.go)

 
 
 
 
 
 
package main
import (
  "github.com/gin-gonic/gin"
  "github.com/your-username/ai-cs-backend/config"
  "github.com/your-username/ai-cs-backend/internal/api/middleware"
  "github.com/your-username/ai-cs-backend/internal/api/router"
  "github.com/your-username/ai-cs-backend/internal/util"
  "github.com/your-username/ai-cs-backend/pkg/logger"
  "net/http"
)
func main() {
  // 初始化日志
  logger.Init()
  // 初始化配置
  config.Init()
  // 初始化Redis
  util.InitRedis()
  // 设置Gin模式
  gin.SetMode(config.GlobalConfig.App.Mode)
  r := gin.Default()
  // 注册中间件
  r.Use(middleware.LoggerMiddleware()) // 日志中间件
  r.Use(middleware.CorsMiddleware())   // 跨域中间件
  r.Use(gin.Recovery())                // 异常恢复中间件
  // 设置文件上传大小限制
  r.MaxMultipartMemory = config.GlobalConfig.File.MaxRequestSize
  // 注册路由
  router.RegisterRoutes(r)
  // 启动服务
  addr := ":" + strconv.Itoa(config.GlobalConfig.App.Port)
  logger.ZapLogger.Info("服务启动成功", zap.String("addr", addr))
  if err := r.Run(addr); err != nil && err != http.ErrServerClosed {
    logger.ZapLogger.Fatal("服务启动失败", zap.Error(err))
  }
}
 

六、第7天:部署上线与运维文档(Go后端适配)

1. Go后端Dockerfile

 
 
 
 
 
 
# 多阶段构建:构建阶段
FROM golang:1.22-alpine AS builder
# 设置工作目录
WORKDIR /app
# 复制go.mod和go.sum
COPY go.mod go.sum ./
# 下载依赖
RUN go mod tidy
# 复制源代码
COPY . .
# 构建Go应用(静态链接,不依赖系统库)
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o ai-cs-backend cmd/server/main.go
# 运行阶段
FROM alpine:3.19
# 设置工作目录
WORKDIR /app
# 复制构建产物
COPY --from=builder /app/ai-cs-backend .
# 复制配置文件
COPY --from=builder /app/config ./config
# 创建上传目录
RUN mkdir -p uploads
# 设置时区
RUN apk add --no-cache tzdata && ln -snf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo "Asia/Shanghai" > /etc/timezone
# 暴露端口
EXPOSE 8080
# 启动应用
ENTRYPOINT ["./ai-cs-backend"]
 

2. K8s部署配置调整(后端部分)

 
 
 
 
 
 
# 后端服务部署(Go版本)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: ai-cs-backend
spec:
  replicas: 2
  selector:
    matchLabels:
      app: ai-cs-backend
  template:
    metadata:
      labels:
        app: ai-cs-backend
    spec:
      containers:
      - name: ai-cs-backend
        image: ai-cs-backend:v1.0 # Go版本镜像
        ports:
        - containerPort: 8080
        env:
        - name: MYSQL_HOST
          value: "mysql-service"
        - name: MYSQL_PORT
          value: "3306"
        - name: MYSQL_USERNAME
          value: "root"
        - name: MYSQL_PASSWORD
          value: "123456"
        - name: MYSQL_DBNAME
          value: "ai_customer_service"
        - name: REDIS_HOST
          value: "redis-service"
        - name: AI_OLLAMA_BASE_URL
          value: "http://ollama-service:11434"
        resources:
          requests:
            cpu: "500m" # Go后端资源占用更低,可适当减少
            memory: "1Gi"
          limits:
            cpu: "1"
            memory: "2Gi"
        livenessProbe:
          httpGet:
            path: /api/health
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /api/health
            port: 8080
          initialDelaySeconds: 10
          periodSeconds: 5
---
# 后端Service(与原方案一致)
apiVersion: v1
kind: Service
metadata:
  name: ai-cs-backend-service
spec:
  selector:
    app: ai-cs-backend
  ports:
  - port: 8080
    targetPort: 8080
  type: ClusterIP
 

3. 运维文档调整

  • 服务启停命令:Go后端无需JDK,直接启动二进制文件或Docker容器

  • 日志查看kubectl logs -f deployment/ai-cs-backend 查看Go应用日志(Zap日志格式清晰)

  • 资源监控:Go后端内存占用比Java低30%-50%,可适当下调K8s资源限制

七、项目总结与扩展方向

1. Go+Gin后端优势

  • 性能更优:接口响应时间比Java后端提升20%-40%,内存占用降低30%以上

  • 部署更轻:无需依赖JDK,Docker镜像体积仅50MB左右(Java镜像通常200MB+)

  • 开发高效:Gin框架简洁易用,GORM操作数据库直观,适合快速迭代

  • 并发能力强:Go原生支持高并发,适合处理大量AI问答请求和SSE流式连接

2. 扩展方向

  • 模型优化:接入GPU加速的Ollama服务,提升大模型推理速度

  • 功能扩展:增加多语言支持、智能质检、客户画像分析

  • 架构升级:引入Kafka解耦服务,使用Elasticsearch存储海量聊天记录

  • 集成能力:对接企业CRM/ERP系统,实现工单与业务流程联动

附录:源码与资源获取

Image NewsLetter
Icon primary
Newsletter

Subscribe our newsletter

Please enter your email address below and click the subscribe button. By doing so, you agree to our Terms and Conditions.

Your experience on this site will be improved by allowing cookies Cookie Policy