本文目录导读:

我将为您提供一个完整的Java直播系统案例,包括核心功能模块和代码实现。
系统架构
直播系统
├── 用户模块(登录、注册、个人信息)
├── 直播模块(开播、观看、互动)
├── 礼物系统
├── 聊天系统
├── 支付系统
└── 管理后台
核心技术栈
- 后端:Spring Boot + MyBatis Plus
- 实时通信:Netty + WebSocket
- 数据库:MySQL + Redis
- 视频处理:FFmpeg + HLS
- 前端:Vue.js + Element UI
核心代码实现
项目结构
com.live.broadcast/
├── controller/
│ ├── UserController.java
│ ├── LiveController.java
│ └── GiftController.java
├── service/
│ ├── UserService.java
│ ├── LiveService.java
│ └── MessageService.java
├── netty/
│ ├── NettyServer.java
│ └── WebSocketHandler.java
├── entity/
│ ├── User.java
│ ├── LiveRoom.java
│ └── GiftRecord.java
└── config/
└── MybatisPlusConfig.java
用户模块
@RestController
@RequestMapping("/api/user")
public class UserController {
@Autowired
private UserService userService;
@PostMapping("/login")
public Result login(@RequestBody LoginRequest request) {
String token = userService.login(request);
return Result.success(token);
}
@PostMapping("/register")
public Result register(@RequestBody User user) {
userService.register(user);
return Result.success();
}
@GetMapping("/info/{userId}")
public Result getUserInfo(@PathVariable Long userId) {
User user = userService.getUserInfo(userId);
return Result.success(user);
}
}
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Override
public String login(LoginRequest request) {
// 验证用户信息
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("username", request.getUsername());
wrapper.eq("password", MD5Util.encrypt(request.getPassword()));
User user = userMapper.selectOne(wrapper);
if (user == null) {
throw new BusinessException("用户名或密码错误");
}
// 生成token并存储到Redis
String token = UUID.randomUUID().toString().replace("-", "");
redisTemplate.opsForValue().set("token:" + token, user.getId(), 24, TimeUnit.HOURS);
return token;
}
@Override
public void updateUserStatus(Long userId, boolean online) {
// 更新用户在线状态
User user = userMapper.selectById(userId);
user.setOnline(online);
userMapper.updateById(user);
}
}
直播模块
@RestController
@RequestMapping("/api/live")
public class LiveController {
@Autowired
private LiveService liveService;
@PostMapping("/start")
public Result startLive(@RequestBody StartLiveRequest request) {
LiveRoom room = liveService.startLive(request);
return Result.success(room);
}
@PostMapping("/stop/{roomId}")
public Result stopLive(@PathVariable Long roomId) {
liveService.stopLive(roomId);
return Result.success();
}
@GetMapping("/info/{roomId}")
public Result getLiveInfo(@PathVariable Long roomId) {
LiveRoom room = liveService.getLiveInfo(roomId);
return Result.success(room);
}
@GetMapping("/hot")
public Result getHotLives() {
List<LiveRoom> rooms = liveService.getHotLives();
return Result.success(rooms);
}
}
@Service
public class LiveServiceImpl implements LiveService {
@Autowired
private LiveRoomMapper liveRoomMapper;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Override
@Transactional
public LiveRoom startLive(StartLiveRequest request) {
// 生成推流地址
String pushUrl = generatePushUrl(request.getUserId());
// 创建直播房间
LiveRoom room = new LiveRoom();
room.setAnchorId(request.getUserId());
room.setTitle(request.getTitle());
room.setCoverUrl(request.getCoverUrl());
room.setPushUrl(pushUrl);
room.setStreamKey(UUID.randomUUID().toString().replace("-", ""));
room.setStatus(1); // 1-直播中
room.setCreateTime(LocalDateTime.now());
liveRoomMapper.insert(room);
// 缓存到Redis
String roomKey = "live_room:" + room.getId();
redisTemplate.opsForValue().set(roomKey, room, 4, TimeUnit.HOURS);
// 异步启动FFmpeg推流
startFFmpegProcess(room);
return room;
}
@Override
public void stopLive(Long roomId) {
LiveRoom room = liveRoomMapper.selectById(roomId);
room.setStatus(0);
room.setEndTime(LocalDateTime.now());
liveRoomMapper.updateById(room);
// 清除Redis缓存
redisTemplate.delete("live_room:" + roomId);
// 停止FFmpeg进程
stopFFmpegProcess(roomId);
}
private String generatePushUrl(Long userId) {
String rtmpHost = "rtmp://live.example.com/live/";
return rtmpHost + userId + "_" + System.currentTimeMillis();
}
}
实时通信(Netty + WebSocket)
@Component
public class WebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
private static final ConcurrentHashMap<Long, Channel> USER_CHANNELS = new ConcurrentHashMap<>();
@Autowired
private MessageService messageService;
@Override
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame frame) {
String message = frame.text();
JSONObject jsonObject = JSON.parseObject(message);
switch (jsonObject.getInteger("type")) {
case 1: // 聊天消息
handleChatMessage(ctx, jsonObject);
break;
case 2: // 礼物消息
handleGiftMessage(ctx, jsonObject);
break;
case 3: // 进入直播间
handleEnterRoom(ctx, jsonObject);
break;
case 4: // 退出直播间
handleExitRoom(ctx, jsonObject);
break;
}
}
private void handleChatMessage(ChannelHandlerContext ctx, JSONObject message) {
Long roomId = message.getLong("roomId");
Long userId = message.getLong("userId");
String content = message.getString("content");
// 存储消息到Redis
ChatMessage chatMessage = new ChatMessage();
chatMessage.setRoomId(roomId);
chatMessage.setUserId(userId);
chatMessage.setContent(content);
chatMessage.setTime(LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
// 广播给直播间所有用户
broadcastToRoom(roomId, JSON.toJSONString(chatMessage));
}
private void handleGiftMessage(ChannelHandlerContext ctx, JSONObject message) {
Long roomId = message.getLong("roomId");
Long userId = message.getLong("userId");
String giftId = message.getString("giftId");
// 验证用户余额和礼物信息
boolean success = messageService.sendGift(roomId, userId, giftId);
if (success) {
GiftMessage giftMessage = new GiftMessage();
giftMessage.setType("gift");
giftMessage.setUserId(userId);
giftMessage.setGiftId(giftId);
giftMessage.setContent("赠送了礼物");
// 广播礼物消息
broadcastToRoom(roomId, JSON.toJSONString(giftMessage));
}
}
private void broadcastToRoom(Long roomId, String message) {
for (Map.Entry<Long, Channel> entry : USER_CHANNELS.entrySet()) {
// 这里需要维护roomId与Channel的映射关系
if (entry.getValue().isActive()) {
entry.getValue().writeAndFlush(new TextWebSocketFrame(message));
}
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
ctx.close();
}
}
礼物系统
@Service
public class GiftServiceImpl implements GiftService {
@Autowired
private GiftMapper giftMapper;
@Autowired
private UserService userService;
@Override
@Transactional
public boolean sendGift(Long roomId, Long userId, String giftId) {
// 获取礼物信息
Gift gift = giftMapper.selectById(giftId);
if (gift == null) {
return false;
}
// 扣减用户余额
User user = userService.getUserById(userId);
if (user.getBalance() < gift.getPrice()) {
throw new BusinessException("余额不足");
}
// 记录礼物消费记录
GiftRecord record = new GiftRecord();
record.setUserId(userId);
record.setRoomId(roomId);
record.setGiftId(giftId);
record.setPrice(gift.getPrice());
record.setTime(LocalDateTime.now());
// 保存记录并扣款
giftRecordMapper.insert(record);
user.setBalance(user.getBalance() - gift.getPrice());
userService.updateUser(user);
// 更新主播收入
LiveRoom room = liveRoomMapper.selectById(roomId);
if (room != null) {
User anchor = userService.getUserById(room.getAnchorId());
anchor.setIncome(anchor.getIncome() + gift.getPrice() * 0.7);
userService.updateUser(anchor);
}
return true;
}
}
@Entity
@Table(name = "gift")
public class Gift {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private BigDecimal price;
private String iconUrl;
private boolean isVip;
// getter/setter...
}
配置类
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
@Bean
public NettyServer nettyServer() {
return new NettyServer(8088);
}
}
@Component
public class NettyServer {
private final int port;
private final ServerBootstrap bootstrap;
public NettyServer(int port) {
this.port = port;
this.bootstrap = new ServerBootstrap();
}
@PostConstruct
public void start() throws InterruptedException {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ch.pipeline()
.addLast(new HttpServerCodec())
.addLast(new HttpObjectAggregator(65536))
.addLast(new WebSocketServerProtocolHandler("/ws"))
.addLast(new WebSocketHandler());
}
});
bootstrap.bind(port).sync();
log.info("Netty server started on port: {}", port);
}
}
数据库设计
-- 用户表
CREATE TABLE `user` (
`id` bigint NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL,
`password` varchar(100) NOT NULL,
`nickname` varchar(50) DEFAULT NULL,
`avatar` varchar(200) DEFAULT NULL,
`balance` decimal(10,2) DEFAULT '0.00',
`income` decimal(10,2) DEFAULT '0.00',
`level` int DEFAULT '1',
`online` tinyint(1) DEFAULT '0',
PRIMARY KEY (`id`)
);
-- 直播间表
CREATE TABLE `live_room` (
`id` bigint NOT NULL AUTO_INCREMENT,
`anchor_id` bigint NOT NULL, varchar(100) NOT NULL,
`cover_url` varchar(200) DEFAULT NULL,
`status` tinyint DEFAULT '0',
`push_url` varchar(200) DEFAULT NULL,
`stream_key` varchar(100) DEFAULT NULL,
`viewer_count` int DEFAULT '0',
`create_time` datetime DEFAULT NULL,
`end_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
);
-- 礼物表
CREATE TABLE `gift` (
`id` varchar(50) NOT NULL,
`name` varchar(50) NOT NULL,
`price` decimal(10,2) NOT NULL DEFAULT '0.00',
`icon_url` varchar(200) DEFAULT NULL,
`is_vip` tinyint(1) DEFAULT '0',
PRIMARY KEY (`id`)
);
-- 礼物记录表
CREATE TABLE `gift_record` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint NOT NULL,
`room_id` bigint NOT NULL,
`gift_id` varchar(50) NOT NULL,
`price` decimal(10,2) NOT NULL,
`time` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
);
-- 聊天记录表
CREATE TABLE `chat_record` (
`id` bigint NOT NULL AUTO_INCREMENT,
`room_id` bigint NOT NULL,
`user_id` bigint NOT NULL,
`content` varchar(500) DEFAULT NULL,
`time` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
);
使用说明
启动系统
# 安装依赖 mvn clean install # 启动应用 java -jar live-broadcast.jar
配置环境
- 修改
application.yml中的数据库连接 - 配置Redis连接地址
- 设置FFmpeg路径
系统优化建议
- 高并发处理:使用消息队列(RabbitMQ/Kafka)处理聊天和礼物消息
- CDN加速:集成CDN分发视频流
- 缓存优化:使用Redis缓存热点数据
- 监控告警:集成Prometheus监控系统状态
这个案例涵盖了直播系统的主要功能模块,您可以根据实际需求进行扩展和优化。