Merge branch 'master' of https://gitee.com/qiliping/qihangerp-cloud
This commit is contained in:
commit
baae830022
|
|
@ -78,7 +78,11 @@
|
|||
<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>-->
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ public class ErpApi {
|
|||
{
|
||||
System.out.println( "Hello erp-api!" );
|
||||
SpringApplication.run(ErpApi.class, args);
|
||||
|
||||
}
|
||||
@Bean
|
||||
@LoadBalanced
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import cn.qihangerp.module.service.OLogisticsCompanyService;
|
|||
import cn.qihangerp.module.service.OShopPlatformService;
|
||||
import cn.qihangerp.module.service.OShopService;
|
||||
import cn.qihangerp.erp.request.ShopBo;
|
||||
import com.alibaba.fastjson2.JSONArray;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import cn.qihangerp.common.AjaxResult;
|
||||
import cn.qihangerp.common.TableDataInfo;
|
||||
|
|
@ -14,7 +15,9 @@ import lombok.AllArgsConstructor;
|
|||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 店铺Controller
|
||||
|
|
@ -30,6 +33,7 @@ public class ShopController extends BaseController {
|
|||
private final OShopService shopService;
|
||||
private final OShopPlatformService platformService;
|
||||
|
||||
|
||||
/**
|
||||
* 查询店铺列表logistics
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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<>();
|
||||
}
|
||||
}
|
||||
|
|
@ -93,3 +93,9 @@ mybatis-plus:
|
|||
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
|
||||
Binary file not shown.
|
|
@ -51,7 +51,7 @@ public class SysLoginController
|
|||
ajax.put(Constants.TOKEN, token);
|
||||
return ajax;
|
||||
}catch (Exception e){
|
||||
return AjaxResult.error(e.getMessage());
|
||||
return AjaxResult.error(500,e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue