新增ai-agent项目
This commit is contained in:
parent
e929a0dee8
commit
e907e6003a
|
|
@ -0,0 +1,38 @@
|
|||
target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
!**/src/main/**/target/
|
||||
!**/src/test/**/target/
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea/modules.xml
|
||||
.idea/jarRepositories.xml
|
||||
.idea/compiler.xml
|
||||
.idea/libraries/
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### Eclipse ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
build/
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
|
||||
### Mac OS ###
|
||||
.DS_Store
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
FROM openjdk:17-jdk-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY ./target/erp-api-2.12.0.jar erp-api.jar
|
||||
|
||||
CMD ["java", "-Duser.timezone=Asia/Shanghai", "-jar", "erp-api.jar"]
|
||||
|
|
@ -0,0 +1,154 @@
|
|||
<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>org.springframework.boot</groupId>-->
|
||||
<!-- <artifactId>spring-boot-starter-parent</artifactId>-->
|
||||
<!-- <version>3.0.2</version>-->
|
||||
<!-- <relativePath/>-->
|
||||
<!-- </parent>-->
|
||||
<parent>
|
||||
<groupId>cn.qihangerp.api</groupId>
|
||||
<artifactId>api</artifactId>
|
||||
<version>2.12.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ai-agent</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<version>0.1.0</version>
|
||||
<name>ai-agent</name>
|
||||
<url>http://maven.apache.org</url>
|
||||
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>org.springframework.boot</groupId>-->
|
||||
<!-- <artifactId>spring-boot-starter</artifactId>-->
|
||||
<!-- </dependency>-->
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>org.springframework.boot</groupId>-->
|
||||
<!-- <artifactId>spring-boot-starter-web</artifactId>-->
|
||||
<!-- <exclusions>-->
|
||||
<!-- <exclusion>-->
|
||||
<!-- <groupId>org.springframework.boot</groupId>-->
|
||||
<!-- <artifactId>spring-boot-starter-tomcat</artifactId>-->
|
||||
<!-- </exclusion>-->
|
||||
<!-- </exclusions>-->
|
||||
<!-- </dependency>-->
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>org.springframework.boot</groupId>-->
|
||||
<!-- <artifactId>spring-boot-starter-undertow</artifactId>-->
|
||||
<!-- </dependency>-->
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>org.springframework.cloud</groupId>-->
|
||||
<!-- <artifactId>spring-cloud-starter</artifactId>-->
|
||||
<!-- <version>4.0.0</version>-->
|
||||
<!-- </dependency>-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>org.springframework.cloud</groupId>-->
|
||||
<!-- <artifactId>spring-cloud-starter-loadbalancer</artifactId>-->
|
||||
<!-- <version>4.0.0</version>-->
|
||||
<!-- </dependency>-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-openfeign</artifactId>
|
||||
<version>4.0.0</version>
|
||||
</dependency>
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>org.springframework.kafka</groupId>-->
|
||||
<!-- <artifactId>spring-kafka</artifactId>-->
|
||||
<!-- </dependency>-->
|
||||
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>com.alibaba.cloud</groupId>-->
|
||||
<!-- <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>-->
|
||||
<!-- </dependency>-->
|
||||
<!--SpringCloud Alibaba nacos 服务发现依赖-->
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.qihangerp.core</groupId>
|
||||
<artifactId>security</artifactId>
|
||||
<version>1.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>okhttp</artifactId>
|
||||
<version>4.12.0</version>
|
||||
</dependency>
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>cn.qihangerp.module</groupId>-->
|
||||
<!-- <artifactId>goods</artifactId>-->
|
||||
<!-- <version>1.0</version>-->
|
||||
<!-- </dependency>-->
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>cn.qihangerp.module</groupId>-->
|
||||
<!-- <artifactId>order</artifactId>-->
|
||||
<!-- <version>1.0</version>-->
|
||||
<!-- </dependency>-->
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>cn.qihangerp.service</groupId>-->
|
||||
<!-- <artifactId>stock</artifactId>-->
|
||||
<!-- <version>2.0.0</version>-->
|
||||
<!-- </dependency>-->
|
||||
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>cn.qihangerp.services</groupId>-->
|
||||
<!-- <artifactId>goods-api</artifactId>-->
|
||||
<!-- <version>1.0.6</version>-->
|
||||
<!-- </dependency>-->
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>cn.qihangerp.service</groupId>-->
|
||||
<!-- <artifactId>order</artifactId>-->
|
||||
<!-- <version>2.0.0</version>-->
|
||||
<!-- </dependency>-->
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>cn.qihangerp.services</groupId>-->
|
||||
<!-- <artifactId>order-api</artifactId>-->
|
||||
<!-- <version>1.0.6</version>-->
|
||||
<!-- </dependency>-->
|
||||
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>8.0.33</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
|
||||
<version>3.5.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.poi</groupId>
|
||||
<artifactId>poi-ooxml</artifactId>
|
||||
<version>5.2.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.qihangerp.service</groupId>
|
||||
<artifactId>service</artifactId>
|
||||
<version>2.0.0</version>
|
||||
</dependency>
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>org.projectlombok</groupId>-->
|
||||
<!-- <artifactId>lombok</artifactId>-->
|
||||
<!-- <version>1.18.30</version>-->
|
||||
<!-- <scope>provided</scope>-->
|
||||
<!-- </dependency>-->
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package cn.qihangerp.erp;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
//import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
|
||||
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
|
||||
import org.springframework.cloud.openfeign.EnableFeignClients;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
//@EnableDiscoveryClient
|
||||
//@MapperScan("cn.qihangerp.oms.mapper")
|
||||
@EnableFeignClients(basePackages = "cn.qihangerp.erp")
|
||||
@EnableDiscoveryClient
|
||||
@ComponentScan(basePackages={"cn.qihangerp"})
|
||||
@SpringBootApplication
|
||||
public class AiAgent {
|
||||
public static void main( String[] args )
|
||||
{
|
||||
System.out.println( "Hello ai-agent!" );
|
||||
SpringApplication.run(AiAgent.class, args);
|
||||
|
||||
}
|
||||
@Bean
|
||||
@LoadBalanced
|
||||
public RestTemplate restTemplate() {
|
||||
return new RestTemplate();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package cn.qihangerp.erp.config;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.DbType;
|
||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
|
||||
@Configuration
|
||||
@MapperScan({"cn.qihangerp.mapper","cn.qihangerp.module.mapper","cn.qihangerp.mapper"})
|
||||
public class MybatisPlusConfig {
|
||||
@Primary
|
||||
@Bean
|
||||
public MybatisPlusInterceptor mybatisPlusInterceptor() {
|
||||
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
|
||||
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); //注意使用哪种数据库
|
||||
return interceptor;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
package cn.qihangerp.erp.controller;
|
||||
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
@AllArgsConstructor
|
||||
@RestController
|
||||
public class HomeController {
|
||||
|
||||
// @Resource
|
||||
// private EchoService echoService;
|
||||
@Autowired
|
||||
private RestTemplate restTemplate;
|
||||
@Autowired
|
||||
private RedisTemplate<String,String> redisTemplate;
|
||||
|
||||
@GetMapping("/")
|
||||
public String home(){
|
||||
return "{'code':0,'msg':'oms-api请通过api访问'}";
|
||||
}
|
||||
|
||||
|
||||
|
||||
@GetMapping(value = "/echo-rest")
|
||||
public String rest() {
|
||||
return restTemplate.getForObject("http://tao-oms/test/na", String.class);
|
||||
}
|
||||
// @GetMapping(value = "/echo-feign")
|
||||
// public String feign() {
|
||||
// String token = "Bearer eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6IjdkOTBmN2EzLWUwNWQtNDkxNy04NjIzLTU1OGRhNGY3NjE3NiJ9._Oukm9b0P1WvcOywLdhs6_BOt_6mRSF41Q6f4fBm_DGUkPR86Qg1tqyRTM5ouTR2Xz46IRuRAVez8Wcl3NIlwg";
|
||||
//
|
||||
// return echoService.echo(token);
|
||||
// }
|
||||
}
|
||||
|
|
@ -0,0 +1,174 @@
|
|||
package cn.qihangerp.erp.controller;
|
||||
|
||||
import com.alibaba.fastjson2.JSONArray;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/sse")
|
||||
public class SseController {
|
||||
|
||||
private static final Map<String, SseEmitter> emitters = new ConcurrentHashMap<>();
|
||||
private final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
|
||||
|
||||
@GetMapping(value = "/connect", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
||||
public SseEmitter connect(@RequestParam String clientId) {
|
||||
SseEmitter emitter = new SseEmitter(Long.MAX_VALUE);
|
||||
emitters.put(clientId, emitter);
|
||||
|
||||
// 设置超时处理
|
||||
emitter.onTimeout(() -> emitters.remove(clientId));
|
||||
emitter.onCompletion(() -> emitters.remove(clientId));
|
||||
|
||||
// 发送连接成功消息
|
||||
try {
|
||||
emitter.send(SseEmitter.event()
|
||||
.name("connected")
|
||||
.data("连接成功"));
|
||||
} catch (IOException e) {
|
||||
emitters.remove(clientId);
|
||||
}
|
||||
|
||||
// 定期发送心跳
|
||||
executorService.scheduleAtFixedRate(() -> {
|
||||
try {
|
||||
if (emitters.containsKey(clientId)) {
|
||||
emitters.get(clientId).send(SseEmitter.event()
|
||||
.name("heartbeat")
|
||||
.data("ping"));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
emitters.remove(clientId);
|
||||
}
|
||||
}, 30, 30, TimeUnit.SECONDS);
|
||||
|
||||
return emitter;
|
||||
}
|
||||
|
||||
@GetMapping("/send")
|
||||
public String sendMessage(@RequestParam String clientId, @RequestParam String message) {
|
||||
log.info("=============来新消息了!");
|
||||
SseEmitter emitter = emitters.get(clientId);
|
||||
if (emitter != null) {
|
||||
try {
|
||||
// 调用opencode接口获取回复
|
||||
String response = callOpencodeApi(message);
|
||||
|
||||
emitter.send(SseEmitter.event()
|
||||
.name("message")
|
||||
.data(response));
|
||||
return "消息发送成功";
|
||||
} catch (Exception e) {
|
||||
emitters.remove(clientId);
|
||||
return "消息发送失败";
|
||||
}
|
||||
}
|
||||
return "客户端不存在";
|
||||
}
|
||||
|
||||
private String callOpencodeApi(String message) throws Exception {
|
||||
// 创建HTTP客户端
|
||||
HttpClient client = HttpClient.newHttpClient();
|
||||
|
||||
// 1. 创建新会话
|
||||
HttpRequest createSessionRequest = HttpRequest.newBuilder()
|
||||
.uri(URI.create("http://localhost:14967/session"))
|
||||
.header("Content-Type", "application/json")
|
||||
.POST(HttpRequest.BodyPublishers.ofString("{}"))
|
||||
.build();
|
||||
|
||||
HttpResponse<String> createSessionResponse = client.send(createSessionRequest, HttpResponse.BodyHandlers.ofString());
|
||||
String sessionId = parseSessionId(createSessionResponse.body());
|
||||
|
||||
// 2. 构建消息请求体
|
||||
JSONObject requestBody = new JSONObject();
|
||||
JSONArray parts = new JSONArray();
|
||||
JSONObject part = new JSONObject();
|
||||
part.put("type", "text");
|
||||
part.put("text", message);
|
||||
parts.add(part);
|
||||
requestBody.put("parts", parts);
|
||||
|
||||
// 3. 向会话发送消息
|
||||
HttpRequest sendMessageRequest = HttpRequest.newBuilder()
|
||||
.uri(URI.create("http://localhost:14967/session/" + sessionId + "/message"))
|
||||
.header("Content-Type", "application/json")
|
||||
.POST(HttpRequest.BodyPublishers.ofString(requestBody.toJSONString()))
|
||||
.build();
|
||||
|
||||
// 发送请求并获取响应
|
||||
HttpResponse<String> response = client.send(sendMessageRequest, HttpResponse.BodyHandlers.ofString());
|
||||
|
||||
// 解析响应,提取AI回复
|
||||
return parseAIResponse(response.body());
|
||||
}
|
||||
|
||||
private String parseSessionId(String responseBody) {
|
||||
// 简单解析JSON,提取sessionId
|
||||
// 实际项目中建议使用JSON库
|
||||
int idIndex = responseBody.indexOf("\"id\":\"");
|
||||
if (idIndex != -1) {
|
||||
int start = idIndex + 6;
|
||||
int end = responseBody.indexOf("\"", start);
|
||||
if (end != -1) {
|
||||
return responseBody.substring(start, end);
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private String parseAIResponse(String responseBody) {
|
||||
log.info("=================AI回复==========");
|
||||
log.info(responseBody);
|
||||
try {
|
||||
// 解析响应,提取AI回复
|
||||
JSONObject jsonObject = JSONObject.parseObject(responseBody);
|
||||
if (jsonObject.containsKey("info")) {
|
||||
JSONArray parts = jsonObject.getJSONArray("parts");
|
||||
for (int i = 0; i < parts.size(); i++) {
|
||||
JSONObject part = parts.getJSONObject(i);
|
||||
if (part.containsKey("text")) {
|
||||
return part.getString("text");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return "无法获取AI回复";
|
||||
}
|
||||
|
||||
@GetMapping("/disconnect")
|
||||
public String disconnect(@RequestParam String clientId) {
|
||||
SseEmitter emitter = emitters.remove(clientId);
|
||||
if (emitter != null) {
|
||||
emitter.complete();
|
||||
return "断开连接成功";
|
||||
}
|
||||
return "客户端不存在";
|
||||
}
|
||||
|
||||
@GetMapping("/status")
|
||||
public String getStatus() {
|
||||
return "当前连接数: " + emitters.size();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
//package cn.qihangerp.oms.feign;
|
||||
//
|
||||
//import org.springframework.cloud.openfeign.FeignClient;
|
||||
//import org.springframework.web.bind.annotation.GetMapping;
|
||||
//import org.springframework.web.bind.annotation.RequestHeader;
|
||||
//
|
||||
//
|
||||
//@FeignClient(name = "open-api")
|
||||
//public interface EchoService {
|
||||
// @GetMapping(value = "/test/na")
|
||||
// String echo(@RequestHeader(name = "Authorization",required = true) String Token);
|
||||
//}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
package cn.qihangerp.erp.feign;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
@FeignClient(name = "oms-api")
|
||||
public interface OpenApiService {
|
||||
@GetMapping(value = "/dou/order/get_detail")
|
||||
JSONObject getDouOrderDetail(@RequestParam String orderId);
|
||||
|
||||
@GetMapping(value = "/dou/refund/get_detail")
|
||||
JSONObject getDouRefundDetail(@RequestParam String id);
|
||||
/**
|
||||
* 抖店发货
|
||||
* @param Token
|
||||
* @return
|
||||
*/
|
||||
// @GetMapping(value = "/dou/ship/order_ship")
|
||||
// JSONObject shipDouOrder(@RequestHeader(name = "Authorization",required = true) String Token, @RequestBody DouOrderShipBo bo);
|
||||
|
||||
// @GetMapping(value = "/dou/ship/order_ship_multi_pack")
|
||||
// JSONObject shipDouOrderMultiPack(@RequestHeader(name = "Authorization",required = true) String Token, @RequestBody DouOrderShipMultiPackBo bo);
|
||||
|
||||
|
||||
@GetMapping(value = "/jd/order/get_detail")
|
||||
JSONObject getJdOrderDetail(@RequestParam Long orderId,@RequestParam Integer vc);
|
||||
|
||||
@GetMapping(value = "/jd/refund/get_detail")
|
||||
JSONObject getJdRefundDetail(@RequestParam Long refundId,@RequestParam Integer vc);
|
||||
|
||||
@GetMapping(value = "/pdd/order/get_detail")
|
||||
JSONObject getPddOrderDetail(@RequestParam String sn);
|
||||
|
||||
@GetMapping(value = "/pdd/refund/get_detail")
|
||||
JSONObject getPddRefundDetail(@RequestParam Long id);
|
||||
|
||||
/**
|
||||
* 淘宝发货
|
||||
* @param Token
|
||||
* @return
|
||||
*/
|
||||
// @GetMapping(value = "/tao/ship/order_ship")
|
||||
// JSONObject shipTaoOrder(@RequestHeader(name = "Authorization",required = true) String Token, @RequestBody TaoOrderShipBo bo);
|
||||
|
||||
@GetMapping(value = "/tao/order/get_detail")
|
||||
JSONObject getTaoOrderDetail(@RequestParam String tid);
|
||||
|
||||
@GetMapping(value = "/tao/refund/get_detail")
|
||||
JSONObject getTaoRefundDetail(@RequestParam Long refundId);
|
||||
|
||||
@GetMapping(value = "/wei/order/get_detail")
|
||||
JSONObject getWeiOrderDetail(@RequestParam String orderId);
|
||||
|
||||
@GetMapping(value = "/wei/refund/get_detail")
|
||||
JSONObject getWeiRefundDetail(@RequestParam String afterSaleOrderId);
|
||||
|
||||
/**
|
||||
* 微信小店发货
|
||||
* @param Token
|
||||
* @return
|
||||
*/
|
||||
// @GetMapping(value = "/wei/ship/order_ship")
|
||||
// JSONObject shipWeiOrder(@RequestHeader(name = "Authorization",required = true) String Token, @RequestBody WeiOrderShipBo bo);
|
||||
}
|
||||
|
|
@ -0,0 +1,365 @@
|
|||
//package cn.qihangerp.erp.serviceImpl;
|
||||
//
|
||||
//import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
//import jakarta.annotation.PostConstruct;
|
||||
//import okhttp3.*;
|
||||
//import org.slf4j.Logger;
|
||||
//import org.slf4j.LoggerFactory;
|
||||
//import org.springframework.beans.factory.annotation.Value;
|
||||
//import org.springframework.stereotype.Service;
|
||||
//import java.io.IOException;
|
||||
//import java.util.*;
|
||||
//import java.util.concurrent.TimeUnit;
|
||||
//
|
||||
//@Service
|
||||
//public class DeepSeekService {
|
||||
//
|
||||
// private static final Logger log = LoggerFactory.getLogger(DeepSeekService.class);
|
||||
// private static final MediaType JSON = MediaType.get("application/json; charset=utf-8");
|
||||
//
|
||||
// @Value("${deepseek.api.key}")
|
||||
// private String apiKey;
|
||||
//
|
||||
// @Value("${deepseek.api.endpoint:https://api.deepseek.com/v1/chat/completions}")
|
||||
// private String apiEndpoint;
|
||||
//
|
||||
// @Value("${deepseek.api.model:deepseek-chat}")
|
||||
// private String model;
|
||||
//
|
||||
// private OkHttpClient okHttpClient;
|
||||
// private final ObjectMapper objectMapper;
|
||||
//
|
||||
// // 缓存最近一次成功的分析结果
|
||||
// private Map<String, Object> cachedAnalysis = new HashMap<>();
|
||||
//
|
||||
// public DeepSeekService(ObjectMapper objectMapper) {
|
||||
// this.objectMapper = objectMapper;
|
||||
// }
|
||||
//
|
||||
// @PostConstruct
|
||||
// public void init() {
|
||||
// // 配置具有重试和连接池功能的OkHttpClient
|
||||
// this.okHttpClient = new OkHttpClient.Builder()
|
||||
// .connectTimeout(15, TimeUnit.SECONDS) // 连接超时
|
||||
// .readTimeout(30, TimeUnit.SECONDS) // 读取超时
|
||||
// .writeTimeout(15, TimeUnit.SECONDS) // 写入超时
|
||||
// .connectionPool(new ConnectionPool(5, 5, TimeUnit.MINUTES)) // 连接池
|
||||
// .addInterceptor(new RetryInterceptor(3)) // 自定义重试拦截器
|
||||
// .addInterceptor(new LoggingInterceptor()) // 日志拦截器
|
||||
// .build();
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 调用DeepSeek API - 带有Spring Retry重试机制
|
||||
// */
|
||||
//// @Retryable(
|
||||
//// value = {IOException.class, RuntimeException.class},
|
||||
//// maxAttempts = 3,
|
||||
//// backoff = @Backoff(delay = 1000, multiplier = 2, maxDelay = 10000)
|
||||
//// )
|
||||
// public Map<String, Object> analyzeData(Map<String, Object> formattedData, String analysisType) {
|
||||
// String cacheKey = generateCacheKey(formattedData, analysisType);
|
||||
//
|
||||
// try {
|
||||
// // 1. 构建请求体
|
||||
// String requestBody = buildRequestBody(formattedData, analysisType);
|
||||
// RequestBody body = RequestBody.create(requestBody, JSON);
|
||||
//
|
||||
// // 2. 构建请求
|
||||
// Request request = new Request.Builder()
|
||||
// .url(apiEndpoint)
|
||||
// .header("Authorization", "Bearer " + apiKey)
|
||||
// .header("Content-Type", "application/json")
|
||||
// .post(body)
|
||||
// .build();
|
||||
//
|
||||
// // 3. 执行请求并处理响应
|
||||
// try (Response response = okHttpClient.newCall(request).execute()) {
|
||||
// if (!response.isSuccessful()) {
|
||||
// handleErrorResponse(response, cacheKey);
|
||||
// }
|
||||
//
|
||||
// String responseBody = response.body().string();
|
||||
// Map<String, Object> result = parseResponse(responseBody, analysisType);
|
||||
//
|
||||
// // 缓存成功的结果
|
||||
// cacheSuccessfulResult(cacheKey, result);
|
||||
// return result;
|
||||
// }
|
||||
//
|
||||
// } catch (Exception e) {
|
||||
// log.error("调用DeepSeek API失败,尝试使用缓存或降级方案", e);
|
||||
// return getFallbackAnalysis(cacheKey, analysisType);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 为补货建议优化的专用方法
|
||||
// */
|
||||
// public Map<String, Object> generateReplenishmentSuggestions(Map<String, Object> inventoryData) {
|
||||
// try {
|
||||
// String prompt = buildReplenishmentPrompt(inventoryData);
|
||||
//
|
||||
// Map<String, Object> requestBody = Map.of(
|
||||
// "model", model,
|
||||
// "messages", List.of(
|
||||
// Map.of("role", "system", "content",
|
||||
// "你是一个经验丰富的电商库存管理专家,擅长制定补货策略。"),
|
||||
// Map.of("role", "user", "content", prompt)
|
||||
// ),
|
||||
// "temperature", 0.2,
|
||||
// "max_tokens", 1500,
|
||||
// "response_format", Map.of("type", "json_object")
|
||||
// );
|
||||
//
|
||||
// String jsonBody = objectMapper.writeValueAsString(requestBody);
|
||||
//
|
||||
// Request request = new Request.Builder()
|
||||
// .url(apiEndpoint)
|
||||
// .header("Authorization", "Bearer " + apiKey)
|
||||
// .post(RequestBody.create(jsonBody, JSON))
|
||||
// .build();
|
||||
//
|
||||
// // 设置更短的超时时间,因为补货建议需要快速响应
|
||||
// OkHttpClient quickClient = okHttpClient.newBuilder()
|
||||
// .readTimeout(15, TimeUnit.SECONDS)
|
||||
// .build();
|
||||
//
|
||||
// try (Response response = quickClient.newCall(request).execute()) {
|
||||
// if (response.isSuccessful()) {
|
||||
// String responseBody = response.body().string();
|
||||
// return parseReplenishmentResponse(responseBody);
|
||||
// } else {
|
||||
// // 如果API失败,使用本地算法生成补货建议
|
||||
// return generateLocalReplenishmentSuggestions(inventoryData);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// } catch (Exception e) {
|
||||
// log.warn("AI补货建议失败,使用本地算法", e);
|
||||
// return generateLocalReplenishmentSuggestions(inventoryData);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 本地补货算法 - 服务降级方案
|
||||
// */
|
||||
// private Map<String, Object> generateLocalReplenishmentSuggestions(Map<String, Object> inventoryData) {
|
||||
// List<Map<String, Object>> products = (List<Map<String, Object>>) inventoryData.get("data");
|
||||
// List<Map<String, Object>> suggestions = new ArrayList<>();
|
||||
// int totalQuantity = 0;
|
||||
// double estimatedCost = 0.0;
|
||||
//
|
||||
// for (Map<String, Object> product : products) {
|
||||
// String status = (String) product.get("inventoryStatus");
|
||||
//
|
||||
// // 只处理需要补货的产品
|
||||
// if ("HEALTHY".equals(status) || "OVERSTOCK".equals(status)) {
|
||||
// continue;
|
||||
// }
|
||||
//
|
||||
// int currentStock = (int) product.getOrDefault("currentStock", 0);
|
||||
// int safetyStock = (int) product.getOrDefault("safetyStock", 100);
|
||||
// double avgDailySales = ((Integer) product.getOrDefault("avgDailySales", 10)).doubleValue();
|
||||
// double coverDays = (double) product.getOrDefault("coverDays", 0.0);
|
||||
//
|
||||
// Map<String, Object> suggestion = new HashMap<>();
|
||||
// suggestion.put("product_id", product.get("productId"));
|
||||
// suggestion.put("product_name", product.get("productName"));
|
||||
//
|
||||
// // 本地补货逻辑
|
||||
// int suggestedQty;
|
||||
// String urgency;
|
||||
//
|
||||
// if (currentStock <= 0) {
|
||||
// suggestedQty = (int) (avgDailySales * 30);
|
||||
// urgency = "紧急";
|
||||
// } else if (coverDays < 3) {
|
||||
// suggestedQty = (int) (avgDailySales * 15 - currentStock);
|
||||
// urgency = "高";
|
||||
// } else if (currentStock < safetyStock) {
|
||||
// suggestedQty = safetyStock * 2 - currentStock;
|
||||
// urgency = "中";
|
||||
// } else {
|
||||
// suggestedQty = (int) (avgDailySales * 7);
|
||||
// urgency = "低";
|
||||
// }
|
||||
//
|
||||
// suggestedQty = Math.max(suggestedQty, 10);
|
||||
// totalQuantity += suggestedQty;
|
||||
//
|
||||
// suggestion.put("suggested_quantity", suggestedQty);
|
||||
// suggestion.put("urgency", urgency);
|
||||
// suggestion.put("reason", "本地算法计算");
|
||||
// suggestion.put("expected_cover_days", suggestedQty / Math.max(avgDailySales, 1));
|
||||
//
|
||||
// suggestions.add(suggestion);
|
||||
// }
|
||||
//
|
||||
// return Map.of(
|
||||
// "success", true,
|
||||
// "source", "local_algorithm",
|
||||
// "replenishment_list", suggestions,
|
||||
// "total_replenishment_quantity", totalQuantity,
|
||||
// "analysis_summary", "基于本地规则生成的补货建议",
|
||||
// "recommendations", List.of(
|
||||
// "建议优先处理标记为'紧急'的商品",
|
||||
// "此为本地降级方案,AI分析恢复后将提供更精确建议"
|
||||
// )
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 自定义重试拦截器
|
||||
// */
|
||||
// private static class RetryInterceptor implements Interceptor {
|
||||
// private final int maxRetries;
|
||||
//
|
||||
// public RetryInterceptor(int maxRetries) {
|
||||
// this.maxRetries = maxRetries;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public Response intercept(Chain chain) throws IOException {
|
||||
// Request request = chain.request();
|
||||
// Response response = null;
|
||||
// IOException exception = null;
|
||||
//
|
||||
// // 重试逻辑
|
||||
// for (int retryCount = 0; retryCount <= maxRetries; retryCount++) {
|
||||
// try {
|
||||
// response = chain.proceed(request);
|
||||
//
|
||||
// // 只有服务器错误(5xx)或特定客户端错误才重试
|
||||
// if (response.isSuccessful() ||
|
||||
// (response.code() != 503 && response.code() != 429 && response.code() != 408)) {
|
||||
// return response;
|
||||
// }
|
||||
//
|
||||
// log.warn("API请求失败,状态码: {}, 重试: {}/{}",
|
||||
// response.code(), retryCount, maxRetries);
|
||||
//
|
||||
// // 关闭响应体
|
||||
// response.close();
|
||||
//
|
||||
// } catch (IOException e) {
|
||||
// exception = e;
|
||||
// log.warn("网络异常,重试: {}/{}", retryCount, maxRetries, e);
|
||||
// }
|
||||
//
|
||||
// // 如果不是最后一次重试,等待一段时间
|
||||
// if (retryCount < maxRetries) {
|
||||
// try {
|
||||
// // 指数退避:1s, 2s, 4s...
|
||||
// long waitTime = (long) Math.pow(2, retryCount) * 1000;
|
||||
// Thread.sleep(waitTime);
|
||||
// } catch (InterruptedException e) {
|
||||
// Thread.currentThread().interrupt();
|
||||
// throw new IOException("重试被中断", e);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if (exception != null) {
|
||||
// throw exception;
|
||||
// }
|
||||
//
|
||||
// return response;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 日志拦截器
|
||||
// */
|
||||
// private static class LoggingInterceptor implements Interceptor {
|
||||
// @Override
|
||||
// public Response intercept(Chain chain) throws IOException {
|
||||
// Request request = chain.request();
|
||||
// long startTime = System.currentTimeMillis();
|
||||
//
|
||||
// log.debug("发送请求: {} {}", request.method(), request.url());
|
||||
//
|
||||
// Response response;
|
||||
// try {
|
||||
// response = chain.proceed(request);
|
||||
// } catch (IOException e) {
|
||||
// long duration = System.currentTimeMillis() - startTime;
|
||||
// log.error("请求失败: {} {} - 耗时: {}ms",
|
||||
// request.method(), request.url(), duration, e);
|
||||
// throw e;
|
||||
// }
|
||||
//
|
||||
// long duration = System.currentTimeMillis() - startTime;
|
||||
// log.info("收到响应: {} {} - 状态: {} - 耗时: {}ms",
|
||||
// request.method(), request.url(), response.code(), duration);
|
||||
//
|
||||
// return response;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 错误处理
|
||||
// */
|
||||
// private void handleErrorResponse(Response response, String cacheKey) throws IOException {
|
||||
// int code = response.code();
|
||||
// String errorBody = response.body() != null ? response.body().string() : "无错误详情";
|
||||
//
|
||||
// log.error("DeepSeek API错误响应: 状态码={}, 错误信息={}", code, errorBody);
|
||||
//
|
||||
// // 根据错误类型处理
|
||||
// if (code == 401) {
|
||||
// throw new RuntimeException("API密钥无效或已过期");
|
||||
// } else if (code == 429) {
|
||||
// throw new RuntimeException("请求频率超限,请稍后重试");
|
||||
// } else if (code == 503) {
|
||||
// // 服务不可用,尝试使用缓存
|
||||
// if (cachedAnalysis.containsKey(cacheKey)) {
|
||||
// log.info("服务不可用,使用缓存结果");
|
||||
// throw new ServiceUnavailableException("服务不可用,已返回缓存结果");
|
||||
// }
|
||||
// throw new RuntimeException("DeepSeek服务暂时不可用,请稍后重试");
|
||||
// } else {
|
||||
// throw new RuntimeException(String.format("API请求失败: %d - %s", code, errorBody));
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 服务降级:获取缓存或基础分析
|
||||
// */
|
||||
// private Map<String, Object> getFallbackAnalysis(String cacheKey, String analysisType) {
|
||||
// // 1. 首先尝试缓存
|
||||
// if (cachedAnalysis.containsKey(cacheKey)) {
|
||||
// log.info("使用缓存的AI分析结果");
|
||||
// Map<String, Object> cached = (Map<String, Object>) cachedAnalysis.get(cacheKey);
|
||||
// cached.put("source", "cached");
|
||||
// return cached;
|
||||
// }
|
||||
//
|
||||
// // 2. 返回基础分析模板
|
||||
// log.info("返回基础分析模板");
|
||||
// return Map.of(
|
||||
// "success", false,
|
||||
// "source", "fallback_template",
|
||||
// "message", "AI分析服务暂时不可用",
|
||||
// "basic_analysis", Map.of(
|
||||
// "suggestion", "建议检查库存水平,重点关注缺货商品",
|
||||
// "generated_at", new Date()
|
||||
// ),
|
||||
// "recommendations", List.of(
|
||||
// "1. 优先处理库存为0的商品",
|
||||
// "2. 检查日销量高但库存低的商品",
|
||||
// "3. AI服务恢复后重新获取详细分析"
|
||||
// )
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// // 其他辅助方法(buildRequestBody, parseResponse等)保持原有逻辑
|
||||
// // ...
|
||||
//}
|
||||
//
|
||||
//// 自定义异常类
|
||||
//class ServiceUnavailableException extends RuntimeException {
|
||||
// public ServiceUnavailableException(String message) {
|
||||
// super(message);
|
||||
// }
|
||||
//}
|
||||
|
|
@ -0,0 +1,273 @@
|
|||
package cn.qihangerp.erp.serviceImpl;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import okhttp3.*;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class InventorySalesAnalyzer {
|
||||
|
||||
// 配置你的 DeepSeek API 信息
|
||||
private static final String API_KEY = "sk-e1f3aecc45e44eca9451d5a659a4bc91";
|
||||
private static final String API_URL = "https://api.deepseek.com/v1/chat/completions";
|
||||
|
||||
private static final MediaType JSON = MediaType.get("application/json; charset=utf-8");
|
||||
private static final OkHttpClient client = new OkHttpClient.Builder()
|
||||
.connectTimeout(30, java.util.concurrent.TimeUnit.SECONDS)
|
||||
.readTimeout(60, java.util.concurrent.TimeUnit.SECONDS)
|
||||
.build();
|
||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
// 你的数据
|
||||
private static final String INVENTORY_JSON = "[{\"id\":1,\"goods_title\":\"雷士照明led吸顶灯灯芯替换圆形灯板节能灯芯冷光高显护眼健康\",\"sku_name\":\"白光12W\",\"stock_num\":12},{\"id\":2,\"goods_title\":\"雷士照明led吸顶灯灯芯替换圆形灯板节能灯芯冷光高显护眼健康\",\"sku_name\":\"白光18W\",\"stock_num\":12},{\"id\":3,\"goods_title\":\"雷士照明led吸顶灯灯芯替换圆形灯板节能灯芯冷光高显护眼健康\",\"sku_name\":\"白光24W\",\"stock_num\":12},{\"id\":4,\"goods_title\":\"雷士照明led吸顶灯灯芯替换圆形灯板节能灯芯冷光高显护眼健康\",\"sku_name\":\"双色36W\",\"stock_num\":12}]";
|
||||
|
||||
private static final String SALES_JSON = "[{\"order_num\":\"1\",\"sku_id\":1,\"count\":1,\"item_amount\":29.32,\"order_time\":\"2025-05-24 23:19:51\"},{\"order_num\":\"1\",\"sku_id\":3,\"count\":1,\"item_amount\":29.32,\"order_time\":\"2025-05-24 23:19:51\"},{\"order_num\":\"1\",\"sku_id\":1,\"count\":1,\"item_amount\":29.32,\"order_time\":\"2025-05-24 23:19:51\"},{\"order_num\":\"1\",\"sku_id\":2,\"count\":1,\"item_amount\":29.32,\"order_time\":\"2025-05-24 23:19:51\"},{\"order_num\":\"1\",\"sku_id\":4,\"count\":1,\"item_amount\":29.32,\"order_time\":\"2025-05-24 23:19:51\"},{\"order_num\":\"1\",\"sku_id\":1,\"count\":1,\"item_amount\":29.32,\"order_time\":\"2025-05-24 23:19:51\"},{\"order_num\":\"1\",\"sku_id\":1,\"count\":1,\"item_amount\":29.32,\"order_time\":\"2025-05-24 23:19:51\"},{\"order_num\":\"1\",\"sku_id\":3,\"count\":1,\"item_amount\":29.32,\"order_time\":\"2025-05-24 23:19:51\"},{\"order_num\":\"1\",\"sku_id\":1,\"count\":1,\"item_amount\":29.32,\"order_time\":\"2025-05-24 23:19:51\"},{\"order_num\":\"1\",\"sku_id\":1,\"count\":1,\"item_amount\":29.32,\"order_time\":\"2025-05-24 23:19:51\"},{\"order_num\":\"1\",\"sku_id\":2,\"count\":1,\"item_amount\":29.32,\"order_time\":\"2025-05-24 23:19:51\"}]";
|
||||
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
System.out.println("开始分析库存与销售数据...\n");
|
||||
|
||||
// 1. 解析数据
|
||||
List<InventoryItem> inventoryList = parseInventoryData();
|
||||
List<SalesOrder> salesList = parseSalesData();
|
||||
|
||||
// 2. 分析数据并生成报告
|
||||
String analysisResult = analyzeInventoryAndSales(inventoryList, salesList);
|
||||
|
||||
System.out.println("=== AI 分析报告 ===\n");
|
||||
System.out.println(analysisResult);
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 核心分析方法
|
||||
*/
|
||||
public static String analyzeInventoryAndSales(List<InventoryItem> inventory,
|
||||
List<SalesOrder> sales) throws IOException {
|
||||
|
||||
// 1. 数据预处理:按 SKU ID 关联库存和销售数据
|
||||
Map<Integer, SkuAnalysis> analysisMap = new HashMap<>();
|
||||
|
||||
// 初始化库存数据
|
||||
for (InventoryItem item : inventory) {
|
||||
SkuAnalysis analysis = new SkuAnalysis();
|
||||
analysis.id = item.id;
|
||||
analysis.goodsTitle = item.goodsTitle;
|
||||
analysis.skuName = item.skuName;
|
||||
analysis.stockNum = item.stockNum;
|
||||
analysisMap.put(item.id, analysis);
|
||||
}
|
||||
|
||||
// 统计销售数据
|
||||
for (SalesOrder order : sales) {
|
||||
if (analysisMap.containsKey(order.skuId)) {
|
||||
SkuAnalysis analysis = analysisMap.get(order.skuId);
|
||||
analysis.totalSales += order.count;
|
||||
analysis.totalRevenue += order.itemAmount;
|
||||
analysis.orderCount++;
|
||||
|
||||
// 记录销售时间(用于趋势分析)
|
||||
analysis.salesTimes.add(order.orderTime);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 计算关键指标
|
||||
for (SkuAnalysis analysis : analysisMap.values()) {
|
||||
// 计算日均销量(假设数据是最近30天的)
|
||||
analysis.dailyAvgSales = analysis.totalSales / 30.0;
|
||||
|
||||
// 计算可售天数
|
||||
if (analysis.dailyAvgSales > 0) {
|
||||
analysis.daysOfSupply = analysis.stockNum / analysis.dailyAvgSales;
|
||||
} else {
|
||||
analysis.daysOfSupply = 999; // 无销售
|
||||
}
|
||||
|
||||
// 判断库存状态
|
||||
analysis.stockStatus = determineStockStatus(analysis.stockNum, analysis.dailyAvgSales);
|
||||
}
|
||||
|
||||
// 3. 构建 AI 分析提示词
|
||||
String prompt = buildAnalysisPrompt(analysisMap);
|
||||
|
||||
// 4. 调用 DeepSeek API
|
||||
return callDeepSeekAPI(prompt);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建 AI 分析提示词
|
||||
*/
|
||||
private static String buildAnalysisPrompt(Map<Integer, SkuAnalysis> analysisMap) {
|
||||
StringBuilder prompt = new StringBuilder();
|
||||
|
||||
prompt.append("你是一名专业的电商库存管理专家。请分析以下 LED 灯具产品的库存与销售数据,并提供专业的分析报告和建议:\n\n");
|
||||
|
||||
prompt.append("=== 数据概览 ===\n");
|
||||
prompt.append("产品名称:雷士照明 LED 吸顶灯灯芯\n");
|
||||
prompt.append("分析时间:").append(new Date()).append("\n\n");
|
||||
|
||||
prompt.append("=== 详细数据 ===\n");
|
||||
prompt.append(String.format("%-8s %-12s %-8s %-8s %-12s %-10s %-15s\n",
|
||||
"SKU ID", "规格", "库存量", "总销量", "总销售额", "可售天数", "库存状态"));
|
||||
prompt.append("-".repeat(80)).append("\n");
|
||||
|
||||
for (SkuAnalysis analysis : analysisMap.values()) {
|
||||
prompt.append(String.format("%-8d %-12s %-8d %-8d %-12.2f %-10.1f %-15s\n",
|
||||
analysis.id,
|
||||
analysis.skuName,
|
||||
analysis.stockNum,
|
||||
analysis.totalSales,
|
||||
analysis.totalRevenue,
|
||||
analysis.daysOfSupply,
|
||||
analysis.stockStatus
|
||||
));
|
||||
}
|
||||
|
||||
prompt.append("\n=== 分析要求 ===\n");
|
||||
prompt.append("请基于以上数据,提供以下分析:\n");
|
||||
prompt.append("1. **库存健康度分析**:评估每个SKU的库存状况,识别缺货风险\n");
|
||||
prompt.append("2. **销售表现分析**:分析各规格产品的销售情况,找出畅销款和滞销款\n");
|
||||
prompt.append("3. **补货建议**:\n");
|
||||
prompt.append(" - 哪些SKU需要立即补货?建议补货数量?\n");
|
||||
prompt.append(" - 哪些SKU库存过多?建议如何清理?\n");
|
||||
prompt.append(" - 建议的安全库存水平\n");
|
||||
prompt.append("4. **运营建议**:基于销售模式,给出采购、促销或产品组合建议\n\n");
|
||||
|
||||
prompt.append("请以专业报告格式回复,包含具体数据和理由。");
|
||||
|
||||
return prompt.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用 DeepSeek API
|
||||
*/
|
||||
private static String callDeepSeekAPI(String prompt) throws IOException {
|
||||
// 构建请求体
|
||||
Map<String, Object> requestBody = new HashMap<>();
|
||||
requestBody.put("model", "deepseek-chat");
|
||||
requestBody.put("messages", Arrays.asList(
|
||||
Map.of("role", "user", "content", prompt)
|
||||
));
|
||||
requestBody.put("temperature", 0.3); // 降低随机性,使分析更稳定
|
||||
requestBody.put("max_tokens", 2000);
|
||||
|
||||
String jsonBody = objectMapper.writeValueAsString(requestBody);
|
||||
|
||||
// 创建请求
|
||||
Request request = new Request.Builder()
|
||||
.url(API_URL)
|
||||
.header("Authorization", "Bearer " + API_KEY)
|
||||
.header("Content-Type", "application/json")
|
||||
.post(RequestBody.create(jsonBody, JSON))
|
||||
.build();
|
||||
|
||||
// 发送请求(带重试机制)
|
||||
for (int attempt = 0; attempt < 3; attempt++) {
|
||||
try (Response response = client.newCall(request).execute()) {
|
||||
if (response.isSuccessful()) {
|
||||
String responseBody = response.body().string();
|
||||
return extractContentFromResponse(responseBody);
|
||||
} else if (response.code() == 429 || response.code() >= 500) {
|
||||
// 频率限制或服务器错误,等待后重试
|
||||
System.out.println("请求失败,状态码: " + response.code() + ",等待重试...");
|
||||
Thread.sleep(2000 * (attempt + 1));
|
||||
continue;
|
||||
} else {
|
||||
throw new IOException("API请求失败: " + response.code() + " - " + response.message());
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new IOException("请求被中断", e);
|
||||
}
|
||||
}
|
||||
|
||||
throw new IOException("API请求失败,已重试3次");
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 API 响应中提取内容
|
||||
*/
|
||||
private static String extractContentFromResponse(String responseBody) throws IOException {
|
||||
Map<String, Object> responseMap = objectMapper.readValue(responseBody,
|
||||
new TypeReference<Map<String, Object>>() {});
|
||||
|
||||
List<Map<String, Object>> choices = (List<Map<String, Object>>) responseMap.get("choices");
|
||||
if (choices != null && !choices.isEmpty()) {
|
||||
Map<String, Object> choice = choices.get(0);
|
||||
Map<String, Object> message = (Map<String, Object>) choice.get("message");
|
||||
return (String) message.get("content");
|
||||
}
|
||||
|
||||
return "未获取到有效回复";
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断库存状态
|
||||
*/
|
||||
private static String determineStockStatus(int stock, double dailySales) {
|
||||
if (stock == 0) return "缺货";
|
||||
if (dailySales == 0) return "滞销";
|
||||
|
||||
double daysOfSupply = stock / dailySales;
|
||||
|
||||
if (daysOfSupply < 7) return "急需补货";
|
||||
if (daysOfSupply < 14) return "需要补货";
|
||||
if (daysOfSupply < 30) return "库存正常";
|
||||
if (daysOfSupply < 60) return "库存偏高";
|
||||
return "库存积压";
|
||||
}
|
||||
|
||||
// 数据解析方法
|
||||
private static List<InventoryItem> parseInventoryData() throws IOException {
|
||||
return objectMapper.readValue(INVENTORY_JSON,
|
||||
new TypeReference<List<InventoryItem>>() {});
|
||||
}
|
||||
|
||||
private static List<SalesOrder> parseSalesData() throws IOException {
|
||||
return objectMapper.readValue(SALES_JSON,
|
||||
new TypeReference<List<SalesOrder>>() {});
|
||||
}
|
||||
|
||||
// 数据类定义
|
||||
static class InventoryItem {
|
||||
public int id;
|
||||
@JsonProperty("goods_title")
|
||||
public String goodsTitle;
|
||||
@JsonProperty("sku_name")
|
||||
public String skuName;
|
||||
@JsonProperty("stock_num")
|
||||
public int stockNum;
|
||||
}
|
||||
|
||||
static class SalesOrder {
|
||||
@JsonProperty("order_num")
|
||||
public String orderNum;
|
||||
@JsonProperty("sku_id")
|
||||
public int skuId;
|
||||
public int count;
|
||||
@JsonProperty("item_amount")
|
||||
public double itemAmount;
|
||||
@JsonProperty("order_time")
|
||||
public String orderTime;
|
||||
}
|
||||
|
||||
static class SkuAnalysis {
|
||||
public int id;
|
||||
public String goodsTitle;
|
||||
public String skuName;
|
||||
public int stockNum;
|
||||
public int totalSales = 0;
|
||||
public double totalRevenue = 0.0;
|
||||
public int orderCount = 0;
|
||||
public double dailyAvgSales = 0.0;
|
||||
public double daysOfSupply = 0.0;
|
||||
public String stockStatus = "未知";
|
||||
public List<String> salesTimes = new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
server:
|
||||
port: 8084
|
||||
spring:
|
||||
application:
|
||||
name: ai-agent
|
||||
cloud:
|
||||
loadbalancer:
|
||||
nacos:
|
||||
enabled: true
|
||||
nacos:
|
||||
# serverAddr: 127.0.0.1:8848
|
||||
discovery:
|
||||
server-addr: 127.0.0.1:8848
|
||||
# username: nacos
|
||||
# password: nacos
|
||||
|
||||
data:
|
||||
# redis 配置
|
||||
redis:
|
||||
# 地址
|
||||
# host: 8.130.98.215
|
||||
host: 127.0.0.1
|
||||
# 端口,默认为6379
|
||||
port: 6379
|
||||
# 数据库索引
|
||||
database: 0
|
||||
# 密码
|
||||
# password: 123321
|
||||
# 连接超时时间
|
||||
timeout: 10s
|
||||
lettuce:
|
||||
pool:
|
||||
# 连接池中的最小空闲连接
|
||||
min-idle: 0
|
||||
# 连接池中的最大空闲连接
|
||||
max-idle: 8
|
||||
# 连接池的最大数据库连接数
|
||||
max-active: 8
|
||||
# #连接池最大阻塞等待时间(使用负值表示没有限制)
|
||||
max-wait: -1ms
|
||||
datasource:
|
||||
driverClassName: com.mysql.cj.jdbc.Driver
|
||||
url: jdbc:mysql://127.0.0.1:3306/qihang-erp?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
|
||||
username: root
|
||||
password: Andy_123
|
||||
hikari:
|
||||
maximum-pool-size: 10
|
||||
min-idle: 5
|
||||
connection-timeout: 30000
|
||||
idle-timeout: 600000
|
||||
max-lifetime: 1800000
|
||||
# kafka:
|
||||
# bootstrap-servers: localhost:9092
|
||||
# producer:
|
||||
# batch-size: 16384 #批量大小
|
||||
# acks: -1 #应答级别:多少个分区副本备份完成时向生产者发送ack确认(可选0、1、all/-1)
|
||||
# retries: 10 # 消息发送重试次数
|
||||
# # transaction-id-prefix: tx_1 #事务id前缀
|
||||
# buffer-memory: 33554432
|
||||
# key-serializer: org.apache.kafka.common.serialization.StringSerializer
|
||||
# value-serializer: org.apache.kafka.common.serialization.StringSerializer
|
||||
# properties:
|
||||
# linger:
|
||||
# ms: 2000 #提交延迟
|
||||
# # partitioner: #指定分区器
|
||||
# # class: com.example.kafkademo.config.CustomizePartitioner
|
||||
# consumer:
|
||||
# group-id: testGroup #默认的消费组ID
|
||||
# enable-auto-commit: true #是否自动提交offset
|
||||
# auto-commit-interval: 2000 #提交offset延时
|
||||
# # 当kafka中没有初始offset或offset超出范围时将自动重置offset
|
||||
# # earliest:重置为分区中最小的offset;
|
||||
# # latest:重置为分区中最新的offset(消费分区中新产生的数据);
|
||||
# # none:只要有一个分区不存在已提交的offset,就抛出异常;
|
||||
# auto-offset-reset: latest
|
||||
# max-poll-records: 500 #单次拉取消息的最大条数
|
||||
# key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
|
||||
# value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
|
||||
# properties:
|
||||
# session:
|
||||
# timeout:
|
||||
# ms: 120000 # 消费会话超时时间(超过这个时间 consumer 没有发送心跳,就会触发 rebalance 操作)
|
||||
# request:
|
||||
# timeout:
|
||||
# ms: 18000 # 消费请求的超时时间
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
mybatis-plus:
|
||||
mapper-locations: classpath*:mapper/**/*Mapper.xml
|
||||
type-aliases-package: cn.qihangerp.oms.domain;cn.qihangerp.module.domain;cn.qihangerp.security.entity;
|
||||
# configuration:
|
||||
# log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 开启sql日志
|
||||
|
||||
deepseek:
|
||||
api:
|
||||
key:
|
||||
endpoint: https://api.deepseek.com/chat/completions
|
||||
model: deepseek-chat
|
||||
|
|
@ -37,10 +37,10 @@ spring:
|
|||
filters:
|
||||
- StripPrefix=2
|
||||
|
||||
- id: open_api_route
|
||||
uri: lb://open-api
|
||||
- id: ai_agent_route
|
||||
uri: lb://ai-agent
|
||||
predicates:
|
||||
- Path=/api/open-api/**
|
||||
- Path=/api/ai-agent/**
|
||||
filters:
|
||||
- StripPrefix=2
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
<url>http://maven.apache.org</url>
|
||||
<modules>
|
||||
<module>gateway</module>
|
||||
<module>ai-agent</module>
|
||||
<module>erp-api</module>
|
||||
<module>sys-api</module>
|
||||
<module>oms-api</module>
|
||||
|
|
|
|||
|
|
@ -1,164 +1,616 @@
|
|||
<template>
|
||||
<div class="dashboard-editor-container">
|
||||
|
||||
<panel-group :chart-data="report" @handleSetLineChartData="handleSetLineChartData" />
|
||||
|
||||
<el-row style="background:#fff;padding:16px 16px 0;margin-bottom:32px;">
|
||||
<line-chart :chart-data="lineChartData" />
|
||||
</el-row>
|
||||
<!-- <el-row>-->
|
||||
<!-- <el-table-->
|
||||
<!-- :data="tableData"-->
|
||||
<!-- style="width: 100%">-->
|
||||
<!-- <el-table-column-->
|
||||
<!-- prop="date"-->
|
||||
<!-- label="日期"-->
|
||||
<!-- width="180">-->
|
||||
<!-- </el-table-column>-->
|
||||
<!-- <el-table-column-->
|
||||
<!-- prop="name"-->
|
||||
<!-- label="姓名"-->
|
||||
<!-- width="180">-->
|
||||
<!-- </el-table-column>-->
|
||||
<!-- <el-table-column-->
|
||||
<!-- prop="address"-->
|
||||
<!-- label="地址">-->
|
||||
<!-- </el-table-column>-->
|
||||
<!-- </el-table>-->
|
||||
<!-- </el-row>-->
|
||||
<el-row :gutter="32">
|
||||
<!-- <el-col :xs="24" :sm="24" :lg="8">-->
|
||||
<!-- <div class="chart-wrapper">-->
|
||||
<!-- <raddar-chart />-->
|
||||
<!-- </div>-->
|
||||
<!-- </el-col>-->
|
||||
<el-col :xs="24" :sm="24" :lg="24">
|
||||
<div class="chart-wrapper">
|
||||
<pie-chart :chart-data="skuTopData" />
|
||||
<div class="chat-home-container">
|
||||
<!-- 左侧聊天区域 -->
|
||||
<div class="chat-container">
|
||||
<div class="chat-header">
|
||||
<div class="header-left">
|
||||
<i class="el-icon-chat-dot-round"></i>
|
||||
<span class="title">工作助手</span>
|
||||
</div>
|
||||
</el-col>
|
||||
<!-- <el-col :xs="24" :sm="24" :lg="12">-->
|
||||
<!-- <div class="chart-wrapper">-->
|
||||
<!-- <bar-chart />-->
|
||||
<!-- </div>-->
|
||||
<!-- </el-col>-->
|
||||
</el-row>
|
||||
<div class="header-right">
|
||||
<el-tag type="success" size="mini">在线</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="chat-messages" ref="messageContainer">
|
||||
<div
|
||||
v-for="(message, index) in messages"
|
||||
:key="index"
|
||||
:class="['message-item', message.isMe ? 'message-right' : 'message-left']"
|
||||
>
|
||||
<div class="message-avatar">
|
||||
<el-avatar :size="32" :src="message.avatar">
|
||||
{{ message.isMe ? '我' : '助' }}
|
||||
</el-avatar>
|
||||
</div>
|
||||
<div class="message-content">
|
||||
<div class="message-text">{{ message.content }}</div>
|
||||
<div class="message-time">{{ message.time }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="chat-input">
|
||||
<el-input
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入消息..."
|
||||
v-model="inputMessage"
|
||||
@keyup.enter.native="sendMessage"
|
||||
></el-input>
|
||||
<div class="input-actions">
|
||||
<el-button type="primary" size="small" @click="sendMessage" :disabled="!inputMessage.trim()">
|
||||
发送
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧导航面板 -->
|
||||
<div class="nav-panel">
|
||||
<div class="nav-header">
|
||||
<i class="el-icon-menu"></i>
|
||||
<span class="title">快捷导航</span>
|
||||
</div>
|
||||
|
||||
<!-- 快捷功能区 -->
|
||||
<div class="quick-actions">
|
||||
<div class="section-title">
|
||||
<i class="el-icon-lightning"></i>
|
||||
快捷操作
|
||||
</div>
|
||||
<div class="action-grid">
|
||||
<div
|
||||
v-for="action in quickActions"
|
||||
:key="action.id"
|
||||
class="action-item"
|
||||
@click="sendQuickMessage(action)"
|
||||
>
|
||||
<div class="action-icon">
|
||||
<i :class="action.icon"></i>
|
||||
</div>
|
||||
<div class="action-text">{{ action.title }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 常用功能区 -->
|
||||
<div class="common-functions">
|
||||
<div class="section-title">
|
||||
<i class="el-icon-star-on"></i>
|
||||
常用功能
|
||||
</div>
|
||||
<div class="function-list">
|
||||
<div
|
||||
v-for="func in commonFunctions"
|
||||
:key="func.id"
|
||||
class="function-item"
|
||||
@click="navigateTo(func.path)"
|
||||
>
|
||||
<i :class="func.icon"></i>
|
||||
<span>{{ func.title }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 系统统计 -->
|
||||
<div class="system-stats">
|
||||
<div class="section-title">
|
||||
<i class="el-icon-data-analysis"></i>
|
||||
系统概览
|
||||
</div>
|
||||
<div class="stats-grid">
|
||||
<div class="stat-item">
|
||||
<div class="stat-value">{{ stats.waitShip }}</div>
|
||||
<div class="stat-label">待发货</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-value">{{ stats.orderCount }}</div>
|
||||
<div class="stat-label">今日订单</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-value">¥{{ stats.salesVolume }}</div>
|
||||
<div class="stat-label">今日销售额</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-value">{{ stats.shopCount }}</div>
|
||||
<div class="stat-label">店铺数</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PanelGroup from './dashboard/PanelGroup'
|
||||
import LineChart from './dashboard/LineChart'
|
||||
import RaddarChart from './dashboard/RaddarChart'
|
||||
import PieChart from './dashboard/PieChart'
|
||||
import BarChart from './dashboard/BarChart'
|
||||
import { todayDaily,salesDaily,salesTopSku } from "@/api/report/report";
|
||||
const lineChartData = {
|
||||
// newVisitis: {
|
||||
// expectedData: [100, 120, 161, 134, 105, 160, 165],
|
||||
// actualData: [120, 82, 91, 154, 162, 140, 145]
|
||||
// },
|
||||
// messages: {
|
||||
// expectedData: [200, 192, 120, 144, 160, 130, 140],
|
||||
// actualData: [180, 160, 151, 106, 145, 150, 130]
|
||||
// },
|
||||
// purchases: {
|
||||
// expectedData: [80, 100, 121, 104, 105, 90, 100],
|
||||
// actualData: [120, 90, 100, 138, 142, 130, 130]
|
||||
// },
|
||||
// shoppings: {
|
||||
// expectedData: [130, 140, 141, 142, 145, 150, 160],
|
||||
// actualData: [120, 82, 91, 154, 162, 140, 130]
|
||||
// }
|
||||
salesDaily: {
|
||||
date:['09-15','09-16','09-17','09-18','09-19','09-20','09-21'],
|
||||
salesVolume: [130, 140, 141, 142, 145, 150, 160],
|
||||
salesOrder: [120, 82, 91, 154, 162, 140, 130]
|
||||
}
|
||||
}
|
||||
import { todayDaily } from "@/api/report/report";
|
||||
import { getToken } from "@/utils/auth";
|
||||
|
||||
export default {
|
||||
name: 'Index',
|
||||
components: {
|
||||
PanelGroup,
|
||||
LineChart,
|
||||
RaddarChart,
|
||||
PieChart,
|
||||
BarChart
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
lineChartData: {
|
||||
date:[],
|
||||
salesVolume:[],
|
||||
salesOrder:[]
|
||||
inputMessage: '',
|
||||
messages: [
|
||||
{
|
||||
content: '您好!我是您的工作助手,有什么可以帮助您的吗?',
|
||||
time: this.formatTime(new Date()),
|
||||
isMe: false,
|
||||
avatar: ''
|
||||
}
|
||||
],
|
||||
quickActions: [
|
||||
{ id: 1, title: '查看待发货', icon: 'el-icon-truck', message: '请帮我查看今天有多少待发货订单' },
|
||||
{ id: 2, title: '同步订单', icon: 'el-icon-refresh', message: '请同步所有店铺的订单数据' },
|
||||
{ id: 3, title: '库存预警', icon: 'el-icon-warning', message: '检查一下哪些商品库存不足' },
|
||||
{ id: 4, title: '今日统计', icon: 'el-icon-data-line', message: '请告诉我今天的销售统计数据' },
|
||||
{ id: 5, title: '商品管理', icon: 'el-icon-goods', message: '我想管理商品信息' },
|
||||
{ id: 6, title: '售后处理', icon: 'el-icon-service', message: '查看需要处理的售后订单' }
|
||||
],
|
||||
commonFunctions: [
|
||||
{ id: 1, title: '订单管理', icon: 'el-icon-s-order', path: '/order' },
|
||||
{ id: 2, title: '商品管理', icon: 'el-icon-goods', path: '/goods' },
|
||||
{ id: 3, title: '库存管理', icon: 'el-icon-box', path: '/stock' },
|
||||
{ id: 4, title: '发货管理', icon: 'el-icon-truck', path: '/shipping' },
|
||||
{ id: 5, title: '售后管理', icon: 'el-icon-service', path: '/refund' },
|
||||
{ id: 6, title: '店铺管理', icon: 'el-icon-shop', path: '/shop' },
|
||||
{ id: 7, title: '采购管理', icon: 'el-icon-shopping-cart-full', path: '/purchase' },
|
||||
{ id: 8, title: '系统设置', icon: 'el-icon-setting', path: '/system' }
|
||||
],
|
||||
stats: {
|
||||
waitShip: 0,
|
||||
salesVolume: 0,
|
||||
orderCount: 0,
|
||||
shopCount: 0
|
||||
},
|
||||
skuTopData:{
|
||||
data:[
|
||||
// { value: 320, name: 'AAA' },
|
||||
// { value: 240, name: 'BBB' },
|
||||
// { value: 149, name: 'CC' },
|
||||
// { value: 100, name: 'DD' },
|
||||
// { value: 59, name: 'DEEE' }
|
||||
]
|
||||
},
|
||||
report:{
|
||||
waitShip:0,
|
||||
salesVolume:5989.98,
|
||||
orderCount:302,
|
||||
shopCount:8
|
||||
}
|
||||
|
||||
sse: null,
|
||||
clientId: '',
|
||||
isSseConnected: false,
|
||||
isLoading: false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 加载统计
|
||||
todayDaily().then(resp=>{
|
||||
|
||||
this.report = resp.data
|
||||
})
|
||||
salesDaily().then(resp=>{
|
||||
|
||||
this.lineChartData.date=[]
|
||||
this.lineChartData.salesVolume =[]
|
||||
this.lineChartData.salesOrder =[]
|
||||
resp.data.forEach(x=>{
|
||||
this.lineChartData.date.push(x.date)
|
||||
this.lineChartData.salesVolume.push(x.amount)
|
||||
this.lineChartData.salesOrder.push(x.count)
|
||||
})
|
||||
})
|
||||
salesTopSku().then(resp=>{
|
||||
console.log("=====",resp.data)
|
||||
this.skuTopData.data= resp.data
|
||||
})
|
||||
this.loadSystemStats();
|
||||
this.initSse();
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.closeSse();
|
||||
},
|
||||
methods: {
|
||||
handleSetLineChartData(type) {
|
||||
// this.lineChartData = lineChartData[type]
|
||||
initSse() {
|
||||
// 生成唯一客户端ID
|
||||
this.clientId = 'client_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
|
||||
// 获取token
|
||||
const token = getToken();
|
||||
|
||||
// 建立SSE连接,携带token
|
||||
this.sse = new EventSource(`${process.env.VUE_APP_BASE_API}/api/ai-agent/sse/connect?clientId=${this.clientId}&token=${token}`);
|
||||
|
||||
// 监听连接成功
|
||||
this.sse.addEventListener('connected', (event) => {
|
||||
console.log('SSE连接成功:', event.data);
|
||||
this.isSseConnected = true;
|
||||
});
|
||||
|
||||
// 监听消息
|
||||
this.sse.addEventListener('message', (event) => {
|
||||
console.log('收到SSE消息:', event.data);
|
||||
// 移除正在思考的消息
|
||||
if (this.isLoading) {
|
||||
this.messages = this.messages.filter(msg => !msg.isLoading);
|
||||
this.isLoading = false;
|
||||
}
|
||||
// 添加实际回复消息
|
||||
this.messages.push({
|
||||
content: event.data,
|
||||
time: this.formatTime(new Date()),
|
||||
isMe: false,
|
||||
avatar: ''
|
||||
});
|
||||
this.scrollToBottom();
|
||||
});
|
||||
|
||||
// 监听心跳
|
||||
this.sse.addEventListener('heartbeat', (event) => {
|
||||
console.log('收到心跳:', event.data);
|
||||
});
|
||||
|
||||
// 监听错误
|
||||
this.sse.onerror = (error) => {
|
||||
console.error('SSE连接错误:', error);
|
||||
this.isSseConnected = false;
|
||||
// 尝试重连
|
||||
setTimeout(() => {
|
||||
this.initSse();
|
||||
}, 5000);
|
||||
};
|
||||
},
|
||||
|
||||
closeSse() {
|
||||
if (this.sse) {
|
||||
this.sse.close();
|
||||
this.sse = null;
|
||||
}
|
||||
},
|
||||
|
||||
sendMessage() {
|
||||
if (!this.inputMessage.trim()) return;
|
||||
|
||||
// 添加用户消息
|
||||
this.messages.push({
|
||||
content: this.inputMessage,
|
||||
time: this.formatTime(new Date()),
|
||||
isMe: true,
|
||||
avatar: ''
|
||||
});
|
||||
|
||||
// 显示正在思考的loading效果
|
||||
this.isLoading = true;
|
||||
this.messages.push({
|
||||
content: '正在思考...',
|
||||
time: this.formatTime(new Date()),
|
||||
isMe: false,
|
||||
avatar: '',
|
||||
isLoading: true
|
||||
});
|
||||
|
||||
// 获取token
|
||||
const token = getToken();
|
||||
|
||||
// 通过SSE发送消息到后端
|
||||
if (this.isSseConnected) {
|
||||
// 使用fetch发送消息
|
||||
fetch(`${process.env.VUE_APP_BASE_API}/api/ai-agent/sse/send?clientId=${this.clientId}&message=${encodeURIComponent(this.inputMessage)}&token=${token}`)
|
||||
.then(response => response.text())
|
||||
.then(data => {
|
||||
console.log('消息发送结果:', data);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('消息发送失败:', error);
|
||||
// 发送失败时使用模拟回复
|
||||
this.generateReply(this.inputMessage);
|
||||
this.isLoading = false;
|
||||
});
|
||||
} else {
|
||||
// SSE未连接时使用模拟回复
|
||||
this.generateReply(this.inputMessage);
|
||||
this.isLoading = false;
|
||||
}
|
||||
|
||||
this.inputMessage = '';
|
||||
this.scrollToBottom();
|
||||
},
|
||||
|
||||
sendQuickMessage(action) {
|
||||
this.inputMessage = action.message;
|
||||
this.sendMessage();
|
||||
},
|
||||
|
||||
generateReply(userMessage) {
|
||||
let reply = '';
|
||||
|
||||
if (userMessage.includes('待发货')) {
|
||||
reply = `您有 ${this.stats.waitShip} 个待发货订单,建议尽快处理。`;
|
||||
} else if (userMessage.includes('订单')) {
|
||||
reply = `今日订单总数:${this.stats.orderCount} 单,销售额:¥${this.stats.salesVolume}`;
|
||||
} else if (userMessage.includes('库存')) {
|
||||
reply = '正在为您检查库存情况,请稍等...';
|
||||
} else if (userMessage.includes('统计')) {
|
||||
reply = `今日数据:订单${this.stats.orderCount}单,销售额¥${this.stats.salesVolume},待发货${this.stats.waitShip}单`;
|
||||
} else {
|
||||
reply = '我理解您的需求,正在为您处理...';
|
||||
}
|
||||
|
||||
this.messages.push({
|
||||
content: reply,
|
||||
time: this.formatTime(new Date()),
|
||||
isMe: false,
|
||||
avatar: ''
|
||||
});
|
||||
|
||||
this.scrollToBottom();
|
||||
},
|
||||
|
||||
navigateTo(path) {
|
||||
this.$router.push(path);
|
||||
},
|
||||
|
||||
loadSystemStats() {
|
||||
todayDaily().then(resp => {
|
||||
this.stats = resp.data;
|
||||
}).catch(error => {
|
||||
console.error('加载统计失败:', error);
|
||||
});
|
||||
},
|
||||
|
||||
formatTime(date) {
|
||||
const hours = date.getHours().toString().padStart(2, '0');
|
||||
const minutes = date.getMinutes().toString().padStart(2, '0');
|
||||
return `${hours}:${minutes}`;
|
||||
},
|
||||
|
||||
scrollToBottom() {
|
||||
this.$nextTick(() => {
|
||||
const container = this.$refs.messageContainer;
|
||||
if (container) {
|
||||
container.scrollTop = container.scrollHeight;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.dashboard-editor-container {
|
||||
padding: 32px;
|
||||
background-color: rgb(240, 242, 245);
|
||||
position: relative;
|
||||
.chat-home-container {
|
||||
display: flex;
|
||||
height: calc(100vh - 84px);
|
||||
background-color: #f5f7fa;
|
||||
padding: 20px;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.chart-wrapper {
|
||||
background: #fff;
|
||||
padding: 16px 16px 0;
|
||||
margin-bottom: 32px;
|
||||
// 左侧聊天区域
|
||||
.chat-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.chat-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid #eee;
|
||||
background: #fafafa;
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
.el-icon-chat-dot-round {
|
||||
font-size: 20px;
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width:1024px) {
|
||||
.chart-wrapper {
|
||||
padding: 8px;
|
||||
.chat-messages {
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
background: #fafafa;
|
||||
|
||||
.message-item {
|
||||
display: flex;
|
||||
margin-bottom: 20px;
|
||||
max-width: 80%;
|
||||
|
||||
&.message-left {
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
&.message-right {
|
||||
align-self: flex-end;
|
||||
flex-direction: row-reverse;
|
||||
|
||||
.message-content {
|
||||
background: #409eff;
|
||||
color: white;
|
||||
.message-time {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.message-avatar {
|
||||
margin: 0 12px;
|
||||
}
|
||||
|
||||
.message-content {
|
||||
background: white;
|
||||
padding: 12px 16px;
|
||||
border-radius: 18px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
|
||||
.message-text {
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.message-time {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chat-input {
|
||||
padding: 20px;
|
||||
border-top: 1px solid #eee;
|
||||
background: white;
|
||||
|
||||
.input-actions {
|
||||
margin-top: 12px;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
// 右侧导航面板
|
||||
.nav-panel {
|
||||
width: 320px;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.nav-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid #eee;
|
||||
background: #fafafa;
|
||||
|
||||
.el-icon-menu {
|
||||
font-size: 18px;
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.section-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 16px 20px 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #666;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
// 快捷操作
|
||||
.quick-actions {
|
||||
.action-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 12px;
|
||||
padding: 0 20px 20px;
|
||||
}
|
||||
|
||||
.action-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 16px;
|
||||
border: 1px solid #eee;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:hover {
|
||||
border-color: #409eff;
|
||||
background: #f0f8ff;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.action-icon {
|
||||
font-size: 24px;
|
||||
color: #409eff;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.action-text {
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 常用功能
|
||||
.common-functions {
|
||||
.function-list {
|
||||
padding: 0 20px 20px;
|
||||
}
|
||||
|
||||
.function-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 16px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
margin-bottom: 8px;
|
||||
|
||||
&:hover {
|
||||
background: #f0f8ff;
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 16px;
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 系统统计
|
||||
.system-stats {
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 16px;
|
||||
padding: 0 20px 20px;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
text-align: center;
|
||||
padding: 16px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
|
||||
.stat-value {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #409eff;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 滚动条样式
|
||||
.chat-messages::-webkit-scrollbar,
|
||||
.nav-panel::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.chat-messages::-webkit-scrollbar-track,
|
||||
.nav-panel::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.chat-messages::-webkit-scrollbar-thumb,
|
||||
.nav-panel::-webkit-scrollbar-thumb {
|
||||
background: #c1c1c1;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.chat-messages::-webkit-scrollbar-thumb:hover,
|
||||
.nav-panel::-webkit-scrollbar-thumb:hover {
|
||||
background: #a8a8a8;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
Loading…
Reference in New Issue