feat(01): Phase 1 - 基础设施 + 数据库 + 公共模块
- 创建10个微服务目录结构和Maven多模块配置 - 完成15张MySQL 5.7表设计和初始化数据(40意图+8话术分类+管理员) - 实现common模块:Result、PageResult、异常处理、MyBatisPlus/Redis/RabbitMQ/跨域配置 - 实现BaseEntity、AutoFillHandler、JsonUtils、JsonTypeHandler - 配置API Gateway路由和全部服务的application.yml - 添加docker-compose.yml(MySQL+Redis+RabbitMQ)
This commit is contained in:
commit
31a395be5e
37
.planning/PROJECT.md
Normal file
37
.planning/PROJECT.md
Normal file
@ -0,0 +1,37 @@
|
||||
# 第九联盟AI坐席辅助系统
|
||||
|
||||
## 项目愿景
|
||||
|
||||
为第九联盟(9artedu.com)课程顾问/销售(坐席)构建一个基于企业微信会话存档 + LLM 的实时话术推荐系统。当学员通过企微咨询课程时,系统实时分析对话内容,识别学员意图,构建学员画像,并推荐最合适的话术,帮助坐席提高转化率和响应效率。
|
||||
|
||||
## 核心业务
|
||||
|
||||
- **数据接入**: 企微会话存档拉取(C SDK + JNA)、RSA解密、消息解析
|
||||
- **AI分析引擎**: 对话上下文理解(最近10轮滑动窗口)、意图识别(40个场景多标签分类)、学员画像构建(5维度标签体系)、话术召回(规则召回+向量召回+热门召回)、话术排序(多因子加权+MMR多样性)、LLM动态话术生成(兜底策略)
|
||||
- **输出交互**: PC端侧边栏H5(360px)、手机端侧边栏H5(80%高度)、模板卡片消息推送、一键发送
|
||||
- **管理后台**: 话术库管理(CRUD+分类+导入导出)、数据统计看板、系统配置
|
||||
|
||||
## 目标用户
|
||||
|
||||
- **主要用户**: 第九联盟课程顾问/销售(坐席)
|
||||
- **服务对象**: CG美术培训潜在学员(外部联系人)
|
||||
|
||||
## 关键指标
|
||||
|
||||
- 端到端延迟目标: < 25秒(含15秒企微回调间隔)
|
||||
- 话术推荐准确率: 基于坐席采纳率和转化率持续优化
|
||||
- 覆盖40个意图场景、8个销售阶段
|
||||
|
||||
## 技术栈
|
||||
|
||||
- 后端: Spring Boot 2.7.x + MyBatis-Plus + MySQL 5.7 + Redis 6.x + RabbitMQ 3.x
|
||||
- 前端: React 18 + TypeScript + Vite + Ant Design / Ant Design Mobile
|
||||
- LLM: 通义千问API (qwen-turbo/qwen-plus)
|
||||
- 企微集成: 企业微信JS-SDK 2.0 + JNA调用C SDK
|
||||
- 部署: Docker + Docker Compose
|
||||
|
||||
## 约束条件
|
||||
|
||||
- 线上环境为 MySQL 5.7(JSON→TEXT适配,向量存Redis)
|
||||
- 用户自有服务器部署
|
||||
- 数据合规要求(使用国产LLM)
|
||||
63
.planning/REQUIREMENTS.md
Normal file
63
.planning/REQUIREMENTS.md
Normal file
@ -0,0 +1,63 @@
|
||||
# 需求文档
|
||||
|
||||
## v1 需求
|
||||
|
||||
### R1. 数据接入层
|
||||
- **R1-01**: 企微会话存档拉取(C SDK + JNA)
|
||||
- **R1-02**: RSA解密 + 消息解析
|
||||
- **R1-03**: 回调接收(msgaudit_notify,15秒间隔)
|
||||
- **R1-04**: 消息去重与存储
|
||||
|
||||
### R2. AI分析引擎
|
||||
- **R2-01**: 对话上下文管理(最近10轮滑动窗口)
|
||||
- **R2-02**: 意图识别(40个场景多标签分类)
|
||||
- **R2-03**: 学员画像构建(5维度标签体系:类型/基础/意向度/关注重点/决策阶段)
|
||||
- **R2-04**: 话术召回(规则召回+向量召回+热门召回)
|
||||
- **R2-05**: 话术排序(多因子加权+MMR多样性)
|
||||
- **R2-06**: LLM动态话术生成(兜底策略)
|
||||
|
||||
### R3. 输出交互层
|
||||
- **R3-01**: PC端侧边栏H5(360px,完整功能)
|
||||
- **R3-02**: 手机端侧边栏H5(80%高度,精简操作)
|
||||
- **R3-03**: 模板卡片消息推送(高优先级场景主动推送)
|
||||
- **R3-04**: 一键发送(sendChatMessage)
|
||||
- **R3-05**: WebSocket实时推送推荐结果
|
||||
|
||||
### R4. 管理后台
|
||||
- **R4-01**: 话术库管理(CRUD+分类+导入导出)
|
||||
- **R4-02**: 数据统计看板(使用数据+效果分析)
|
||||
- **R4-03**: 系统配置(推送设置+免打扰)
|
||||
- **R4-04**: 管理员登录与权限控制
|
||||
|
||||
### R5. 基础设施
|
||||
- **R5-01**: 企微OAuth2认证 + JWT
|
||||
- **R5-02**: API网关(路由/限流/鉴权)
|
||||
- **R5-03**: MySQL 5.7 数据库(15张表)
|
||||
- **R5-04**: Redis 缓存(会话/向量/Token)
|
||||
- **R5-05**: RabbitMQ 消息队列(异步处理)
|
||||
- **R5-06**: Docker Compose 部署
|
||||
|
||||
## v2 需求(后续迭代)
|
||||
|
||||
- 多企业租户支持
|
||||
- 更复杂的反馈闭环和模型自优化
|
||||
- 更丰富的数据分析维度
|
||||
- 移动端App原生应用
|
||||
|
||||
## 数据库表清单
|
||||
|
||||
1. archive_messages - 企微会话存档消息
|
||||
2. conversations - 会话表
|
||||
3. conversation_turns - 对话轮次表
|
||||
4. intent_categories - 意图分类表(40个)
|
||||
5. intent_recognitions - 意图识别记录表
|
||||
6. customer_profiles - 学员画像表
|
||||
7. utterances - 话术库表
|
||||
8. utterance_categories - 话术分类表(8个阶段)
|
||||
9. utterance_performance - 话术效果表
|
||||
10. recommendations - 推荐记录表
|
||||
11. knowledge_documents - 知识库文档表
|
||||
12. staffs - 员工表
|
||||
13. customers - 客户表
|
||||
14. admins - 管理员表
|
||||
15. operation_logs - 操作日志表
|
||||
50
.planning/ROADMAP.md
Normal file
50
.planning/ROADMAP.md
Normal file
@ -0,0 +1,50 @@
|
||||
# 路线图
|
||||
|
||||
## Phase 1: 基础设施 + 数据库 + 公共模块
|
||||
**目标**: 搭建项目骨架,创建所有服务的空壳,完成数据库设计和公共模块
|
||||
**成功标准**:
|
||||
- [ ] 10个微服务目录结构完整
|
||||
- [ ] common模块包含Result、PageResult、异常处理、MyBatis配置、Redis配置、RabbitMQ配置、跨域配置、BaseEntity、AutoFillHandler、JsonUtils、JsonTypeHandler
|
||||
- [ ] init.sql包含15张表创建语句和初始化数据
|
||||
- [ ] docker-compose.yml基础服务(MySQL、Redis、RabbitMQ)
|
||||
- [ ] 网关服务基础路由配置
|
||||
|
||||
## Phase 2: 认证服务 + 会话存档服务
|
||||
**目标**: 实现企微OAuth登录和会话存档拉取
|
||||
**成功标准**:
|
||||
- [ ] auth-service: 企微OAuth2登录、JWT签发、JS-SDK签名
|
||||
- [ ] archive-service: JNA接口、RSA解密、存档拉取、回调接收、消息存储
|
||||
|
||||
## Phase 3: 对话服务 + 意图服务
|
||||
**目标**: 实现对话上下文管理和意图识别
|
||||
**成功标准**:
|
||||
- [ ] conversation-service: 会话管理、上下文窗口(Redis List)、轮次解析、消息事件处理
|
||||
- [ ] intent-service: 40个意图分类、规则匹配+LLM混合识别、学员画像构建、槽位填充
|
||||
|
||||
## Phase 4: 推荐服务 + LLM生成服务
|
||||
**目标**: 实现话术推荐引擎和动态生成
|
||||
**成功标准**:
|
||||
- [ ] recommendation-service: 三层召回(规则/向量/热门)、多因子排序、MMR多样性重排、WebSocket推送
|
||||
- [ ] generation-service: 通义千问API封装、Prompt引擎、Token控制、动态话术生成
|
||||
|
||||
## Phase 5: 前端侧边栏H5
|
||||
**目标**: 实现坐席使用的侧边栏推荐界面
|
||||
**成功标准**:
|
||||
- [ ] PC端侧边栏(360px): 学员画像、阶段指示器、话术推荐卡片、一键发送、搜索面板
|
||||
- [ ] 手机端侧边栏(80%高度): 精简版UI
|
||||
- [ ] 企微JS-SDK集成、WebSocket实时更新
|
||||
|
||||
## Phase 6: 管理后台 + 知识库
|
||||
**目标**: 实现话术库管理和数据分析
|
||||
**成功标准**:
|
||||
- [ ] kb-admin-service: 话术CRUD、分类管理、导入导出、向量生成
|
||||
- [ ] analytics-service: 数据统计、话术效果分析
|
||||
- [ ] admin前端: 登录页、话术库管理、数据看板
|
||||
|
||||
## Phase 7: 部署配置与集成测试
|
||||
**目标**: 完成Docker部署和端到端测试
|
||||
**成功标准**:
|
||||
- [ ] 所有服务的Dockerfile
|
||||
- [ ] docker-compose.yml完整编排
|
||||
- [ ] nginx.conf配置
|
||||
- [ ] 端到端流程验证(回调→拉取→识别→推荐→推送)
|
||||
22
.planning/STATE.md
Normal file
22
.planning/STATE.md
Normal file
@ -0,0 +1,22 @@
|
||||
# 项目状态
|
||||
|
||||
## 当前阶段
|
||||
Phase 1: 基础设施 + 数据库 + 公共模块 (未开始)
|
||||
|
||||
## 已完成
|
||||
- [x] 项目初始化(git repo、.planning目录)
|
||||
- [x] PROJECT.md 创建
|
||||
- [x] REQUIREMENTS.md 创建
|
||||
- [x] ROADMAP.md 创建
|
||||
- [x] config.json 创建
|
||||
|
||||
## 进行中
|
||||
- [ ] Phase 1 开发
|
||||
|
||||
## 待开始
|
||||
- Phase 2: 认证服务 + 会话存档服务
|
||||
- Phase 3: 对话服务 + 意图服务
|
||||
- Phase 4: 推荐服务 + LLM生成服务
|
||||
- Phase 5: 前端侧边栏H5
|
||||
- Phase 6: 管理后台 + 知识库
|
||||
- Phase 7: 部署配置与集成测试
|
||||
8
.planning/config.json
Normal file
8
.planning/config.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"mode": "YOLO",
|
||||
"depth": "Comprehensive",
|
||||
"execution": "Parallel",
|
||||
"projectName": "第九联盟AI坐席辅助系统",
|
||||
"version": "1.0.0",
|
||||
"createdAt": "2026-05-12"
|
||||
}
|
||||
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"kimi.yoloMode": true
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
package com.artedu.analytics;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* 数据分析服务启动类
|
||||
*/
|
||||
@SpringBootApplication(scanBasePackages = {"com.artedu.analytics", "com.artedu.common"})
|
||||
public class AnalyticsServiceApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(AnalyticsServiceApplication.class, args);
|
||||
}
|
||||
}
|
||||
35
backend/analytics-service/src/main/resources/application.yml
Normal file
35
backend/analytics-service/src/main/resources/application.yml
Normal file
@ -0,0 +1,35 @@
|
||||
server:
|
||||
port: 8088
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: analytics-service
|
||||
datasource:
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
url: jdbc:mysql://${MYSQL_HOST:localhost}:${MYSQL_PORT:3306}/${MYSQL_DATABASE:ai_assistant}?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai&useSSL=false
|
||||
username: ${MYSQL_USERNAME:root}
|
||||
password: ${MYSQL_PASSWORD:root}
|
||||
redis:
|
||||
host: ${REDIS_HOST:localhost}
|
||||
port: ${REDIS_PORT:6379}
|
||||
password: ${REDIS_PASSWORD:}
|
||||
database: 0
|
||||
lettuce:
|
||||
pool:
|
||||
max-active: 8
|
||||
max-idle: 8
|
||||
min-idle: 0
|
||||
rabbitmq:
|
||||
host: ${RABBITMQ_HOST:localhost}
|
||||
port: ${RABBITMQ_PORT:5672}
|
||||
username: ${RABBITMQ_USERNAME:admin}
|
||||
password: ${RABBITMQ_PASSWORD:admin}
|
||||
|
||||
mybatis-plus:
|
||||
configuration:
|
||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||
global-config:
|
||||
db-config:
|
||||
logic-delete-field: deleted
|
||||
logic-delete-value: 1
|
||||
logic-not-delete-value: 0
|
||||
24
backend/archive-service/pom.xml
Normal file
24
backend/archive-service/pom.xml
Normal file
@ -0,0 +1,24 @@
|
||||
<?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>
|
||||
<parent>
|
||||
<groupId>com.artedu</groupId>
|
||||
<artifactId>ai-assistant-parent</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>archive-service</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>会话存档服务</name>
|
||||
<dependencies>
|
||||
<dependency><groupId>com.artedu</groupId><artifactId>common</artifactId><version>${project.version}</version></dependency>
|
||||
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>
|
||||
<dependency><groupId>net.java.dev.jna</groupId><artifactId>jna</artifactId></dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@ -0,0 +1,15 @@
|
||||
package com.artedu.archive;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* 会话存档服务启动类
|
||||
*/
|
||||
@SpringBootApplication(scanBasePackages = {"com.artedu.archive", "com.artedu.common"})
|
||||
public class ArchiveServiceApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(ArchiveServiceApplication.class, args);
|
||||
}
|
||||
}
|
||||
35
backend/archive-service/src/main/resources/application.yml
Normal file
35
backend/archive-service/src/main/resources/application.yml
Normal file
@ -0,0 +1,35 @@
|
||||
server:
|
||||
port: 8082
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: archive-service
|
||||
datasource:
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
url: jdbc:mysql://${MYSQL_HOST:localhost}:${MYSQL_PORT:3306}/${MYSQL_DATABASE:ai_assistant}?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai&useSSL=false
|
||||
username: ${MYSQL_USERNAME:root}
|
||||
password: ${MYSQL_PASSWORD:root}
|
||||
redis:
|
||||
host: ${REDIS_HOST:localhost}
|
||||
port: ${REDIS_PORT:6379}
|
||||
password: ${REDIS_PASSWORD:}
|
||||
database: 0
|
||||
lettuce:
|
||||
pool:
|
||||
max-active: 8
|
||||
max-idle: 8
|
||||
min-idle: 0
|
||||
rabbitmq:
|
||||
host: ${RABBITMQ_HOST:localhost}
|
||||
port: ${RABBITMQ_PORT:5672}
|
||||
username: ${RABBITMQ_USERNAME:admin}
|
||||
password: ${RABBITMQ_PASSWORD:admin}
|
||||
|
||||
mybatis-plus:
|
||||
configuration:
|
||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||
global-config:
|
||||
db-config:
|
||||
logic-delete-field: deleted
|
||||
logic-delete-value: 1
|
||||
logic-not-delete-value: 0
|
||||
26
backend/auth-service/pom.xml
Normal file
26
backend/auth-service/pom.xml
Normal file
@ -0,0 +1,26 @@
|
||||
<?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>
|
||||
<parent>
|
||||
<groupId>com.artedu</groupId>
|
||||
<artifactId>ai-assistant-parent</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>auth-service</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>认证授权服务</name>
|
||||
<dependencies>
|
||||
<dependency><groupId>com.artedu</groupId><artifactId>common</artifactId><version>${project.version}</version></dependency>
|
||||
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>
|
||||
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-api</artifactId></dependency>
|
||||
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-impl</artifactId><scope>runtime</scope></dependency>
|
||||
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-jackson</artifactId><scope>runtime</scope></dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@ -0,0 +1,15 @@
|
||||
package com.artedu.auth;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* 认证授权服务启动类
|
||||
*/
|
||||
@SpringBootApplication(scanBasePackages = {"com.artedu.auth", "com.artedu.common"})
|
||||
public class AuthServiceApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(AuthServiceApplication.class, args);
|
||||
}
|
||||
}
|
||||
39
backend/auth-service/src/main/resources/application.yml
Normal file
39
backend/auth-service/src/main/resources/application.yml
Normal file
@ -0,0 +1,39 @@
|
||||
server:
|
||||
port: 8081
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: auth-service
|
||||
datasource:
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
url: jdbc:mysql://${MYSQL_HOST:localhost}:${MYSQL_PORT:3306}/${MYSQL_DATABASE:ai_assistant}?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai&useSSL=false
|
||||
username: ${MYSQL_USERNAME:root}
|
||||
password: ${MYSQL_PASSWORD:root}
|
||||
redis:
|
||||
host: ${REDIS_HOST:localhost}
|
||||
port: ${REDIS_PORT:6379}
|
||||
password: ${REDIS_PASSWORD:}
|
||||
database: 0
|
||||
lettuce:
|
||||
pool:
|
||||
max-active: 8
|
||||
max-idle: 8
|
||||
min-idle: 0
|
||||
|
||||
wecom:
|
||||
corp-id: ${WECOM_CORP_ID:}
|
||||
agent-id: ${WECOM_AGENT_ID:}
|
||||
secret: ${WECOM_SECRET:}
|
||||
|
||||
jwt:
|
||||
secret: ${JWT_SECRET:your-256-bit-secret-key-here-at-least-32-characters}
|
||||
expiration: 86400
|
||||
|
||||
mybatis-plus:
|
||||
configuration:
|
||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||
global-config:
|
||||
db-config:
|
||||
logic-delete-field: deleted
|
||||
logic-delete-value: 1
|
||||
logic-not-delete-value: 0
|
||||
54
backend/common/pom.xml
Normal file
54
backend/common/pom.xml
Normal file
@ -0,0 +1,54 @@
|
||||
<?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>
|
||||
|
||||
<parent>
|
||||
<groupId>com.artedu</groupId>
|
||||
<artifactId>ai-assistant-parent</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>common</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>公共模块</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</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-data-redis</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-amqp</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@ -0,0 +1,32 @@
|
||||
package com.artedu.common.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
import org.springframework.web.filter.CorsFilter;
|
||||
|
||||
/**
|
||||
* 跨域配置
|
||||
* 允许企微H5侧边栏跨域访问API
|
||||
*/
|
||||
@Configuration
|
||||
public class CorsConfig {
|
||||
|
||||
@Bean
|
||||
public CorsFilter corsFilter() {
|
||||
CorsConfiguration config = new CorsConfiguration();
|
||||
config.addAllowedOriginPattern("*");
|
||||
config.setAllowCredentials(true);
|
||||
config.addAllowedHeader("*");
|
||||
config.addAllowedMethod("*");
|
||||
config.addExposedHeader("Authorization");
|
||||
config.addExposedHeader("X-Request-Id");
|
||||
config.setMaxAge(3600L);
|
||||
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
source.registerCorsConfiguration("/**", config);
|
||||
|
||||
return new CorsFilter(source);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
package com.artedu.common.config;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.DbType;
|
||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* MyBatis-Plus配置
|
||||
* 启用分页插件(MySQL 5.7方言)
|
||||
*/
|
||||
@Configuration
|
||||
public class MyBatisPlusConfig {
|
||||
|
||||
@Bean
|
||||
public MybatisPlusInterceptor mybatisPlusInterceptor() {
|
||||
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
|
||||
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
|
||||
return interceptor;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,79 @@
|
||||
package com.artedu.common.config;
|
||||
|
||||
import org.springframework.amqp.core.*;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* RabbitMQ配置
|
||||
* 定义消息队列、交换机和绑定关系
|
||||
*/
|
||||
@Configuration
|
||||
public class RabbitConfig {
|
||||
|
||||
public static final String ARCHIVE_EXCHANGE = "archive.exchange";
|
||||
public static final String CONVERSATION_EXCHANGE = "conversation.exchange";
|
||||
|
||||
public static final String QUEUE_NEW_MESSAGE = "archive.new_message.queue";
|
||||
public static final String QUEUE_INTENT_ANALYZE = "conversation.intent_analyze.queue";
|
||||
public static final String QUEUE_RECOMMEND = "intent.recommend.queue";
|
||||
|
||||
public static final String ROUTING_KEY_NEW_MESSAGE = "archive.message.new";
|
||||
public static final String ROUTING_KEY_INTENT_ANALYZE = "conversation.intent.analyze";
|
||||
public static final String ROUTING_KEY_RECOMMEND = "intent.recommend";
|
||||
|
||||
@Bean
|
||||
public DirectExchange archiveExchange() {
|
||||
return new DirectExchange(ARCHIVE_EXCHANGE, true, false);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TopicExchange conversationExchange() {
|
||||
return new TopicExchange(CONVERSATION_EXCHANGE, true, false);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Queue newMessageQueue() {
|
||||
return QueueBuilder.durable(QUEUE_NEW_MESSAGE)
|
||||
.withArgument("x-dead-letter-exchange", "")
|
||||
.withArgument("x-dead-letter-routing-key", QUEUE_NEW_MESSAGE + ".dlq")
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Queue intentAnalyzeQueue() {
|
||||
return QueueBuilder.durable(QUEUE_INTENT_ANALYZE)
|
||||
.withArgument("x-dead-letter-exchange", "")
|
||||
.withArgument("x-dead-letter-routing-key", QUEUE_INTENT_ANALYZE + ".dlq")
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Queue recommendQueue() {
|
||||
return QueueBuilder.durable(QUEUE_RECOMMEND)
|
||||
.withArgument("x-dead-letter-exchange", "")
|
||||
.withArgument("x-dead-letter-routing-key", QUEUE_RECOMMEND + ".dlq")
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Binding newMessageBinding() {
|
||||
return BindingBuilder.bind(newMessageQueue())
|
||||
.to(archiveExchange())
|
||||
.with(ROUTING_KEY_NEW_MESSAGE);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Binding intentAnalyzeBinding() {
|
||||
return BindingBuilder.bind(intentAnalyzeQueue())
|
||||
.to(conversationExchange())
|
||||
.with(ROUTING_KEY_INTENT_ANALYZE);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Binding recommendBinding() {
|
||||
return BindingBuilder.bind(recommendQueue())
|
||||
.to(conversationExchange())
|
||||
.with(ROUTING_KEY_RECOMMEND);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,42 @@
|
||||
package com.artedu.common.config;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
|
||||
/**
|
||||
* Redis配置
|
||||
* 配置Key和Value的序列化方式
|
||||
*/
|
||||
@Configuration
|
||||
public class RedisConfig {
|
||||
|
||||
@Bean
|
||||
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
|
||||
RedisTemplate<String, Object> template = new RedisTemplate<>();
|
||||
template.setConnectionFactory(connectionFactory);
|
||||
|
||||
StringRedisSerializer stringSerializer = new StringRedisSerializer();
|
||||
template.setKeySerializer(stringSerializer);
|
||||
template.setHashKeySerializer(stringSerializer);
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.activateDefaultTyping(
|
||||
LaissezFaireSubTypeValidator.instance,
|
||||
ObjectMapper.DefaultTyping.NON_FINAL,
|
||||
JsonTypeInfo.As.PROPERTY
|
||||
);
|
||||
GenericJackson2JsonRedisSerializer jsonSerializer = new GenericJackson2JsonRedisSerializer(mapper);
|
||||
template.setValueSerializer(jsonSerializer);
|
||||
template.setHashValueSerializer(jsonSerializer);
|
||||
|
||||
template.afterPropertiesSet();
|
||||
return template;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
package com.artedu.common.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 通用实体基类
|
||||
* 所有实体类继承此类,自动填充创建时间和更新时间
|
||||
*/
|
||||
@Data
|
||||
public class BaseEntity {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
package com.artedu.common.exception;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 业务异常
|
||||
* 用于抛出可预期的业务逻辑错误
|
||||
*/
|
||||
@Getter
|
||||
public class BusinessException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final int code;
|
||||
|
||||
public BusinessException(String message) {
|
||||
super(message);
|
||||
this.code = -1;
|
||||
}
|
||||
|
||||
public BusinessException(int code, String message) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public BusinessException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
this.code = -1;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
package com.artedu.common.exception;
|
||||
|
||||
import com.artedu.common.result.Result;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
/**
|
||||
* 全局异常处理器
|
||||
* 统一处理控制器层抛出的异常,转换为标准响应格式
|
||||
*/
|
||||
@Slf4j
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
@ExceptionHandler(BusinessException.class)
|
||||
public Result<Void> handleBusinessException(BusinessException e) {
|
||||
log.warn("业务异常: {}", e.getMessage());
|
||||
return Result.fail(e.getCode(), e.getMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler(BindException.class)
|
||||
public Result<Void> handleBindException(BindException e) {
|
||||
String message = e.getBindingResult().getFieldErrors().stream()
|
||||
.map(error -> error.getField() + ": " + error.getDefaultMessage())
|
||||
.findFirst()
|
||||
.orElse("参数校验失败");
|
||||
log.warn("参数校验失败: {}", message);
|
||||
return Result.fail(400, message);
|
||||
}
|
||||
|
||||
@ExceptionHandler(Exception.class)
|
||||
public Result<Void> handleException(Exception e) {
|
||||
log.error("系统异常: {}", e.getMessage(), e);
|
||||
return Result.fail(500, "系统繁忙,请稍后重试");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
package com.artedu.common.handler;
|
||||
|
||||
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.ibatis.reflection.MetaObject;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* MyBatis-Plus字段自动填充处理器
|
||||
* 自动填充created_at和updated_at字段
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class AutoFillHandler implements MetaObjectHandler {
|
||||
|
||||
@Override
|
||||
public void insertFill(MetaObject metaObject) {
|
||||
this.strictInsertFill(metaObject, "createdAt", LocalDateTime.class, LocalDateTime.now());
|
||||
this.strictInsertFill(metaObject, "updatedAt", LocalDateTime.class, LocalDateTime.now());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFill(MetaObject metaObject) {
|
||||
this.strictUpdateFill(metaObject, "updatedAt", LocalDateTime.class, LocalDateTime.now());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,61 @@
|
||||
package com.artedu.common.mybatis;
|
||||
|
||||
import com.artedu.common.util.JsonUtils;
|
||||
import org.apache.ibatis.type.BaseTypeHandler;
|
||||
import org.apache.ibatis.type.JdbcType;
|
||||
import org.apache.ibatis.type.MappedJdbcTypes;
|
||||
import org.apache.ibatis.type.MappedTypes;
|
||||
|
||||
import java.sql.CallableStatement;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* MyBatis JSON类型处理器 - 用于MySQL 5.7 TEXT字段
|
||||
* 自动将Java对象序列化为JSON字符串存入TEXT字段
|
||||
* 读取时自动将JSON字符串反序列化为Java对象
|
||||
*/
|
||||
@MappedJdbcTypes(JdbcType.VARCHAR)
|
||||
@MappedTypes(Object.class)
|
||||
public class JsonTypeHandler extends BaseTypeHandler<Object> {
|
||||
|
||||
private final Class<?> type;
|
||||
|
||||
public JsonTypeHandler(Class<?> type) {
|
||||
if (type == null) {
|
||||
throw new IllegalArgumentException("Type argument cannot be null");
|
||||
}
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException {
|
||||
ps.setString(i, JsonUtils.toJson(parameter));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getNullableResult(ResultSet rs, String columnName) throws SQLException {
|
||||
String json = rs.getString(columnName);
|
||||
return parseJson(json);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
|
||||
String json = rs.getString(columnIndex);
|
||||
return parseJson(json);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
|
||||
String json = cs.getString(columnIndex);
|
||||
return parseJson(json);
|
||||
}
|
||||
|
||||
private Object parseJson(String json) {
|
||||
if (json == null || json.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return JsonUtils.fromJson(json, type);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
package com.artedu.common.result;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 分页响应结构
|
||||
*/
|
||||
@Data
|
||||
public class PageResult<T> implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Integer page;
|
||||
private Integer size;
|
||||
private Long total;
|
||||
private Integer totalPages;
|
||||
private List<T> list;
|
||||
|
||||
public static <T> PageResult<T> of(Integer page, Integer size, Long total, List<T> list) {
|
||||
PageResult<T> result = new PageResult<>();
|
||||
result.setPage(page);
|
||||
result.setSize(size);
|
||||
result.setTotal(total);
|
||||
result.setTotalPages((int) Math.ceil((double) total / size));
|
||||
result.setList(list != null ? list : Collections.emptyList());
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,82 @@
|
||||
package com.artedu.common.result;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 统一API响应结构
|
||||
* 所有接口返回此结构,确保前端处理一致
|
||||
*
|
||||
* @param <T> 响应数据类型
|
||||
*/
|
||||
@Data
|
||||
public class Result<T> implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 状态码: 0-成功 其他-失败
|
||||
*/
|
||||
private Integer code;
|
||||
|
||||
/**
|
||||
* 提示消息
|
||||
*/
|
||||
private String message;
|
||||
|
||||
/**
|
||||
* 响应数据
|
||||
*/
|
||||
private T data;
|
||||
|
||||
/**
|
||||
* 时间戳
|
||||
*/
|
||||
private Long timestamp;
|
||||
|
||||
public Result() {
|
||||
this.timestamp = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public static <T> Result<T> success() {
|
||||
Result<T> result = new Result<>();
|
||||
result.setCode(0);
|
||||
result.setMessage("success");
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <T> Result<T> success(T data) {
|
||||
Result<T> result = new Result<>();
|
||||
result.setCode(0);
|
||||
result.setMessage("success");
|
||||
result.setData(data);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <T> Result<T> success(T data, String message) {
|
||||
Result<T> result = new Result<>();
|
||||
result.setCode(0);
|
||||
result.setMessage(message);
|
||||
result.setData(data);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <T> Result<T> fail(String message) {
|
||||
Result<T> result = new Result<>();
|
||||
result.setCode(-1);
|
||||
result.setMessage(message);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <T> Result<T> fail(int code, String message) {
|
||||
Result<T> result = new Result<>();
|
||||
result.setCode(code);
|
||||
result.setMessage(message);
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean isSuccess() {
|
||||
return this.code != null && this.code == 0;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,82 @@
|
||||
package com.artedu.common.util;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* JSON工具类 - 用于MySQL 5.7 TEXT字段的序列化/反序列化
|
||||
* 替代MySQL 8.0的JSON类型
|
||||
*/
|
||||
@Slf4j
|
||||
public class JsonUtils {
|
||||
|
||||
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||
|
||||
static {
|
||||
OBJECT_MAPPER.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
||||
}
|
||||
|
||||
public static String toJson(Object obj) {
|
||||
if (obj == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return OBJECT_MAPPER.writeValueAsString(obj);
|
||||
} catch (JsonProcessingException e) {
|
||||
log.error("JSON序列化失败: {}", e.getMessage(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> T fromJson(String json, Class<T> clazz) {
|
||||
if (json == null || json.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return OBJECT_MAPPER.readValue(json, clazz);
|
||||
} catch (JsonProcessingException e) {
|
||||
log.error("JSON反序列化失败: {}", e.getMessage(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> List<T> fromJsonList(String json, Class<T> clazz) {
|
||||
if (json == null || json.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
try {
|
||||
return OBJECT_MAPPER.readValue(json, OBJECT_MAPPER.getTypeFactory().constructCollectionType(List.class, clazz));
|
||||
} catch (JsonProcessingException e) {
|
||||
log.error("JSON反序列化为List失败: {}", e.getMessage(), e);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
public static Map<String, Object> fromJsonMap(String json) {
|
||||
if (json == null || json.isEmpty()) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
try {
|
||||
return OBJECT_MAPPER.readValue(json, new TypeReference<Map<String, Object>>() {});
|
||||
} catch (JsonProcessingException e) {
|
||||
log.error("JSON反序列化为Map失败: {}", e.getMessage(), e);
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
}
|
||||
|
||||
public static String safeJson(Object obj) {
|
||||
String json = toJson(obj);
|
||||
return json != null ? json : "{}";
|
||||
}
|
||||
|
||||
public static String safeParse(String json) {
|
||||
return json != null && !json.isEmpty() ? json : "{}";
|
||||
}
|
||||
}
|
||||
24
backend/conversation-service/pom.xml
Normal file
24
backend/conversation-service/pom.xml
Normal file
@ -0,0 +1,24 @@
|
||||
<?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>
|
||||
<parent>
|
||||
<groupId>com.artedu</groupId>
|
||||
<artifactId>ai-assistant-parent</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>conversation-service</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>对话管理服务</name>
|
||||
<dependencies>
|
||||
<dependency><groupId>com.artedu</groupId><artifactId>common</artifactId><version>${project.version}</version></dependency>
|
||||
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>
|
||||
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@ -0,0 +1,15 @@
|
||||
package com.artedu.conversation;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* 对话管理服务启动类
|
||||
*/
|
||||
@SpringBootApplication(scanBasePackages = {"com.artedu.conversation", "com.artedu.common"})
|
||||
public class ConversationServiceApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(ConversationServiceApplication.class, args);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
server:
|
||||
port: 8083
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: conversation-service
|
||||
datasource:
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
url: jdbc:mysql://${MYSQL_HOST:localhost}:${MYSQL_PORT:3306}/${MYSQL_DATABASE:ai_assistant}?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai&useSSL=false
|
||||
username: ${MYSQL_USERNAME:root}
|
||||
password: ${MYSQL_PASSWORD:root}
|
||||
redis:
|
||||
host: ${REDIS_HOST:localhost}
|
||||
port: ${REDIS_PORT:6379}
|
||||
password: ${REDIS_PASSWORD:}
|
||||
database: 0
|
||||
lettuce:
|
||||
pool:
|
||||
max-active: 8
|
||||
max-idle: 8
|
||||
min-idle: 0
|
||||
rabbitmq:
|
||||
host: ${RABBITMQ_HOST:localhost}
|
||||
port: ${RABBITMQ_PORT:5672}
|
||||
username: ${RABBITMQ_USERNAME:admin}
|
||||
password: ${RABBITMQ_PASSWORD:admin}
|
||||
|
||||
mybatis-plus:
|
||||
configuration:
|
||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||
global-config:
|
||||
db-config:
|
||||
logic-delete-field: deleted
|
||||
logic-delete-value: 1
|
||||
logic-not-delete-value: 0
|
||||
28
backend/gateway/pom.xml
Normal file
28
backend/gateway/pom.xml
Normal file
@ -0,0 +1,28 @@
|
||||
<?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>
|
||||
<parent>
|
||||
<groupId>com.artedu</groupId>
|
||||
<artifactId>ai-assistant-parent</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>gateway</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>API网关</name>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-gateway</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@ -0,0 +1,15 @@
|
||||
package com.artedu.gateway;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* API网关启动类
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class GatewayApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(GatewayApplication.class, args);
|
||||
}
|
||||
}
|
||||
48
backend/gateway/src/main/resources/application.yml
Normal file
48
backend/gateway/src/main/resources/application.yml
Normal file
@ -0,0 +1,48 @@
|
||||
server:
|
||||
port: 8080
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: gateway
|
||||
cloud:
|
||||
gateway:
|
||||
routes:
|
||||
- id: auth-service
|
||||
uri: http://localhost:8081
|
||||
predicates:
|
||||
- Path=/api/v1/auth/**
|
||||
- id: archive-service
|
||||
uri: http://localhost:8082
|
||||
predicates:
|
||||
- Path=/api/v1/archive/**
|
||||
- id: conversation-service
|
||||
uri: http://localhost:8083
|
||||
predicates:
|
||||
- Path=/api/v1/conversations/**
|
||||
- id: intent-service
|
||||
uri: http://localhost:8084
|
||||
predicates:
|
||||
- Path=/api/v1/intent/**
|
||||
- id: recommend-service
|
||||
uri: http://localhost:8085
|
||||
predicates:
|
||||
- Path=/api/v1/recommend/**
|
||||
- id: generation-service
|
||||
uri: http://localhost:8086
|
||||
predicates:
|
||||
- Path=/api/v1/generation/**
|
||||
- id: kb-admin-service
|
||||
uri: http://localhost:8087
|
||||
predicates:
|
||||
- Path=/api/v1/admin/**
|
||||
- id: analytics-service
|
||||
uri: http://localhost:8088
|
||||
predicates:
|
||||
- Path=/api/v1/analytics/**
|
||||
globalcors:
|
||||
cors-configurations:
|
||||
'[/**]':
|
||||
allowedOrigins: "*"
|
||||
allowedMethods: "*"
|
||||
allowedHeaders: "*"
|
||||
allowCredentials: true
|
||||
23
backend/generation-service/pom.xml
Normal file
23
backend/generation-service/pom.xml
Normal file
@ -0,0 +1,23 @@
|
||||
<?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>
|
||||
<parent>
|
||||
<groupId>com.artedu</groupId>
|
||||
<artifactId>ai-assistant-parent</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>generation-service</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>LLM生成服务</name>
|
||||
<dependencies>
|
||||
<dependency><groupId>com.artedu</groupId><artifactId>common</artifactId><version>${project.version}</version></dependency>
|
||||
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@ -0,0 +1,15 @@
|
||||
package com.artedu.generation;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* LLM生成服务启动类
|
||||
*/
|
||||
@SpringBootApplication(scanBasePackages = {"com.artedu.generation", "com.artedu.common"})
|
||||
public class GenerationServiceApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(GenerationServiceApplication.class, args);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
server:
|
||||
port: 8086
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: generation-service
|
||||
datasource:
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
url: jdbc:mysql://${MYSQL_HOST:localhost}:${MYSQL_PORT:3306}/${MYSQL_DATABASE:ai_assistant}?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai&useSSL=false
|
||||
username: ${MYSQL_USERNAME:root}
|
||||
password: ${MYSQL_PASSWORD:root}
|
||||
redis:
|
||||
host: ${REDIS_HOST:localhost}
|
||||
port: ${REDIS_PORT:6379}
|
||||
password: ${REDIS_PASSWORD:}
|
||||
database: 0
|
||||
lettuce:
|
||||
pool:
|
||||
max-active: 8
|
||||
max-idle: 8
|
||||
min-idle: 0
|
||||
rabbitmq:
|
||||
host: ${RABBITMQ_HOST:localhost}
|
||||
port: ${RABBITMQ_PORT:5672}
|
||||
username: ${RABBITMQ_USERNAME:admin}
|
||||
password: ${RABBITMQ_PASSWORD:admin}
|
||||
|
||||
mybatis-plus:
|
||||
configuration:
|
||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||
global-config:
|
||||
db-config:
|
||||
logic-delete-field: deleted
|
||||
logic-delete-value: 1
|
||||
logic-not-delete-value: 0
|
||||
23
backend/intent-service/pom.xml
Normal file
23
backend/intent-service/pom.xml
Normal file
@ -0,0 +1,23 @@
|
||||
<?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>
|
||||
<parent>
|
||||
<groupId>com.artedu</groupId>
|
||||
<artifactId>ai-assistant-parent</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>intent-service</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>意图识别服务</name>
|
||||
<dependencies>
|
||||
<dependency><groupId>com.artedu</groupId><artifactId>common</artifactId><version>${project.version}</version></dependency>
|
||||
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@ -0,0 +1,15 @@
|
||||
package com.artedu.intent;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* 意图识别服务启动类
|
||||
*/
|
||||
@SpringBootApplication(scanBasePackages = {"com.artedu.intent", "com.artedu.common"})
|
||||
public class IntentServiceApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(IntentServiceApplication.class, args);
|
||||
}
|
||||
}
|
||||
35
backend/intent-service/src/main/resources/application.yml
Normal file
35
backend/intent-service/src/main/resources/application.yml
Normal file
@ -0,0 +1,35 @@
|
||||
server:
|
||||
port: 8084
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: intent-service
|
||||
datasource:
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
url: jdbc:mysql://${MYSQL_HOST:localhost}:${MYSQL_PORT:3306}/${MYSQL_DATABASE:ai_assistant}?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai&useSSL=false
|
||||
username: ${MYSQL_USERNAME:root}
|
||||
password: ${MYSQL_PASSWORD:root}
|
||||
redis:
|
||||
host: ${REDIS_HOST:localhost}
|
||||
port: ${REDIS_PORT:6379}
|
||||
password: ${REDIS_PASSWORD:}
|
||||
database: 0
|
||||
lettuce:
|
||||
pool:
|
||||
max-active: 8
|
||||
max-idle: 8
|
||||
min-idle: 0
|
||||
rabbitmq:
|
||||
host: ${RABBITMQ_HOST:localhost}
|
||||
port: ${RABBITMQ_PORT:5672}
|
||||
username: ${RABBITMQ_USERNAME:admin}
|
||||
password: ${RABBITMQ_PASSWORD:admin}
|
||||
|
||||
mybatis-plus:
|
||||
configuration:
|
||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||
global-config:
|
||||
db-config:
|
||||
logic-delete-field: deleted
|
||||
logic-delete-value: 1
|
||||
logic-not-delete-value: 0
|
||||
16
backend/kb-admin-service/pom.xml
Normal file
16
backend/kb-admin-service/pom.xml
Normal file
@ -0,0 +1,16 @@
|
||||
<?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>
|
||||
<parent>
|
||||
<groupId>com.artedu</groupId>
|
||||
<artifactId>ai-assistant-parent</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>kb-admin-service</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>话术库管理服务</name>
|
||||
<dependencies>
|
||||
<dependency><groupId>com.artedu</groupId><artifactId>common</artifactId><version>${project.version}</version></dependency>
|
||||
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-we
|
||||
@ -0,0 +1,15 @@
|
||||
package com.artedu.kbadmin;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* 话术库管理服务启动类
|
||||
*/
|
||||
@SpringBootApplication(scanBasePackages = {"com.artedu.kbadmin", "com.artedu.common"})
|
||||
public class KbAdminServiceApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(KbAdminServiceApplication.class, args);
|
||||
}
|
||||
}
|
||||
35
backend/kb-admin-service/src/main/resources/application.yml
Normal file
35
backend/kb-admin-service/src/main/resources/application.yml
Normal file
@ -0,0 +1,35 @@
|
||||
server:
|
||||
port: 8087
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: kb-admin-service
|
||||
datasource:
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
url: jdbc:mysql://${MYSQL_HOST:localhost}:${MYSQL_PORT:3306}/${MYSQL_DATABASE:ai_assistant}?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai&useSSL=false
|
||||
username: ${MYSQL_USERNAME:root}
|
||||
password: ${MYSQL_PASSWORD:root}
|
||||
redis:
|
||||
host: ${REDIS_HOST:localhost}
|
||||
port: ${REDIS_PORT:6379}
|
||||
password: ${REDIS_PASSWORD:}
|
||||
database: 0
|
||||
lettuce:
|
||||
pool:
|
||||
max-active: 8
|
||||
max-idle: 8
|
||||
min-idle: 0
|
||||
rabbitmq:
|
||||
host: ${RABBITMQ_HOST:localhost}
|
||||
port: ${RABBITMQ_PORT:5672}
|
||||
username: ${RABBITMQ_USERNAME:admin}
|
||||
password: ${RABBITMQ_PASSWORD:admin}
|
||||
|
||||
mybatis-plus:
|
||||
configuration:
|
||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||
global-config:
|
||||
db-config:
|
||||
logic-delete-field: deleted
|
||||
logic-delete-value: 1
|
||||
logic-not-delete-value: 0
|
||||
97
backend/pom.xml
Normal file
97
backend/pom.xml
Normal file
@ -0,0 +1,97 @@
|
||||
<?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.artedu</groupId>
|
||||
<artifactId>ai-assistant-parent</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>第九联盟AI坐席辅助系统</name>
|
||||
<description>基于企微会话存档+LLM的实时话术推荐系统</description>
|
||||
|
||||
<modules>
|
||||
<module>common</module>
|
||||
<module>gateway</module>
|
||||
<module>auth-service</module>
|
||||
<module>archive-service</module>
|
||||
<module>conversation-service</module>
|
||||
<module>intent-service</module>
|
||||
<module>recommendation-service</module>
|
||||
<module>generation-service</module>
|
||||
<module>kb-admin-service</module>
|
||||
<module>analytics-service</module>
|
||||
</modules>
|
||||
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
|
||||
<spring-boot.version>2.7.18</spring-boot.version>
|
||||
<spring-cloud.version>2021.0.8</spring-cloud.version>
|
||||
<mybatis-plus.version>3.5.5</mybatis-plus.version>
|
||||
<jjwt.version>0.11.5</jjwt.version>
|
||||
<jna.version>5.13.0</jna.version>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-dependencies</artifactId>
|
||||
<version>${spring-boot.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-dependencies</artifactId>
|
||||
<version>${spring-cloud.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||||
<version>${mybatis-plus.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-api</artifactId>
|
||||
<version>${jjwt.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-impl</artifactId>
|
||||
<version>${jjwt.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-jackson</artifactId>
|
||||
<version>${jjwt.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.java.dev.jna</groupId>
|
||||
<artifactId>jna</artifactId>
|
||||
<version>${jna.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<build>
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<version>${spring-boot.version}</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
</build>
|
||||
</project>
|
||||
24
backend/recommendation-service/pom.xml
Normal file
24
backend/recommendation-service/pom.xml
Normal file
@ -0,0 +1,24 @@
|
||||
<?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>
|
||||
<parent>
|
||||
<groupId>com.artedu</groupId>
|
||||
<artifactId>ai-assistant-parent</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>recommendation-service</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>话术推荐服务</name>
|
||||
<dependencies>
|
||||
<dependency><groupId>com.artedu</groupId><artifactId>common</artifactId><version>${project.version}</version></dependency>
|
||||
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>
|
||||
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@ -0,0 +1,15 @@
|
||||
package com.artedu.recommend;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* 话术推荐服务启动类
|
||||
*/
|
||||
@SpringBootApplication(scanBasePackages = {"com.artedu.recommend", "com.artedu.common"})
|
||||
public class RecommendationServiceApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(RecommendationServiceApplication.class, args);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
server:
|
||||
port: 8085
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: recommendation-service
|
||||
datasource:
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
url: jdbc:mysql://${MYSQL_HOST:localhost}:${MYSQL_PORT:3306}/${MYSQL_DATABASE:ai_assistant}?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai&useSSL=false
|
||||
username: ${MYSQL_USERNAME:root}
|
||||
password: ${MYSQL_PASSWORD:root}
|
||||
redis:
|
||||
host: ${REDIS_HOST:localhost}
|
||||
port: ${REDIS_PORT:6379}
|
||||
password: ${REDIS_PASSWORD:}
|
||||
database: 0
|
||||
lettuce:
|
||||
pool:
|
||||
max-active: 8
|
||||
max-idle: 8
|
||||
min-idle: 0
|
||||
rabbitmq:
|
||||
host: ${RABBITMQ_HOST:localhost}
|
||||
port: ${RABBITMQ_PORT:5672}
|
||||
username: ${RABBITMQ_USERNAME:admin}
|
||||
password: ${RABBITMQ_PASSWORD:admin}
|
||||
|
||||
mybatis-plus:
|
||||
configuration:
|
||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||
global-config:
|
||||
db-config:
|
||||
logic-delete-field: deleted
|
||||
logic-delete-value: 1
|
||||
logic-not-delete-value: 0
|
||||
60
docker-compose.yml
Normal file
60
docker-compose.yml
Normal file
@ -0,0 +1,60 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
mysql:
|
||||
image: mysql:5.7
|
||||
container_name: ai-assistant-mysql
|
||||
restart: always
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: root
|
||||
MYSQL_DATABASE: ai_assistant
|
||||
MYSQL_CHARACTER_SET_SERVER: utf8mb4
|
||||
MYSQL_COLLATION_SERVER: utf8mb4_unicode_ci
|
||||
ports:
|
||||
- "3306:3306"
|
||||
volumes:
|
||||
- mysql_data:/var/lib/mysql
|
||||
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
|
||||
command: --default-authentication-plugin=mysql_native_password
|
||||
healthcheck:
|
||||
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-proot"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
redis:
|
||||
image: redis:6-alpine
|
||||
container_name: ai-assistant-redis
|
||||
restart: always
|
||||
ports:
|
||||
- "6379:6379"
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
rabbitmq:
|
||||
image: rabbitmq:3-management-alpine
|
||||
container_name: ai-assistant-rabbitmq
|
||||
restart: always
|
||||
ports:
|
||||
- "5672:5672"
|
||||
- "15672:15672"
|
||||
environment:
|
||||
RABBITMQ_DEFAULT_USER: admin
|
||||
RABBITMQ_DEFAULT_PASS: admin
|
||||
volumes:
|
||||
- rabbitmq_data:/var/lib/rabbitmq
|
||||
healthcheck:
|
||||
test: ["CMD", "rabbitmq-diagnostics", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
volumes:
|
||||
mysql_data:
|
||||
redis_data:
|
||||
rabbitmq_data:
|
||||
459
init.sql
Normal file
459
init.sql
Normal file
@ -0,0 +1,459 @@
|
||||
-- ============================================================
|
||||
-- 第九联盟AI坐席辅助系统 - 数据库初始化脚本
|
||||
-- MySQL 5.7适配版
|
||||
-- ============================================================
|
||||
|
||||
-- 创建数据库
|
||||
CREATE DATABASE IF NOT EXISTS `ai_assistant` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
USE `ai_assistant`;
|
||||
|
||||
-- ============================================================
|
||||
-- 企微会话存档消息表 (archive_messages)
|
||||
-- ============================================================
|
||||
CREATE TABLE IF NOT EXISTS `archive_messages` (
|
||||
`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT COMMENT '自增主键',
|
||||
`msgid` VARCHAR(255) NOT NULL COMMENT '企微消息唯一ID',
|
||||
`seq` BIGINT(20) NOT NULL COMMENT '存档序列号,用于增量拉取',
|
||||
`corp_id` VARCHAR(64) NOT NULL COMMENT '企业ID',
|
||||
`action` VARCHAR(32) DEFAULT 'send' COMMENT 'send/recall/agree/disagree',
|
||||
`from_user` VARCHAR(128) NOT NULL COMMENT '发送者userid',
|
||||
`from_role` VARCHAR(32) NOT NULL COMMENT '发送者角色: INTERNAL-企业内部成员 EXTERNAL-外部联系人 SYSTEM-系统',
|
||||
`to_user` VARCHAR(128) COMMENT '接收者userid(单聊)',
|
||||
`tolist` TEXT COMMENT '接收者列表(群聊),JSON数组字符串',
|
||||
`roomid` VARCHAR(255) COMMENT '群聊ID(群聊时有值)',
|
||||
`msgtype` VARCHAR(64) NOT NULL COMMENT 'text/image/voice/video/file/link/location/weapp/chatrecord/voip',
|
||||
`msgtime` BIGINT(20) NOT NULL COMMENT '消息发送时间,UTC毫秒时间戳',
|
||||
`content` LONGTEXT COMMENT '文本消息内容(长文本使用LONGTEXT)',
|
||||
`media_data` TEXT COMMENT '媒体消息元数据(sdkfileid/文件大小/时长等),JSON字符串',
|
||||
`session_id` VARCHAR(128) COMMENT '关联的会话ID(业务生成)',
|
||||
`decrypt_status` TINYINT DEFAULT 1 COMMENT '解密状态: 1成功 2失败',
|
||||
`decrypt_error` VARCHAR(512) COMMENT '解密失败原因',
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
UNIQUE KEY `uk_msgid_corp` (`msgid`, `corp_id`) COMMENT '消息ID+企业ID唯一',
|
||||
INDEX `idx_seq` (`seq`) COMMENT '序列号索引,用于增量拉取',
|
||||
INDEX `idx_session_id` (`session_id`) COMMENT '会话ID索引',
|
||||
INDEX `idx_from_user_msgtime` (`from_user`, `msgtime`) COMMENT '发送者+时间索引',
|
||||
INDEX `idx_to_user_msgtime` (`to_user`, `msgtime`) COMMENT '接收者+时间索引',
|
||||
INDEX `idx_roomid_msgtime` (`roomid`, `msgtime`) COMMENT '群聊ID+时间索引',
|
||||
INDEX `idx_msgtime` (`msgtime`) COMMENT '消息时间索引',
|
||||
INDEX `idx_corp_msgtype` (`corp_id`, `msgtype`, `msgtime`) COMMENT '企业+消息类型+时间'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='企微会话存档消息表';
|
||||
|
||||
-- ============================================================
|
||||
-- 会话表 (conversations)
|
||||
-- ============================================================
|
||||
CREATE TABLE IF NOT EXISTS `conversations` (
|
||||
`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT COMMENT '自增主键',
|
||||
`session_id` VARCHAR(128) NOT NULL COMMENT '会话唯一ID(业务生成)',
|
||||
`corp_id` VARCHAR(64) NOT NULL COMMENT '企业ID',
|
||||
`customer_id` VARCHAR(128) NOT NULL COMMENT '学员(外部联系人)userid',
|
||||
`customer_name` VARCHAR(64) COMMENT '学员昵称',
|
||||
`staff_id` VARCHAR(128) NOT NULL COMMENT '坐席(企业成员)userid',
|
||||
`staff_name` VARCHAR(64) COMMENT '坐席姓名',
|
||||
`room_id` VARCHAR(128) COMMENT '群聊ID(群聊时有值)',
|
||||
`status` VARCHAR(32) DEFAULT 'ACTIVE' COMMENT '会话状态: ACTIVE-活跃 IDLE-空闲 CLOSED-关闭 ARCHIVED-归档',
|
||||
`current_stage` VARCHAR(32) DEFAULT '开场白' COMMENT '当前对话阶段',
|
||||
`stage_confidence` DECIMAL(4,3) DEFAULT 0.000 COMMENT '阶段判断置信度',
|
||||
`round_count` INT(11) DEFAULT 0 COMMENT '对话轮次',
|
||||
`intent_count` INT(11) DEFAULT 0 COMMENT '意图识别次数',
|
||||
`last_msg_id` VARCHAR(255) COMMENT '最后一条消息ID',
|
||||
`last_msg_time` TIMESTAMP NULL COMMENT '最后消息时间',
|
||||
`last_msg_content` VARCHAR(512) COMMENT '最后消息内容摘要',
|
||||
`start_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '会话开始时间',
|
||||
`end_time` TIMESTAMP NULL COMMENT '会话结束时间',
|
||||
`idle_duration` INT(11) DEFAULT 0 COMMENT '空闲时长(分钟)',
|
||||
`context_summary` TEXT COMMENT '对话摘要(LLM生成)',
|
||||
`key_events` TEXT COMMENT '关键事件列表,JSON字符串',
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
UNIQUE KEY `uk_session_id` (`session_id`) COMMENT '会话ID唯一',
|
||||
INDEX `idx_corp_staff` (`corp_id`, `staff_id`, `status`) COMMENT '企业+坐席+状态',
|
||||
INDEX `idx_corp_customer` (`corp_id`, `customer_id`, `status`) COMMENT '企业+学员+状态',
|
||||
INDEX `idx_status_time` (`status`, `last_msg_time`) COMMENT '状态+时间',
|
||||
INDEX `idx_created_at` (`created_at`) COMMENT '创建时间索引'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会话表';
|
||||
|
||||
-- ============================================================
|
||||
-- 对话轮次表 (conversation_turns)
|
||||
-- ============================================================
|
||||
CREATE TABLE IF NOT EXISTS `conversation_turns` (
|
||||
`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT COMMENT '自增主键',
|
||||
`turn_id` VARCHAR(128) NOT NULL COMMENT '轮次ID',
|
||||
`session_id` VARCHAR(128) NOT NULL COMMENT '会话ID',
|
||||
`turn_number` INT(11) NOT NULL COMMENT '轮次序号',
|
||||
`student_msg_id` VARCHAR(255) COMMENT '学员消息ID',
|
||||
`student_content` TEXT COMMENT '学员消息内容',
|
||||
`seat_msg_id` VARCHAR(255) COMMENT '坐席回复消息ID',
|
||||
`seat_content` TEXT COMMENT '坐席回复内容',
|
||||
`turn_type` VARCHAR(32) DEFAULT 'QUERY' COMMENT '轮次类型: OPEN-开场 QUERY-咨询 OBJECTION-异议 INTEREST-意向 SILENCE-沉默 FOLLOWUP-跟进 CLOSE-收尾',
|
||||
`primary_intent` VARCHAR(32) COMMENT '主意图编码',
|
||||
`intent_confidence` DECIMAL(4,3) COMMENT '意图置信度',
|
||||
`student_emotion` VARCHAR(32) DEFAULT 'NEUTRAL' COMMENT '学员情绪: POSITIVE-积极 NEUTRAL-中性 NEGATIVE-消极 ANXIOUS-焦虑 EXCITED-兴奋',
|
||||
`key_topics` TEXT COMMENT '涉及主题列表,JSON字符串',
|
||||
`slot_changes` TEXT COMMENT '槽位变化,JSON字符串',
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
UNIQUE KEY `uk_turn_id` (`turn_id`) COMMENT '轮次ID唯一',
|
||||
INDEX `idx_session_turn` (`session_id`, `turn_number`) COMMENT '会话+轮次号',
|
||||
INDEX `idx_session_time` (`session_id`, `created_at`) COMMENT '会话+时间'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='对话轮次表';
|
||||
|
||||
-- ============================================================
|
||||
-- 意图分类表 (intent_categories)
|
||||
-- ============================================================
|
||||
CREATE TABLE IF NOT EXISTS `intent_categories` (
|
||||
`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT COMMENT '自增主键',
|
||||
`code` VARCHAR(32) NOT NULL COMMENT '意图编码,如INT-COURSE-01',
|
||||
`name` VARCHAR(64) NOT NULL COMMENT '意图名称',
|
||||
`domain` VARCHAR(32) NOT NULL COMMENT '所属领域',
|
||||
`description` VARCHAR(256) COMMENT '意图说明',
|
||||
`priority` VARCHAR(8) DEFAULT 'P2' COMMENT '优先级: P1-高 P2-中 P3-低',
|
||||
`trigger_keywords` TEXT COMMENT '触发关键词列表,JSON数组字符串',
|
||||
`sample_expressions` TEXT COMMENT '典型表达示例,JSON数组字符串',
|
||||
`status` TINYINT DEFAULT 1 COMMENT '状态: 1启用 0停用',
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
UNIQUE KEY `uk_code` (`code`) COMMENT '意图编码唯一',
|
||||
INDEX `idx_domain_priority` (`domain`, `priority`) COMMENT '领域+优先级'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='意图分类表';
|
||||
|
||||
-- ============================================================
|
||||
-- 意图识别记录表 (intent_recognitions)
|
||||
-- ============================================================
|
||||
CREATE TABLE IF NOT EXISTS `intent_recognitions` (
|
||||
`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT COMMENT '自增主键',
|
||||
`session_id` VARCHAR(128) NOT NULL COMMENT '会话ID',
|
||||
`turn_id` VARCHAR(128) COMMENT '轮次ID',
|
||||
`msg_id` VARCHAR(255) NOT NULL COMMENT '消息ID',
|
||||
`customer_id` VARCHAR(128) NOT NULL COMMENT '学员ID',
|
||||
`staff_id` VARCHAR(128) NOT NULL COMMENT '坐席ID',
|
||||
`message_content` TEXT COMMENT '消息内容摘要',
|
||||
`primary_intent_code` VARCHAR(32) NOT NULL COMMENT '主意图编码',
|
||||
`primary_intent_confidence` DECIMAL(4,3) COMMENT '主意图置信度',
|
||||
`secondary_intents` TEXT COMMENT '辅意图列表,JSON字符串',
|
||||
`sentiment` VARCHAR(32) DEFAULT 'NEUTRAL' COMMENT '情感: POSITIVE-积极 NEUTRAL-中性 NEGATIVE-消极',
|
||||
`sentiment_confidence` DECIMAL(4,3) COMMENT '情感置信度',
|
||||
`recognition_method` VARCHAR(32) DEFAULT 'HYBRID' COMMENT '识别方法: RULE-规则 LLM-大模型 HYBRID-混合',
|
||||
`llm_model` VARCHAR(64) COMMENT '使用的LLM模型',
|
||||
`prompt_tokens` INT(11) COMMENT 'Prompt Token数',
|
||||
`completion_tokens` INT(11) COMMENT 'Completion Token数',
|
||||
`elapsed_ms` INT(11) COMMENT '识别耗时(毫秒)',
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
INDEX `idx_session_msg` (`session_id`, `msg_id`) COMMENT '会话+消息',
|
||||
INDEX `idx_customer_time` (`customer_id`, `created_at`) COMMENT '学员+时间',
|
||||
INDEX `idx_intent_code` (`primary_intent_code`, `created_at`) COMMENT '意图编码+时间'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='意图识别记录表';
|
||||
|
||||
-- ============================================================
|
||||
-- 学员画像表 (customer_profiles)
|
||||
-- ============================================================
|
||||
CREATE TABLE IF NOT EXISTS `customer_profiles` (
|
||||
`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT COMMENT '自增主键',
|
||||
`customer_id` VARCHAR(128) NOT NULL COMMENT '学员userid',
|
||||
`corp_id` VARCHAR(64) NOT NULL COMMENT '企业ID',
|
||||
`student_type` VARCHAR(32) COMMENT '学员类型: 在校大学生/转行人员/在职提升/高中毕业生/家长代询',
|
||||
`student_type_confidence` DECIMAL(4,3) COMMENT '学员类型置信度',
|
||||
`skill_level` VARCHAR(32) COMMENT '基础水平: 零基础/有美术基础/相关专业/有从业经验',
|
||||
`skill_level_confidence` DECIMAL(4,3) COMMENT '基础水平置信度',
|
||||
`intent_level` VARCHAR(16) DEFAULT '低' COMMENT '意向度等级: 高/中/低/无意向',
|
||||
`intent_score` DECIMAL(6,2) DEFAULT 0.00 COMMENT '意向度分数',
|
||||
`concern_focus` VARCHAR(32) COMMENT '关注重点: 价格敏感型/就业导向型/师资看重型/时间灵活型/品牌信任型',
|
||||
`concern_focus_confidence` DECIMAL(4,3) COMMENT '关注重点置信度',
|
||||
`decision_stage` VARCHAR(32) DEFAULT '信息了解' COMMENT '决策阶段: 信息了解/方案比较/购买决策/报名成交',
|
||||
`decision_stage_confidence` DECIMAL(4,3) COMMENT '决策阶段置信度',
|
||||
`interested_courses` TEXT COMMENT '意向课程列表,JSON字符串',
|
||||
`budget_hint` VARCHAR(64) COMMENT '预算暗示',
|
||||
`preferred_city` VARCHAR(32) COMMENT '意向城市',
|
||||
`age` INT(11) COMMENT '年龄',
|
||||
`education` VARCHAR(32) COMMENT '学历',
|
||||
`current_occupation` VARCHAR(64) COMMENT '当前职业',
|
||||
`slot_data` TEXT COMMENT '完整槽位数据,JSON字符串',
|
||||
`conversation_count` INT(11) DEFAULT 0 COMMENT '对话次数',
|
||||
`last_conversation_time` TIMESTAMP NULL COMMENT '上次对话时间',
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
UNIQUE KEY `uk_customer_corp` (`customer_id`, `corp_id`) COMMENT '学员+企业唯一',
|
||||
INDEX `idx_intent_score` (`intent_score`) COMMENT '意向度分数索引',
|
||||
INDEX `idx_decision_stage` (`decision_stage`) COMMENT '决策阶段索引'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='学员画像表';
|
||||
|
||||
-- ============================================================
|
||||
-- 话术库表 (utterances)
|
||||
-- ============================================================
|
||||
CREATE TABLE IF NOT EXISTS `utterances` (
|
||||
`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT COMMENT '自增主键',
|
||||
`utterance_id` VARCHAR(64) NOT NULL COMMENT '话术唯一ID(业务编码)',
|
||||
`corp_id` VARCHAR(64) NOT NULL COMMENT '企业ID',
|
||||
`title` VARCHAR(128) NOT NULL COMMENT '话术标题',
|
||||
`content` TEXT NOT NULL COMMENT '话术内容(富文本)',
|
||||
`content_text` TEXT COMMENT '纯文本内容(用于全文检索)',
|
||||
`version` INT(11) DEFAULT 1 COMMENT '版本号',
|
||||
`status` VARCHAR(32) DEFAULT 'DRAFT' COMMENT '状态: DRAFT-草稿 ACTIVE-启用 INACTIVE-停用 DEPRECATED-废弃',
|
||||
`priority` INT(11) DEFAULT 5 COMMENT '优先级(1-10,越小越优先)',
|
||||
`stage_tags` TEXT COMMENT '适用阶段标签,JSON数组字符串',
|
||||
`intent_tags` TEXT COMMENT '匹配意图标签,JSON数组字符串',
|
||||
`profile_tags` TEXT COMMENT '适用画像标签,JSON数组字符串',
|
||||
`course_type_tags` TEXT COMMENT '适用课程标签,JSON数组字符串',
|
||||
`emotion_tags` TEXT COMMENT '情感标签,JSON数组字符串',
|
||||
`topic_tags` TEXT COMMENT '主题标签,JSON数组字符串',
|
||||
`used_count` INT(11) DEFAULT 0 COMMENT '使用次数',
|
||||
`selected_count` INT(11) DEFAULT 0 COMMENT '选中次数',
|
||||
`positive_feedback` INT(11) DEFAULT 0 COMMENT '正面反馈数',
|
||||
`negative_feedback` INT(11) DEFAULT 0 COMMENT '负面反馈数',
|
||||
`conversion_count` INT(11) DEFAULT 0 COMMENT '转化次数',
|
||||
`success_rate` DECIMAL(5,4) DEFAULT 0.0000 COMMENT '成功率(0-1)',
|
||||
`avg_response_time_ms` INT(11) COMMENT '平均响应时间(毫秒)',
|
||||
`has_vector` TINYINT DEFAULT 0 COMMENT '是否已生成向量: 0否 1是',
|
||||
`vector_updated_at` TIMESTAMP NULL COMMENT '向量更新时间',
|
||||
`trigger_keywords` TEXT COMMENT '触发关键词,JSON字符串',
|
||||
`variables` TEXT COMMENT '变量定义,JSON字符串',
|
||||
`created_by` VARCHAR(64) COMMENT '创建人',
|
||||
`reviewed_by` VARCHAR(64) COMMENT '审核人',
|
||||
`source` VARCHAR(32) DEFAULT 'MANUAL' COMMENT '来源: MANUAL-人工录入 IMPORTED-导入 LLM_GENERATED-AI生成',
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
UNIQUE KEY `uk_utterance_id` (`utterance_id`) COMMENT '话术ID唯一',
|
||||
INDEX `idx_corp_status` (`corp_id`, `status`) COMMENT '企业+状态',
|
||||
INDEX `idx_success_rate` (`success_rate`) COMMENT '成功率索引',
|
||||
INDEX `idx_used_count` (`used_count`) COMMENT '使用次数索引',
|
||||
FULLTEXT KEY `ft_content` (`content_text`) COMMENT '全文检索索引'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='话术库表';
|
||||
|
||||
-- ============================================================
|
||||
-- 话术分类表 (utterance_categories)
|
||||
-- ============================================================
|
||||
CREATE TABLE IF NOT EXISTS `utterance_categories` (
|
||||
`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT COMMENT '自增主键',
|
||||
`code` VARCHAR(32) NOT NULL COMMENT '分类编码',
|
||||
`name` VARCHAR(64) NOT NULL COMMENT '分类名称',
|
||||
`parent_code` VARCHAR(32) DEFAULT 'ROOT' COMMENT '父级编码',
|
||||
`sort_order` INT(11) DEFAULT 0 COMMENT '排序号',
|
||||
`color` VARCHAR(16) DEFAULT '#1890ff' COMMENT '显示颜色',
|
||||
`description` VARCHAR(256) COMMENT '分类说明',
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
UNIQUE KEY `uk_code` (`code`) COMMENT '分类编码唯一'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='话术分类表';
|
||||
|
||||
-- ============================================================
|
||||
-- 话术效果表 (utterance_performance)
|
||||
-- ============================================================
|
||||
CREATE TABLE IF NOT EXISTS `utterance_performance` (
|
||||
`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT COMMENT '自增主键',
|
||||
`utterance_id` VARCHAR(64) NOT NULL COMMENT '话术ID',
|
||||
`corp_id` VARCHAR(64) NOT NULL COMMENT '企业ID',
|
||||
`hour_window` VARCHAR(13) NOT NULL COMMENT '时间窗口,格式: 2025-01-15-14',
|
||||
`show_count` INT(11) DEFAULT 0 COMMENT '展示次数',
|
||||
`click_count` INT(11) DEFAULT 0 COMMENT '点击次数',
|
||||
`send_count` INT(11) DEFAULT 0 COMMENT '发送次数',
|
||||
`positive_count` INT(11) DEFAULT 0 COMMENT '正面反馈数',
|
||||
`negative_count` INT(11) DEFAULT 0 COMMENT '负面反馈数',
|
||||
`ignore_count` INT(11) DEFAULT 0 COMMENT '忽略次数',
|
||||
`conversion_count` INT(11) DEFAULT 0 COMMENT '转化次数',
|
||||
`ctr` DECIMAL(5,4) COMMENT '点击率',
|
||||
`conversion_rate` DECIMAL(5,4) COMMENT '转化率',
|
||||
`score` DECIMAL(6,4) COMMENT '综合评分(EWA算法)',
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
UNIQUE KEY `uk_utterance_hour` (`utterance_id`, `hour_window`) COMMENT '话术+时间窗口唯一',
|
||||
INDEX `idx_corp_hour` (`corp_id`, `hour_window`) COMMENT '企业+时间窗口'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='话术效果表';
|
||||
|
||||
-- ============================================================
|
||||
-- 推荐记录表 (recommendations)
|
||||
-- ============================================================
|
||||
CREATE TABLE IF NOT EXISTS `recommendations` (
|
||||
`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT COMMENT '自增主键',
|
||||
`recommendation_id` VARCHAR(128) NOT NULL COMMENT '推荐记录ID',
|
||||
`session_id` VARCHAR(128) NOT NULL COMMENT '会话ID',
|
||||
`turn_id` VARCHAR(128) COMMENT '轮次ID',
|
||||
`customer_id` VARCHAR(128) NOT NULL COMMENT '学员ID',
|
||||
`staff_id` VARCHAR(128) NOT NULL COMMENT '坐席ID',
|
||||
`corp_id` VARCHAR(64) NOT NULL COMMENT '企业ID',
|
||||
`primary_intent` VARCHAR(32) COMMENT '主意图编码',
|
||||
`secondary_intents` TEXT COMMENT '辅意图列表,JSON字符串',
|
||||
`student_type` VARCHAR(32) COMMENT '学员类型',
|
||||
`current_stage` VARCHAR(32) COMMENT '当前阶段',
|
||||
`student_message` VARCHAR(512) COMMENT '学员消息',
|
||||
`recommended_count` INT(11) DEFAULT 0 COMMENT '推荐条数',
|
||||
`recall_sources` TEXT COMMENT '召回来源统计,JSON字符串',
|
||||
`generation_mode` VARCHAR(32) DEFAULT 'KB_ONLY' COMMENT '生成模式: KB_ONLY-话术库 KB_LLM_MIX-混合 LLM_ONLY-纯AI',
|
||||
`elapsed_ms` INT(11) COMMENT '推荐耗时(毫秒)',
|
||||
`staff_action` VARCHAR(32) DEFAULT 'NOT_SEEN' COMMENT '坐席操作: SENT-发送 EDITED-编辑后发送 IGNORED-忽略 NOT_SEEN-未查看',
|
||||
`feedback` VARCHAR(32) DEFAULT 'NONE' COMMENT '反馈: POSITIVE-正面 NEGATIVE-负面 NONE-无',
|
||||
`feedback_reason` VARCHAR(128) COMMENT '反馈原因',
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`action_time` TIMESTAMP NULL COMMENT '操作时间',
|
||||
UNIQUE KEY `uk_rec_id` (`recommendation_id`) COMMENT '推荐ID唯一',
|
||||
INDEX `idx_session` (`session_id`, `created_at`) COMMENT '会话+时间',
|
||||
INDEX `idx_staff` (`staff_id`, `created_at`) COMMENT '坐席+时间',
|
||||
INDEX `idx_intent` (`primary_intent`, `created_at`) COMMENT '意图+时间'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='推荐记录表';
|
||||
|
||||
-- ============================================================
|
||||
-- 知识库文档表 (knowledge_documents)
|
||||
-- ============================================================
|
||||
CREATE TABLE IF NOT EXISTS `knowledge_documents` (
|
||||
`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT COMMENT '自增主键',
|
||||
`doc_id` VARCHAR(64) NOT NULL COMMENT '文档唯一ID',
|
||||
`corp_id` VARCHAR(64) NOT NULL COMMENT '企业ID',
|
||||
`title` VARCHAR(256) NOT NULL COMMENT '文档标题',
|
||||
`content` LONGTEXT COMMENT '文档内容',
|
||||
`content_text` LONGTEXT COMMENT '纯文本内容(用于全文检索)',
|
||||
`doc_type` VARCHAR(32) DEFAULT 'KNOWLEDGE' COMMENT '文档类型: KNOWLEDGE-知识 UTTERANCE-话术 FAQ-常见问题',
|
||||
`category` VARCHAR(64) COMMENT '分类',
|
||||
`tags` TEXT COMMENT '标签,JSON数组字符串',
|
||||
`status` VARCHAR(32) DEFAULT 'ACTIVE' COMMENT '状态: ACTIVE-启用 INACTIVE-停用',
|
||||
`has_vector` TINYINT DEFAULT 0 COMMENT '是否已生成向量',
|
||||
`vector_updated_at` TIMESTAMP NULL COMMENT '向量更新时间',
|
||||
`created_by` VARCHAR(64) COMMENT '创建人',
|
||||
`updated_by` VARCHAR(64) COMMENT '更新人',
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
UNIQUE KEY `uk_doc_id` (`doc_id`) COMMENT '文档ID唯一',
|
||||
INDEX `idx_corp_type` (`corp_id`, `doc_type`, `status`) COMMENT '企业+类型+状态',
|
||||
FULLTEXT KEY `ft_content` (`content_text`) COMMENT '全文检索'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='知识库文档表';
|
||||
|
||||
-- ============================================================
|
||||
-- 员工表 (staffs)
|
||||
-- ============================================================
|
||||
CREATE TABLE IF NOT EXISTS `staffs` (
|
||||
`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT COMMENT '自增主键',
|
||||
`staff_id` VARCHAR(128) NOT NULL COMMENT '企微用户ID',
|
||||
`corp_id` VARCHAR(64) NOT NULL COMMENT '企业ID',
|
||||
`name` VARCHAR(64) COMMENT '姓名',
|
||||
`avatar` VARCHAR(512) COMMENT '头像URL',
|
||||
`department` VARCHAR(128) COMMENT '部门',
|
||||
`role` VARCHAR(32) DEFAULT 'ADVISOR' COMMENT '角色: ADMIN-管理员 SUPERVISOR-主管 ADVISOR-顾问',
|
||||
`status` VARCHAR(32) DEFAULT 'ACTIVE' COMMENT '状态: ACTIVE-在职 INACTIVE-离职',
|
||||
`push_settings` TEXT COMMENT '推送设置,JSON字符串',
|
||||
`last_login_time` TIMESTAMP NULL COMMENT '最后登录时间',
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
UNIQUE KEY `uk_staff_corp` (`staff_id`, `corp_id`) COMMENT '员工+企业唯一',
|
||||
INDEX `idx_corp_role` (`corp_id`, `role`) COMMENT '企业+角色'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='员工表';
|
||||
|
||||
-- ============================================================
|
||||
-- 客户表 (customers)
|
||||
-- ============================================================
|
||||
CREATE TABLE IF NOT EXISTS `customers` (
|
||||
`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT COMMENT '自增主键',
|
||||
`customer_id` VARCHAR(128) NOT NULL COMMENT '外部联系人userid',
|
||||
`corp_id` VARCHAR(64) NOT NULL COMMENT '企业ID',
|
||||
`name` VARCHAR(64) COMMENT '姓名/昵称',
|
||||
`avatar` VARCHAR(512) COMMENT '头像URL',
|
||||
`type` VARCHAR(32) DEFAULT 'UNKNOWN' COMMENT '客户类型: UNKNOWN-未知 INDIVIDUAL-个人 PARENT-家长 ENTERPRISE-企业',
|
||||
`gender` VARCHAR(8) COMMENT '性别: MALE-男 FEMALE-女',
|
||||
`phone` VARCHAR(32) COMMENT '手机号',
|
||||
`unionid` VARCHAR(128) COMMENT '微信unionid',
|
||||
`add_time` TIMESTAMP NULL COMMENT '添加时间',
|
||||
`status` VARCHAR(32) DEFAULT 'ACTIVE' COMMENT '状态: ACTIVE-正常 BLOCKED-拉黑 DELETED-删除',
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
UNIQUE KEY `uk_customer_corp` (`customer_id`, `corp_id`) COMMENT '客户+企业唯一',
|
||||
INDEX `idx_corp_status` (`corp_id`, `status`) COMMENT '企业+状态'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='客户表';
|
||||
|
||||
-- ============================================================
|
||||
-- 管理员表 (admins)
|
||||
-- ============================================================
|
||||
CREATE TABLE IF NOT EXISTS `admins` (
|
||||
`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT COMMENT '自增主键',
|
||||
`username` VARCHAR(64) NOT NULL COMMENT '用户名',
|
||||
`password` VARCHAR(256) NOT NULL COMMENT '密码(BCrypt加密)',
|
||||
`name` VARCHAR(64) COMMENT '姓名',
|
||||
`email` VARCHAR(128) COMMENT '邮箱',
|
||||
`phone` VARCHAR(32) COMMENT '手机号',
|
||||
`role` VARCHAR(32) DEFAULT 'ADMIN' COMMENT '角色: SUPER_ADMIN-超级管理员 ADMIN-管理员 OPERATOR-运营',
|
||||
`status` VARCHAR(32) DEFAULT 'ACTIVE' COMMENT '状态: ACTIVE-启用 DISABLED-停用',
|
||||
`last_login_time` TIMESTAMP NULL COMMENT '最后登录时间',
|
||||
`last_login_ip` VARCHAR(64) COMMENT '最后登录IP',
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
UNIQUE KEY `uk_username` (`username`) COMMENT '用户名唯一'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='管理员表';
|
||||
|
||||
-- ============================================================
|
||||
-- 操作日志表 (operation_logs)
|
||||
-- ============================================================
|
||||
CREATE TABLE IF NOT EXISTS `operation_logs` (
|
||||
`id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT COMMENT '自增主键',
|
||||
`operator_id` VARCHAR(64) NOT NULL COMMENT '操作人ID',
|
||||
`operator_name` VARCHAR(64) COMMENT '操作人姓名',
|
||||
`operator_type` VARCHAR(32) DEFAULT 'STAFF' COMMENT '操作人类型: STAFF-员工 ADMIN-管理员',
|
||||
`module` VARCHAR(64) COMMENT '操作模块',
|
||||
`action` VARCHAR(64) COMMENT '操作动作',
|
||||
`description` TEXT COMMENT '操作描述',
|
||||
`request_params` TEXT COMMENT '请求参数',
|
||||
`response_result` TEXT COMMENT '响应结果',
|
||||
`ip` VARCHAR(64) COMMENT '操作IP',
|
||||
`user_agent` VARCHAR(512) COMMENT 'User-Agent',
|
||||
`status` TINYINT DEFAULT 1 COMMENT '状态: 1成功 0失败',
|
||||
`elapsed_ms` INT(11) COMMENT '耗时(毫秒)',
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
INDEX `idx_operator` (`operator_id`, `created_at`) COMMENT '操作人+时间',
|
||||
INDEX `idx_module` (`module`, `action`, `created_at`) COMMENT '模块+动作+时间',
|
||||
INDEX `idx_created_at` (`created_at`) COMMENT '创建时间索引'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='操作日志表';
|
||||
|
||||
-- ============================================================
|
||||
-- 初始化数据
|
||||
-- ============================================================
|
||||
|
||||
-- 40个意图分类
|
||||
INSERT INTO `intent_categories` (`code`, `name`, `domain`, `description`, `priority`, `trigger_keywords`, `sample_expressions`, `status`) VALUES
|
||||
('INT-COURSE-01', '课程内容咨询', '课程相关', '询问具体学什么内容', 'P1', '["学什么","课程","内容","教什么","学什么软件"]', '["你们的课程具体学什么内容?","课程都教哪些软件?","能说一下课程体系吗?"]', 1),
|
||||
('INT-COURSE-02', '课程选择建议', '课程相关', '不知道该选哪个方向', 'P1', '["选哪个","推荐","适合我","怎么选","方向"]', '["我应该学3D建模还是原画?","哪个方向好就业?","您能帮我推荐一下吗?"]', 1),
|
||||
('INT-COURSE-03', '课程大纲索要', '课程相关', '想看详细课程安排', 'P1', '["大纲","课程表","安排","详细","阶段"]', '["能发一下课程大纲吗?","具体是怎么安排的?","每个阶段学什么?"]', 1),
|
||||
('INT-COURSE-04', '试听预约咨询', '课程相关', '想试听或体验课程', 'P1', '["试听","体验","免费","公开课","试学"]', '["有免费试听吗?","怎么预约试听课?","可以体验一下吗?"]', 1),
|
||||
('INT-COURSE-05', '课程更新迭代', '课程相关', '担心课程内容过时', 'P2', '["更新","过时","新版","跟上","行业"]', '["课程内容会更新吗?","跟得上行业吗?","用的是最新技术吗?"]', 1),
|
||||
('INT-COURSE-06', '线上vs线下选择', '课程相关', '不知道选哪种学习方式', 'P1', '["线上","线下","面授","直播","网课","区别"]', '["线上和线下有什么区别?","线上课程效果好吗?","可以线上学习吗?"]', 1),
|
||||
('INT-COURSE-07', '课程难度询问', '课程相关', '担心零基础能否学会', 'P1', '["零基础","能学会吗","难吗","没有基础","小白","能不能学会"]', '["零基础能学会吗?","我没有美术基础可以学吗?","课程难度大不大?"]', 1),
|
||||
('INT-COURSE-08', '学习周期询问', '课程相关', '想知道学多久', 'P2', '["多久","多长时间","几个月","学多久","周期"]', '["课程要学多久?","多长时间能学会?","几个月可以学完?"]', 1),
|
||||
('INT-PRICE-01', '学费价格询问', '价格相关', '直接问学费多少', 'P1', '["多少钱","学费","价格","费用","收费","怎么收费"]', '["学费多少钱?","课程怎么收费?","一共多少钱?"]', 1),
|
||||
('INT-PRICE-02', '优惠活动询问', '价格相关', '想知道有没有优惠', 'P1', '["优惠","活动","打折","减免","便宜","特价"]', '["现在有优惠活动吗?","能便宜点吗?","有没有折扣?"]', 1),
|
||||
('INT-PRICE-03', '分期付款咨询', '价格相关', '关心付款方式', 'P2', '["分期","贷款","月付","首付","月供","花呗"]', '["可以分期付款吗?","有贷款方案吗?","月供多少?"]', 1),
|
||||
('INT-PRICE-04', '退费政策询问', '价格相关', '关心退费保障', 'P2', '["退费","退款","退钱","学不好退","保障"]', '["学不好可以退费吗?","退费流程是怎样的?","有保障吗?"]', 1),
|
||||
('INT-PRICE-05', '性价比比较', '价格相关', '和别家比较', 'P2', '["对比","比较","别家","其他机构","性价比","划算"]', '["你们的课程和XX比怎么样?","为什么价格比别人高?","性价比高吗?"]', 1),
|
||||
('INT-JOB-01', '就业方向咨询', '就业相关', '学完能做什么', 'P1', '["就业","工作","岗位","方向","做什么"]', '["学完能去哪些公司工作?","就业方向有哪些?","能做什么岗位?"]', 1),
|
||||
('INT-JOB-02', '薪资水平询问', '就业相关', '关心工资待遇', 'P1', '["工资","薪资","月薪","收入","待遇","多少钱一个月"]', '["毕业后工资能拿多少?","行业薪资水平怎么样?","能挣多少?"]', 1),
|
||||
('INT-JOB-03', '就业率询问', '就业相关', '关心能否找到工作', 'P1', '["就业率","找到工作","保证","推荐","找工作"]', '["就业率高吗?","能保证就业吗?","好找工作吗?"]', 1),
|
||||
('INT-JOB-04', '就业服务了解', '就业相关', '了解就业推荐服务', 'P2', '["就业服务","推荐","怎么找工作","帮找工作"]', '["你们怎么帮学员找工作?","有就业推荐吗?","就业服务包含什么?"]', 1),
|
||||
('INT-JOB-05', '合作企业询问', '就业相关', '想知道合作企业', 'P2', '["合作企业","大厂","腾讯","米哈游","网易","公司"]', '["合作企业有哪些?","有米哈游/腾讯这样的公司吗?","就业单位有哪些?"]', 1),
|
||||
('INT-JOB-06', '作品集指导', '就业相关', '关心作品质量', 'P2', '["作品集","作品","项目","Demo","案例"]', '["学完能做出作品集吗?","作品质量怎么样?","能参与项目吗?"]', 1),
|
||||
('INT-JOB-07', '实习机会咨询', '就业相关', '想参与真实项目', 'P3', '["实习","真实项目","实训","参与项目","实战"]', '["有实习机会吗?","可以参与真实项目吗?","有企业实训吗?"]', 1),
|
||||
('INT-TEACH-01', '师资背景询问', '师资相关', '想知道老师是谁', 'P2', '["老师","师资","讲师","谁教的","教学经验"]', '["老师是什么背景?","老师有项目经验吗?","谁来讲课?"]', 1),
|
||||
('INT-TEACH-02', '教学模式了解', '师资相关', '想知道怎么上课', 'P2', '["怎么上课","教学方式","班型","一对一","多少人"]', '["是怎么上课的?","一个班多少人?","教学模式是怎样的?"]', 1),
|
||||
('INT-TEACH-03', '课后答疑服务', '师资相关', '关心课后辅导', 'P2', '["答疑","辅导","课后","问问题","指导"]', '["课后有问题可以问老师吗?","答疑及时吗?","有课后辅导吗?"]', 1),
|
||||
('INT-TEACH-04', '师生比例询问', '师资相关', '关心教学质量', 'P3', '["一个老师","带多少学生","比例","师生"]', '["一个老师带多少学生?","能照顾到每个人吗?","班级多大?"]', 1),
|
||||
('INT-TEACH-05', '学习效果保障', '师资相关', '担心学不会怎么办', 'P2', '["学不会","怎么办","重修","保障","跟不上"]', '["学不会怎么办?","怎么保证学习效果?","跟不上怎么办?"]', 1),
|
||||
('INT-BASIC-01', '学历要求咨询', '基础条件', '学历是否够', 'P2', '["学历","要求","高中","大专","本科"]', '["需要什么学历?","高中生可以学吗?","学历要求高吗?"]', 1),
|
||||
('INT-BASIC-02', '年龄限制询问', '基础条件', '年龄是否合适', 'P2', '["年龄","多大","几岁","限制","30岁"]', '["有没有年龄限制?","30岁了还能学吗?","年龄大能学会吗?"]', 1),
|
||||
('INT-BASIC-03', '零基础入学', '基础条件', '零基础可行性', 'P1', '["零基础","没有基础","小白","完全不会","从来没"]', '["我没有美术基础可以学吗?","零基础能学会吗?","完全没接触过可以学吗?"]', 1),
|
||||
('INT-BASIC-04', '证书颁发询问', '基础条件', '学完发什么证书', 'P3', '["证书","毕业证","结业证","证明","认证"]', '["学完发证书吗?","证书有用吗?","有毕业证吗?"]', 1),
|
||||
('INT-LOC-01', '校区地址询问', '校区相关', '想知道校区在哪', 'P2', '["校区","地址","在哪","位置","交通"]', '["校区在哪里?","附近有地铁吗?","具体地址是什么?"]', 1),
|
||||
('INT-LOC-02', '校区参观预约', '校区相关', '想去参观', 'P2', '["参观","看看","实地考察","走访","预约"]', '["可以参观校区吗?","怎么预约参观?","能去看看吗?"]', 1),
|
||||
('INT-LOC-03', '教学设备了解', '校区相关', '关心硬件条件', 'P3', '["设备","电脑","配置","硬件","机房"]', '["需要自备电脑吗?","校区提供设备吗?","电脑配置怎么样?"]', 1),
|
||||
('INT-LOC-04', '住宿安排咨询', '校区相关', '外地学员住宿', 'P3', '["住宿","宿舍","外地","住哪","租房"]', '["外地学员有住宿吗?","住宿怎么安排?","有宿舍吗?"]', 1),
|
||||
('INT-QUAL-01', '机构正规性', '机构资质', '是否正规机构', 'P2', '["正规","资质","办学","合法","备案"]', '["你们是正规机构吗?","有办学资质吗?","是国家认可的吗?"]', 1),
|
||||
('INT-QUAL-02', '口碑评价询问', '机构资质', '想看学员评价', 'P3', '["评价","口碑","反馈","怎么样","好吗"]', '["学员评价怎么样?","有老学员反馈吗?","口碑如何?"]', 1),
|
||||
('INT-QUAL-03', '媒体报道了解', '机构资质', '想看权威背书', 'P3', '["报道","媒体","电视","新闻","央视"]', '["有上过电视报道吗?","媒体报道过吗?","央视那个是真的吗?"]', 1),
|
||||
('INT-OTHER-01', '大师课咨询', '其他', '进阶课程', 'P3', '["大师课","研修班","进阶","艺术家","高级"]', '["大师课和普通课什么区别?","研修班怎么报名?","大师课学费多少?"]', 1),
|
||||
('INT-OTHER-02', '企业培训定制', '其他', 'B端需求', 'P3', '["企业培训","定制","公司","团队","B端"]', '["有企业定制培训吗?","公司团队能报名吗?","企业内训怎么合作?"]', 1),
|
||||
('INT-OTHER-03', '比赛活动咨询', '其他', 'GGAC等赛事', 'P3', '["比赛","GGAC","大赛","活动","赛事"]', '["GGAC比赛怎么参加?","有比赛推荐吗?","学员能参加大赛吗?"]', 1),
|
||||
('INT-OTHER-04', '转班转学咨询', '其他', '变更需求', 'P3', '["转班","换方向","转学","调整","换校区"]', '["可以换课程方向吗?","可以换校区吗?","转班怎么办理?"]', 1);
|
||||
|
||||
-- 8个话术分类(销售阶段)
|
||||
INSERT INTO `utterance_categories` (`code`, `name`, `parent_code`, `sort_order`, `color`, `description`) VALUES
|
||||
('STAGE-01', '开场白', 'ROOT', 1, '#52c41a', '首次接待学员/家长,建立信任,收集基础信息'),
|
||||
('STAGE-02', '需求探询', 'ROOT', 2, '#1890ff', '了解学员背景和目标,精准画像,挖掘需求'),
|
||||
('STAGE-03', '课程推荐', 'ROOT', 3, '#fa8c16', '匹配适合的课程方向,精准推荐1-2个方向'),
|
||||
('STAGE-04', '价值塑造', 'ROOT', 4, '#722ed1', '强调优势和差异化,建立价值认同'),
|
||||
('STAGE-05', '报价沟通', 'ROOT', 5, '#eb2f96', '价格说明和方案,化解价格顾虑'),
|
||||
('STAGE-06', '异议处理', 'ROOT', 6, '#f5222d', '应对各类顾虑,消除障碍,建立信心'),
|
||||
('STAGE-07', '促成报名', 'ROOT', 7, '#faad14', '推动转化成交,促成立即行动'),
|
||||
('STAGE-08', '跟进维护', 'ROOT', 8, '#13c2c2', '未成交学员跟进,保持联系,等待时机');
|
||||
|
||||
-- 系统管理员(密码: admin123)
|
||||
INSERT INTO `admins` (`username`, `password`, `name`, `email`, `role`, `status`, `created_at`) VALUES
|
||||
('admin', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iOE1Qh6G', '系统管理员', 'admin@9artedu.com', 'SUPER_ADMIN', 'ACTIVE', NOW());
|
||||
8062
product1.0/v2-final-完整开发方案.md
Normal file
8062
product1.0/v2-final-完整开发方案.md
Normal file
File diff suppressed because it is too large
Load Diff
BIN
product1.0/九艺AI坐席辅助系统-完整开发方案.docx
Normal file
BIN
product1.0/九艺AI坐席辅助系统-完整开发方案.docx
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user