From d1fc3b0df5c35b6f47f2742001f8c8a3693501af Mon Sep 17 00:00:00 2001 From: "xiao12feng@outlook.com" Date: Thu, 18 Dec 2025 14:52:20 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B0=86=E9=A1=B9=E7=9B=AE=E6=94=B9=E6=88=90ja?= =?UTF-8?q?va=E7=A8=8B=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../example/livestreaming/ChatAdapter.java | 78 +++++ .../example/livestreaming/ChatMessage.java | 54 ++++ .../main/res/drawable/ic_arrow_back_24.xml | 10 + .../res/layout/activity_room_detail_new.xml | 289 ++++++++++++++++++ .../main/res/layout/dialog_stream_info.xml | 152 +++++++++ .../src/main/res/layout/item_chat_message.xml | 36 +++ java-backend/README.md | 126 ++++++++ java-backend/data/livestream.mv.db | Bin 0 -> 16384 bytes java-backend/pom.xml | 88 ++++++ .../LivestreamingApplication.java | 17 ++ .../livestreaming/config/WebConfig.java | 18 ++ .../controller/HealthController.java | 20 ++ .../controller/RoomController.java | 64 ++++ .../controller/SrsCallbackController.java | 84 +++++ .../livestreaming/dto/ApiResponse.java | 27 ++ .../livestreaming/dto/CreateRoomRequest.java | 14 + .../livestreaming/dto/RoomResponse.java | 47 +++ .../example/livestreaming/entity/Room.java | 53 ++++ .../exception/GlobalExceptionHandler.java | 32 ++ .../repository/RoomRepository.java | 13 + .../livestreaming/service/RoomService.java | 89 ++++++ .../src/main/resources/application.yml | 50 +++ java-backend/target/classes/application.yml | 50 +++ .../LivestreamingApplication.class | Bin 0 -> 1159 bytes .../livestreaming/config/WebConfig.class | Bin 0 -> 1461 bytes .../controller/HealthController.class | Bin 0 -> 1197 bytes .../controller/RoomController.class | Bin 0 -> 4706 bytes .../controller/SrsCallbackController.class | Bin 0 -> 3488 bytes .../livestreaming/dto/ApiResponse.class | Bin 0 -> 4331 bytes .../livestreaming/dto/CreateRoomRequest.class | Bin 0 -> 2667 bytes .../dto/RoomResponse$StreamUrls.class | Bin 0 -> 2893 bytes .../livestreaming/dto/RoomResponse.class | Bin 0 -> 7087 bytes .../example/livestreaming/entity/Room.class | Bin 0 -> 6206 bytes .../exception/GlobalExceptionHandler.class | Bin 0 -> 3935 bytes .../repository/RoomRepository.class | Bin 0 -> 651 bytes .../livestreaming/service/RoomService.class | Bin 0 -> 5187 bytes live-streaming/docker/srs/srs-emulator.conf | 92 ++++++ 模拟器优化指南.md | 89 ++++++ 38 files changed, 1592 insertions(+) create mode 100644 android-app/app/src/main/java/com/example/livestreaming/ChatAdapter.java create mode 100644 android-app/app/src/main/java/com/example/livestreaming/ChatMessage.java create mode 100644 android-app/app/src/main/res/drawable/ic_arrow_back_24.xml create mode 100644 android-app/app/src/main/res/layout/activity_room_detail_new.xml create mode 100644 android-app/app/src/main/res/layout/dialog_stream_info.xml create mode 100644 android-app/app/src/main/res/layout/item_chat_message.xml create mode 100644 java-backend/README.md create mode 100644 java-backend/data/livestream.mv.db create mode 100644 java-backend/pom.xml create mode 100644 java-backend/src/main/java/com/example/livestreaming/LivestreamingApplication.java create mode 100644 java-backend/src/main/java/com/example/livestreaming/config/WebConfig.java create mode 100644 java-backend/src/main/java/com/example/livestreaming/controller/HealthController.java create mode 100644 java-backend/src/main/java/com/example/livestreaming/controller/RoomController.java create mode 100644 java-backend/src/main/java/com/example/livestreaming/controller/SrsCallbackController.java create mode 100644 java-backend/src/main/java/com/example/livestreaming/dto/ApiResponse.java create mode 100644 java-backend/src/main/java/com/example/livestreaming/dto/CreateRoomRequest.java create mode 100644 java-backend/src/main/java/com/example/livestreaming/dto/RoomResponse.java create mode 100644 java-backend/src/main/java/com/example/livestreaming/entity/Room.java create mode 100644 java-backend/src/main/java/com/example/livestreaming/exception/GlobalExceptionHandler.java create mode 100644 java-backend/src/main/java/com/example/livestreaming/repository/RoomRepository.java create mode 100644 java-backend/src/main/java/com/example/livestreaming/service/RoomService.java create mode 100644 java-backend/src/main/resources/application.yml create mode 100644 java-backend/target/classes/application.yml create mode 100644 java-backend/target/classes/com/example/livestreaming/LivestreamingApplication.class create mode 100644 java-backend/target/classes/com/example/livestreaming/config/WebConfig.class create mode 100644 java-backend/target/classes/com/example/livestreaming/controller/HealthController.class create mode 100644 java-backend/target/classes/com/example/livestreaming/controller/RoomController.class create mode 100644 java-backend/target/classes/com/example/livestreaming/controller/SrsCallbackController.class create mode 100644 java-backend/target/classes/com/example/livestreaming/dto/ApiResponse.class create mode 100644 java-backend/target/classes/com/example/livestreaming/dto/CreateRoomRequest.class create mode 100644 java-backend/target/classes/com/example/livestreaming/dto/RoomResponse$StreamUrls.class create mode 100644 java-backend/target/classes/com/example/livestreaming/dto/RoomResponse.class create mode 100644 java-backend/target/classes/com/example/livestreaming/entity/Room.class create mode 100644 java-backend/target/classes/com/example/livestreaming/exception/GlobalExceptionHandler.class create mode 100644 java-backend/target/classes/com/example/livestreaming/repository/RoomRepository.class create mode 100644 java-backend/target/classes/com/example/livestreaming/service/RoomService.class create mode 100644 live-streaming/docker/srs/srs-emulator.conf create mode 100644 模拟器优化指南.md diff --git a/android-app/app/src/main/java/com/example/livestreaming/ChatAdapter.java b/android-app/app/src/main/java/com/example/livestreaming/ChatAdapter.java new file mode 100644 index 00000000..0b8c9179 --- /dev/null +++ b/android-app/app/src/main/java/com/example/livestreaming/ChatAdapter.java @@ -0,0 +1,78 @@ +package com.example.livestreaming; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.DiffUtil; +import androidx.recyclerview.widget.ListAdapter; +import androidx.recyclerview.widget.RecyclerView; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; + +public class ChatAdapter extends ListAdapter { + + private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm", Locale.getDefault()); + + public ChatAdapter() { + super(DIFF_CALLBACK); + } + + @NonNull + @Override + public ChatViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.item_chat_message, parent, false); + return new ChatViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull ChatViewHolder holder, int position) { + holder.bind(getItem(position)); + } + + static class ChatViewHolder extends RecyclerView.ViewHolder { + private final TextView usernameText; + private final TextView messageText; + private final TextView timeText; + + public ChatViewHolder(@NonNull View itemView) { + super(itemView); + usernameText = itemView.findViewById(R.id.usernameText); + messageText = itemView.findViewById(R.id.messageText); + timeText = itemView.findViewById(R.id.timeText); + } + + public void bind(ChatMessage message) { + if (message.isSystemMessage()) { + usernameText.setVisibility(View.GONE); + messageText.setText(message.getMessage()); + messageText.setTextColor(itemView.getContext().getColor(R.color.purple_500)); + } else { + usernameText.setVisibility(View.VISIBLE); + usernameText.setText(message.getUsername() + ":"); + messageText.setText(message.getMessage()); + messageText.setTextColor(itemView.getContext().getColor(android.R.color.black)); + } + + timeText.setText(TIME_FORMAT.format(new Date(message.getTimestamp()))); + } + } + + private static final DiffUtil.ItemCallback DIFF_CALLBACK = new DiffUtil.ItemCallback() { + @Override + public boolean areItemsTheSame(@NonNull ChatMessage oldItem, @NonNull ChatMessage newItem) { + // 使用时间戳作为唯一标识 + return oldItem.getTimestamp() == newItem.getTimestamp(); + } + + @Override + public boolean areContentsTheSame(@NonNull ChatMessage oldItem, @NonNull ChatMessage newItem) { + return oldItem.getTimestamp() == newItem.getTimestamp(); + } + }; +} \ No newline at end of file diff --git a/android-app/app/src/main/java/com/example/livestreaming/ChatMessage.java b/android-app/app/src/main/java/com/example/livestreaming/ChatMessage.java new file mode 100644 index 00000000..5278445a --- /dev/null +++ b/android-app/app/src/main/java/com/example/livestreaming/ChatMessage.java @@ -0,0 +1,54 @@ +package com.example.livestreaming; + +public class ChatMessage { + private String username; + private String message; + private long timestamp; + private boolean isSystemMessage; + + public ChatMessage(String username, String message) { + this.username = username; + this.message = message; + this.timestamp = System.currentTimeMillis(); + this.isSystemMessage = false; + } + + public ChatMessage(String message, boolean isSystemMessage) { + this.message = message; + this.timestamp = System.currentTimeMillis(); + this.isSystemMessage = isSystemMessage; + this.username = isSystemMessage ? "系统" : "匿名用户"; + } + + public String getUsername() { + return username; + } + + public String getMessage() { + return message; + } + + public long getTimestamp() { + return timestamp; + } + + public boolean isSystemMessage() { + return isSystemMessage; + } + + public void setUsername(String username) { + this.username = username; + } + + public void setMessage(String message) { + this.message = message; + } + + public void setTimestamp(long timestamp) { + this.timestamp = timestamp; + } + + public void setSystemMessage(boolean systemMessage) { + isSystemMessage = systemMessage; + } +} \ No newline at end of file diff --git a/android-app/app/src/main/res/drawable/ic_arrow_back_24.xml b/android-app/app/src/main/res/drawable/ic_arrow_back_24.xml new file mode 100644 index 00000000..e2e8516f --- /dev/null +++ b/android-app/app/src/main/res/drawable/ic_arrow_back_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/android-app/app/src/main/res/layout/activity_room_detail_new.xml b/android-app/app/src/main/res/layout/activity_room_detail_new.xml new file mode 100644 index 00000000..47f3634f --- /dev/null +++ b/android-app/app/src/main/res/layout/activity_room_detail_new.xml @@ -0,0 +1,289 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android-app/app/src/main/res/layout/dialog_stream_info.xml b/android-app/app/src/main/res/layout/dialog_stream_info.xml new file mode 100644 index 00000000..4d6d56c5 --- /dev/null +++ b/android-app/app/src/main/res/layout/dialog_stream_info.xml @@ -0,0 +1,152 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android-app/app/src/main/res/layout/item_chat_message.xml b/android-app/app/src/main/res/layout/item_chat_message.xml new file mode 100644 index 00000000..712fd214 --- /dev/null +++ b/android-app/app/src/main/res/layout/item_chat_message.xml @@ -0,0 +1,36 @@ + + + + + + + + + + \ No newline at end of file diff --git a/java-backend/README.md b/java-backend/README.md new file mode 100644 index 00000000..bfee9255 --- /dev/null +++ b/java-backend/README.md @@ -0,0 +1,126 @@ +# 直播系统 Java 后端 + +基于 Spring Boot 3.2 + JPA + H2/MySQL 的直播系统后端服务。 + +## 技术栈 + +- Java 17 +- Spring Boot 3.2 +- Spring Data JPA +- H2 Database (开发) / MySQL (生产) +- Lombok + +## 快速开始 + +### 1. 使用 IDEA 打开项目 + +1. 打开 IntelliJ IDEA +2. 选择 `File` -> `Open` +3. 选择 `java-backend` 文件夹 +4. 等待 Maven 依赖下载完成 + +### 2. 运行项目 + +方式一:直接运行 +- 找到 `LivestreamingApplication.java` +- 右键点击 -> `Run 'LivestreamingApplication'` + +方式二:Maven 命令 +```bash +cd java-backend +mvn spring-boot:run +``` + +### 3. 访问服务 + +- API 地址: http://localhost:3001/api +- H2 控制台: http://localhost:3001/h2-console + - JDBC URL: `jdbc:h2:file:./data/livestream` + - 用户名: `sa` + - 密码: (空) + +## API 接口 + +### 直播间接口 + +| 方法 | 路径 | 说明 | +|------|------|------| +| GET | /api/rooms | 获取所有直播间 | +| GET | /api/rooms/{id} | 获取单个直播间 | +| POST | /api/rooms | 创建直播间 | +| DELETE | /api/rooms/{id} | 删除直播间 | + +### 创建直播间请求示例 + +```json +POST /api/rooms +{ + "title": "我的直播间", + "streamerName": "主播名称" +} +``` + +### 响应示例 + +```json +{ + "success": true, + "data": { + "id": "uuid", + "title": "我的直播间", + "streamerName": "主播名称", + "streamKey": "uuid", + "isLive": false, + "viewerCount": 0, + "streamUrls": { + "rtmp": "rtmp://localhost:1935/live/uuid", + "flv": "http://localhost:8080/live/uuid.flv", + "hls": "http://localhost:8080/live/uuid/index.m3u8" + } + } +} +``` + +## 切换到 MySQL + +1. 安装 MySQL 并创建数据库: +```sql +CREATE DATABASE livestream CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +``` + +2. 修改 `application.yml`: +```yaml +spring: + datasource: + url: jdbc:mysql://localhost:3306/livestream?useSSL=false&serverTimezone=Asia/Shanghai + driver-class-name: com.mysql.cj.jdbc.Driver + username: root + password: your_password +``` + +## 项目结构 + +``` +java-backend/ +├── src/main/java/com/example/livestreaming/ +│ ├── LivestreamingApplication.java # 启动类 +│ ├── config/ # 配置类 +│ ├── controller/ # 控制器 +│ ├── dto/ # 数据传输对象 +│ ├── entity/ # 实体类 +│ ├── exception/ # 异常处理 +│ ├── repository/ # 数据访问层 +│ └── service/ # 业务逻辑层 +├── src/main/resources/ +│ └── application.yml # 配置文件 +└── pom.xml # Maven 配置 +``` + +## Android App 配置 + +确保 Android App 的 API 地址指向此服务: + +```kotlin +// build.gradle.kts +buildConfigField("String", "API_BASE_URL_DEVICE", "\"http://你的电脑IP:3001/api/\"") +``` diff --git a/java-backend/data/livestream.mv.db b/java-backend/data/livestream.mv.db new file mode 100644 index 0000000000000000000000000000000000000000..1473e2fc5731ae3d3b9f78c57a374430030801ce GIT binary patch literal 16384 zcmeHO%Wm676eZ<2R-L#73KR&sC>X_#ZGaW{5TC2i5)(1e!OfAj;o=;e@tRv>3kb+&TBma4ruys+$t;4aWW} zQ%<(s!Jk_a_)dI50)SlQ0%l9146mo(xe0bk38K4Y&R0ir)|9@2e zY2%au$^d16GC&!i3{VCr1C#;E0A+wOKpFTi7)Up*PsZcOlyq1VAc&}92z+!r#Ad}0WHkNmkN6S3h)ji##=fabKKCJiu!3Oa%az}l z!d<2)%NkMxStu&HAbWBs78L`jNYKQhs>xVWJ-MiRy60Euo+K!t z7X(FLMY8H^N)f9S5eo_~V&q{#(kjKUQqhEpp+6W;j><2k^6>mL8c%RJ@XD>T!Qc=L z&TtJy$h|zl{DMt%Q9ewTLg0WH#zvcukq;g&w-$`SVQ2hp<|?zgwVHJNqICqG%6pd) zwk8vFnblebdtATPWOOxrKNL!p3-Xl!1FEc9x0cog6M1$|tk??48^% zgI8K$2$*k-y)Fy}xYL^GWOHvY_BuGk6Flr1dUE%{KM*?)1+#Iya6r?`2+bgVjtn*3Gf5HJ|)LzqhQ<^AC<-V zLy2HC`=k+bv5|*yRWogbe}ym2?C$5!(ZI~+jV)r9Airvr%>lcKvnj5tmf7?(^#{=t zt86|Yt!C3!0xl)*W@l3zM6VtwRj?( ztM(NEIqjo`it_wrY4I{Eu|6BH2CK3OQ&~^9#@Y}@ zefc=J9Veu+m~5nEqx`}qsZ5_PXPZxHgX43&PtseQ){^uVpKHQ?mgv$HmgKuQ-Oc4Y zF^~{{Haa_nHE>DtOAf3k>ltI34)rV#@?+mWJ3%A=(iF5GQ;bH6u`-u-C4PocBp6jl z!5PNNG#$^Ogwudh(x+Kj3>7U!c^xo=08?c@4@_dkJ4t>QLU5MtONLRoBas$_cpv7; z*9^1!18g5L%%7jb7CP(KH)Kl-$^d16GC&!i3{VD^W?)`_XRGMZY!$sG1WF+3A68IL z#rb;DzPIoo5`uRBHKDcTu)B`i zSlal|vA+TT)poB1p*k(seqwjlcQ?8F8+OXpd6!D;KJZ&9x51>=)Pdk0m+Kr*+`!;FivD4|g zyW0@{&+Plf|28-!u17-JDdk$;eZPoeJ-8~fGd*aedJrw82me`XC3-NgwX%tCX{7~v zFrEw-_26uJyjFI}nI1%`9s~tA_ZoJl2c;{TRhv%Fb11KWOM5dgbe&MGyiE_1Jdo-^ z_ED+_p_6vrB!3Xg#4y$W@q6&R%%=K(>Bk~!NEx6EPzERil!5;>1M~Vj)Bm}D(Q63A KXXRhh|9=Bprb-0> literal 0 HcmV?d00001 diff --git a/java-backend/pom.xml b/java-backend/pom.xml new file mode 100644 index 00000000..c88f930c --- /dev/null +++ b/java-backend/pom.xml @@ -0,0 +1,88 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 3.2.0 + + + + com.example + livestreaming-backend + 1.0.0 + livestreaming-backend + 直播系统后端服务 + + + 17 + + + + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + + com.mysql + mysql-connector-j + runtime + + + + + com.h2database + h2 + runtime + + + + + org.projectlombok + lombok + true + + + + + org.springframework.boot + spring-boot-starter-validation + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + diff --git a/java-backend/src/main/java/com/example/livestreaming/LivestreamingApplication.java b/java-backend/src/main/java/com/example/livestreaming/LivestreamingApplication.java new file mode 100644 index 00000000..a43a87e4 --- /dev/null +++ b/java-backend/src/main/java/com/example/livestreaming/LivestreamingApplication.java @@ -0,0 +1,17 @@ +package com.example.livestreaming; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class LivestreamingApplication { + + public static void main(String[] args) { + SpringApplication.run(LivestreamingApplication.class, args); + System.out.println("========================================"); + System.out.println(" 直播系统后端服务启动成功!"); + System.out.println(" API地址: http://localhost:3001/api"); + System.out.println(" H2控制台: http://localhost:3001/h2-console"); + System.out.println("========================================"); + } +} diff --git a/java-backend/src/main/java/com/example/livestreaming/config/WebConfig.java b/java-backend/src/main/java/com/example/livestreaming/config/WebConfig.java new file mode 100644 index 00000000..f61c87e5 --- /dev/null +++ b/java-backend/src/main/java/com/example/livestreaming/config/WebConfig.java @@ -0,0 +1,18 @@ +package com.example.livestreaming.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebConfig implements WebMvcConfigurer { + + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") + .allowedOrigins("*") + .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") + .allowedHeaders("*") + .maxAge(3600); + } +} diff --git a/java-backend/src/main/java/com/example/livestreaming/controller/HealthController.java b/java-backend/src/main/java/com/example/livestreaming/controller/HealthController.java new file mode 100644 index 00000000..af0d0c58 --- /dev/null +++ b/java-backend/src/main/java/com/example/livestreaming/controller/HealthController.java @@ -0,0 +1,20 @@ +package com.example.livestreaming.controller; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.time.LocalDateTime; +import java.util.Map; + +@RestController +public class HealthController { + + @GetMapping("/health") + public ResponseEntity> health() { + return ResponseEntity.ok(Map.of( + "status", "ok", + "timestamp", LocalDateTime.now().toString() + )); + } +} diff --git a/java-backend/src/main/java/com/example/livestreaming/controller/RoomController.java b/java-backend/src/main/java/com/example/livestreaming/controller/RoomController.java new file mode 100644 index 00000000..b0f9d4ed --- /dev/null +++ b/java-backend/src/main/java/com/example/livestreaming/controller/RoomController.java @@ -0,0 +1,64 @@ +package com.example.livestreaming.controller; + +import com.example.livestreaming.dto.ApiResponse; +import com.example.livestreaming.dto.CreateRoomRequest; +import com.example.livestreaming.dto.RoomResponse; +import com.example.livestreaming.service.RoomService; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/rooms") +@RequiredArgsConstructor +@CrossOrigin(origins = "*") +public class RoomController { + + private final RoomService roomService; + + /** + * 获取所有直播间 + * GET /api/rooms + */ + @GetMapping + public ResponseEntity>> getAllRooms() { + List rooms = roomService.getAllRooms(); + return ResponseEntity.ok(ApiResponse.success(rooms)); + } + + /** + * 获取单个直播间 + * GET /api/rooms/{id} + */ + @GetMapping("/{id}") + public ResponseEntity> getRoomById(@PathVariable String id) { + return roomService.getRoomById(id) + .map(room -> ResponseEntity.ok(ApiResponse.success(room))) + .orElse(ResponseEntity.notFound().build()); + } + + /** + * 创建直播间 + * POST /api/rooms + */ + @PostMapping + public ResponseEntity> createRoom(@Valid @RequestBody CreateRoomRequest request) { + RoomResponse room = roomService.createRoom(request); + return ResponseEntity.ok(ApiResponse.success(room, "直播间创建成功")); + } + + /** + * 删除直播间 + * DELETE /api/rooms/{id} + */ + @DeleteMapping("/{id}") + public ResponseEntity> deleteRoom(@PathVariable String id) { + if (roomService.deleteRoom(id)) { + return ResponseEntity.ok(ApiResponse.success(null, "删除成功")); + } + return ResponseEntity.notFound().build(); + } +} diff --git a/java-backend/src/main/java/com/example/livestreaming/controller/SrsCallbackController.java b/java-backend/src/main/java/com/example/livestreaming/controller/SrsCallbackController.java new file mode 100644 index 00000000..0ad76528 --- /dev/null +++ b/java-backend/src/main/java/com/example/livestreaming/controller/SrsCallbackController.java @@ -0,0 +1,84 @@ +package com.example.livestreaming.controller; + +import com.example.livestreaming.service.RoomService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.Map; + +/** + * SRS/NodeMediaServer 回调接口 + * 用于接收推流开始/结束的通知 + */ +@Slf4j +@RestController +@RequestMapping("/api/srs") +@RequiredArgsConstructor +@CrossOrigin(origins = "*") +public class SrsCallbackController { + + private final RoomService roomService; + + /** + * 推流开始回调 + * POST /api/srs/on_publish + */ + @PostMapping("/on_publish") + public ResponseEntity> onPublish(@RequestBody Map body) { + String stream = (String) body.get("stream"); + log.info("推流开始: stream={}", stream); + + if (stream != null) { + roomService.setLiveStatus(stream, true); + } + + return ResponseEntity.ok(successResponse()); + } + + /** + * 推流结束回调 + * POST /api/srs/on_unpublish + */ + @PostMapping("/on_unpublish") + public ResponseEntity> onUnpublish(@RequestBody Map body) { + String stream = (String) body.get("stream"); + log.info("推流结束: stream={}", stream); + + if (stream != null) { + roomService.setLiveStatus(stream, false); + } + + return ResponseEntity.ok(successResponse()); + } + + /** + * 播放开始回调 + * POST /api/srs/on_play + */ + @PostMapping("/on_play") + public ResponseEntity> onPlay(@RequestBody Map body) { + String stream = (String) body.get("stream"); + log.info("观众进入: stream={}", stream); + return ResponseEntity.ok(successResponse()); + } + + /** + * 播放结束回调 + * POST /api/srs/on_stop + */ + @PostMapping("/on_stop") + public ResponseEntity> onStop(@RequestBody Map body) { + String stream = (String) body.get("stream"); + log.info("观众离开: stream={}", stream); + return ResponseEntity.ok(successResponse()); + } + + private Map successResponse() { + Map result = new HashMap<>(); + result.put("code", 0); + return result; + } +} diff --git a/java-backend/src/main/java/com/example/livestreaming/dto/ApiResponse.java b/java-backend/src/main/java/com/example/livestreaming/dto/ApiResponse.java new file mode 100644 index 00000000..380968de --- /dev/null +++ b/java-backend/src/main/java/com/example/livestreaming/dto/ApiResponse.java @@ -0,0 +1,27 @@ +package com.example.livestreaming.dto; + +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.AllArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ApiResponse { + + private boolean success; + private T data; + private String message; + + public static ApiResponse success(T data) { + return new ApiResponse<>(true, data, null); + } + + public static ApiResponse success(T data, String message) { + return new ApiResponse<>(true, data, message); + } + + public static ApiResponse error(String message) { + return new ApiResponse<>(false, null, message); + } +} diff --git a/java-backend/src/main/java/com/example/livestreaming/dto/CreateRoomRequest.java b/java-backend/src/main/java/com/example/livestreaming/dto/CreateRoomRequest.java new file mode 100644 index 00000000..a4963b80 --- /dev/null +++ b/java-backend/src/main/java/com/example/livestreaming/dto/CreateRoomRequest.java @@ -0,0 +1,14 @@ +package com.example.livestreaming.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +public class CreateRoomRequest { + + @NotBlank(message = "标题不能为空") + private String title; + + @NotBlank(message = "主播名称不能为空") + private String streamerName; +} diff --git a/java-backend/src/main/java/com/example/livestreaming/dto/RoomResponse.java b/java-backend/src/main/java/com/example/livestreaming/dto/RoomResponse.java new file mode 100644 index 00000000..0aff8ad3 --- /dev/null +++ b/java-backend/src/main/java/com/example/livestreaming/dto/RoomResponse.java @@ -0,0 +1,47 @@ +package com.example.livestreaming.dto; + +import com.example.livestreaming.entity.Room; +import lombok.Data; + +@Data +public class RoomResponse { + + private String id; + private String title; + private String streamerName; + private String streamKey; + private boolean isLive; + private int viewerCount; + private String createdAt; + private String startedAt; + private StreamUrls streamUrls; + + @Data + public static class StreamUrls { + private String rtmp; + private String flv; + private String hls; + } + + public static RoomResponse fromEntity(Room room, String rtmpHost, int rtmpPort, String httpHost, int httpPort) { + RoomResponse response = new RoomResponse(); + response.setId(room.getId()); + response.setTitle(room.getTitle()); + response.setStreamerName(room.getStreamerName()); + response.setStreamKey(room.getStreamKey()); + response.setLive(room.isLive()); + response.setViewerCount(room.getViewerCount()); + response.setCreatedAt(room.getCreatedAt() != null ? room.getCreatedAt().toString() : null); + response.setStartedAt(room.getStartedAt() != null ? room.getStartedAt().toString() : null); + + // 构建流地址 + StreamUrls urls = new StreamUrls(); + String streamKey = room.getStreamKey(); + urls.setRtmp(String.format("rtmp://%s:%d/live/%s", rtmpHost, rtmpPort, streamKey)); + urls.setFlv(String.format("http://%s:%d/live/%s.flv", httpHost, httpPort, streamKey)); + urls.setHls(String.format("http://%s:%d/live/%s/index.m3u8", httpHost, httpPort, streamKey)); + response.setStreamUrls(urls); + + return response; + } +} diff --git a/java-backend/src/main/java/com/example/livestreaming/entity/Room.java b/java-backend/src/main/java/com/example/livestreaming/entity/Room.java new file mode 100644 index 00000000..9a328f00 --- /dev/null +++ b/java-backend/src/main/java/com/example/livestreaming/entity/Room.java @@ -0,0 +1,53 @@ +package com.example.livestreaming.entity; + +import jakarta.persistence.*; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.AllArgsConstructor; +import java.time.LocalDateTime; +import java.util.UUID; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Entity +@Table(name = "rooms") +public class Room { + + @Id + private String id; + + @Column(nullable = false) + private String title; + + @Column(name = "streamer_name", nullable = false) + private String streamerName; + + @Column(name = "stream_key", unique = true, nullable = false) + private String streamKey; + + @Column(name = "is_live") + private boolean isLive = false; + + @Column(name = "viewer_count") + private int viewerCount = 0; + + @Column(name = "created_at") + private LocalDateTime createdAt; + + @Column(name = "started_at") + private LocalDateTime startedAt; + + @PrePersist + public void prePersist() { + if (id == null) { + id = UUID.randomUUID().toString(); + } + if (streamKey == null) { + streamKey = id; + } + if (createdAt == null) { + createdAt = LocalDateTime.now(); + } + } +} diff --git a/java-backend/src/main/java/com/example/livestreaming/exception/GlobalExceptionHandler.java b/java-backend/src/main/java/com/example/livestreaming/exception/GlobalExceptionHandler.java new file mode 100644 index 00000000..c5579c19 --- /dev/null +++ b/java-backend/src/main/java/com/example/livestreaming/exception/GlobalExceptionHandler.java @@ -0,0 +1,32 @@ +package com.example.livestreaming.exception; + +import com.example.livestreaming.dto.ApiResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import java.util.stream.Collectors; + +@Slf4j +@RestControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity> handleValidationException(MethodArgumentNotValidException ex) { + String message = ex.getBindingResult().getFieldErrors().stream() + .map(FieldError::getDefaultMessage) + .collect(Collectors.joining(", ")); + return ResponseEntity.badRequest().body(ApiResponse.error(message)); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity> handleException(Exception ex) { + log.error("服务器错误", ex); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(ApiResponse.error("服务器内部错误")); + } +} diff --git a/java-backend/src/main/java/com/example/livestreaming/repository/RoomRepository.java b/java-backend/src/main/java/com/example/livestreaming/repository/RoomRepository.java new file mode 100644 index 00000000..50791f55 --- /dev/null +++ b/java-backend/src/main/java/com/example/livestreaming/repository/RoomRepository.java @@ -0,0 +1,13 @@ +package com.example.livestreaming.repository; + +import com.example.livestreaming.entity.Room; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface RoomRepository extends JpaRepository { + + Optional findByStreamKey(String streamKey); +} diff --git a/java-backend/src/main/java/com/example/livestreaming/service/RoomService.java b/java-backend/src/main/java/com/example/livestreaming/service/RoomService.java new file mode 100644 index 00000000..5a371881 --- /dev/null +++ b/java-backend/src/main/java/com/example/livestreaming/service/RoomService.java @@ -0,0 +1,89 @@ +package com.example.livestreaming.service; + +import com.example.livestreaming.dto.CreateRoomRequest; +import com.example.livestreaming.dto.RoomResponse; +import com.example.livestreaming.entity.Room; +import com.example.livestreaming.repository.RoomRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class RoomService { + + private final RoomRepository roomRepository; + + @Value("${livestream.rtmp.host:localhost}") + private String rtmpHost; + + @Value("${livestream.rtmp.port:1935}") + private int rtmpPort; + + @Value("${livestream.http.host:localhost}") + private String httpHost; + + @Value("${livestream.http.port:8080}") + private int httpPort; + + /** + * 获取所有直播间 + */ + public List getAllRooms() { + return roomRepository.findAll().stream() + .map(room -> RoomResponse.fromEntity(room, rtmpHost, rtmpPort, httpHost, httpPort)) + .collect(Collectors.toList()); + } + + /** + * 根据ID获取直播间 + */ + public Optional getRoomById(String id) { + return roomRepository.findById(id) + .map(room -> RoomResponse.fromEntity(room, rtmpHost, rtmpPort, httpHost, httpPort)); + } + + /** + * 创建直播间 + */ + @Transactional + public RoomResponse createRoom(CreateRoomRequest request) { + Room room = new Room(); + room.setTitle(request.getTitle()); + room.setStreamerName(request.getStreamerName()); + + Room saved = roomRepository.save(room); + return RoomResponse.fromEntity(saved, rtmpHost, rtmpPort, httpHost, httpPort); + } + + /** + * 更新直播状态(供SRS回调使用) + */ + @Transactional + public Optional setLiveStatus(String streamKey, boolean isLive) { + return roomRepository.findByStreamKey(streamKey) + .map(room -> { + room.setLive(isLive); + room.setStartedAt(isLive ? LocalDateTime.now() : null); + return roomRepository.save(room); + }); + } + + /** + * 删除直播间 + */ + @Transactional + public boolean deleteRoom(String id) { + if (roomRepository.existsById(id)) { + roomRepository.deleteById(id); + return true; + } + return false; + } +} diff --git a/java-backend/src/main/resources/application.yml b/java-backend/src/main/resources/application.yml new file mode 100644 index 00000000..97b87083 --- /dev/null +++ b/java-backend/src/main/resources/application.yml @@ -0,0 +1,50 @@ +server: + port: 3001 + +spring: + application: + name: livestreaming-backend + + # 数据库配置 - 默认使用H2内存数据库(开发测试) + # 如需使用MySQL,请取消下方注释并配置 + datasource: + # H2 内存数据库(开发测试用,无需安装) + url: jdbc:h2:file:./data/livestream;AUTO_SERVER=TRUE + driver-class-name: org.h2.Driver + username: sa + password: + + # MySQL 配置(生产环境使用) + # url: jdbc:mysql://localhost:3306/livestream?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true + # driver-class-name: com.mysql.cj.jdbc.Driver + # username: root + # password: your_password + + jpa: + hibernate: + ddl-auto: update + show-sql: true + properties: + hibernate: + format_sql: true + + # H2 控制台(开发时可访问 http://localhost:3001/h2-console) + h2: + console: + enabled: true + path: /h2-console + +# 直播服务配置 +livestream: + rtmp: + host: localhost + port: 1935 + http: + host: localhost + port: 8080 + +# 日志配置 +logging: + level: + com.example.livestreaming: DEBUG + org.hibernate.SQL: DEBUG diff --git a/java-backend/target/classes/application.yml b/java-backend/target/classes/application.yml new file mode 100644 index 00000000..97b87083 --- /dev/null +++ b/java-backend/target/classes/application.yml @@ -0,0 +1,50 @@ +server: + port: 3001 + +spring: + application: + name: livestreaming-backend + + # 数据库配置 - 默认使用H2内存数据库(开发测试) + # 如需使用MySQL,请取消下方注释并配置 + datasource: + # H2 内存数据库(开发测试用,无需安装) + url: jdbc:h2:file:./data/livestream;AUTO_SERVER=TRUE + driver-class-name: org.h2.Driver + username: sa + password: + + # MySQL 配置(生产环境使用) + # url: jdbc:mysql://localhost:3306/livestream?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true + # driver-class-name: com.mysql.cj.jdbc.Driver + # username: root + # password: your_password + + jpa: + hibernate: + ddl-auto: update + show-sql: true + properties: + hibernate: + format_sql: true + + # H2 控制台(开发时可访问 http://localhost:3001/h2-console) + h2: + console: + enabled: true + path: /h2-console + +# 直播服务配置 +livestream: + rtmp: + host: localhost + port: 1935 + http: + host: localhost + port: 8080 + +# 日志配置 +logging: + level: + com.example.livestreaming: DEBUG + org.hibernate.SQL: DEBUG diff --git a/java-backend/target/classes/com/example/livestreaming/LivestreamingApplication.class b/java-backend/target/classes/com/example/livestreaming/LivestreamingApplication.class new file mode 100644 index 0000000000000000000000000000000000000000..5e0c686d717e266bdb2a6cbbf0924b31ec436d92 GIT binary patch literal 1159 zcmb7DOHUI~6#gz0T51(60-^$g4`{0}1zk}gK@y`$RU)P?TusZRy*QoQ%-kaGbVo=; z;#LfCrHL`%BWT3<1Nwt1w6OT?axu&8dq&pe4AHdA8D_??g(ugP0aXwV!^XO<$= zq9H7X?%0F$eui_(rh-!OOc=+}8AOyJA?>{Glsx@{Z4~(XYT52cYVuUeOp;scUL!meSaFl zFvF0hP0!wRH@4kZPbRd2Ql$x9H@$TzNJmXvPA126qa;FzGw7OjW4yBV#$DNQpKl-L zT^Jvs20PMQtbk$XX@@!+jE1^;i>rd1pEbO>D{hlxcutn>9KSBSpZgA5{86t(483>D zmJ&st5svUCXjxMEhe3g+8V=syE2FF=CFoC{*S72$EnM9Oq%a7YE}bXPL|=3^M1v&h z#2N9H#217<_~tNC&kS-nLNv1HK>R34Z8(N@3gZ22`@w)@lqJ5Rar*r@VIQfT5PAq>fDncWLq|s)ei8{>B+N_nw$PaoYr=?6N6B?BrN9v6#fQUcCBl{iU@ecYg>@@7DNjbtOYfNHf$|6{+hN!8QktRyW0XU;k)<; z6BB&^AIf;9El?AGsOTm;w=?s7=bLlReEa_SD}Y5TYZzciR^5gv_IRUd3)7Zu;Rl}J z4e8X)s_SgYy16MTna)Ti3kR9+JFX~VK^rp zvEFJ_gjeJhTQJ0OZk5|5?nyQ8Erx<^=`+maeqULNz4gX`%l_*+@jE| zW{W!RP_h;B7L5|LRCq&!X-dZz*<7}m)p3_W%NL5T^6Qq;Y#O-N@9UZfw%wYqOb-k^ z>^Hp>ye2$9O!Y=zbVTiWY+xQw2)@Dhmg|(ntMS+WBv=f(*ULPHrIUq!pDJRcE3Cj( zK?fv*vd(f_URA6}ReaH-hhbJ_AH&2(%L!ydl%y}I9G8!3olegE(jR(Z0gge%_7u%b>KXOnJWEN+#DVo9s4#b(Gp+qd`rC>|YP zZu6LZlK2&`LHy&yJ5S7M;>{AX@@TBf+VhC`@f0cYY=LZHMiZrSvD2pUjCv(!3HSjg C9h&$6 literal 0 HcmV?d00001 diff --git a/java-backend/target/classes/com/example/livestreaming/controller/HealthController.class b/java-backend/target/classes/com/example/livestreaming/controller/HealthController.class new file mode 100644 index 0000000000000000000000000000000000000000..d3d98e7e580c8ae0b7b9433cad84c0bbda7ee52a GIT binary patch literal 1197 zcmbVLZBG+H5Pqf@J5u5PNxhDv(c zKM-}}+t9eK3dKt$UE5ecaX82lMh|6E3RiOIo7$nOTf)GK_O)Fj^p)p#3E6eCr}F5- zU;+IYAQY?GSFdBQrotUra}}XfHFfFk%1|eE*O|2q9TA>X&qc1nJQ^us!eV*8YQh5% z1tGVzA4*TPOn4+3)&^orMS<}nwc%TBKXBUMwm$HsjYB3@>pgR&nko@n?TS}2=$ff8 z+cv?ibOKIERaxVQFufHswx@P=q`CcPzHh9wnn@9s&m`VbHBr<4o{%T8;)Svd8zex4 z{-$(e=I@A(202Wz9ub#~qa3CQ8FQ4w3^$tqv)>EyxPogAt`erwMv@JY&gHqZYKN@? zu49&`>YEl}hL!n8TqP-Wvw&N;O~_f(X`e80oN`)}AU@_sjPk3AY zTe|akx}1-P#7yr&I9MXg^>{!En8tm=>KT;&=aA*MO&r$MrcRFk+|Tf8h&qpVI9-f0J!sh##QP3E*{@Ey4(jTxyj6 zGZ;gO*Ae!P^E>el6k#$gyn$?*H(t6>x>&l@#^ukr(Z-!N?tV;D6PS^d(k;r1_8>|W literal 0 HcmV?d00001 diff --git a/java-backend/target/classes/com/example/livestreaming/controller/RoomController.class b/java-backend/target/classes/com/example/livestreaming/controller/RoomController.class new file mode 100644 index 0000000000000000000000000000000000000000..14d1f047749cef3c53cc19dbd09f1de97b842add GIT binary patch literal 4706 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 CCStw- literal 0 HcmV?d00001 diff --git a/java-backend/target/classes/com/example/livestreaming/dto/ApiResponse.class b/java-backend/target/classes/com/example/livestreaming/dto/ApiResponse.class new file mode 100644 index 0000000000000000000000000000000000000000..c3c4529a1278386a90d5f2301eedcd7db3a70f0b GIT binary patch literal 4331 zcmb_fTXP#l7XDhA(a6e}D8Wt;36Sg>kYqc~a>q9?&dp#u7kkZOmfeiy#-3Q3F`AJJ z{t3G;`?h(>15`nvLKOusP{nUr7QQnxQbv|yY<8=nsQdKkbNS9WU&}xJ_rbRSX3(;b zQK;1X#)SUZY3$bZMBUxfLD}qi}L@zv7g_z>2%&Ibo}*6|$?VlvoX3bhfxpUsws7^v~lr z%DrYPi-(Sw7T%d&o$R$STc-CT7@DT~EUg*y{-!QK;e?Hocw8Yj?RsuFt57bl_qs_* zl9VcI3da{+PcOF`8@jpbY}BLN{hCu>bDFN~6XB`FF5xG;?StZy4!8Zy8&1<{=ukHa z{LMZ%^>KbAjKgacBRk(jy)ku2HR(Lsl$ z2II5N{=yL|QxygoTnU}p&XTj6OnH9W3ATk*$cW|2f<(G(;~gwAwi~w=mM$~S1tQVZ zL90%(HIumVBb^xXp&w&bz#9Hy;oUBlj*o1-2ez)Y=hR#J`iBb7^(#=SPm(H}{hET*2O3-Z{Ig!>ag%2=oUi*NaU7=%q{H)hkIfNk8d;3|wfe7Rh2 z&)?A#ajhGuY z+Y^uyB_`&bdVR$W_0;hkw({`!zNcU+ColDk8apSwF5gKT&8Uikz2K3v=Ic%n=)l59 z3eWan(Jwm-Y=Wl`R%H*zP~bdgFw4HmA|ss0RYnMi(t&7Q;GPu2^>Io}3{o>bd5jX^ zDrJh_$G(Aj�=eQ2dPBEQa`hE;>4aVXp01Eiw+A>XH<=JI#;Dq2QPJd7|T|6#2PH zS%zncpV2;kh9f_xVztB%FGS(1M|d2NY(+`DK8Z6OuV*8#FQ>E1s%qjjlgh5tm0b)~ zl8abXLVgVC8LkD6bNm+E=C~Hz%<307e~*haVphtgi9ce5=MNDR$+MJ@H}HHX;PQbk zD*KCaSQjMc--v$v_nMx-MMl3Lm_@piLg%VyzQp;@`yvz9IF}$Jz)I_;Yb3C1>2sEOR$A zX^j7fV^vCiSW##Jtwl(-nJqzhsj3$*bz zeV<3c%KrzCSy1-RIAtNre|hyYo`{zNotn)PLPLJi^g_sA1&a1Kz=B=>0gi)c4=`1k zfG}(CC?`E~DJmI%Z_x6-xlm70X42nsacbI_Ia{3Cmg4*a`Qr7vFpJLZnW#Q;!0P0S zqjzDA-^WrUpPMw~pkyQ`d8rwX&i1dxOR=Y<7Fp(c9XA-bi3Qx`Dnm-Uh+9lXYH=DX zTp6jfmJ{3sZH?S7q%&fy6e&;rLOaPOBfH08L`TFvI?bl`k-{*{wnAIS=oV3pMpyeX z)lD(WFop#!LFjAznO}1c@IG)5a}6iiKP;9=V&3I%quvUoDE%wk-ao|oLsB~bkR_Dy z3%jwu3Ggx98q8*hFVXFYAgf~}tLk_k+8BsDR%87n8Qx?O<7at@1DB^gkqMP#Q-d>< vq$+2^Vws4=2D7|eq@c(PG8r{q&R{bMSK~wOrNQg`G~jb51h|VPLX>_5fQK{0 literal 0 HcmV?d00001 diff --git a/java-backend/target/classes/com/example/livestreaming/dto/CreateRoomRequest.class b/java-backend/target/classes/com/example/livestreaming/dto/CreateRoomRequest.class new file mode 100644 index 0000000000000000000000000000000000000000..ab53885faea05bbc6212de7ce01c42b29aa3dba8 GIT binary patch literal 2667 zcmb7G&u<%55dNOMYdh)YXPdO{4`^u$ah%jGw9wj3p-q3KBxy)Okl@0L^HguW-VM8( zh@Lrcs)RUIoDc^hq#_jwLKOspOOf~+IF=Uf9D(vZubsrTX^Wh^oi}fG=9_P3p8d-| z>puV(#e5Pmfn3q66*b9z23OyR-h_ zi*)xI3?tlE{Zy()wuzI`EQ|hV;=c0sm3~Ge+bT?Ak zQ;Rk3uCz$1cf#>13tl;QQMt+|gNsCOjCqTy4FWqXm`Dh8P1vrQTCOZ8e@>DY&7N13 z&b;((y{{>auw;{{qZ9wLR1De0JP4Tujgr>U_gJuW^i;5sW8YwNikW%Vc~XTXZ*f}s zbPy_^D7yI8R*wP+HimR~)ZiuRi$dn@c7{A?VE_jhvMAm2+FC20x6p~McEmAgVLuLN z-H?Sex&(yR*hw{SY%?z`DPN#nb23otxouTteb-YxYAkhcWYMVXmdFj(!KQ8B!KYwbowM!*_(IkaPr3<4D-F(YuOiHuh1B{3)P%;Qa2vWN>DV7tfJ zA2EJ-8*oi>w~g8q|Jwqza|3ihY{#Z5rf0X=wxij$1A5pwVYKe1qXc(7eCnReQ5Jlf zLtkV2Z3;2;an&Yz?dNJm2H1&R-0=#3qNCkU=V&kXG4DqasI^>FSlTL!ZMmZe@Rh_ylJB zR`y4T$>E_jJXhUPI5aiPC2x%UjBRP-E_zmBjiik=yim8ukok^$p?!qZ!v`X&Y?m~&wrnt&gK#12{Vp^v?@?Cs-#_p@sJ9GqzOTy_;|>5 zb05=+KA3fbx{cAOq8a0zYB_T{zMj)gov==Boj>OAxZ&%pWg_%4Hr72IKzdbH#p;OQt>&vn>HnpcARO>dCz;E^M1eY_RoKw{0d+aUl>RUoT~fH zvb<+ETdpj-&V~%awzQj$cc;7>`sJGMH)}Fz`CcINOVQPG+YJn80)uz$4ZG~xgkD{_ zE9;?v)()F3fuXsrD?XyIszCZHcY|xg4FrrkGOR_=gN2cP;G?y5FLau6*4uCbXT_Bl zJkJm9(4iqpzUwzv{I&9B>B+Vo%2mqOruspyCXjnRYjn>O&R`rCPCO+(5B%7EHx5f{x3>gU?bar~+X4eBSot{1*@%}M%;+LY@j%Rv z=$IYlJYh%l-lfp4ug%-7BnM{04jL+dERe#;tTOYgg%5C=nip%c^OtDaES+!5VBKY% z>4{b?A8FC@kyJDE<7yjXfnT&Rg^vUdH|;e!<$HBIyy1jY;sv4Yg^b{pK6&WPOfRQz z!Dc2L&k4^(_Fq@_&seyGX@TsLzuvCPX-5^{f$n}9S53mSU-bQuU1zuA{0CXg# zvmU#9%C&<)a^kQ!k==?{>E0A?M&dbfdxIm+(D4B}`Jhz%9=i6Z z^gG0N(9@4fzd_8879Zn8bR>~q80AvY#{R&boc0t49>N;SX^-({2T`c#gcK^7oUSmL zrx<#Oy*WLX=>ioi20_J&nKKl~Oh87??0~dKG$`Fuf45^pH*@p=qZp%?!?=fW)?^BK zI4tBbzAd7Tw@GQZjA^_>O6N#Bk9SFB;IdWTBW0k?n7>cT#3|}5!7|Lhu+M<_8;1;} z`Dc6=dJbVWIp6yA80I(~)t^trkg zQbjty2DOpiHOdI)3eG0uFc_=Sq5E^=COW2&UZSH+4RT`^zLBQvK* zSI7FN;9RUQu{MnwDyY(09gCPCm0~A<%zx}u3QjWE=SXQ>5932pdKBbuka(pWp)UqNy{K z1VN~8Y;ShD2I?=k+`nzN4^$1aQH5M#1i^w7_=LY2t|`-~Kwud+*aDy8CT=5#&m;8m EzX4ear2qf` literal 0 HcmV?d00001 diff --git a/java-backend/target/classes/com/example/livestreaming/dto/RoomResponse.class b/java-backend/target/classes/com/example/livestreaming/dto/RoomResponse.class new file mode 100644 index 0000000000000000000000000000000000000000..ec2920768482f862122346ecf94e471ff2a68489 GIT binary patch literal 7087 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 literal 0 HcmV?d00001 diff --git a/java-backend/target/classes/com/example/livestreaming/entity/Room.class b/java-backend/target/classes/com/example/livestreaming/entity/Room.class new file mode 100644 index 0000000000000000000000000000000000000000..0c307cac29ff4e77eb155f8a0835fc55e841a1be GIT binary patch literal 6206 zcmb_g>vJ1d75}aETFGl$F>w;rNl2*MIIpk~8e)rsS z&pqd^{_>w!eh**>ze%9Qz+E$5G3Q*ii;D#(S8$h|ieGl@qFb8FIVIoqS8}I3ub4p8 zK>DJ+Z08C#MJFy?bY^@5E$*y=&arjDq+h1#fPu`ZD$R?|S-0X|C^$z-CC|5g*DF;F z?4ZGdUH0wVqEoK872he%IJwbTn#DL}!7;G2p~|pVs1{39GfUM%!6t5mOMwAPd+n4b z>>{nBCG~2nai{e-smBjGD+W@Uo?ehayjpUXstU~D$gPYq5C)7X0|~b>EnzXxy6ifa zobs^8g;6%jSv6;RMzfMLjGXVx9-%m+VSIimBaKyTWnB7I^cv=Mz$ zv=XEi%g#w19s`ly{bvpAX&97~YkI_v4tujsk`d{#5JNnLyV0A#J_GG)kE*^~$elSe zdd$LpWa&!TF3oyH;cy>&_iL}$vLK_w{6n5XBNio#Q-2S=lG+{>+T?hM@!2MlDKoM;1w@;3%UE9 zl2ev_CzMIk(~`jwf?*3u#OXJgj1fl!aaB=DDiqHFfpsEe>y-q*i*|@#nqSlXjV2`b;~n zg(hk(Tmi}O3gp1mILhw?hx*J!d#41ios12RM|($wFLoh+cLwe>*zcM)H>Y+!;?7dl zF3`0NtI)M*VEz%PM3!Bc**rgHC7UrW8%?E^;zV z$@F9xOiwsakJSukYP1=xpvZ<#AVWFmmR$cniSlp5 z)$)vU+?86JlzYHIsp47$u|G3#B^;(eTLV78-d1obJ!77?P+h$Jo^CKa zy|U;uP=6$}yA6^qfEcz5g-O?U20G&SiHU2hNfNnUcXr6=-=j{55M20LLs%d4gvdZ& zK!gh2Ib^tYbhJrubcT%mtgNFY_Mzc|U8y*g1YY9Rt}cp=;*!911G{gp%KX$p#9o#^ zxpSFlk^2Ql36A7#^4%g0FW|NEEyc2R3Kj+Ic7Y(R1TAb4=%`6~X$YiUN*WRA7IaSG zEj1PP!$75mOqFd!W*@8cq{f$p|+;IT+`e5e2hGU?_Bn` zxbr63vM=MV8|Yibon+*0qBSdmRV6=k6Di8yv5G_2X%fME_BeKEB1P9BJ_?CX@e)M2nhBRKU=$ zW5xnZL)zOWKu4PcVt3pGXrwtHHrGvna?Js;!*2pKdabD#@q(}k(0Fq|ye(`3bf~$d z@v^ZA&_m4uP2w~Y^42`c6TF3?iDxCFC28vuNe-H1pH5Dp^yxoCZj3dZ*M0+MH-|sk zEW9ox0en)yKfF2oNVD*|90l-g3jPC|!{?fX*YzxbPb>HjV(PZB?_R}dv+%kW2Jl-I z{3AHG8T@#&@VcS~@Y@yqhw$Os!ZUMJxoakKbX^YM?@;j5o5MfUtoORQ2k^TTJn#4Q z-nU4l@8GWo+xoIEAQrvW_bQBM5sO^w`y-6;{_G80P(u<WRGeQDPvaA$68IuMkH<)vco7SDoKzCm;ou2UDf|`3@g%8M{GFZeDN+{x$$Q8r zNwwi$Y~`OK)vg-m1hyy4f1^DC!?Iq-wge*lGmY1=LzBt>iA0b@mzYFR`L3)_B6eK< z3MNHRvZrxh(6zJHgr%*o&|{;9zM@i;A6CSO=Vp=3lb`VSMfP@pNruSOp6^{ ze=nZH7Z|VzZ!b?VStOy(VIE&16=V910b13_S7 z)|sqZHI@a2$wr3jwr?t?IfKPt|LRkd&EBL8kUHt|Df037Eb(#p%<-wbjIZ)l`%CFxB^No-v^L1o@hv%{pc1!se>S>+K3$RQ?dD~91K(v!yzXNxK;J?@4 z9RG>%a?%Dj!mBG$wtp2zvjAT{bKbuLg^<^{WDncDf`(lNBv6L@5 z+XrjqlLb>>qc|^TkJAUCb(S%a($akZ*J`8klf!jtYCSClkT`BWKHUo@sFD0@w+ zr3scMjNClZG)>be&F`T!6{VCtPooxmUm16h`T@s3WOVsO^&|Y41KHZ2^LdeHNxmfv L{F0mZYh?ZlQHyy? literal 0 HcmV?d00001 diff --git a/java-backend/target/classes/com/example/livestreaming/exception/GlobalExceptionHandler.class b/java-backend/target/classes/com/example/livestreaming/exception/GlobalExceptionHandler.class new file mode 100644 index 0000000000000000000000000000000000000000..360ed7300be7b3922fff3720de9464a3ca92bdf7 GIT binary patch literal 3935 zcmcgv>vI!T6#w1yv2D7xB~l9VeiT}$ED8dW@@Pv6)wEzzqXG&y$!)srW;bj$Y3mCS zK}GS2I?VXRZ+t^>Mq55Oes=sfaO9tGJo{*}Y12|<#&+7xy}9R{-+BGc-9P@h_Zxu2 z_)bEA;ZVxRDq@OfbDB^zwJ7qIDR@@ZGqjr$IZHKk<%DJ=xfYpQpX7R46Q+b(hSqUj z@#m=+4=hrQRm8XT8R_Z1f~uuy zYy>fagoKwFx)vwXd4wS26}(EdA?!kAIO-3o>l1}@+1e<&XK+r!S%$sK41nJQ8L#6F z8VN~qis7&yi#szPiHiebic*{v`8>}!1IeihCyAbCltGtyPIG%KSi)|_7I&1C$c*8b zA4qBck+58POcjq$@ohflFZWrB++*@ zvCAdPu+#637fhf2kulSlw8wt9E71r87bOr;Y+bY>PSPxm)9t1}U2cMM&^9 zM$gA1u|yhHKwFjHbcwXa5Nt6vj$+x7X-QR$nvG`y^SL$^$u#mwGxQ6=)D zr1cqwmA6cubBjA4#3fvo@E*gKato+>(U=fQlxLG^P6zqu+;#AMTw!RUc9`2@_|u3Pm#)dBW&MopY3}Em7FR71ZmGw+4BP!K zmDr^mA8)G_SA+Nn*Cl++&{wU3QF3fyyNDbGG%B*yCYMSWPl;Ccvw@zq^uG~7S~bzC zi=OsRV~EW_{u!a4Xc53l`fm#W&1iFWY<2YR;E5^pw7;#}`77Gzi?li-vU>n)2!$=Y z7VF6NdPms?Y@}y1?QWvK&1b<<*y7;1OvY{Odw2bUt+VK2n1#Y{56^)m6vTTpcB7>j=XrVekQ}#hWA=z*`O!J40{N`W^bS!~FrlxP}|}9AD5*h|Yb9 Pukj5jJVWgKZGGdvP_DB2 literal 0 HcmV?d00001 diff --git a/java-backend/target/classes/com/example/livestreaming/repository/RoomRepository.class b/java-backend/target/classes/com/example/livestreaming/repository/RoomRepository.class new file mode 100644 index 0000000000000000000000000000000000000000..e09b4f70bb87f5dbe460f32ede142d2b599dcfd8 GIT binary patch literal 651 zcmbV~%}xR_5XYw=tl$TzC*J^LVh_gCs)-&vCvq!LBQ)0OQBZS{+Cj#j=j`xu3ZkY z-%S}Uq6d|zzNQOftjAnNra1g{po9_0`Ya~7SXao47EtC>;h!YC8)7if;w!n Ytq9m)Q--ZsaC?5)fF}D@PTc|c0>0kRGynhq literal 0 HcmV?d00001 diff --git a/java-backend/target/classes/com/example/livestreaming/service/RoomService.class b/java-backend/target/classes/com/example/livestreaming/service/RoomService.class new file mode 100644 index 0000000000000000000000000000000000000000..6b910a10186cf1e2496d2fab93276d8155f30385 GIT binary patch literal 5187 zcmb_g`+po&760B%(rGqBvj)-{5|PHX**w}!EY&t!0&O5|AWfGf4U(!j*}Z9|ncZ1t zWP zvyh`1ZP<->Y3L=4{D@b~(6I-51-1o{6zJY$N3fbAG>F%92Ql5JBZ+=S)F>7$f^a(7 zz|1=6PDlN?R4QZ@uu>D@Al-&R?AI_Pu&b))CM|RvKuVw~ZxjU%CnHg*%8XWX_dW}NM~I_|(BI>)nB#074v=bQ?;ul##;9L5nkJ{xcm7>?=~YEeBC zsu;<1BB6cCEXe6neo;De22)Tas>+ag!!gx7Tx|B1O=2^3gAoX@t$=-eZ89g23>EZs zV5n6UhAv1p8!@=019GwG$oDd{cSxs0Kzo+lkTKAvS-cv5*%;NeDzpz?jZLOdfM zDX|C8maP-Jra}me)~&ss!`jQrtI{Y^dFQIjjGTrC1omz%O|sy7Yko6d4lys)~qZs9MNX4j~jG}uAYUgw;!(_pAja4eMFDhU&%%W+j zD)WjC4cpZyuj3}{P$OGMB3OGwM+bJq;i9C0FpWY7R~tm083YRR$l_xiyQ zxY!6$k%*;hoA_j%r5i+><^(7e7%uM&&80))-5A4?eEr6S#{|3Mx~8ru*eu2V!_awZkU`PB)5G zy*9YXhT2NR%Z2MBnsS(jrmK9y3(*2Gl9Lt-P*8R1CdY!(`U^U~h%b>`USRKXgNnL| zybN`p{3|-Ximz3;Hz1#9kQklp$|<85HX^p+t)ag4RkWY1y2H!nWMQ?=b+cp2vh$1f zO6r6xNQbpH$Ijh^Sr!P($S>xMJ;MTL8cC0a^9D1ZEMlZ8jrWBIk_!!ty>*YcEq6}X4anBz>xGx!+`biro%s9fG^>FBo% zey-ye_@#jE7e&LNKaa6a^@Mp-=adRjeAX^GSvg^<%AHWS3%^tjs6Na4L*2gQvPVkW zTPw;`NX|~0IBwe>fo&9nZtBMI0)C_6MS)(wY5N7&EUelqw9}6;361c}LdS3M5^pc; zzvU%?H=;=&o(!0uGzvLOx_hQ?yS}z`Vx2H?R=l*T=2bj;3afzpj%)A zx3iyL!#;305W`grZ{R&KyoTK`_yD zaH0gB!ZeR6KW^jfG~dd~r|4&;aQYQw9=d{=Q$vI6m|w^H)%!#1$SP%qHn613%uMg+ z^gJJnPk~SIDx9irJIM?Hq(f=&T6=FK6jB?t?=(L_|_+(wLP0(v6=$#2)quYan6^%Oa zDc@^khEL-&)JW;k8sjp!pmg~xKF34<>%$yLRp7cq9X=n!CA5c=FUQb)x#=>uo9LA? zK4yvDIqbq2V&uU{47)4ZbcZo~%0DT(%1#M_Ift*4-#56Yg85CpRWQdwMdAZj@U4Nt zm(leiS7W|hMW>`IECzUqS7gQgWD{i(El4C{s_^iLVmrxgWnbqYZQ45efrMm4LT5!n zrypZMwya%mh3&|jux0rR**;EP6(V|vy4Ugjq3%m)y^0?{%RkiC@vI6;1