From b5bb4909e4944d928930aaa4d1ae86b72ffa8df4 Mon Sep 17 00:00:00 2001 From: ovo Date: Sun, 8 Dec 2024 15:28:12 +0800 Subject: [PATCH] feat: --- docs/api/video-api.md | 9 + docs/api/video-api.txt | 218 ++++++++++++++++++ pom.xml | 40 ++++ scripts/train_recommend_model.py | 44 ++++ .../guwan/backend/config/HadoopConfig.java | 28 +++ .../guwan/backend/config/SwaggerConfig.java | 29 +++ .../backend/controller/VideoController.java | 160 +++++++++++++ .../controller/VideoStreamController.java | 66 ++++++ .../com/guwan/backend/dto/video/VideoDTO.java | 57 +++++ .../java/com/guwan/backend/entity/Video.java | 42 ++++ .../com/guwan/backend/entity/VideoLike.java | 19 ++ .../guwan/backend/mapper/VideoLikeMapper.java | 9 + .../com/guwan/backend/mapper/VideoMapper.java | 9 + .../backend/service/DeepRecommendService.java | 114 +++++++++ .../backend/service/HdfsStorageService.java | 82 +++++++ .../service/VideoRecommendService.java | 21 ++ .../guwan/backend/service/VideoService.java | 28 +++ .../impl/VideoRecommendServiceImpl.java | 143 ++++++++++++ .../service/impl/VideoServiceImpl.java | 215 +++++++++++++++++ .../com/guwan/backend/util/MinioUtil.java | 12 + src/main/resources/application.yml | 83 ++++--- .../db/migration/V3__create_video_table.sql | 20 ++ .../migration/V4__create_video_like_table.sql | 10 + .../V5__create_user_behavior_table.sql | 14 ++ .../V6__create_video_tag_weight_table.sql | 9 + .../V7__create_user_interest_table.sql | 10 + 26 files changed, 1461 insertions(+), 30 deletions(-) create mode 100644 docs/api/video-api.md create mode 100644 docs/api/video-api.txt create mode 100644 scripts/train_recommend_model.py create mode 100644 src/main/java/com/guwan/backend/config/HadoopConfig.java create mode 100644 src/main/java/com/guwan/backend/config/SwaggerConfig.java create mode 100644 src/main/java/com/guwan/backend/controller/VideoController.java create mode 100644 src/main/java/com/guwan/backend/controller/VideoStreamController.java create mode 100644 src/main/java/com/guwan/backend/dto/video/VideoDTO.java create mode 100644 src/main/java/com/guwan/backend/entity/Video.java create mode 100644 src/main/java/com/guwan/backend/entity/VideoLike.java create mode 100644 src/main/java/com/guwan/backend/mapper/VideoLikeMapper.java create mode 100644 src/main/java/com/guwan/backend/mapper/VideoMapper.java create mode 100644 src/main/java/com/guwan/backend/service/DeepRecommendService.java create mode 100644 src/main/java/com/guwan/backend/service/HdfsStorageService.java create mode 100644 src/main/java/com/guwan/backend/service/VideoRecommendService.java create mode 100644 src/main/java/com/guwan/backend/service/VideoService.java create mode 100644 src/main/java/com/guwan/backend/service/impl/VideoRecommendServiceImpl.java create mode 100644 src/main/java/com/guwan/backend/service/impl/VideoServiceImpl.java create mode 100644 src/main/resources/db/migration/V3__create_video_table.sql create mode 100644 src/main/resources/db/migration/V4__create_video_like_table.sql create mode 100644 src/main/resources/db/migration/V5__create_user_behavior_table.sql create mode 100644 src/main/resources/db/migration/V6__create_video_tag_weight_table.sql create mode 100644 src/main/resources/db/migration/V7__create_user_interest_table.sql diff --git a/docs/api/video-api.md b/docs/api/video-api.md new file mode 100644 index 0000000..3c53ebc --- /dev/null +++ b/docs/api/video-api.md @@ -0,0 +1,9 @@ +# 视频模块接口文档 + +## 基础信息 +- 基础路径: `/api/videos` +- 请求头: 需要携带 `Authorization: Bearer {token}` (除了获取视频列表和视频详情) + +## 接口列表 + +### 1. 上传视频 \ No newline at end of file diff --git a/docs/api/video-api.txt b/docs/api/video-api.txt new file mode 100644 index 0000000..82353b9 --- /dev/null +++ b/docs/api/video-api.txt @@ -0,0 +1,218 @@ +# 视频模块接口文档 + +## 基础信息 +- 基础路径: `/api/videos` +- 请求头: 需要携带 `Authorization: Bearer {token}` (除了获取视频列表和视频详情) + +## 接口列表 + +### 1. 上传视频 +POST /api/videos/upload + +// 请求参数 (multipart/form-data) +{ + file: File, // 视频文件 + title: string, // 视频标题 + description: string, // 视频描述 + tags?: string // 视频标签,可选,多个标签用逗号分隔 +} + +// 响应 +{ + code: number, // 200表示成功 + message: string, // 响应消息 + data: { + id: number, // 视频ID + title: string, // 视频标题 + description: string, // 视频描述 + url: string, // 视频URL + coverUrl: string, // 封面URL + duration: number, // 视频时长(秒) + size: number, // 文件大小(字节) + status: string, // 状态:DRAFT-草稿,PUBLISHED-已发布,DELETED-已删除 + userId: number, // 上传用户ID + username: string, // 上传用户名 + createdTime: string, // 创建时间 + updatedTime: string, // 更新时间 + viewCount: number, // 观看次数 + likeCount: number, // 点赞次数 + tags: string, // 标签 + hasLiked: boolean // 当前用户是否已点赞 + } +} + +### 2. 获取视频列表 +GET /api/videos?pageNum=1&pageSize=10&keyword=xxx + +// 请求参数 (query) +{ + pageNum?: number, // 页码,默认1 + pageSize?: number, // 每页条数,默认10 + keyword?: string // 搜索关键词,可选 +} + +// 响应 +{ + code: number, + message: string, + data: { + records: Array<{ // 视频列表 + id: number, + title: string, + description: string, + url: string, + coverUrl: string, + duration: number, + size: number, + status: string, + userId: number, + username: string, + createdTime: string, + updatedTime: string, + viewCount: number, + likeCount: number, + tags: string, + hasLiked: boolean + }>, + total: number, // 总记录数 + size: number, // 每页条数 + current: number, // 当前页码 + pages: number // 总页数 + } +} + +### 3. 获取视频详情 +GET /api/videos/{id} + +// 响应 +{ + code: number, + message: string, + data: { + id: number, + title: string, + description: string, + url: string, + coverUrl: string, + duration: number, + size: number, + status: string, + userId: number, + username: string, + createdTime: string, + updatedTime: string, + viewCount: number, + likeCount: number, + tags: string, + hasLiked: boolean + } +} + +### 4. 更新视频信息 +PUT /api/videos/{id} + +// 请求体 +{ + title: string, + description: string, + tags?: string +} + +// 响应 +{ + code: number, + message: string, + data: VideoDTO // 同上面的视频详情 +} + +### 5. 删除视频 +DELETE /api/videos/{id} + +// 响应 +{ + code: number, + message: string, + data: null +} + +### 6. 视频点赞/取消点赞 +POST /api/videos/{id}/like + +// 响应 +{ + code: number, + message: string, + data: null +} + +### 7. 增加观看次数 +POST /api/videos/{id}/view + +// 响应 +{ + code: number, + message: string, + data: null +} + +### 8. 获取推荐视频 +GET /api/videos/recommend?limit=10 + +// 请求参数 (query) +{ + limit?: number // 返回数量,默认10 +} + +// 响应 +{ + code: number, + message: string, + data: Array // 视频列表 +} + +### 9. 获取相似视频 +GET /api/videos/{id}/similar?limit=10 + +// 请求参数 (query) +{ + limit?: number // 返回数量,默认10 +} + +// 响应 +{ + code: number, + message: string, + data: Array // 视频列表 +} + +## 错误码说明 +{ + 200: "操作成功", + 400: "请求参数错误", + 401: "未登录或token已过期", + 403: "无权限执行此操作", + 404: "资源不存在", + 500: "服务器内部错误" +} + +## 数据结构 + +### VideoDTO +{ + id: number; // 视频ID + title: string; // 视频标题 + description: string; // 视频描述 + url: string; // 视频URL + coverUrl: string; // 封面URL + duration: number; // 视频时长(秒) + size: number; // 文件大小(字节) + status: string; // 状态:DRAFT-草稿,PUBLISHED-已发布,DELETED-已删除 + userId: number; // 上传用户ID + username: string; // 上传用户名 + createdTime: string; // 创建时间 + updatedTime: string; // 更新时间 + viewCount: number; // 观看次数 + likeCount: number; // 点赞次数 + tags: string; // 标签,多个用逗号分隔 + hasLiked: boolean; // 当前用户是否已点赞 +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index b151597..2a177c3 100644 --- a/pom.xml +++ b/pom.xml @@ -174,6 +174,46 @@ 2.11.0 + + + ai.djl + api + 0.25.0 + + + + + ai.djl.pytorch + pytorch-engine + 0.25.0 + + + + + ai.djl.pytorch + pytorch-model-zoo + 0.25.0 + + + + + org.apache.hadoop + hadoop-client + 3.3.6 + + + org.apache.hadoop + hadoop-hdfs + 3.3.6 + + + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + 2.3.0 + + diff --git a/scripts/train_recommend_model.py b/scripts/train_recommend_model.py new file mode 100644 index 0000000..19f382a --- /dev/null +++ b/scripts/train_recommend_model.py @@ -0,0 +1,44 @@ +import torch +import torch.nn as nn +import torch.optim as optim +import pandas as pd +from sklearn.preprocessing import StandardScaler + +class RecommendModel(nn.Module): + def __init__(self, input_size, embedding_size): + super().__init__() + self.encoder = nn.Sequential( + nn.Linear(input_size, 256), + nn.ReLU(), + nn.Dropout(0.3), + nn.Linear(256, 128), + nn.ReLU(), + nn.Linear(128, embedding_size) + ) + + def forward(self, x): + return self.encoder(x) + +def train_model(): + # 加载数据 + videos_df = pd.read_csv('videos.csv') + behaviors_df = pd.read_csv('behaviors.csv') + + # 数据预处理 + scaler = StandardScaler() + features = scaler.fit_transform(videos_df[['duration', 'view_count', 'like_count']]) + + # 创建模型 + model = RecommendModel(input_size=features.shape[1], embedding_size=128) + criterion = nn.CosineEmbeddingLoss() + optimizer = optim.Adam(model.parameters()) + + # 训练模型 + for epoch in range(100): + # ... 训练代码 + + # 保存模型 + torch.save(model.state_dict(), '../src/main/resources/models/recommend_model.pt') + +if __name__ == '__main__': + train_model() \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/config/HadoopConfig.java b/src/main/java/com/guwan/backend/config/HadoopConfig.java new file mode 100644 index 0000000..c1cd653 --- /dev/null +++ b/src/main/java/com/guwan/backend/config/HadoopConfig.java @@ -0,0 +1,28 @@ +package com.guwan.backend.config; + +import org.apache.hadoop.fs.FileSystem; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class HadoopConfig { + + @Value("${hadoop.fs.defaultFS}") + private String fsDefaultFS; + + @Value("${hadoop.username}") + private String username; + + @Bean + public FileSystem fileSystem() throws Exception { + org.apache.hadoop.conf.Configuration conf = new org.apache.hadoop.conf.Configuration(); + conf.set("fs.defaultFS", fsDefaultFS); + // 设置副本数 + conf.set("dfs.replication", "3"); + // 设置块大小,适合大文件存储 + conf.set("dfs.blocksize", "128m"); + + return FileSystem.get(conf); + } +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/config/SwaggerConfig.java b/src/main/java/com/guwan/backend/config/SwaggerConfig.java new file mode 100644 index 0000000..9ab9f6c --- /dev/null +++ b/src/main/java/com/guwan/backend/config/SwaggerConfig.java @@ -0,0 +1,29 @@ +package com.guwan.backend.config; + +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.info.License; +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.security.SecurityScheme; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SwaggerConfig { + + @Bean + public OpenAPI springShopOpenAPI() { + return new OpenAPI() + .info(new Info() + .title("视频平台 API") + .description("视频平台后端接口文档") + .version("v1.0.0") + .license(new License().name("Apache 2.0").url("http://springdoc.org"))) + .components(new Components() + .addSecuritySchemes("bearer-jwt", + new SecurityScheme() + .type(SecurityScheme.Type.HTTP) + .scheme("bearer") + .bearerFormat("JWT"))); + } +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/controller/VideoController.java b/src/main/java/com/guwan/backend/controller/VideoController.java new file mode 100644 index 0000000..e4b5596 --- /dev/null +++ b/src/main/java/com/guwan/backend/controller/VideoController.java @@ -0,0 +1,160 @@ +package com.guwan.backend.controller; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.guwan.backend.annotation.OperationLog; +import com.guwan.backend.common.Result; +import com.guwan.backend.dto.video.VideoDTO; +import com.guwan.backend.service.VideoService; +import com.guwan.backend.service.VideoRecommendService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; + +@Slf4j +@Tag(name = "视频管理", description = "视频相关接口") +@RestController +@RequestMapping("/api/videos") +@RequiredArgsConstructor +public class VideoController { + + private final VideoService videoService; + private final VideoRecommendService recommendService; + + @Operation(summary = "上传视频", description = "上传视频文件并返回视频信息") + @SecurityRequirement(name = "bearer-jwt") + @PostMapping("/upload") + public Result uploadVideo( + @Parameter(description = "视频文件") @RequestParam("file") MultipartFile file, + @Parameter(description = "视频标题") @RequestParam("title") String title, + @Parameter(description = "视频描述") @RequestParam("description") String description, + @Parameter(description = "视频标签,多个用逗号分隔") @RequestParam(value = "tags", required = false) String tags) { + try { + VideoDTO video = videoService.uploadVideo(file, title, description, tags); + return Result.success(video); + } catch (Exception e) { + log.error("上传视频失败", e); + return Result.error(e.getMessage()); + } + } + + @Operation(summary = "更新视频信息") + @SecurityRequirement(name = "bearer-jwt") + @PutMapping("/{id}") + public Result updateVideo( + @Parameter(description = "视频ID") @PathVariable Long id, + @RequestBody VideoDTO videoDTO) { + try { + videoDTO.setId(id); + return Result.success(videoService.updateVideo(videoDTO)); + } catch (Exception e) { + log.error("更新视频失败", e); + return Result.error(e.getMessage()); + } + } + + @Operation(summary = "删除视频") + @SecurityRequirement(name = "bearer-jwt") + @DeleteMapping("/{id}") + public Result deleteVideo( + @Parameter(description = "视频ID") @PathVariable Long id) { + try { + videoService.deleteVideo(id); + return Result.success(); + } catch (Exception e) { + log.error("删除视频失败", e); + return Result.error(e.getMessage()); + } + } + + @Operation(summary = "获取视频详情") + @GetMapping("/{id}") + public Result getVideo( + @Parameter(description = "视频ID") @PathVariable Long id) { + try { + VideoDTO video = videoService.getVideoById(id); + if (video == null) { + return Result.notFound("视频不存在"); + } + return Result.success(video); + } catch (Exception e) { + log.error("获取视频失败", e); + return Result.error(e.getMessage()); + } + } + + @Operation(summary = "获取视频列表", description = "支持分页和关键词搜索") + @GetMapping + public Result> getVideoList( + @Parameter(description = "页码") @RequestParam(defaultValue = "1") Integer pageNum, + @Parameter(description = "每页条数") @RequestParam(defaultValue = "10") Integer pageSize, + @Parameter(description = "搜索关键词") @RequestParam(required = false) String keyword) { + try { + return Result.success(videoService.getVideoList(pageNum, pageSize, keyword)); + } catch (Exception e) { + log.error("获取视频列表失败", e); + return Result.error(e.getMessage()); + } + } + + @Operation(summary = "增加视频观看次数") + @PostMapping("/{id}/view") + public Result incrementViewCount( + @Parameter(description = "视频ID") @PathVariable Long id) { + try { + videoService.incrementViewCount(id); + return Result.success(); + } catch (Exception e) { + log.error("增加观看次数失败", e); + return Result.error(e.getMessage()); + } + } + + @Operation(summary = "视频点赞/取消点赞") + @SecurityRequirement(name = "bearer-jwt") + @PostMapping("/{id}/like") + public Result toggleLike( + @Parameter(description = "视频ID") @PathVariable Long id) { + try { + videoService.toggleLike(id); + return Result.success(); + } catch (Exception e) { + log.error("点赞失败", e); + return Result.error(e.getMessage()); + } + } + + @Operation(summary = "获取推荐视频") + @SecurityRequirement(name = "bearer-jwt") + @GetMapping("/recommend") + public Result> getRecommendVideos( + @Parameter(description = "返回数量") @RequestParam(defaultValue = "10") Integer limit) { + try { + Long userId = securityUtil.getCurrentUserId(); + return Result.success(recommendService.getRecommendVideos(userId, limit)); + } catch (Exception e) { + log.error("获取推荐视频失败", e); + return Result.error(e.getMessage()); + } + } + + @Operation(summary = "获取相似视频") + @GetMapping("/{id}/similar") + public Result> getSimilarVideos( + @Parameter(description = "视频ID") @PathVariable Long id, + @Parameter(description = "返回数量") @RequestParam(defaultValue = "10") Integer limit) { + try { + return Result.success(recommendService.getSimilarVideos(id, limit)); + } catch (Exception e) { + log.error("获取相似视频失败", e); + return Result.error(e.getMessage()); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/controller/VideoStreamController.java b/src/main/java/com/guwan/backend/controller/VideoStreamController.java new file mode 100644 index 0000000..66a4833 --- /dev/null +++ b/src/main/java/com/guwan/backend/controller/VideoStreamController.java @@ -0,0 +1,66 @@ +package com.guwan.backend.controller; + +import com.guwan.backend.entity.Video; +import com.guwan.backend.service.HdfsStorageService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.io.InputStreamResource; +import org.springframework.core.io.ResourceRegion; +import org.springframework.http.*; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; +import java.io.InputStream; + +@Slf4j +@RestController +@RequestMapping("/api/stream") +@RequiredArgsConstructor +public class VideoStreamController { + + private final HdfsStorageService hdfsStorageService; + private static final long CHUNK_SIZE = 1024 * 1024; // 1MB chunks + + @GetMapping("/video/{id}") + public ResponseEntity streamVideo( + @PathVariable Long id, + @RequestHeader(value = "Range", required = false) String rangeHeader) { + try { + // 获取视频路径 + Video video = videoMapper.selectById(id); + if (video == null) { + return ResponseEntity.notFound().build(); + } + + // 获取视频流 + InputStream videoStream = hdfsStorageService.getVideoStream(video.getUrl()); + InputStreamResource resource = new InputStreamResource(videoStream); + + // 获取视频元数据 + VideoMetadata metadata = hdfsStorageService.getVideoMetadata(video.getUrl()); + long contentLength = metadata.getSize(); + + // 处理Range请求 + HttpRange range = rangeHeader == null ? null : HttpRange.parseRanges(rangeHeader).get(0); + ResourceRegion region; + + if (range != null) { + long start = range.getRangeStart(contentLength); + long end = range.getRangeEnd(contentLength); + long rangeLength = Math.min(CHUNK_SIZE, end - start + 1); + region = new ResourceRegion(resource, start, rangeLength); + } else { + long rangeLength = Math.min(CHUNK_SIZE, contentLength); + region = new ResourceRegion(resource, 0, rangeLength); + } + + return ResponseEntity.status(HttpStatus.PARTIAL_CONTENT) + .contentType(MediaTypeFactory.getMediaType(resource).orElse(MediaType.APPLICATION_OCTET_STREAM)) + .body(region); + + } catch (IOException e) { + log.error("视频流处理失败", e); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/dto/video/VideoDTO.java b/src/main/java/com/guwan/backend/dto/video/VideoDTO.java new file mode 100644 index 0000000..190c6ca --- /dev/null +++ b/src/main/java/com/guwan/backend/dto/video/VideoDTO.java @@ -0,0 +1,57 @@ +package com.guwan.backend.dto.video; + +import lombok.Data; +import java.time.LocalDateTime; +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "视频信息DTO") +@Data +public class VideoDTO { + @Schema(description = "视频ID") + private Long id; + + @Schema(description = "视频标题") + private String title; + + @Schema(description = "视频描述") + private String description; + + @Schema(description = "视频URL") + private String url; + + @Schema(description = "封面URL") + private String coverUrl; + + @Schema(description = "视频时长(秒)") + private Long duration; + + @Schema(description = "文件大小(字节)") + private Long size; + + @Schema(description = "状态:DRAFT-草稿,PUBLISHED-已发布,DELETED-已删除") + private String status; + + @Schema(description = "上传用户ID") + private Long userId; + + @Schema(description = "上传用户名") + private String username; + + @Schema(description = "创建时间") + private LocalDateTime createdTime; + + @Schema(description = "更新时间") + private LocalDateTime updatedTime; + + @Schema(description = "观看次数") + private Integer viewCount; + + @Schema(description = "点赞次数") + private Integer likeCount; + + @Schema(description = "标签,多个用逗号分隔") + private String tags; + + @Schema(description = "当前用户是否已点赞") + private Boolean hasLiked; +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/entity/Video.java b/src/main/java/com/guwan/backend/entity/Video.java new file mode 100644 index 0000000..eff638a --- /dev/null +++ b/src/main/java/com/guwan/backend/entity/Video.java @@ -0,0 +1,42 @@ +package com.guwan.backend.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import java.time.LocalDateTime; + +@Data +@TableName("video") +public class Video { + @TableId(type = IdType.AUTO) + private Long id; + + private String title; // 视频标题 + + private String description; // 视频描述 + + private String url; // 视频URL + + private String coverUrl; // 封面图URL + + private Long duration; // 视频时长(秒) + + private Long size; // 文件大小(字节) + + private String status; // 状态:DRAFT-草稿,PUBLISHED-已发布,DELETED-已删除 + + private Long userId; // 上传用户ID + + private String username; // 上传用户名 + + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createdTime; + + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updatedTime; + + private Integer viewCount; // 观看次数 + + private Integer likeCount; // 点赞次数 + + private String tags; // 标签,逗号分隔 +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/entity/VideoLike.java b/src/main/java/com/guwan/backend/entity/VideoLike.java new file mode 100644 index 0000000..70c6738 --- /dev/null +++ b/src/main/java/com/guwan/backend/entity/VideoLike.java @@ -0,0 +1,19 @@ +package com.guwan.backend.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import java.time.LocalDateTime; + +@Data +@TableName("video_like") +public class VideoLike { + @TableId(type = IdType.AUTO) + private Long id; + + private Long videoId; + + private Long userId; + + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createdTime; +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/mapper/VideoLikeMapper.java b/src/main/java/com/guwan/backend/mapper/VideoLikeMapper.java new file mode 100644 index 0000000..f189b14 --- /dev/null +++ b/src/main/java/com/guwan/backend/mapper/VideoLikeMapper.java @@ -0,0 +1,9 @@ +package com.guwan.backend.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.guwan.backend.entity.VideoLike; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface VideoLikeMapper extends BaseMapper { +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/mapper/VideoMapper.java b/src/main/java/com/guwan/backend/mapper/VideoMapper.java new file mode 100644 index 0000000..05c5c0e --- /dev/null +++ b/src/main/java/com/guwan/backend/mapper/VideoMapper.java @@ -0,0 +1,9 @@ +package com.guwan.backend.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.guwan.backend.entity.Video; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface VideoMapper extends BaseMapper