新增AI商品查询

This commit is contained in:
启航 2026-03-11 13:06:10 +08:00
parent 8adf745796
commit 27116b83aa
7 changed files with 910 additions and 33 deletions

296
AGENTS.md Normal file
View File

@ -0,0 +1,296 @@
# AGENTS.md - 智能编码指南
## 项目概述
启航电商ERP系统 (qihang-ecom-erp-open) 技术栈:
- **前端**: Vue 2 + ElementUI + Vuex + Vue Router
- **后端**: SpringCloud 微服务 (Java 17) + Maven
- **数据库**: MySQL 8 + Redis 7
---
## 构建与测试命令
### 前端 (Vue)
```bash
cd vue
npm install
npm run dev # 开发服务器 (端口 88)
npm run build:prod # 生产构建
npm run build:stage # 测试环境构建
npm run preview # 预览构建结果
npm run lint # 代码检查
npm run lint -- --fix # 自动修复
npx eslint --ext .js,.vue src/views/example.vue # 检查单个文件
```
### 后端 (Java/Maven)
```bash
mvn clean install # 构建所有模块
mvn clean package -DskipTests # 构建跳过测试
mvn -pl <module-name> clean install # 构建指定模块
java -jar target/*.jar # 运行 SpringBoot 应用
```
---
## 代码风格指南
### 基本原则
1. **添加注释** - 除非用户明确要求,否则要添加注释
2. **中文注释** - 如需注释,使用中文
3. **保持一致** - 遵循现有代码模式
---
### Vue/JavaScript 规范
#### 命名规范
- **组件**: PascalCase (`UserProfile.vue`)
- **文件/变量**: camelCase (`userName`, `orderList`)
- **常量**: UPPER_SNAKE_CASE
- **布尔变量**: 使用 `is`、`has`、`can` 前缀
#### 导入顺序
1. Vue/Vue Router/VueX
2. 第三方库 (axios, element-ui)
3. @ 别名导入 (@/utils, @/api)
4. 相对路径 (./, ../)
```javascript
// 正确示例
import Vue from 'vue'
import axios from 'axios'
import { getToken } from '@/utils/auth'
import Cookies from 'js-cookie'
```
#### ESLint 格式化
- 缩进: 2 空格
- 引号: 单引号
- 分号: 不使用
- 大括号: 1TBS 风格
#### 模板规范
- 组件名 PascalCase 或 kebab-case
- v-for 必须带 :key
```vue
<template>
<el-input v-model="form.username" />
<UserProfile :user-id="userId" />
</template>
```
#### 组件结构
```vue
<template>
<!-- 模板内容 -->
</template>
<script>
export default {
name: 'ComponentName',
components: {},
props: {},
data() { return {} },
computed: {},
watch: {},
created() {},
methods: {}
}
</script>
<style lang="scss">
/* 样式内容 */
</style>
```
---
### Java 规范
#### 包结构
```
cn.qihangerp
├── api/ # Controller
├── module/service # Service 接口
├── serviceImpl/ # Service 实现
├── mapper/ # MyBatis Mapper
├── model/ # Entity, DTO, VO, BO
│ ├── entity/ # 数据库实体
│ ├── dto/ # 数据传输对象
│ ├── vo/ # 视图对象
│ ├── bo/ # 业务对象
│ └── query/ # 查询条件
└── common/ # 通用工具
```
#### 命名规范
- **类**: PascalCase
- **接口**: 以 I 开头 (IUserService)
- **方法**: camelCase
#### Spring 规范
- Service: `I{Entity}Service` / `{Entity}ServiceImpl`
- Controller: `{Entity}Controller`
- 使用 @Autowired 注入
- 使用 MyBatis-Plus `IService<T>`
#### 代码风格
- 使用 Lombok @Data, @Slf4j
- 使用 fastjson2 处理 JSON
- 返回 `ResultVo<T>`
- 使用 `PageQuery` / `PageResult<T>` 分页
```java
public interface OOrderService extends IService<OOrder> {
PageResult<OOrder> queryPageList(OrderSearchRequest bo, PageQuery pageQuery);
ResultVo<Integer> manualShipmentOrder(OrderShipRequest shipBo, String createBy);
}
```
---
### 错误处理
**前端**: 使用 ElementUI `this.$message()` 反馈
```javascript
this.$message({ message: '操作失败', type: 'error' })
```
**后端**: 返回 `ResultVo` 错误码,使用 `@ExceptionHandler`
```java
@ExceptionHandler(Exception.class)
public ResultVo<String> handleException(Exception e) {
return ResultVo.error(e.getMessage());
}
```
---
### Git 工作流
1. 从 main 创建功能分支
2. 提交格式: `feat: add feature` / `fix: resolve issue`
3. 提交前运行 `npm run lint`
---
## 项目目录结构
### 前端 (vue/src/)
```
vue/src/
├── api/ # API 接口定义
├── assets/ # 静态资源
├── components/ # 公共组件
├── layout/ # 布局组件
├── plugins/ # Vue 插件
├── router/ # 路由配置
├── store/ # Vuex 状态管理
├── utils/ # 工具函数
└── views/ # 页面组件
```
### 后端模块
```
api/ # API 模块 (gateway, erp-api, open-api, ai-agent)
core/ # 公共库
mapper/ # MyBatis Mapper
model/ # 实体类
service/ # Service 接口
serviceImpl/ # Service 实现
```
---
## 开发技巧
### 路径别名
使用 `@` 代替相对路径
```javascript
// 推荐
import foo from '@/utils/foo'
// 避免
import foo from '../../../utils/foo'
```
### API 请求
通过 `utils/request.js`,自动处理 token 和 401
### 环境变量
在 vue/ 下创建 `.env.development`
```
VUE_APP_BASE_API=/prod-api
NODE_OPTIONS=--openssl-legacy-provider
```
---
## 技术栈版本
- Node.js >= 20.0.0 | Java 17 | Maven 3.9
- Vue 2.6.12 | ElementUI 2.15.13
- Spring Boot 3.0.2 | Spring Cloud 2022.0.0
---
## 平台命名规范
| 平台 | 前缀 | 示例 |
|------|------|------|
| 淘宝/天猫 | Tao | TaoOrderService |
| 京东 | Jd | JdGoodsService |
| 拼多多 | Pdd | PddOrderService |
| 抖音/抖店 | Dou | DouRefundService |
| 微信小店 | Wei | WeiOrderService |
| 线下/私域 | Offline | OfflineOrderService |
---
## 关键文件
| 文件 | 用途 |
|------|------|
| `vue/src/utils/request.js` | Axios 拦截器 |
| `vue/src/utils/auth.js` | Token 管理 |
| `vue/src/api/` | API 端点定义 |
| `vue/src/store/` | Vuex 模块 |
| `vue/.eslintrc.js` | ESLint 配置 |
| `vue/vue.config.js` | Vue CLI 配置 |
| `pom.xml` | Maven 父 POM |
---
## 数据库规范
- 表名: `o_` 订单, `g_` 商品, `s_` 库存
- 使用 MyBatis-Plus 注解
- Entity 类位于 `model/src/main/java/cn/qihangerp/model/entity/`
- 使用 `PageQuery` 分页查询
- 时间戳: `createTime`, `updateTime`
---
## API 响应格式
### 后端
```java
// 成功
ResultVo.success(data);
ResultVo.success();
// 失败
ResultVo.error("错误信息");
ResultVo.error(500, "错误信息");
```
### 前端
```javascript
// 统一通过 utils/request.js 处理
// 成功返回 res.data失败抛出异常
```

View File

@ -0,0 +1,215 @@
package cn.qihangerp.erp.service;
import cn.qihangerp.model.entity.OGoodsSku;
import cn.qihangerp.module.service.OGoodsSkuService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.stream.Collectors;
/**
* 商品服务类用于AI查询商品信息
* 调用已有的 OGoodsSkuService 进行数据库查询
*/
@Service
public class GoodsService {
@Autowired
private OGoodsSkuService oGoodsSkuService;
/**
* 根据ID查询商品SKU
* @param id 商品SKU ID
* @return 商品信息
*/
public Goods getGoodsSkuById(Long id) {
OGoodsSku sku = oGoodsSkuService.getById(id);
if (sku == null) {
return null;
}
return convertToGoods(sku);
}
/**
* 根据商品名称模糊查询商品SKU
* @param keyword 关键词
* @return 商品SKU列表
*/
public List<Goods> searchGoodsByName(String keyword) {
LambdaQueryWrapper<OGoodsSku> wrapper = new LambdaQueryWrapper<>();
wrapper.like(OGoodsSku::getGoodsName, keyword)
.or()
.like(OGoodsSku::getSkuName, keyword);
List<OGoodsSku> list = oGoodsSkuService.list(wrapper);
return list.stream().map(this::convertToGoods).collect(Collectors.toList());
}
/**
* 根据商品编码查询商品SKU
* @param goodsNum 商品编码
* @return 商品SKU列表
*/
public List<Goods> searchGoodsByGoodsNum(String goodsNum) {
LambdaQueryWrapper<OGoodsSku> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(OGoodsSku::getGoodsNum, goodsNum);
List<OGoodsSku> list = oGoodsSkuService.list(wrapper);
return list.stream().map(this::convertToGoods).collect(Collectors.toList());
}
/**
* 根据SKU编码查询商品SKU
* @param skuCode SKU编码
* @return 商品SKU信息
*/
public Goods getGoodsSkuBySkuCode(String skuCode) {
LambdaQueryWrapper<OGoodsSku> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(OGoodsSku::getSkuCode, skuCode);
OGoodsSku sku = oGoodsSkuService.getOne(wrapper);
if (sku == null) {
return null;
}
return convertToGoods(sku);
}
/**
* 根据商品ID查询商品SKU
* @param goodsId 商品ID
* @return 商品SKU列表
*/
public List<Goods> searchGoodsByGoodsId(Long goodsId) {
LambdaQueryWrapper<OGoodsSku> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(OGoodsSku::getGoodsId, goodsId);
List<OGoodsSku> list = oGoodsSkuService.list(wrapper);
return list.stream().map(this::convertToGoods).collect(Collectors.toList());
}
/**
* 获取所有商品SKU
* @return 商品SKU列表
*/
public List<Goods> getAllGoods() {
List<OGoodsSku> list = oGoodsSkuService.list();
return list.stream().map(this::convertToGoods).collect(Collectors.toList());
}
/**
* 通用搜索商品SKU
* @param keyword 关键词可搜索名称编码SKU编码等
* @return 商品SKU列表
*/
public List<Goods> searchGoods(String keyword) {
LambdaQueryWrapper<OGoodsSku> wrapper = new LambdaQueryWrapper<>();
wrapper.like(OGoodsSku::getGoodsName, keyword)
.or().like(OGoodsSku::getGoodsNum, keyword)
.or().like(OGoodsSku::getSkuCode, keyword)
.or().like(OGoodsSku::getSkuName, keyword)
.or().like(OGoodsSku::getBarCode, keyword);
List<OGoodsSku> list = oGoodsSkuService.list(wrapper);
return list.stream().map(this::convertToGoods).collect(Collectors.toList());
}
/**
* 转换为商品对象
*/
private Goods convertToGoods(OGoodsSku sku) {
return new Goods(
sku.getId(),
sku.getGoodsId(),
sku.getGoodsName(),
sku.getGoodsNum(),
sku.getSkuName(),
sku.getSkuCode(),
sku.getColorValue(),
sku.getSizeValue(),
sku.getStyleValue(),
sku.getBarCode(),
sku.getPurPrice(),
sku.getRetailPrice(),
sku.getUnitCost(),
sku.getStatus(),
sku.getLowQty(),
sku.getHighQty()
);
}
/**
* 商品SKU实体类
*/
public static class Goods {
private Long id;
private Long goodsId;
private String goodsName;
private String goodsNum;
private String skuName;
private String skuCode;
private String colorValue;
private String sizeValue;
private String styleValue;
private String barCode;
private java.math.BigDecimal purPrice;
private java.math.BigDecimal retailPrice;
private java.math.BigDecimal unitCost;
private Integer status;
private Integer lowQty;
private Integer highQty;
public Goods(Long id, Long goodsId, String goodsName, String goodsNum, String skuName,
String skuCode, String colorValue, String sizeValue, String styleValue,
String barCode, java.math.BigDecimal purPrice, java.math.BigDecimal retailPrice,
java.math.BigDecimal unitCost, Integer status, Integer lowQty, Integer highQty) {
this.id = id;
this.goodsId = goodsId;
this.goodsName = goodsName;
this.goodsNum = goodsNum;
this.skuName = skuName;
this.skuCode = skuCode;
this.colorValue = colorValue;
this.sizeValue = sizeValue;
this.styleValue = styleValue;
this.barCode = barCode;
this.purPrice = purPrice;
this.retailPrice = retailPrice;
this.unitCost = unitCost;
this.status = status;
this.lowQty = lowQty;
this.highQty = highQty;
}
public Long getId() { return id; }
public Long getGoodsId() { return goodsId; }
public String getGoodsName() { return goodsName; }
public String getGoodsNum() { return goodsNum; }
public String getSkuName() { return skuName; }
public String getSkuCode() { return skuCode; }
public String getColorValue() { return colorValue; }
public String getSizeValue() { return sizeValue; }
public String getStyleValue() { return styleValue; }
public String getBarCode() { return barCode; }
public java.math.BigDecimal getPurPrice() { return purPrice; }
public java.math.BigDecimal getRetailPrice() { return retailPrice; }
public java.math.BigDecimal getUnitCost() { return unitCost; }
public Integer getStatus() { return status; }
public Integer getLowQty() { return lowQty; }
public Integer getHighQty() { return highQty; }
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("商品ID: ").append(id).append(", ");
sb.append("商品名称: ").append(goodsName != null ? goodsName : "").append(", ");
sb.append("商品编码: ").append(goodsNum != null ? goodsNum : "").append(", ");
sb.append("SKU名称: ").append(skuName != null ? skuName : "").append(", ");
sb.append("SKU编码: ").append(skuCode != null ? skuCode : "").append(", ");
if (colorValue != null) sb.append("颜色: ").append(colorValue).append(", ");
if (sizeValue != null) sb.append("尺寸: ").append(sizeValue).append(", ");
if (styleValue != null) sb.append("款式: ").append(styleValue).append(", ");
if (barCode != null) sb.append("条码: ").append(barCode).append(", ");
sb.append("采购价: ").append(purPrice).append(", ");
sb.append("零售价: ").append(retailPrice).append(", ");
sb.append("成本价: ").append(unitCost);
return sb.toString();
}
}
}

View File

@ -0,0 +1,149 @@
package cn.qihangerp.erp.service;
import dev.langchain4j.agent.tool.Tool;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* 商品工具服务用于AI查询商品信息
*/
@Component
public class GoodsToolService {
@Autowired
private GoodsService goodsService;
/**
* 根据商品SKU ID查询商品信息
* @param id 商品SKU ID
* @return 商品信息
*/
@Tool("根据商品SKU ID查询商品信息")
public String getGoodsById(String id) {
try {
Long skuId = Long.parseLong(id);
GoodsService.Goods goods = goodsService.getGoodsSkuById(skuId);
if (goods == null) {
return "未找到ID为" + id + "的商品";
}
return goods.toString().replace("\n", "<br>");
} catch (NumberFormatException e) {
return "无效的商品ID格式" + id;
}
}
/**
* 根据商品名称搜索商品
* @param keyword 商品名称关键词
* @return 商品列表
*/
@Tool("根据商品名称搜索商品信息")
public String searchGoodsByName(String keyword) {
List<GoodsService.Goods> goodsList = goodsService.searchGoodsByName(keyword);
if (goodsList.isEmpty()) {
return "未找到名称包含'" + keyword + "'的商品";
}
StringBuilder sb = new StringBuilder();
sb.append("找到").append(goodsList.size()).append("个商品:<br>");
for (GoodsService.Goods goods : goodsList) {
sb.append(goods.toString()).append("<br><br>");
}
return sb.toString();
}
/**
* 根据商品编码搜索商品
* @param goodsNum 商品编码
* @return 商品列表
*/
@Tool("根据商品编码查询商品信息")
public String searchGoodsByGoodsNum(String goodsNum) {
List<GoodsService.Goods> goodsList = goodsService.searchGoodsByGoodsNum(goodsNum);
if (goodsList.isEmpty()) {
return "未找到编码为'" + goodsNum + "'的商品";
}
StringBuilder sb = new StringBuilder();
sb.append("找到").append(goodsList.size()).append("个商品:<br>");
for (GoodsService.Goods goods : goodsList) {
sb.append(goods.toString()).append("<br><br>");
}
return sb.toString();
}
/**
* 根据SKU编码查询商品
* @param skuCode SKU编码
* @return 商品信息
*/
@Tool("根据SKU编码查询商品信息")
public String getGoodsBySkuCode(String skuCode) {
GoodsService.Goods goods = goodsService.getGoodsSkuBySkuCode(skuCode);
if (goods == null) {
return "未找到SKU编码为'" + skuCode + "'的商品";
}
return goods.toString().replace("\n", "<br>");
}
/**
* 通用搜索商品支持名称编码SKU编码条码等
* @param keyword 搜索关键词
* @return 商品列表
*/
@Tool("通用搜索商品信息支持名称、编码、SKU编码、条码搜索")
public String searchGoods(String keyword) {
List<GoodsService.Goods> goodsList = goodsService.searchGoods(keyword);
if (goodsList.isEmpty()) {
return "未找到与'" + keyword + "'相关的商品";
}
StringBuilder sb = new StringBuilder();
sb.append("找到").append(goodsList.size()).append("个商品:<br>");
for (GoodsService.Goods goods : goodsList) {
sb.append(goods.toString()).append("<br><br>");
}
return sb.toString();
}
/**
* 获取所有商品
* @return 商品列表
*/
@Tool("获取所有商品信息")
public String getAllGoods() {
List<GoodsService.Goods> goodsList = goodsService.getAllGoods();
if (goodsList.isEmpty()) {
return "暂无商品数据";
}
StringBuilder sb = new StringBuilder();
sb.append("所有商品(共").append(goodsList.size()).append("个):<br>");
for (GoodsService.Goods goods : goodsList) {
sb.append(goods.toString()).append("<br><br>");
}
return sb.toString();
}
/**
* 根据商品ID查询所有SKU
* @param goodsId 商品ID
* @return SKU列表
*/
@Tool("根据商品ID查询该商品的所有SKU规格")
public String getGoodsSkusByGoodsId(String goodsId) {
try {
Long id = Long.parseLong(goodsId);
List<GoodsService.Goods> goodsList = goodsService.searchGoodsByGoodsId(id);
if (goodsList.isEmpty()) {
return "未找到商品ID为" + goodsId + "的SKU";
}
StringBuilder sb = new StringBuilder();
sb.append("商品ID ").append(goodsId).append(" 共有").append(goodsList.size()).append("个SKU<br>");
for (GoodsService.Goods goods : goodsList) {
sb.append(goods.toString()).append("<br><br>");
}
return sb.toString();
} catch (NumberFormatException e) {
return "无效的商品ID格式" + goodsId;
}
}
}

View File

@ -1,6 +1,8 @@
package cn.qihangerp.erp.service; package cn.qihangerp.erp.service;
import dev.langchain4j.agent.tool.Tool; import dev.langchain4j.agent.tool.Tool;
import org.springframework.stereotype.Component;
import java.util.List; import java.util.List;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
@ -8,6 +10,7 @@ import java.time.format.DateTimeFormatter;
/** /**
* 订单工具服务用于AI查询订单信息 * 订单工具服务用于AI查询订单信息
*/ */
@Component
public class OrderToolService { public class OrderToolService {
private final OrderService orderService; private final OrderService orderService;

View File

@ -3,6 +3,7 @@ package cn.qihangerp.erp.serviceImpl;
import dev.langchain4j.model.ollama.OllamaChatModel; import dev.langchain4j.model.ollama.OllamaChatModel;
import dev.langchain4j.model.openai.OpenAiChatModel; import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.service.AiServices; import dev.langchain4j.service.AiServices;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.io.BufferedReader; import java.io.BufferedReader;
@ -15,6 +16,7 @@ import java.time.format.DateTimeFormatter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import cn.qihangerp.erp.service.OrderToolService; import cn.qihangerp.erp.service.OrderToolService;
import cn.qihangerp.erp.service.GoodsToolService;
import cn.qihangerp.erp.serviceImpl.ConversationHistoryManager; import cn.qihangerp.erp.serviceImpl.ConversationHistoryManager;
/** /**
@ -23,6 +25,12 @@ import cn.qihangerp.erp.serviceImpl.ConversationHistoryManager;
@Service @Service
public class AiService { public class AiService {
@Autowired
private OrderToolService orderToolService;
@Autowired
private GoodsToolService goodsToolService;
/** /**
* 页面规则类 * 页面规则类
*/ */
@ -195,14 +203,20 @@ public class AiService {
System.out.println("========================================"); System.out.println("========================================");
System.out.println("发送给AI的消息: " + enhancedMessage); System.out.println("发送给AI的消息: " + enhancedMessage);
// 尝试创建订单工具服务 // 使用注入的工具服务订单工具和商品工具
OrderToolService orderToolService = null; OrderToolService orderSvc = orderToolService;
GoodsToolService goodsSvc = goodsToolService;
try { try {
orderToolService = new OrderToolService(); if (orderSvc == null) {
System.out.println("成功创建OrderToolService"); orderSvc = new OrderToolService();
}
if (goodsSvc == null) {
goodsSvc = new GoodsToolService();
}
System.out.println("成功创建AI工具服务");
} catch (Exception e) { } catch (Exception e) {
System.out.println("创建OrderToolService失败: " + e.getMessage()); System.out.println("创建AI工具服务失败: " + e.getMessage());
// 工具创建失败仍然继续执行只是不使用工具
} }
// 根据模型名称选择使用Ollama还是DeepSeek API // 根据模型名称选择使用Ollama还是DeepSeek API
@ -223,10 +237,20 @@ public class AiService {
.timeout(Duration.ofSeconds(300)) .timeout(Duration.ofSeconds(300))
.build(); .build();
if (orderToolService != null) { if (orderSvc != null && goodsSvc != null) {
aiService = AiServices.builder(OrderAiService.class) aiService = AiServices.builder(OrderAiService.class)
.chatModel(deepSeekModelInstance) .chatModel(deepSeekModelInstance)
.tools(orderToolService) .tools(orderSvc, goodsSvc)
.build();
} else if (orderSvc != null) {
aiService = AiServices.builder(OrderAiService.class)
.chatModel(deepSeekModelInstance)
.tools(orderSvc)
.build();
} else if (goodsSvc != null) {
aiService = AiServices.builder(OrderAiService.class)
.chatModel(deepSeekModelInstance)
.tools(goodsSvc)
.build(); .build();
} else { } else {
aiService = AiServices.builder(OrderAiService.class) aiService = AiServices.builder(OrderAiService.class)
@ -249,10 +273,20 @@ public class AiService {
.timeout(Duration.ofSeconds(300)) // 超时时间设置为300秒5分钟 .timeout(Duration.ofSeconds(300)) // 超时时间设置为300秒5分钟
.build(); .build();
if (orderToolService != null) { if (orderSvc != null && goodsSvc != null) {
aiService = AiServices.builder(OrderAiService.class) aiService = AiServices.builder(OrderAiService.class)
.chatModel(modelInstance) .chatModel(modelInstance)
.tools(orderToolService) .tools(orderSvc, goodsSvc)
.build();
} else if (orderSvc != null) {
aiService = AiServices.builder(OrderAiService.class)
.chatModel(modelInstance)
.tools(orderSvc)
.build();
} else if (goodsSvc != null) {
aiService = AiServices.builder(OrderAiService.class)
.chatModel(modelInstance)
.tools(goodsSvc)
.build(); .build();
} else { } else {
aiService = AiServices.builder(OrderAiService.class) aiService = AiServices.builder(OrderAiService.class)

View File

@ -8,29 +8,29 @@
## 页面映射表 ## 页面映射表
| 关键词 | 路由路径 | 提示消息 | | 关键词 | 路由路径 | 提示消息 |
| --- | --- | --- | | --- |-------------------| --- |
| 打开店铺管理 | /shop/shop_list | 正在跳转到店铺管理页面 | | 打开店铺管理 | /shop/shop_list | 正在跳转到店铺管理页面 |
| 进入店铺管理 | /shop/shop_list | 正在跳转到店铺管理页面 | | 进入店铺管理 | /shop/shop_list | 正在跳转到店铺管理页面 |
| 前往店铺管理 | /shop/shop_list | 正在跳转到店铺管理页面 | | 前往店铺管理 | /shop/shop_list | 正在跳转到店铺管理页面 |
| 打开订单管理 | /order/order_list | 正在跳转到订单管理页面 | | 打开订单管理 | /order/order_list | 正在跳转到订单管理页面 |
| 进入订单管理 | /order/order_list | 正在跳转到订单管理页面 | | 进入订单管理 | /order/order_list | 正在跳转到订单管理页面 |
| 前往订单管理 | /order/order_list | 正在跳转到订单管理页面 | | 前往订单管理 | /order/order_list | 正在跳转到订单管理页面 |
| 打开商品管理 | /goods | 正在跳转到商品管理页面 | | 打开商品管理 | /goods/goods_list | 正在跳转到商品管理页面 |
| 进入商品管理 | /goods | 正在跳转到商品管理页面 | | 进入商品管理 | /goods/goods_list | 正在跳转到商品管理页面 |
| 前往商品管理 | /goods | 正在跳转到商品管理页面 | | 前往商品管理 | /goods/goods_list | 正在跳转到商品管理页面 |
| 打开库存管理 | /stock | 正在跳转到库存管理页面 | | 打开库存管理 | /stock | 正在跳转到库存管理页面 |
| 进入库存管理 | /stock | 正在跳转到库存管理页面 | | 进入库存管理 | /stock | 正在跳转到库存管理页面 |
| 前往库存管理 | /stock | 正在跳转到库存管理页面 | | 前往库存管理 | /stock | 正在跳转到库存管理页面 |
| 打开发货管理 | /shipping | 正在跳转到发货管理页面 | | 打开发货管理 | /shipping | 正在跳转到发货管理页面 |
| 进入发货管理 | /shipping | 正在跳转到发货管理页面 | | 进入发货管理 | /shipping | 正在跳转到发货管理页面 |
| 前往发货管理 | /shipping | 正在跳转到发货管理页面 | | 前往发货管理 | /shipping | 正在跳转到发货管理页面 |
| 打开售后管理 | /refund | 正在跳转到售后管理页面 | | 打开售后管理 | /refund | 正在跳转到售后管理页面 |
| 进入售后管理 | /refund | 正在跳转到售后管理页面 | | 进入售后管理 | /refund | 正在跳转到售后管理页面 |
| 前往售后管理 | /refund | 正在跳转到售后管理页面 | | 前往售后管理 | /refund | 正在跳转到售后管理页面 |
| 打开采购管理 | /purchase | 正在跳转到采购管理页面 | | 打开采购管理 | /purchase | 正在跳转到采购管理页面 |
| 进入采购管理 | /purchase | 正在跳转到采购管理页面 | | 进入采购管理 | /purchase | 正在跳转到采购管理页面 |
| 前往采购管理 | /purchase | 正在跳转到采购管理页面 | | 前往采购管理 | /purchase | 正在跳转到采购管理页面 |
| 打开系统设置 | /system | 正在跳转到系统设置页面 | | 打开系统设置 | /system | 正在跳转到系统设置页面 |
| 进入系统设置 | /system | 正在跳转到系统设置页面 | | 进入系统设置 | /system | 正在跳转到系统设置页面 |
| 前往系统设置 | /system | 正在跳转到系统设置页面 | | 前往系统设置 | /system | 正在跳转到系统设置页面 |

180
docs/ai-agent-analysis.md Normal file
View File

@ -0,0 +1,180 @@
## api/ai-agent 目录代码结构分析
### 一、模块概述
ai-agent 是一个基于 **LangChain4J** 的 AI 智能助手模块,用于实现对话式电商管理功能。
**核心技术栈:**
- LangChain4J (AI 框架)
- Ollama (本地大模型) / DeepSeek API (云端大模型)
- SSE (Server-Sent Events) 实时通信
- Spring Cloud 微服务
---
### 二、目录结构
```
api/ai-agent/
├── src/main/java/cn/qihangerp/erp/
│ ├── AiAgent.java # 启动类
│ ├── controller/
│ │ ├── SseController.java # SSE实时通信控制器 ⭐核心
│ │ ├── HomeController.java # 主页控制器
│ │ ├── OllamaController.java # Ollama模型控制器
│ │ └── AiUserRoleController.java # AI用户角色控制器
│ ├── service/
│ │ ├── OrderService.java # 订单服务 (模拟数据)
│ │ └── OrderToolService.java # AI订单工具 ⭐核心
│ ├── serviceImpl/
│ │ ├── AiService.java # AI服务核心逻辑 ⭐⭐核心
│ │ ├── ConversationHistoryManager.java # 对话历史管理
│ │ ├── SessionManager.java # 会话管理
│ │ ├── InventorySalesAnalyzer.java # 库存销售分析
│ │ └── DeepSeekService.java # DeepSeek API封装
│ ├── feign/
│ │ ├── OpenApiService.java # 内部API调用
│ │ └── EchoService.java
│ └── config/
│ └── MybatisPlusConfig.java
└── src/main/resources/
├── application.yml # 配置文件
└── page-rules.md # 页面跳转规则
```
---
### 三、核心业务逻辑
#### 1. SseController (SseController.java:30-240)
**职责:** 处理前端 SSE 实时通信
```
客户端连接 → 鉴权 → 创建会话 → 心跳保活 → AI处理 → 返回响应
```
**关键接口:**
- `GET /sse/connect` - 建立 SSE 连接
- `GET /sse/send` - 发送消息并获取 AI 回复
- `GET /sse/disconnect` - 断开连接
- `GET /sse/history` - 获取对话历史
- `GET /sse/status` - 获取服务状态
---
#### 2. AiService (AiService.java:24-302)
**职责:** AI 消息处理核心服务
```
用户消息 → 页面规则匹配 → 上下文增强 → 模型调用 → 返回响应
```
**核心流程:**
1. **页面规则匹配** (`checkPageRules`)
- 从 `page-rules.md` 加载规则
- 匹配关键词 → 返回导航指令 JSON
2. **上下文增强**
- 将"今天"替换为实际日期
- 限制历史上下文最长 2000 字符
3. **模型选择**
- `deepseek` 前缀 → DeepSeek API
- 其他 → Ollama 本地模型
4. **Tool 集成**
- 绑定 `OrderToolService` 提供订单查询能力
---
#### 3. OrderToolService (OrderToolService.java:11-115)
**职责:** AI 可调用的订单查询工具
| 方法 | 功能 |
|------|------|
| `getOrderById` | 按订单号查询 |
| `getAllOrders` | 获取所有订单 |
| `getOrdersByStatus` | 按状态查询 |
| `getPendingOrders` | 待发货订单 |
| `getOrdersByDate` | 按日期查询 |
使用 `@Tool` 注解声明为 LangChain4J 工具
---
#### 4. ConversationHistoryManager (ConversationHistoryManager.java:17-145)
**职责:** 对话历史持久化管理
- 依赖 `IAiConversationHistoryService` 存储到数据库
- 支持获取历史、获取最近N条、清空历史
---
#### 5. SessionManager (SessionManager.java:16-101)
**职责:** 用户会话管理
- 内存缓存 + 数据库持久化
- `userId ↔ sessionId` 双向映射
---
#### 6. InventorySalesAnalyzer (InventorySalesAnalyzer.java:11-273)
**职责:** 库存销售分析(独立工具类)
- 解析库存和销售 JSON 数据
- 计算关键指标:日均销量、可售天数、库存状态
- 调用 DeepSeek API 生成分析报告
---
### 四、数据流图
```
┌─────────────┐ SSE ┌──────────────────┐
│ 前端 Vue │ ──────────→ │ SseController │
└─────────────┘ └────────┬─────────┘
┌────────────────┼────────────────┐
▼ ▼ ▼
┌──────────────┐ ┌─────────────┐ ┌──────────────┐
│ SessionManager│ │Conversation │ │ AiService │
│ │ │ HistoryMgr │ │ │
└──────────────┘ └─────────────┘ └──────┬───────┘
┌──────────────────┼──────────────────┐
▼ ▼ ▼
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ PageRules │ │OrderToolSvc │ │ LLM Model │
│ (页面导航) │ │ (订单查询) │ │(Ollama/DeepSeek)
└─────────────┘ └──────────────┘ └─────────────┘
```
---
### 五、配置说明 (application.yml)
```yaml
server.port: 8084 # AI服务端口
spring.datasource: MySQL连接配置
spring.data.redis: Redis连接配置
deepseek.api: # DeepSeek API配置
key: sk-xxxx
endpoint: https://api.deepseek.com/v1
model: deepseek-chat
```
---
### 六、待优化点
1. **OrderService.java** - 目前使用模拟数据,需对接真实 ERP 订单数据
2. **API Key 硬编码** - InventorySalesAnalyzer 中的 API Key 应配置化
3. **SessionManager** - 内存缓存无持久化,重启丢失
4. **错误处理** - AI 服务调用失败时需更完善的降级策略