From 27116b83aa77e5ab8dbf618c98a1f13bd27c8c4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=AF=E8=88=AA?= <280645618@qq.com> Date: Wed, 11 Mar 2026 13:06:10 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9EAI=E5=95=86=E5=93=81=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 296 ++++++++++++++++++ .../qihangerp/erp/service/GoodsService.java | 215 +++++++++++++ .../erp/service/GoodsToolService.java | 149 +++++++++ .../erp/service/OrderToolService.java | 3 + .../qihangerp/erp/serviceImpl/AiService.java | 54 +++- api/ai-agent/src/main/resources/page-rules.md | 46 +-- docs/ai-agent-analysis.md | 180 +++++++++++ 7 files changed, 910 insertions(+), 33 deletions(-) create mode 100644 AGENTS.md create mode 100644 api/ai-agent/src/main/java/cn/qihangerp/erp/service/GoodsService.java create mode 100644 api/ai-agent/src/main/java/cn/qihangerp/erp/service/GoodsToolService.java create mode 100644 docs/ai-agent-analysis.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..da94d53e --- /dev/null +++ b/AGENTS.md @@ -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 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 + +``` + +#### 组件结构 +```vue + + + + + +``` + +--- + +### 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` + +#### 代码风格 +- 使用 Lombok @Data, @Slf4j +- 使用 fastjson2 处理 JSON +- 返回 `ResultVo` +- 使用 `PageQuery` / `PageResult` 分页 + +```java +public interface OOrderService extends IService { + PageResult queryPageList(OrderSearchRequest bo, PageQuery pageQuery); + ResultVo manualShipmentOrder(OrderShipRequest shipBo, String createBy); +} +``` + +--- + +### 错误处理 + +**前端**: 使用 ElementUI `this.$message()` 反馈 +```javascript +this.$message({ message: '操作失败', type: 'error' }) +``` + +**后端**: 返回 `ResultVo` 错误码,使用 `@ExceptionHandler` +```java +@ExceptionHandler(Exception.class) +public ResultVo 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,失败抛出异常 +``` diff --git a/api/ai-agent/src/main/java/cn/qihangerp/erp/service/GoodsService.java b/api/ai-agent/src/main/java/cn/qihangerp/erp/service/GoodsService.java new file mode 100644 index 00000000..9d4a5bd3 --- /dev/null +++ b/api/ai-agent/src/main/java/cn/qihangerp/erp/service/GoodsService.java @@ -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 searchGoodsByName(String keyword) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.like(OGoodsSku::getGoodsName, keyword) + .or() + .like(OGoodsSku::getSkuName, keyword); + List list = oGoodsSkuService.list(wrapper); + return list.stream().map(this::convertToGoods).collect(Collectors.toList()); + } + + /** + * 根据商品编码查询商品SKU + * @param goodsNum 商品编码 + * @return 商品SKU列表 + */ + public List searchGoodsByGoodsNum(String goodsNum) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(OGoodsSku::getGoodsNum, goodsNum); + List 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 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 searchGoodsByGoodsId(Long goodsId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(OGoodsSku::getGoodsId, goodsId); + List list = oGoodsSkuService.list(wrapper); + return list.stream().map(this::convertToGoods).collect(Collectors.toList()); + } + + /** + * 获取所有商品SKU + * @return 商品SKU列表 + */ + public List getAllGoods() { + List list = oGoodsSkuService.list(); + return list.stream().map(this::convertToGoods).collect(Collectors.toList()); + } + + /** + * 通用搜索商品SKU + * @param keyword 关键词(可搜索名称、编码、SKU编码等) + * @return 商品SKU列表 + */ + public List searchGoods(String keyword) { + LambdaQueryWrapper 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 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(); + } + } +} diff --git a/api/ai-agent/src/main/java/cn/qihangerp/erp/service/GoodsToolService.java b/api/ai-agent/src/main/java/cn/qihangerp/erp/service/GoodsToolService.java new file mode 100644 index 00000000..330e7fea --- /dev/null +++ b/api/ai-agent/src/main/java/cn/qihangerp/erp/service/GoodsToolService.java @@ -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", "
"); + } catch (NumberFormatException e) { + return "无效的商品ID格式:" + id; + } + } + + /** + * 根据商品名称搜索商品 + * @param keyword 商品名称关键词 + * @return 商品列表 + */ + @Tool("根据商品名称搜索商品信息") + public String searchGoodsByName(String keyword) { + List goodsList = goodsService.searchGoodsByName(keyword); + if (goodsList.isEmpty()) { + return "未找到名称包含'" + keyword + "'的商品"; + } + StringBuilder sb = new StringBuilder(); + sb.append("找到").append(goodsList.size()).append("个商品:
"); + for (GoodsService.Goods goods : goodsList) { + sb.append(goods.toString()).append("

"); + } + return sb.toString(); + } + + /** + * 根据商品编码搜索商品 + * @param goodsNum 商品编码 + * @return 商品列表 + */ + @Tool("根据商品编码查询商品信息") + public String searchGoodsByGoodsNum(String goodsNum) { + List goodsList = goodsService.searchGoodsByGoodsNum(goodsNum); + if (goodsList.isEmpty()) { + return "未找到编码为'" + goodsNum + "'的商品"; + } + StringBuilder sb = new StringBuilder(); + sb.append("找到").append(goodsList.size()).append("个商品:
"); + for (GoodsService.Goods goods : goodsList) { + sb.append(goods.toString()).append("

"); + } + 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", "
"); + } + + /** + * 通用搜索商品(支持名称、编码、SKU编码、条码等) + * @param keyword 搜索关键词 + * @return 商品列表 + */ + @Tool("通用搜索商品信息,支持名称、编码、SKU编码、条码搜索") + public String searchGoods(String keyword) { + List goodsList = goodsService.searchGoods(keyword); + if (goodsList.isEmpty()) { + return "未找到与'" + keyword + "'相关的商品"; + } + StringBuilder sb = new StringBuilder(); + sb.append("找到").append(goodsList.size()).append("个商品:
"); + for (GoodsService.Goods goods : goodsList) { + sb.append(goods.toString()).append("

"); + } + return sb.toString(); + } + + /** + * 获取所有商品 + * @return 商品列表 + */ + @Tool("获取所有商品信息") + public String getAllGoods() { + List goodsList = goodsService.getAllGoods(); + if (goodsList.isEmpty()) { + return "暂无商品数据"; + } + StringBuilder sb = new StringBuilder(); + sb.append("所有商品(共").append(goodsList.size()).append("个):
"); + for (GoodsService.Goods goods : goodsList) { + sb.append(goods.toString()).append("

"); + } + 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 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:
"); + for (GoodsService.Goods goods : goodsList) { + sb.append(goods.toString()).append("

"); + } + return sb.toString(); + } catch (NumberFormatException e) { + return "无效的商品ID格式:" + goodsId; + } + } +} diff --git a/api/ai-agent/src/main/java/cn/qihangerp/erp/service/OrderToolService.java b/api/ai-agent/src/main/java/cn/qihangerp/erp/service/OrderToolService.java index 96bb8c62..1a19a2b4 100644 --- a/api/ai-agent/src/main/java/cn/qihangerp/erp/service/OrderToolService.java +++ b/api/ai-agent/src/main/java/cn/qihangerp/erp/service/OrderToolService.java @@ -1,6 +1,8 @@ package cn.qihangerp.erp.service; import dev.langchain4j.agent.tool.Tool; +import org.springframework.stereotype.Component; + import java.util.List; import java.time.LocalDate; import java.time.format.DateTimeFormatter; @@ -8,6 +10,7 @@ import java.time.format.DateTimeFormatter; /** * 订单工具服务,用于AI查询订单信息 */ +@Component public class OrderToolService { private final OrderService orderService; diff --git a/api/ai-agent/src/main/java/cn/qihangerp/erp/serviceImpl/AiService.java b/api/ai-agent/src/main/java/cn/qihangerp/erp/serviceImpl/AiService.java index 9ae3f024..b18a9fea 100644 --- a/api/ai-agent/src/main/java/cn/qihangerp/erp/serviceImpl/AiService.java +++ b/api/ai-agent/src/main/java/cn/qihangerp/erp/serviceImpl/AiService.java @@ -3,6 +3,7 @@ package cn.qihangerp.erp.serviceImpl; import dev.langchain4j.model.ollama.OllamaChatModel; import dev.langchain4j.model.openai.OpenAiChatModel; import dev.langchain4j.service.AiServices; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import java.io.BufferedReader; @@ -15,6 +16,7 @@ import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.List; import cn.qihangerp.erp.service.OrderToolService; +import cn.qihangerp.erp.service.GoodsToolService; import cn.qihangerp.erp.serviceImpl.ConversationHistoryManager; /** @@ -23,6 +25,12 @@ import cn.qihangerp.erp.serviceImpl.ConversationHistoryManager; @Service public class AiService { + @Autowired + private OrderToolService orderToolService; + + @Autowired + private GoodsToolService goodsToolService; + /** * 页面规则类 */ @@ -195,14 +203,20 @@ public class AiService { System.out.println("========================================"); System.out.println("发送给AI的消息: " + enhancedMessage); - // 尝试创建订单工具服务 - OrderToolService orderToolService = null; + // 使用注入的工具服务(订单工具和商品工具) + OrderToolService orderSvc = orderToolService; + GoodsToolService goodsSvc = goodsToolService; + try { - orderToolService = new OrderToolService(); - System.out.println("成功创建OrderToolService"); + if (orderSvc == null) { + orderSvc = new OrderToolService(); + } + if (goodsSvc == null) { + goodsSvc = new GoodsToolService(); + } + System.out.println("成功创建AI工具服务"); } catch (Exception e) { - System.out.println("创建OrderToolService失败: " + e.getMessage()); - // 工具创建失败,仍然继续执行,只是不使用工具 + System.out.println("创建AI工具服务失败: " + e.getMessage()); } // 根据模型名称选择使用Ollama还是DeepSeek API @@ -223,10 +237,20 @@ public class AiService { .timeout(Duration.ofSeconds(300)) .build(); - if (orderToolService != null) { + if (orderSvc != null && goodsSvc != null) { aiService = AiServices.builder(OrderAiService.class) .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(); } else { aiService = AiServices.builder(OrderAiService.class) @@ -249,10 +273,20 @@ public class AiService { .timeout(Duration.ofSeconds(300)) // 超时时间设置为300秒(5分钟) .build(); - if (orderToolService != null) { + if (orderSvc != null && goodsSvc != null) { aiService = AiServices.builder(OrderAiService.class) .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(); } else { aiService = AiServices.builder(OrderAiService.class) diff --git a/api/ai-agent/src/main/resources/page-rules.md b/api/ai-agent/src/main/resources/page-rules.md index 599386e3..12ff0d63 100644 --- a/api/ai-agent/src/main/resources/page-rules.md +++ b/api/ai-agent/src/main/resources/page-rules.md @@ -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 | 正在跳转到订单管理页面 | -| 打开商品管理 | /goods | 正在跳转到商品管理页面 | -| 进入商品管理 | /goods | 正在跳转到商品管理页面 | -| 前往商品管理 | /goods | 正在跳转到商品管理页面 | -| 打开库存管理 | /stock | 正在跳转到库存管理页面 | -| 进入库存管理 | /stock | 正在跳转到库存管理页面 | -| 前往库存管理 | /stock | 正在跳转到库存管理页面 | -| 打开发货管理 | /shipping | 正在跳转到发货管理页面 | -| 进入发货管理 | /shipping | 正在跳转到发货管理页面 | -| 前往发货管理 | /shipping | 正在跳转到发货管理页面 | -| 打开售后管理 | /refund | 正在跳转到售后管理页面 | -| 进入售后管理 | /refund | 正在跳转到售后管理页面 | -| 前往售后管理 | /refund | 正在跳转到售后管理页面 | -| 打开采购管理 | /purchase | 正在跳转到采购管理页面 | -| 进入采购管理 | /purchase | 正在跳转到采购管理页面 | -| 前往采购管理 | /purchase | 正在跳转到采购管理页面 | -| 打开系统设置 | /system | 正在跳转到系统设置页面 | -| 进入系统设置 | /system | 正在跳转到系统设置页面 | -| 前往系统设置 | /system | 正在跳转到系统设置页面 | \ No newline at end of file +| 打开商品管理 | /goods/goods_list | 正在跳转到商品管理页面 | +| 进入商品管理 | /goods/goods_list | 正在跳转到商品管理页面 | +| 前往商品管理 | /goods/goods_list | 正在跳转到商品管理页面 | +| 打开库存管理 | /stock | 正在跳转到库存管理页面 | +| 进入库存管理 | /stock | 正在跳转到库存管理页面 | +| 前往库存管理 | /stock | 正在跳转到库存管理页面 | +| 打开发货管理 | /shipping | 正在跳转到发货管理页面 | +| 进入发货管理 | /shipping | 正在跳转到发货管理页面 | +| 前往发货管理 | /shipping | 正在跳转到发货管理页面 | +| 打开售后管理 | /refund | 正在跳转到售后管理页面 | +| 进入售后管理 | /refund | 正在跳转到售后管理页面 | +| 前往售后管理 | /refund | 正在跳转到售后管理页面 | +| 打开采购管理 | /purchase | 正在跳转到采购管理页面 | +| 进入采购管理 | /purchase | 正在跳转到采购管理页面 | +| 前往采购管理 | /purchase | 正在跳转到采购管理页面 | +| 打开系统设置 | /system | 正在跳转到系统设置页面 | +| 进入系统设置 | /system | 正在跳转到系统设置页面 | +| 前往系统设置 | /system | 正在跳转到系统设置页面 | \ No newline at end of file diff --git a/docs/ai-agent-analysis.md b/docs/ai-agent-analysis.md new file mode 100644 index 00000000..1e1ef927 --- /dev/null +++ b/docs/ai-agent-analysis.md @@ -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 服务调用失败时需更完善的降级策略