重构部分表结构与接口,完成不带评论的帖子的增删改查

lzt
forely 2 weeks ago
parent 010cf3dc0f
commit 1c2a2ef67e

@ -1,12 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="luojia_channel@192.168.59.129" uuid="fef17b9b-50ef-45a3-bed5-7aa6704a7372">
<driver-ref>mysql.8</driver-ref>
<data-source source="LOCAL" name="0@192.168.59.129" uuid="ad808a5f-004d-4f31-9402-19010c1be1ab">
<driver-ref>redis</driver-ref>
<synchronize>true</synchronize>
<imported>true</imported>
<jdbc-driver>com.mysql.cj.jdbc.Driver</jdbc-driver>
<jdbc-url>jdbc:mysql://192.168.59.129:3306/luojia_channel?useUnicode=true&amp;characterEncoding=UTF-8&amp;autoReconnect=true&amp;serverTimezone=Asia/Shanghai</jdbc-url>
<jdbc-driver>jdbc.RedisDriver</jdbc-driver>
<jdbc-url>jdbc:redis://192.168.59.129:6379/0</jdbc-url>
<jdbc-additional-properties>
<property name="com.intellij.clouds.kubernetes.db.host.port" />
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
@ -14,12 +14,13 @@
</jdbc-additional-properties>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
<data-source source="LOCAL" name="0@192.168.59.129" uuid="ad808a5f-004d-4f31-9402-19010c1be1ab">
<driver-ref>redis</driver-ref>
<data-source source="LOCAL" name="luojia_channel@192.168.59.129" uuid="666814a4-5179-4ab7-96f4-c2a810ed102b">
<driver-ref>mysql.8</driver-ref>
<synchronize>true</synchronize>
<imported>true</imported>
<jdbc-driver>jdbc.RedisDriver</jdbc-driver>
<jdbc-url>jdbc:redis://192.168.59.129:6379/0</jdbc-url>
<remarks>$PROJECT_DIR$/service/src/main/resources/application.yaml</remarks>
<jdbc-driver>com.mysql.cj.jdbc.Driver</jdbc-driver>
<jdbc-url>jdbc:mysql://192.168.59.129:3306/luojia_channel?useUnicode=true&amp;characterEncoding=UTF-8&amp;autoReconnect=true&amp;serverTimezone=Asia/Shanghai</jdbc-url>
<jdbc-additional-properties>
<property name="com.intellij.clouds.kubernetes.db.host.port" />
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="SqlDialectMappings">
<file url="file://$PROJECT_DIR$/service/src/main/resources/db/luojia_channel.sql" dialect="GenericSQL" />
<file url="file://$PROJECT_DIR$/service/src/main/resources/db/luojia_channel.sql" dialect="MySQL" />
<file url="PROJECT" dialect="MySQL" />
</component>
</project>

@ -15,7 +15,9 @@ public class WebMvcConfig implements WebMvcConfigurer {
// 拦截器
registry.addInterceptor(authInterceptor)
.excludePathPatterns("/user/login",
"/user/register"
"/user/register",
"/post/page",
"/post/detail"
);
}

@ -12,6 +12,7 @@ import lombok.NoArgsConstructor;
public class UserDTO {
private Long userId;
private String username;
private String avatar;
private String accessToken;
private String refreshToken;
}

@ -1,12 +0,0 @@
package com.luojia_channel.common.dto;
import lombok.Data;
@Data
public class CreatePostDTO {
private String title;
private String content;
private Long userId;
private Long categoryId;
}

@ -0,0 +1,7 @@
package com.luojia_channel.common.exception;
public class PostException extends BaseException{
public PostException(String msg){
super(500, msg);
}
}

@ -2,6 +2,7 @@ package com.luojia_channel.common.utils;
import com.alibaba.ttl.TransmittableThreadLocal;
import com.luojia_channel.common.domain.UserDTO;
import com.luojia_channel.common.exception.UserException;
import java.util.Optional;
@ -15,15 +16,21 @@ public final class UserContext {
public static Long getUserId() {
UserDTO userInfoDTO = USER_THREAD_LOCAL.get();
return Optional.ofNullable(userInfoDTO).map(UserDTO::getUserId).orElse(null);
if(userInfoDTO == null){
throw new UserException("用户不存在");
}
return userInfoDTO.getUserId();
}
public static String getUsername() {
UserDTO userInfoDTO = USER_THREAD_LOCAL.get();
return Optional.ofNullable(userInfoDTO).map(UserDTO::getUsername).orElse(null);
}
public static String getAvatar() {
UserDTO userInfoDTO = USER_THREAD_LOCAL.get();
return Optional.ofNullable(userInfoDTO).map(UserDTO::getAvatar).orElse(null);
}
public static String getAccessToken() {
UserDTO userInfoDTO = USER_THREAD_LOCAL.get();

@ -101,6 +101,11 @@
<artifactId>minio</artifactId>
<version>8.5.12</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.15</version>
</dependency>
</dependencies>
<build>

@ -1,10 +1,12 @@
package com.luojia_channel.modules.file.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import org.springframework.web.multipart.MultipartFile;
@Data
@AllArgsConstructor
@Builder
public class UploadFileDTO {
private MultipartFile file;

@ -5,10 +5,12 @@ import com.luojia_channel.modules.file.dto.CompleteUploadDTO;
import com.luojia_channel.modules.file.dto.UploadChunkDTO;
import com.luojia_channel.modules.file.dto.UploadFileDTO;
import com.luojia_channel.modules.file.entity.LjFile;
import org.springframework.web.multipart.MultipartFile;
public interface FileService extends IService<LjFile> {
Boolean createBucket(String name);
Boolean deleteBucket(String name);
String uploadFile(MultipartFile file);
Long uploadFileAndGetFileId(UploadFileDTO uploadFileDTO);
Boolean uploadChunk(UploadChunkDTO chunkDTO);
Long completeUpload(CompleteUploadDTO completeDTO);

@ -13,29 +13,43 @@ import com.luojia_channel.modules.file.service.FileService;
import com.luojia_channel.modules.file.utils.GeneratePathUtil;
import com.luojia_channel.modules.file.utils.ValidateFileUtil;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import io.minio.messages.Item;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;
import org.springframework.web.multipart.MultipartFile;
import java.io.InputStream;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import static com.luojia_channel.modules.file.constants.FileConstant.CHUNK_BUCKET;
import static com.luojia_channel.modules.file.constants.FileConstant.CHUNK_PREFIX;
@Service
@Slf4j
@RequiredArgsConstructor
public class FileServiceImpl extends ServiceImpl<LjFileMapper, LjFile> implements FileService {
private final MinioClient minioClient;
private final RedisUtil redisUtil;
private final ValidateFileUtil validateFileUtil;
private final GeneratePathUtil generatePathUtil;
@Value("${minio.endpoint}")
private String endpoint;
//线程池
private static final ExecutorService SAVE_TO_DB_EXECUTOR = Executors.newFixedThreadPool(10);
private void init(){
createBucket("videos");
@ -67,6 +81,46 @@ public class FileServiceImpl extends ServiceImpl<LjFileMapper, LjFile> implement
return true;
}
// 普通上传MultipartFile
@Override
public String uploadFile(MultipartFile file){
String bucket;
String objectName;
String fileUrl;
try {
InputStream inputStream = file.getInputStream();
String fileName = file.getOriginalFilename();
String fileMd5 = DigestUtils.md5DigestAsHex(inputStream);
String fileType = detectFileType(file);
fileUrl = validateFileUtil.getExistedFileUrl(fileMd5);
if(fileUrl != null){
return fileUrl;
}
validateFileUtil.validateFile(new UploadFileDTO(file, fileType, fileMd5));
bucket = generatePathUtil.getBucketName(fileType);
objectName = generatePathUtil.getObjectName(fileName, fileMd5);
minioClient.putObject(
PutObjectArgs.builder()
.bucket(bucket)
.object(objectName)
.stream(file.getInputStream(),
file.getSize(), -1)
.build()
);
fileUrl = endpoint + "/" + bucket + "/" + objectName;
/* 线
SAVE_TO_DB_EXECUTOR.submit(() -> saveFileToDB(fileName, fileUrl, file.getSize(),
fileMd5, fileType, 1));
*/
saveFileToDB(fileName, fileUrl, file.getSize(),
fileMd5, fileType, 1);
} catch (Exception e){
log.error("文件上传失败: {}", e.getMessage());
throw new FileException("文件上传失败");
}
return fileUrl;
}
// 普通上传
@Override
@Transactional(rollbackFor = Exception.class)
@ -101,9 +155,6 @@ public class FileServiceImpl extends ServiceImpl<LjFileMapper, LjFile> implement
private Long saveFileToDB(String fileName, String fileUrl, Long fileSize,
String fileMd5, String fileType, Integer fileStatus){
Long userId = UserContext.getUserId();
if(userId == null){
throw new FileException("不存在的用户试图上传文件");
}
LjFile file= LjFile.builder()
.fileName(fileName)
.fileUrl(fileUrl)
@ -263,5 +314,29 @@ public class FileServiceImpl extends ServiceImpl<LjFileMapper, LjFile> implement
}
}
private String detectFileType(MultipartFile file) throws Exception {
// 获取 MIME 类型
String mimeType = file.getContentType();
if (mimeType == null || mimeType.isEmpty()) {
throw new IllegalArgumentException("Could not determine file type");
}
// 根据 MIME 类型分类
if (mimeType.startsWith("image/")) {
return "image";
} else if (mimeType.startsWith("video/")) {
return "video";
} else {
throw new FileException("Unsupported file type: " + mimeType);
}
}
private String getObjectUrl(MinioClient minioClient, String bucket, String objectName) throws Exception {
GetPresignedObjectUrlArgs args = GetPresignedObjectUrlArgs.builder()
.method(Method.GET)
.bucket(bucket)
.object(objectName)
.build();
return minioClient.getPresignedObjectUrl(args);
}
}

@ -99,4 +99,13 @@ public class ValidateFileUtil {
List<Integer> uploadedChunks = redisUtil.sGet(key);
return uploadedChunks.size() == totalChunks;
}
public String getExistedFileUrl(String fileMd5) {
LjFile file = ljFileMapper.selectOne(Wrappers.<LjFile>lambdaQuery()
.eq(LjFile::getFileMd5, fileMd5));
if(file == null){
return null;
}
return file.getFileUrl();
}
}

@ -0,0 +1,64 @@
package com.luojia_channel.modules.post.controller;
import com.luojia_channel.common.domain.Result;
import com.luojia_channel.common.domain.page.PageResponse;
import com.luojia_channel.common.utils.UserContext;
import com.luojia_channel.modules.post.dto.req.CommentPageQueryDTO;
import com.luojia_channel.modules.post.dto.resp.CommentInfoDTO;
import com.luojia_channel.modules.post.entity.Comment;
import com.luojia_channel.modules.post.service.CommentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/comments")
public class CommentController {
private final CommentService commentService;
@Autowired
public CommentController(CommentService commentService) {
this.commentService = commentService;
}
// 创建评论
@PostMapping
public Result<Void> saveComment(@RequestBody Comment comment) {
commentService.saveComment(comment);
return Result.success();
}
// 更新评论
@PutMapping("/{id}")
public Result<Void> updateComment(@PathVariable Long id, @RequestBody Comment comment) {
Long currentUserId = UserContext.getUserId();
commentService.updateComment(comment, currentUserId);
return Result.success();
}
// 删除评论
@DeleteMapping("/{id}")
public Result<Void> deleteComment(@PathVariable Long id) {
Long currentUserId = UserContext.getUserId();
commentService.deleteComment(id, currentUserId);
return Result.success();
}
// 根据帖子ID分页获取评论
@GetMapping("/list")
public Result<PageResponse<CommentInfoDTO>> getCommentsByPostId(@RequestBody CommentPageQueryDTO commentPageQueryDTO) {
PageResponse<CommentInfoDTO> commentList = commentService.getCommentsByPostId(commentPageQueryDTO);
return Result.success(commentList);
}
// 根据帖子ID获取嵌套评论
@GetMapping("/nested/post/{postId}")
public Result<List<CommentInfoDTO>> getNestedCommentsByPostId(@PathVariable Long postId) {
List<CommentInfoDTO> nestedComments = commentService.getNestedCommentsByPostId(postId);
return Result.success(nestedComments);
}
}

@ -1,16 +1,10 @@
package com.luojia_channel.modules.post.controller;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.luojia_channel.common.domain.Result;
import com.luojia_channel.common.domain.page.PageResponse;
import com.luojia_channel.modules.file.dto.UploadFileDTO;
import com.luojia_channel.modules.post.dto.req.PostCreateDTO;
import com.luojia_channel.modules.post.dto.req.PostSaveDTO;
import com.luojia_channel.modules.post.dto.req.PostPageQueryDTO;
import com.luojia_channel.modules.post.dto.req.PostUpdateDTO;
import com.luojia_channel.modules.post.dto.resp.PostBasicInfoDTO;
import com.luojia_channel.modules.post.entity.Post;
import com.luojia_channel.modules.post.service.PostService;
@ -27,46 +21,46 @@ public class PostController {
// 创建帖子
@PostMapping
public Result<Void> createPost(@RequestBody PostCreateDTO postCreateDTO) {
postService.createPost(postCreateDTO);
public Result<Void> createPost(@RequestBody PostSaveDTO postSaveDTO) {
postService.createPost(postSaveDTO);
return Result.success();
}
// 设置帖子封面
@PostMapping("/cover")
public Result<Void> setCover(@RequestParam("file") MultipartFile file,
@RequestParam("fileType") String fileType,
@RequestParam("fileMd5") String fileMd5){
UploadFileDTO fileDTO = UploadFileDTO.builder()
.file(file).fileType(fileType).fileMd5(fileMd5)
.build();
postService.setCover(fileDTO);
return Result.success();
public Result<String> setCover(@RequestParam("file") MultipartFile file){
return Result.success(postService.setCover(file));
}
// 更新帖子
@PutMapping()
public Result<Void> updatePost(@RequestBody PostUpdateDTO postUpdateDTO) {
postService.updatePost(postUpdateDTO);
@PutMapping
public Result<Void> updatePost(@RequestBody PostSaveDTO postSaveDTO) {
postService.updatePost(postSaveDTO);
return Result.success();
}
// 删除帖子
@DeleteMapping("/{id}")
public Result<Void> deletePost(@PathVariable Long id) {
@DeleteMapping
public Result<Void> deletePost(@RequestParam("id") Long id) {
postService.deletePost(id);
return Result.success();
}
// 根据ID获取帖子详情
@GetMapping("/{id}")
public Post getPostById(@PathVariable Long id) {
return postService.getById(id);
@GetMapping("/detail")
public Post getPostDetail(@RequestParam("id") Long id) {
return postService.getPostDetail(id);
}
// 分页查询帖子
@GetMapping
public Result<PageResponse<PostBasicInfoDTO>> pagePost(PostPageQueryDTO postPageQueryDTO) {
@GetMapping("/list")
public Result<PageResponse<PostBasicInfoDTO>> pagePost(@RequestBody PostPageQueryDTO postPageQueryDTO) {
return Result.success(postService.pagePost(postPageQueryDTO));
}
// 查看自己的帖子
@GetMapping("/of/me")
public Result<PageResponse<PostBasicInfoDTO>> pagePostOfMe(@RequestBody PostPageQueryDTO postPageQueryDTO) {
return Result.success(postService.pagePostOfMe(postPageQueryDTO));
}
}

@ -0,0 +1,9 @@
package com.luojia_channel.modules.post.dto.req;
import com.luojia_channel.common.domain.page.PageRequest;
import lombok.Data;
@Data
public class CommentPageQueryDTO extends PageRequest {
private Integer postId;
}

@ -3,9 +3,10 @@ package com.luojia_channel.modules.post.dto.req;
import lombok.Data;
@Data
public class PostCreateDTO {
public class PostSaveDTO {
private String title;
private String image;
private String content;
private Long userId;
private Long categoryId;
private Integer status; // 是否匿名
}

@ -1,10 +0,0 @@
package com.luojia_channel.modules.post.dto.req;
import lombok.Data;
@Data
public class PostUpdateDTO {
private String title;
private String content;
private Long categoryId;
}

@ -0,0 +1,26 @@
package com.luojia_channel.modules.post.dto.resp;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
import java.util.List;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class CommentInfoDTO {
private Long id;
private String content;
private Long userId;
private String postType;
private Long postId;
private Long parentCommentId;
private Long topId;
private LocalDateTime createTime;
private List<CommentInfoDTO> commentInfoDTOList;
}

@ -5,9 +5,16 @@ import lombok.Data;
@Data
public class PostBasicInfoDTO {
private Long id;
private String coverUrl;
private String image;
private String title;
private Integer likeCount;
private Integer commentCount;
private Integer favoriteCount;
private Boolean isLike;
private Long userId;
// 对于匿名的帖子,下述字段进行了处理,如匿名默认名称,头像
private String userName;
private String userAvatar;
}

@ -0,0 +1,22 @@
package com.luojia_channel.modules.post.dto.resp;
import com.luojia_channel.modules.post.entity.Comment;
import java.util.List;
public class PostInfoDTO {
private Long id;
private String image;
private String title;
private Integer likeCount;
private Integer commentCount;
private Integer favoriteCount;
private String content;
private Boolean isLike;
private Long userId;
private String userName;
private String userAvatar;
private List<Comment> commentList;
}

@ -0,0 +1,26 @@
package com.luojia_channel.modules.post.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@TableName("comment")
public class Comment {
private Long id;
private String content;
private Long userId;
private String postType;
private Long postId;
private Long parentCommentId;
private Long topId;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}

@ -8,6 +8,8 @@ import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Data
@NoArgsConstructor
@AllArgsConstructor
@ -17,14 +19,15 @@ public class Post {
@TableId(type = IdType.AUTO)
private Long id;
private String title;
private Long coverId;
private String image;
private String content;
private Integer status;
private Integer likeCount;
private Integer commentCount;
private Integer favoriteCount;
private Integer viewCount;
private Long userId;
private Long categoryId;
private String createTime;
private String updateTime;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}

@ -0,0 +1,8 @@
package com.luojia_channel.modules.post.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.luojia_channel.modules.post.entity.Comment;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface CommentMapper extends BaseMapper<Comment> {
}

@ -0,0 +1,21 @@
package com.luojia_channel.modules.post.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.luojia_channel.modules.post.dto.resp.CommentInfoDTO;
import com.luojia_channel.modules.post.entity.Comment;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public interface CommentService {
void saveComment(Comment comment);
void updateComment(Comment comment, Long userId);
void deleteComment(Long id, Long userId);
Page<CommentInfoDTO> getCommentsByPostId(Long postId, int pageNum, int pageSize);
List<CommentInfoDTO> getNestedCommentsByPostId(Long postId);
}

@ -1,7 +1,25 @@
package com.luojia_channel.modules.post.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.luojia_channel.common.domain.page.PageResponse;
import com.luojia_channel.modules.post.dto.req.PostSaveDTO;
import com.luojia_channel.modules.post.dto.req.PostPageQueryDTO;
import com.luojia_channel.modules.post.dto.resp.PostBasicInfoDTO;
import com.luojia_channel.modules.post.entity.Post;
import org.springframework.web.multipart.MultipartFile;
public interface PostService extends IService<Post> {
void createPost(PostSaveDTO postSaveDTO);
String setCover(MultipartFile file);
void updatePost(PostSaveDTO postSaveDTO);
void deletePost(Long id);
Post getPostDetail(Long id);
PageResponse<PostBasicInfoDTO> pagePost(PostPageQueryDTO postPageQueryDTO);
PageResponse<PostBasicInfoDTO> pagePostOfMe(PostPageQueryDTO postPageQueryDTO);
}

@ -0,0 +1,124 @@
package com.luojia_channel.modules.post.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.luojia_channel.common.exception.PostException;
import com.luojia_channel.modules.post.dto.resp.CommentInfoDTO;
import com.luojia_channel.modules.post.entity.Comment;
import com.luojia_channel.modules.post.mapper.CommentMapper;
import com.luojia_channel.modules.post.service.CommentService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
@RequiredArgsConstructor
public class CommentServiceImpl extends ServiceImpl<CommentMapper, Comment> implements CommentService {
private CommentMapper commentMapper;
@Override
public void saveComment(Comment comment) {
comment.setCreateTime(LocalDateTime.now());
comment.setUpdateTime(LocalDateTime.now());
commentMapper.insert(comment);
}
@Override
public void updateComment(Comment comment, Long userId) {
validatePostOwnership(comment.getId(), userId);
comment.setUpdateTime(LocalDateTime.now());
if(!updateById(comment)){
throw new PostException("更新帖子失败");
}
}
@Override
public void deleteComment(Long id, Long userId) {
validatePostOwnership(id, userId);
commentMapper.deleteById(id);
}
@Override
public Page<CommentInfoDTO> getCommentsByPostId(Long postId, int pageNum, int pageSize) {
LambdaQueryWrapper<Comment> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Comment::getPostId, postId)
.isNull(Comment::getParentCommentId)
.orderByAsc(Comment::getCreateTime);
Page<Comment> page = new Page<>(pageNum, pageSize);
commentMapper.selectPage(page, queryWrapper);
Page<CommentInfoDTO> commentInfoDTOS = new Page<>();
BeanUtils.copyProperties(page, commentInfoDTOS, "records");
List<CommentInfoDTO> records = convertToDTO(page.getRecords());
commentInfoDTOS.setRecords(records);
return commentInfoDTOS;
}
@Override
public List<CommentInfoDTO> getNestedCommentsByPostId(Long postId) {
LambdaQueryWrapper<Comment> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Comment::getPostId, postId)
.orderByAsc(Comment::getCreateTime);
List<Comment> comments = commentMapper.selectList(queryWrapper);
return buildNestedComments(comments);
}
private void validatePostOwnership(Long commentId, Long userId) {
Comment comment = commentMapper.selectById(commentId);
if (comment == null) {
throw new PostException("评论不存在");
}
if (!userId.equals(comment.getUserId())) {
throw new PostException("你无权操作他人的评论");
}
}
private List<CommentInfoDTO> convertToDTO(List<Comment> comments) {
List<CommentInfoDTO> dtos = new ArrayList<>();
for (Comment comment : comments) {
CommentInfoDTO dto = new CommentInfoDTO();
BeanUtils.copyProperties(comment, dto);
dtos.add(dto);
}
return dtos;
}
private List<CommentInfoDTO> buildNestedComments(List<Comment> comments) {
Map<Long, CommentInfoDTO> map = new HashMap<>();
List<CommentInfoDTO> rootComments = new ArrayList<>();
for (Comment comment : comments) {
CommentInfoDTO dto = new CommentInfoDTO();
BeanUtils.copyProperties(comment, dto);
map.put(comment.getId(), dto);
if (comment.getParentCommentId() == null) {
rootComments.add(dto);
} else {
CommentInfoDTO parentDto = map.get(comment.getParentCommentId());
if (parentDto != null && parentDto.getCommentInfoDTOList() == null) {
parentDto.setCommentInfoDTOList(new ArrayList<>());
}
if (parentDto != null) {
parentDto.getCommentInfoDTOList().add(dto);
}
}
}
return rootComments;
}
}

@ -1,25 +1,102 @@
package com.luojia_channel.modules.post.service.impl;
import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.luojia_channel.modules.file.mapper.LjFileMapper;
import com.luojia_channel.common.domain.page.PageResponse;
import com.luojia_channel.common.exception.PostException;
import com.luojia_channel.common.utils.PageUtil;
import com.luojia_channel.common.utils.RedisUtil;
import com.luojia_channel.common.utils.UserContext;
import com.luojia_channel.modules.file.service.impl.FileServiceImpl;
import com.luojia_channel.modules.post.dto.req.PostSaveDTO;
import com.luojia_channel.modules.post.dto.req.PostPageQueryDTO;
import com.luojia_channel.modules.post.dto.resp.PostBasicInfoDTO;
import com.luojia_channel.modules.post.entity.Post;
import com.luojia_channel.modules.post.mapper.PostMapper;
import com.luojia_channel.modules.post.service.PostService;
import com.luojia_channel.modules.post.utils.ValidatePostUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.time.LocalDateTime;
import java.util.concurrent.TimeUnit;
@Service
@RequiredArgsConstructor
public class PostServiceImpl extends ServiceImpl<PostMapper, Post> implements PostService {
private final PostMapper postMapper;
private final LjFileMapper fileMapper;
private String getUrlById(String id){
return fileMapper.selectById(id).getFileUrl();
private final FileServiceImpl fileService;
private final ValidatePostUtil validatePostUtil;
private final RedisUtil redisUtil;
@Override
public void createPost(PostSaveDTO postSaveDTO) {
validatePostUtil.validatePost(postSaveDTO);
Long userId = UserContext.getUserId();
Post post = BeanUtil.copyProperties(postSaveDTO, Post.class);
post.setUserId(userId);
if(!save(post)){
throw new PostException("创建帖子失败");
}
}
@Override
public String setCover(MultipartFile file) {
return fileService.uploadFile(file);
}
@Override
public void updatePost(PostSaveDTO postSaveDTO) {
validatePostUtil.validatePost(postSaveDTO);
Post post = BeanUtil.copyProperties(postSaveDTO, Post.class);
post.setUpdateTime(LocalDateTime.now());
if(!updateById(post)){
throw new PostException("更新帖子失败");
}
}
@Override
public void deletePost(Long id) {
validatePostUtil.validatePostOwnership(id);
int delete = postMapper.deleteById(id);
if(delete <= 0){
throw new PostException("删除帖子失败");
}
}
@Override
public Post getPostDetail(Long id) {
return redisUtil.safeGet("post:detail" + id.toString(), Post.class,
() -> {
Post post = getById(id);
if(post == null){
throw new PostException("帖子不存在或被删除");
}
return post;
}, 10, TimeUnit.MINUTES);
}
@Override
public PageResponse<PostBasicInfoDTO> pagePost(PostPageQueryDTO postPageQueryDTO) {
// TODO 目前分页查询直接按照创建时间顺序排序了,未来考虑加入多种规则
LambdaQueryWrapper<Post> queryWrapper = Wrappers.lambdaQuery(Post.class)
.orderByDesc(Post::getCreateTime);
IPage<Post> postPage = postMapper.selectPage(PageUtil.convert(postPageQueryDTO), queryWrapper);
return PageUtil.convert(postPage, PostBasicInfoDTO.class);
}
@Override
public PageResponse<PostBasicInfoDTO> pagePostOfMe(PostPageQueryDTO postPageQueryDTO) {
Long userId = UserContext.getUserId();
LambdaQueryWrapper<Post> queryWrapper = Wrappers.lambdaQuery(Post.class)
.eq(Post::getUserId, userId)
.orderByDesc(Post::getCreateTime);
IPage<Post> postPage = postMapper.selectPage(PageUtil.convert(postPageQueryDTO), queryWrapper);
return PageUtil.convert(postPage, PostBasicInfoDTO.class);
}
}

@ -1,4 +1,49 @@
package com.luojia_channel.modules.post.utils;
import cn.hutool.core.util.StrUtil;
import com.luojia_channel.common.exception.PostException;
import com.luojia_channel.common.utils.UserContext;
import com.luojia_channel.modules.post.dto.req.PostSaveDTO;
import com.luojia_channel.modules.post.entity.Post;
import com.luojia_channel.modules.post.mapper.PostMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
@Component
@RequiredArgsConstructor
public class ValidatePostUtil {
private final PostMapper postMapper;
public void validatePost(PostSaveDTO postSaveDTO) {
// 非空字段检验
if (StrUtil.isBlank(postSaveDTO.getTitle())) {
throw new PostException("标题不能为空");
}
if (StrUtil.isBlank(postSaveDTO.getImage())) {
throw new PostException("未设置图片");
}
if (StrUtil.isBlank(postSaveDTO.getContent())) {
throw new PostException("内容不能为空");
}
if (postSaveDTO.getCategoryId() == null || postSaveDTO.getCategoryId() <= 0) {
throw new PostException("分区不合法");
}
}
public void validatePostOwnership(Long id){
Long userId = UserContext.getUserId();
Post post = postMapper.selectById(id);
if(post == null){
throw new PostException("帖子不存在");
}
if(!userId.equals(post.getUserId())){
throw new PostException("你无权操作他人的帖子");
}
}
public void AIValidate(PostSaveDTO postSaveDTO){
}
}

@ -26,13 +26,7 @@ public class UserInfoController {
}
@PostMapping("/avatar")
public Result<Void> updateAvatar(@RequestParam("file") MultipartFile file,
@RequestParam("fileType") String fileType,
@RequestParam("fileMd5") String fileMd5) {
UploadFileDTO fileDTO = UploadFileDTO.builder()
.file(file).fileType(fileType).fileMd5(fileMd5)
.build();
userInfoService.updateAvatar(fileDTO);
return Result.success();
public Result<String> uploadAvatar(@RequestParam("file") MultipartFile file) {
return Result.success(userInfoService.uploadAvatar(file));
}
}

@ -12,5 +12,5 @@ public interface UserInfoService extends IService<User> {
void updatePassword(String password);
void updateAvatar(UploadFileDTO uploadFileDTO);
String uploadAvatar(MultipartFile file);
}

@ -16,6 +16,7 @@ import com.luojia_channel.modules.user.service.UserInfoService;
import com.luojia_channel.modules.user.utils.ValidateUserUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.time.LocalDateTime;
@ -26,8 +27,6 @@ public class UserInfoServiceImpl extends ServiceImpl<UserMapper, User> implement
private final UserMapper userMapper;
private final ValidateUserUtil validateUserUtil;
private final FileServiceImpl fileService;
private final GeneratePathUtil generatePathUtil;
private final ValidateFileUtil validateFileUtil;
@Override
public void updateInfo(UserChangeInfoDTO userChangeInfoDTO) {
@ -63,31 +62,7 @@ public class UserInfoServiceImpl extends ServiceImpl<UserMapper, User> implement
}
@Override
public void updateAvatar(UploadFileDTO uploadFileDTO) {
Long userId = UserContext.getUserId();
User user = userMapper.selectById(userId);
if(user == null){
throw new UserException("用户不存在");
}
validateFileUtil.validateFile(uploadFileDTO);
try {
/*
InputStream inputStream = uploadFileDTO.getFile().getInputStream();
String fileMd5 = DigestUtils.md5DigestAsHex(inputStream);
String fileType = "image";
uploadFileDTO.setFileType(fileType);
uploadFileDTO.setFileMd5(fileMd5);
*/
fileService.uploadFileAndGetFileId(uploadFileDTO);
String filePath = generatePathUtil
.getObjectName(uploadFileDTO.getFile().getOriginalFilename(),
uploadFileDTO.getFileMd5());
user.setAvatar(filePath);
updateById(user);
} catch (Exception e) {
throw new UserException("上传头像失败");
}
public String uploadAvatar(MultipartFile file) {
return fileService.uploadFile(file);
}
}

@ -0,0 +1,22 @@
INSERT INTO `post` (`title`, `image`, `content`, `status`, `like_count`, `comment_count`, `favorite_count`, `view_count`, `user_id`, `category_id`)
VALUES
('秋日散步记', 'http://example.com/images/post1.jpg', '今天去公园散步,看到了很多美丽的景色...', 0, 15, 8, 5, 100, 1, 1),
('美食推荐', 'http://example.com/images/post2.jpg', '这家餐厅的披萨非常好吃,强烈推荐给大家...', 1, 20, 12, 7, 150, 2, 2),
('旅行计划', 'http://example.com/images/post3.jpg', '计划下个月去云南旅游,期待已久的旅程...', 0, 10, 5, 3, 80, 3, 3),
('学习心得', 'http://example.com/images/post4.jpg', '最近学到了一个新的编程技巧,感觉很有用...', 0, 25, 18, 10, 200, 4, 4),
('电影分享', 'http://example.com/images/post5.jpg', '昨晚看了《无间道》,剧情非常精彩...', 1, 30, 20, 12, 250, 5, 5),
('健身日常', 'http://example.com/images/post6.jpg', '每天坚持锻炼身体,保持健康的生活方式...', 0, 5, 2, 1, 30, 6, 1),
('宠物趣事', 'http://example.com/images/post7.jpg', '我家的小狗今天做了件搞笑的事情...', 0, 8, 4, 2, 40, 7, 2),
('读书笔记', 'http://example.com/images/post8.jpg', '最近读了一本书,《活着》让人深思...', 1, 12, 6, 4, 60, 8, 3),
('科技前沿', 'http://example.com/images/post9.jpg', '最新的AI技术真的太神奇了改变了我们的生活...', 0, 18, 9, 6, 90, 9, 4),
('摄影技巧', 'http://example.com/images/post10.jpg', '分享几个摄影小技巧,让你的照片更加出色...', 0, 22, 11, 8, 110, 10, 5),
('音乐分享', 'http://example.com/images/post11.jpg', '最近发现了一首好听的歌,一起来听听吧...', 1, 7, 3, 2, 50, 1, 1),
('烹饪食谱', 'http://example.com/images/post12.jpg', '教大家做一道简单的家常菜...', 0, 14, 7, 5, 70, 2, 2),
('游戏体验', 'http://example.com/images/post13.jpg', '玩了一个新出的游戏,超级好玩...', 0, 21, 10, 7, 120, 3, 3),
('时尚搭配', 'http://example.com/images/post14.jpg', '分享我的最新穿搭,希望对你有所帮助...', 1, 9, 4, 3, 45, 4, 4),
('户外运动', 'http://example.com/images/post15.jpg', '周末和朋友们一起去徒步,真的很放松...', 0, 17, 8, 6, 85, 5, 5),
('艺术欣赏', 'http://example.com/images/post16.jpg', '欣赏了一些印象派画作,感受到了不一样的美...', 0, 11, 5, 2, 60, 6, 1),
('创业故事', 'http://example.com/images/post17.jpg', '一位朋友刚刚开始了他的创业之旅,很激动人心...', 1, 13, 6, 4, 75, 7, 2),
('健康饮食', 'http://example.com/images/post18.jpg', '分享一些健康的饮食习惯,让我们一起变得更健康...', 0, 19, 9, 7, 100, 8, 3),
('科技创新', 'http://example.com/images/post19.jpg', '最新的科技成果真是令人惊叹,未来可期...', 0, 24, 12, 8, 130, 9, 4),
('心灵感悟', 'http://example.com/images/post20.jpg', '写下了自己的内心感受,希望能够激励更多的人...', 1, 6, 3, 2, 55, 10, 5);

@ -9,7 +9,7 @@ CREATE TABLE `user` (
`phone` VARCHAR(20) UNIQUE COMMENT '注册手机号',
`email` VARCHAR(100) UNIQUE COMMENT '邮箱',
`student_id` VARCHAR(20) UNIQUE COMMENT '学号',
`avatar` VARCHAR(255) COMMENT '头像URL',
`avatar` VARCHAR(512) COMMENT '头像URL',
`gender` TINYINT DEFAULT 0 COMMENT '性别0未知1男2女',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
@ -56,8 +56,9 @@ DROP TABLE IF EXISTS `post`;
CREATE TABLE `post` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
`title` VARCHAR(255) NOT NULL COMMENT '标题',
`cover_id` BIGINT NOT NULL COMMENT '封面图片ID',
`image` VARCHAR(512) NOT NULL COMMENT '图片url',
`content` TEXT NOT NULL COMMENT '文字内容',
`status` TINYINT NOT NULL DEFAULT 0 COMMENT '是否匿名0否1是',
`like_count` INT DEFAULT 0 COMMENT '点赞数',
`comment_count` INT DEFAULT 0 COMMENT '评论数',
`favorite_count` INT DEFAULT 0 COMMENT '收藏数',
@ -76,8 +77,8 @@ DROP TABLE IF EXISTS `video`;
CREATE TABLE `video` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
`title` VARCHAR(255) NOT NULL COMMENT '标题',
`cover_id` BIGINT NOT NULL COMMENT '封面图片ID',
`video_file_id` BIGINT NOT NULL COMMENT '视频文件ID',
`cover` VARCHAR(512) NOT NULL COMMENT '封面图片url',
`video_file` VARCHAR(512) NOT NULL COMMENT '视频文件url',
`play_count` BIGINT DEFAULT 0 COMMENT '播放次数',
`like_count` INT DEFAULT 0 COMMENT '点赞数',
`comment_count` INT DEFAULT 0 COMMENT '评论数',

@ -1,19 +1,36 @@
# 本地开发环境
# lj:
# db:
# host: localhost
# password: 123456
# redis:
# host: localhost
# port: 6379
# password: 123456
# rabbitmq:
# host: localhost
# port: 15672
# username: root
# password: 123456
# minio:
# endpoint: http://localhost:9000
# accessKey: minioadmin
# secretKey: minioadmin
lj:
db:
host: localhost
password: 123456
host: 192.168.59.129
password: Forely123!
redis:
host: localhost
host: 192.168.59.129
port: 6379
password: 123456
password: Forely123!
rabbitmq:
host: localhost
port: 15672
username: root
password: 123456
host: 192.168.59.129
port: 5672
username: admin
password: Forely123!
minio:
endpoint: http://localhost:9000
accessKey: minioadmin
secretKey: minioadmin
endpoint: http://192.168.59.129:9000
accessKey: forely
secretKey: Forely123!

@ -0,0 +1,22 @@
INSERT INTO `post` (`title`, `image`, `content`, `status`, `like_count`, `comment_count`, `favorite_count`, `view_count`, `user_id`, `category_id`)
VALUES
('秋日散步记', 'http://example.com/images/post1.jpg', '今天去公园散步,看到了很多美丽的景色...', 0, 15, 8, 5, 100, 1, 1),
('美食推荐', 'http://example.com/images/post2.jpg', '这家餐厅的披萨非常好吃,强烈推荐给大家...', 1, 20, 12, 7, 150, 2, 2),
('旅行计划', 'http://example.com/images/post3.jpg', '计划下个月去云南旅游,期待已久的旅程...', 0, 10, 5, 3, 80, 3, 3),
('学习心得', 'http://example.com/images/post4.jpg', '最近学到了一个新的编程技巧,感觉很有用...', 0, 25, 18, 10, 200, 4, 4),
('电影分享', 'http://example.com/images/post5.jpg', '昨晚看了《无间道》,剧情非常精彩...', 1, 30, 20, 12, 250, 5, 5),
('健身日常', 'http://example.com/images/post6.jpg', '每天坚持锻炼身体,保持健康的生活方式...', 0, 5, 2, 1, 30, 6, 1),
('宠物趣事', 'http://example.com/images/post7.jpg', '我家的小狗今天做了件搞笑的事情...', 0, 8, 4, 2, 40, 7, 2),
('读书笔记', 'http://example.com/images/post8.jpg', '最近读了一本书,《活着》让人深思...', 1, 12, 6, 4, 60, 8, 3),
('科技前沿', 'http://example.com/images/post9.jpg', '最新的AI技术真的太神奇了改变了我们的生活...', 0, 18, 9, 6, 90, 9, 4),
('摄影技巧', 'http://example.com/images/post10.jpg', '分享几个摄影小技巧,让你的照片更加出色...', 0, 22, 11, 8, 110, 10, 5),
('音乐分享', 'http://example.com/images/post11.jpg', '最近发现了一首好听的歌,一起来听听吧...', 1, 7, 3, 2, 50, 1, 1),
('烹饪食谱', 'http://example.com/images/post12.jpg', '教大家做一道简单的家常菜...', 0, 14, 7, 5, 70, 2, 2),
('游戏体验', 'http://example.com/images/post13.jpg', '玩了一个新出的游戏,超级好玩...', 0, 21, 10, 7, 120, 3, 3),
('时尚搭配', 'http://example.com/images/post14.jpg', '分享我的最新穿搭,希望对你有所帮助...', 1, 9, 4, 3, 45, 4, 4),
('户外运动', 'http://example.com/images/post15.jpg', '周末和朋友们一起去徒步,真的很放松...', 0, 17, 8, 6, 85, 5, 5),
('艺术欣赏', 'http://example.com/images/post16.jpg', '欣赏了一些印象派画作,感受到了不一样的美...', 0, 11, 5, 2, 60, 6, 1),
('创业故事', 'http://example.com/images/post17.jpg', '一位朋友刚刚开始了他的创业之旅,很激动人心...', 1, 13, 6, 4, 75, 7, 2),
('健康饮食', 'http://example.com/images/post18.jpg', '分享一些健康的饮食习惯,让我们一起变得更健康...', 0, 19, 9, 7, 100, 8, 3),
('科技创新', 'http://example.com/images/post19.jpg', '最新的科技成果真是令人惊叹,未来可期...', 0, 24, 12, 8, 130, 9, 4),
('心灵感悟', 'http://example.com/images/post20.jpg', '写下了自己的内心感受,希望能够激励更多的人...', 1, 6, 3, 2, 55, 10, 5);

@ -9,7 +9,7 @@ CREATE TABLE `user` (
`phone` VARCHAR(20) UNIQUE COMMENT '注册手机号',
`email` VARCHAR(100) UNIQUE COMMENT '邮箱',
`student_id` VARCHAR(20) UNIQUE COMMENT '学号',
`avatar` VARCHAR(255) COMMENT '头像URL',
`avatar` VARCHAR(512) COMMENT '头像URL',
`gender` TINYINT DEFAULT 0 COMMENT '性别0未知1男2女',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
@ -23,6 +23,16 @@ CREATE TABLE `user` (
UNIQUE INDEX `uk_student_id` (`student_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
## 分类表
DROP TABLE IF EXISTS `category`;
CREATE TABLE `category` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
`name` VARCHAR(50) NOT NULL UNIQUE COMMENT '分类名称',
`description` VARCHAR(255) COMMENT '分类描述',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='分类表';
## 文件表
DROP TABLE IF EXISTS `lj_file`;
CREATE TABLE `lj_file` (
@ -35,7 +45,10 @@ CREATE TABLE `lj_file` (
`file_status` INT NOT NULL DEFAULT 0 COMMENT '文件状态0:正在上传, 1:上传成功, 2:失败或删除, 3:审核中)',
`user_id` BIGINT NOT NULL COMMENT '上传用户ID',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
INDEX idx_user_id (user_id),
INDEX idx_file_status (file_status),
INDEX idx_file_md5 (file_md5)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文件存储表';
## 图文帖子表
@ -43,48 +56,44 @@ DROP TABLE IF EXISTS `post`;
CREATE TABLE `post` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
`title` VARCHAR(255) NOT NULL COMMENT '标题',
`cover_id` BIGINT NOT NULL COMMENT '封面图片ID',
`image` VARCHAR(512) NOT NULL COMMENT '图片url',
`content` TEXT NOT NULL COMMENT '文字内容',
`status` TINYINT NOT NULL DEFAULT 0 COMMENT '是否匿名0否1是',
`like_count` INT DEFAULT 0 COMMENT '点赞数',
`comment_count` INT DEFAULT 0 COMMENT '评论数',
`favorite_count` INT DEFAULT 0 COMMENT '收藏数',
`view_count` INT DEFAULT 0 COMMENT '浏览数',
`user_id` BIGINT NOT NULL COMMENT '发布用户ID',
`category_id` BIGINT NOT NULL COMMENT '分类ID',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
FOREIGN KEY (`user_id`) REFERENCES `user`(`id`),
FOREIGN KEY (`cover_id`) REFERENCES `lj_file`(`id`)
INDEX idx_user_id (user_id),
INDEX idx_category_id (category_id),
INDEX idx_create_time (create_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='图文帖子表';
## 帖子图片关联表
DROP TABLE IF EXISTS `post_image`;
CREATE TABLE `post_image` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
`post_id` BIGINT NOT NULL COMMENT '图文帖子ID',
`file_id` BIGINT NOT NULL COMMENT '图片文件ID',
FOREIGN KEY (`post_id`) REFERENCES `post`(`id`),
FOREIGN KEY (`file_id`) REFERENCES `lj_file`(`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='图文帖子与图片关联表';
## 视频表
DROP TABLE IF EXISTS `video`;
CREATE TABLE `video` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
`title` VARCHAR(255) NOT NULL COMMENT '标题',
`cover_id` BIGINT NOT NULL COMMENT '封面图片ID',
`video_file_id` BIGINT NOT NULL COMMENT '视频文件ID',
`cover` VARCHAR(512) NOT NULL COMMENT '封面图片url',
`video_file` VARCHAR(512) NOT NULL COMMENT '视频文件url',
`play_count` BIGINT DEFAULT 0 COMMENT '播放次数',
`like_count` INT DEFAULT 0 COMMENT '点赞数',
`comment_count` INT DEFAULT 0 COMMENT '评论数',
`favorite_count` INT DEFAULT 0 COMMENT '收藏数',
`view_count` INT DEFAULT 0 COMMENT '浏览数',
`user_id` BIGINT NOT NULL COMMENT '发布用户ID',
`duration` INT NOT NULL COMMENT '视频时长(秒)',
`category` VARCHAR(50) NOT NULL COMMENT '分类(如“音乐”、“游戏”)',
`category_id` BIGINT NOT NULL COMMENT '分类ID',
`tags` VARCHAR(255) COMMENT '标签(逗号分隔)',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
FOREIGN KEY (`user_id`) REFERENCES `user`(`id`),
FOREIGN KEY (`cover_id`) REFERENCES `lj_file`(`id`),
FOREIGN KEY (`video_file_id`) REFERENCES `lj_file`(`id`)
INDEX idx_user_id (user_id),
INDEX idx_category_id (category_id),
INDEX idx_duration (duration),
INDEX idx_create_time (create_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='视频表';
## 评论表
@ -99,7 +108,46 @@ CREATE TABLE `comment` (
`top_id` BIGINT COMMENT '顶层评论ID',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
FOREIGN KEY (`user_id`) REFERENCES `user`(`id`)
INDEX idx_post_id (post_id),
INDEX idx_user_id (user_id),
INDEX idx_create_time (create_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='评论表';
## 点赞记录表
DROP TABLE IF EXISTS `like_record`;
CREATE TABLE `like_record` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
`user_id` BIGINT NOT NULL COMMENT '用户ID',
`post_type` VARCHAR(20) NOT NULL COMMENT '帖子类型post/video',
`post_id` BIGINT NOT NULL COMMENT '关联的帖子ID',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
INDEX idx_user_id (user_id),
INDEX idx_post_type (post_type),
INDEX idx_post_id (post_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='点赞记录表';
## 收藏记录表
DROP TABLE IF EXISTS `favorite_record`;
CREATE TABLE `favorite_record` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
`user_id` BIGINT NOT NULL COMMENT '用户ID',
`post_type` VARCHAR(20) NOT NULL COMMENT '帖子类型post/video',
`post_id` BIGINT NOT NULL COMMENT '关联的帖子ID',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
INDEX idx_user_id (user_id),
INDEX idx_post_type (post_type),
INDEX idx_post_id (post_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='收藏记录表';
## 浏览记录表
DROP TABLE IF EXISTS `view_record`;
CREATE TABLE `view_record` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
`user_id` BIGINT NOT NULL COMMENT '用户ID',
`post_type` VARCHAR(20) NOT NULL COMMENT '帖子类型post/video',
`post_id` BIGINT NOT NULL COMMENT '关联的帖子ID',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
INDEX idx_user_id (user_id),
INDEX idx_post_type (post_type),
INDEX idx_post_id (post_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='浏览记录表';

Loading…
Cancel
Save