From c438d99d4783ec1070c2ee554c7d020ece8ee6fe Mon Sep 17 00:00:00 2001 From: johnniang Date: Fri, 26 Apr 2019 17:09:27 +0800 Subject: [PATCH] Complete migration handler --- .../controller/admin/api/MenuController.java | 2 +- .../admin/api/RecoveryController.java | 13 + .../content/api/MenuController.java | 2 +- .../controller/core/InstallController.java | 4 +- .../run/halo/app/model/entity/Attachment.java | 2 +- .../run/halo/app/model/entity/BaseEntity.java | 9 +- .../run/halo/app/model/entity/BasePost.java | 5 +- .../java/run/halo/app/model/entity/Menu.java | 12 +- .../java/run/halo/app/model/entity/Photo.java | 20 + .../run/halo/app/model/params/MenuParam.java | 2 +- .../app/repository/AttachmentRepository.java | 10 + .../run/halo/app/service/RecoveryService.java | 9 + .../service/impl/AttachmentServiceImpl.java | 28 +- .../app/service/impl/RecoveryServiceImpl.java | 399 +++++++++++++++++- .../halo/app/service/RecoveryServiceTest.java | 56 +++ src/test/resources/migration-test.json | 207 +++++++++ 16 files changed, 765 insertions(+), 15 deletions(-) create mode 100644 src/test/java/run/halo/app/service/RecoveryServiceTest.java create mode 100644 src/test/resources/migration-test.json diff --git a/src/main/java/run/halo/app/controller/admin/api/MenuController.java b/src/main/java/run/halo/app/controller/admin/api/MenuController.java index 530139b12..9d25b72e8 100644 --- a/src/main/java/run/halo/app/controller/admin/api/MenuController.java +++ b/src/main/java/run/halo/app/controller/admin/api/MenuController.java @@ -34,7 +34,7 @@ public class MenuController { @GetMapping @ApiOperation("Lists all menus") - public List listAll(@SortDefault(sort = "sort", direction = DESC) Sort sort) { + public List listAll(@SortDefault(sort = "priority", direction = DESC) Sort sort) { return menuService.listDtos(sort); } diff --git a/src/main/java/run/halo/app/controller/admin/api/RecoveryController.java b/src/main/java/run/halo/app/controller/admin/api/RecoveryController.java index a7af13d5f..13d14e8fc 100644 --- a/src/main/java/run/halo/app/controller/admin/api/RecoveryController.java +++ b/src/main/java/run/halo/app/controller/admin/api/RecoveryController.java @@ -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); + } } diff --git a/src/main/java/run/halo/app/controller/content/api/MenuController.java b/src/main/java/run/halo/app/controller/content/api/MenuController.java index 320a2f342..1a06bc774 100644 --- a/src/main/java/run/halo/app/controller/content/api/MenuController.java +++ b/src/main/java/run/halo/app/controller/content/api/MenuController.java @@ -31,7 +31,7 @@ public class MenuController { @GetMapping @ApiOperation("Lists all menus") - public List listAll(@SortDefault(sort = "sort", direction = DESC) Sort sort) { + public List listAll(@SortDefault(sort = "priority", direction = DESC) Sort sort) { return menuService.listDtos(sort); } } diff --git a/src/main/java/run/halo/app/controller/core/InstallController.java b/src/main/java/run/halo/app/controller/core/InstallController.java index cf3b5c380..f9088e8c8 100644 --- a/src/main/java/run/halo/app/controller/core/InstallController.java +++ b/src/main/java/run/halo/app/controller/core/InstallController.java @@ -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); } diff --git a/src/main/java/run/halo/app/model/entity/Attachment.java b/src/main/java/run/halo/app/model/entity/Attachment.java index 7ccff4817..8de2ca865 100644 --- a/src/main/java/run/halo/app/model/entity/Attachment.java +++ b/src/main/java/run/halo/app/model/entity/Attachment.java @@ -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; diff --git a/src/main/java/run/halo/app/model/entity/BaseEntity.java b/src/main/java/run/halo/app/model/entity/BaseEntity.java index e14505dab..bdbd97b37 100644 --- a/src/main/java/run/halo/app/model/entity/BaseEntity.java +++ b/src/main/java/run/halo/app/model/entity/BaseEntity.java @@ -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 diff --git a/src/main/java/run/halo/app/model/entity/BasePost.java b/src/main/java/run/halo/app/model/entity/BasePost.java index bcb3dcf4d..905d2f9d5 100644 --- a/src/main/java/run/halo/app/model/entity/BasePost.java +++ b/src/main/java/run/halo/app/model/entity/BasePost.java @@ -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; diff --git a/src/main/java/run/halo/app/model/entity/Menu.java b/src/main/java/run/halo/app/model/entity/Menu.java index 2932a4ea5..fa6d5c656 100644 --- a/src/main/java/run/halo/app/model/entity/Menu.java +++ b/src/main/java/run/halo/app/model/entity/Menu.java @@ -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; + } } } diff --git a/src/main/java/run/halo/app/model/entity/Photo.java b/src/main/java/run/halo/app/model/entity/Photo.java index 4d18670be..add81ffaa 100644 --- a/src/main/java/run/halo/app/model/entity/Photo.java +++ b/src/main/java/run/halo/app/model/entity/Photo.java @@ -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 = ""; + } } } diff --git a/src/main/java/run/halo/app/model/params/MenuParam.java b/src/main/java/run/halo/app/model/params/MenuParam.java index 55cb6266b..ebbac7fac 100644 --- a/src/main/java/run/halo/app/model/params/MenuParam.java +++ b/src/main/java/run/halo/app/model/params/MenuParam.java @@ -27,7 +27,7 @@ public class MenuParam implements InputConverter { @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}") diff --git a/src/main/java/run/halo/app/repository/AttachmentRepository.java b/src/main/java/run/halo/app/repository/AttachmentRepository.java index ce00b91f4..ecd9118e4 100644 --- a/src/main/java/run/halo/app/repository/AttachmentRepository.java +++ b/src/main/java/run/halo/app/repository/AttachmentRepository.java @@ -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 findAllMediaType(); + + /** + * Counts by attachment path. + * + * @param path attachment path must not be blank + * @return count of the given path + */ + long countByPath(@NonNull String path); } diff --git a/src/main/java/run/halo/app/service/RecoveryService.java b/src/main/java/run/halo/app/service/RecoveryService.java index 1a70c017f..e4ace0e75 100644 --- a/src/main/java/run/halo/app/service/RecoveryService.java +++ b/src/main/java/run/halo/app/service/RecoveryService.java @@ -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); } diff --git a/src/main/java/run/halo/app/service/impl/AttachmentServiceImpl.java b/src/main/java/run/halo/app/service/impl/AttachmentServiceImpl.java index 0361ea0c2..f2735f5af 100644 --- a/src/main/java/run/halo/app/service/impl/AttachmentServiceImpl.java +++ b/src/main/java/run/halo/app/service/impl/AttachmentServiceImpl.java @@ -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 0) { + throw new AlreadyExistsException("The attachment with path " + attachment.getPath() + " exists already"); + } + } + /** * Get attachment type from options. * diff --git a/src/main/java/run/halo/app/service/impl/RecoveryServiceImpl.java b/src/main/java/run/halo/app/service/impl/RecoveryServiceImpl.java index ff0fcdf7b..bb8b038a5 100644 --- a/src/main/java/run/halo/app/service/impl/RecoveryServiceImpl.java +++ b/src/main/java/run/halo/app/service/impl/RecoveryServiceImpl.java @@ -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 migrationMap = (Map) migrationObject; + + // Handle attachments + List attachments = handleAttachments(migrationMap.get("attachments")); + + log.debug("Migrated attachments: [{}]", attachments); + + // Handle links + List links = handleLinks(migrationMap.get("links")); + + log.debug("Migrated links: [{}]", links); + + // Handle galleries + List photos = handleGalleries(migrationMap.get("galleries")); + + log.debug("Migrated photos: [{}]", photos); + + // Handle menus + List menus = handleMenus(migrationMap.get("menus")); + + log.debug("Migrated menus: [{}]", menus); + + // Handle posts + List 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 handleBasePosts(@Nullable Object postsObject) { + if (!(postsObject instanceof List)) { + return Collections.emptyList(); + } + + List postObjectList = (List) postsObject; + + List result = new LinkedList<>(); + + postObjectList.forEach(postObject -> { + if (!(postObject instanceof Map)) { + return; + } + + Map postMap = (Map) 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 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 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 handleMenus(@Nullable Object menusObject) { + if (!(menusObject instanceof List)) { + return Collections.emptyList(); + } + + List menuObjectList = (List) menusObject; + + List result = new LinkedList<>(); + + menuObjectList.forEach(menuObject -> { + if (!(menuObject instanceof Map)) { + return; + } + + Map menuMap = (Map) 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 handleGalleries(@Nullable Object galleriesObject) { + if (!(galleriesObject instanceof List)) { + return Collections.emptyList(); + } + + List galleryObjectList = (List) galleriesObject; + + List result = new LinkedList<>(); + + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); + + galleryObjectList.forEach(galleryObject -> { + if (!(galleriesObject instanceof Map)) { + return; + } + + Map galleryMap = (Map) 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 handleLinks(@Nullable Object linksObject) { + if (!(linksObject instanceof List)) { + return Collections.emptyList(); + } + + List linkObjectList = (List) linksObject; + + List result = new LinkedList<>(); + + linkObjectList.forEach(linkObject -> { + if (!(linkObject instanceof Map)) { + return; + } + + Map linkMap = (Map) 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 handleAttachments(@Nullable Object attachmentsObject) { + if (!(attachmentsObject instanceof List)) { + return Collections.emptyList(); + } + + List attachmentObjectList = (List) attachmentsObject; + + List result = new LinkedList<>(); + + attachmentObjectList.forEach(attachmentObject -> { + if (!(attachmentObject instanceof Map)) { + return; + } + + Map attachmentMap = (Map) 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; + } + } + } diff --git a/src/test/java/run/halo/app/service/RecoveryServiceTest.java b/src/test/java/run/halo/app/service/RecoveryServiceTest.java new file mode 100644 index 000000000..93fbf5ccb --- /dev/null +++ b/src/test/java/run/halo/app/service/RecoveryServiceTest.java @@ -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 migrationMap = (Map) 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)); + } +} \ No newline at end of file diff --git a/src/test/resources/migration-test.json b/src/test/resources/migration-test.json new file mode 100644 index 000000000..8cac879d5 --- /dev/null +++ b/src/test/resources/migration-test.json @@ -0,0 +1,207 @@ +{ + "attachments": [ + { + "attachPath": "/upload/2018/4/avatar.jpeg", + "attachCreated": 1524883947683, + "attachType": "image/jpeg", + "attachSuffix": ".jpeg", + "attachName": "avatar.jpeg", + "attachId": 59, + "attachSmallPath": "/upload/2018/4/avatar_small.jpeg" + } + ], + "options": { + "mail_from_name": "Ryan0up'S Blog" + }, + "links": [ + { + "linkId": 76, + "linkUrl": "http://slogc.cc", + "linkPic": "http://www.gravatar.com/avatar/c8d4e2c9e04f1a117b4eacf107ab941d?s=256&d=identicon", + "linkDesc": "基佬~", + "linkName": "SNAIL Blog" + } + ], + "galleries": [ + { + "galleryThumbnailUrl": "https://cdn.ryanc.cc/img/blog/gallery/Photo_1.jpeg", + "galleryId": 166, + "galleryName": "长江", + "galleryDate": "2016-04-28", + "galleryLocation": "重庆", + "galleryDesc": "长江", + "galleryUrl": "https://cdn.ryanc.cc/img/blog/gallery/Photo_1.jpeg" + } + ], + "menus": [ + { + "menuTarget": "_self", + "menuUrl": "/p/about", + "menuIcon": "", + "menuId": 360, + "menuName": "About", + "menuSort": 5 + } + ], + "posts": [ + { + "postUrl": "spring-boot-with-docker", + "postType": "post", + "postContent": "
\n

Docker 这个词在近两年也算是一个热门词汇了,越来越多的小伙伴选择使用 Docker 来部署自己的项目。今天我将介绍一下如何使用 Docker 来部署Spring Boot 项目。

\n
\n

前言

\n

最开始想使用 Docker 来部署 Spring Boot 应用的时候,到网上搜索了很多相关的内容,从构建 Docker 镜像到应用部署,相关教程都差不多,没什么意思,Dockerfile 大多都是都是像下面这种:

\n
FROM openjdk:8-jdk-alpine\nADD demo.jar app.jar\nENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]\n
\n

不是说这种不行,只是灵活性太差了,示例太简单,而且每次都需要手动构建,而且还得先把项目打包成 Jar 包,十分繁琐,如果是想要部署到服务器上,还得在服务器上打包成镜像,太过于繁琐,不适合运用到生产环境。

\n

实际操作

\n

假设我们现在有这么几个需求:

\n
    \n
  1. 实现自动构建,我们自己不需要将项目打包成 Jar 包。
  2. \n
  3. 项目有图片上传功能,
  4. \n
\n", + "postUpdate": 1546096272752, + "postStatus": 1, + "postId": 6000, + "allowComment": 1, + "postSummary": "Docker这个词在近两年也算是一个热门词汇了,越来越多的小伙伴选择使用Docker来部署自己的项目。今天我将介绍一下如何使用Docker来部署SpringBoot项目。前言最开始想使用Docker来部署SpringBoot应用的时候,到网上搜索了很多相关的内容,从构建Docker镜像到应用部署,相", + "tags": [ + { + "tagId": 5192, + "tagName": "Docker", + "tagUrl": "docker" + }, + { + "tagId": 12, + "tagName": "SpringBoot", + "tagUrl": "springboot" + }, + { + "tagId": 11, + "tagName": "Java", + "tagUrl": "java" + }, + { + "tagId": 34, + "tagName": "maven", + "tagUrl": "maven" + } + ], + "postViews": 0, + "postThumbnail": "/static/halo-frontend/images/thumbnail/thumbnail-5.jpg", + "postDate": 1546089840000, + "postTitle": "Spring Boot 和 Docker", + "postContentMd": "> Docker 这个词在近两年也算是一个热门词汇了,越来越多的小伙伴选择使用 Docker 来部署自己的项目。今天我将介绍一下如何使用 Docker 来部署Spring Boot 项目。\n\n## 前言\n最开始想使用 Docker 来部署 Spring Boot 应用的时候,到网上搜索了很多相关的内容,从构建 Docker 镜像到应用部署,相关教程都差不多,没什么意思,Dockerfile 大多都是都是像下面这种:\n```docker\nFROM openjdk:8-jdk-alpine\nADD demo.jar app.jar\nENTRYPOINT [\"java\",\"-Djava.security.egd=file:/dev/./urandom\",\"-jar\",\"/app.jar\"]\n```\n不是说这种不行,只是灵活性太差了,示例太简单,而且每次都需要手动构建,而且还得先把项目打包成 Jar 包,十分繁琐,如果是想要部署到服务器上,还得在服务器上打包成镜像,太过于繁琐,不适合运用到生产环境。\n\n## 实际操作\n假设我们现在有这么几个需求:\n1. 实现自动构建,我们自己不需要将项目打包成 Jar 包。\n2. 项目有图片上传功能,", + "categories": [ + { + "cateUrl": "study-notes", + "cateDesc": "学习笔记", + "cateId": 9, + "cateName": "学习笔记" + } + ] + }, + { + "postUrl": "1551432643915", + "postType": "post", + "postContent": "", + "postUpdate": 1551432662994, + "postStatus": 1, + "postId": 6555, + "allowComment": 1, + "postSummary": "", + "postViews": 0, + "postThumbnail": "https://ryanc.cc/static/halo-frontend/images/thumbnail/thumbnail-5.jpg", + "postDate": 1551432662994, + "postTitle": "小米 MIX3 体验", + "postContentMd": "", + "postPassword": "" + }, + { + "postUrl": "springboot-study-jpa", + "comments": [ + { + "commentParent": 0, + "commentAuthorIp": "*.*.*.*", + "commentAuthor": "RYAN0UP", + "commentId": 711, + "commentAuthorUrl": "https://ryanc.cc", + "commentAuthorAvatarMd5": "7cc7f29278071bd4dce995612d428834", + "commentAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36", + "commentContent": "测试测试", + "isAdmin": 0, + "commentAuthorEmail": "i@ryanc.cc", + "commentStatus": 0 + }, + { + "commentParent": 711, + "commentAuthorIp": "*.*.*.*", + "commentAuthor": "RYAN0UP", + "commentId": 714, + "commentAuthorUrl": "https://ryanc.cc", + "commentAuthorAvatarMd5": "7cc7f29278071bd4dce995612d428834", + "commentAgent": "Mozilla/5.0 (Linux; Android 7.1.1; OS105 Build/NMF26X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.126 Mobile Safari/537.36", + "commentContent": "@RYAN0UP 测试成功", + "isAdmin": 1, + "commentAuthorEmail": "i@ryanc.cc", + "commentStatus": 0 + }, + { + "commentParent": 714, + "commentAuthorIp": "*.*.*.*", + "commentAuthor": "买了外卖", + "commentId": 5038, + "commentAuthorUrl": "", + "commentAgent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3432.0 Safari/537.36", + "commentContent": "@RYAN0UP 测试失败咯", + "isAdmin": 0, + "commentAuthorEmail": "", + "commentStatus": 0 + }, + { + "commentParent": 5038, + "commentAuthorIp": "*.*.*.*", + "commentAuthor": "RYAN0UP", + "commentId": 5040, + "commentAuthorUrl": "https://ryanc.cc", + "commentAuthorAvatarMd5": "7cc7f29278071bd4dce995612d428834", + "commentAgent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.81 Safari/537.36", + "commentContent": "@买了外卖 丫的再在我网站做测试,拉你黑名单", + "isAdmin": 1, + "commentAuthorEmail": "i@ryanc.cc", + "commentStatus": 0 + } + ], + "postType": "post", + "postContent": "
\n

前面学习了一个简单的Spring Boot框架的搭建,以及配置文件的基础部分,今天来记录一下最近所学操作数据库的一个工具JPA,这个JPA可以让你基本不需要写SQL语句就可以执行增删改查的操作。当然了,如果涉及到比较复杂的操作的话,也可以使用原生的SQL语句。

\n
\n

引入依赖

首先要使用JPA的话,肯定是需要引入依赖的,既然要使用JPA操作数据库的话,数据库的驱动依赖也是必不可少的。对数据库的操作无非增删改查,为了方便演示,这里还需要引入一个模板引擎来渲染页面,模板引擎有很多种,这里选择freemarker。

\n

\"\"

\n
<!-- JPA依赖 -->\n<dependency>\n  <groupId>org.springframework.boot</groupId>\n  <artifactId>spring-boot-starter-data-jpa</artifactId>\n</dependency>\n<!-- freemarker依赖 -->\n<dependency>\n  <groupId>org.springframework.boot</groupId>\n  <artifactId>spring-boot-starter-freemarker</artifactId>\n</dependency>\n<dependency>\n  <groupId>org.springframework.boot</groupId>\n  <artifactId>spring-boot-starter-web</artifactId>\n</dependency>\n<!-- MySQL依赖 -->\n<dependency>\n  <groupId>mysql</groupId>\n  <artifactId>mysql-connector-java</artifactId>\n  <scope>runtime</scope>\n</dependency>\n<dependency>\n  <groupId>org.springframework.boot</groupId>\n  <artifactId>spring-boot-starter-test</artifactId>\n  <scope>test</scope>\n</dependency>\n
\n

准备数据

    \n
  1. 这里我们是用的是MySQL,先建一个简单的数据表吧。
\n
CREATE DATABASE jpadb;\nUSE jpadb;\nCREATE TABLE superhero\n(\n  id INT PRIMARY KEY AUTO_INCREMENT,\n  name VARCHAR(20) NOT NULL ,\n  sex CHAR(2) NOT NULL ,\n  age int NOT NULL ,\n  skill VARCHAR(50) NOT NULL\n)CHARSET utf8;\n
\n

然后随便插入几条数据,备用。

\n
    \n
  1. 建立对应的实体类
\n
@Entity\n@Table(name = "superhero") //这里需要指定我们刚刚建好的数据表,因为JPA会自动帮助键表\npublic class SuperHero {\n    @Id\n    @GeneratedValue //自增注解\n    private Integer id;\n    private String name;\n    private String sex;\n    private Integer age;\n    private String skill;\n      //省略get和set方法\n}\n
\n

建立Repository接口

新建一个类SuperHeroRepository,使用JPA的关键就是这个Repository接口,使用它可以方便的对实体类进行访问。

\n
public interface SuperHeroRepository extends JpaRepository<SuperHero,Integer> {}\n
\n

像上面这样就好了?对,目前是这样的!

\n

Service层

哈哈,现在就可以使用Service对数据库进行操作啦!

\n
//Service注解,必须写!不然Controller使用Service的时候运行会报错。\n@Service\npublic class SuperHeroService {\n      //@Autowired,自动注入,用过SSM的都知道,这里可以自动注入\n    @Autowired\n    private SuperHeroRepository superHeroRepository;\n\n    /**\n     * 查询所有超级英雄\n     * @return List\n     */\n    public List<SuperHero> findAll(){\n        return superHeroRepository.findAll();\n    }\n}\n
\n

现在就可以查询superhero表中所有的超级英雄,并且findAll方法返回的数据类型就是List,非常方便!

\n

Controller处理并渲染页面

Service层写好之后,就可以使用Controller来处理并渲染页面了。这里要注入Service类同样可以使用自动注入。

\n
@Controller\npublic class IndexController {\n      //自动注入\n      @Autowired\n    private SuperHeroService superHeroService;\n\n      //设置请求路径为/heros\n    @GetMapping(value = "/heros")\n    public String index(Model model){\n          //调用service层的findAll方法查询所有数据\n        List<SuperHero> superHeroList = superHeroService.findAll();\n          //使用Model对象保存数据\n        model.addAttribute("superHeroList",superHeroList);\n          //返回freemarker模板的名称,所以需要在templates文件夹下建立index.ftl文件\n        return "index";\n    }\n}\n
\n
<!-- index.ftl -->\n<!DOCTYPE html>\n<html lang="zh">\n    <head>\n        <meta charset="UTF-8">\n        <title>Title</title>\n    </head>\n    <body>\n        <table>\n            <tr>\n                <th>编号</th>\n                <th>名字</th>\n                <th>性别</th>\n                <th>年龄</th>\n                <th>技能</th>\n            </tr>\n              <!-- 这是freemarker的循环语法,可以自行去了解 -->\n            <#list superHeroList as superHero>\n                <tr>\n                    <td>${superHero.id}</td>\n                    <td>${superHero.name}</td>\n                    <td>${superHero.sex}</td>\n                    <td>${superHero.age}</td>\n                    <td>${superHero.skill}</td>\n                </tr>\n            </#list>\n        </table>\n    </body>\n</html>\n
\n

好啦,现在所有代码就写完啦!但是似乎还忘了什么,对的,application.yaml还没配置。

\n
# 指定访问端口 默认是8080\nserver:\n  port: 8090\n\nspring:\n# 配置数据源,主要有classname,url,username,password\n  datasource:\n    driver-class-name: com.mysql.jdbc.Driver\n    url: jdbc:mysql://localhost:3306/jpadb\n    username: root\n    password:  123456\n# jpa配置\n  jpa:\n      # 是否在控制台显示sql语句\n    show-sql: true\n    hibernate:\n    # 这个就比较关键了,如果想让它自动建表的话,可以写create和update,但是create每次运行项目的时候,都会重建一次,update则不会,只会更新数据表接口,数据还在。\n      ddl-auto: update\n
\n

好了,一个简单的查询就做好了,访问localhost:8090就可以看到下面的表格啦!

\n

\"\"

\n", + "postUpdate": 1556110800048, + "postStatus": 0, + "postId": 23, + "allowComment": 1, + "postSummary": "前面学习了一个简单的SpringBoot框架的搭建,以及配置文件的基础部分,今天来记录一下最近所学操作数据库的一个工具JPA,这个JPA可以让你基本不需要写SQL语句就可以执行增删改查的操作。当然了,如果涉及到比较复杂的操作的话,也可以使用原生的SQL语句。引入依赖首先要使用JPA的话,肯定是需要引", + "tags": [ + { + "tagId": 11, + "tagName": "Java", + "tagUrl": "java" + }, + { + "tagId": 12, + "tagName": "SpringBoot", + "tagUrl": "springboot" + }, + { + "tagId": 34, + "tagName": "maven", + "tagUrl": "maven" + }, + { + "tagId": 20, + "tagName": "sql", + "tagUrl": "sql" + } + ], + "postViews": 701, + "postThumbnail": "/static/halo-frontend/images/thumbnail/thumbnail-9.jpg", + "postDate": 1513996216749, + "postTitle": "Spring Boot学习笔记(三)之JPA", + "postContentMd": "> 前面学习了一个简单的Spring Boot框架的搭建,以及配置文件的基础部分,今天来记录一下最近所学操作数据库的一个工具`JPA`,这个JPA可以让你基本不需要写SQL语句就可以执行增删改查的操作。当然了,如果涉及到比较复杂的操作的话,也可以使用原生的SQL语句。\n\n## 引入依赖\n\n首先要使用JPA的话,肯定是需要引入依赖的,既然要使用JPA操作数据库的话,数据库的驱动依赖也是必不可少的。对数据库的操作无非增删改查,为了方便演示,这里还需要引入一个模板引擎来渲染页面,模板引擎有很多种,这里选择freemarker。\n\n![](https://cdn.ryanc.cc/img/blog/thumbnails/springboot-study-jpa/springboot-study-jpa-1.png)\n\n\n\n```xml\n\n\n  org.springframework.boot\n  spring-boot-starter-data-jpa\n\n\n\n  org.springframework.boot\n  spring-boot-starter-freemarker\n\n\n  org.springframework.boot\n  spring-boot-starter-web\n\n\n\n  mysql\n  mysql-connector-java\n  runtime\n\n\n  org.springframework.boot\n  spring-boot-starter-test\n  test\n\n```\n\n## 准备数据\n\n1. 这里我们是用的是MySQL,先建一个简单的数据表吧。\n\n```sql\nCREATE DATABASE jpadb;\nUSE jpadb;\nCREATE TABLE superhero\n(\n  id INT PRIMARY KEY AUTO_INCREMENT,\n  name VARCHAR(20) NOT NULL ,\n  sex CHAR(2) NOT NULL ,\n  age int NOT NULL ,\n  skill VARCHAR(50) NOT NULL\n)CHARSET utf8;\n```\n\n然后随便插入几条数据,备用。\n\n2. 建立对应的实体类\n\n```java\n@Entity\n@Table(name = \"superhero\") //这里需要指定我们刚刚建好的数据表,因为JPA会自动帮助键表\npublic class SuperHero {\n    @Id\n    @GeneratedValue //自增注解\n    private Integer id;\n    private String name;\n    private String sex;\n    private Integer age;\n    private String skill;\n      //省略get和set方法\n}\n```\n\n## 建立Repository接口\n\n新建一个类`SuperHeroRepository`,使用JPA的关键就是这个Repository接口,使用它可以方便的对实体类进行访问。\n\n```java\npublic interface SuperHeroRepository extends JpaRepository {}\n```\n\n像上面这样就好了?对,目前是这样的!\n\n## Service层\n\n哈哈,现在就可以使用Service对数据库进行操作啦!\n\n```java\n//Service注解,必须写!不然Controller使用Service的时候运行会报错。\n@Service\npublic class SuperHeroService {\n      //@Autowired,自动注入,用过SSM的都知道,这里可以自动注入\n    @Autowired\n    private SuperHeroRepository superHeroRepository;\n\n    /**\n     * 查询所有超级英雄\n     * @return List\n     */\n    public List findAll(){\n        return superHeroRepository.findAll();\n    }\n}\n```\n\n现在就可以查询`superhero`表中所有的超级英雄,并且findAll方法返回的数据类型就是List,非常方便!\n\n## Controller处理并渲染页面\n\nService层写好之后,就可以使用Controller来处理并渲染页面了。这里要注入Service类同样可以使用自动注入。\n\n```java\n@Controller\npublic class IndexController {\n      //自动注入\n      @Autowired\n    private SuperHeroService superHeroService;\n\n      //设置请求路径为/heros\n    @GetMapping(value = \"/heros\")\n    public String index(Model model){\n          //调用service层的findAll方法查询所有数据\n        List superHeroList = superHeroService.findAll();\n          //使用Model对象保存数据\n        model.addAttribute(\"superHeroList\",superHeroList);\n          //返回freemarker模板的名称,所以需要在templates文件夹下建立index.ftl文件\n        return \"index\";\n    }\n}\n```\n\n```html\n\n\n\n    \n        \n        Title\n    \n    \n        \n            \n                \n                \n                \n                \n                \n            \n              \n            <#list superHeroList as superHero>\n                \n                    \n                    \n                    \n                    \n                    \n                \n            \n        
编号名字性别年龄技能
${superHero.id}${superHero.name}${superHero.sex}${superHero.age}${superHero.skill}
\n    \n\n```\n\n好啦,现在所有代码就写完啦!但是似乎还忘了什么,对的,application.yaml还没配置。\n\n```yaml\n# 指定访问端口 默认是8080\nserver:\n  port: 8090\n\nspring:\n# 配置数据源,主要有classname,url,username,password\n  datasource:\n    driver-class-name: com.mysql.jdbc.Driver\n    url: jdbc:mysql://localhost:3306/jpadb\n    username: root\n    password:  123456\n# jpa配置\n  jpa:\n      # 是否在控制台显示sql语句\n    show-sql: true\n    hibernate:\n    # 这个就比较关键了,如果想让它自动建表的话,可以写create和update,但是create每次运行项目的时候,都会重建一次,update则不会,只会更新数据表接口,数据还在。\n      ddl-auto: update\n```\n\n好了,一个简单的查询就做好了,访问`localhost:8090`就可以看到下面的表格啦!\n\n![](https://cdn.ryanc.cc/img/blog/thumbnails/springboot-study-jpa/springboot-study-jpa-2.png)\n\n", + "categories": [ + { + "cateUrl": "study-notes", + "cateDesc": "学习笔记", + "cateId": 9, + "cateName": "学习笔记" + } + ] + } + ] +}