SRS 4.0 HTTP回调实战:SpringBoot 5个接口实现推拉流鉴权与日志记录

流媒体服务在现代互联网应用中扮演着越来越重要的角色,而SRS(Simple Realtime Server)作为一款高性能的开源流媒体服务器,凭借其轻量级、高并发的特性,已经成为众多开发者的首选。本文将深入探讨如何利用SRS 4.0的HTTP回调机制,结合SpringBoot后端实现一个完整的流媒体业务系统,涵盖推流/拉流鉴权、用户行为日志记录等核心功能。

1. SRS HTTP回调机制解析

HTTP回调是SRS提供的一种灵活的事件通知机制,它允许开发者在特定事件发生时(如客户端连接、推流、拉流等),通过HTTP请求将相关数据发送到开发者自定义的服务端接口。这种机制为业务逻辑的扩展提供了极大的便利。

SRS 4.0支持的主要回调事件包括:

  • on_connect :客户端连接到指定vhost和app时触发
  • on_publish :客户端开始推流时触发
  • on_play :客户端开始拉流播放时触发
  • on_stop :客户端停止播放时触发
  • on_close :客户端断开连接时触发

每个回调事件都会携带丰富的上下文信息,例如:

{
  "action": "on_publish",
  "client_id": "1985",
  "ip": "192.168.1.10",
  "vhost": "video.test.com",
  "app": "live",
  "stream": "livestream",
  "param": "?token=xxx&salt=yyy"
}

2. SpringBoot项目架构设计

为了实现一个完整的流媒体业务系统,我们需要设计合理的项目结构。以下是推荐的SpringBoot项目模块划分:

src/main/java
├── com.example.live
│   ├── config          # 配置类
│   ├── constant        # 常量定义
│   ├── controller      # 控制器
│   ├── callbackbean    # 回调数据结构
│   ├── service         # 业务服务
│   └── utils           # 工具类
src/main/resources
├── static              # 静态资源
└── templates           # 模板文件

2.1 核心依赖配置

在pom.xml中需要添加以下关键依赖:

<dependencies>
    <!-- SpringBoot Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <!-- JWT支持 -->
    <dependency>
        <groupId>com.auth0</groupId>
        <artifactId>java-jwt</artifactId>
        <version>3.18.2</version>
    </dependency>
    
    <!-- 日志记录 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-log4j2</artifactId>
    </dependency>
</dependencies>

3. JWT鉴权方案实现

在流媒体系统中,安全认证是必不可少的环节。我们采用JWT(JSON Web Token)作为鉴权方案,它具有无状态、易扩展的特点,非常适合分布式系统。

3.1 Token工具类实现

public class TokenUtils {
    private static final long EXPIRE_TIME = 60 * 60 * 1000; // 1小时有效
    private static final String SECRET = "your-secret-key"; // 加密密钥

    /**
     * 生成签名
     */
    public static String sign(String username) {
        Date expireDate = new Date(System.currentTimeMillis() + EXPIRE_TIME);
        Algorithm algorithm = Algorithm.HMAC256(SECRET);
        return JWT.create()
                .withIssuer("srs-auth") // 签发者
                .withClaim("username", username) // 自定义声明
                .withExpiresAt(expireDate) // 过期时间
                .sign(algorithm);
    }

    /**
     * 验证Token
     */
    public static boolean verify(String token, String username) {
        try {
            Algorithm algorithm = Algorithm.HMAC256(SECRET);
            JWTVerifier verifier = JWT.require(algorithm)
                    .withClaim("username", username)
                    .build();
            verifier.verify(token);
            return true;
        } catch (Exception e) {
            return false;
        }
    }
}

3.2 推流URL生成

推流URL需要包含Token参数,供后续鉴权使用:

@Service
public class StreamServiceImpl implements StreamService {
    @Override
    public String generateURL(String username) {
        String token = TokenUtils.sign(username);
        String stream = UUID.randomUUID().toString();
        return "rtmp://your-srs-server/live/" + stream + "?token=" + token;
    }
}

4. 核心回调接口实现

4.1 连接回调(on_connect)

@PostMapping("/connect")
public int onConnect(@RequestBody CallBackDataOnConnect data) {
    // 验证app名称是否正确
    if(!"live".equals(data.getApp())) {
        log.warn("Invalid app name: {}", data.getApp());
        return 1; // 拒绝连接
    }
    
    log.info("Client connected - IP: {}, Vhost: {}, App: {}", 
            data.getIp(), data.getVhost(), data.getApp());
    return 0; // 允许连接
}

4.2 推流回调(on_publish)

@PostMapping("/publish")
public int onPublish(@RequestBody CallBackDataOnPublish data) {
    // 1. 验证app名称
    if(!"live".equals(data.getApp())) {
        log.warn("Publish rejected - invalid app: {}", data.getApp());
        return 1;
    }
    
    // 2. 提取并验证Token
    String token = extractToken(data.getParam());
    if(token == null || !TokenUtils.verify(token, "expected-username")) {
        log.warn("Publish rejected - invalid token");
        return 1;
    }
    
    log.info("Stream published - Stream: {}, Client: {}", 
            data.getStream(), data.getIp());
    return 0;
}

private String extractToken(String param) {
    if(param == null || !param.startsWith("?token=")) {
        return null;
    }
    return param.substring(7); // 截取?token=后面的部分
}

4.3 播放回调(on_play)

@PostMapping("/play")
public int onPlay(@RequestBody CallBackDataOnPlay data) {
    // 1. 基础验证
    if(!"live".equals(data.getApp())) {
        log.warn("Play rejected - invalid app: {}", data.getApp());
        return 1;
    }
    
    // 2. Token验证
    String token = extractToken(data.getParam());
    if(token == null || !TokenUtils.verify(token, "expected-username")) {
        log.warn("Play rejected - invalid token");
        return 1;
    }
    
    // 3. 记录播放行为
    log.info("Stream playing - Stream: {}, Client: {}, Page: {}", 
            data.getStream(), data.getIp(), data.getPageUrl());
    return 0;
}

4.4 停止播放回调(on_stop)

@PostMapping("/stop")
public int onStop(@RequestBody String rawData) {
    // 解析JSON数据
    CallBackDataOnStop data = parseStopData(rawData);
    
    log.info("Play stopped - Stream: {}, Duration: {}", 
            data.getStream(), data.getDuration());
    return 0;
}

4.5 断开连接回调(on_close)

@PostMapping("/close")
public int onClose(@RequestBody CallBackDataOnClose data) {
    log.info("Client disconnected - IP: {}, Bytes sent: {}, Bytes received: {}", 
            data.getIp(), data.getSendBytes(), data.getRecvBytes());
    return 0;
}

5. SRS服务配置

要让SRS能够正确调用我们的SpringBoot接口,需要在SRS配置文件中进行相应设置:

vhost __defaultVhost__ {
    http_hooks {
        enabled on;
        on_connect http://your-springboot-server:8080/connect;
        on_close http://your-springboot-server:8080/close;
        on_publish http://your-springboot-server:8080/publish;
        on_unpublish http://your-springboot-server:8080/unpublish;
        on_play http://your-springboot-server:8080/play;
        on_stop http://your-springboot-server:8080/stop;
    }
}

6. 日志记录与监控

完善的日志系统对于流媒体服务至关重要。我们采用Log4j2进行日志记录,并设计了以下日志规范:

  1. 访问日志 :记录所有客户端连接、断开事件
  2. 推流日志 :记录推流开始、结束事件及推流质量
  3. 播放日志 :记录播放开始、结束事件及播放质量
  4. 异常日志 :记录所有鉴权失败、异常情况

日志配置示例(log4j2.xml):

<Configuration>
    <Appenders>
        <RollingFile name="FileAppender" fileName="logs/app.log"
                     filePattern="logs/app-%d{yyyy-MM-dd}.log.gz">
            <PatternLayout>
                <Pattern>%d{yyyy-MM-dd HH:mm:ss} [%t] %-5level %logger{36} - %msg%n</Pattern>
            </PatternLayout>
            <Policies>
                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
            </Policies>
        </RollingFile>
    </Appenders>
    <Loggers>
        <Logger name="com.example.live.controller" level="info" additivity="false">
            <AppenderRef ref="FileAppender"/>
        </Logger>
        <Root level="error">
            <AppenderRef ref="FileAppender"/>
        </Root>
    </Loggers>
</Configuration>

7. 性能优化与安全建议

在实际部署时,还需要考虑以下优化和安全措施:

  1. 接口性能优化

    • 使用缓存减少数据库查询
    • 异步处理非关键日志记录
    • 合理设置HTTP超时时间
  2. 安全加固

    • 启用HTTPS加密通信
    • 实现IP白名单机制
    • 定期轮换JWT密钥
  3. 高可用设计

    • 部署多个SRS边缘节点
    • 实现回调服务的负载均衡
    • 建立完善的监控告警系统

通过以上实现,我们构建了一个完整的流媒体业务系统,具备完善的鉴权机制和操作日志记录功能。这套方案已经在多个实际项目中得到验证,能够稳定支持高并发的流媒体服务需求。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐