新增AI商品查询
This commit is contained in:
parent
8adf745796
commit
27116b83aa
|
|
@ -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,失败抛出异常
|
||||
```
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -9,16 +9,16 @@
|
|||
## 页面映射表
|
||||
|
||||
| 关键词 | 路由路径 | 提示消息 |
|
||||
| --- | --- | --- |
|
||||
| --- |-------------------| --- |
|
||||
| 打开店铺管理 | /shop/shop_list | 正在跳转到店铺管理页面 |
|
||||
| 进入店铺管理 | /shop/shop_list | 正在跳转到店铺管理页面 |
|
||||
| 前往店铺管理 | /shop/shop_list | 正在跳转到店铺管理页面 |
|
||||
| 打开订单管理 | /order/order_list | 正在跳转到订单管理页面 |
|
||||
| 进入订单管理 | /order/order_list | 正在跳转到订单管理页面 |
|
||||
| 前往订单管理 | /order/order_list | 正在跳转到订单管理页面 |
|
||||
| 打开商品管理 | /goods | 正在跳转到商品管理页面 |
|
||||
| 进入商品管理 | /goods | 正在跳转到商品管理页面 |
|
||||
| 前往商品管理 | /goods | 正在跳转到商品管理页面 |
|
||||
| 打开商品管理 | /goods/goods_list | 正在跳转到商品管理页面 |
|
||||
| 进入商品管理 | /goods/goods_list | 正在跳转到商品管理页面 |
|
||||
| 前往商品管理 | /goods/goods_list | 正在跳转到商品管理页面 |
|
||||
| 打开库存管理 | /stock | 正在跳转到库存管理页面 |
|
||||
| 进入库存管理 | /stock | 正在跳转到库存管理页面 |
|
||||
| 前往库存管理 | /stock | 正在跳转到库存管理页面 |
|
||||
|
|
|
|||
|
|
@ -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 服务调用失败时需更完善的降级策略
|
||||
Loading…
Reference in New Issue