Skip to content

adminkk/xiaodu-demo

Repository files navigation

百度小度 API 集成项目

基于 Spring Boot + OkHttp 实现的百度小度 DuerOS API 集成项目,支持OAuth认证和7种推送模板类型。采用DTO设计模式,提供清晰、类型安全的API接口。

功能特性

  • ✅ OAuth 2.0 认证(获取/刷新Access Token)
  • ✅ 7种推送模板类型完整实现
  • DTO设计模式 - 请求响应全部对象化
  • 统一响应格式 - Result统一封装
  • 参数校验 - JSR-303注解验证
  • ✅ 完整的异常处理和日志记录
  • ✅ 代码复用性高,易于维护
  • 完整测试套件 - JUnit 单元测试 + 集成测试
  • 便捷测试工具 - Shell/Batch 脚本 + Postman 集合

快速开始

1. 配置应用信息

编辑 src/main/resources/application.yml

xiaodu:
  api:
    base-url: https://dueros.baidu.com/business
    app-id: YOUR_APP_ID          # 替换为你的 APP_ID
    app-secret: YOUR_APP_SECRET  # 替换为你的 APP_SECRET

2. 构建并运行

# 构建项目
mvn clean package

# 运行项目
mvn spring-boot:run

3. 运行测试

项目提供了完整的测试用例,包括 OAuth 认证和所有 7 种推送模板的测试。

注意: 运行测试前,请修改测试类中的 TEST_CUID 为您的真实设备 ID:

// 位置:src/test/java/com/xiaodu/demo/service/XiaoduPushServiceTest.java
private static final String TEST_CUID = "your-device-cuid-here";

运行所有测试:

mvn test

运行单个测试:

# 测试 OAuth 服务
mvn test -Dtest=XiaoduOAuthServiceTest

# 测试推送服务
mvn test -Dtest=XiaoduPushServiceTest

测试覆盖:

  • ✅ OAuth 认证测试(获取/刷新 Token)
  • ✅ 纯文本推送测试
  • ✅ 全图片推送测试
  • ✅ 左图右文推送测试
  • ✅ 上图下文推送测试
  • ✅ 音频推送测试
  • ✅ 视频推送测试
  • ✅ SSML 播报推送测试

4. 使用测试脚本

项目提供了便捷的测试脚本,可一键测试所有接口:

Linux/Mac:

# 赋予执行权限
chmod +x test-api.sh

# 运行测试(替换为您的设备 CUID)
./test-api.sh your-device-cuid

Windows:

# 运行测试(替换为您的设备 CUID)
test-api.bat your-device-cuid

5. 使用 Postman

项目提供了 Postman 集合文件 Xiaodu-API.postman_collection.json,包含所有接口:

  1. 导入 Postman 集合
  2. 设置环境变量:
  3. 执行 "获取 Access Token" 请求
  4. 将返回的 accessToken 设置到环境变量 access_token
  5. 测试其他推送接口

API接口文档

统一响应格式

所有接口统一返回Result<T>对象:

{
  "success": true,
  "errno": null,
  "message": "操作成功",
  "data": {
    // 具体业务数据
  }
}

OAuth认证接口

1. 获取Access Token

curl -X GET http://localhost:8080/api/xiaodu/token

响应示例:

{
  "success": true,
  "message": "操作成功",
  "data": {
    "accessToken": "eyJ0eXAiOiJKV1QiLCJhbGc...",
    "expiresIn": 2592000,
    "deadline": 1543393183,
    "refreshToken": "eyJ0eXAiOiJKV1QiLCJhbGc..."
  }
}

推送接口

所有推送接口都使用 POST + JSON Body 方式,并在Header中携带Authorization。

1. 纯文本推送(AllText)

接口: POST /api/xiaodu/push/text

请求示例:

curl -X POST \
  http://localhost:8080/api/xiaodu/push/text \
  -H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "cuid": "MA718CNBFCL010122",
    "title": "托尔斯泰格言",
    "content": "托尔斯泰 - 理想的书籍,是智慧的钥匙"
  }'

请求字段:

字段 类型 必填 说明
cuid String 设备唯一标识
title String 推送标题
content String 推送内容

响应示例:

{
  "success": true,
  "message": "推送成功",
  "data": {
    "result": "success",
    "msgId": "1234567890"
  }
}

2. 全图片推送(AllImage)

接口: POST /api/xiaodu/push/allImage

请求示例:

curl -X POST \
  http://localhost:8080/api/xiaodu/push/allImage \
  -H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "cuid": "MA718CNBFCL010122",
    "imageSrc": "https://example.com/image.jpg",
    "imageThumb": "https://example.com/thumb.jpg"
  }'

请求字段:

字段 类型 必填 说明
cuid String 设备唯一标识
imageSrc String 图片地址
imageThumb String 图片缩略图地址

3. 左图右文推送(TextRight)

接口: POST /api/xiaodu/push/textRight

请求示例:

curl -X POST \
  http://localhost:8080/api/xiaodu/push/textRight \
  -H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "cuid": "MA718CNBFCL010122",
    "title": "托尔斯泰格言",
    "content": "托尔斯泰 - 理想的书籍,是智慧的钥匙",
    "image": "https://example.com/image.jpg"
  }'

请求字段:

字段 类型 必填 说明
cuid String 设备唯一标识
title String 推送标题
content String 推送内容
image String 图片地址

4. 上图下文推送(TextBottom)

接口: POST /api/xiaodu/push/textBottom

请求参数同左图右文,使用相同的请求格式。


5. 音频推送(AudioPlayer)

接口: POST /api/xiaodu/push/audio

请求示例:

curl -X POST \
  http://localhost:8080/api/xiaodu/push/audio \
  -H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "cuid": "MA718CNBFCL010122",
    "streamUrl": "https://example.com/audio.mp3",
    "title": "告白气球",
    "titleSubtext1": "周杰伦",
    "titleSubtext2": "周杰伦的床边故事",
    "artSrc": "https://example.com/cover.jpg"
  }'

请求字段:

字段 类型 必填 说明
cuid String 设备唯一标识
streamUrl String 音频流地址
title String 音频标题
titleSubtext1 String 子标题1(歌手名)
titleSubtext2 String 子标题2(专辑名)
artSrc String 封面图片

6. 视频推送(VideoPlayer)

接口: POST /api/xiaodu/push/video

请求示例:

curl -X POST \
  http://localhost:8080/api/xiaodu/push/video \
  -H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "cuid": "MA718CNBFCL010122",
    "streamUrl": "https://example.com/video.mp4"
  }'

请求字段:

字段 类型 必填 说明
cuid String 设备唯一标识
streamUrl String 视频流地址

7. SSML播报推送(SsmlText)

接口: POST /api/xiaodu/push/ssml

请求示例:

curl -X POST \
  http://localhost:8080/api/xiaodu/push/ssml \
  -H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "cuid": "MA718CNBFCL010122",
    "title": "托尔斯泰格言",
    "content": "托尔斯泰 - 理想的书籍,是智慧的钥匙",
    "outputSpeech": "<speak>这句话之后会有3秒钟的停顿<silence time=\"3s\"></silence>停顿结束</speak>"
  }'

请求字段:

字段 类型 必填 说明
cuid String 设备唯一标识
title String 推送标题
content String 显示内容
outputSpeech String SSML播报内容

代码使用示例

在Java代码中使用(推荐DTO方式)

@Autowired
private XiaoduOAuthService oauthService;

@Autowired
private XiaoduPushService pushService;

public void example() {
    // 1. 获取Access Token
    TokenData tokenData = oauthService.getAccessToken();
    String accessToken = tokenData.getAccessToken();

    // 2. 发送纯文本推送(使用DTO)
    TextPushRequest textRequest = TextPushRequest.builder()
        .cuid("MA718CNBFCL010122")
        .title("托尔斯泰格言")
        .content("托尔斯泰 - 理想的书籍,是智慧的钥匙")
        .build();

    PushResponse response = pushService.sendTextPush(accessToken, textRequest);
    System.out.println("推送成功,消息ID:" + response.getMsgId());

    // 3. 发送音频推送(使用DTO)
    AudioPushRequest audioRequest = AudioPushRequest.builder()
        .cuid("MA718CNBFCL010122")
        .streamUrl("https://example.com/audio.mp3")
        .title("告白气球")
        .titleSubtext1("周杰伦")
        .build();

    PushResponse audioResponse = pushService.sendAudioPush(accessToken, audioRequest);
}

项目结构

xiaodu-demo/
├── src/
│   ├── main/java/com/xiaodu/demo/
│   │   ├── config/              # 配置类
│   │   │   ├── OkHttpConfig.java
│   │   │   └── XiaoduApiProperties.java
│   │   ├── constant/            # 常量定义
│   │   │   └── PushTemplateType.java
│   │   ├── controller/          # REST控制器(使用DTO)
│   │   │   ├── XiaoduOAuthController.java
│   │   │   └── XiaoduPushController.java
│   │   ├── service/             # 业务服务
│   │   │   ├── XiaoduOAuthService.java
│   │   │   └── XiaoduPushService.java
│   │   ├── dto/                 # 数据传输对象
│   │   │   ├── request/         # 请求DTO
│   │   │   │   ├── BasePushRequest.java
│   │   │   │   ├── TextPushRequest.java
│   │   │   │   ├── AllImagePushRequest.java
│   │   │   │   ├── ImageTextPushRequest.java
│   │   │   │   ├── AudioPushRequest.java
│   │   │   │   ├── VideoPushRequest.java
│   │   │   │   └── SsmlPushRequest.java
│   │   │   └── response/        # 响应DTO
│   │   │       ├── Result.java
│   │   │       ├── TokenResponse.java
│   │   │       └── PushResponse.java
│   │   ├── model/               # 实体类(百度API原始数据结构)
│   │   │   ├── PushParam.java
│   │   │   ├── TokenData.java
│   │   │   └── XiaoduResponse.java
│   │   ├── util/                # 工具类
│   │   │   └── OkHttpUtil.java
│   │   ├── exception/           # 异常类
│   │   │   ├── XiaoduApiException.java
│   │   │   └── GlobalExceptionHandler.java
│   │   └── XiaoduDemoApplication.java
│   ├── main/resources/
│   │   └── application.yml      # 应用配置
│   └── test/java/com/xiaodu/demo/service/  # 测试用例
│       ├── XiaoduOAuthServiceTest.java     # OAuth服务测试
│       └── XiaoduPushServiceTest.java      # 推送服务测试
├── test-api.sh                  # Linux/Mac 测试脚本
├── test-api.bat                 # Windows 测试脚本
├── Xiaodu-API.postman_collection.json  # Postman 集合
├── pom.xml
└── README.md

设计优势

1. DTO设计模式

  • ✅ 请求参数对象化,类型安全
  • ✅ 参数校验自动化(JSR-303注解)
  • ✅ 代码可读性强,易于维护

2. 统一响应格式

Result<T> {
    success: Boolean   // 是否成功
    errno: Integer     // 错误码
    message: String    // 提示信息
    data: T           // 业务数据
}

3. 继承结构

  • BasePushRequest - 所有推送请求的基类
  • 各推送类型继承基类,复用cuid字段
  • 提高代码复用性

4. 参数校验示例

@NotBlank(message = "设备ID不能为空")
private String cuid;

@NotBlank(message = "标题不能为空")
private String title;

模板类型总览

模板类型 接口路径 请求DTO 响应DTO 说明
AllText /push/text TextPushRequest PushResponse 纯文本
AllImage /push/allImage AllImagePushRequest PushResponse 纯图片
TextRight /push/textRight ImageTextPushRequest PushResponse 左图右文
TextBottom /push/textBottom ImageTextPushRequest PushResponse 上图下文
AudioPlayer /push/audio AudioPushRequest PushResponse 音频
VideoPlayer /push/video VideoPushRequest PushResponse 视频
SsmlText /push/ssml SsmlPushRequest PushResponse SSML播报

技术栈

  • Spring Boot 2.7.18
  • OkHttp 4.9.3
  • Gson(JSON序列化)
  • Lombok(简化代码)
  • JSR-303(参数校验)
  • Java 8+

注意事项

  1. Access Token有效期:默认30天,建议提前5分钟刷新
  2. 请求格式:所有推送接口使用POST + JSON Body
  3. 参数校验:必填字段会自动校验,返回清晰的错误提示
  4. 设备ID:cuid需要从小度设备或API中获取
  5. 媒体资源:图片、音频、视频URL必须是公网可访问的HTTPS地址

完整测试示例

端到端测试流程(使用 curl)

# 1. 获取 Access Token
curl -X GET "http://localhost:8080/api/xiaodu/token"

# 响应示例:
# {
#   "success": true,
#   "message": "操作成功",
#   "data": {
#     "accessToken": "24.abcdef123456...",
#     "expiresIn": 2592000,
#     "deadline": 1738108800
#   }
# }

# 2. 使用获取的 Token 发送推送(以纯文本推送为例)
ACCESS_TOKEN="24.abcdef123456..."  # 替换为实际获取的 token

curl -X POST "http://localhost:8080/api/xiaodu/push/text" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "cuid": "your-device-cuid",
    "title": "测试推送",
    "content": "这是一条测试消息"
  }'

# 响应示例:
# {
#   "success": true,
#   "message": "推送成功",
#   "data": {
#     "result": "success",
#     "msgId": "1234567890"
#   }
# }

批量测试所有推送类型

# 设置变量
ACCESS_TOKEN="your-access-token"
DEVICE_CUID="your-device-cuid"
BASE_URL="http://localhost:8080/api/xiaodu"

# 1. 纯文本推送
curl -X POST "$BASE_URL/push/text" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"cuid\":\"$DEVICE_CUID\",\"title\":\"天气提醒\",\"content\":\"今天北京晴转多云\"}"

# 2. 全图片推送
curl -X POST "$BASE_URL/push/allImage" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"cuid\":\"$DEVICE_CUID\",\"imageSrc\":\"https://example.com/image.jpg\",\"imageThumb\":\"https://example.com/thumb.jpg\"}"

# 3. 左图右文推送
curl -X POST "$BASE_URL/push/textRight" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"cuid\":\"$DEVICE_CUID\",\"title\":\"新闻标题\",\"content\":\"新闻内容\",\"image\":\"https://example.com/news.jpg\"}"

# 4. 音频推送
curl -X POST "$BASE_URL/push/audio" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"cuid\":\"$DEVICE_CUID\",\"streamUrl\":\"https://example.com/music.mp3\",\"title\":\"歌曲名\",\"titleSubtext1\":\"歌手名\"}"

# 5. 视频推送
curl -X POST "$BASE_URL/push/video" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"cuid\":\"$DEVICE_CUID\",\"streamUrl\":\"https://example.com/video.mp4\"}"

最佳实践

1. Token 管理

推荐实现 Token 缓存和自动刷新机制:

@Service
public class TokenManagerService {

    @Autowired
    private XiaoduOAuthService oauthService;

    private String cachedToken;
    private long tokenExpireTime;

    /**
     * 获取有效的 Access Token(带缓存)
     */
    public synchronized String getValidToken() {
        // 检查 token 是否即将过期(提前 5 分钟刷新)
        if (cachedToken == null || System.currentTimeMillis() > tokenExpireTime - 300000) {
            refreshToken();
        }
        return cachedToken;
    }

    private void refreshToken() {
        TokenData tokenData = oauthService.getAccessToken();
        this.cachedToken = tokenData.getAccessToken();
        this.tokenExpireTime = tokenData.getDeadline() * 1000L;
        log.info("Token已更新,过期时间:{}", new Date(tokenExpireTime));
    }
}

2. 异步推送

对于高并发场景,建议使用异步推送:

@Service
public class AsyncPushService {

    @Autowired
    private XiaoduPushService pushService;

    @Autowired
    private TokenManagerService tokenManager;

    @Async
    public CompletableFuture<PushResponse> sendTextPushAsync(TextPushRequest request) {
        String token = tokenManager.getValidToken();
        PushResponse response = pushService.sendTextPush(token, request);
        return CompletableFuture.completedFuture(response);
    }
}

3. 错误重试

实现自动重试机制:

@Service
public class RetryablePushService {

    private static final int MAX_RETRIES = 3;

    @Retryable(
        value = {XiaoduApiException.class},
        maxAttempts = MAX_RETRIES,
        backoff = @Backoff(delay = 1000, multiplier = 2)
    )
    public PushResponse sendWithRetry(String token, TextPushRequest request) {
        return pushService.sendTextPush(token, request);
    }
}

4. 日志脱敏

生产环境建议对敏感信息脱敏:

private String maskToken(String token) {
    if (token == null || token.length() < 10) {
        return "***";
    }
    return token.substring(0, 10) + "..." + token.substring(token.length() - 10);
}

log.info("使用 Token: {}", maskToken(accessToken));

配置详解

使用环境变量(推荐用于生产环境)

application.yml:

xiaodu:
  api:
    base-url: ${XIAODU_BASE_URL:https://dueros.baidu.com/business}
    app-id: ${XIAODU_APP_ID}
    app-secret: ${XIAODU_APP_SECRET}

设置环境变量:

# Linux/Mac
export XIAODU_APP_ID="your-app-id"
export XIAODU_APP_SECRET="your-app-secret"

# Windows
set XIAODU_APP_ID=your-app-id
set XIAODU_APP_SECRET=your-app-secret

多环境配置

application-dev.yml (开发环境):

xiaodu:
  api:
    base-url: https://dueros-test.baidu.com/business
    app-id: dev-app-id
    app-secret: dev-app-secret

logging:
  level:
    okhttp3.logging.HttpLoggingInterceptor: DEBUG
    com.xiaodu.demo: DEBUG

application-prod.yml (生产环境):

xiaodu:
  api:
    base-url: https://dueros.baidu.com/business
    app-id: ${XIAODU_APP_ID}
    app-secret: ${XIAODU_APP_SECRET}

logging:
  level:
    okhttp3.logging.HttpLoggingInterceptor: INFO
    com.xiaodu.demo: INFO

启动时指定环境:

# 开发环境
java -jar xiaodu-demo.jar --spring.profiles.active=dev

# 生产环境
java -jar xiaodu-demo.jar --spring.profiles.active=prod

常见问题 (FAQ)

Q1: 如何获取设备 CUID?

A: CUID(Client Unique ID)是设备的唯一标识,需要通过以下方式获取:

  • 从百度小度开放平台的设备管理页面查看
  • 通过设备绑定时的回调接口获取
  • 联系百度小度技术支持获取测试设备 CUID

Q2: 推送失败,返回 401 错误怎么办?

A: 401 错误通常表示 Access Token 无效或过期,请检查:

  1. Token 是否正确获取
  2. Token 是否已过期(有效期 30 天)
  3. 请求头格式是否正确:Authorization: Bearer {token}
  4. App ID 和 App Secret 是否正确

Q3: 如何查看 HTTP 请求详细日志?

A: 项目已配置 OkHttp 日志拦截器,在 application.yml 中设置日志级别:

logging:
  level:
    okhttp3.logging.HttpLoggingInterceptor: DEBUG  # 显示完整请求响应
    com.xiaodu.demo: DEBUG

Q4: 推送成功但设备没有收到消息?

A: 请检查:

  1. CUID 是否正确
  2. 设备是否在线
  3. 设备是否已授权接收推送
  4. 推送内容格式是否符合要求
  5. 媒体资源 URL 是否可访问(需要 HTTPS)

Q5: 如何在生产环境中管理 Token?

A: 建议:

  1. 使用缓存机制存储 Token(Redis/内存)
  2. 实现自动刷新逻辑(提前 5-10 分钟)
  3. 使用分布式锁避免并发刷新
  4. 记录 Token 刷新日志便于追踪

Q6: 参数校验失败如何处理?

A: 项目使用 JSR-303 校验,失败时会返回:

{
  "success": false,
  "errno": 400,
  "message": "设备ID不能为空",
  "data": null
}

请根据错误信息检查请求参数。

Q7: 如何集成到现有项目?

A: 有两种方式:

  1. 作为独立服务:启动本项目,其他项目通过 REST API 调用
  2. 集成到现有项目:复制 service、dto、config 等包到您的项目中

Q8: 支持批量推送吗?

A: 当前实现是单个设备推送,如需批量推送,可以:

public List<PushResponse> batchPush(String token, List<TextPushRequest> requests) {
    return requests.stream()
        .map(req -> pushService.sendTextPush(token, req))
        .collect(Collectors.toList());
}

错误码说明

错误码 说明 解决方案
0 成功 -
401 Token 无效 重新获取 Access Token
403 权限不足 检查 App ID 和 Secret
404 接口不存在 检查 API 路径
400 参数错误 检查请求参数格式
500 服务器错误 联系技术支持

性能优化建议

  1. 连接池配置:OkHttp 默认连接池已优化,如需调整:
@Bean
public OkHttpClient okHttpClient() {
    ConnectionPool connectionPool = new ConnectionPool(10, 5, TimeUnit.MINUTES);
    return new OkHttpClient.Builder()
        .connectionPool(connectionPool)
        .connectTimeout(30, TimeUnit.SECONDS)
        .readTimeout(30, TimeUnit.SECONDS)
        .writeTimeout(30, TimeUnit.SECONDS)
        .addInterceptor(loggingInterceptor)
        .build();
}
  1. 异步处理:使用 @Async 注解实现异步推送
  2. 批量处理:合并多个推送请求减少网络开销
  3. Token 缓存:避免频繁请求 Token 接口

监控与运维

健康检查

@RestController
@RequestMapping("/actuator")
public class HealthController {

    @GetMapping("/health")
    public Map<String, String> health() {
        Map<String, String> health = new HashMap<>();
        health.put("status", "UP");
        health.put("service", "xiaodu-push-service");
        return health;
    }
}

日志监控

建议监控以下日志关键字:

  • 推送失败 - 推送失败次数
  • HTTP请求异常 - 网络问题
  • Token已更新 - Token 刷新记录

指标收集

可集成 Spring Boot Actuator 和 Prometheus:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
</dependency>

贡献指南

欢迎提交 Issue 和 Pull Request!

  1. Fork 本仓库
  2. 创建特性分支 (git checkout -b feature/AmazingFeature)
  3. 提交更改 (git commit -m 'Add some AmazingFeature')
  4. 推送到分支 (git push origin feature/AmazingFeature)
  5. 开启 Pull Request

更新日志

v1.0.0 (2024-01-30)

  • ✅ 实现 OAuth 2.0 认证
  • ✅ 支持 7 种推送模板
  • ✅ DTO 设计模式
  • ✅ 统一异常处理
  • ✅ HTTP 请求日志
  • ✅ 完整测试用例

相关链接

License

MIT License

About

基于 Spring Boot + OkHttp 实现的百度小度 DuerOS API 集成项目,支持OAuth认证和7种推送模板类型。采用DTO设计模式,提供清晰、类型安全的API接口。

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors