Compare commits

...

2 Commits

Author SHA1 Message Date
ljw 572ba946fd Merge branch 'dev' of https://git.njzscloud.com/lzq/njzscloud into dev 2025-11-24 10:41:06 +08:00
ljw 6f30570604 ai勘料 2025-11-24 10:40:58 +08:00
11 changed files with 1061 additions and 1 deletions

View File

@ -125,6 +125,11 @@
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
<dependency>
<groupId>com.volcengine</groupId>
<artifactId>volcengine-java-sdk-ark-runtime</artifactId>
<version>LATEST</version>
</dependency>
</dependencies>

View File

@ -0,0 +1,520 @@
# 豆包垃圾分析接口文档
## 接口概述
本接口基于火山引擎豆包AI模型提供垃圾车辆图片分析功能。通过上传多张垃圾车辆图片AI会自动识别车棚状态、垃圾类型、轻物质和重物质占比等信息。
**接口路径:** `/douBao/analyzeGarbage`
**请求方式:** `POST`
**Content-Type** `application/json`
---
## 请求参数
### 请求体GarbageAnalysisParam
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| imageUrls | List&lt;String&gt; | 是 | 图片URL列表支持多张图片至少需要一张 |
| orderSn | String | 否 | 订单号,用于关联订单信息 |
| remark | String | 否 | 备注信息,可提供额外的分析上下文 |
### 参数说明
- **imageUrls**图片URL列表支持HTTP/HTTPS协议的图片链接。建议使用可公开访问的图片URL。
- **orderSn**:订单号,用于在分析结果中关联订单信息,便于后续查询和追踪。
- **remark**备注信息可以包含订单相关的额外信息如装货地点、卸货地点等有助于AI更准确地进行分析。
---
## 响应结果
### 响应体GarbageAnalysisResult
| 字段名 | 类型 | 说明 |
|--------|------|------|
| isShedOpened | Boolean | 车棚是否完全打开。true表示已打开false表示未打开 |
| details | String | 明细信息车厢内包含的物品列表。格式物品1(数量描述),物品2(数量描述),例如:石块(多),木材(少) |
| lightMaterialPercentage | BigDecimal | 轻物质占比百分比0-100 |
| heavyMaterialPercentage | BigDecimal | 重物质占比百分比0-100 |
| garbageType | String | 垃圾类型,可能的值:拆除垃圾、装修垃圾、抛货 |
| judgmentBasis | String | 判断依据说明AI判断垃圾类型的原因 |
| analysisTime | Long | 分析时间戳(毫秒) |
| rawResponse | String | AI原始响应内容用于调试和问题排查 |
| garbageTypes | List&lt;GarbageTypeInfo&gt; | 垃圾类型分析列表(保留字段,用于兼容) |
| totalWeight | BigDecimal | 总重量(单位:千克)(保留字段,用于兼容) |
### GarbageTypeInfo 对象结构(保留字段)
| 字段名 | 类型 | 说明 |
|--------|------|------|
| typeName | String | 垃圾类型名称 |
| category | String | 具体垃圾种类 |
| judgmentBasis | String | 判断依据 |
| percentage | BigDecimal | 占比百分比0-100 |
| weight | BigDecimal | 估算重量(单位:千克) |
---
## 请求示例
### 场景一最小参数仅图片URL
**适用场景:** 快速测试仅提供必要的图片URL
```json
{
"imageUrls": [
"https://example.com/garbage-truck-1.jpg"
]
}
```
**cURL 命令:**
```bash
curl -X POST "http://localhost:8080/douBao/analyzeGarbage" \
-H "Content-Type: application/json" \
-d '{
"imageUrls": [
"https://example.com/garbage-truck-1.jpg"
]
}'
```
---
### 场景二:单张图片 + 订单号
**适用场景:** 关联订单信息
```json
{
"imageUrls": [
"https://example.com/garbage-truck-1.jpg"
],
"orderSn": "ORD20240101001"
}
```
**cURL 命令:**
```bash
curl -X POST "http://localhost:8080/douBao/analyzeGarbage" \
-H "Content-Type: application/json" \
-d '{
"imageUrls": [
"https://example.com/garbage-truck-1.jpg"
],
"orderSn": "ORD20240101001"
}'
```
---
### 场景三:多张图片(推荐)
**适用场景:** 从不同角度拍摄,提高分析准确性
```json
{
"imageUrls": [
"https://example.com/garbage-truck-front.jpg",
"https://example.com/garbage-truck-side.jpg",
"https://example.com/garbage-truck-back.jpg"
],
"orderSn": "ORD20240101001",
"remark": "装货地点:南京市建邺区,卸货地点:南京市江宁区"
}
```
**cURL 命令:**
```bash
curl -X POST "http://localhost:8080/douBao/analyzeGarbage" \
-H "Content-Type: application/json" \
-d '{
"imageUrls": [
"https://example.com/garbage-truck-front.jpg",
"https://example.com/garbage-truck-side.jpg",
"https://example.com/garbage-truck-back.jpg"
],
"orderSn": "ORD20240101001",
"remark": "装货地点:南京市建邺区,卸货地点:南京市江宁区"
}'
```
---
### 场景四:完整参数示例
**适用场景:** 生产环境,包含所有可选信息
```json
{
"imageUrls": [
"https://cdn.example.com/orders/2024/01/01/truck-front-001.jpg",
"https://cdn.example.com/orders/2024/01/01/truck-side-001.jpg"
],
"orderSn": "ORD20240101001",
"remark": "订单编号ORD20240101001装货地点南京市建邺区奥体中心卸货地点南京市江宁区垃圾处理厂装货时间2024-01-01 10:00:00"
}
```
**cURL 命令:**
```bash
curl -X POST "http://localhost:8080/douBao/analyzeGarbage" \
-H "Content-Type: application/json" \
-d '{
"imageUrls": [
"https://cdn.example.com/orders/2024/01/01/truck-front-001.jpg",
"https://cdn.example.com/orders/2024/01/01/truck-side-001.jpg"
],
"orderSn": "ORD20240101001",
"remark": "订单编号ORD20240101001装货地点南京市建邺区奥体中心卸货地点南京市江宁区垃圾处理厂装货时间2024-01-01 10:00:00"
}'
```
---
### Java 示例
#### 方式一:使用链式调用(推荐)
```java
import com.njzscloud.supervisory.doubao.param.GarbageAnalysisParam;
import com.njzscloud.supervisory.doubao.result.GarbageAnalysisResult;
import com.njzscloud.common.core.utils.R;
import java.util.Arrays;
// 最小参数
GarbageAnalysisParam param1 = new GarbageAnalysisParam()
.setImageUrls(Arrays.asList("https://example.com/garbage-truck-1.jpg"));
// 完整参数
GarbageAnalysisParam param2 = new GarbageAnalysisParam()
.setImageUrls(Arrays.asList(
"https://example.com/garbage-truck-front.jpg",
"https://example.com/garbage-truck-side.jpg"
))
.setOrderSn("ORD20240101001")
.setRemark("装货地点:南京市建邺区,卸货地点:南京市江宁区");
R<GarbageAnalysisResult> result = douBaoController.analyzeGarbage(param2);
if (result.getCode() == 200) {
GarbageAnalysisResult data = result.getData();
System.out.println("车棚状态:" + (data.getIsShedOpened() ? "已打开" : "未打开"));
System.out.println("垃圾类型:" + data.getGarbageType());
System.out.println("轻物质占比:" + data.getLightMaterialPercentage() + "%");
System.out.println("重物质占比:" + data.getHeavyMaterialPercentage() + "%");
}
```
#### 方式二:传统方式
```java
GarbageAnalysisParam param = new GarbageAnalysisParam();
param.setImageUrls(Arrays.asList(
"https://example.com/garbage-truck-1.jpg",
"https://example.com/garbage-truck-2.jpg"
));
param.setOrderSn("ORD20240101001");
param.setRemark("装货地点:南京市建邺区,卸货地点:南京市江宁区");
R<GarbageAnalysisResult> result = douBaoController.analyzeGarbage(param);
```
---
### JavaScript/Axios 示例
```javascript
// 使用 fetch
fetch('http://localhost:8080/douBao/analyzeGarbage', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
imageUrls: [
'https://example.com/garbage-truck-1.jpg',
'https://example.com/garbage-truck-2.jpg'
],
orderSn: 'ORD20240101001',
remark: '装货地点:南京市建邺区,卸货地点:南京市江宁区'
})
})
.then(response => response.json())
.then(data => {
if (data.code === 200) {
console.log('分析成功:', data.data);
console.log('车棚状态:', data.data.isShedOpened ? '已打开' : '未打开');
console.log('垃圾类型:', data.data.garbageType);
} else {
console.error('分析失败:', data.msg);
}
})
.catch(error => console.error('请求错误:', error));
// 使用 axios
import axios from 'axios';
const analyzeGarbage = async () => {
try {
const response = await axios.post('http://localhost:8080/douBao/analyzeGarbage', {
imageUrls: [
'https://example.com/garbage-truck-1.jpg',
'https://example.com/garbage-truck-2.jpg'
],
orderSn: 'ORD20240101001',
remark: '装货地点:南京市建邺区,卸货地点:南京市江宁区'
});
if (response.data.code === 200) {
console.log('分析结果:', response.data.data);
}
} catch (error) {
console.error('请求失败:', error);
}
};
```
---
### Postman/Apifox 测试配置
#### 请求配置
- **请求方式:** `POST`
- **请求URL** `http://localhost:8080/douBao/analyzeGarbage`
- **Headers**
- `Content-Type: application/json`
#### Bodyraw JSON示例
**示例1最小参数**
```json
{
"imageUrls": [
"https://example.com/garbage-truck-1.jpg"
]
}
```
**示例2完整参数**
```json
{
"imageUrls": [
"https://example.com/garbage-truck-front.jpg",
"https://example.com/garbage-truck-side.jpg",
"https://example.com/garbage-truck-back.jpg"
],
"orderSn": "ORD20240101001",
"remark": "装货地点:南京市建邺区奥体中心;卸货地点:南京市江宁区垃圾处理厂"
}
```
---
### Python 示例
```python
import requests
import json
url = "http://localhost:8080/douBao/analyzeGarbage"
# 最小参数
payload1 = {
"imageUrls": [
"https://example.com/garbage-truck-1.jpg"
]
}
# 完整参数
payload2 = {
"imageUrls": [
"https://example.com/garbage-truck-front.jpg",
"https://example.com/garbage-truck-side.jpg"
],
"orderSn": "ORD20240101001",
"remark": "装货地点:南京市建邺区,卸货地点:南京市江宁区"
}
headers = {
"Content-Type": "application/json"
}
response = requests.post(url, headers=headers, data=json.dumps(payload2))
result = response.json()
if result.get("code") == 200:
data = result.get("data")
print(f"车棚状态:{'已打开' if data.get('isShedOpened') else '未打开'}")
print(f"垃圾类型:{data.get('garbageType')}")
print(f"轻物质占比:{data.get('lightMaterialPercentage')}%")
print(f"重物质占比:{data.get('heavyMaterialPercentage')}%")
else:
print(f"分析失败:{result.get('msg')}")
```
---
### 测试图片URL说明
**注意:** 实际测试时请替换为真实可访问的图片URL。图片URL需要满足以下条件
1. **可公开访问**图片URL必须可以通过HTTP/HTTPS协议直接访问
2. **支持格式**:建议使用 JPG、PNG 等常见图片格式
3. **图片质量**:建议图片清晰,能够清楚看到车辆和垃圾内容
4. **推荐来源**
- OSS对象存储如阿里云OSS、腾讯云COS等
- CDN加速域名
- 公网可访问的图片服务器
**测试用占位图片(仅供参考,实际使用时请替换):**
```
https://via.placeholder.com/800x600.jpg?text=Garbage+Truck+Front
https://via.placeholder.com/800x600.jpg?text=Garbage+Truck+Side
```
---
### 本地开发环境测试
如果是在本地开发环境测试,请确保:
1. **服务已启动**确保Spring Boot应用已启动
2. **端口正确**:默认端口通常是 `8080`,请根据实际配置调整
3. **跨域配置**如果前端调用可能需要配置CORS
4. **完整URL示例**
- 本地:`http://localhost:8080/douBao/analyzeGarbage`
- 开发环境:`http://dev.example.com:8080/douBao/analyzeGarbage`
- 生产环境:`https://api.example.com/douBao/analyzeGarbage`
---
## 响应示例
### 成功响应(车棚已打开)
```json
{
"code": 200,
"msg": "操作成功",
"data": {
"isShedOpened": true,
"details": "石块(多),木材(少)",
"lightMaterialPercentage": 10.00,
"heavyMaterialPercentage": 90.00,
"garbageType": "拆除垃圾",
"judgmentBasis": "车厢内主要为石块,这类垃圾多来自建筑物拆除等工程",
"analysisTime": 1704067200000,
"rawResponse": "明细:石块(多),木材(少);轻物质:10%;重物质:90%;垃圾类型:拆除垃圾;判断依据:车厢内主要为石块,这类垃圾多来自建筑物拆除等工程",
"garbageTypes": null,
"totalWeight": null
}
}
```
### 成功响应(车棚未打开)
```json
{
"code": 200,
"msg": "操作成功",
"data": {
"isShedOpened": false,
"details": "未打开车棚",
"lightMaterialPercentage": null,
"heavyMaterialPercentage": null,
"garbageType": null,
"judgmentBasis": null,
"analysisTime": 1704067200000,
"rawResponse": "未打开车棚",
"garbageTypes": null,
"totalWeight": null
}
}
```
### 错误响应
```json
{
"code": 500,
"msg": "垃圾分析失败垃圾分析参数不能为空且必须包含至少一张图片URL",
"data": null
}
```
---
## 错误码说明
| 错误码 | 说明 |
|--------|------|
| 200 | 请求成功 |
| 400 | 请求参数错误图片URL列表为空 |
| 500 | 服务器内部错误AI服务调用失败、解析失败等 |
---
## 业务逻辑说明
### 分析流程
1. **车棚状态检测**AI首先判断车棚是否完全打开
- 如果车棚未完全打开,直接返回"未打开车棚",不进行后续分析
- 如果车棚已打开,继续执行垃圾分析
2. **垃圾识别**(仅当车棚已打开时):
- 识别车厢内包含的物品类型和数量
- 如果有多辆车,只分析画面中间的车辆
- 分析轻物质和重物质的占比百分比0-100两者之和应为100%
- 判断垃圾类型:拆除垃圾、装修垃圾或抛货
- 提供判断依据说明
### 垃圾类型说明
- **拆除垃圾**:主要来自建筑物拆除等工程,通常包含大量石块、混凝土块等重物质
- **装修垃圾**:主要来自室内装修,通常包含白色板材、泡沫、木棍等轻物质
- **抛货**:其他类型的垃圾
---
## 注意事项
1. **图片要求**
- 图片URL必须可公开访问
- 支持HTTP/HTTPS协议
- 建议图片清晰,能够清楚看到车辆和垃圾内容
- 支持多张图片,建议从不同角度拍摄
2. **性能考虑**
- AI分析需要一定时间建议设置合理的超时时间
- 图片数量越多,分析时间可能越长
3. **数据准确性**
- AI分析结果仅供参考建议结合人工审核
- 如果分析结果不准确,可以通过 `rawResponse` 字段查看AI原始响应进行排查
4. **字段说明**
- `garbageTypes``totalWeight` 为保留字段当前版本可能为null
- `isShedOpened` 为false时其他分析字段`garbageType`、`lightMaterialPercentage` 等可能为null
5. **格式要求**
- AI返回的格式为固定格式解析失败时会记录警告日志
- 如果解析结果不完整,建议查看 `rawResponse` 字段获取原始响应
---
## 更新日志
| 版本 | 日期 | 更新内容 |
|------|------|----------|
| 1.0.0 | 2024-01-01 | 初始版本,支持垃圾车辆图片分析功能 |
---
## 联系方式
如有问题或建议,请联系开发团队。

View File

@ -0,0 +1,48 @@
package com.njzscloud.supervisory.doubao.config;
import com.volcengine.ark.runtime.service.ArkService;
import lombok.RequiredArgsConstructor;
import okhttp3.ConnectionPool;
import okhttp3.Dispatcher;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;
import java.util.concurrent.TimeUnit;
/**
*
*/
@Configuration
@RequiredArgsConstructor
@EnableConfigurationProperties(DouBaoProperties.class)
public class DouBaoConfiguration {
private final DouBaoProperties properties;
@Bean(destroyMethod = "shutdownExecutor")
@ConditionalOnMissingBean
public ArkService arkService() {
String apiKey = properties.getApiKey();
if (!StringUtils.hasText(apiKey)) {
throw new IllegalStateException("豆包API Key未配置请设置环境变量 ARK_API_KEY 或在配置文件中设置 doubao.api-key");
}
ConnectionPool connectionPool = new ConnectionPool(
properties.getConnectionPoolSize(),
properties.getConnectionKeepAliveSeconds(),
TimeUnit.SECONDS
);
Dispatcher dispatcher = new Dispatcher();
return ArkService.builder()
.dispatcher(dispatcher)
.connectionPool(connectionPool)
.baseUrl(properties.getBaseUrl())
.apiKey(apiKey)
.build();
}
}

View File

@ -0,0 +1,52 @@
package com.njzscloud.supervisory.doubao.config;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Accessors;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
*
*/
@Getter
@Setter
@ToString
@Accessors(chain = true)
@ConfigurationProperties(prefix = "doubao")
public class DouBaoProperties {
/**
* API Key ARK_API_KEY 使
*/
private String apiKey;
/**
* URL https://ark.cn-beijing.volces.com/api/v3
*/
private String baseUrl = "https://ark.cn-beijing.volces.com/api/v3";
/**
* ID doubao-seed-1-6-vision-250815
*/
private String model = "doubao-seed-1-6-vision-250815";
/**
* 5
*/
private Integer connectionPoolSize = 5;
/**
* 1
*/
private Long connectionKeepAliveSeconds = 1L;
/**
* API Key
*/
public String getApiKey() {
String envApiKey = System.getenv("ARK_API_KEY");
return envApiKey != null ? envApiKey : apiKey;
}
}

View File

@ -0,0 +1,31 @@
package com.njzscloud.supervisory.doubao.controller;
import com.njzscloud.common.core.utils.R;
import com.njzscloud.supervisory.doubao.param.GarbageAnalysisParam;
import com.njzscloud.supervisory.doubao.result.GarbageAnalysisResult;
import com.njzscloud.supervisory.doubao.sevice.DouBaoService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
*
* 使SDK
*/
@Slf4j
@RestController
@RequestMapping("/douBao")
@RequiredArgsConstructor
public class DouBaoController {
private final DouBaoService douBaoService;
@PostMapping("/analyzeGarbage")
public R<GarbageAnalysisResult> analyzeGarbage(@RequestBody GarbageAnalysisParam param) {
return R.success(douBaoService.analyzeGarbage(param));
}
}

View File

@ -0,0 +1,34 @@
package com.njzscloud.supervisory.doubao.param;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Accessors;
import java.util.List;
/**
*
*/
@Getter
@Setter
@ToString
@Accessors(chain = true)
public class GarbageAnalysisParam {
/**
* URL
*/
private List<String> imageUrls;
/**
*
*/
private String orderSn;
/**
*
*/
private String remark;
}

View File

@ -0,0 +1,105 @@
package com.njzscloud.supervisory.doubao.result;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Accessors;
import java.math.BigDecimal;
import java.util.List;
/**
*
*/
@Getter
@Setter
@ToString
@Accessors(chain = true)
public class GarbageAnalysisResult {
/**
*
*/
private Boolean isShedOpened;
/**
* 1(),2()
* (),()
*/
private String details;
/**
* 0-100
*/
private BigDecimal lightMaterialPercentage;
/**
* 0-100
*/
private BigDecimal heavyMaterialPercentage;
/**
*
*/
private String garbageType;
/**
*
*/
private String judgmentBasis;
/**
*
*/
private Long analysisTime;
/**
*
*/
private String rawResponse;
/**
*
*/
private List<GarbageTypeInfo> garbageTypes;
/**
*
*/
private BigDecimal totalWeight;
/**
*
*/
@Getter
@Setter
@ToString
@Accessors(chain = true)
public static class GarbageTypeInfo {
/**
*
*/
private String typeName;
/**
*
*/
private String category;
/**
* AI
*/
private String judgmentBasis;
/**
* 0-100
*/
private BigDecimal percentage;
/**
*
*/
private BigDecimal weight;
}
}

View File

@ -0,0 +1,18 @@
package com.njzscloud.supervisory.doubao.sevice;
import com.njzscloud.supervisory.doubao.param.GarbageAnalysisParam;
import com.njzscloud.supervisory.doubao.result.GarbageAnalysisResult;
/**
*
*/
public interface DouBaoService {
/**
* -
*
* @param param URL
* @return
*/
GarbageAnalysisResult analyzeGarbage(GarbageAnalysisParam param);
}

View File

@ -0,0 +1,242 @@
package com.njzscloud.supervisory.doubao.sevice.impl;
import com.njzscloud.supervisory.doubao.config.DouBaoProperties;
import com.njzscloud.supervisory.doubao.param.GarbageAnalysisParam;
import com.njzscloud.supervisory.doubao.result.GarbageAnalysisResult;
import com.njzscloud.supervisory.doubao.sevice.DouBaoService;
import com.volcengine.ark.runtime.model.completion.chat.ChatCompletionContentPart;
import com.volcengine.ark.runtime.model.completion.chat.ChatCompletionRequest;
import com.volcengine.ark.runtime.model.completion.chat.ChatMessage;
import com.volcengine.ark.runtime.model.completion.chat.ChatMessageRole;
import com.volcengine.ark.runtime.service.ArkService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
*
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class DouBaoServiceImpl implements DouBaoService {
private final ArkService arkService;
private final DouBaoProperties douBaoProperties;
/**
* -
*
* @param param URL
* @return
*/
@Override
public GarbageAnalysisResult analyzeGarbage(GarbageAnalysisParam param) {
try {
log.info("开始调用火山引擎豆包API进行垃圾分析订单号{},图片数量:{}",
param != null ? param.getOrderSn() : "未知",
param != null && param.getImageUrls() != null ? param.getImageUrls().size() : 0);
// 参数校验
if (param == null || CollectionUtils.isEmpty(param.getImageUrls())) {
throw new IllegalArgumentException("垃圾分析参数不能为空且必须包含至少一张图片URL");
}
// 构建消息列表
final List<ChatMessage> messages = new ArrayList<>();
final List<ChatCompletionContentPart> multiParts = new ArrayList<>();
// 添加所有图片URL
for (String imageUrl : param.getImageUrls()) {
if (StringUtils.hasText(imageUrl)) {
multiParts.add(ChatCompletionContentPart.builder()
.type("image_url")
.imageUrl(new ChatCompletionContentPart.ChatCompletionContentPartImageURL(imageUrl))
.build());
}
}
// 添加文本提示词要求返回JSON格式
String textPrompt = buildGarbageAnalysisPrompt(param);
multiParts.add(ChatCompletionContentPart.builder()
.type("text")
.text(textPrompt)
.build());
// 构建用户消息
final ChatMessage userMessage = ChatMessage.builder()
.role(ChatMessageRole.USER)
.multiContent(multiParts)
.build();
messages.add(userMessage);
// 构建请求
ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder()
.model(douBaoProperties.getModel())
.messages(messages)
.build();
// 调用API并获取响应
String response = arkService.createChatCompletion(chatCompletionRequest)
.getChoices()
.stream()
.map(choice -> String.valueOf(choice.getMessage().getContent()))
.collect(Collectors.joining("\n"));
log.info("火山引擎豆包API调用成功原始响应内容{}", response);
// 解析响应并转换为结果对象
GarbageAnalysisResult result = parseGarbageAnalysisResponse(response);
result.setRawResponse(response);
result.setAnalysisTime(System.currentTimeMillis());
log.info("垃圾分析完成,车棚状态:{},垃圾类型:{},轻物质占比:{}%,重物质占比:{}%",
result.getIsShedOpened() != null && result.getIsShedOpened() ? "已打开" : "未打开",
result.getGarbageType(),
result.getLightMaterialPercentage(),
result.getHeavyMaterialPercentage());
return result;
} catch (Exception e) {
log.error("调用火山引擎豆包API进行垃圾分析失败订单号{}",
param != null ? param.getOrderSn() : "未知", e);
throw new RuntimeException("垃圾分析失败:" + e.getMessage(), e);
}
}
/**
*
* AI
*/
private String buildGarbageAnalysisPrompt(GarbageAnalysisParam param) {
StringBuilder prompt = new StringBuilder();
prompt.append("请仔细分析这些图片中的垃圾内容。\n\n");
// 添加订单信息(如果有)
if (StringUtils.hasText(param.getOrderSn())) {
prompt.append("订单号:").append(param.getOrderSn()).append("\n");
}
if (StringUtils.hasText(param.getRemark())) {
prompt.append("备注:").append(param.getRemark()).append("\n");
}
prompt.append("\n请按照以下要求进行分析\n");
prompt.append("1. 首先判断图片中是否有垃圾运输车辆:\n");
prompt.append(" - 如果图片中没有车辆直接是垃圾堆、垃圾场等场景请直接跳到第3步分析垃圾内容\n");
prompt.append(" - 如果图片中有车辆请继续第2步判断车棚状态\n");
prompt.append("2. 如果图片中有车辆,判断车棚是否完全打开:\n");
prompt.append(" - 如果车棚没有完全打开,无法看到车厢内的垃圾内容,直接回答:未打开车棚\n");
prompt.append(" - 如果车棚完全打开可以看到车厢内的垃圾请继续第3步分析\n");
prompt.append("3. 分析垃圾内容(适用于:无车辆的场景,或有车辆且车棚已打开的场景):\n");
prompt.append(" - 识别垃圾中包含哪几种物品(如果有多辆车,只分析画面中间的车辆或主要垃圾堆)\n");
prompt.append(" - 说明哪种物品占比最多\n");
prompt.append(" - 给出轻物质和重物质的大概占比使用百分比数值0-100\n");
prompt.append(" - 判断垃圾类型:拆除垃圾、装修垃圾或抛货\n");
prompt.append(" - 说明判断垃圾类型的原因\n\n");
prompt.append("请严格按照以下格式返回结果,不要添加任何其他文字说明:\n");
prompt.append("如果图片中有车辆且车棚未打开,直接回答:未打开车棚\n");
prompt.append("如果图片中没有车辆,或者有车辆且车棚已打开,按照以下格式回答:\n");
prompt.append("明细:物品1(数量描述),物品2(数量描述);轻物质:x%;重物质:x%;垃圾类型:x;判断依据:x\n\n");
prompt.append("格式说明:\n");
prompt.append("- 明细:列出垃圾中包含的所有物品,用逗号分隔,每个物品后标注数量(多、少、较多、较少等)\n");
prompt.append("- 轻物质轻物质的占比百分比0-100\n");
prompt.append("- 重物质重物质的占比百分比0-100轻物质和重物质占比之和应为100%\n");
prompt.append("- 垃圾类型:拆除垃圾、装修垃圾或抛货\n");
prompt.append("- 判断依据:详细说明为什么判断为该垃圾类型\n\n");
prompt.append("示例:\n");
prompt.append("明细:石块(多);轻物质:0%;重物质:100%;垃圾类型:拆除垃圾;判断依据:主要为石块,这类垃圾多来自建筑物拆除等工程\n");
prompt.append("明细:白色板材(多),白色泡沫(少),木棍(少);轻物质:90%;重物质:10%;垃圾类型:装修垃圾;判断依据:主要是白色板材、泡沫等装修常见废弃物\n");
prompt.append("明细:纸箱(多),塑料瓶(少),泡沫箱(少);轻物质:95%;重物质:5%;垃圾类型:抛货;判断依据:主要是轻质包装材料,属于抛货类垃圾\n\n");
prompt.append("注意:必须严格按照上述格式返回,不要添加任何其他文字说明。");
return prompt.toString();
}
/**
*
* AI
* :xx(x),xx(x);:x%;:x%;:x;:x
*
*/
private GarbageAnalysisResult parseGarbageAnalysisResponse(String response) {
if (!StringUtils.hasText(response)) {
throw new RuntimeException("AI响应为空无法解析");
}
try {
GarbageAnalysisResult result = new GarbageAnalysisResult();
String trimmed = response.trim();
// 检查是否是"未打开车棚"的情况
if (trimmed.contains("未打开车棚")) {
result.setIsShedOpened(false);
result.setDetails("未打开车棚");
result.setLightMaterialPercentage(BigDecimal.ZERO);
result.setHeavyMaterialPercentage(BigDecimal.ZERO);
result.setGarbageType("无");
result.setJudgmentBasis("车棚未完全打开,无法进行分析");
return result;
}
// 车棚已打开,解析详细格式
result.setIsShedOpened(true);
// 提取明细
Pattern detailsPattern = Pattern.compile("明细[:]([^;]+)");
Matcher detailsMatcher = detailsPattern.matcher(trimmed);
if (detailsMatcher.find()) {
result.setDetails(detailsMatcher.group(1).trim());
}
// 提取轻物质占比
Pattern lightPattern = Pattern.compile("轻物质[:](\\d+(?:\\.\\d+)?)%");
Matcher lightMatcher = lightPattern.matcher(trimmed);
if (lightMatcher.find()) {
result.setLightMaterialPercentage(new BigDecimal(lightMatcher.group(1)));
}
// 提取重物质占比
Pattern heavyPattern = Pattern.compile("重物质[:](\\d+(?:\\.\\d+)?)%");
Matcher heavyMatcher = heavyPattern.matcher(trimmed);
if (heavyMatcher.find()) {
result.setHeavyMaterialPercentage(new BigDecimal(heavyMatcher.group(1)));
}
// 提取垃圾类型
Pattern typePattern = Pattern.compile("垃圾类型[:]([^;]+)");
Matcher typeMatcher = typePattern.matcher(trimmed);
if (typeMatcher.find()) {
result.setGarbageType(typeMatcher.group(1).trim());
}
// 提取判断依据
Pattern basisPattern = Pattern.compile("判断依据[:](.+)");
Matcher basisMatcher = basisPattern.matcher(trimmed);
if (basisMatcher.find()) {
result.setJudgmentBasis(basisMatcher.group(1).trim());
}
// 验证必要字段
if (result.getDetails() == null || result.getGarbageType() == null) {
log.warn("解析结果不完整,响应内容:{}", response);
}
return result;
} catch (Exception e) {
log.error("解析垃圾分析响应失败,响应内容:{}", response, e);
throw new RuntimeException("解析AI响应失败" + e.getMessage(), e);
}
}
}

View File

@ -80,6 +80,9 @@ wechat:
notify-url: http://115.29.236.92:8082/payment/wechat/notify
# 退款回调地址
refund-notify-url: http://115.29.236.92:8082/payment/wechat/refundNotify
doubao:
api-name: api-key-20250411103646
api-key: e0fc97bb-2bb6-4525-a79b-1e3555026821
mqtt:
enabled: true
broker: tcp://139.224.54.144:1883

View File

@ -70,7 +70,9 @@ wechat:
notify-url: http://139.224.32.69:80/api/payment/wechat/notify
# 退款回调地址
refund-notify-url: http://139.224.32.69:80/api/payment/wechat/refundNotify
doubao:
api-name: api-key-20250411103646
api-key: e0fc97bb-2bb6-4525-a79b-1e3555026821
mqtt:
enabled: true
broker: tcp://127.0.0.1:1883