添加视频下载和 m3u8 文件获取功能

- 新增 m3u8 文件获取和 ts 文件下载功能
- 添加视频控制器,实现视频列表、详情和 m3u8 文件获取接口
- 优化应用配置,增加开发环境配置
- 重构 HTTP 请求组件,支持文件下载
- 新增 JSON 结果封装类,统一接口返回格式
This commit is contained in:
燕陇琪 2024-12-28 20:11:31 +08:00
parent 789135eea8
commit fdd2dd700b
6 changed files with 167 additions and 6 deletions

View File

@ -0,0 +1,65 @@
package top.yuchat.crawler.video.models.controller;
import java.io.IOException;
import java.util.List;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import com.baomidou.mybatisplus.core.metadata.IPage;
import lombok.RequiredArgsConstructor;
import top.yuchat.crawler.video.models.entity.ClassifyInfo;
import top.yuchat.crawler.video.models.entity.MadouVideoInfo;
import top.yuchat.crawler.video.models.service.ClassifyInfoService;
import top.yuchat.crawler.video.models.service.MadouVideoService;
import top.yuchat.crawler.video.utils.JsonResult;
@RestController
@RequestMapping("/video")
@RequiredArgsConstructor
public class VideoController {
private final ClassifyInfoService classifyInfoService;
private final MadouVideoService madouVideoService;
@GetMapping("/classify")
public JsonResult<List<ClassifyInfo>> getClassifyInfo() {
return JsonResult.ok(classifyInfoService.list());
}
@GetMapping("/madou")
public JsonResult<IPage<MadouVideoInfo>> getMadouVideo(
@RequestParam(defaultValue = "1", required = false) Integer page,
@RequestParam(defaultValue = "20", required = false) Integer size,
@RequestParam(required = false) String classify,
@RequestParam(required = false) String title) {
return JsonResult.ok(madouVideoService.pageList(page, size, classify, title));
}
@GetMapping("/madou/{id}")
public JsonResult<MadouVideoInfo> getMadouVideoById(@PathVariable Long id) {
MadouVideoInfo madouVideoInfo = madouVideoService.getById(id);
madouVideoInfo.setM3u8Url("/video/" + id + "/index.m3u8");
return JsonResult.ok(madouVideoInfo);
}
@ResponseBody
@GetMapping("/{id}/index.m3u8")
public String getM3u8(@PathVariable Long id) throws IOException {
return madouVideoService.getM3u8(id);
}
@ResponseBody
@GetMapping("/{id}/{ts}")
public void getM3u8(@PathVariable Long id, @PathVariable String ts) throws IOException {
madouVideoService.downloadTs(id, ts);
}
}

View File

@ -6,6 +6,7 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -61,6 +62,7 @@ public class MadouVideoService extends ServiceImpl<MadouVideoMapper, MadouVideoI
private final ClassifyInfoService classifyInfoService; private final ClassifyInfoService classifyInfoService;
private final HttpRequestComponent httpRequestComponent; private final HttpRequestComponent httpRequestComponent;
private final HttpServletResponse httpServletResponse;
ExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(72); ExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(72);
@ -221,4 +223,41 @@ public class MadouVideoService extends ServiceImpl<MadouVideoMapper, MadouVideoI
return this.page(new Page<>(page, size), queryWrapper); return this.page(new Page<>(page, size), queryWrapper);
} }
public String getM3u8(Long id) throws IOException {
// https://video.yuchat.top/m3u8/{id}/index.m3u8 去获取内容
String url = "https://video.yuchat.top/m3u8/" + id + "/index.m3u8";
String m3u8 = "";
try {
m3u8 = httpRequestComponent.get(url);
} catch (Exception e) {
log.warn("获取m3u8失败错误信息{},url:{}", e.getMessage(), url);
}
if (StringUtils.isNotBlank(m3u8)) {
return m3u8;
}
// 如果是404则去数据可查询原始数据
MadouVideoInfo videoInfo = getById(id);
if (videoInfo == null) {
throw new RuntimeException("视频不存在");
}
return httpRequestComponent.get(videoInfo.getM3u8Url());
}
public void downloadTs(Long id, String ts) throws IOException {
// https://video.yuchat.top/m3u8/{id}/{ts} 去获取内容
String url = "https://video.yuchat.top/m3u8/" + id + "/" + ts;
try {
httpRequestComponent.download(url, httpServletResponse.getOutputStream());
return;
} catch (Exception e) {
log.warn("下载ts失败错误信息{},url:{}", e.getMessage(), url);
}
MadouVideoInfo videoInfo = getById(id);
if (videoInfo == null) {
throw new RuntimeException("视频不存在");
}
httpRequestComponent.download(videoInfo.getM3u8Url().replace("index.m3u8", ts), httpServletResponse.getOutputStream());
}
} }

View File

@ -18,9 +18,12 @@ import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.ssl.SSLContextBuilder; import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.util.EntityUtils; import org.apache.http.util.EntityUtils;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.security.KeyManagementException; import java.security.KeyManagementException;
@ -109,6 +112,21 @@ public class HttpRequestComponent {
Files.copy(entity.getContent(), path); Files.copy(entity.getContent(), path);
} }
public void download(String url, OutputStream os) throws IOException {
HttpGet httpGet = new HttpGet(url);
httpGet.setConfig(requestConfig);
CloseableHttpResponse response = closeableHttpClient.execute(httpGet);
int statusCode = response.getStatusLine().getStatusCode();
if (HttpStatus.SC_OK != statusCode) {
log.error("文件下载失败,状态码:{}", statusCode);
throw new RuntimeException("文件下载失败");
}
HttpEntity entity = response.getEntity();
IOUtils.copy(entity.getContent(), os);
}
public long getFileSize(String url) throws IOException { public long getFileSize(String url) throws IOException {
HttpGet httpGet = new HttpGet(url); HttpGet httpGet = new HttpGet(url);
httpGet.setConfig(requestConfig); httpGet.setConfig(requestConfig);

View File

@ -0,0 +1,35 @@
package top.yuchat.crawler.video.utils;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class JsonResult<T> {
private int code;
private String message;
private T data;
public static <T> JsonResult<T> ok(T data) {
return new JsonResult<>(200, "请求执行成功!", data);
}
public static <T> JsonResult<T> ok() {
return ok(null);
}
public static <T> JsonResult<T> error(int code, String message) {
return new JsonResult<>(code, message, null);
}
public static <T> JsonResult<T> error(String message) {
return error(500, message);
}
public static <T> JsonResult<T> error(Exception e) {
return error(e.getMessage());
}
}

View File

@ -1,11 +1,11 @@
spring: spring:
datasource: datasource:
driver-class-name: org.postgresql.Driver driver-class-name: org.postgresql.Driver
url: jdbc:postgresql://pgsql.yuchat.top:5432/postgres url: jdbc:postgresql://yuchat-databases:5432/postgres
username: postgres username: postgres
password: longqi@1314 password: longqi@1314
top: # 日志级别,生产环境改为warn
yuchat: logging:
download: level:
path: F:\videos root: info

View File

@ -1,4 +1,5 @@
minio: minio:
endpoint: http://minio.yuchat:9000 endpoint: http://minio.yuchat:9000
access-key: CGG6QZHvKI0JEuXM5gBJ access-key: CGG6QZHvKI0JEuXM5gBJ
@ -9,3 +10,6 @@ top:
yuchat: yuchat:
download: download:
path: F:\videos path: F:\videos
spring:
profiles:
active: dev