【JAVA】酷动视频

项目介绍

本项目仿照bilbili,旨在提供一个前后端分离的视频平台,实现了视频的上传、点赞、收藏、弹幕、评论、个人主页内容与权限的编辑查看、私聊与大模型文生、智能ppt等功能。
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Gateway服务

Controller

LoginController

采用了双token进行校验,当短token即将过期时,客户端可以使用长token来刷新短token,而不需要用户重新输入凭据。这通过以下步骤实现:

  1. 客户端发送一个请求,携带即将过期的短token和长token。
  2. 服务器验证长token的有效性。
  3. 如果长token有效,服务器生成一个新的短token并返回给客户端。
  4. 客户端更新短token,继续使用。
@PostMapping("/refreshToken")
public Result<Boolean> refreshToken(ServerWebExchange exchange){
   
    log.info("刷新token");
    String shortToken= exchange.getRequest().getHeaders().getFirst(SHORT_TOKEN);
    log.info(shortToken);
    String longToken=exchange.getRequest().getCookies().getFirst(LONG_TOKEN).getValue();
    exchange.getResponse().getHeaders().set(SHORT_TOKEN, JwtUtil.refreshToken(shortToken,0));
    ResponseCookie cookie = ResponseCookie.from(LONG_TOKEN, JwtUtil.refreshToken(longToken,1))
            .httpOnly(true)
            .path("/")
            .maxAge(LONG_TOKEN_EXPIRATION )
            .build();
    exchange.getResponse().addCookie(cookie);
    return Result.success(true);
}

Filter

这个过滤器的主要作用是:

  1. 记录请求路径,帮助跟踪和监控请求。
  2. 设置用户权限到Spring Security的上下文中,以便在后续的请求处理中进行授权检查。

Common服务

Client

searchClient

使用feign来进行服务间通信,使其他微服务来调用search服务的功能。

  1. 定义 Feign 客户端接口:创建一个接口,并使用 @FeignClient 注解来指定要调用的服务名称和相关配置。然后在接口的方法上使用注解(如 @GetMapping、@PostMapping 等)来定义具体的请求路径和方法。
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@FeignClient(name = "search-service", url = "http://search-service:8201")
public interface SearchClient {
   
    @GetMapping("/search/likelyVideoRecommend/{videoId}")
    List<RecommendVideo> getRecommendVideo(@PathVariable String videoId);
}

videoClient

这个 VideoClient 接口定义了与视频服务通信的三个方法,分别用于上传视频、获取视频流和上传视频封面。通过 Feign 客户端,可以非常方便地调用这些远程服务,就像调用本地方法一样简单。

@Component
@FeignClient(name = "video", url = "http://localhost:10201")
public interface VideoClient {
   
    @PostMapping(value = "/videoEncode/uploadVideo", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    void uploadVideo(@RequestPart("multipartFile") MultipartFile multipartFile);

    @PostMapping("/videoEncode/getVideoInputStream")
    ResponseEntity<Resource> getVideo(@RequestBody UploadVideo uploadVideo);

    @PostMapping(value = "/videoEncode/uploadVideoCover", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    void uploadVideoCover(@RequestPart("multipartFile") MultipartFile multipartFile);
}

noticeClient

SendNoticeClient 是一个使用 Spring Cloud OpenFeign 的 Feign 客户端接口,用于与通知服务进行通信。它定义了多个方法,用于发送不同类型的通知,包括动态通知、数据库变更通知、点赞通知、评论通知、聊天通知和上传通知。

CustomMultipartFile

MultipartFile 是 Spring Framework 中的一个接口,主要用于处理文件上传。它提供了对上传文件的访问和操作功能,使得在 Spring MVC 应用程序中处理文件上传变得非常方便。
这个 CustomMultipartFile 类是一个自定义的 MultipartFile 实现,用于在远程调用时传递输入流(InputStream)。它的主要作用是将输入流包装成一个 MultipartFile 对象,以便在 Spring 的文件上传相关方法中使用。

UserCenter服务

编辑用户信息

处理逻辑,判断用户是否更改id、头像、简介,然后修改数据库并调用client发送同步消息,保证数据一致性。

/**
 * 修改用户信息并发送数据同步消息
 */
@Override
public Result<Boolean> editSelfInfo(MultipartFile file, Integer userId, String nickname, String intro) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
   
    // 初始化存储更新信息的 Map
    Map<String, Object> map = new HashMap<>();

    // MybatisPlus构建更新条件,确保只更新指定 userId 的用户信息
    LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();
    wrapper.eq(User::getId, userId);

    // 如果用户上传了头像文件
    if (file != null) {
   
        // 生成随机文件名,避免文件名冲突
        String coverName = UUID.randomUUID().toString().substring(0, 10) + file.getOriginalFilename();

        // 将文件上传到 MinIO 对象存储
        minioClient.putObject(
            PutObjectArgs.builder()
            .contentType(file.getContentType())
            .stream(file.getInputStream(), -1, 10485760)
            .bucket(bucketName)
            .object(coverName)
            .build()
        );

        // 生成文件的访问 URL
        String url = filePath + bucketName + "/" + coverName;

        // 将头像 URL 存储到 Map 和更新条件中
        map.put(USER_COVER, url);
        wrapper.set(User::getCover, url);
    }

    // 如果用户提供了新的昵称
    if (nickname != null) {
   
        // 将昵称存储到 Map 和更新条件中
        map.put(USER_NICKNAME, nickname);
        wrapper.set(User::getNickname, nickname);
    }

    // 如果用户提供了新的简介
    if (intro != null) {
   
        // 将简介存储到 Map 和更新条件中
        map.put(USER_INTRO, intro);
        wrapper.set(User::getIntro, intro);
    }

    // 更新数据库中的用户信息
    userMapper.update(null, wrapper);

    // 构建通知消息的 Map
    map.put(TABLE_ID, userId);
    map.put(OPERATION_TYPE, OPERATION_TYPE_UPDATE);
    map.put(TABLE_NAME, USER_TABLE_NAME);

    // 发送数据同步通知,告知其他系统或服务用户信息已更新
    sendNoticeClient.sendDBChangeNotice(map);

    // 返回成功响应
    return Result.success(true);
}

Video服务

视频上传

分片上传: 将大视频文件分割成多个分片,逐个上传到 MinIO。
断点续传: 通过记录已上传的分片信息,支持在网络波动时继续上传未完成的分片。
封面提取: 在上传过程中从视频中提取封面,转换为 Base64 编码。
分片合并: 当所有分片上传完成后,合并分片生成完整的视频文件。

/**
 * 上传视频时获取视频封面
 */
@Override
public Result<List<String>> uploadPart(UploadPartRequest uploadPartRequest) throws IOException, EncoderException, ServerException, InsufficientDataException, ErrorResponseException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
   
    // 提取分片标识符的有效部分
    int commaIndex = uploadPartRequest.getResumableIdentifier().indexOf(',');
    uploadPartRequest.setResumableIdentifier(uploadPartRequest.getResumableIdentifier().substring(0, commaIndex));
    String resumableIdentifier = uploadPartRequest.getResumableIdentifier();
    
    // 初始化视频名称和封面
    String videoName = "";
    String videoCover = "";

    // 检查是否需要提取视频封面
    if (uploadPartMap.get(resumableIdentifier) == null || uploadPartMap.get(resumableIdentifier).getHasCutImg() == false) {
   
        // 获取视频文件输入流
        InputStream videoFileInputStream = uploadPartRequest.getFile().getInputStream();
        byte[] bytes = IoUtil.readBytes(videoFileInputStream);
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);

        // 创建临时文件路径和文件名
        String filePath = Files.createTempDirectory(".tmp").toString();
        String coverFileName = UUID.randomUUID().toString().substring(0, 10) + ".jpg";
        String videoFileName = "video";
        File directory = new File(filePath);
        File videoFile = new File(filePath, videoFileName);
        File coverFile = new File(directory, coverFileName);

        // 将视频数据写入临时文件
        Files.copy(byteArrayInputStream, Paths.get(videoFile.getAbsolutePa
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值