Complete migration handler

pull/146/head
johnniang 2019-04-26 17:09:27 +08:00
parent e13e56f57b
commit c438d99d47
16 changed files with 765 additions and 15 deletions

View File

@ -34,7 +34,7 @@ public class MenuController {
@GetMapping
@ApiOperation("Lists all menus")
public List<MenuDTO> listAll(@SortDefault(sort = "sort", direction = DESC) Sort sort) {
public List<MenuDTO> listAll(@SortDefault(sort = "priority", direction = DESC) Sort sort) {
return menuService.listDtos(sort);
}

View File

@ -1,7 +1,12 @@
package run.halo.app.controller.admin.api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.web.bind.annotation.PostMapping;
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.multipart.MultipartFile;
import run.halo.app.service.RecoveryService;
/**
@ -19,4 +24,12 @@ public class RecoveryController {
public RecoveryController(RecoveryService recoveryService) {
this.recoveryService = recoveryService;
}
@PostMapping
@ApiOperation("Migrate from halo v0.4.3")
public void migrateFromVersion_0_4_3(
@ApiParam("This file content type should be json")
@RequestPart("file") MultipartFile file) {
recoveryService.migrateFromV0_4_3(file);
}
}

View File

@ -31,7 +31,7 @@ public class MenuController {
@GetMapping
@ApiOperation("Lists all menus")
public List<MenuDTO> listAll(@SortDefault(sort = "sort", direction = DESC) Sort sort) {
public List<MenuDTO> listAll(@SortDefault(sort = "priority", direction = DESC) Sort sort) {
return menuService.listDtos(sort);
}
}

View File

@ -137,14 +137,14 @@ public class InstallController {
// TODO i18n
menuIndex.setName("首页");
menuIndex.setUrl("/");
menuIndex.setSort(1);
menuIndex.setPriority(1);
menuService.create(menuIndex);
Menu menuArchive = new Menu();
// TODO i18n
menuArchive.setName("归档");
menuArchive.setUrl("/archives");
menuArchive.setSort(2);
menuArchive.setPriority(2);
menuService.create(menuArchive);
}

View File

@ -60,7 +60,7 @@ public class Attachment extends BaseEntity {
private String mediaType;
/**
* Attachment suffix,such as .png,.jpg
* Attachment suffix,such as png, zip, mp4, jpge.
*/
@Column(name = "suffix", columnDefinition = "varchar(50) default ''")
private String suffix;

View File

@ -45,8 +45,13 @@ public class BaseEntity {
protected void prePersist() {
deleted = false;
Date now = DateUtils.now();
createTime = now;
updateTime = now;
if (createTime == null) {
createTime = now;
}
if (updateTime == null) {
updateTime = now;
}
}
@PreUpdate

View File

@ -129,7 +129,10 @@ public class BasePost extends BaseEntity {
super.prePersist();
id = null;
editTime = getCreateTime();
if (editTime == null) {
editTime = getCreateTime();
}
if (status == null) {
status = PostStatus.DRAFT;

View File

@ -43,8 +43,8 @@ public class Menu extends BaseEntity {
/**
* Sort.
*/
@Column(name = "sort", columnDefinition = "int default 0")
private Integer sort;
@Column(name = "priority", columnDefinition = "int default 0")
private Integer priority;
/**
* Page opening method
@ -70,8 +70,8 @@ public class Menu extends BaseEntity {
id = null;
if (sort == null) {
sort = 0;
if (priority == null) {
priority = 0;
}
if (target == null) {
@ -81,5 +81,9 @@ public class Menu extends BaseEntity {
if (icon == null) {
icon = "";
}
if (parentId == null) {
parentId = 0;
}
}
}

View File

@ -76,5 +76,25 @@ public class Photo extends BaseEntity {
public void prePersist() {
super.prePersist();
id = null;
if (takeTime == null) {
takeTime = this.getCreateTime();
}
if (description == null) {
description = "";
}
if (location == null) {
location = "";
}
if (thumbnail == null) {
thumbnail = "";
}
if (team == null) {
team = "";
}
}
}

View File

@ -27,7 +27,7 @@ public class MenuParam implements InputConverter<Menu> {
@Size(max = 1023, message = "Length of menu url must not be more than {max}")
private String url;
@Min(value = 0, message = "Menu sort must not be less than {value}")
@Min(value = 0, message = "Menu priority must not be less than {value}")
private Integer sort;
@Size(max = 50, message = "Length of menu target must not be more than {max}")

View File

@ -2,6 +2,7 @@ package run.halo.app.repository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import org.springframework.lang.NonNull;
import run.halo.app.model.entity.Attachment;
import run.halo.app.repository.base.BaseRepository;
@ -16,8 +17,17 @@ public interface AttachmentRepository extends BaseRepository<Attachment, Integer
/**
* Find all attachment media type.
*
* @return list of media type.
*/
@Query(value = "select distinct a.mediaType from Attachment a")
List<String> findAllMediaType();
/**
* Counts by attachment path.
*
* @param path attachment path must not be blank
* @return count of the given path
*/
long countByPath(@NonNull String path);
}

View File

@ -1,5 +1,8 @@
package run.halo.app.service;
import org.springframework.lang.NonNull;
import org.springframework.web.multipart.MultipartFile;
/**
* Recovery service interface.
*
@ -8,4 +11,10 @@ package run.halo.app.service;
*/
public interface RecoveryService {
/**
* Migrates from halo version 0.4.3.
*
* @param file multipart file must not be null
*/
void migrateFromV0_4_3(@NonNull MultipartFile file);
}

View File

@ -9,6 +9,7 @@ import org.springframework.lang.NonNull;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.web.multipart.MultipartFile;
import run.halo.app.exception.AlreadyExistsException;
import run.halo.app.handler.file.FileHandlers;
import run.halo.app.model.dto.AttachmentDTO;
import run.halo.app.model.entity.Attachment;
@ -27,7 +28,7 @@ import java.util.List;
import java.util.Objects;
/**
* AttachmentService implementation class
* AttachmentService implementation
*
* @author : RYAN0UP
* @date : 2019-03-14
@ -164,6 +165,31 @@ public class AttachmentServiceImpl extends AbstractCrudService<Attachment, Integ
return attachmentRepository.findAllMediaType();
}
@Override
public Attachment create(Attachment attachment) {
Assert.notNull(attachment, "Attachment must not be null");
// Check attachment path
pathMustNotExist(attachment);
return super.create(attachment);
}
/**
* Attachment path must not be exist.
*
* @param attachment attachment must not be null
*/
private void pathMustNotExist(@NonNull Attachment attachment) {
Assert.notNull(attachment, "Attachment must not be null");
long pathCount = attachmentRepository.countByPath(attachment.getPath());
if (pathCount > 0) {
throw new AlreadyExistsException("The attachment with path " + attachment.getPath() + " exists already");
}
}
/**
* Get attachment type from options.
*

View File

@ -1,7 +1,24 @@
package run.halo.app.service.impl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Service;
import run.halo.app.service.RecoveryService;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.multipart.MultipartFile;
import run.halo.app.exception.ServiceException;
import run.halo.app.model.entity.*;
import run.halo.app.model.enums.AttachmentType;
import run.halo.app.model.enums.PostStatus;
import run.halo.app.service.*;
import run.halo.app.utils.BeanUtils;
import run.halo.app.utils.JsonUtils;
import java.io.IOException;
import java.io.InputStreamReader;
import java.text.SimpleDateFormat;
import java.util.*;
/**
* Recovery service implementation.
@ -9,7 +26,387 @@ import run.halo.app.service.RecoveryService;
* @author johnniang
* @date 19-4-26
*/
@Slf4j
@Service
@SuppressWarnings("unchecked")
public class RecoveryServiceImpl implements RecoveryService {
private final AttachmentService attachmentService;
private final PostService postService;
private final LinkService linkService;
private final MenuService menuService;
private final CategoryService categoryService;
private final TagService tagService;
private final PostCommentService postCommentService;
private final SheetCommentService sheetCommentService;
private final SheetService sheetService;
private final PhotoService photoService;
public RecoveryServiceImpl(AttachmentService attachmentService,
PostService postService,
LinkService linkService,
MenuService menuService,
CategoryService categoryService,
TagService tagService,
PostCommentService postCommentService,
SheetCommentService sheetCommentService,
SheetService sheetService,
PhotoService photoService) {
this.attachmentService = attachmentService;
this.postService = postService;
this.linkService = linkService;
this.menuService = menuService;
this.categoryService = categoryService;
this.tagService = tagService;
this.postCommentService = postCommentService;
this.sheetCommentService = sheetCommentService;
this.sheetService = sheetService;
this.photoService = photoService;
}
@Override
public void migrateFromV0_4_3(MultipartFile file) {
// TODO Async execution
// Get migration content
try {
String migrationContent = FileCopyUtils.copyToString(new InputStreamReader(file.getInputStream()));
Object migrationObject = JsonUtils.jsonToObject(migrationContent, Object.class);
if (migrationObject instanceof Map) {
Map<String, Object> migrationMap = (Map<String, Object>) migrationObject;
// Handle attachments
List<Attachment> attachments = handleAttachments(migrationMap.get("attachments"));
log.debug("Migrated attachments: [{}]", attachments);
// Handle links
List<Link> links = handleLinks(migrationMap.get("links"));
log.debug("Migrated links: [{}]", links);
// Handle galleries
List<Photo> photos = handleGalleries(migrationMap.get("galleries"));
log.debug("Migrated photos: [{}]", photos);
// Handle menus
List<Menu> menus = handleMenus(migrationMap.get("menus"));
log.debug("Migrated menus: [{}]", menus);
// Handle posts
List<BasePost> posts = handleBasePosts(migrationMap.get("posts"));
log.debug("Migrated posts: [{}]", posts);
}
} catch (IOException e) {
throw new ServiceException("Failed to read multipart file " + file.getOriginalFilename(), e);
}
}
@NonNull
private List<BasePost> handleBasePosts(@Nullable Object postsObject) {
if (!(postsObject instanceof List)) {
return Collections.emptyList();
}
List<Object> postObjectList = (List<Object>) postsObject;
List<BasePost> result = new LinkedList<>();
postObjectList.forEach(postObject -> {
if (!(postObject instanceof Map)) {
return;
}
Map<String, Object> postMap = (Map<String, Object>) postObject;
BasePost post = new BasePost();
post.setTitle(postMap.getOrDefault("postTitle", "").toString());
post.setUrl(postMap.getOrDefault("postUrl", "").toString());
post.setOriginalContent(postMap.getOrDefault("postContentMd", "").toString());
post.setFormatContent(postMap.getOrDefault("postContent", "").toString());
post.setSummary(postMap.getOrDefault("postSummary", "").toString());
post.setThumbnail(postMap.getOrDefault("postThumbnail", "").toString());
post.setVisits(getLongOrDefault(postMap.get("postViews").toString(), 0L));
post.setDisallowComment(false);
post.setTemplate(postMap.getOrDefault("customTpl", "").toString());
// Set disallow comment
Integer allowComment = getIntegerOrDefault(postMap.getOrDefault("allowComment", "1").toString(), 1);
if (allowComment != 1) {
post.setDisallowComment(true);
}
// Set create time
Long createTime = getLongOrDefault(postMap.get("postDate").toString(), 0L);
if (createTime != 0L) {
post.setCreateTime(new Date(createTime));
}
// Set update time
Long updateTime = getLongOrDefault(postMap.get("postUpdate").toString(), 0L);
if (updateTime != 0L) {
post.setUpdateTime(new Date(updateTime));
}
// Set status (default draft)
Integer postStatus = getIntegerOrDefault(postMap.get("postStatus").toString(), 1);
if (postStatus == 0) {
post.setStatus(PostStatus.PUBLISHED);
} else if (postStatus == 1) {
post.setStatus(PostStatus.DRAFT);
} else {
post.setStatus(PostStatus.RECYCLE);
}
String postType = postMap.getOrDefault("postType", "post").toString();
try {
if (postType.equalsIgnoreCase("post")) {
// TODO Handle post
result.add(handlePost(post, postMap));
} else {
// TODO Handle page
result.add(handleSheet(post, postMap));
}
} catch (Exception e) {
log.warn("Failed to migrate a post or sheet", e);
// Ignore this exception
}
});
return result;
}
@NonNull
private Post handlePost(@NonNull BasePost basePost, @NonNull Map<String, Object> postMap) {
Post post = BeanUtils.transformFrom(basePost, Post.class);
// Create it
Post createdPost = postService.create(post);
Object commentsObject = postMap.get("comments");
// TODO Handle comments
return createdPost;
}
@NonNull
private Sheet handleSheet(@NonNull BasePost basePost, @NonNull Map<String, Object> postMap) {
Sheet sheet = BeanUtils.transformFrom(basePost, Sheet.class);
// Create it
Sheet createdSheet = sheetService.create(sheet);
Object commentsObject = postMap.get("comments");
// TODO Handle comments
return createdSheet;
}
@NonNull
private List<Menu> handleMenus(@Nullable Object menusObject) {
if (!(menusObject instanceof List)) {
return Collections.emptyList();
}
List<Object> menuObjectList = (List<Object>) menusObject;
List<Menu> result = new LinkedList<>();
menuObjectList.forEach(menuObject -> {
if (!(menuObject instanceof Map)) {
return;
}
Map<String, Object> menuMap = (Map<String, Object>) menuObject;
Menu menu = new Menu();
menu.setName(menuMap.getOrDefault("menuName", "").toString());
menu.setUrl(menuMap.getOrDefault("menuUrl", "").toString());
// Set priority
String sortString = menuMap.getOrDefault("menuSort", "0").toString();
menu.setPriority(getIntegerOrDefault(sortString, 0));
menu.setTarget(menuMap.getOrDefault("menuTarget", "_self").toString());
menu.setIcon(menuMap.getOrDefault("menuIcon", "").toString());
try {
// Create menu
result.add(menuService.create(menu));
} catch (Exception e) {
log.warn("Failed to migrate a menu", e);
}
});
return result;
}
@NonNull
private List<Photo> handleGalleries(@Nullable Object galleriesObject) {
if (!(galleriesObject instanceof List)) {
return Collections.emptyList();
}
List<Object> galleryObjectList = (List<Object>) galleriesObject;
List<Photo> result = new LinkedList<>();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
galleryObjectList.forEach(galleryObject -> {
if (!(galleriesObject instanceof Map)) {
return;
}
Map<String, Object> galleryMap = (Map<String, Object>) galleryObject;
Photo photo = new Photo();
photo.setName(galleryMap.getOrDefault("galleryName", "").toString());
photo.setDescription(galleryMap.getOrDefault("galleryDesc", "").toString());
photo.setLocation(galleryMap.getOrDefault("galleryLocation", "").toString());
photo.setThumbnail(galleryMap.getOrDefault("galleryThumbnailUrl", "").toString());
photo.setUrl(galleryMap.getOrDefault("galleryUrl", "").toString());
Object galleryDate = galleryMap.get("galleryDate");
try {
if (galleryDate != null) {
photo.setTakeTime(dateFormat.parse(galleryDate.toString()));
}
// Create it
result.add(photoService.create(photo));
} catch (Exception e) {
log.warn("Failed to create a photo", e);
// Ignore this exception
}
});
return result;
}
@NonNull
private List<Link> handleLinks(@Nullable Object linksObject) {
if (!(linksObject instanceof List)) {
return Collections.emptyList();
}
List<Object> linkObjectList = (List<Object>) linksObject;
List<Link> result = new LinkedList<>();
linkObjectList.forEach(linkObject -> {
if (!(linkObject instanceof Map)) {
return;
}
Map<String, Object> linkMap = (Map<String, Object>) linkObject;
Link link = new Link();
link.setName(linkMap.getOrDefault("linkName", "").toString());
link.setUrl(linkMap.getOrDefault("linkUrl", "").toString());
link.setLogo(linkMap.getOrDefault("linkPic", "").toString());
link.setDescription(linkMap.getOrDefault("linkDesc", "").toString());
try {
result.add(linkService.create(link));
} catch (Exception e) {
log.warn("Failed to migrate a link", e);
}
});
return result;
}
@NonNull
private List<Attachment> handleAttachments(@Nullable Object attachmentsObject) {
if (!(attachmentsObject instanceof List)) {
return Collections.emptyList();
}
List<Object> attachmentObjectList = (List<Object>) attachmentsObject;
List<Attachment> result = new LinkedList<>();
attachmentObjectList.forEach(attachmentObject -> {
if (!(attachmentObject instanceof Map)) {
return;
}
Map<String, Object> attachmentMap = (Map<String, Object>) attachmentObject;
// Convert to attachment param
Attachment attachment = new Attachment();
attachment.setName(attachmentMap.getOrDefault("attachName", "").toString());
attachment.setPath(StringUtils.removeStart(attachmentMap.getOrDefault("attachPath", "").toString(), "/"));
attachment.setThumbPath(attachmentMap.getOrDefault("attachSmallPath", "").toString());
attachment.setMediaType(attachmentMap.getOrDefault("attachType", "").toString());
attachment.setSuffix(StringUtils.removeStart(attachmentMap.getOrDefault("attachSuffix", "").toString(), "."));
attachment.setSize(0L);
if (StringUtils.startsWith(attachment.getPath(), "/upload")) {
// Set this key
attachment.setFileKey(attachment.getPath());
}
// Set location
String attachLocation = attachmentMap.get("attachLocation").toString();
if (StringUtils.equalsIgnoreCase(attachLocation, "qiniu")) {
attachment.setType(AttachmentType.QNYUN);
} else if (StringUtils.equalsIgnoreCase(attachLocation, "upyun")) {
attachment.setType(AttachmentType.UPYUN);
} else {
attachment.setType(AttachmentType.LOCAL);
}
try {
// Save to db
Attachment createdAttachment = attachmentService.create(attachment);
result.add(createdAttachment);
} catch (Exception e) {
// Ignore this exception
log.warn("Failed to migrate an attachment " + attachment.getPath(), e);
}
});
return result;
}
@NonNull
private Integer getIntegerOrDefault(@Nullable String numberString, int defaultNumber) {
try {
return Integer.valueOf(numberString);
} catch (Exception e) {
// Ignore this exception
return defaultNumber;
}
}
@NonNull
private Long getLongOrDefault(@Nullable String numberString, long defaultNumber) {
try {
return Long.valueOf(numberString);
} catch (Exception e) {
// Ignore this exception
return defaultNumber;
}
}
}

View File

@ -0,0 +1,56 @@
package run.halo.app.service;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.springframework.util.ResourceUtils;
import run.halo.app.utils.JsonUtils;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Map;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
/**
* @author johnniang
* @date 19-4-26
*/
@Slf4j
public class RecoveryServiceTest {
@Test
public void getMigrationFileContent() throws IOException, URISyntaxException {
String migrationContent = getMigrationContent();
assertNotNull(migrationContent);
assertTrue(migrationContent.length() > 0);
}
@Test
public void resolveMigrationContent() throws IOException, URISyntaxException {
String migrationContent = getMigrationContent();
Object migrationObject = JsonUtils.jsonToObject(migrationContent, Object.class);
log.debug(migrationObject.getClass().toString());
if (migrationObject instanceof Map) {
Map<String, Object> migrationMap = (Map<String, Object>) migrationObject;
migrationMap.forEach((key, value) -> log.debug("Key: [{}], value type: [{}], value: [{}]", key, value.getClass().getTypeName(), value));
}
}
private String getMigrationContent() throws IOException, URISyntaxException {
URL migrationUrl = ResourceUtils.getURL(ResourceUtils.CLASSPATH_URL_PREFIX + "migration-test.json");
Path path = Paths.get(migrationUrl.toURI());
return new String(Files.readAllBytes(path));
}
}

File diff suppressed because one or more lines are too long