diff --git a/njzscloud-svr/pom.xml b/njzscloud-svr/pom.xml
index 3cfe9fd..2ed0d55 100644
--- a/njzscloud-svr/pom.xml
+++ b/njzscloud-svr/pom.xml
@@ -125,6 +125,11 @@
aspectjweaver
1.9.7
+
+ com.volcengine
+ volcengine-java-sdk-ark-runtime
+ LATEST
+
diff --git a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/doubao/API文档.md b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/doubao/API文档.md
new file mode 100644
index 0000000..34f0251
--- /dev/null
+++ b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/doubao/API文档.md
@@ -0,0 +1,520 @@
+# 豆包垃圾分析接口文档
+
+## 接口概述
+
+本接口基于火山引擎豆包AI模型,提供垃圾车辆图片分析功能。通过上传多张垃圾车辆图片,AI会自动识别车棚状态、垃圾类型、轻物质和重物质占比等信息。
+
+**接口路径:** `/douBao/analyzeGarbage`
+**请求方式:** `POST`
+**Content-Type:** `application/json`
+
+---
+
+## 请求参数
+
+### 请求体(GarbageAnalysisParam)
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| imageUrls | List<String> | 是 | 图片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<GarbageTypeInfo> | 垃圾类型分析列表(保留字段,用于兼容) |
+| 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 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 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`
+
+#### Body(raw 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 | 初始版本,支持垃圾车辆图片分析功能 |
+
+---
+
+## 联系方式
+
+如有问题或建议,请联系开发团队。
+
diff --git a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/doubao/config/DouBaoConfiguration.java b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/doubao/config/DouBaoConfiguration.java
new file mode 100644
index 0000000..b769859
--- /dev/null
+++ b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/doubao/config/DouBaoConfiguration.java
@@ -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();
+ }
+}
+
diff --git a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/doubao/config/DouBaoProperties.java b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/doubao/config/DouBaoProperties.java
new file mode 100644
index 0000000..3312ee0
--- /dev/null
+++ b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/doubao/config/DouBaoProperties.java
@@ -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;
+ }
+}
+
diff --git a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/doubao/controller/DouBaoController.java b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/doubao/controller/DouBaoController.java
new file mode 100644
index 0000000..b654df3
--- /dev/null
+++ b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/doubao/controller/DouBaoController.java
@@ -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 analyzeGarbage(@RequestBody GarbageAnalysisParam param) {
+ return R.success(douBaoService.analyzeGarbage(param));
+ }
+
+}
diff --git a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/doubao/param/GarbageAnalysisParam.java b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/doubao/param/GarbageAnalysisParam.java
new file mode 100644
index 0000000..d30eec0
--- /dev/null
+++ b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/doubao/param/GarbageAnalysisParam.java
@@ -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 imageUrls;
+
+ /**
+ * 订单号(可选)
+ */
+ private String orderSn;
+
+ /**
+ * 备注信息(可选)
+ */
+ private String remark;
+}
+
diff --git a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/doubao/result/GarbageAnalysisResult.java b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/doubao/result/GarbageAnalysisResult.java
new file mode 100644
index 0000000..3c2d3e3
--- /dev/null
+++ b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/doubao/result/GarbageAnalysisResult.java
@@ -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 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;
+ }
+}
+
diff --git a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/doubao/sevice/DouBaoService.java b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/doubao/sevice/DouBaoService.java
new file mode 100644
index 0000000..2f26a4d
--- /dev/null
+++ b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/doubao/sevice/DouBaoService.java
@@ -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);
+}
diff --git a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/doubao/sevice/impl/DouBaoServiceImpl.java b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/doubao/sevice/impl/DouBaoServiceImpl.java
new file mode 100644
index 0000000..666cbe0
--- /dev/null
+++ b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/doubao/sevice/impl/DouBaoServiceImpl.java
@@ -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 messages = new ArrayList<>();
+ final List 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);
+ }
+ }
+}
diff --git a/njzscloud-svr/src/main/resources/application-dev.yml b/njzscloud-svr/src/main/resources/application-dev.yml
index 078efbf..7bae6aa 100644
--- a/njzscloud-svr/src/main/resources/application-dev.yml
+++ b/njzscloud-svr/src/main/resources/application-dev.yml
@@ -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
diff --git a/njzscloud-svr/src/main/resources/application-prod.yml b/njzscloud-svr/src/main/resources/application-prod.yml
index 9891674..ad723ff 100644
--- a/njzscloud-svr/src/main/resources/application-prod.yml
+++ b/njzscloud-svr/src/main/resources/application-prod.yml
@@ -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