mirror of https://github.com/halo-dev/halo
commit
d6bfa0748c
|
@ -5,7 +5,8 @@
|
||||||
|
|
||||||
**我确定我已经查看了** (标注`[ ]`为`[x]`)
|
**我确定我已经查看了** (标注`[ ]`为`[x]`)
|
||||||
|
|
||||||
- [ ] [Halo 使用文档](https://docs.halo.run/)
|
- [ ] [Halo 使用文档](https://halo.run/docs)
|
||||||
|
- [ ] [Halo 论坛](https://bbs.halo.run)
|
||||||
- [ ] [Github Wiki 常见问题](https://github.com/halo-dev/halo/wiki/4.-%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98)
|
- [ ] [Github Wiki 常见问题](https://github.com/halo-dev/halo/wiki/4.-%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98)
|
||||||
- [ ] [其他 Issues](https://github.com/halo-dev/halo/issues)
|
- [ ] [其他 Issues](https://github.com/halo-dev/halo/issues)
|
||||||
|
|
||||||
|
|
|
@ -35,9 +35,10 @@
|
||||||
|
|
||||||
## 周边
|
## 周边
|
||||||
|
|
||||||
- 后台管理源码:<[https://github.com/halo-dev/halo-admin](https://github.com/halo-dev/halo-admin)>
|
- 后台管理(halo-admin):<[https://github.com/halo-dev/halo-admin](https://github.com/halo-dev/halo-admin)>
|
||||||
|
- 独立评论模块(halo-comment):<[https://github.com/halo-dev/halo-comment](https://github.com/halo-dev/halo-comment)>
|
||||||
|
- 管理 APP(halo-app):<[https://github.com/halo-dev/halo-app](https://github.com/halo-dev/halo-app)>
|
||||||
- 主题仓库:<[https://halo.run/theme](https://halo.run/theme)>
|
- 主题仓库:<[https://halo.run/theme](https://halo.run/theme)>
|
||||||
- 管理 APP:<[https://github.com/halo-dev/halo-app](https://github.com/halo-dev/halo-app)>
|
|
||||||
|
|
||||||
## 许可证
|
## 许可证
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ apply plugin: 'io.spring.dependency-management'
|
||||||
|
|
||||||
group = 'run.halo.app'
|
group = 'run.halo.app'
|
||||||
archivesBaseName = 'halo'
|
archivesBaseName = 'halo'
|
||||||
version = '1.0.0-beta.7'
|
version = '1.0.0-beta.8'
|
||||||
sourceCompatibility = '1.8'
|
sourceCompatibility = '1.8'
|
||||||
description = 'Halo, personal blog system developed in Java.'
|
description = 'Halo, personal blog system developed in Java.'
|
||||||
|
|
||||||
|
@ -56,6 +56,10 @@ dependencies {
|
||||||
implementation 'com.atlassian.commonmark:commonmark:0.12.1'
|
implementation 'com.atlassian.commonmark:commonmark:0.12.1'
|
||||||
implementation 'com.atlassian.commonmark:commonmark-ext-gfm-tables:0.12.1'
|
implementation 'com.atlassian.commonmark:commonmark-ext-gfm-tables:0.12.1'
|
||||||
implementation 'com.atlassian.commonmark:commonmark-ext-yaml-front-matter:0.12.1'
|
implementation 'com.atlassian.commonmark:commonmark-ext-yaml-front-matter:0.12.1'
|
||||||
|
implementation 'com.atlassian.commonmark:commonmark-ext-autolink:0.12.1'
|
||||||
|
implementation 'com.atlassian.commonmark:commonmark-ext-gfm-strikethrough:0.12.1'
|
||||||
|
implementation 'com.atlassian.commonmark:commonmark-ext-heading-anchor:0.12.1'
|
||||||
|
implementation 'com.atlassian.commonmark:commonmark-ext-ins:0.12.1'
|
||||||
implementation 'io.springfox:springfox-swagger2:2.9.2'
|
implementation 'io.springfox:springfox-swagger2:2.9.2'
|
||||||
implementation 'io.springfox:springfox-swagger-ui:2.9.2'
|
implementation 'io.springfox:springfox-swagger-ui:2.9.2'
|
||||||
implementation 'org.apache.commons:commons-lang3:3.8.1'
|
implementation 'org.apache.commons:commons-lang3:3.8.1'
|
||||||
|
|
|
@ -113,7 +113,8 @@ public class HaloConfiguration {
|
||||||
ApiAuthenticationFilter apiFilter = new ApiAuthenticationFilter(haloProperties, optionService);
|
ApiAuthenticationFilter apiFilter = new ApiAuthenticationFilter(haloProperties, optionService);
|
||||||
apiFilter.addExcludeUrlPatterns(
|
apiFilter.addExcludeUrlPatterns(
|
||||||
"/api/content/*/comments",
|
"/api/content/*/comments",
|
||||||
"/api/content/**/comments/**"
|
"/api/content/**/comments/**",
|
||||||
|
"/api/content/options/comment"
|
||||||
);
|
);
|
||||||
|
|
||||||
DefaultAuthenticationFailureHandler failureHandler = new DefaultAuthenticationFailureHandler();
|
DefaultAuthenticationFailureHandler failureHandler = new DefaultAuthenticationFailureHandler();
|
||||||
|
|
|
@ -1,9 +1,18 @@
|
||||||
package run.halo.app.controller.admin.api;
|
package run.halo.app.controller.admin.api;
|
||||||
|
|
||||||
|
import io.swagger.annotations.ApiOperation;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestPart;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
import run.halo.app.model.dto.post.BasePostDetailDTO;
|
||||||
import run.halo.app.service.BackupService;
|
import run.halo.app.service.BackupService;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Backup controller
|
* Backup controller
|
||||||
*
|
*
|
||||||
|
@ -19,4 +28,15 @@ public class BackupController {
|
||||||
public BackupController(BackupService backupService) {
|
public BackupController(BackupService backupService) {
|
||||||
this.backupService = backupService;
|
this.backupService = backupService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("import/markdowns")
|
||||||
|
@ApiOperation("Import markdowns")
|
||||||
|
public List<BasePostDetailDTO> backupMarkdowns(@RequestPart("files") MultipartFile[] files) throws IOException {
|
||||||
|
List<BasePostDetailDTO> result = new LinkedList<>();
|
||||||
|
for (MultipartFile file : files) {
|
||||||
|
BasePostDetailDTO post = backupService.importMarkdowns(file);
|
||||||
|
result.add(post);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,7 +50,7 @@ public class JournalController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
@ApiOperation("Gets latest journals")
|
@ApiOperation("Lists journals")
|
||||||
public Page<JournalWithCmtCountDTO> pageBy(@PageableDefault(sort = "updateTime", direction = DESC) Pageable pageable,
|
public Page<JournalWithCmtCountDTO> pageBy(@PageableDefault(sort = "updateTime", direction = DESC) Pageable pageable,
|
||||||
JournalQuery journalQuery) {
|
JournalQuery journalQuery) {
|
||||||
Page<Journal> journalPage = journalService.pageBy(journalQuery, pageable);
|
Page<Journal> journalPage = journalService.pageBy(journalQuery, pageable);
|
||||||
|
|
|
@ -40,7 +40,7 @@ public class RecoveryController {
|
||||||
@ApiParam("This file content type should be json")
|
@ApiParam("This file content type should be json")
|
||||||
@RequestPart("file") MultipartFile file) {
|
@RequestPart("file") MultipartFile file) {
|
||||||
if (optionService.getByPropertyOrDefault(PrimaryProperties.IS_INSTALLED, Boolean.class, false)) {
|
if (optionService.getByPropertyOrDefault(PrimaryProperties.IS_INSTALLED, Boolean.class, false)) {
|
||||||
throw new BadRequestException("You cannot migrate after blog installing");
|
throw new BadRequestException("不能在博客初始化完成之后迁移数据");
|
||||||
}
|
}
|
||||||
|
|
||||||
recoveryService.migrateFromV0_4_3(file);
|
recoveryService.migrateFromV0_4_3(file);
|
||||||
|
|
|
@ -5,6 +5,7 @@ import org.springframework.data.domain.Page;
|
||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
||||||
import org.springframework.data.web.PageableDefault;
|
import org.springframework.data.web.PageableDefault;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import run.halo.app.model.dto.InternalSheetDTO;
|
||||||
import run.halo.app.model.dto.post.BasePostDetailDTO;
|
import run.halo.app.model.dto.post.BasePostDetailDTO;
|
||||||
import run.halo.app.model.entity.Sheet;
|
import run.halo.app.model.entity.Sheet;
|
||||||
import run.halo.app.model.enums.PostStatus;
|
import run.halo.app.model.enums.PostStatus;
|
||||||
|
@ -13,6 +14,7 @@ import run.halo.app.model.vo.SheetListVO;
|
||||||
import run.halo.app.service.SheetService;
|
import run.halo.app.service.SheetService;
|
||||||
|
|
||||||
import javax.validation.Valid;
|
import javax.validation.Valid;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import static org.springframework.data.domain.Sort.Direction.DESC;
|
import static org.springframework.data.domain.Sort.Direction.DESC;
|
||||||
|
|
||||||
|
@ -20,6 +22,7 @@ import static org.springframework.data.domain.Sort.Direction.DESC;
|
||||||
* Sheet controller.
|
* Sheet controller.
|
||||||
*
|
*
|
||||||
* @author johnniang
|
* @author johnniang
|
||||||
|
* @author ryanwang
|
||||||
* @date 19-4-24
|
* @date 19-4-24
|
||||||
*/
|
*/
|
||||||
@RestController
|
@RestController
|
||||||
|
@ -46,6 +49,12 @@ public class SheetController {
|
||||||
return sheetService.convertToListVo(sheetPage);
|
return sheetService.convertToListVo(sheetPage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("internal")
|
||||||
|
@ApiOperation("Lists internal sheets")
|
||||||
|
public List<InternalSheetDTO> internalSheets() {
|
||||||
|
return sheetService.listInternal();
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping
|
@PostMapping
|
||||||
@ApiOperation("Creates a sheet")
|
@ApiOperation("Creates a sheet")
|
||||||
public BasePostDetailDTO createBy(@RequestBody @Valid SheetParam sheetParam,
|
public BasePostDetailDTO createBy(@RequestBody @Valid SheetParam sheetParam,
|
||||||
|
|
|
@ -117,6 +117,13 @@ public class ThemeController {
|
||||||
themeSettingService.save(settings, themeId);
|
themeSettingService.save(settings, themeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PutMapping("{themeId}")
|
||||||
|
public ThemeProperty updateTheme(@PathVariable("themeId") String themeId,
|
||||||
|
@RequestPart(name = "file", required = false) MultipartFile file) {
|
||||||
|
|
||||||
|
return themeService.update(themeId);
|
||||||
|
}
|
||||||
|
|
||||||
@DeleteMapping("{themeId}")
|
@DeleteMapping("{themeId}")
|
||||||
@ApiOperation("Deletes a theme")
|
@ApiOperation("Deletes a theme")
|
||||||
public void deleteBy(@PathVariable("themeId") String themeId) {
|
public void deleteBy(@PathVariable("themeId") String themeId) {
|
||||||
|
@ -145,4 +152,5 @@ public class ThemeController {
|
||||||
public BaseResponse exists(@RequestParam(value = "template") String template) {
|
public BaseResponse exists(@RequestParam(value = "template") String template) {
|
||||||
return BaseResponse.ok(themeService.templateExists(template));
|
return BaseResponse.ok(themeService.templateExists(template));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import run.halo.app.model.dto.UserDTO;
|
||||||
import run.halo.app.model.entity.User;
|
import run.halo.app.model.entity.User;
|
||||||
import run.halo.app.model.params.PasswordParam;
|
import run.halo.app.model.params.PasswordParam;
|
||||||
import run.halo.app.model.params.UserParam;
|
import run.halo.app.model.params.UserParam;
|
||||||
|
import run.halo.app.model.support.BaseResponse;
|
||||||
import run.halo.app.model.support.UpdateCheck;
|
import run.halo.app.model.support.UpdateCheck;
|
||||||
import run.halo.app.service.UserService;
|
import run.halo.app.service.UserService;
|
||||||
import run.halo.app.utils.ValidationUtils;
|
import run.halo.app.utils.ValidationUtils;
|
||||||
|
@ -45,7 +46,8 @@ public class UserController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@PutMapping("profiles/password")
|
@PutMapping("profiles/password")
|
||||||
public void updatePassword(@RequestBody @Valid PasswordParam passwordParam, User user) {
|
public BaseResponse updatePassword(@RequestBody @Valid PasswordParam passwordParam, User user) {
|
||||||
userService.updatePassword(passwordParam.getOldPassword(), passwordParam.getNewPassword(), user.getId());
|
userService.updatePassword(passwordParam.getOldPassword(), passwordParam.getNewPassword(), user.getId());
|
||||||
|
return BaseResponse.ok("密码修改成功");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,13 +8,18 @@ import org.springframework.data.web.SortDefault;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import run.halo.app.cache.lock.CacheLock;
|
import run.halo.app.cache.lock.CacheLock;
|
||||||
import run.halo.app.model.dto.BaseCommentDTO;
|
import run.halo.app.model.dto.BaseCommentDTO;
|
||||||
|
import run.halo.app.model.entity.JournalComment;
|
||||||
|
import run.halo.app.model.enums.CommentStatus;
|
||||||
import run.halo.app.model.params.JournalCommentParam;
|
import run.halo.app.model.params.JournalCommentParam;
|
||||||
import run.halo.app.model.vo.BaseCommentVO;
|
import run.halo.app.model.vo.BaseCommentVO;
|
||||||
import run.halo.app.model.vo.BaseCommentWithParentVO;
|
import run.halo.app.model.vo.BaseCommentWithParentVO;
|
||||||
|
import run.halo.app.model.vo.CommentWithHasChildrenVO;
|
||||||
import run.halo.app.service.JournalCommentService;
|
import run.halo.app.service.JournalCommentService;
|
||||||
import run.halo.app.service.JournalService;
|
import run.halo.app.service.JournalService;
|
||||||
import run.halo.app.service.OptionService;
|
import run.halo.app.service.OptionService;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import static org.springframework.data.domain.Sort.Direction.DESC;
|
import static org.springframework.data.domain.Sort.Direction.DESC;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -39,12 +44,32 @@ public class JournalController {
|
||||||
this.optionService = optionService;
|
this.optionService = optionService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("{journalId:\\d+}/comments/top_view")
|
||||||
|
public Page<CommentWithHasChildrenVO> listTopComments(@PathVariable("journalId") Integer journalId,
|
||||||
|
@RequestParam(name = "page", required = false, defaultValue = "0") int page,
|
||||||
|
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
|
||||||
|
Page<CommentWithHasChildrenVO> result = journalCommentService.pageTopCommentsBy(journalId, CommentStatus.PUBLISHED, PageRequest.of(page, optionService.getCommentPageSize(), sort));
|
||||||
|
return journalCommentService.filterIpAddress(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("{journalId:\\d+}/comments/{commentParentId:\\d+}/children")
|
||||||
|
public List<BaseCommentDTO> listChildrenBy(@PathVariable("journalId") Integer journalId,
|
||||||
|
@PathVariable("commentParentId") Long commentParentId,
|
||||||
|
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
|
||||||
|
// Find all children comments
|
||||||
|
List<JournalComment> postComments = journalCommentService.listChildrenBy(journalId, commentParentId, CommentStatus.PUBLISHED, sort);
|
||||||
|
// Convert to base comment dto
|
||||||
|
List<BaseCommentDTO> result = journalCommentService.convertTo(postComments);
|
||||||
|
return journalCommentService.filterIpAddress(result);
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("{journalId:\\d+}/comments/tree_view")
|
@GetMapping("{journalId:\\d+}/comments/tree_view")
|
||||||
@ApiOperation("Lists comments with tree view")
|
@ApiOperation("Lists comments with tree view")
|
||||||
public Page<BaseCommentVO> listCommentsTree(@PathVariable("journalId") Integer journalId,
|
public Page<BaseCommentVO> listCommentsTree(@PathVariable("journalId") Integer journalId,
|
||||||
@RequestParam(name = "page", required = false, defaultValue = "0") int page,
|
@RequestParam(name = "page", required = false, defaultValue = "0") int page,
|
||||||
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
|
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
|
||||||
return journalCommentService.pageVosBy(journalId, PageRequest.of(page, optionService.getCommentPageSize(), sort));
|
Page<BaseCommentVO> result = journalCommentService.pageVosBy(journalId, PageRequest.of(page, optionService.getCommentPageSize(), sort));
|
||||||
|
return journalCommentService.filterIpAddress(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("{journalId:\\d+}/comments/list_view")
|
@GetMapping("{journalId:\\d+}/comments/list_view")
|
||||||
|
@ -52,7 +77,8 @@ public class JournalController {
|
||||||
public Page<BaseCommentWithParentVO> listComments(@PathVariable("journalId") Integer journalId,
|
public Page<BaseCommentWithParentVO> listComments(@PathVariable("journalId") Integer journalId,
|
||||||
@RequestParam(name = "page", required = false, defaultValue = "0") int page,
|
@RequestParam(name = "page", required = false, defaultValue = "0") int page,
|
||||||
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
|
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
|
||||||
return journalCommentService.pageWithParentVoBy(journalId, PageRequest.of(page, optionService.getCommentPageSize(), sort));
|
Page<BaseCommentWithParentVO> result = journalCommentService.pageWithParentVoBy(journalId, PageRequest.of(page, optionService.getCommentPageSize(), sort));
|
||||||
|
return journalCommentService.filterIpAddress(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("comments")
|
@PostMapping("comments")
|
||||||
|
|
|
@ -8,6 +8,7 @@ import run.halo.app.model.dto.OptionDTO;
|
||||||
import run.halo.app.model.support.BaseResponse;
|
import run.halo.app.model.support.BaseResponse;
|
||||||
import run.halo.app.service.OptionService;
|
import run.halo.app.service.OptionService;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
@ -48,4 +49,14 @@ public class OptionController {
|
||||||
public BaseResponse<Object> getBy(@PathVariable("key") String key) {
|
public BaseResponse<Object> getBy(@PathVariable("key") String key) {
|
||||||
return BaseResponse.ok(HttpStatus.OK.getReasonPhrase(), optionService.getByKey(key).orElse(null));
|
return BaseResponse.ok(HttpStatus.OK.getReasonPhrase(), optionService.getByKey(key).orElse(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@GetMapping("comment")
|
||||||
|
@ApiOperation("Options for comment")
|
||||||
|
public Map<String, Object> comment() {
|
||||||
|
List<String> keys = new ArrayList<>();
|
||||||
|
keys.add("comment_gavatar_default");
|
||||||
|
keys.add("comment_content_placeholder");
|
||||||
|
return optionService.listOptions(keys);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,7 +83,10 @@ public class PostController {
|
||||||
public Page<CommentWithHasChildrenVO> listTopComments(@PathVariable("postId") Integer postId,
|
public Page<CommentWithHasChildrenVO> listTopComments(@PathVariable("postId") Integer postId,
|
||||||
@RequestParam(name = "page", required = false, defaultValue = "0") int page,
|
@RequestParam(name = "page", required = false, defaultValue = "0") int page,
|
||||||
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
|
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
|
||||||
return postCommentService.pageTopCommentsBy(postId, CommentStatus.PUBLISHED, PageRequest.of(page, optionService.getCommentPageSize(), sort));
|
|
||||||
|
Page<CommentWithHasChildrenVO> result = postCommentService.pageTopCommentsBy(postId, CommentStatus.PUBLISHED, PageRequest.of(page, optionService.getCommentPageSize(), sort));
|
||||||
|
|
||||||
|
return postCommentService.filterIpAddress(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -94,7 +97,10 @@ public class PostController {
|
||||||
// Find all children comments
|
// Find all children comments
|
||||||
List<PostComment> postComments = postCommentService.listChildrenBy(postId, commentParentId, CommentStatus.PUBLISHED, sort);
|
List<PostComment> postComments = postCommentService.listChildrenBy(postId, commentParentId, CommentStatus.PUBLISHED, sort);
|
||||||
// Convert to base comment dto
|
// Convert to base comment dto
|
||||||
return postCommentService.convertTo(postComments);
|
|
||||||
|
List<BaseCommentDTO> result = postCommentService.convertTo(postComments);
|
||||||
|
|
||||||
|
return postCommentService.filterIpAddress(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("{postId:\\d+}/comments/tree_view")
|
@GetMapping("{postId:\\d+}/comments/tree_view")
|
||||||
|
@ -102,7 +108,8 @@ public class PostController {
|
||||||
public Page<BaseCommentVO> listCommentsTree(@PathVariable("postId") Integer postId,
|
public Page<BaseCommentVO> listCommentsTree(@PathVariable("postId") Integer postId,
|
||||||
@RequestParam(name = "page", required = false, defaultValue = "0") int page,
|
@RequestParam(name = "page", required = false, defaultValue = "0") int page,
|
||||||
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
|
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
|
||||||
return postCommentService.pageVosBy(postId, PageRequest.of(page, optionService.getCommentPageSize(), sort));
|
Page<BaseCommentVO> result = postCommentService.pageVosBy(postId, PageRequest.of(page, optionService.getCommentPageSize(), sort));
|
||||||
|
return postCommentService.filterIpAddress(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("{postId:\\d+}/comments/list_view")
|
@GetMapping("{postId:\\d+}/comments/list_view")
|
||||||
|
@ -110,7 +117,8 @@ public class PostController {
|
||||||
public Page<BaseCommentWithParentVO> listComments(@PathVariable("postId") Integer postId,
|
public Page<BaseCommentWithParentVO> listComments(@PathVariable("postId") Integer postId,
|
||||||
@RequestParam(name = "page", required = false, defaultValue = "0") int page,
|
@RequestParam(name = "page", required = false, defaultValue = "0") int page,
|
||||||
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
|
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
|
||||||
return postCommentService.pageWithParentVoBy(postId, PageRequest.of(page, optionService.getCommentPageSize(), sort));
|
Page<BaseCommentWithParentVO> result = postCommentService.pageWithParentVoBy(postId, PageRequest.of(page, optionService.getCommentPageSize(), sort));
|
||||||
|
return postCommentService.filterIpAddress(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("comments")
|
@PostMapping("comments")
|
||||||
|
|
|
@ -8,13 +8,18 @@ import org.springframework.data.web.SortDefault;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import run.halo.app.cache.lock.CacheLock;
|
import run.halo.app.cache.lock.CacheLock;
|
||||||
import run.halo.app.model.dto.BaseCommentDTO;
|
import run.halo.app.model.dto.BaseCommentDTO;
|
||||||
|
import run.halo.app.model.entity.SheetComment;
|
||||||
|
import run.halo.app.model.enums.CommentStatus;
|
||||||
import run.halo.app.model.params.SheetCommentParam;
|
import run.halo.app.model.params.SheetCommentParam;
|
||||||
import run.halo.app.model.vo.BaseCommentVO;
|
import run.halo.app.model.vo.BaseCommentVO;
|
||||||
import run.halo.app.model.vo.BaseCommentWithParentVO;
|
import run.halo.app.model.vo.BaseCommentWithParentVO;
|
||||||
|
import run.halo.app.model.vo.CommentWithHasChildrenVO;
|
||||||
import run.halo.app.service.OptionService;
|
import run.halo.app.service.OptionService;
|
||||||
import run.halo.app.service.SheetCommentService;
|
import run.halo.app.service.SheetCommentService;
|
||||||
import run.halo.app.service.SheetService;
|
import run.halo.app.service.SheetService;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import static org.springframework.data.domain.Sort.Direction.DESC;
|
import static org.springframework.data.domain.Sort.Direction.DESC;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -39,13 +44,33 @@ public class SheetController {
|
||||||
this.optionService = optionService;
|
this.optionService = optionService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("{sheetId:\\d+}/comments/top_view")
|
||||||
|
public Page<CommentWithHasChildrenVO> listTopComments(@PathVariable("sheetId") Integer sheetId,
|
||||||
|
@RequestParam(name = "page", required = false, defaultValue = "0") int page,
|
||||||
|
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
|
||||||
|
Page<CommentWithHasChildrenVO> result = sheetCommentService.pageTopCommentsBy(sheetId, CommentStatus.PUBLISHED, PageRequest.of(page, optionService.getCommentPageSize(), sort));
|
||||||
|
return sheetCommentService.filterIpAddress(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("{sheetId:\\d+}/comments/{commentParentId:\\d+}/children")
|
||||||
|
public List<BaseCommentDTO> listChildrenBy(@PathVariable("sheetId") Integer sheetId,
|
||||||
|
@PathVariable("commentParentId") Long commentParentId,
|
||||||
|
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
|
||||||
|
// Find all children comments
|
||||||
|
List<SheetComment> sheetComments = sheetCommentService.listChildrenBy(sheetId, commentParentId, CommentStatus.PUBLISHED, sort);
|
||||||
|
// Convert to base comment dto
|
||||||
|
List<BaseCommentDTO> result = sheetCommentService.convertTo(sheetComments);
|
||||||
|
return sheetCommentService.filterIpAddress(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@GetMapping("{sheetId:\\d+}/comments/tree_view")
|
@GetMapping("{sheetId:\\d+}/comments/tree_view")
|
||||||
@ApiOperation("Lists comments with tree view")
|
@ApiOperation("Lists comments with tree view")
|
||||||
public Page<BaseCommentVO> listCommentsTree(@PathVariable("sheetId") Integer sheetId,
|
public Page<BaseCommentVO> listCommentsTree(@PathVariable("sheetId") Integer sheetId,
|
||||||
@RequestParam(name = "page", required = false, defaultValue = "0") int page,
|
@RequestParam(name = "page", required = false, defaultValue = "0") int page,
|
||||||
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
|
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
|
||||||
return sheetCommentService.pageVosBy(sheetId, PageRequest.of(page, optionService.getCommentPageSize(), sort));
|
Page<BaseCommentVO> result = sheetCommentService.pageVosBy(sheetId, PageRequest.of(page, optionService.getCommentPageSize(), sort));
|
||||||
|
return sheetCommentService.filterIpAddress(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("{sheetId:\\d+}/comments/list_view")
|
@GetMapping("{sheetId:\\d+}/comments/list_view")
|
||||||
|
@ -53,7 +78,8 @@ public class SheetController {
|
||||||
public Page<BaseCommentWithParentVO> listComments(@PathVariable("sheetId") Integer sheetId,
|
public Page<BaseCommentWithParentVO> listComments(@PathVariable("sheetId") Integer sheetId,
|
||||||
@RequestParam(name = "page", required = false, defaultValue = "0") int page,
|
@RequestParam(name = "page", required = false, defaultValue = "0") int page,
|
||||||
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
|
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
|
||||||
return sheetCommentService.pageWithParentVoBy(sheetId, PageRequest.of(page, optionService.getCommentPageSize(), sort));
|
Page<BaseCommentWithParentVO> result = sheetCommentService.pageWithParentVoBy(sheetId, PageRequest.of(page, optionService.getCommentPageSize(), sort));
|
||||||
|
return sheetCommentService.filterIpAddress(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("comments")
|
@PostMapping("comments")
|
||||||
|
|
|
@ -57,11 +57,7 @@ public class CommonController implements ErrorController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (statusCode == 500) {
|
return statusCode == 500 ? "redirect:/500" : "redirect:/404";
|
||||||
return "redirect:/500";
|
|
||||||
} else {
|
|
||||||
return "redirect:/404";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
package run.halo.app.exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Theme update exception.
|
||||||
|
*
|
||||||
|
* @author johnniang
|
||||||
|
* @date 19-5-30
|
||||||
|
*/
|
||||||
|
public class ThemeUpdateException extends ServiceException {
|
||||||
|
|
||||||
|
public ThemeUpdateException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ThemeUpdateException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
|
@ -46,6 +46,7 @@ public class AliYunFileHandler implements FileHandler {
|
||||||
String ossAccessKey = optionService.getByPropertyOfNonNull(AliYunProperties.OSS_ACCESS_KEY).toString();
|
String ossAccessKey = optionService.getByPropertyOfNonNull(AliYunProperties.OSS_ACCESS_KEY).toString();
|
||||||
String ossAccessSecret = optionService.getByPropertyOfNonNull(AliYunProperties.OSS_ACCESS_SECRET).toString();
|
String ossAccessSecret = optionService.getByPropertyOfNonNull(AliYunProperties.OSS_ACCESS_SECRET).toString();
|
||||||
String ossBucketName = optionService.getByPropertyOfNonNull(AliYunProperties.OSS_BUCKET_NAME).toString();
|
String ossBucketName = optionService.getByPropertyOfNonNull(AliYunProperties.OSS_BUCKET_NAME).toString();
|
||||||
|
String ossStyleRule = optionService.getByPropertyOfNonNull(AliYunProperties.OSS_STYLE_RULE).toString();
|
||||||
String ossSource = StringUtils.join("https://", ossBucketName, "." + ossEndPoint);
|
String ossSource = StringUtils.join("https://", ossBucketName, "." + ossEndPoint);
|
||||||
|
|
||||||
// Init OSS client
|
// Init OSS client
|
||||||
|
@ -61,7 +62,7 @@ public class AliYunFileHandler implements FileHandler {
|
||||||
// Upload
|
// Upload
|
||||||
PutObjectResult putObjectResult = ossClient.putObject(ossBucketName, upFilePath, file.getInputStream());
|
PutObjectResult putObjectResult = ossClient.putObject(ossBucketName, upFilePath, file.getInputStream());
|
||||||
if (putObjectResult == null) {
|
if (putObjectResult == null) {
|
||||||
throw new FileOperationException("Failed to upload file " + file.getOriginalFilename() + " to AliYun " + upFilePath);
|
throw new FileOperationException("上传附件 " + file.getOriginalFilename() + " 到阿里云失败 ");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Response result
|
// Response result
|
||||||
|
@ -78,7 +79,7 @@ public class AliYunFileHandler implements FileHandler {
|
||||||
BufferedImage image = ImageIO.read(file.getInputStream());
|
BufferedImage image = ImageIO.read(file.getInputStream());
|
||||||
uploadResult.setWidth(image.getWidth());
|
uploadResult.setWidth(image.getWidth());
|
||||||
uploadResult.setHeight(image.getHeight());
|
uploadResult.setHeight(image.getHeight());
|
||||||
uploadResult.setThumbPath(filePath);
|
uploadResult.setThumbPath(StringUtils.isBlank(ossStyleRule) ? filePath : filePath + ossStyleRule);
|
||||||
}
|
}
|
||||||
|
|
||||||
return uploadResult;
|
return uploadResult;
|
||||||
|
@ -113,7 +114,7 @@ public class AliYunFileHandler implements FileHandler {
|
||||||
try {
|
try {
|
||||||
ossClient.deleteObject(new DeleteObjectsRequest(ossBucketName).withKey(key));
|
ossClient.deleteObject(new DeleteObjectsRequest(ossBucketName).withKey(key));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new FileOperationException("Failed to delete file " + key + " from AliYun", e);
|
throw new FileOperationException("附件 " + key + " 从阿里云删除失败", e);
|
||||||
} finally {
|
} finally {
|
||||||
ossClient.shutdown();
|
ossClient.shutdown();
|
||||||
}
|
}
|
||||||
|
|
|
@ -162,7 +162,7 @@ public class LocalFileHandler implements FileHandler {
|
||||||
return uploadResult;
|
return uploadResult;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
log.error("Failed to upload file to local: " + uploadPath, e);
|
log.error("Failed to upload file to local: " + uploadPath, e);
|
||||||
throw new ServiceException("Failed to upload file to local").setErrorData(uploadPath);
|
throw new ServiceException("上传附件失败").setErrorData(uploadPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,7 +177,7 @@ public class LocalFileHandler implements FileHandler {
|
||||||
try {
|
try {
|
||||||
Files.delete(path);
|
Files.delete(path);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new FileOperationException("Failed to delete " + key + " file", e);
|
throw new FileOperationException("附件 " + key + " 删除失败", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete thumb if necessary
|
// Delete thumb if necessary
|
||||||
|
@ -197,7 +197,7 @@ public class LocalFileHandler implements FileHandler {
|
||||||
log.warn("Thumbnail: [{}] way not exist", thumbnailPath.toString());
|
log.warn("Thumbnail: [{}] way not exist", thumbnailPath.toString());
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new FileOperationException("Failed to delete " + thumbnailName + " thumbnail", e);
|
throw new FileOperationException("附件缩略图 " + thumbnailName + " 删除失败", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -53,11 +53,11 @@ public class QnYunFileHandler implements FileHandler {
|
||||||
|
|
||||||
// Get all config
|
// Get all config
|
||||||
Zone zone = optionService.getQnYunZone();
|
Zone zone = optionService.getQnYunZone();
|
||||||
String accessKey = optionService.getByPropertyOfNonNull(QnYunProperties.ACCESS_KEY).toString();
|
String accessKey = optionService.getByPropertyOfNonNull(QnYunProperties.OSS_ACCESS_KEY).toString();
|
||||||
String secretKey = optionService.getByPropertyOfNonNull(QnYunProperties.SECRET_KEY).toString();
|
String secretKey = optionService.getByPropertyOfNonNull(QnYunProperties.OSS_SECRET_KEY).toString();
|
||||||
String bucket = optionService.getByPropertyOfNonNull(QnYunProperties.BUCKET).toString();
|
String bucket = optionService.getByPropertyOfNonNull(QnYunProperties.OSS_BUCKET).toString();
|
||||||
String domain = optionService.getByPropertyOfNonNull(QnYunProperties.DOMAIN).toString();
|
String domain = optionService.getByPropertyOfNonNull(QnYunProperties.OSS_DOMAIN).toString();
|
||||||
String smallUrl = optionService.getByPropertyOrDefault(QnYunProperties.SMALL_URL, String.class, "");
|
String styleRule = optionService.getByPropertyOrDefault(QnYunProperties.OSS_STYLE_RULE, String.class, "");
|
||||||
|
|
||||||
// Create configuration
|
// Create configuration
|
||||||
Configuration configuration = new Configuration(zone);
|
Configuration configuration = new Configuration(zone);
|
||||||
|
@ -109,9 +109,10 @@ public class QnYunFileHandler implements FileHandler {
|
||||||
result.setWidth(putSet.getWidth());
|
result.setWidth(putSet.getWidth());
|
||||||
result.setHeight(putSet.getHeight());
|
result.setHeight(putSet.getHeight());
|
||||||
result.setMediaType(MediaType.valueOf(Objects.requireNonNull(file.getContentType())));
|
result.setMediaType(MediaType.valueOf(Objects.requireNonNull(file.getContentType())));
|
||||||
|
result.setSize(file.getSize());
|
||||||
|
|
||||||
if (isImageType(result.getMediaType())) {
|
if (isImageType(result.getMediaType())) {
|
||||||
result.setThumbPath(StringUtils.isBlank(smallUrl) ? filePath : filePath + smallUrl);
|
result.setThumbPath(StringUtils.isBlank(styleRule) ? filePath : filePath + styleRule);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
@ -120,7 +121,7 @@ public class QnYunFileHandler implements FileHandler {
|
||||||
log.error("QnYun error response: [{}]", ((QiniuException) e).response);
|
log.error("QnYun error response: [{}]", ((QiniuException) e).response);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new FileOperationException("Failed to upload file " + file.getOriginalFilename() + " to QnYun", e);
|
throw new FileOperationException("上传附件 " + file.getOriginalFilename() + " 到七牛云失败", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,9 +131,9 @@ public class QnYunFileHandler implements FileHandler {
|
||||||
|
|
||||||
// Get all config
|
// Get all config
|
||||||
Zone zone = optionService.getQnYunZone();
|
Zone zone = optionService.getQnYunZone();
|
||||||
String accessKey = optionService.getByPropertyOfNonNull(QnYunProperties.ACCESS_KEY).toString();
|
String accessKey = optionService.getByPropertyOfNonNull(QnYunProperties.OSS_ACCESS_KEY).toString();
|
||||||
String secretKey = optionService.getByPropertyOfNonNull(QnYunProperties.SECRET_KEY).toString();
|
String secretKey = optionService.getByPropertyOfNonNull(QnYunProperties.OSS_SECRET_KEY).toString();
|
||||||
String bucket = optionService.getByPropertyOfNonNull(QnYunProperties.BUCKET).toString();
|
String bucket = optionService.getByPropertyOfNonNull(QnYunProperties.OSS_BUCKET).toString();
|
||||||
|
|
||||||
// Create configuration
|
// Create configuration
|
||||||
Configuration configuration = new Configuration(zone);
|
Configuration configuration = new Configuration(zone);
|
||||||
|
|
|
@ -50,7 +50,7 @@ public class SmmsFileHandler implements FileHandler {
|
||||||
|
|
||||||
if (!FileHandler.isImageType(file.getContentType())) {
|
if (!FileHandler.isImageType(file.getContentType())) {
|
||||||
log.error("Invalid extension: [{}]", file.getContentType());
|
log.error("Invalid extension: [{}]", file.getContentType());
|
||||||
throw new FileOperationException("Invalid extension for file " + file.getOriginalFilename() + ". Only \"jpeg, jpg, png, gif, bmp\" files are supported");
|
throw new FileOperationException("不支持的文件类型,仅支持 \"jpeg, jpg, png, gif, bmp\" 格式的图片");
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpHeaders headers = new HttpHeaders();
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
@ -65,7 +65,7 @@ public class SmmsFileHandler implements FileHandler {
|
||||||
body.add("smfile", new HttpClientUtils.MultipartFileResource(file.getBytes(), file.getOriginalFilename()));
|
body.add("smfile", new HttpClientUtils.MultipartFileResource(file.getBytes(), file.getOriginalFilename()));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
log.error("Failed to get file input stream", e);
|
log.error("Failed to get file input stream", e);
|
||||||
throw new FileOperationException("Failed to upload " + file.getOriginalFilename() + " file", e);
|
throw new FileOperationException("上传附件 " + file.getOriginalFilename() + " 到 SM.MS 失败", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
body.add("ssl", false);
|
body.add("ssl", false);
|
||||||
|
@ -79,7 +79,7 @@ public class SmmsFileHandler implements FileHandler {
|
||||||
// Check status
|
// Check status
|
||||||
if (mapResponseEntity.getStatusCode().isError()) {
|
if (mapResponseEntity.getStatusCode().isError()) {
|
||||||
log.error("Server response detail: [{}]", mapResponseEntity.toString());
|
log.error("Server response detail: [{}]", mapResponseEntity.toString());
|
||||||
throw new FileOperationException("Smms server response error. status: " + mapResponseEntity.getStatusCodeValue());
|
throw new FileOperationException("SM.MS 服务状态异常,状态码: " + mapResponseEntity.getStatusCodeValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get smms response
|
// Get smms response
|
||||||
|
@ -88,7 +88,7 @@ public class SmmsFileHandler implements FileHandler {
|
||||||
// Check error
|
// Check error
|
||||||
if (!isResponseSuccessfully(smmsResponse)) {
|
if (!isResponseSuccessfully(smmsResponse)) {
|
||||||
log.error("Smms response detail: [{}]", smmsResponse);
|
log.error("Smms response detail: [{}]", smmsResponse);
|
||||||
throw new FileOperationException(smmsResponse == null ? "Smms response is null" : smmsResponse.getMsg()).setErrorData(smmsResponse);
|
throw new FileOperationException(smmsResponse == null ? "SM.MS 服务返回内容为空" : smmsResponse.getMsg()).setErrorData(smmsResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get response data
|
// Get response data
|
||||||
|
@ -128,7 +128,7 @@ public class SmmsFileHandler implements FileHandler {
|
||||||
|
|
||||||
if (responseEntity.getStatusCode().isError()) {
|
if (responseEntity.getStatusCode().isError()) {
|
||||||
log.debug("Smms server response error: [{}]", responseEntity.toString());
|
log.debug("Smms server response error: [{}]", responseEntity.toString());
|
||||||
throw new FileOperationException("Smms server response error");
|
throw new FileOperationException("SM.MS 服务状态异常");
|
||||||
}
|
}
|
||||||
|
|
||||||
log.debug("Smms response detail: [{}]", responseEntity.getBody());
|
log.debug("Smms response detail: [{}]", responseEntity.getBody());
|
||||||
|
@ -155,7 +155,7 @@ public class SmmsFileHandler implements FileHandler {
|
||||||
@Data
|
@Data
|
||||||
@ToString
|
@ToString
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
static class SmmsResponse {
|
private static class SmmsResponse {
|
||||||
|
|
||||||
private String code;
|
private String code;
|
||||||
|
|
||||||
|
@ -168,7 +168,7 @@ public class SmmsFileHandler implements FileHandler {
|
||||||
@Data
|
@Data
|
||||||
@ToString(callSuper = true)
|
@ToString(callSuper = true)
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
static class SmmsResponseData {
|
private static class SmmsResponseData {
|
||||||
|
|
||||||
private String filename;
|
private String filename;
|
||||||
|
|
||||||
|
|
|
@ -44,8 +44,8 @@ public class UpYunFileHandler implements FileHandler {
|
||||||
String ossBucket = optionService.getByPropertyOfNonNull(UpYunProperties.OSS_BUCKET).toString();
|
String ossBucket = optionService.getByPropertyOfNonNull(UpYunProperties.OSS_BUCKET).toString();
|
||||||
String ossDomain = optionService.getByPropertyOfNonNull(UpYunProperties.OSS_DOMAIN).toString();
|
String ossDomain = optionService.getByPropertyOfNonNull(UpYunProperties.OSS_DOMAIN).toString();
|
||||||
String ossOperator = optionService.getByPropertyOfNonNull(UpYunProperties.OSS_OPERATOR).toString();
|
String ossOperator = optionService.getByPropertyOfNonNull(UpYunProperties.OSS_OPERATOR).toString();
|
||||||
// small url can be null
|
// style rule can be null
|
||||||
String ossSmallUrl = optionService.getByPropertyOrDefault(UpYunProperties.OSS_SMALL_URL, String.class, "");
|
String ossStyleRule = optionService.getByPropertyOrDefault(UpYunProperties.OSS_STYLE_RULE, String.class, "");
|
||||||
|
|
||||||
// Create up yun
|
// Create up yun
|
||||||
UpYun upYun = new UpYun(ossBucket, ossOperator, ossPassword);
|
UpYun upYun = new UpYun(ossBucket, ossOperator, ossPassword);
|
||||||
|
@ -67,7 +67,7 @@ public class UpYunFileHandler implements FileHandler {
|
||||||
// Write file
|
// Write file
|
||||||
boolean uploadSuccess = upYun.writeFile(upFilePath, file.getInputStream(), true, null);
|
boolean uploadSuccess = upYun.writeFile(upFilePath, file.getInputStream(), true, null);
|
||||||
if (!uploadSuccess) {
|
if (!uploadSuccess) {
|
||||||
throw new FileOperationException("Failed to upload file " + file.getOriginalFilename() + " to UpYun " + upFilePath);
|
throw new FileOperationException("上传附件 " + file.getOriginalFilename() + " 到又拍云失败" + upFilePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
String filePath = StringUtils.removeEnd(ossDomain, "/") + upFilePath;
|
String filePath = StringUtils.removeEnd(ossDomain, "/") + upFilePath;
|
||||||
|
@ -86,12 +86,12 @@ public class UpYunFileHandler implements FileHandler {
|
||||||
BufferedImage image = ImageIO.read(file.getInputStream());
|
BufferedImage image = ImageIO.read(file.getInputStream());
|
||||||
uploadResult.setWidth(image.getWidth());
|
uploadResult.setWidth(image.getWidth());
|
||||||
uploadResult.setHeight(image.getHeight());
|
uploadResult.setHeight(image.getHeight());
|
||||||
uploadResult.setThumbPath(StringUtils.isBlank(ossSmallUrl) ? filePath : filePath + ossSmallUrl);
|
uploadResult.setThumbPath(StringUtils.isBlank(ossStyleRule) ? filePath : filePath + ossStyleRule);
|
||||||
}
|
}
|
||||||
|
|
||||||
return uploadResult;
|
return uploadResult;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new FileOperationException("Failed to upload file " + file.getOriginalFilename() + " to UpYun", e);
|
throw new FileOperationException("上传附件 " + file.getOriginalFilename() + " 到又拍云失败", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,7 +118,7 @@ public class UpYunFileHandler implements FileHandler {
|
||||||
log.warn("Failed to delete file " + filePath + " from UpYun");
|
log.warn("Failed to delete file " + filePath + " from UpYun");
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new FileOperationException("Failed to delete file " + key + " from UpYun", e);
|
throw new FileOperationException("附件从又拍云删除失败", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package run.halo.app.handler.theme.config.impl;
|
package run.halo.app.handler.theme.config.impl;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
|
||||||
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
|
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
@ -18,7 +20,12 @@ import java.io.IOException;
|
||||||
@Service
|
@Service
|
||||||
public class YamlThemePropertyResolver implements ThemePropertyResolver {
|
public class YamlThemePropertyResolver implements ThemePropertyResolver {
|
||||||
|
|
||||||
private final ObjectMapper yamlMapper = new ObjectMapper(new YAMLFactory());
|
private final ObjectMapper yamlMapper;
|
||||||
|
|
||||||
|
public YamlThemePropertyResolver() {
|
||||||
|
yamlMapper = new ObjectMapper(new YAMLFactory());
|
||||||
|
yamlMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ThemeProperty resolve(String content) throws IOException {
|
public ThemeProperty resolve(String content) throws IOException {
|
||||||
|
|
|
@ -28,6 +28,16 @@ public class ThemeProperty {
|
||||||
*/
|
*/
|
||||||
private String website;
|
private String website;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Theme remote branch.(default is master)
|
||||||
|
*/
|
||||||
|
private String branch;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Theme repo url.
|
||||||
|
*/
|
||||||
|
private String repo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Theme description.
|
* Theme description.
|
||||||
*/
|
*/
|
||||||
|
@ -49,7 +59,7 @@ public class ThemeProperty {
|
||||||
private Author author;
|
private Author author;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Theme path.
|
* Theme full path.
|
||||||
*/
|
*/
|
||||||
private String themePath;
|
private String themePath;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
package run.halo.app.model.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ryanwang
|
||||||
|
* @date 2019-05-25
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class BackupDTO {
|
||||||
|
|
||||||
|
private String fileName;
|
||||||
|
|
||||||
|
private Date createTime;
|
||||||
|
|
||||||
|
private String fileSize;
|
||||||
|
|
||||||
|
private String fileType;
|
||||||
|
|
||||||
|
private String type;
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
package run.halo.app.model.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Theme controller.
|
||||||
|
*
|
||||||
|
* @author ryanwang
|
||||||
|
* @date : 2019/5/4
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class InternalSheetDTO {
|
||||||
|
|
||||||
|
private Integer id;
|
||||||
|
|
||||||
|
private String title;
|
||||||
|
|
||||||
|
private String url;
|
||||||
|
|
||||||
|
private boolean status;
|
||||||
|
}
|
|
@ -12,6 +12,7 @@ import run.halo.app.utils.HaloUtils;
|
||||||
import javax.validation.constraints.Min;
|
import javax.validation.constraints.Min;
|
||||||
import javax.validation.constraints.NotBlank;
|
import javax.validation.constraints.NotBlank;
|
||||||
import javax.validation.constraints.Size;
|
import javax.validation.constraints.Size;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -50,6 +51,8 @@ public class PostParam implements InputConverter<Post> {
|
||||||
@Min(value = 0, message = "Post top priority must not be less than {value}")
|
@Min(value = 0, message = "Post top priority must not be less than {value}")
|
||||||
private Integer topPriority = 0;
|
private Integer topPriority = 0;
|
||||||
|
|
||||||
|
private Date createTime;
|
||||||
|
|
||||||
private PostCreateFrom createFrom = PostCreateFrom.ADMIN;
|
private PostCreateFrom createFrom = PostCreateFrom.ADMIN;
|
||||||
|
|
||||||
private Set<Integer> tagIds;
|
private Set<Integer> tagIds;
|
||||||
|
|
|
@ -26,7 +26,12 @@ public enum AliYunProperties implements PropertyEnum {
|
||||||
/**
|
/**
|
||||||
* Aliyun oss access secret.
|
* Aliyun oss access secret.
|
||||||
*/
|
*/
|
||||||
OSS_ACCESS_SECRET("oss_aliyun_access_secret", String.class, "");
|
OSS_ACCESS_SECRET("oss_aliyun_access_secret", String.class, ""),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aliyun oss style rule
|
||||||
|
*/
|
||||||
|
OSS_STYLE_RULE("oss_aliyun_style_rule", String.class, "");
|
||||||
|
|
||||||
private final String value;
|
private final String value;
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,8 @@ package run.halo.app.model.properties;
|
||||||
* Comment properties.
|
* Comment properties.
|
||||||
*
|
*
|
||||||
* @author johnniang
|
* @author johnniang
|
||||||
* @date 4/1/19
|
* @author ryanwang
|
||||||
|
* @date 2019-04-01
|
||||||
*/
|
*/
|
||||||
public enum CommentProperties implements PropertyEnum {
|
public enum CommentProperties implements PropertyEnum {
|
||||||
|
|
||||||
|
@ -22,9 +23,7 @@ public enum CommentProperties implements PropertyEnum {
|
||||||
|
|
||||||
PAGE_SIZE("comment_page_size", Integer.class, "10"),
|
PAGE_SIZE("comment_page_size", Integer.class, "10"),
|
||||||
|
|
||||||
CONTENT_PLACEHOLDER("comment_content_placeholder", String.class, ""),
|
CONTENT_PLACEHOLDER("comment_content_placeholder", String.class, "");
|
||||||
|
|
||||||
CUSTOM_STYLE("comment_custom_style", String.class, "");
|
|
||||||
|
|
||||||
private final String value;
|
private final String value;
|
||||||
|
|
||||||
|
|
|
@ -8,17 +8,17 @@ package run.halo.app.model.properties;
|
||||||
*/
|
*/
|
||||||
public enum QnYunProperties implements PropertyEnum {
|
public enum QnYunProperties implements PropertyEnum {
|
||||||
|
|
||||||
ZONE("oss_qiniu_zone", String.class, "auto"),
|
OSS_ZONE("oss_qiniu_zone", String.class, "auto"),
|
||||||
|
|
||||||
ACCESS_KEY("oss_qiniu_access_key", String.class, ""),
|
OSS_ACCESS_KEY("oss_qiniu_access_key", String.class, ""),
|
||||||
|
|
||||||
SECRET_KEY("oss_qiniu_secret_key", String.class, ""),
|
OSS_SECRET_KEY("oss_qiniu_secret_key", String.class, ""),
|
||||||
|
|
||||||
DOMAIN("oss_qiniu_domain", String.class, ""),
|
OSS_DOMAIN("oss_qiniu_domain", String.class, ""),
|
||||||
|
|
||||||
BUCKET("oss_qiniu_bucket", String.class, ""),
|
OSS_BUCKET("oss_qiniu_bucket", String.class, ""),
|
||||||
|
|
||||||
SMALL_URL("oss_qiniu_small_url", String.class, "");
|
OSS_STYLE_RULE("oss_qiniu_style_rule", String.class, "");
|
||||||
|
|
||||||
private final String value;
|
private final String value;
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ public enum UpYunProperties implements PropertyEnum {
|
||||||
|
|
||||||
OSS_OPERATOR("oss_upyun_operator", String.class, ""),
|
OSS_OPERATOR("oss_upyun_operator", String.class, ""),
|
||||||
|
|
||||||
OSS_SMALL_URL("oss_upyun_small_url", String.class, "");
|
OSS_STYLE_RULE("oss_upyun_style_rule", String.class, "");
|
||||||
|
|
||||||
private final String defaultValue;
|
private final String defaultValue;
|
||||||
private String value;
|
private String value;
|
||||||
|
|
|
@ -36,4 +36,12 @@ public interface CategoryRepository extends BaseRepository<Category, Integer> {
|
||||||
* @return Optional of Category
|
* @return Optional of Category
|
||||||
*/
|
*/
|
||||||
Optional<Category> getBySlugName(@NonNull String slugName);
|
Optional<Category> getBySlugName(@NonNull String slugName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get category by name.
|
||||||
|
*
|
||||||
|
* @param name name
|
||||||
|
* @return Optional of Category
|
||||||
|
*/
|
||||||
|
Optional<Category> getByName(@NonNull String name);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,14 @@
|
||||||
package run.halo.app.repository;
|
package run.halo.app.repository;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.lang.NonNull;
|
||||||
import run.halo.app.model.entity.JournalComment;
|
import run.halo.app.model.entity.JournalComment;
|
||||||
|
import run.halo.app.model.projection.CommentChildrenCountProjection;
|
||||||
|
import run.halo.app.model.projection.CommentCountProjection;
|
||||||
import run.halo.app.repository.base.BaseCommentRepository;
|
import run.halo.app.repository.base.BaseCommentRepository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Journal comment repository.
|
* Journal comment repository.
|
||||||
*
|
*
|
||||||
|
@ -11,8 +17,18 @@ import run.halo.app.repository.base.BaseCommentRepository;
|
||||||
*/
|
*/
|
||||||
public interface JournalCommentRepository extends BaseCommentRepository<JournalComment> {
|
public interface JournalCommentRepository extends BaseCommentRepository<JournalComment> {
|
||||||
|
|
||||||
// @Query("select new run.halo.app.model.projection.CommentCountProjection(count(comment.id), comment.postId) from JournalComment comment where comment.postId in ?1 group by comment.postId")
|
@Query("select new run.halo.app.model.projection.CommentCountProjection(count(comment.id), comment.postId) " +
|
||||||
// @NonNull
|
"from JournalComment comment " +
|
||||||
// @Override
|
"where comment.postId in ?1 group by comment.postId")
|
||||||
// List<CommentCountProjection> countByPostIds(@NonNull Iterable<Integer> postIds);
|
@NonNull
|
||||||
|
@Override
|
||||||
|
List<CommentCountProjection> countByPostIds(@NonNull Iterable<Integer> postIds);
|
||||||
|
|
||||||
|
@Query("select new run.halo.app.model.projection.CommentChildrenCountProjection(count(comment.id), comment.parentId) " +
|
||||||
|
"from JournalComment comment " +
|
||||||
|
"where comment.parentId in ?1 " +
|
||||||
|
"group by comment.parentId")
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
List<CommentChildrenCountProjection> findDirectChildrenCount(@NonNull Iterable<Long> commentIds);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,14 @@
|
||||||
package run.halo.app.repository;
|
package run.halo.app.repository;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.lang.NonNull;
|
||||||
import run.halo.app.model.entity.PostComment;
|
import run.halo.app.model.entity.PostComment;
|
||||||
|
import run.halo.app.model.projection.CommentChildrenCountProjection;
|
||||||
|
import run.halo.app.model.projection.CommentCountProjection;
|
||||||
import run.halo.app.repository.base.BaseCommentRepository;
|
import run.halo.app.repository.base.BaseCommentRepository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PostComment repository.
|
* PostComment repository.
|
||||||
*
|
*
|
||||||
|
@ -11,9 +17,17 @@ import run.halo.app.repository.base.BaseCommentRepository;
|
||||||
*/
|
*/
|
||||||
public interface PostCommentRepository extends BaseCommentRepository<PostComment> {
|
public interface PostCommentRepository extends BaseCommentRepository<PostComment> {
|
||||||
|
|
||||||
// @Query("select new run.halo.app.model.projection.CommentCountProjection(count(comment.id), comment.postId) from PostComment comment where comment.postId in ?1 group by comment.postId")
|
@Query("select new run.halo.app.model.projection.CommentCountProjection(count(comment.id), comment.postId) " +
|
||||||
// @NonNull
|
"from PostComment comment " +
|
||||||
// @Override
|
"where comment.postId in ?1 group by comment.postId")
|
||||||
// List<CommentCountProjection> countByPostIds(@NonNull Iterable<Integer> postIds);
|
@NonNull
|
||||||
|
@Override
|
||||||
|
List<CommentCountProjection> countByPostIds(@NonNull Iterable<Integer> postIds);
|
||||||
|
|
||||||
|
@Query("select new run.halo.app.model.projection.CommentChildrenCountProjection(count(comment.id), comment.parentId) " +
|
||||||
|
"from PostComment comment " +
|
||||||
|
"where comment.parentId in ?1 " +
|
||||||
|
"group by comment.parentId")
|
||||||
|
@NonNull
|
||||||
|
List<CommentChildrenCountProjection> findDirectChildrenCount(@NonNull Iterable<Long> commentIds);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,14 @@
|
||||||
package run.halo.app.repository;
|
package run.halo.app.repository;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.lang.NonNull;
|
||||||
import run.halo.app.model.entity.SheetComment;
|
import run.halo.app.model.entity.SheetComment;
|
||||||
|
import run.halo.app.model.projection.CommentChildrenCountProjection;
|
||||||
|
import run.halo.app.model.projection.CommentCountProjection;
|
||||||
import run.halo.app.repository.base.BaseCommentRepository;
|
import run.halo.app.repository.base.BaseCommentRepository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sheet comment repository.
|
* Sheet comment repository.
|
||||||
*
|
*
|
||||||
|
@ -10,9 +16,18 @@ import run.halo.app.repository.base.BaseCommentRepository;
|
||||||
* @date 19-4-24
|
* @date 19-4-24
|
||||||
*/
|
*/
|
||||||
public interface SheetCommentRepository extends BaseCommentRepository<SheetComment> {
|
public interface SheetCommentRepository extends BaseCommentRepository<SheetComment> {
|
||||||
//
|
|
||||||
// @Query("select new run.halo.app.model.projection.CommentCountProjection(count(comment.id), comment.postId) from SheetComment comment where comment.postId in ?1 group by comment.postId")
|
@Query("select new run.halo.app.model.projection.CommentCountProjection(count(comment.id), comment.postId) " +
|
||||||
// @NonNull
|
"from SheetComment comment " +
|
||||||
// @Override
|
"where comment.postId in ?1 group by comment.postId")
|
||||||
// List<CommentCountProjection> countByPostIds(@NonNull Iterable<Integer> postIds);
|
@NonNull
|
||||||
|
@Override
|
||||||
|
List<CommentCountProjection> countByPostIds(@NonNull Iterable<Integer> postIds);
|
||||||
|
|
||||||
|
@Query("select new run.halo.app.model.projection.CommentChildrenCountProjection(count(comment.id), comment.parentId) " +
|
||||||
|
"from SheetComment comment " +
|
||||||
|
"where comment.parentId in ?1 " +
|
||||||
|
"group by comment.parentId")
|
||||||
|
@NonNull
|
||||||
|
List<CommentChildrenCountProjection> findDirectChildrenCount(@NonNull Iterable<Long> commentIds);
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,4 +29,11 @@ public interface TagRepository extends BaseRepository<Tag, Integer> {
|
||||||
* @return Tag
|
* @return Tag
|
||||||
*/
|
*/
|
||||||
Optional<Tag> getBySlugName(@NonNull String slugName);
|
Optional<Tag> getBySlugName(@NonNull String slugName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get tag by name
|
||||||
|
* @param name name
|
||||||
|
* @return Tag
|
||||||
|
*/
|
||||||
|
Optional<Tag> getByName(@NonNull String name);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
package run.halo.app.service;
|
package run.halo.app.service;
|
||||||
|
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
import run.halo.app.model.dto.post.BasePostDetailDTO;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Backup service interface.
|
* Backup service interface.
|
||||||
*
|
*
|
||||||
|
@ -7,4 +12,12 @@ package run.halo.app.service;
|
||||||
* @date 19-4-26
|
* @date 19-4-26
|
||||||
*/
|
*/
|
||||||
public interface BackupService {
|
public interface BackupService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Backup posts and sheets
|
||||||
|
*
|
||||||
|
* @param file file
|
||||||
|
* @return post info
|
||||||
|
*/
|
||||||
|
BasePostDetailDTO importMarkdowns(MultipartFile file) throws IOException;
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,14 @@ public interface CategoryService extends CrudService<Category, Integer> {
|
||||||
@NonNull
|
@NonNull
|
||||||
Category getBySlugName(@NonNull String slugName);
|
Category getBySlugName(@NonNull String slugName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Category by name.
|
||||||
|
*
|
||||||
|
* @param name name
|
||||||
|
* @return Category
|
||||||
|
*/
|
||||||
|
Category getByName(@NonNull String name);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes category and post categories.
|
* Removes category and post categories.
|
||||||
*
|
*
|
||||||
|
|
|
@ -101,10 +101,11 @@ public interface PostService extends BasePostService<Post> {
|
||||||
* Import post from markdown document.
|
* Import post from markdown document.
|
||||||
*
|
*
|
||||||
* @param markdown markdown document.
|
* @param markdown markdown document.
|
||||||
|
* @param filename filename
|
||||||
* @return imported post
|
* @return imported post
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
Post importMarkdown(@NonNull String markdown);
|
PostDetailVO importMarkdown(@NonNull String markdown, String filename);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Export post to markdown file by post id.
|
* Export post to markdown file by post id.
|
||||||
|
|
|
@ -2,15 +2,19 @@ package run.halo.app.service;
|
||||||
|
|
||||||
import org.springframework.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
import org.springframework.lang.NonNull;
|
import org.springframework.lang.NonNull;
|
||||||
|
import run.halo.app.model.dto.InternalSheetDTO;
|
||||||
import run.halo.app.model.entity.Sheet;
|
import run.halo.app.model.entity.Sheet;
|
||||||
import run.halo.app.model.enums.PostStatus;
|
import run.halo.app.model.enums.PostStatus;
|
||||||
import run.halo.app.model.vo.SheetListVO;
|
import run.halo.app.model.vo.SheetListVO;
|
||||||
import run.halo.app.service.base.BasePostService;
|
import run.halo.app.service.base.BasePostService;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sheet service interface.
|
* Sheet service interface.
|
||||||
*
|
*
|
||||||
* @author johnniang
|
* @author johnniang
|
||||||
|
* @author ryanwang
|
||||||
* @date 19-4-24
|
* @date 19-4-24
|
||||||
*/
|
*/
|
||||||
public interface SheetService extends BasePostService<Sheet> {
|
public interface SheetService extends BasePostService<Sheet> {
|
||||||
|
@ -72,6 +76,14 @@ public interface SheetService extends BasePostService<Sheet> {
|
||||||
@NonNull
|
@NonNull
|
||||||
String exportMarkdown(@NonNull Sheet sheet);
|
String exportMarkdown(@NonNull Sheet sheet);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List internal sheets.
|
||||||
|
*
|
||||||
|
* @return list of internal sheets
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
List<InternalSheetDTO> listInternal();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts to list dto page.
|
* Converts to list dto page.
|
||||||
*
|
*
|
||||||
|
|
|
@ -25,6 +25,15 @@ public interface TagService extends CrudService<Tag, Integer> {
|
||||||
@NonNull
|
@NonNull
|
||||||
Tag getBySlugNameOfNonNull(@NonNull String slugName);
|
Tag getBySlugNameOfNonNull(@NonNull String slugName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get tag by tag name.
|
||||||
|
*
|
||||||
|
* @param name name
|
||||||
|
* @return Tag
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
Tag getByName(@NonNull String name);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts to tag dto.
|
* Converts to tag dto.
|
||||||
*
|
*
|
||||||
|
|
|
@ -71,6 +71,16 @@ public interface ThemeService {
|
||||||
*/
|
*/
|
||||||
String CUSTOM_SHEET_PREFIX = "sheet_";
|
String CUSTOM_SHEET_PREFIX = "sheet_";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Theme provider remote name.
|
||||||
|
*/
|
||||||
|
String THEME_PROVIDER_REMOTE_NAME = "theme-provider";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default remote branch name.
|
||||||
|
*/
|
||||||
|
String DEFAULT_REMOTE_BRANCH = "master";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get theme property by theme id.
|
* Get theme property by theme id.
|
||||||
*
|
*
|
||||||
|
@ -241,4 +251,13 @@ public interface ThemeService {
|
||||||
* Reloads themes
|
* Reloads themes
|
||||||
*/
|
*/
|
||||||
void reload();
|
void reload();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates theme by theme id.
|
||||||
|
*
|
||||||
|
* @param themeId theme id must not be blank
|
||||||
|
* @return theme property
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
ThemeProperty update(@NonNull String themeId);
|
||||||
}
|
}
|
||||||
|
|
|
@ -214,4 +214,26 @@ public interface BaseCommentService<COMMENT extends BaseComment> extends CrudSer
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
List<COMMENT> listChildrenBy(@NonNull Integer targetId, @NonNull Long commentParentId, @NonNull CommentStatus status, @NonNull Sort sort);
|
List<COMMENT> listChildrenBy(@NonNull Integer targetId, @NonNull Long commentParentId, @NonNull CommentStatus status, @NonNull Sort sort);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters comment ip address.
|
||||||
|
*
|
||||||
|
* @param comment comment dto must not be null
|
||||||
|
*/
|
||||||
|
<T extends BaseCommentDTO> T filterIpAddress(@NonNull T comment);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters comment ip address.
|
||||||
|
*
|
||||||
|
* @param comments comment dto list
|
||||||
|
*/
|
||||||
|
<T extends BaseCommentDTO> List<T> filterIpAddress(@Nullable List<T> comments);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters comment ip address.
|
||||||
|
*
|
||||||
|
* @param commentPage comment page
|
||||||
|
*/
|
||||||
|
<T extends BaseCommentDTO> Page<T> filterIpAddress(@NonNull Page<T> commentPage);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package run.halo.app.service.impl;
|
||||||
import cn.hutool.core.lang.Validator;
|
import cn.hutool.core.lang.Validator;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.context.ApplicationEventPublisher;
|
||||||
import org.springframework.lang.NonNull;
|
import org.springframework.lang.NonNull;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
@ -58,6 +59,8 @@ public class AdminServiceImpl implements AdminService {
|
||||||
|
|
||||||
private final StringCacheStore cacheStore;
|
private final StringCacheStore cacheStore;
|
||||||
|
|
||||||
|
private final ApplicationEventPublisher eventPublisher;
|
||||||
|
|
||||||
private final String driverClassName;
|
private final String driverClassName;
|
||||||
|
|
||||||
public AdminServiceImpl(PostService postService,
|
public AdminServiceImpl(PostService postService,
|
||||||
|
@ -70,6 +73,7 @@ public class AdminServiceImpl implements AdminService {
|
||||||
UserService userService,
|
UserService userService,
|
||||||
LinkService linkService,
|
LinkService linkService,
|
||||||
StringCacheStore cacheStore,
|
StringCacheStore cacheStore,
|
||||||
|
ApplicationEventPublisher eventPublisher,
|
||||||
@Value("${spring.datasource.driver-class-name}") String driverClassName) {
|
@Value("${spring.datasource.driver-class-name}") String driverClassName) {
|
||||||
this.postService = postService;
|
this.postService = postService;
|
||||||
this.sheetService = sheetService;
|
this.sheetService = sheetService;
|
||||||
|
@ -81,6 +85,7 @@ public class AdminServiceImpl implements AdminService {
|
||||||
this.userService = userService;
|
this.userService = userService;
|
||||||
this.linkService = linkService;
|
this.linkService = linkService;
|
||||||
this.cacheStore = cacheStore;
|
this.cacheStore = cacheStore;
|
||||||
|
this.eventPublisher = eventPublisher;
|
||||||
this.driverClassName = driverClassName;
|
this.driverClassName = driverClassName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,7 +117,7 @@ public class AdminServiceImpl implements AdminService {
|
||||||
|
|
||||||
if (SecurityContextHolder.getContext().isAuthenticated()) {
|
if (SecurityContextHolder.getContext().isAuthenticated()) {
|
||||||
// If the user has been logged in
|
// If the user has been logged in
|
||||||
throw new BadRequestException("You have been logged in, do not log in repeatedly please");
|
throw new BadRequestException("您已登录,请不要重复登录");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate new token
|
// Generate new token
|
||||||
|
@ -125,7 +130,7 @@ public class AdminServiceImpl implements AdminService {
|
||||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||||
|
|
||||||
if (authentication == null) {
|
if (authentication == null) {
|
||||||
throw new BadRequestException("You haven't logged in yet, so you can't log out");
|
throw new BadRequestException("您尚未登录,因此无法注销");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get current user
|
// Get current user
|
||||||
|
|
|
@ -188,7 +188,7 @@ public class AttachmentServiceImpl extends AbstractCrudService<Attachment, Integ
|
||||||
long pathCount = attachmentRepository.countByPath(attachment.getPath());
|
long pathCount = attachmentRepository.countByPath(attachment.getPath());
|
||||||
|
|
||||||
if (pathCount > 0) {
|
if (pathCount > 0) {
|
||||||
throw new AlreadyExistsException("The attachment with path " + attachment.getPath() + " exists already");
|
throw new AlreadyExistsException("附件路径为 " + attachment.getPath() + " 已经存在");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,13 @@
|
||||||
package run.halo.app.service.impl;
|
package run.halo.app.service.impl;
|
||||||
|
|
||||||
|
import cn.hutool.core.io.IoUtil;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
import run.halo.app.model.dto.post.BasePostDetailDTO;
|
||||||
import run.halo.app.service.BackupService;
|
import run.halo.app.service.BackupService;
|
||||||
|
import run.halo.app.service.PostService;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Backup service implementation.
|
* Backup service implementation.
|
||||||
|
@ -12,4 +18,20 @@ import run.halo.app.service.BackupService;
|
||||||
@Service
|
@Service
|
||||||
public class BackupServiceImpl implements BackupService {
|
public class BackupServiceImpl implements BackupService {
|
||||||
|
|
||||||
|
private final PostService postService;
|
||||||
|
|
||||||
|
public BackupServiceImpl(PostService postService) {
|
||||||
|
this.postService = postService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BasePostDetailDTO importMarkdowns(MultipartFile file) throws IOException {
|
||||||
|
|
||||||
|
// Read markdown content.
|
||||||
|
String markdown = IoUtil.read(file.getInputStream(), "UTF-8");
|
||||||
|
|
||||||
|
// TODO sheet import
|
||||||
|
|
||||||
|
return postService.importMarkdown(markdown, file.getOriginalFilename());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -470,6 +470,53 @@ public abstract class BaseCommentServiceImpl<COMMENT extends BaseComment> extend
|
||||||
return childrenList;
|
return childrenList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T extends BaseCommentDTO> T filterIpAddress(@NonNull T comment) {
|
||||||
|
Assert.notNull(comment, "Base comment dto must not be null");
|
||||||
|
|
||||||
|
// Clear ip address
|
||||||
|
comment.setIpAddress("");
|
||||||
|
|
||||||
|
// Handle base comment vo
|
||||||
|
if (comment instanceof BaseCommentVO) {
|
||||||
|
BaseCommentVO baseCommentVO = (BaseCommentVO) comment;
|
||||||
|
Queue<BaseCommentVO> commentQueue = new LinkedList<>();
|
||||||
|
commentQueue.offer(baseCommentVO);
|
||||||
|
while (!commentQueue.isEmpty()) {
|
||||||
|
BaseCommentVO current = commentQueue.poll();
|
||||||
|
|
||||||
|
// Clear ip address
|
||||||
|
current.setIpAddress("");
|
||||||
|
|
||||||
|
if (!CollectionUtils.isEmpty(current.getChildren())) {
|
||||||
|
// Add children
|
||||||
|
commentQueue.addAll(current.getChildren());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T extends BaseCommentDTO> List<T> filterIpAddress(List<T> comments) {
|
||||||
|
if (CollectionUtils.isEmpty(comments)) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
comments.forEach(this::filterIpAddress);
|
||||||
|
|
||||||
|
return comments;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T extends BaseCommentDTO> Page<T> filterIpAddress(Page<T> commentPage) {
|
||||||
|
Assert.notNull(commentPage, "Comment page must not be null");
|
||||||
|
commentPage.forEach(this::filterIpAddress);
|
||||||
|
|
||||||
|
return commentPage;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get children comments recursively.
|
* Get children comments recursively.
|
||||||
*
|
*
|
||||||
|
|
|
@ -342,7 +342,7 @@ public abstract class BasePostServiceImpl<POST extends BasePost> extends Abstrac
|
||||||
}
|
}
|
||||||
|
|
||||||
if (exist) {
|
if (exist) {
|
||||||
throw new AlreadyExistsException("The post url " + post.getUrl() + " has been exist");
|
throw new AlreadyExistsException("文章路径 " + post.getUrl() + " 已存在");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ import run.halo.app.repository.CategoryRepository;
|
||||||
import run.halo.app.service.CategoryService;
|
import run.halo.app.service.CategoryService;
|
||||||
import run.halo.app.service.PostCategoryService;
|
import run.halo.app.service.PostCategoryService;
|
||||||
import run.halo.app.service.base.AbstractCrudService;
|
import run.halo.app.service.base.AbstractCrudService;
|
||||||
|
import run.halo.app.utils.ServiceUtils;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
@ -52,11 +53,11 @@ public class CategoryServiceImpl extends AbstractCrudService<Category, Integer>
|
||||||
|
|
||||||
if (count > 0) {
|
if (count > 0) {
|
||||||
log.error("Category has exist already: [{}]", category);
|
log.error("Category has exist already: [{}]", category);
|
||||||
throw new AlreadyExistsException("The category has exist already");
|
throw new AlreadyExistsException("该分类已存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check parent id
|
// Check parent id
|
||||||
if (category.getParentId() > 0) {
|
if (!ServiceUtils.isEmptyId(category.getParentId())) {
|
||||||
count = categoryRepository.countById(category.getParentId());
|
count = categoryRepository.countById(category.getParentId());
|
||||||
|
|
||||||
if (count == 0) {
|
if (count == 0) {
|
||||||
|
@ -154,6 +155,11 @@ public class CategoryServiceImpl extends AbstractCrudService<Category, Integer>
|
||||||
return categoryRepository.getBySlugName(slugName).orElseThrow(() -> new NotFoundException("The Category does not exist").setErrorData(slugName));
|
return categoryRepository.getBySlugName(slugName).orElseThrow(() -> new NotFoundException("The Category does not exist").setErrorData(slugName));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Category getByName(String name) {
|
||||||
|
return categoryRepository.getByName(name).orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeCategoryAndPostCategoryBy(Integer categoryId) {
|
public void removeCategoryAndPostCategoryBy(Integer categoryId) {
|
||||||
// Remove category
|
// Remove category
|
||||||
|
|
|
@ -86,7 +86,7 @@ public class LinkServiceImpl extends AbstractCrudService<Link, Integer> implemen
|
||||||
boolean exist = existByName(linkParam.getName());
|
boolean exist = existByName(linkParam.getName());
|
||||||
|
|
||||||
if (exist) {
|
if (exist) {
|
||||||
throw new AlreadyExistsException("Link name " + linkParam.getName() + " has already existed").setErrorData(linkParam.getName());
|
throw new AlreadyExistsException("友情链接 " + linkParam.getName() + " 已存在").setErrorData(linkParam.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
return create(linkParam.convertTo());
|
return create(linkParam.convertTo());
|
||||||
|
|
|
@ -171,7 +171,7 @@ public class MenuServiceImpl extends AbstractCrudService<Menu, Integer> implemen
|
||||||
}
|
}
|
||||||
|
|
||||||
if (exist) {
|
if (exist) {
|
||||||
throw new AlreadyExistsException("The menu name " + menu.getName() + " already exists");
|
throw new AlreadyExistsException("菜单 " + menu.getName() + " 已存在");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -312,7 +312,7 @@ public class OptionServiceImpl extends AbstractCrudService<Option, Integer> impl
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Zone getQnYunZone() {
|
public Zone getQnYunZone() {
|
||||||
return getByProperty(QnYunProperties.ZONE).map(qiniuZone -> {
|
return getByProperty(QnYunProperties.OSS_ZONE).map(qiniuZone -> {
|
||||||
|
|
||||||
Zone zone;
|
Zone zone;
|
||||||
switch (qiniuZone.toString()) {
|
switch (qiniuZone.toString()) {
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
package run.halo.app.service.impl;
|
package run.halo.app.service.impl;
|
||||||
|
|
||||||
|
import cn.hutool.core.date.DateUtil;
|
||||||
import cn.hutool.core.text.StrBuilder;
|
import cn.hutool.core.text.StrBuilder;
|
||||||
|
import cn.hutool.core.util.RandomUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.context.ApplicationEventPublisher;
|
import org.springframework.context.ApplicationEventPublisher;
|
||||||
|
@ -29,6 +32,7 @@ import run.halo.app.service.*;
|
||||||
import run.halo.app.utils.DateUtils;
|
import run.halo.app.utils.DateUtils;
|
||||||
import run.halo.app.utils.MarkdownUtils;
|
import run.halo.app.utils.MarkdownUtils;
|
||||||
import run.halo.app.utils.ServiceUtils;
|
import run.halo.app.utils.ServiceUtils;
|
||||||
|
import run.halo.app.utils.SlugUtils;
|
||||||
|
|
||||||
import javax.persistence.criteria.Predicate;
|
import javax.persistence.criteria.Predicate;
|
||||||
import javax.persistence.criteria.Root;
|
import javax.persistence.criteria.Root;
|
||||||
|
@ -275,7 +279,7 @@ public class PostServiceImpl extends BasePostServiceImpl<Post> implements PostSe
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Post importMarkdown(String markdown) {
|
public PostDetailVO importMarkdown(String markdown, String filename) {
|
||||||
Assert.notNull(markdown, "Markdown document must not be null");
|
Assert.notNull(markdown, "Markdown document must not be null");
|
||||||
|
|
||||||
// Render markdown to html document.
|
// Render markdown to html document.
|
||||||
|
@ -284,7 +288,80 @@ public class PostServiceImpl extends BasePostServiceImpl<Post> implements PostSe
|
||||||
// Gets frontMatter
|
// Gets frontMatter
|
||||||
Map<String, List<String>> frontMatter = MarkdownUtils.getFrontMatter(markdown);
|
Map<String, List<String>> frontMatter = MarkdownUtils.getFrontMatter(markdown);
|
||||||
|
|
||||||
return null;
|
Post post = new Post();
|
||||||
|
|
||||||
|
List<String> elementValue;
|
||||||
|
|
||||||
|
Set<Integer> tagIds = new HashSet<>();
|
||||||
|
|
||||||
|
Set<Integer> categoryIds = new HashSet<>();
|
||||||
|
if (frontMatter.size() > 0) {
|
||||||
|
for (String key : frontMatter.keySet()) {
|
||||||
|
elementValue = frontMatter.get(key);
|
||||||
|
for (String ele : elementValue) {
|
||||||
|
switch (key) {
|
||||||
|
case "title":
|
||||||
|
post.setTitle(ele);
|
||||||
|
break;
|
||||||
|
case "date":
|
||||||
|
post.setCreateTime(DateUtil.parse(ele));
|
||||||
|
break;
|
||||||
|
case "updated":
|
||||||
|
post.setUpdateTime(DateUtil.parse(ele));
|
||||||
|
break;
|
||||||
|
case "permalink":
|
||||||
|
post.setUrl(ele);
|
||||||
|
break;
|
||||||
|
case "thumbnail":
|
||||||
|
post.setThumbnail(ele);
|
||||||
|
break;
|
||||||
|
case "status":
|
||||||
|
post.setStatus(PostStatus.valueOf(ele));
|
||||||
|
break;
|
||||||
|
case "comments":
|
||||||
|
post.setDisallowComment(Boolean.parseBoolean(ele));
|
||||||
|
break;
|
||||||
|
case "tags":
|
||||||
|
Tag tag = tagService.getByName(ele);
|
||||||
|
if (null == tag) {
|
||||||
|
tag = new Tag();
|
||||||
|
tag.setName(ele);
|
||||||
|
tag.setSlugName(SlugUtils.slugify(ele));
|
||||||
|
tag = tagService.create(tag);
|
||||||
|
}
|
||||||
|
tagIds.add(tag.getId());
|
||||||
|
case "categories":
|
||||||
|
Category category = categoryService.getByName(ele);
|
||||||
|
if (null == category) {
|
||||||
|
category = new Category();
|
||||||
|
category.setName(ele);
|
||||||
|
category.setSlugName(SlugUtils.slugify(ele));
|
||||||
|
category.setDescription(ele);
|
||||||
|
category = categoryService.create(category);
|
||||||
|
}
|
||||||
|
categoryIds.add(category.getId());
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null == post.getStatus()) {
|
||||||
|
post.setStatus(PostStatus.PUBLISHED);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StrUtil.isEmpty(post.getTitle())) {
|
||||||
|
post.setTitle(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StrUtil.isEmpty(post.getUrl())) {
|
||||||
|
post.setUrl(DateUtil.format(new Date(), "yyyyMMddHHmmss" + RandomUtil.randomNumbers(5)));
|
||||||
|
}
|
||||||
|
|
||||||
|
post.setOriginalContent(markdown);
|
||||||
|
|
||||||
|
return createBy(post, tagIds, categoryIds, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -128,7 +128,7 @@ public class RecoveryServiceImpl implements RecoveryService {
|
||||||
log.debug("Migrated posts: [{}]", posts);
|
log.debug("Migrated posts: [{}]", posts);
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new ServiceException("Failed to read multipart file " + file.getOriginalFilename(), e);
|
throw new ServiceException("备份文件 " + file.getOriginalFilename() + " 读取失败", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ import org.springframework.stereotype.Service;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import run.halo.app.event.logger.LogEvent;
|
import run.halo.app.event.logger.LogEvent;
|
||||||
import run.halo.app.event.post.SheetVisitEvent;
|
import run.halo.app.event.post.SheetVisitEvent;
|
||||||
|
import run.halo.app.model.dto.InternalSheetDTO;
|
||||||
import run.halo.app.model.entity.Sheet;
|
import run.halo.app.model.entity.Sheet;
|
||||||
import run.halo.app.model.enums.LogType;
|
import run.halo.app.model.enums.LogType;
|
||||||
import run.halo.app.model.enums.PostStatus;
|
import run.halo.app.model.enums.PostStatus;
|
||||||
|
@ -16,9 +17,11 @@ import run.halo.app.repository.SheetRepository;
|
||||||
import run.halo.app.service.OptionService;
|
import run.halo.app.service.OptionService;
|
||||||
import run.halo.app.service.SheetCommentService;
|
import run.halo.app.service.SheetCommentService;
|
||||||
import run.halo.app.service.SheetService;
|
import run.halo.app.service.SheetService;
|
||||||
|
import run.halo.app.service.ThemeService;
|
||||||
import run.halo.app.utils.MarkdownUtils;
|
import run.halo.app.utils.MarkdownUtils;
|
||||||
import run.halo.app.utils.ServiceUtils;
|
import run.halo.app.utils.ServiceUtils;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -27,6 +30,7 @@ import java.util.Set;
|
||||||
* Sheet service implementation.
|
* Sheet service implementation.
|
||||||
*
|
*
|
||||||
* @author johnniang
|
* @author johnniang
|
||||||
|
* @author ryanwang
|
||||||
* @date 19-4-24
|
* @date 19-4-24
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
|
@ -38,14 +42,18 @@ public class SheetServiceImpl extends BasePostServiceImpl<Sheet> implements Shee
|
||||||
|
|
||||||
private final SheetCommentService sheetCommentService;
|
private final SheetCommentService sheetCommentService;
|
||||||
|
|
||||||
|
private final ThemeService themeService;
|
||||||
|
|
||||||
public SheetServiceImpl(SheetRepository sheetRepository,
|
public SheetServiceImpl(SheetRepository sheetRepository,
|
||||||
ApplicationEventPublisher eventPublisher,
|
ApplicationEventPublisher eventPublisher,
|
||||||
SheetCommentService sheetCommentService,
|
SheetCommentService sheetCommentService,
|
||||||
OptionService optionService) {
|
OptionService optionService,
|
||||||
|
ThemeService themeService) {
|
||||||
super(sheetRepository, optionService);
|
super(sheetRepository, optionService);
|
||||||
this.sheetRepository = sheetRepository;
|
this.sheetRepository = sheetRepository;
|
||||||
this.eventPublisher = eventPublisher;
|
this.eventPublisher = eventPublisher;
|
||||||
this.sheetCommentService = sheetCommentService;
|
this.sheetCommentService = sheetCommentService;
|
||||||
|
this.themeService = themeService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -136,6 +144,39 @@ public class SheetServiceImpl extends BasePostServiceImpl<Sheet> implements Shee
|
||||||
return content.toString();
|
return content.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<InternalSheetDTO> listInternal() {
|
||||||
|
|
||||||
|
List<InternalSheetDTO> internalSheetDTOS = new ArrayList<>();
|
||||||
|
|
||||||
|
// links sheet
|
||||||
|
InternalSheetDTO linkSheet = new InternalSheetDTO();
|
||||||
|
linkSheet.setId(1);
|
||||||
|
linkSheet.setTitle("友情链接");
|
||||||
|
linkSheet.setUrl("/links");
|
||||||
|
linkSheet.setStatus(themeService.templateExists("links.ftl"));
|
||||||
|
|
||||||
|
// photos sheet
|
||||||
|
InternalSheetDTO photoSheet = new InternalSheetDTO();
|
||||||
|
photoSheet.setId(2);
|
||||||
|
photoSheet.setTitle("图库页面");
|
||||||
|
photoSheet.setUrl("/photos");
|
||||||
|
photoSheet.setStatus(themeService.templateExists("photos.ftl"));
|
||||||
|
|
||||||
|
// journals sheet
|
||||||
|
InternalSheetDTO journalSheet = new InternalSheetDTO();
|
||||||
|
journalSheet.setId(3);
|
||||||
|
journalSheet.setTitle("日志页面");
|
||||||
|
journalSheet.setUrl("/journals");
|
||||||
|
journalSheet.setStatus(themeService.templateExists("journals.ftl"));
|
||||||
|
|
||||||
|
internalSheetDTOS.add(linkSheet);
|
||||||
|
internalSheetDTOS.add(photoSheet);
|
||||||
|
internalSheetDTOS.add(journalSheet);
|
||||||
|
|
||||||
|
return internalSheetDTOS;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Sheet removeById(Integer id) {
|
public Sheet removeById(Integer id) {
|
||||||
Sheet sheet = super.removeById(id);
|
Sheet sheet = super.removeById(id);
|
||||||
|
|
|
@ -42,7 +42,7 @@ public class TagServiceImpl extends AbstractCrudService<Tag, Integer> implements
|
||||||
|
|
||||||
if (count > 0) {
|
if (count > 0) {
|
||||||
// If the tag has exist already
|
// If the tag has exist already
|
||||||
throw new AlreadyExistsException("The tag has already exist").setErrorData(tag);
|
throw new AlreadyExistsException("该标签已存在").setErrorData(tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get tag name
|
// Get tag name
|
||||||
|
@ -60,6 +60,12 @@ public class TagServiceImpl extends AbstractCrudService<Tag, Integer> implements
|
||||||
return tagRepository.getBySlugName(slugName).orElseThrow(() -> new NotFoundException("The tag does not exist").setErrorData(slugName));
|
return tagRepository.getBySlugName(slugName).orElseThrow(() -> new NotFoundException("The tag does not exist").setErrorData(slugName));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Tag getByName(String name) {
|
||||||
|
return tagRepository.getByName(name).orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TagDTO convertTo(Tag tag) {
|
public TagDTO convertTo(Tag tag) {
|
||||||
Assert.notNull(tag, "Tag must not be null");
|
Assert.notNull(tag, "Tag must not be null");
|
||||||
|
|
|
@ -3,7 +3,12 @@ package run.halo.app.service.impl;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.eclipse.jgit.api.Git;
|
import org.eclipse.jgit.api.Git;
|
||||||
|
import org.eclipse.jgit.api.PullResult;
|
||||||
import org.eclipse.jgit.api.errors.GitAPIException;
|
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||||
|
import org.eclipse.jgit.errors.RepositoryNotFoundException;
|
||||||
|
import org.eclipse.jgit.lib.Ref;
|
||||||
|
import org.eclipse.jgit.transport.RemoteConfig;
|
||||||
|
import org.eclipse.jgit.transport.URIish;
|
||||||
import org.springframework.context.ApplicationEventPublisher;
|
import org.springframework.context.ApplicationEventPublisher;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.lang.NonNull;
|
import org.springframework.lang.NonNull;
|
||||||
|
@ -32,7 +37,9 @@ import run.halo.app.utils.FilenameUtils;
|
||||||
import run.halo.app.utils.HaloUtils;
|
import run.halo.app.utils.HaloUtils;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
@ -241,7 +248,7 @@ public class ThemeServiceImpl implements ThemeService {
|
||||||
|
|
||||||
if (themeId.equals(getActivatedThemeId())) {
|
if (themeId.equals(getActivatedThemeId())) {
|
||||||
// Prevent to delete the activated theme
|
// Prevent to delete the activated theme
|
||||||
throw new BadRequestException("You can't delete the activated theme").setErrorData(themeId);
|
throw new BadRequestException("不能删除正在使用的主题").setErrorData(themeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -251,7 +258,7 @@ public class ThemeServiceImpl implements ThemeService {
|
||||||
// Delete theme cache
|
// Delete theme cache
|
||||||
eventPublisher.publishEvent(new ThemeUpdatedEvent(this));
|
eventPublisher.publishEvent(new ThemeUpdatedEvent(this));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new ServiceException("Failed to delete theme folder", e).setErrorData(themeId);
|
throw new ServiceException("主题删除失败", e).setErrorData(themeId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -288,7 +295,7 @@ public class ThemeServiceImpl implements ThemeService {
|
||||||
|
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new ServiceException("Failed to read options file", e);
|
throw new ServiceException("读取主题配置文件失败", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -362,7 +369,7 @@ public class ThemeServiceImpl implements ThemeService {
|
||||||
Assert.notNull(file, "Multipart file must not be null");
|
Assert.notNull(file, "Multipart file must not be null");
|
||||||
|
|
||||||
if (!StringUtils.endsWithIgnoreCase(file.getOriginalFilename(), ".zip")) {
|
if (!StringUtils.endsWithIgnoreCase(file.getOriginalFilename(), ".zip")) {
|
||||||
throw new UnsupportedMediaTypeException("Unsupported theme media type: " + file.getContentType()).setErrorData(file.getOriginalFilename());
|
throw new UnsupportedMediaTypeException("不支持的文件类型: " + file.getContentType()).setErrorData(file.getOriginalFilename());
|
||||||
}
|
}
|
||||||
|
|
||||||
ZipInputStream zis = null;
|
ZipInputStream zis = null;
|
||||||
|
@ -386,7 +393,7 @@ public class ThemeServiceImpl implements ThemeService {
|
||||||
// Go to the base folder and add the theme into system
|
// Go to the base folder and add the theme into system
|
||||||
return add(FileUtils.skipZipParentFolder(themeTempPath));
|
return add(FileUtils.skipZipParentFolder(themeTempPath));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new ServiceException("Failed to upload theme file: " + file.getOriginalFilename(), e);
|
throw new ServiceException("上传主题失败: " + file.getOriginalFilename(), e);
|
||||||
} finally {
|
} finally {
|
||||||
// Close zip input stream
|
// Close zip input stream
|
||||||
FileUtils.closeQuietly(zis);
|
FileUtils.closeQuietly(zis);
|
||||||
|
@ -440,15 +447,16 @@ public class ThemeServiceImpl implements ThemeService {
|
||||||
// Create temp path
|
// Create temp path
|
||||||
Path themeTmpPath = tmpPath.resolve(HaloUtils.randomUUIDWithoutDash());
|
Path themeTmpPath = tmpPath.resolve(HaloUtils.randomUUIDWithoutDash());
|
||||||
|
|
||||||
if (StringUtils.endsWithIgnoreCase(uri, ".git")) {
|
if (StringUtils.endsWithIgnoreCase(uri, ".zip")) {
|
||||||
cloneFromGit(uri, themeTmpPath);
|
|
||||||
} else {
|
|
||||||
downloadZipAndUnzip(uri, themeTmpPath);
|
downloadZipAndUnzip(uri, themeTmpPath);
|
||||||
|
} else {
|
||||||
|
uri = StringUtils.appendIfMissingIgnoreCase(uri, ".git", ".git");
|
||||||
|
cloneFromGit(uri, themeTmpPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
return add(themeTmpPath);
|
return add(themeTmpPath);
|
||||||
} catch (IOException | GitAPIException e) {
|
} catch (IOException | GitAPIException e) {
|
||||||
throw new ServiceException("Failed to fetch theme from remote " + uri, e);
|
throw new ServiceException("主题拉取失败 " + uri, e);
|
||||||
} finally {
|
} finally {
|
||||||
FileUtils.deleteFolderQuietly(tmpPath);
|
FileUtils.deleteFolderQuietly(tmpPath);
|
||||||
}
|
}
|
||||||
|
@ -459,12 +467,93 @@ public class ThemeServiceImpl implements ThemeService {
|
||||||
eventPublisher.publishEvent(new ThemeUpdatedEvent(this));
|
eventPublisher.publishEvent(new ThemeUpdatedEvent(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ThemeProperty update(String themeId) {
|
||||||
|
Assert.hasText(themeId, "Theme id must not be blank");
|
||||||
|
|
||||||
|
ThemeProperty updatingTheme = getThemeOfNonNullBy(themeId);
|
||||||
|
|
||||||
|
try {
|
||||||
|
pullFromGit(updatingTheme);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new ThemeUpdateException("主题更新失败!您与主题作者可能同时更改了同一个文件,您也可以尝试删除主题并重新拉取最新的主题", e).setErrorData(themeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
eventPublisher.publishEvent(new ThemeUpdatedEvent(this));
|
||||||
|
|
||||||
|
return getThemeOfNonNullBy(themeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void pullFromGit(@NonNull ThemeProperty themeProperty) throws IOException, GitAPIException, URISyntaxException {
|
||||||
|
Assert.notNull(themeProperty, "Theme property must not be null");
|
||||||
|
|
||||||
|
// Get branch
|
||||||
|
String branch = StringUtils.isBlank(themeProperty.getBranch()) ?
|
||||||
|
DEFAULT_REMOTE_BRANCH : themeProperty.getBranch();
|
||||||
|
|
||||||
|
File themeFolder = Paths.get(themeProperty.getThemePath()).toFile();
|
||||||
|
// Open the theme path
|
||||||
|
Git git;
|
||||||
|
|
||||||
|
try {
|
||||||
|
git = Git.open(themeFolder);
|
||||||
|
} catch (RepositoryNotFoundException e) {
|
||||||
|
// Repository is not initialized
|
||||||
|
git = Git.init().setDirectory(themeFolder).call();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force to set remote name
|
||||||
|
git.remoteRemove().setRemoteName(THEME_PROVIDER_REMOTE_NAME).call();
|
||||||
|
RemoteConfig remoteConfig = git.remoteAdd()
|
||||||
|
.setName(THEME_PROVIDER_REMOTE_NAME)
|
||||||
|
.setUri(new URIish(themeProperty.getRepo()))
|
||||||
|
.call();
|
||||||
|
|
||||||
|
// Add all changes
|
||||||
|
git.add()
|
||||||
|
.addFilepattern(".")
|
||||||
|
.call();
|
||||||
|
// Commit the changes
|
||||||
|
git.commit().setMessage("Commit by halo automatically").call();
|
||||||
|
|
||||||
|
// Check out to specified branch
|
||||||
|
if (!StringUtils.equalsIgnoreCase(branch, git.getRepository().getBranch())) {
|
||||||
|
boolean present = git.branchList()
|
||||||
|
.call()
|
||||||
|
.stream()
|
||||||
|
.map(Ref::getName)
|
||||||
|
.anyMatch(name -> StringUtils.equalsIgnoreCase(name, branch));
|
||||||
|
|
||||||
|
git.checkout()
|
||||||
|
.setCreateBranch(true)
|
||||||
|
.setForced(!present)
|
||||||
|
.setName(branch)
|
||||||
|
.call();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pull with rebasing
|
||||||
|
PullResult pullResult = git.pull()
|
||||||
|
.setRemote(remoteConfig.getName())
|
||||||
|
.setRemoteBranchName(branch)
|
||||||
|
.setRebase(true)
|
||||||
|
.call();
|
||||||
|
|
||||||
|
if (!pullResult.isSuccessful()) {
|
||||||
|
log.debug("Rebase result: [{}]", pullResult.getRebaseResult());
|
||||||
|
log.debug("Merge result: [{}]", pullResult.getMergeResult());
|
||||||
|
|
||||||
|
throw new ThemeUpdateException("拉取失败!您与主题作者可能同时更改了同一个文件");
|
||||||
|
}
|
||||||
|
// Close git
|
||||||
|
git.close();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clones theme from git.
|
* Clones theme from git.
|
||||||
*
|
*
|
||||||
* @param gitUrl git url must not be blank
|
* @param gitUrl git url must not be blank
|
||||||
* @param targetPath target path must not be null
|
* @param targetPath target path must not be null
|
||||||
* @throws GitAPIException
|
* @throws GitAPIException throws when clone error
|
||||||
*/
|
*/
|
||||||
private void cloneFromGit(@NonNull String gitUrl, @NonNull Path targetPath) throws GitAPIException {
|
private void cloneFromGit(@NonNull String gitUrl, @NonNull Path targetPath) throws GitAPIException {
|
||||||
Assert.hasText(gitUrl, "Git url must not be blank");
|
Assert.hasText(gitUrl, "Git url must not be blank");
|
||||||
|
@ -486,7 +575,7 @@ public class ThemeServiceImpl implements ThemeService {
|
||||||
*
|
*
|
||||||
* @param zipUrl zip url must not be null
|
* @param zipUrl zip url must not be null
|
||||||
* @param targetPath target path must not be null
|
* @param targetPath target path must not be null
|
||||||
* @throws IOException
|
* @throws IOException throws when download zip or unzip error
|
||||||
*/
|
*/
|
||||||
private void downloadZipAndUnzip(@NonNull String zipUrl, @NonNull Path targetPath) throws IOException {
|
private void downloadZipAndUnzip(@NonNull String zipUrl, @NonNull Path targetPath) throws IOException {
|
||||||
Assert.hasText(zipUrl, "Zip url must not be blank");
|
Assert.hasText(zipUrl, "Zip url must not be blank");
|
||||||
|
@ -498,7 +587,7 @@ public class ThemeServiceImpl implements ThemeService {
|
||||||
log.debug("Download response: [{}]", downloadResponse.getStatusCode());
|
log.debug("Download response: [{}]", downloadResponse.getStatusCode());
|
||||||
|
|
||||||
if (downloadResponse.getStatusCode().isError() || downloadResponse.getBody() == null) {
|
if (downloadResponse.getStatusCode().isError() || downloadResponse.getBody() == null) {
|
||||||
throw new ServiceException("Failed to download " + zipUrl + ", status: " + downloadResponse.getStatusCode());
|
throw new ServiceException("下载失败 " + zipUrl + ", 状态码: " + downloadResponse.getStatusCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
log.debug("Downloaded [{}]", zipUrl);
|
log.debug("Downloaded [{}]", zipUrl);
|
||||||
|
@ -514,7 +603,7 @@ public class ThemeServiceImpl implements ThemeService {
|
||||||
* Creates temporary path.
|
* Creates temporary path.
|
||||||
*
|
*
|
||||||
* @return temporary path
|
* @return temporary path
|
||||||
* @throws IOException
|
* @throws IOException if an I/O error occurs or the temporary-file directory does not exist
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
private Path createTempPath() throws IOException {
|
private Path createTempPath() throws IOException {
|
||||||
|
|
|
@ -105,7 +105,7 @@ public class ThemeSettingServiceImpl extends AbstractCrudService<ThemeSetting, I
|
||||||
try {
|
try {
|
||||||
configuration.setSharedVariable("settings", listAsMapBy(themeService.getActivatedThemeId()));
|
configuration.setSharedVariable("settings", listAsMapBy(themeService.getActivatedThemeId()));
|
||||||
} catch (TemplateModelException e) {
|
} catch (TemplateModelException e) {
|
||||||
throw new ServiceException("Save theme settings error", e);
|
throw new ServiceException("主题设置保存失败", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -106,7 +106,7 @@ public class UserServiceImpl extends AbstractCrudService<User, Integer> implemen
|
||||||
Assert.notNull(userId, "User id must not be blank");
|
Assert.notNull(userId, "User id must not be blank");
|
||||||
|
|
||||||
if (oldPassword.equals(newPassword)) {
|
if (oldPassword.equals(newPassword)) {
|
||||||
throw new BadRequestException("There is nothing changed because new password is equal to old password");
|
throw new BadRequestException("新密码和旧密码不能相同");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the user
|
// Get the user
|
||||||
|
@ -114,7 +114,7 @@ public class UserServiceImpl extends AbstractCrudService<User, Integer> implemen
|
||||||
|
|
||||||
// Check the user old password
|
// Check the user old password
|
||||||
if (!BCrypt.checkpw(oldPassword, user.getPassword())) {
|
if (!BCrypt.checkpw(oldPassword, user.getPassword())) {
|
||||||
throw new BadRequestException("Old password is mismatch").setErrorData(oldPassword);
|
throw new BadRequestException("旧密码错误").setErrorData(oldPassword);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set new password
|
// Set new password
|
||||||
|
@ -148,7 +148,7 @@ public class UserServiceImpl extends AbstractCrudService<User, Integer> implemen
|
||||||
if (user.getExpireTime() != null && user.getExpireTime().after(now)) {
|
if (user.getExpireTime() != null && user.getExpireTime().after(now)) {
|
||||||
long seconds = TimeUnit.MILLISECONDS.toSeconds(user.getExpireTime().getTime() - now.getTime());
|
long seconds = TimeUnit.MILLISECONDS.toSeconds(user.getExpireTime().getTime() - now.getTime());
|
||||||
// If expired
|
// If expired
|
||||||
throw new ForbiddenException("You have been temporarily disabled,please try again " + HaloUtils.timeFormat(seconds) + " later").setErrorData(seconds);
|
throw new ForbiddenException("账号已被停用,请 " + HaloUtils.timeFormat(seconds) + " 后重试").setErrorData(seconds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,7 +164,7 @@ public class UserServiceImpl extends AbstractCrudService<User, Integer> implemen
|
||||||
public User create(User user) {
|
public User create(User user) {
|
||||||
// Check user
|
// Check user
|
||||||
if (count() != 0) {
|
if (count() != 0) {
|
||||||
throw new BadRequestException("This blog already exists a blogger");
|
throw new BadRequestException("当前博客已有用户");
|
||||||
}
|
}
|
||||||
|
|
||||||
User createdUser = super.create(user);
|
User createdUser = super.create(user);
|
||||||
|
|
|
@ -64,7 +64,7 @@ public class FileUtils {
|
||||||
*
|
*
|
||||||
* @param deletingPath deleting path must not be null
|
* @param deletingPath deleting path must not be null
|
||||||
*/
|
*/
|
||||||
public static void deleteFolder(Path deletingPath) throws IOException {
|
public static void deleteFolder(@NonNull Path deletingPath) throws IOException {
|
||||||
Assert.notNull(deletingPath, "Deleting path must not be null");
|
Assert.notNull(deletingPath, "Deleting path must not be null");
|
||||||
|
|
||||||
log.debug("Deleting [{}]", deletingPath);
|
log.debug("Deleting [{}]", deletingPath);
|
||||||
|
@ -249,9 +249,9 @@ public class FileUtils {
|
||||||
/**
|
/**
|
||||||
* Deletes folder quietly.
|
* Deletes folder quietly.
|
||||||
*
|
*
|
||||||
* @param deletingPath deleting path must not be null
|
* @param deletingPath deleting path
|
||||||
*/
|
*/
|
||||||
public static void deleteFolderQuietly(@NonNull Path deletingPath) {
|
public static void deleteFolderQuietly(@Nullable Path deletingPath) {
|
||||||
try {
|
try {
|
||||||
if (deletingPath != null) {
|
if (deletingPath != null) {
|
||||||
FileUtils.deleteFolder(deletingPath);
|
FileUtils.deleteFolder(deletingPath);
|
||||||
|
|
|
@ -1,31 +1,19 @@
|
||||||
package run.halo.app.utils;
|
package run.halo.app.utils;
|
||||||
|
|
||||||
import cn.hutool.core.text.StrBuilder;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.lang.NonNull;
|
import org.springframework.lang.NonNull;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
|
||||||
import java.awt.image.BufferedImage;
|
|
||||||
import java.io.*;
|
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.URL;
|
|
||||||
import java.net.URLConnection;
|
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.nio.file.attribute.BasicFileAttributes;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import static run.halo.app.model.support.HaloConst.FILE_SEPARATOR;
|
import static run.halo.app.model.support.HaloConst.FILE_SEPARATOR;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <pre>
|
* Common utils
|
||||||
* 常用工具
|
|
||||||
* </pre>
|
|
||||||
*
|
*
|
||||||
* @author ryanwang
|
* @author ryanwang
|
||||||
* @date : 2017/12/22
|
* @date : 2017/12/22
|
||||||
|
@ -165,187 +153,4 @@ public class HaloUtils {
|
||||||
}
|
}
|
||||||
return machineAddress.getHostAddress();
|
return machineAddress.getHostAddress();
|
||||||
}
|
}
|
||||||
|
|
||||||
// /**
|
|
||||||
// * 获取备份文件信息
|
|
||||||
// *
|
|
||||||
// * @param dir dir
|
|
||||||
// * @return List
|
|
||||||
// */
|
|
||||||
// public static List<BackupDto> getBackUps(String dir) {
|
|
||||||
// final StrBuilder srcPathStr = new StrBuilder(System.getProperties().getProperty("user.home"));
|
|
||||||
// srcPathStr.append("/halo/backup/");
|
|
||||||
// srcPathStr.append(dir);
|
|
||||||
// final File srcPath = new File(srcPathStr.toString());
|
|
||||||
// final File[] files = srcPath.listFiles();
|
|
||||||
// final List<BackupDto> backupDtos = new ArrayList<>();
|
|
||||||
// BackupDto backupDto;
|
|
||||||
// // 遍历文件
|
|
||||||
// if (null != files) {
|
|
||||||
// for (File file : files) {
|
|
||||||
// if (file.isFile()) {
|
|
||||||
// if (StrUtil.equals(file.getName(), ".DS_Store")) {
|
|
||||||
// continue;
|
|
||||||
// }
|
|
||||||
// backupDto = new BackupDto();
|
|
||||||
// backupDto.setFileName(file.getName());
|
|
||||||
// backupDto.setCreateAt(getCreateTime(file.getAbsolutePath()));
|
|
||||||
// backupDto.setFileType(FileUtil.getType(file));
|
|
||||||
// backupDto.setFileSize(parseSize(file.length()));
|
|
||||||
// backupDto.setBackupType(dir);
|
|
||||||
// backupDtos.add(backupDto);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return backupDtos;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * 转换文件大小
|
|
||||||
// *
|
|
||||||
// * @param size size
|
|
||||||
// * @return String
|
|
||||||
// */
|
|
||||||
// public static String parseSize(long size) {
|
|
||||||
// if (size < CommonParamsEnum.BYTE.getValue()) {
|
|
||||||
// return size + "B";
|
|
||||||
// } else {
|
|
||||||
// size = size / 1024;
|
|
||||||
// }
|
|
||||||
// if (size < CommonParamsEnum.BYTE.getValue()) {
|
|
||||||
// return size + "KB";
|
|
||||||
// } else {
|
|
||||||
// size = size / 1024;
|
|
||||||
// }
|
|
||||||
// if (size < CommonParamsEnum.BYTE.getValue()) {
|
|
||||||
// size = size * 100;
|
|
||||||
// return size / 100 + "." + size % 100 + "MB";
|
|
||||||
// } else {
|
|
||||||
// size = size * 100 / 1024;
|
|
||||||
// return size / 100 + "." + size % 100 + "GB";
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取文件创建时间
|
|
||||||
*
|
|
||||||
* @param srcPath 文件绝对路径
|
|
||||||
* @return 时间
|
|
||||||
*/
|
|
||||||
public static Date getCreateTime(String srcPath) {
|
|
||||||
try {
|
|
||||||
BasicFileAttributes basicFileAttributes = Files.readAttributes(Paths.get(srcPath), BasicFileAttributes.class);
|
|
||||||
return new Date(basicFileAttributes.creationTime().toMillis());
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException("Failed to open the " + srcPath + " file", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取文件长和宽
|
|
||||||
*
|
|
||||||
* @param file file
|
|
||||||
* @return String
|
|
||||||
*/
|
|
||||||
public static String getImageWh(File file) {
|
|
||||||
try {
|
|
||||||
final BufferedImage image = ImageIO.read(new FileInputStream(file));
|
|
||||||
return image.getWidth() + "x" + image.getHeight();
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException("Failed to get read image file", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 导出为文件
|
|
||||||
*
|
|
||||||
* @param data 内容
|
|
||||||
* @param filePath 保存路径
|
|
||||||
* @param fileName 文件名
|
|
||||||
*/
|
|
||||||
public static void postToFile(String data, String filePath, String fileName) throws IOException {
|
|
||||||
FileWriter fileWriter = null;
|
|
||||||
BufferedWriter bufferedWriter = null;
|
|
||||||
try {
|
|
||||||
final File file = new File(filePath);
|
|
||||||
if (!file.exists()) {
|
|
||||||
file.mkdirs();
|
|
||||||
}
|
|
||||||
fileWriter = new FileWriter(file.getAbsoluteFile() + "/" + fileName, true);
|
|
||||||
bufferedWriter = new BufferedWriter(fileWriter);
|
|
||||||
bufferedWriter.write(data);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException("Failed to export file", e);
|
|
||||||
} finally {
|
|
||||||
if (null != bufferedWriter) {
|
|
||||||
bufferedWriter.close();
|
|
||||||
}
|
|
||||||
if (null != fileWriter) {
|
|
||||||
fileWriter.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 百度主动推送
|
|
||||||
*
|
|
||||||
* @param blogUrl 博客地址
|
|
||||||
* @param token 百度推送token
|
|
||||||
* @param urls 文章路径
|
|
||||||
* @return String
|
|
||||||
*/
|
|
||||||
public static String baiduPost(String blogUrl, String token, String urls) {
|
|
||||||
Assert.hasText(blogUrl, "blog url must not be blank");
|
|
||||||
Assert.hasText(token, "token must not be blank");
|
|
||||||
Assert.hasText(urls, "urls must not be blank");
|
|
||||||
|
|
||||||
final StrBuilder url = new StrBuilder("http://data.zz.baidu.com/urls?site=");
|
|
||||||
url.append(blogUrl);
|
|
||||||
url.append("&token=");
|
|
||||||
url.append(token);
|
|
||||||
|
|
||||||
final StrBuilder result = new StrBuilder();
|
|
||||||
PrintWriter out = null;
|
|
||||||
BufferedReader in = null;
|
|
||||||
try {
|
|
||||||
// 建立URL之间的连接
|
|
||||||
final URLConnection conn = new URL(url.toString()).openConnection();
|
|
||||||
// 设置通用的请求属性
|
|
||||||
conn.setRequestProperty("Host", "data.zz.baidu.com");
|
|
||||||
conn.setRequestProperty("User-Agent", "curl/7.12.1");
|
|
||||||
conn.setRequestProperty("Content-Length", "83");
|
|
||||||
conn.setRequestProperty("Content-Type", "text/plain");
|
|
||||||
|
|
||||||
// 发送POST请求必须设置如下两行
|
|
||||||
conn.setDoInput(true);
|
|
||||||
conn.setDoOutput(true);
|
|
||||||
|
|
||||||
// 获取conn对应的输出流
|
|
||||||
out = new PrintWriter(conn.getOutputStream());
|
|
||||||
out.print(urls.trim());
|
|
||||||
// 进行输出流的缓冲
|
|
||||||
out.flush();
|
|
||||||
// 通过BufferedReader输入流来读取Url的响应
|
|
||||||
in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
|
|
||||||
String line;
|
|
||||||
while ((line = in.readLine()) != null) {
|
|
||||||
result.append(line);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException("Failed to push posts to baidu", e);
|
|
||||||
} finally {
|
|
||||||
try {
|
|
||||||
if (null != out) {
|
|
||||||
out.close();
|
|
||||||
}
|
|
||||||
if (null != in) {
|
|
||||||
in.close();
|
|
||||||
}
|
|
||||||
} catch (IOException ex) {
|
|
||||||
// Ignore this exception
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,13 @@ package run.halo.app.utils;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.commonmark.Extension;
|
import org.commonmark.Extension;
|
||||||
|
import org.commonmark.ext.autolink.AutolinkExtension;
|
||||||
import org.commonmark.ext.front.matter.YamlFrontMatterExtension;
|
import org.commonmark.ext.front.matter.YamlFrontMatterExtension;
|
||||||
import org.commonmark.ext.front.matter.YamlFrontMatterVisitor;
|
import org.commonmark.ext.front.matter.YamlFrontMatterVisitor;
|
||||||
|
import org.commonmark.ext.gfm.strikethrough.StrikethroughExtension;
|
||||||
import org.commonmark.ext.gfm.tables.TablesExtension;
|
import org.commonmark.ext.gfm.tables.TablesExtension;
|
||||||
|
import org.commonmark.ext.heading.anchor.HeadingAnchorExtension;
|
||||||
|
import org.commonmark.ext.ins.InsExtension;
|
||||||
import org.commonmark.node.Node;
|
import org.commonmark.node.Node;
|
||||||
import org.commonmark.parser.Parser;
|
import org.commonmark.parser.Parser;
|
||||||
import org.commonmark.renderer.html.HtmlRenderer;
|
import org.commonmark.renderer.html.HtmlRenderer;
|
||||||
|
@ -26,30 +30,73 @@ import java.util.Set;
|
||||||
*/
|
*/
|
||||||
public class MarkdownUtils {
|
public class MarkdownUtils {
|
||||||
|
|
||||||
/**
|
|
||||||
* Front-matter extension
|
|
||||||
*/
|
|
||||||
private static final Set<Extension> EXTENSIONS_YAML = Collections.singleton(YamlFrontMatterExtension.create());
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Table extension
|
* commonmark-java extension for autolinking
|
||||||
*/
|
*/
|
||||||
private static final Set<Extension> EXTENSIONS_TABLE = Collections.singleton(TablesExtension.create());
|
private static final Set<Extension> EXTENSIONS_AUTO_LINK = Collections.singleton(AutolinkExtension.create());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* commonmark-java extension for strikethrough
|
||||||
|
*/
|
||||||
|
private static final Set<Extension> EXTENSIONS_STRIKETHROUGH = Collections.singleton(StrikethroughExtension.create());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* commonmark-java extension for tables
|
||||||
|
*/
|
||||||
|
private static final Set<Extension> EXTENSIONS_TABLES = Collections.singleton(TablesExtension.create());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* commonmark-java extension for adding id attributes to h tags
|
||||||
|
*/
|
||||||
|
private static final Set<Extension> EXTENSIONS_HEADING_ANCHOR = Collections.singleton(HeadingAnchorExtension.create());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* commonmark-java extension for <ins> (underline)
|
||||||
|
*/
|
||||||
|
private static final Set<Extension> EXTENSIONS_INS = Collections.singleton(InsExtension.create());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* commonmark-java extension for YAML front matter
|
||||||
|
*/
|
||||||
|
private static final Set<Extension> EXTENSIONS_YAML_FRONT_MATTER = Collections.singleton(YamlFrontMatterExtension.create());
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse Markdown content
|
* Parse Markdown content
|
||||||
*/
|
*/
|
||||||
private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS_YAML).extensions(EXTENSIONS_TABLE).build();
|
private static final Parser PARSER = Parser.builder()
|
||||||
|
.extensions(EXTENSIONS_AUTO_LINK)
|
||||||
|
.extensions(EXTENSIONS_STRIKETHROUGH)
|
||||||
|
.extensions(EXTENSIONS_TABLES)
|
||||||
|
.extensions(EXTENSIONS_HEADING_ANCHOR)
|
||||||
|
.extensions(EXTENSIONS_INS)
|
||||||
|
.extensions(EXTENSIONS_YAML_FRONT_MATTER)
|
||||||
|
.build();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render HTML content
|
* Render HTML content
|
||||||
*/
|
*/
|
||||||
private static final HtmlRenderer RENDERER = HtmlRenderer.builder().extensions(EXTENSIONS_YAML).extensions(EXTENSIONS_TABLE).build();
|
private static final HtmlRenderer RENDERER = HtmlRenderer.builder()
|
||||||
|
.extensions(EXTENSIONS_AUTO_LINK)
|
||||||
|
.extensions(EXTENSIONS_STRIKETHROUGH)
|
||||||
|
.extensions(EXTENSIONS_TABLES)
|
||||||
|
.extensions(EXTENSIONS_HEADING_ANCHOR)
|
||||||
|
.extensions(EXTENSIONS_INS)
|
||||||
|
.extensions(EXTENSIONS_YAML_FRONT_MATTER)
|
||||||
|
.build();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render text content
|
* Render text content
|
||||||
*/
|
*/
|
||||||
private static final TextContentRenderer TEXT_CONTENT_RENDERER = TextContentRenderer.builder().extensions(EXTENSIONS_YAML).extensions(EXTENSIONS_TABLE).build();
|
private static final TextContentRenderer TEXT_CONTENT_RENDERER = TextContentRenderer.builder()
|
||||||
|
.extensions(EXTENSIONS_AUTO_LINK)
|
||||||
|
.extensions(EXTENSIONS_STRIKETHROUGH)
|
||||||
|
.extensions(EXTENSIONS_TABLES)
|
||||||
|
.extensions(EXTENSIONS_HEADING_ANCHOR)
|
||||||
|
.extensions(EXTENSIONS_INS)
|
||||||
|
.extensions(EXTENSIONS_YAML_FRONT_MATTER)
|
||||||
|
.build();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render Markdown content
|
* Render Markdown content
|
||||||
|
@ -59,20 +106,25 @@ public class MarkdownUtils {
|
||||||
* @see <a href="https://github.com/otale/tale/blob/master/src/main/java/com/tale/utils/TaleUtils.java">TaleUtils.java</a>
|
* @see <a href="https://github.com/otale/tale/blob/master/src/main/java/com/tale/utils/TaleUtils.java">TaleUtils.java</a>
|
||||||
*/
|
*/
|
||||||
public static String renderMarkdown(String content) {
|
public static String renderMarkdown(String content) {
|
||||||
|
|
||||||
final Node document = PARSER.parse(content);
|
final Node document = PARSER.parse(content);
|
||||||
String renderContent = RENDERER.render(document);
|
String renderContent = RENDERER.render(document);
|
||||||
|
|
||||||
// render netease music short url
|
// render netease music short url
|
||||||
if (content.contains(HaloConst.NETEASE_MUSIC_PREFIX)) {
|
if (content.contains(HaloConst.NETEASE_MUSIC_PREFIX)) {
|
||||||
renderContent = content.replaceAll(HaloConst.NETEASE_MUSIC_REG_PATTERN, HaloConst.NETEASE_MUSIC_IFRAME);
|
renderContent = content.replaceAll(HaloConst.NETEASE_MUSIC_REG_PATTERN, HaloConst.NETEASE_MUSIC_IFRAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
// render bilibili video short url
|
// render bilibili video short url
|
||||||
if (content.contains(HaloConst.BILIBILI_VIDEO_PREFIX)) {
|
if (content.contains(HaloConst.BILIBILI_VIDEO_PREFIX)) {
|
||||||
renderContent = content.replaceAll(HaloConst.BILIBILI_VIDEO_REG_PATTERN, HaloConst.BILIBILI_VIDEO_IFRAME);
|
renderContent = content.replaceAll(HaloConst.BILIBILI_VIDEO_REG_PATTERN, HaloConst.BILIBILI_VIDEO_IFRAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
// render youtube video short url
|
// render youtube video short url
|
||||||
if (content.contains(HaloConst.YOUTUBE_VIDEO_PREFIX)) {
|
if (content.contains(HaloConst.YOUTUBE_VIDEO_PREFIX)) {
|
||||||
renderContent = content.replaceAll(HaloConst.YOUTUBE_VIDEO_REG_PATTERN, HaloConst.YOUTUBE_VIDEO_IFRAME);
|
renderContent = content.replaceAll(HaloConst.YOUTUBE_VIDEO_REG_PATTERN, HaloConst.YOUTUBE_VIDEO_IFRAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
return renderContent;
|
return renderContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,6 +136,7 @@ public class MarkdownUtils {
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
public static String renderText(@Nullable String markdownContent) {
|
public static String renderText(@Nullable String markdownContent) {
|
||||||
|
|
||||||
if (StringUtils.isBlank(markdownContent)) {
|
if (StringUtils.isBlank(markdownContent)) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1 @@
|
||||||
|
.category-tree[data-v-3d736771]{margin-top:1rem}
|
|
@ -1 +0,0 @@
|
||||||
.category-tree[data-v-42549218]{margin-top:1rem}
|
|
|
@ -1 +1 @@
|
||||||
.attach-detail-img img{width:100%}.attach-item{width:50%;margin:0 auto;position:relative;padding-bottom:28%;overflow:hidden;float:left;cursor:pointer}.attach-item img{width:100%;height:100%;position:absolute;top:0;left:0}.upload-button[data-v-6d086f28]{position:fixed;bottom:30px;right:30px}.theme-thumb[data-v-6d086f28]{width:100%;margin:0 auto;position:relative;padding-bottom:56%;overflow:hidden}.theme-thumb img[data-v-6d086f28]{width:100%;height:100%;position:absolute;top:0;left:0}
|
.attach-detail-img img{width:100%}.attach-item{width:50%;margin:0 auto;position:relative;padding-bottom:28%;overflow:hidden;float:left;cursor:pointer}.attach-item img{width:100%;height:100%;position:absolute;top:0;left:0}.upload-button[data-v-76b16ba4]{position:fixed;bottom:30px;right:30px}.theme-thumb[data-v-76b16ba4]{width:100%;margin:0 auto;position:relative;padding-bottom:56%;overflow:hidden}.theme-thumb img[data-v-76b16ba4]{width:100%;height:100%;position:absolute;top:0;left:0}
|
|
@ -1 +1 @@
|
||||||
<!DOCTYPE html><html lang=zh-cmn-Hans><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"><meta name=robots content=noindex,nofllow><meta name=generator content=Halo><link rel=icon href=/logo.png><title>Halo Dashboard</title><link href=/css/chunk-0aab7d1a.701a4459.css rel=prefetch><link href=/css/chunk-14e8932a.b6783003.css rel=prefetch><link href=/css/chunk-1c8b985a.c1990d7c.css rel=prefetch><link href=/css/chunk-31829c73.f3153164.css rel=prefetch><link href=/css/chunk-4d54295e.43661ea3.css rel=prefetch><link href=/css/chunk-4fb0639b.210847fe.css rel=prefetch><link href=/css/chunk-5d83fd61.7cf84e9e.css rel=prefetch><link href=/css/chunk-75751d79.7d0c0e85.css rel=prefetch><link href=/css/chunk-898a93f6.909d3d1b.css rel=prefetch><link href=/css/chunk-92a6af22.ca1bfaac.css rel=prefetch><link href=/css/chunk-9449c032.dafef2de.css rel=prefetch><link href=/css/chunk-b6cd2e50.0090ea54.css rel=prefetch><link href=/css/chunk-c5b09f02.5e377c2d.css rel=prefetch><link href=/css/chunk-cc47e7d0.bb8e6a18.css rel=prefetch><link href=/css/fail.e9c3e2d8.css rel=prefetch><link href=/js/chunk-0aab7d1a.dba028d3.js rel=prefetch><link href=/js/chunk-142c8832.ac1f84d9.js rel=prefetch><link href=/js/chunk-14e8932a.c6563b86.js rel=prefetch><link href=/js/chunk-1c8b985a.8f4810a0.js rel=prefetch><link href=/js/chunk-2d0b64bf.14e99e26.js rel=prefetch><link href=/js/chunk-2d0d65a2.f7c7af76.js rel=prefetch><link href=/js/chunk-2d21a35c.19f1174e.js rel=prefetch><link href=/js/chunk-31829c73.36a05806.js rel=prefetch><link href=/js/chunk-407d6578.b0b0e2da.js rel=prefetch><link href=/js/chunk-4d54295e.e29bba1d.js rel=prefetch><link href=/js/chunk-4fb0639b.1668db6b.js rel=prefetch><link href=/js/chunk-5bf599cc.b6b7c7ec.js rel=prefetch><link href=/js/chunk-5d83fd61.71621386.js rel=prefetch><link href=/js/chunk-71fa6d51.79e1ddb2.js rel=prefetch><link href=/js/chunk-75751d79.f0f603bc.js rel=prefetch><link href=/js/chunk-87e2df70.8d9028cf.js rel=prefetch><link href=/js/chunk-898a93f6.c0e9bb6c.js rel=prefetch><link href=/js/chunk-92a6af22.b84ae833.js rel=prefetch><link href=/js/chunk-9449c032.9e9ce69b.js rel=prefetch><link href=/js/chunk-b6cd2e50.d2c0d717.js rel=prefetch><link href=/js/chunk-c5b09f02.3952a273.js rel=prefetch><link href=/js/chunk-cc47e7d0.30a4457f.js rel=prefetch><link href=/js/fail.b90b115d.js rel=prefetch><link href=/css/app.d74a405f.css rel=preload as=style><link href=/css/chunk-vendors.6b95225a.css rel=preload as=style><link href=/js/app.25d2f925.js rel=preload as=script><link href=/js/chunk-vendors.fb9e7626.js rel=preload as=script><link href=/css/chunk-vendors.6b95225a.css rel=stylesheet><link href=/css/app.d74a405f.css rel=stylesheet></head><body><noscript><strong>We're sorry but vue-antd-pro doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script src=/js/chunk-vendors.fb9e7626.js></script><script src=/js/app.25d2f925.js></script></body></html>
|
<!DOCTYPE html><html lang=zh-cmn-Hans><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"><meta name=robots content=noindex,nofllow><meta name=generator content=Halo><link rel=icon href=/logo.png><title>Halo Dashboard</title><link href=/css/chunk-02f1697e.7cf84e9e.css rel=prefetch><link href=/css/chunk-0aab7d1a.701a4459.css rel=prefetch><link href=/css/chunk-14e8932a.b6783003.css rel=prefetch><link href=/css/chunk-1c8b985a.c1990d7c.css rel=prefetch><link href=/css/chunk-1ea08528.db89d50a.css rel=prefetch><link href=/css/chunk-31829c73.f3153164.css rel=prefetch><link href=/css/chunk-4fb0639b.743bdcba.css rel=prefetch><link href=/css/chunk-75751d79.7d0c0e85.css rel=prefetch><link href=/css/chunk-898a93f6.909d3d1b.css rel=prefetch><link href=/css/chunk-9449c032.dafef2de.css rel=prefetch><link href=/css/chunk-b6cd2e50.0090ea54.css rel=prefetch><link href=/css/chunk-c5b09f02.5e377c2d.css rel=prefetch><link href=/css/chunk-edd856c6.ca1bfaac.css rel=prefetch><link href=/css/chunk-efde06fa.aaca0f75.css rel=prefetch><link href=/css/fail.e9c3e2d8.css rel=prefetch><link href=/js/chunk-02f1697e.fbb91c62.js rel=prefetch><link href=/js/chunk-0aab7d1a.dba028d3.js rel=prefetch><link href=/js/chunk-142c8832.0d298dce.js rel=prefetch><link href=/js/chunk-14e8932a.c6563b86.js rel=prefetch><link href=/js/chunk-1c8b985a.d1e58af8.js rel=prefetch><link href=/js/chunk-1ea08528.6dd705ab.js rel=prefetch><link href=/js/chunk-2d0b64bf.14e99e26.js rel=prefetch><link href=/js/chunk-2d0d65a2.d68d48c2.js rel=prefetch><link href=/js/chunk-2d21a35c.19f1174e.js rel=prefetch><link href=/js/chunk-31829c73.620e8a22.js rel=prefetch><link href=/js/chunk-407d6578.1ef8e454.js rel=prefetch><link href=/js/chunk-4fb0639b.1be7f2f2.js rel=prefetch><link href=/js/chunk-5bf599cc.b6b7c7ec.js rel=prefetch><link href=/js/chunk-71fa6d51.79e1ddb2.js rel=prefetch><link href=/js/chunk-75751d79.1b49b36e.js rel=prefetch><link href=/js/chunk-87e2df70.8d9028cf.js rel=prefetch><link href=/js/chunk-898a93f6.c0e9bb6c.js rel=prefetch><link href=/js/chunk-9449c032.9e9ce69b.js rel=prefetch><link href=/js/chunk-b6cd2e50.cc4cab63.js rel=prefetch><link href=/js/chunk-c5b09f02.3952a273.js rel=prefetch><link href=/js/chunk-edd856c6.20633ba8.js rel=prefetch><link href=/js/chunk-efde06fa.9e1e510c.js rel=prefetch><link href=/js/fail.b90b115d.js rel=prefetch><link href=/css/app.803dd628.css rel=preload as=style><link href=/css/chunk-vendors.80056587.css rel=preload as=style><link href=/js/app.63c77087.js rel=preload as=script><link href=/js/chunk-vendors.fb9e7626.js rel=preload as=script><link href=/css/chunk-vendors.80056587.css rel=stylesheet><link href=/css/app.803dd628.css rel=stylesheet></head><body><noscript><strong>We're sorry but vue-antd-pro doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script src=/js/chunk-vendors.fb9e7626.js></script><script src=/js/app.63c77087.js></script></body></html>
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1 @@
|
||||||
|
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-407d6578"],{aa1e9:function(t,a,e){"use strict";e.r(a);var n=function(){var t=this,a=t.$createElement,e=t._self._c||a;return e("div",{staticClass:"page-header-index-wide"},[e("a-row",{attrs:{gutter:12}},[e("a-col",{style:{"padding-bottom":"12px"},attrs:{xl:10,lg:10,md:10,sm:24,xs:24}},[e("a-card",{attrs:{title:"添加标签"}},[e("a-form",{attrs:{layout:"horizontal"}},[e("a-form-item",{attrs:{label:"名称:",help:"* 页面上所显示的名称"}},[e("a-input",{model:{value:t.tagToCreate.name,callback:function(a){t.$set(t.tagToCreate,"name",a)},expression:"tagToCreate.name"}})],1),e("a-form-item",{attrs:{label:"别名",help:"* 一般为单个标签页面的标识,最好为英文"}},[e("a-input",{model:{value:t.tagToCreate.slugName,callback:function(a){t.$set(t.tagToCreate,"slugName",a)},expression:"tagToCreate.slugName"}})],1),e("a-form-item",[e("a-button",{attrs:{type:"primary"},on:{click:t.handleCreateTag}},[t._v("保存")])],1)],1)],1)],1),e("a-col",{style:{"padding-bottom":"12px"},attrs:{xl:14,lg:14,md:14,sm:24,xs:24}},[e("a-card",{attrs:{title:"所有标签"}},t._l(t.tags,function(a){return e("a-tooltip",{key:a.id,attrs:{placement:"topLeft"}},[e("template",{slot:"title"},[e("span",[t._v(t._s(a.postCount)+" 篇文章")])]),e("a-tag",{attrs:{closable:"",color:"blue"},on:{close:function(e){return t.handleDeleteTag(a.id)}}},[t._v(t._s(a.name))])],2)}),1)],1)],1)],1)},o=[],l=e("d28db"),r={data:function(){return{tags:[],tagToCreate:{},tagToUpdate:{}}},created:function(){this.loadTags()},methods:{loadTags:function(){var t=this;l["a"].listAll(!0).then(function(a){t.tags=a.data.data})},handleCreateTag:function(){var t=this;l["a"].create(this.tagToCreate).then(function(a){t.loadTags()})},handleUpdateTag:function(t){var a=this;l["a"].update(t,this.tagToUpdate).then(function(t){a.loadTags()})},handleDeleteTag:function(t){var a=this;l["a"].delete(t).then(function(t){a.$message.success("删除成功!"),a.loadTags()})}}},s=r,c=e("17cc"),i=Object(c["a"])(s,n,o,!1,null,null,null);a["default"]=i.exports},d28db:function(t,a,e){"use strict";var n=e("9efd"),o="/api/admin/tags",l={listAll:function(){var t=arguments.length>0&&void 0!==arguments[0]&&arguments[0];return Object(n["a"])({url:o,params:{more:t},method:"get"})},createWithName:function(t){return Object(n["a"])({url:o,data:{name:t},method:"post"})},create:function(t){return Object(n["a"])({url:o,data:t,method:"post"})},update:function(t,a){return Object(n["a"])({url:"".concat(o,"/").concat(t),data:a,method:"put"})},delete:function(t){return Object(n["a"])({url:"".concat(o,"/").concat(t),method:"delete"})}};a["a"]=l}}]);
|
|
@ -1 +0,0 @@
|
||||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-407d6578"],{aa1e9:function(t,a,e){"use strict";e.r(a);var n=function(){var t=this,a=t.$createElement,e=t._self._c||a;return e("div",{staticClass:"page-header-index-wide"},[e("a-row",{attrs:{gutter:12}},[e("a-col",{style:{"padding-bottom":"12px"},attrs:{xl:10,lg:10,md:10,sm:24,xs:24}},[e("a-card",{attrs:{title:"添加标签"}},[e("a-form",{attrs:{layout:"horizontal"}},[e("a-form-item",{attrs:{label:"名称:",help:"* 页面上所显示的名称"}},[e("a-input",{model:{value:t.tagToCreate.name,callback:function(a){t.$set(t.tagToCreate,"name",a)},expression:"tagToCreate.name"}})],1),e("a-form-item",{attrs:{label:"路径名称:",help:"* 这是文章路径上显示的名称,最好为英文"}},[e("a-input",{model:{value:t.tagToCreate.slugName,callback:function(a){t.$set(t.tagToCreate,"slugName",a)},expression:"tagToCreate.slugName"}})],1),e("a-form-item",[e("a-button",{attrs:{type:"primary"},on:{click:t.handleCreateTag}},[t._v("保存")])],1)],1)],1)],1),e("a-col",{style:{"padding-bottom":"12px"},attrs:{xl:14,lg:14,md:14,sm:24,xs:24}},[e("a-card",{attrs:{title:"所有标签"}},t._l(t.tags,function(a){return e("a-tooltip",{key:a.id,attrs:{placement:"topLeft"}},[e("template",{slot:"title"},[e("span",[t._v(t._s(a.postCount)+" 篇文章")])]),e("a-tag",{attrs:{closable:"",color:"blue"},on:{close:function(e){return t.handleDeleteTag(a.id)}}},[t._v(t._s(a.name))])],2)}),1)],1)],1)],1)},o=[],l=e("d28db"),r={data:function(){return{tags:[],tagToCreate:{},tagToUpdate:{}}},created:function(){this.loadTags()},methods:{loadTags:function(){var t=this;l["a"].listAll(!0).then(function(a){t.tags=a.data.data})},handleCreateTag:function(){var t=this;l["a"].create(this.tagToCreate).then(function(a){t.loadTags()})},handleUpdateTag:function(t){var a=this;l["a"].update(t,this.tagToUpdate).then(function(t){a.loadTags()})},handleDeleteTag:function(t){var a=this;l["a"].delete(t).then(function(t){a.$message.success("删除成功!"),a.loadTags()})}}},s=r,c=e("17cc"),i=Object(c["a"])(s,n,o,!1,null,null,null);a["default"]=i.exports},d28db:function(t,a,e){"use strict";var n=e("9efd"),o="/api/admin/tags",l={listAll:function(){var t=arguments.length>0&&void 0!==arguments[0]&&arguments[0];return Object(n["a"])({url:o,params:{more:t},method:"get"})},createWithName:function(t){return Object(n["a"])({url:o,data:{name:t},method:"post"})},create:function(t){return Object(n["a"])({url:o,data:t,method:"post"})},update:function(t,a){return Object(n["a"])({url:"".concat(o,"/").concat(t),data:a,method:"put"})},delete:function(t){return Object(n["a"])({url:"".concat(o,"/").concat(t),method:"delete"})}};a["a"]=l}}]);
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -13,23 +13,25 @@ server:
|
||||||
enabled: true
|
enabled: true
|
||||||
mime-types: application/javascript,text/css,application/json,application/xml,text/html,text/xml,text/plain
|
mime-types: application/javascript,text/css,application/json,application/xml,text/html,text/xml,text/plain
|
||||||
spring:
|
spring:
|
||||||
|
jackson:
|
||||||
|
date-format: yyyy-MM-dd HH:mm:ss
|
||||||
output:
|
output:
|
||||||
ansi:
|
ansi:
|
||||||
enabled: always
|
enabled: always
|
||||||
datasource:
|
datasource:
|
||||||
type: com.zaxxer.hikari.HikariDataSource
|
type: com.zaxxer.hikari.HikariDataSource
|
||||||
|
|
||||||
# H2database 配置
|
# H2 Database 配置
|
||||||
driver-class-name: org.h2.Driver
|
driver-class-name: org.h2.Driver
|
||||||
url: jdbc:h2:file:~/halo-dev/db/halo
|
url: jdbc:h2:file:~/halo-dev/db/halo
|
||||||
username: admin
|
username: admin
|
||||||
password: 123456
|
password: 123456
|
||||||
|
|
||||||
# MySql 配置
|
# MySQL 配置
|
||||||
# driver-class-name: com.mysql.cj.jdbc.Driver
|
# driver-class-name: com.mysql.cj.jdbc.Driver
|
||||||
# url: jdbc:mysql://127.0.0.1:3306/halodb?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
|
# url: jdbc:mysql://127.0.0.1:3306/halodb?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
|
||||||
# username: root
|
# username: root
|
||||||
# password: 123456
|
# password: 123456
|
||||||
|
|
||||||
h2:
|
h2:
|
||||||
console:
|
console:
|
||||||
|
@ -44,8 +46,8 @@ spring:
|
||||||
open-in-view: false
|
open-in-view: false
|
||||||
servlet:
|
servlet:
|
||||||
multipart:
|
multipart:
|
||||||
max-file-size: 10MB
|
max-file-size: 10240MB
|
||||||
max-request-size: 10MB
|
max-request-size: 10240MB
|
||||||
|
|
||||||
cache:
|
cache:
|
||||||
type: none
|
type: none
|
||||||
|
|
|
@ -13,23 +13,25 @@ server:
|
||||||
enabled: true
|
enabled: true
|
||||||
mime-types: application/javascript,text/css,application/json,application/xml,text/html,text/xml,text/plain
|
mime-types: application/javascript,text/css,application/json,application/xml,text/html,text/xml,text/plain
|
||||||
spring:
|
spring:
|
||||||
|
jackson:
|
||||||
|
date-format: yyyy-MM-dd HH:mm:ss
|
||||||
output:
|
output:
|
||||||
ansi:
|
ansi:
|
||||||
enabled: always
|
enabled: always
|
||||||
datasource:
|
datasource:
|
||||||
type: com.zaxxer.hikari.HikariDataSource
|
type: com.zaxxer.hikari.HikariDataSource
|
||||||
|
|
||||||
# H2database 配置
|
# H2 Database 配置
|
||||||
driver-class-name: org.h2.Driver
|
driver-class-name: org.h2.Driver
|
||||||
url: jdbc:h2:file:~/halo-test/db/halo
|
url: jdbc:h2:file:~/halo-test/db/halo
|
||||||
username: admin
|
username: admin
|
||||||
password: 123456
|
password: 123456
|
||||||
|
|
||||||
# MySql 配置
|
# MySQL 配置
|
||||||
# driver-class-name: com.mysql.cj.jdbc.Driver
|
# driver-class-name: com.mysql.cj.jdbc.Driver
|
||||||
# url: jdbc:mysql://127.0.0.1:3306/halodb?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
|
# url: jdbc:mysql://127.0.0.1:3306/halodb?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
|
||||||
# username: root
|
# username: root
|
||||||
# password: 123456
|
# password: 123456
|
||||||
|
|
||||||
h2:
|
h2:
|
||||||
console:
|
console:
|
||||||
|
|
|
@ -4,19 +4,19 @@ spring:
|
||||||
datasource:
|
datasource:
|
||||||
type: com.zaxxer.hikari.HikariDataSource
|
type: com.zaxxer.hikari.HikariDataSource
|
||||||
|
|
||||||
# H2Database 配置,如果你需要使用 MySQL,请注释掉该配置并取消注释 MySQL 的配置。
|
# H2 Database 配置,如果你需要使用 MySQL,请注释掉该配置并取消注释 MySQL 的配置。
|
||||||
driver-class-name: org.h2.Driver
|
driver-class-name: org.h2.Driver
|
||||||
url: jdbc:h2:file:~/.halo/db/halo
|
url: jdbc:h2:file:~/.halo/db/halo
|
||||||
username: admin
|
username: admin
|
||||||
password: 123456
|
password: 123456
|
||||||
|
|
||||||
# MySql 配置,如果你需要使用 H2Database,请注释掉该配置并取消注释上方 H2Database 的配置。
|
# MySQL 配置,如果你需要使用 H2 Database,请注释掉该配置并取消注释上方 H2 Database 的配置。
|
||||||
# driver-class-name: com.mysql.cj.jdbc.Driver
|
# driver-class-name: com.mysql.cj.jdbc.Driver
|
||||||
# url: jdbc:mysql://127.0.0.1:3306/halodb?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
|
# url: jdbc:mysql://127.0.0.1:3306/halodb?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
|
||||||
# username: root
|
# username: root
|
||||||
# password: 123456
|
# password: 123456
|
||||||
|
|
||||||
# H2Database 的控制台相关配置,如果你使用的是 MySQL ,请注释掉下方内容。
|
# H2 Database 的控制台相关配置,如果你使用的是 MySQL ,请注释掉下方内容。
|
||||||
h2:
|
h2:
|
||||||
console:
|
console:
|
||||||
settings:
|
settings:
|
||||||
|
|
|
@ -13,6 +13,8 @@ server:
|
||||||
enabled: true
|
enabled: true
|
||||||
mime-types: application/javascript,text/css,application/json,application/xml,text/html,text/xml,text/plain
|
mime-types: application/javascript,text/css,application/json,application/xml,text/html,text/xml,text/plain
|
||||||
spring:
|
spring:
|
||||||
|
jackson:
|
||||||
|
date-format: yyyy-MM-dd HH:mm:ss
|
||||||
devtools:
|
devtools:
|
||||||
add-properties: false
|
add-properties: false
|
||||||
output:
|
output:
|
||||||
|
@ -21,17 +23,17 @@ spring:
|
||||||
datasource:
|
datasource:
|
||||||
type: com.zaxxer.hikari.HikariDataSource
|
type: com.zaxxer.hikari.HikariDataSource
|
||||||
|
|
||||||
# H2database 配置
|
# H2 Database 配置
|
||||||
driver-class-name: org.h2.Driver
|
driver-class-name: org.h2.Driver
|
||||||
url: jdbc:h2:file:~/.halo/db/halo
|
url: jdbc:h2:file:~/.halo/db/halo
|
||||||
username: admin
|
username: admin
|
||||||
password: 123456
|
password: 123456
|
||||||
|
|
||||||
# MySql 配置
|
# MySQL 配置
|
||||||
# driver-class-name: com.mysql.cj.jdbc.Driver
|
# driver-class-name: com.mysql.cj.jdbc.Driver
|
||||||
# url: jdbc:mysql://127.0.0.1:3306/halodb?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
|
# url: jdbc:mysql://127.0.0.1:3306/halodb?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
|
||||||
# username: root
|
# username: root
|
||||||
# password: 123456
|
# password: 123456
|
||||||
|
|
||||||
h2:
|
h2:
|
||||||
console:
|
console:
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,12 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="content-type" content="text/html; charset=utf-8">
|
||||||
|
<link rel="alternate" type="application/rss+xml" title="atom 1.0" href="/atom.xml">
|
||||||
|
<title>Not Found</title>
|
||||||
|
<link href="${static!}/source/css/style.min.css" type="text/css" rel="stylesheet"/>
|
||||||
|
</head>
|
||||||
|
<div class="page_404">
|
||||||
|
<p>The page you are looking for is missing</p>
|
||||||
|
</div>
|
||||||
|
</html>
|
|
@ -0,0 +1,12 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="content-type" content="text/html; charset=utf-8">
|
||||||
|
<link rel="alternate" type="application/rss+xml" title="atom 1.0" href="/atom.xml">
|
||||||
|
<title>Internal Error</title>
|
||||||
|
<link href="${static!}/source/css/style.min.css" type="text/css" rel="stylesheet"/>
|
||||||
|
</head>
|
||||||
|
<div class="page_404">
|
||||||
|
<p>The page you are looking for is error</p>
|
||||||
|
</div>
|
||||||
|
</html>
|
|
@ -0,0 +1,18 @@
|
||||||
|
<h1><a href="https://github.com/halo-dev" target="_blank">halo-theme-anatole</a></h1>
|
||||||
|
|
||||||
|
## 说明
|
||||||
|
|
||||||
|
该主题的原作者为 [Caicai](https://www.caicai.me),非常感谢做出这么优秀的主题。
|
||||||
|
|
||||||
|
原主题地址:[https://github.com/hi-caicai/farbox-theme-Anatole](https://github.com/hi-caicai/farbox-theme-Anatole)
|
||||||
|
|
||||||
|
## 预览截图
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
## 使用方法
|
||||||
|
|
||||||
|
1. 克隆或者[下载](https://github.com/halo-dev/halo-theme-anatole/releases)。
|
||||||
|
2. 压缩为 zip 压缩包之后在后台的主题设置直接上传即可使用。
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue