From d8b1f6f95969cd6af33b5e92111d6e1d794dbb81 Mon Sep 17 00:00:00 2001 From: MiniDay <372403923@qq.com> Date: Thu, 6 Apr 2023 04:49:23 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=BC=80=E5=8F=91=E4=B8=AD...?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + .../blog/config/BlogExceptionHandler.java | 25 +++ .../blog/controller/AttachController.java | 51 ++++-- .../application/blog/entity/AttachEntity.java | 8 + .../application/blog/entity/BlogEntity.java | 6 - .../blog/entity/mapper/AttachMapper.java | 16 +- .../blog/entity/mapper/BlogMapper.java | 8 - .../blog/entity/repo/AttachRepository.java | 11 ++ .../blog/entity/repo/BlogRepository.java | 22 +-- .../blog/service/IAttachService.java | 22 +++ .../blog/service/impl/AttachService.java | 147 ++++++++++++++++++ .../blog/service/impl/BlogService.java | 69 +++++--- .../application/blog/util/BlogUtils.java | 20 +-- .../blog/vo/attach/AttachInfoResponseVO.java | 8 +- .../blog/vo/blog/BlogInfoResponseVO.java | 2 + .../blog/vo/blog/BlogUpdateRequireVO.java | 7 +- .../vo/setting/SettingInfoResponseVO.java | 2 + .../blog/vo/user/UserCreateRequireVO.java | 2 + .../blog/vo/user/UserInfoResponseVO.java | 2 + .../blog/vo/user/UserLoginRequireVO.java | 3 + .../blog/vo/user/UserUpdateRequireVO.java | 2 + .../src/main/resources/application.yml | 8 +- blog-frontend/src/views/BlogEditView.vue | 18 +-- blog-frontend/src/views/BlogReadView.vue | 15 +- 24 files changed, 378 insertions(+), 98 deletions(-) diff --git a/.gitignore b/.gitignore index 3deede0..61c633b 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,5 @@ out/ node_modules/ dist/ *.d.ts + +uploads/ \ No newline at end of file diff --git a/blog-backend/src/main/java/cn/hamster3/application/blog/config/BlogExceptionHandler.java b/blog-backend/src/main/java/cn/hamster3/application/blog/config/BlogExceptionHandler.java index 5bbb3de..07ec484 100644 --- a/blog-backend/src/main/java/cn/hamster3/application/blog/config/BlogExceptionHandler.java +++ b/blog-backend/src/main/java/cn/hamster3/application/blog/config/BlogExceptionHandler.java @@ -13,6 +13,7 @@ import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; +import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; @@ -53,6 +54,18 @@ public class BlogExceptionHandler { return ResponseVO.failed(e.getMessage()); } + @ExceptionHandler(IOException.class) + public ResponseVO onIOException(IOException e) { + if ("dev".equals(environment.getProperty("spring.profiles.active"))) { + log.error("", e); + StringWriter writer = new StringWriter(); + e.printStackTrace(new PrintWriter(writer)); + // StringWriter 不需要 close() + return new ResponseVO<>(403, e.getMessage(), writer.toString()); + } + return ResponseVO.failed(e.getMessage()); + } + @ExceptionHandler(Exception.class) public ResponseVO onException(Exception e) { if ("dev".equals(environment.getProperty("spring.profiles.active"))) { @@ -64,4 +77,16 @@ public class BlogExceptionHandler { } return ResponseVO.failed(e.getMessage()); } + + @ExceptionHandler(Throwable.class) + public ResponseVO onThrowable(Throwable e) { + if ("dev".equals(environment.getProperty("spring.profiles.active"))) { + log.error("", e); + StringWriter writer = new StringWriter(); + e.printStackTrace(new PrintWriter(writer)); + // StringWriter 不需要 close() + return new ResponseVO<>(403, e.getMessage(), writer.toString()); + } + return ResponseVO.failed(e.getMessage()); + } } diff --git a/blog-backend/src/main/java/cn/hamster3/application/blog/controller/AttachController.java b/blog-backend/src/main/java/cn/hamster3/application/blog/controller/AttachController.java index 854d41e..cdd7bc6 100644 --- a/blog-backend/src/main/java/cn/hamster3/application/blog/controller/AttachController.java +++ b/blog-backend/src/main/java/cn/hamster3/application/blog/controller/AttachController.java @@ -1,37 +1,66 @@ package cn.hamster3.application.blog.controller; +import cn.hamster3.application.blog.service.IAttachService; +import cn.hamster3.application.blog.vo.PageableVO; import cn.hamster3.application.blog.vo.ResponseVO; +import cn.hamster3.application.blog.vo.attach.AttachInfoResponseVO; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import org.springframework.core.io.InputStreamResource; +import org.springframework.data.domain.PageRequest; import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; +import java.io.IOException; + @Tag(name = "AttachController", description = "附件相关接口") @RestController @RequestMapping(value = "/api/v1/attach", produces = MediaType.APPLICATION_JSON_VALUE) public class AttachController { + @Resource + private IAttachService attachService; + @PostMapping("/") - public ResponseVO createAttach(@RequestBody MultipartFile file) { - return ResponseVO.success(); + @Operation(summary = "新建附件") + public ResponseVO createAttach(@RequestParam MultipartFile file) throws IOException { + return attachService.createAttach(file); } @GetMapping("/{attachID}/") - public ResponseVO getAttach(@PathVariable String attachID) { - return ResponseVO.success(); + @Operation(summary = "获取附件信息") + public ResponseVO getAttachInfo(@PathVariable Long attachID) { + return attachService.getAttachInfo(attachID); + } + + @GetMapping("/{attachID}/content") + @Operation(summary = "获取附件内容") + public ResponseEntity getAttachContent(@PathVariable Long attachID) throws IOException { + return attachService.getAttachContent(attachID); } @GetMapping("/") - public ResponseVO getAttachList() { - return ResponseVO.success(); + @Operation(summary = "获取附件列表") + public ResponseVO> getAttachList( + @Parameter(description = "页码", example = "0") int page, + @Parameter(description = "大小", example = "10") int size + ) { + return attachService.getAttachList(PageRequest.of(page, size)); } @PutMapping("/{attachID}/") - public ResponseVO modifyAttach(@PathVariable String attachID, @RequestBody MultipartFile file) { - return ResponseVO.success(); + @Operation(summary = "更新附件") + public ResponseVO updateAttach(@PathVariable Long attachID, @RequestParam MultipartFile file) throws IOException { + return attachService.updateAttach(attachID, file); } @DeleteMapping("/{attachID}/") - public ResponseVO deleteAttach(@PathVariable String attachID) { - return ResponseVO.success(); + @Operation(summary = "删除附件") + public ResponseVO deleteAttach(@PathVariable Long attachID) { + return attachService.deleteAttach(attachID); } -} + +} \ No newline at end of file diff --git a/blog-backend/src/main/java/cn/hamster3/application/blog/entity/AttachEntity.java b/blog-backend/src/main/java/cn/hamster3/application/blog/entity/AttachEntity.java index 29b3f17..80e6cc7 100644 --- a/blog-backend/src/main/java/cn/hamster3/application/blog/entity/AttachEntity.java +++ b/blog-backend/src/main/java/cn/hamster3/application/blog/entity/AttachEntity.java @@ -21,6 +21,14 @@ public class AttachEntity { @Column(name = "id", nullable = false, updatable = false) private Long id; + @Setter + @Column(name = "name", nullable = false) + private String name; + + @Setter + @Column(name = "content_type", nullable = false) + private String contentType; + @Setter @Lob @Basic(fetch = FetchType.LAZY) diff --git a/blog-backend/src/main/java/cn/hamster3/application/blog/entity/BlogEntity.java b/blog-backend/src/main/java/cn/hamster3/application/blog/entity/BlogEntity.java index a161994..d8d4017 100644 --- a/blog-backend/src/main/java/cn/hamster3/application/blog/entity/BlogEntity.java +++ b/blog-backend/src/main/java/cn/hamster3/application/blog/entity/BlogEntity.java @@ -4,8 +4,6 @@ import jakarta.persistence.*; import lombok.Getter; import lombok.Setter; import lombok.ToString; -import org.hibernate.annotations.JdbcTypeCode; -import org.hibernate.type.SqlTypes; import org.springframework.data.annotation.CreatedBy; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.LastModifiedBy; @@ -32,10 +30,6 @@ public class BlogEntity { @Column(name = "abstracts", nullable = false, length = 512) private String abstracts; - @Column(name = "password", length = 60) - @JdbcTypeCode(SqlTypes.VARCHAR) - private String password; - @ToString.Exclude @Lob @Basic(fetch = FetchType.LAZY) diff --git a/blog-backend/src/main/java/cn/hamster3/application/blog/entity/mapper/AttachMapper.java b/blog-backend/src/main/java/cn/hamster3/application/blog/entity/mapper/AttachMapper.java index 58e9a5f..47b77cc 100644 --- a/blog-backend/src/main/java/cn/hamster3/application/blog/entity/mapper/AttachMapper.java +++ b/blog-backend/src/main/java/cn/hamster3/application/blog/entity/mapper/AttachMapper.java @@ -3,20 +3,24 @@ package cn.hamster3.application.blog.entity.mapper; import cn.hamster3.application.blog.entity.AttachEntity; import cn.hamster3.application.blog.entity.UserEntity; import cn.hamster3.application.blog.vo.attach.AttachInfoResponseVO; +import cn.hamster3.application.blog.vo.user.UserInfoResponseVO; import org.jetbrains.annotations.NotNull; import org.mapstruct.Mapper; import org.mapstruct.MappingConstants; import org.mapstruct.ReportingPolicy; -import java.util.UUID; - @Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE, componentModel = MappingConstants.ComponentModel.SPRING) public interface AttachMapper { AttachInfoResponseVO entityToInfoVO(AttachEntity entity); - @NotNull - @SuppressWarnings("unused") - default UUID map(@NotNull UserEntity value) { - return value.getId(); + default UserInfoResponseVO mapToInfoVO(@NotNull UserEntity value) { + return new UserInfoResponseVO( + value.getId(), + value.getEmail(), + value.getNickname(), + value.getRole(), + value.getCreateTime(), + value.getUpdateTime() + ); } } diff --git a/blog-backend/src/main/java/cn/hamster3/application/blog/entity/mapper/BlogMapper.java b/blog-backend/src/main/java/cn/hamster3/application/blog/entity/mapper/BlogMapper.java index 02e3ee1..5cc9f39 100644 --- a/blog-backend/src/main/java/cn/hamster3/application/blog/entity/mapper/BlogMapper.java +++ b/blog-backend/src/main/java/cn/hamster3/application/blog/entity/mapper/BlogMapper.java @@ -10,20 +10,12 @@ import org.mapstruct.Mapper; import org.mapstruct.MappingConstants; import org.mapstruct.ReportingPolicy; -import java.util.UUID; - @Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE, componentModel = MappingConstants.ComponentModel.SPRING) public interface BlogMapper { BlogEntity voToEntity(BlogUpdateRequireVO vo); BlogInfoResponseVO entityToInfoVO(BlogEntity entity); - @NotNull - @SuppressWarnings("unused") - default UUID mapToUUID(@NotNull UserEntity value) { - return value.getId(); - } - default UserInfoResponseVO mapToInfoVO(@NotNull UserEntity value) { return new UserInfoResponseVO( value.getId(), diff --git a/blog-backend/src/main/java/cn/hamster3/application/blog/entity/repo/AttachRepository.java b/blog-backend/src/main/java/cn/hamster3/application/blog/entity/repo/AttachRepository.java index cf5fa14..3a827f6 100644 --- a/blog-backend/src/main/java/cn/hamster3/application/blog/entity/repo/AttachRepository.java +++ b/blog-backend/src/main/java/cn/hamster3/application/blog/entity/repo/AttachRepository.java @@ -5,11 +5,22 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; +import org.springframework.transaction.annotation.Transactional; import java.util.UUID; public interface AttachRepository extends JpaRepository, JpaSpecificationExecutor { + boolean existsByIdAndCreator_Id(Long id, UUID id1); + @Query("select a from AttachEntity a where a.id = ?1") + AttachEntity findByIdWithContent(Long id); + + @Transactional + @Modifying + @Query("update AttachEntity a set a.data = ?1, a.contentType = ?2 where a.id = ?3") + void updateDataAndContentTypeById(byte[] data, String contentType, Long id); + @Query("select a from AttachEntity a where a.creator.id = ?1 order by a.createTime DESC") Page findByCreator_IdOrderByCreateTimeDesc(UUID id, Pageable pageable); } \ No newline at end of file diff --git a/blog-backend/src/main/java/cn/hamster3/application/blog/entity/repo/BlogRepository.java b/blog-backend/src/main/java/cn/hamster3/application/blog/entity/repo/BlogRepository.java index ec82bb8..0565ba8 100644 --- a/blog-backend/src/main/java/cn/hamster3/application/blog/entity/repo/BlogRepository.java +++ b/blog-backend/src/main/java/cn/hamster3/application/blog/entity/repo/BlogRepository.java @@ -3,22 +3,26 @@ package cn.hamster3.application.blog.entity.repo; import cn.hamster3.application.blog.entity.BlogEntity; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.repository.*; -import org.springframework.transaction.annotation.Transactional; +import org.springframework.data.jpa.repository.EntityGraph; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Query; import java.util.Optional; import java.util.UUID; public interface BlogRepository extends JpaRepository, JpaSpecificationExecutor { - @Query("select b from BlogEntity b where b.creator.id = ?1 order by b.createTime DESC") - Page findByCreator_IdOrderByCreateTimeDesc(UUID id, Pageable pageable); + boolean existsByIdAndCreator_Id(Long id, UUID id1); + + @EntityGraph(attributePaths = {"creator"}) + @Query("select b from BlogEntity b where b.id = ?1") + BlogEntity findByIdWithCreator(Long id); @EntityGraph(attributePaths = {"content"}) @Query("select b from BlogEntity b where b.id = ?1") - Optional findByIDWithContent(Long id); + Optional findByIdWithContent(Long id); + + @Query("select b from BlogEntity b where b.creator.id = ?1 order by b.createTime DESC") + Page findByCreator_IdOrderByCreateTimeDesc(UUID id, Pageable pageable); - @Transactional - @Modifying - @Query("update BlogEntity b set b.title = ?1, b.abstracts = ?2, b.password = ?3, b.content = ?4 where b.id = ?5") - void updateTitleAndAbstractsAndPasswordAndContentById(String title, String abstracts, String password, String content, Long id); } \ No newline at end of file diff --git a/blog-backend/src/main/java/cn/hamster3/application/blog/service/IAttachService.java b/blog-backend/src/main/java/cn/hamster3/application/blog/service/IAttachService.java index e37d5c7..b3d15a6 100644 --- a/blog-backend/src/main/java/cn/hamster3/application/blog/service/IAttachService.java +++ b/blog-backend/src/main/java/cn/hamster3/application/blog/service/IAttachService.java @@ -1,4 +1,26 @@ package cn.hamster3.application.blog.service; +import cn.hamster3.application.blog.vo.PageableVO; +import cn.hamster3.application.blog.vo.ResponseVO; +import cn.hamster3.application.blog.vo.attach.AttachInfoResponseVO; +import org.jetbrains.annotations.NotNull; +import org.springframework.core.io.InputStreamResource; +import org.springframework.data.domain.Pageable; +import org.springframework.http.ResponseEntity; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; + public interface IAttachService { + @NotNull ResponseVO createAttach(@NotNull MultipartFile file) throws IOException; + + @NotNull ResponseVO getAttachInfo(@NotNull Long attachID); + + @NotNull ResponseEntity getAttachContent(@NotNull Long attachID) throws IOException; + + @NotNull ResponseVO> getAttachList(@NotNull Pageable pageable); + + @NotNull ResponseVO updateAttach(@NotNull Long attachID, @NotNull MultipartFile file) throws IOException; + + @NotNull ResponseVO deleteAttach(@NotNull Long attachID); } diff --git a/blog-backend/src/main/java/cn/hamster3/application/blog/service/impl/AttachService.java b/blog-backend/src/main/java/cn/hamster3/application/blog/service/impl/AttachService.java index f932faf..472392f 100644 --- a/blog-backend/src/main/java/cn/hamster3/application/blog/service/impl/AttachService.java +++ b/blog-backend/src/main/java/cn/hamster3/application/blog/service/impl/AttachService.java @@ -1,8 +1,155 @@ package cn.hamster3.application.blog.service.impl; +import cn.hamster3.application.blog.config.security.BlogUser; +import cn.hamster3.application.blog.entity.AttachEntity; +import cn.hamster3.application.blog.entity.mapper.AttachMapper; +import cn.hamster3.application.blog.entity.repo.AttachRepository; import cn.hamster3.application.blog.service.IAttachService; +import cn.hamster3.application.blog.util.BlogUtils; +import cn.hamster3.application.blog.vo.PageableVO; +import cn.hamster3.application.blog.vo.ResponseVO; +import cn.hamster3.application.blog.vo.attach.AttachInfoResponseVO; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; +import org.springframework.core.io.InputStreamResource; +import org.springframework.data.domain.Pageable; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; + +@Slf4j @Service public class AttachService implements IAttachService { + private static final File ATTACH_FOLDER = new File("uploads"); + + static { + if (ATTACH_FOLDER.mkdirs()) { + log.info("attach folder created!"); + } + } + + @Resource + private AttachMapper attachMapper; + @Resource + private AttachRepository attachRepo; + + @Override + public @NotNull ResponseVO createAttach(@NotNull MultipartFile file) throws IOException { + ResponseVO checked = BlogUtils.checkAuthorPermission(); + if (checked != null) { + return checked; + } + log.info("prepare to save file: {}({} bytes)", file.getOriginalFilename(), file.getSize()); + AttachEntity attachEntity = new AttachEntity(); + attachEntity.setName(file.getOriginalFilename()); + attachEntity.setContentType(file.getContentType()); + attachEntity.setData(file.getBytes()); + attachEntity = attachRepo.save(attachEntity); + + File localCacheFile = new File(ATTACH_FOLDER, String.valueOf(attachEntity.getId())); + Files.copy(file.getInputStream(), localCacheFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + + return ResponseVO.success(attachEntity.getId()); + } + + @Override + public @NotNull ResponseVO getAttachInfo(@NotNull Long attachID) { + AttachEntity entity = attachRepo.findById(attachID).orElse(null); + if (entity == null) { + return ResponseVO.notFound(); + } + return ResponseVO.success(attachMapper.entityToInfoVO(entity)); + } + + @Override + public @NotNull ResponseEntity getAttachContent(@NotNull Long attachID) throws IOException { + File localCacheFile = new File(ATTACH_FOLDER, String.valueOf(attachID)); + AttachEntity entity; + if (localCacheFile.exists()) { + entity = attachRepo.findById(attachID).orElse(null); + } else { + entity = attachRepo.findByIdWithContent(attachID); + } + if (entity == null) { + return ResponseEntity.notFound().build(); + } + if (!localCacheFile.exists()) { + try (ByteArrayInputStream stream = new ByteArrayInputStream(entity.getData())) { + Files.copy(stream, localCacheFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + } + } + InputStream stream = Files.newInputStream(localCacheFile.toPath()); + return ResponseEntity + .ok() + .header("Cache-Control", "no-cache, no-store, must-revalidate") + .header("Content-Disposition", String.format("attachment; filename=\"%s\"", entity.getName())) + .header("Pragma", "no-cache") + .header("Expires", "0") + .contentLength(localCacheFile.length()) + .contentType(MediaType.parseMediaType(entity.getContentType())) + .body(new InputStreamResource(stream)); + } + + @Override + public @NotNull ResponseVO> getAttachList(@NotNull Pageable pageable) { + return PageableVO.success(attachRepo.findAll(pageable).map(o -> attachMapper.entityToInfoVO(o))); + } + + @Override + public @NotNull ResponseVO updateAttach(@NotNull Long attachID, @NotNull MultipartFile file) throws IOException { + ResponseVO checked = BlogUtils.checkAuthorPermission(); + if (checked != null) { + return checked; + } + if (!attachRepo.existsById(attachID)) { + return ResponseVO.notFound(); + } + attachRepo.updateDataAndContentTypeById(file.getBytes(), file.getContentType(), attachID); + + File localCacheFile = new File(ATTACH_FOLDER, String.valueOf(attachID)); + Files.copy(file.getInputStream(), localCacheFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + log.info("written attach data to local cache file: {}", localCacheFile.getName()); + + return ResponseVO.success(); + } + + @Override + public @NotNull ResponseVO deleteAttach(@NotNull Long attachID) { + BlogUser user = BlogUtils.getCurrentUser().orElse(null); + if (user == null) { + return ResponseVO.unauthorized(); + } + switch (user.getRole()) { + case AUTHOR -> { + if (!attachRepo.existsByIdAndCreator_Id(attachID, user.getId())) { + return ResponseVO.failed("该附件不存在或不属于你!"); + } + } + case ADMIN -> { + if (!attachRepo.existsById(attachID)) { + return ResponseVO.failed("该附件不存在!"); + } + } + default -> { + return ResponseVO.failed("你没有这个权限!"); + } + } + attachRepo.deleteById(attachID); + + File localCacheFile = new File(ATTACH_FOLDER, String.valueOf(attachID)); + if (localCacheFile.delete()) { + log.info("deleted local attach cache file: {}", localCacheFile.getName()); + } + + return ResponseVO.success(); + } } diff --git a/blog-backend/src/main/java/cn/hamster3/application/blog/service/impl/BlogService.java b/blog-backend/src/main/java/cn/hamster3/application/blog/service/impl/BlogService.java index c40fdb1..3c5a175 100644 --- a/blog-backend/src/main/java/cn/hamster3/application/blog/service/impl/BlogService.java +++ b/blog-backend/src/main/java/cn/hamster3/application/blog/service/impl/BlogService.java @@ -1,9 +1,8 @@ package cn.hamster3.application.blog.service.impl; import cn.hamster3.application.blog.config.security.BlogUser; -import cn.hamster3.application.blog.config.security.UserAuditorAware; +import cn.hamster3.application.blog.constant.UserRole; import cn.hamster3.application.blog.entity.BlogEntity; -import cn.hamster3.application.blog.entity.UserEntity; import cn.hamster3.application.blog.entity.mapper.BlogMapper; import cn.hamster3.application.blog.entity.repo.BlogRepository; import cn.hamster3.application.blog.service.IBlogService; @@ -26,16 +25,13 @@ public class BlogService implements IBlogService { @Resource private BlogRepository blogRepo; - @Resource - private UserAuditorAware userAuditorAware; - @Override public @NotNull ResponseVO createBlog(@NotNull BlogUpdateRequireVO requireVO) { - log.info("create blog vo: {}", requireVO); - UserEntity user = userAuditorAware.getCurrentAuditor().orElse(null); - if (user == null) { - return ResponseVO.unauthorized(); + ResponseVO checked = BlogUtils.checkAuthorPermission(); + if (checked != null) { + return checked; } + log.info("create blog vo: {}", requireVO); BlogEntity entity = blogMapper.voToEntity(requireVO); entity = blogRepo.save(entity); return ResponseVO.success(entity.getId()); @@ -43,7 +39,7 @@ public class BlogService implements IBlogService { @Override public @NotNull ResponseVO getBlogInfo(@NotNull Long blogID) { - return blogRepo.findByIDWithContent(blogID) + return blogRepo.findByIdWithContent(blogID) .map(o -> ResponseVO.success(blogMapper.entityToInfoVO(o))) .orElseThrow(() -> new IllegalArgumentException("未找到该文章!")); } @@ -55,30 +51,55 @@ public class BlogService implements IBlogService { @Override public @NotNull ResponseVO updateBlog(@NotNull Long blogID, @NotNull BlogUpdateRequireVO requireVO) { - if (!blogRepo.existsById(blogID)) { - return ResponseVO.failed("该博文不存在!"); - } BlogUser user = BlogUtils.getCurrentUser().orElse(null); if (user == null) { return ResponseVO.unauthorized(); } - //todo 权限检查 - blogRepo.updateTitleAndAbstractsAndPasswordAndContentById( - requireVO.getTitle(), - requireVO.getAbstracts(), - requireVO.getPassword(), - requireVO.getContent(), - blogID - ); + log.info("update blog vo: {}", requireVO); + BlogEntity blogEntity = blogRepo.findByIdWithCreator(blogID); + if (blogEntity == null) { + return ResponseVO.failed("该博文不存在!"); + } + if (user.getRole() == UserRole.GUEST) { + return ResponseVO.failed("你没有这个权限!"); + } + if (user.getRole() != UserRole.ADMIN) { + if (!blogEntity.getCreator().getId().equals(user.getId())) { + return ResponseVO.failed("你没有这个权限!"); + } + } + blogEntity.setTitle(requireVO.getTitle()); + blogEntity.setAbstracts(requireVO.getAbstracts()); + blogEntity.setContent(requireVO.getContent()); + blogEntity.setTop(requireVO.getTop()); + blogEntity.setPublish(requireVO.getPublish()); + blogEntity.setTags(requireVO.getTags()); + blogRepo.save(blogEntity); return ResponseVO.success(); } @Override public @NotNull ResponseVO removeBlog(@NotNull Long blogID) { - if (!blogRepo.existsById(blogID)) { - return ResponseVO.failed("该博文不存在!"); + BlogUser user = BlogUtils.getCurrentUser().orElse(null); + if (user == null) { + return ResponseVO.unauthorized(); } - //todo 权限检查 + switch (user.getRole()) { + case AUTHOR -> { + if (!blogRepo.existsByIdAndCreator_Id(blogID, user.getId())) { + return ResponseVO.failed("该博文不存在或不属于你!"); + } + } + case ADMIN -> { + if (!blogRepo.existsById(blogID)) { + return ResponseVO.failed("该博文不存在!"); + } + } + default -> { + return ResponseVO.failed("你没有这个权限!"); + } + } + blogRepo.deleteById(blogID); return ResponseVO.success(); } diff --git a/blog-backend/src/main/java/cn/hamster3/application/blog/util/BlogUtils.java b/blog-backend/src/main/java/cn/hamster3/application/blog/util/BlogUtils.java index f04e248..d241b29 100644 --- a/blog-backend/src/main/java/cn/hamster3/application/blog/util/BlogUtils.java +++ b/blog-backend/src/main/java/cn/hamster3/application/blog/util/BlogUtils.java @@ -23,14 +23,9 @@ public class BlogUtils { public static Optional getCurrentUser() { Authentication authentication = getCurrentAuthentication(); if (authentication == null) { - log.info("current user authentication: null"); return Optional.empty(); } log.info("current user authentication: {}", authentication); - log.info("current user authentication getPrincipal: {}", authentication.getPrincipal()); - log.info("current user authentication getCredentials: {}", authentication.getCredentials()); - log.info("current user authentication getDetails: {}", authentication.getDetails()); - log.info("current user authentication getAuthorities: {}", authentication.getAuthorities()); if (!authentication.isAuthenticated()) { return Optional.empty(); } @@ -45,14 +40,21 @@ public class BlogUtils { return getCurrentUser().map(BlogUser::getId); } - @NotNull - public static UserRole getCurrentUserRole() { - return getCurrentUser().map(BlogUser::getRole).orElse(UserRole.GUEST); + @Nullable + public static ResponseVO checkAuthorPermission() { + BlogUser user = getCurrentUser().orElse(null); + if (user == null) { + return ResponseVO.unauthorized(); + } + return switch (user.getRole()) { + case AUTHOR, ADMIN -> null; + default -> ResponseVO.failed("你没有这个权限!"); + }; } @Nullable public static ResponseVO checkAdminPermission() { - BlogUser user = BlogUtils.getCurrentUser().orElse(null); + BlogUser user = getCurrentUser().orElse(null); if (user == null) { return ResponseVO.unauthorized(); } diff --git a/blog-backend/src/main/java/cn/hamster3/application/blog/vo/attach/AttachInfoResponseVO.java b/blog-backend/src/main/java/cn/hamster3/application/blog/vo/attach/AttachInfoResponseVO.java index 7faa530..105240f 100644 --- a/blog-backend/src/main/java/cn/hamster3/application/blog/vo/attach/AttachInfoResponseVO.java +++ b/blog-backend/src/main/java/cn/hamster3/application/blog/vo/attach/AttachInfoResponseVO.java @@ -1,19 +1,23 @@ package cn.hamster3.application.blog.vo.attach; +import cn.hamster3.application.blog.vo.user.UserInfoResponseVO; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; import java.util.Date; -import java.util.UUID; @Data +@NoArgsConstructor @AllArgsConstructor public class AttachInfoResponseVO { @NotNull private Long id; @NotNull - private UUID creator; + private UserInfoResponseVO creator; + @NotNull + private UserInfoResponseVO updater; @NotNull private Date createTime; @NotNull diff --git a/blog-backend/src/main/java/cn/hamster3/application/blog/vo/blog/BlogInfoResponseVO.java b/blog-backend/src/main/java/cn/hamster3/application/blog/vo/blog/BlogInfoResponseVO.java index c35ab08..41cbb91 100644 --- a/blog-backend/src/main/java/cn/hamster3/application/blog/vo/blog/BlogInfoResponseVO.java +++ b/blog-backend/src/main/java/cn/hamster3/application/blog/vo/blog/BlogInfoResponseVO.java @@ -4,11 +4,13 @@ import cn.hamster3.application.blog.vo.user.UserInfoResponseVO; import lombok.AllArgsConstructor; import lombok.Data; import jakarta.validation.constraints.NotNull; +import lombok.NoArgsConstructor; import java.util.Date; import java.util.Set; @Data +@NoArgsConstructor @AllArgsConstructor public class BlogInfoResponseVO { @NotNull diff --git a/blog-backend/src/main/java/cn/hamster3/application/blog/vo/blog/BlogUpdateRequireVO.java b/blog-backend/src/main/java/cn/hamster3/application/blog/vo/blog/BlogUpdateRequireVO.java index 302191e..f3e4c10 100644 --- a/blog-backend/src/main/java/cn/hamster3/application/blog/vo/blog/BlogUpdateRequireVO.java +++ b/blog-backend/src/main/java/cn/hamster3/application/blog/vo/blog/BlogUpdateRequireVO.java @@ -1,15 +1,16 @@ package cn.hamster3.application.blog.vo.blog; -import jakarta.annotation.Nullable; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; import org.hibernate.validator.constraints.Length; import java.util.Set; @Data +@NoArgsConstructor @AllArgsConstructor public class BlogUpdateRequireVO { @Length(max = 32, message = "标题长度不能超过 32 个字符!") @@ -19,10 +20,6 @@ public class BlogUpdateRequireVO { @Length(max = 512, message = "摘要长度不能超过 512 个字符!") private String abstracts; - @Nullable - @Length(max = 16, message = "密码最大长度不能超过 16 个字符!") - private String password; - @NotBlank(message = "博客文章内容不能为空!") private String content; diff --git a/blog-backend/src/main/java/cn/hamster3/application/blog/vo/setting/SettingInfoResponseVO.java b/blog-backend/src/main/java/cn/hamster3/application/blog/vo/setting/SettingInfoResponseVO.java index 74a1b3d..d75e2f6 100644 --- a/blog-backend/src/main/java/cn/hamster3/application/blog/vo/setting/SettingInfoResponseVO.java +++ b/blog-backend/src/main/java/cn/hamster3/application/blog/vo/setting/SettingInfoResponseVO.java @@ -3,10 +3,12 @@ package cn.hamster3.application.blog.vo.setting; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; import java.util.Date; @Data +@NoArgsConstructor @AllArgsConstructor public class SettingInfoResponseVO { @NotNull diff --git a/blog-backend/src/main/java/cn/hamster3/application/blog/vo/user/UserCreateRequireVO.java b/blog-backend/src/main/java/cn/hamster3/application/blog/vo/user/UserCreateRequireVO.java index 35c1162..dd239d4 100644 --- a/blog-backend/src/main/java/cn/hamster3/application/blog/vo/user/UserCreateRequireVO.java +++ b/blog-backend/src/main/java/cn/hamster3/application/blog/vo/user/UserCreateRequireVO.java @@ -4,9 +4,11 @@ import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; import org.hibernate.validator.constraints.Length; @Data +@NoArgsConstructor @AllArgsConstructor public class UserCreateRequireVO { @Email(message = "邮箱配置不合法!") diff --git a/blog-backend/src/main/java/cn/hamster3/application/blog/vo/user/UserInfoResponseVO.java b/blog-backend/src/main/java/cn/hamster3/application/blog/vo/user/UserInfoResponseVO.java index aa34894..77b8fd5 100644 --- a/blog-backend/src/main/java/cn/hamster3/application/blog/vo/user/UserInfoResponseVO.java +++ b/blog-backend/src/main/java/cn/hamster3/application/blog/vo/user/UserInfoResponseVO.java @@ -4,11 +4,13 @@ import cn.hamster3.application.blog.constant.UserRole; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; import java.util.Date; import java.util.UUID; @Data +@NoArgsConstructor @AllArgsConstructor public class UserInfoResponseVO { @NotNull diff --git a/blog-backend/src/main/java/cn/hamster3/application/blog/vo/user/UserLoginRequireVO.java b/blog-backend/src/main/java/cn/hamster3/application/blog/vo/user/UserLoginRequireVO.java index 2b2dd03..196c402 100644 --- a/blog-backend/src/main/java/cn/hamster3/application/blog/vo/user/UserLoginRequireVO.java +++ b/blog-backend/src/main/java/cn/hamster3/application/blog/vo/user/UserLoginRequireVO.java @@ -4,9 +4,12 @@ import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; + import org.hibernate.validator.constraints.Length; @Data +@NoArgsConstructor @AllArgsConstructor public class UserLoginRequireVO { @Email(message = "邮箱配置不合法!") diff --git a/blog-backend/src/main/java/cn/hamster3/application/blog/vo/user/UserUpdateRequireVO.java b/blog-backend/src/main/java/cn/hamster3/application/blog/vo/user/UserUpdateRequireVO.java index 3e3ce68..7456903 100644 --- a/blog-backend/src/main/java/cn/hamster3/application/blog/vo/user/UserUpdateRequireVO.java +++ b/blog-backend/src/main/java/cn/hamster3/application/blog/vo/user/UserUpdateRequireVO.java @@ -3,9 +3,11 @@ package cn.hamster3.application.blog.vo.user; import cn.hamster3.application.blog.constant.UserRole; import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; import org.hibernate.validator.constraints.Length; @Data +@NoArgsConstructor @AllArgsConstructor public class UserUpdateRequireVO { private String email; diff --git a/blog-backend/src/main/resources/application.yml b/blog-backend/src/main/resources/application.yml index 91cdc83..bbafe9f 100644 --- a/blog-backend/src/main/resources/application.yml +++ b/blog-backend/src/main/resources/application.yml @@ -1,12 +1,12 @@ server: port: 8080 + +spring: servlet: context-path: / multipart: - enable: true - max-file-size: 16M - max-require-size: 32M -spring: + max-file-size: 16MB + max-request-size: 32MB # 要使用的环境预设 # prod: 生产环境 # dev: 开发环境 diff --git a/blog-frontend/src/views/BlogEditView.vue b/blog-frontend/src/views/BlogEditView.vue index ce63991..c0d0e06 100644 --- a/blog-frontend/src/views/BlogEditView.vue +++ b/blog-frontend/src/views/BlogEditView.vue @@ -106,7 +106,7 @@ function handleCreated(editor: IDomEditor) { /> - + diff --git a/blog-frontend/src/views/BlogReadView.vue b/blog-frontend/src/views/BlogReadView.vue index 5e5308e..791548d 100644 --- a/blog-frontend/src/views/BlogReadView.vue +++ b/blog-frontend/src/views/BlogReadView.vue @@ -28,16 +28,22 @@ function deleteBlog() {