mirror of https://github.com/halo-dev/halo
refactor: WordPress data import (#506)
* refactor: WordPress data import * refactor: change wordpress convert class to interface and add wordpress convert null judgment * fix: add some class doc * fix: exclude commons io * feat: add custom annotation: @PropertyMappingTo and reflection utils, It can be used to mapping the relationship between source model and target model * feat: replace the original wordpressconvert implementation with a custom annotation * fix: change wordpress convert method name * fix: change wordpress convert method name * fix: merger conflictpull/523/head
parent
4be31d8923
commit
61f0748a6a
|
@ -60,6 +60,8 @@ ext {
|
|||
flywayVersion = '6.1.0'
|
||||
h2Version = '1.4.196'
|
||||
levelDbVersion = '0.12'
|
||||
jsonVersion = '20190722'
|
||||
fastJsonVersion = '1.2.56'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
@ -104,6 +106,9 @@ dependencies {
|
|||
implementation "net.sf.image4j:image4j:$image4jVersion"
|
||||
implementation "org.flywaydb:flyway-core:$flywayVersion"
|
||||
|
||||
implementation "org.json:json:$jsonVersion"
|
||||
implementation "com.alibaba:fastjson:$fastJsonVersion"
|
||||
|
||||
implementation "org.iq80.leveldb:leveldb:$levelDbVersion"
|
||||
runtimeOnly "com.h2database:h2:$h2Version"
|
||||
runtimeOnly 'mysql:mysql-connector-java'
|
||||
|
|
|
@ -1,29 +1,23 @@
|
|||
package run.halo.app.handler.migrate;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dom4j.Element;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import run.halo.app.exception.ServiceException;
|
||||
import run.halo.app.model.entity.BasePost;
|
||||
import run.halo.app.model.entity.Category;
|
||||
import run.halo.app.model.entity.Post;
|
||||
import run.halo.app.model.entity.Tag;
|
||||
import run.halo.app.handler.migrate.converter.Converter;
|
||||
import run.halo.app.handler.migrate.converter.WordPressConverter;
|
||||
import run.halo.app.handler.migrate.support.vo.PostVO;
|
||||
import run.halo.app.handler.migrate.support.wordpress.Rss;
|
||||
import run.halo.app.model.enums.MigrateType;
|
||||
import run.halo.app.service.*;
|
||||
import run.halo.app.utils.MarkdownUtils;
|
||||
import run.halo.app.utils.WordPressMigrateUtils;
|
||||
import run.halo.app.utils.XmlMigrateUtils;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* WordPress migrate handler
|
||||
|
@ -90,138 +84,21 @@ public class WordPressMigrateHandler implements MigrateHandler {
|
|||
public void migrate(MultipartFile file) {
|
||||
try {
|
||||
String migrationContent = FileCopyUtils.copyToString(new InputStreamReader(file.getInputStream(), StandardCharsets.UTF_8));
|
||||
Element rootElement = WordPressMigrateUtils.getRootElement(new FileInputStream(migrationContent));
|
||||
Map<String, Object> resultSetMapping = WordPressMigrateUtils.getResultSetMapping(rootElement);
|
||||
String jsonString = XmlMigrateUtils.xml2jsonString(new FileInputStream(migrationContent));
|
||||
JSONObject json = JSONObject.parseObject(jsonString);
|
||||
Rss rss = json.getObject("rss", Rss.class);
|
||||
|
||||
// Handle categories
|
||||
List<Category> categories = handleCategories(resultSetMapping.get("wp:category"));
|
||||
// 转换
|
||||
Converter<Rss, List<PostVO>> converter = new WordPressConverter();
|
||||
|
||||
// Handle tags
|
||||
List<Tag> tags = handleTags(resultSetMapping.get("wp:tag"));
|
||||
List<PostVO> postVoList = converter.convertFrom(rss);
|
||||
|
||||
// Handle posts
|
||||
List<BasePost> posts = handlePosts(resultSetMapping.get("item"));
|
||||
|
||||
log.debug("Migrated posts: [{}]", posts);
|
||||
log.debug("Migrated posts: [{}]", postVoList);
|
||||
} catch (Exception e) {
|
||||
throw new ServiceException("WordPress 导出文件 " + file.getOriginalFilename() + " 读取失败", e);
|
||||
throw new ServiceException("WordPress 入出文件 " + file.getOriginalFilename() + " 读取失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
private List<Category> handleCategories(@Nullable Object categoriesObject) {
|
||||
|
||||
if (!(categoriesObject instanceof List)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<Object> categoryObjectList = (List<Object>) categoriesObject;
|
||||
|
||||
List<Category> result = new LinkedList<>();
|
||||
|
||||
categoryObjectList.forEach(categoryObject -> {
|
||||
|
||||
if (!(categoryObject instanceof Map)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, Object> categoryMap = (Map<String, Object>) categoryObject;
|
||||
|
||||
String slugName = categoryMap.getOrDefault("wp:category_nicename", "").toString();
|
||||
|
||||
Category category = categoryService.getBySlugName(slugName);
|
||||
|
||||
if (null == category) {
|
||||
category = new Category();
|
||||
category.setName(categoryMap.getOrDefault("wp:cat_name", "").toString());
|
||||
category.setSlugName(slugName);
|
||||
category = categoryService.create(category);
|
||||
}
|
||||
|
||||
try {
|
||||
result.add(category);
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to migrate a category", e);
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<Tag> handleTags(@Nullable Object tagsObject) {
|
||||
|
||||
if (!(tagsObject instanceof List)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<Object> tagObjectList = (List<Object>) tagsObject;
|
||||
|
||||
List<Tag> result = new LinkedList<>();
|
||||
|
||||
tagObjectList.forEach(tagObject -> {
|
||||
if (!(tagObject instanceof Map)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, Object> tagMap = (Map<String, Object>) tagObject;
|
||||
|
||||
String slugName = tagMap.getOrDefault("wp:tag_slug", "").toString();
|
||||
|
||||
Tag tag = tagService.getBySlugName(slugName);
|
||||
|
||||
if (null == tag) {
|
||||
tag = new Tag();
|
||||
tag.setName(tagMap.getOrDefault("wp:tag_name", "").toString());
|
||||
tag.setSlugName(slugName);
|
||||
tag = tagService.create(tag);
|
||||
}
|
||||
|
||||
try {
|
||||
result.add(tag);
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to migrate a tag", e);
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private List<BasePost> handlePosts(@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("title", "").toString());
|
||||
post.setUrl(postMap.getOrDefault("wp:post_name", "").toString());
|
||||
post.setOriginalContent(MarkdownUtils.renderMarkdown(postMap.getOrDefault("content:encoded", "").toString()));
|
||||
post.setFormatContent(postMap.getOrDefault("content:encoded", "").toString());
|
||||
post.setSummary(postMap.getOrDefault("excerpt:encoded", "").toString());
|
||||
|
||||
String url = postMap.getOrDefault("wp:post_name", "").toString();
|
||||
|
||||
Post temp = postService.getByUrl(url);
|
||||
|
||||
if (temp != null) {
|
||||
post.setUrl(post.getUrl() + "_1");
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportType(MigrateType type) {
|
||||
return MigrateType.WORDPRESS.equals(type);
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
package run.halo.app.handler.migrate.converter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 博客迁移数据转换器,只定义从SOURCE转TARGET的单向转换
|
||||
*
|
||||
* @author guqing
|
||||
* @date 2020-01-18 16:45
|
||||
*/
|
||||
public interface Converter<SOURCE, TARGET> {
|
||||
|
||||
/**
|
||||
* 将source转换为target
|
||||
*
|
||||
* @param s 需要转换的源对象
|
||||
* @return 返回转换得到的结果对象
|
||||
*/
|
||||
TARGET convertFrom(SOURCE s);
|
||||
|
||||
/**
|
||||
* 从SOURCE转为TARGET
|
||||
*
|
||||
* @param s source,需要转换的原始SOURCE集合
|
||||
* @param function 具体转换逻辑
|
||||
* @return 返回转换得到的TARGET对象
|
||||
*/
|
||||
default TARGET convertFrom(SOURCE s, Function<SOURCE, TARGET> function) {
|
||||
return function.apply(s);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量从SOURCE转换得到TARGET
|
||||
*
|
||||
* @param list 需要转换的原始SOURCE集合
|
||||
* @param function 具体转换逻辑
|
||||
* @return 返回转换得到的TARGET集合结果
|
||||
*/
|
||||
default List<TARGET> batchConverterFromDto(List<SOURCE> list, Function<SOURCE, TARGET> function) {
|
||||
return list.stream().map(s -> convertFrom(s, function)).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
package run.halo.app.handler.migrate.converter;
|
||||
|
||||
import run.halo.app.handler.migrate.support.vo.PostVO;
|
||||
import run.halo.app.handler.migrate.support.wordpress.*;
|
||||
import run.halo.app.handler.migrate.utils.RelationMapperUtils;
|
||||
import run.halo.app.model.entity.BaseComment;
|
||||
import run.halo.app.model.entity.BasePost;
|
||||
import run.halo.app.model.entity.Category;
|
||||
import run.halo.app.model.entity.Tag;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* WordPress博客数据迁移转换器
|
||||
*
|
||||
* @author guqing
|
||||
* @date 2020-01-18 16:50
|
||||
*/
|
||||
public class WordPressConverter implements Converter<Rss, List<PostVO>> {
|
||||
|
||||
@Override
|
||||
public List<PostVO> convertFrom(Rss rss) {
|
||||
return convertFrom(rss, this::apply);
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义转换规则
|
||||
*/
|
||||
public List<PostVO> apply(Rss rss) {
|
||||
if (Objects.isNull(rss) || Objects.isNull(rss.getChannel())) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
Channel channel = rss.getChannel();
|
||||
List<Item> items = channel.getItems();
|
||||
|
||||
if (items == null) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return getBasePost(items);
|
||||
}
|
||||
|
||||
private List<PostVO> getBasePost(List<Item> items) {
|
||||
List<PostVO> posts = new ArrayList<>();
|
||||
if (items == null) {
|
||||
return posts;
|
||||
}
|
||||
|
||||
for (Item item : items) {
|
||||
PostVO postVo = new PostVO();
|
||||
// 设置文章
|
||||
BasePost post = getBasePostFromItem(item);
|
||||
postVo.setBasePost(post);
|
||||
|
||||
// 获取标签和分类
|
||||
List<WpCategory> categories = item.getCategories();
|
||||
List<Category> categoryModelList = new ArrayList<>();
|
||||
List<Tag> tags = new ArrayList<>();
|
||||
if (categories != null) {
|
||||
categories.forEach(category -> {
|
||||
String domain = category.getDomain();
|
||||
if ("post_tag".equals(domain)) {
|
||||
Tag tag = RelationMapperUtils.convertFrom(category, Tag.class);
|
||||
tags.add(tag);
|
||||
} else if ("category".equals(domain)) {
|
||||
Category categoryModel = RelationMapperUtils.convertFrom(category, Category.class);
|
||||
categoryModelList.add(categoryModel);
|
||||
}
|
||||
});
|
||||
}
|
||||
// 设置标签和分类
|
||||
postVo.setCategories(categoryModelList);
|
||||
postVo.setTags(tags);
|
||||
|
||||
// 设置评论
|
||||
List<BaseComment> comments = getCommentsFromItem(item);
|
||||
postVo.setComments(comments);
|
||||
|
||||
posts.add(postVo);
|
||||
}
|
||||
|
||||
return posts;
|
||||
}
|
||||
|
||||
private List<BaseComment> getCommentsFromItem(Item item) {
|
||||
List<BaseComment> baseComments = new ArrayList<>();
|
||||
if (Objects.isNull(item) || Objects.isNull(item.getComments())) {
|
||||
return baseComments;
|
||||
}
|
||||
|
||||
List<Comment> comments = item.getComments();
|
||||
for (Comment comment : comments) {
|
||||
BaseComment baseComment = RelationMapperUtils.convertFrom(comment, BaseComment.class);
|
||||
baseComments.add(baseComment);
|
||||
}
|
||||
|
||||
return baseComments;
|
||||
}
|
||||
|
||||
private BasePost getBasePostFromItem(Item item) {
|
||||
BasePost post = RelationMapperUtils.convertFrom(item, BasePost.class);
|
||||
String postDate = item.getPostDate();
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
if (postDate != null) {
|
||||
LocalDateTime dateTime = LocalDateTime.parse(postDate, formatter);
|
||||
Date date = Date.from(dateTime.atZone(ZoneId.systemDefault()).toInstant());
|
||||
post.setEditTime(date);
|
||||
// 设置url为文章编辑时的时间毫秒数
|
||||
post.setUrl(post.getEditTime() + "");
|
||||
} else {
|
||||
post.setUrl(System.currentTimeMillis() + "");
|
||||
}
|
||||
return post;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package run.halo.app.handler.migrate.support.vo;
|
||||
|
||||
import lombok.Data;
|
||||
import run.halo.app.model.entity.BaseComment;
|
||||
import run.halo.app.model.entity.BasePost;
|
||||
import run.halo.app.model.entity.Category;
|
||||
import run.halo.app.model.entity.Tag;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author guqing
|
||||
* @date 2020-01-18 16:52
|
||||
*/
|
||||
@Data
|
||||
public class PostVO {
|
||||
private BasePost basePost;
|
||||
private List<Tag> tags;
|
||||
private List<Category> categories;
|
||||
private List<BaseComment> comments;
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package run.halo.app.handler.migrate.support.wordpress;
|
||||
|
||||
import com.alibaba.fastjson.annotation.JSONField;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* WordPress导出的xml中对应的channel节点下的子节点将被映射为该类的属性
|
||||
* </p>
|
||||
*
|
||||
* @author guqing
|
||||
* @date 2019-11-17 13:57
|
||||
*/
|
||||
@Data
|
||||
public class Channel {
|
||||
@JSONField(name = "title")
|
||||
private String title;
|
||||
private String link;
|
||||
private String description;
|
||||
private String pubDate;
|
||||
private String language;
|
||||
private String author;
|
||||
|
||||
@JSONField(name = "item")
|
||||
private List<Item> items;
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
package run.halo.app.handler.migrate.support.wordpress;
|
||||
|
||||
import com.alibaba.fastjson.annotation.JSONField;
|
||||
import lombok.Data;
|
||||
import run.halo.app.handler.migrate.utils.PropertyMappingTo;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* WordPress导出的xml中对于的comment节点下的子节点值将被映射为该类的属性,
|
||||
* 最终会被转换为{@link run.halo.app.model.entity.PostComment}
|
||||
* </p>
|
||||
*
|
||||
* @author guqing
|
||||
* @date 2019-11-17 14:01
|
||||
*/
|
||||
@Data
|
||||
public class Comment {
|
||||
@JSONField(name = "wp:comment_id")
|
||||
private String commentId;
|
||||
|
||||
@JSONField(name = "wp:comment_author")
|
||||
@PropertyMappingTo("author")
|
||||
private String commentAuthor;
|
||||
|
||||
@JSONField(name = "wp:comment_author_email")
|
||||
@PropertyMappingTo("email")
|
||||
private String commentAuthorEmail;
|
||||
|
||||
@JSONField(name = "wp:comment_author_url")
|
||||
@PropertyMappingTo("authorUrl")
|
||||
private String commentAuthorUrl;
|
||||
|
||||
@JSONField(name = "wp:comment_author_IP")
|
||||
@PropertyMappingTo("ipAddress")
|
||||
private String commentAuthorIp;
|
||||
|
||||
@JSONField(name = "wp:comment_date")
|
||||
private String commentDate;
|
||||
|
||||
@JSONField(name = "wp:comment_content")
|
||||
@PropertyMappingTo("content")
|
||||
private String commentContent;
|
||||
|
||||
@JSONField(name = "wp:comment_approved")
|
||||
private String commentApproved;
|
||||
|
||||
@JSONField(name = "wp:comment_parent")
|
||||
@PropertyMappingTo("parentId")
|
||||
private Long commentParent;
|
||||
|
||||
@JSONField(name = "wp:comment_user_id")
|
||||
private String commentUserId;
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
package run.halo.app.handler.migrate.support.wordpress;
|
||||
|
||||
import com.alibaba.fastjson.annotation.JSONField;
|
||||
import lombok.Data;
|
||||
import run.halo.app.handler.migrate.utils.PropertyMappingTo;
|
||||
import run.halo.app.model.entity.BasePost;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <p> WordPress导出的xml中对于的item子节点的值将会被映射到该类的属性上,最终被解析为文章属性{@link BasePost} </p>
|
||||
*
|
||||
* @author guqing
|
||||
* @date 2019-11-17 13:59
|
||||
*/
|
||||
@Data
|
||||
public class Item {
|
||||
private String title;
|
||||
|
||||
private String link;
|
||||
|
||||
private String pubDate;
|
||||
|
||||
@JSONField(name = "post_date")
|
||||
private String postDate;
|
||||
|
||||
@JSONField(name = "dc:creator")
|
||||
private String creator;
|
||||
|
||||
private String description;
|
||||
|
||||
@JSONField(name = "content:encoded")
|
||||
@PropertyMappingTo("formatContent")
|
||||
private String content;
|
||||
|
||||
@JSONField(name = "wp:comment_status")
|
||||
private String commentStatus;
|
||||
|
||||
@JSONField(name = "wp:status")
|
||||
private String status;
|
||||
|
||||
@JSONField(name = "wp:post_password")
|
||||
@PropertyMappingTo("password")
|
||||
private String postPassword;
|
||||
|
||||
@JSONField(name = "wp:is_sticky")
|
||||
private Integer isSticky;
|
||||
|
||||
@JSONField(name = "wp:comment")
|
||||
private List<Comment> comments;
|
||||
|
||||
@JSONField(name = "category")
|
||||
private List<WpCategory> categories;
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package run.halo.app.handler.migrate.support.wordpress;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* WordPress导出的xml数据中对应的rss节点下的子节点channel将会被映射到该类属性
|
||||
* 如果不写这个Rss类包装想要的Channel,会无法解析到想要的结果
|
||||
*
|
||||
* @author guqing
|
||||
* @date 2020-01-17 00:45
|
||||
*/
|
||||
@Data
|
||||
public class Rss {
|
||||
private Channel channel;
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package run.halo.app.handler.migrate.support.wordpress;
|
||||
|
||||
import lombok.Data;
|
||||
import run.halo.app.handler.migrate.utils.PropertyMappingTo;
|
||||
import run.halo.app.model.entity.PostCategory;
|
||||
|
||||
/**
|
||||
* WordPress导出的xml数据中对应的category的子节点将会被映射到该类属性,
|
||||
* 最终会被转换为文章分类{@link PostCategory}
|
||||
*
|
||||
* @author guqing
|
||||
* @date 2020-01-18 16:09
|
||||
*/
|
||||
@Data
|
||||
public class WpCategory {
|
||||
private String domain;
|
||||
|
||||
@PropertyMappingTo("slugName")
|
||||
private String nicename;
|
||||
|
||||
@PropertyMappingTo("name")
|
||||
private String content;
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package run.halo.app.handler.migrate.utils;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 该注解用于定义两个对象之间的属性映射关系
|
||||
* @author guqing
|
||||
* @date 2020-1-19 13:51
|
||||
*/
|
||||
@Target({ElementType.FIELD, ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface PropertyMappingTo {
|
||||
/**
|
||||
* value对应的是目标对象的属性名称
|
||||
* @return 返回源对象属性对应的目标对象的属性名
|
||||
*/
|
||||
String value() default "";
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
package run.halo.app.handler.migrate.utils;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 关系映射工具类,用于使用@PropertyMappingTo注解映射源对象与目标对象属性之间的关系
|
||||
*
|
||||
* @author guqing
|
||||
* @date 2020-01-19 01:47
|
||||
*/
|
||||
public class RelationMapperUtils {
|
||||
private RelationMapperUtils() {
|
||||
}
|
||||
|
||||
public static <SOURCE, TARGET> TARGET convertFrom(SOURCE source, Class<TARGET> targetClazz) {
|
||||
Map<String, Class> methodNameAndTypeMap = new HashMap<>(16);
|
||||
TARGET target;
|
||||
try {
|
||||
// 通过类的详情信息,创建目标对象 这一步等同于Hello target = new Hello();
|
||||
target = targetClazz.getConstructor().newInstance();
|
||||
Field[] targetFields = targetClazz.getDeclaredFields();
|
||||
for (Field field : targetFields) {
|
||||
methodNameAndTypeMap.put(field.getName(), field.getType());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("目标对象创建失败");
|
||||
}
|
||||
|
||||
return propertyMapper(source, target, methodNameAndTypeMap);
|
||||
}
|
||||
|
||||
private static <SOURCE, TARGET> TARGET propertyMapper(SOURCE source, TARGET target, Map<String, Class> methodNameAndTypeMap) {
|
||||
// 判断传入源数据是否为空,如果空,则抛自定义异常
|
||||
if (null == source) {
|
||||
throw new IllegalArgumentException("数据源不能为空");
|
||||
}
|
||||
// 获取源对象的类的详情信息
|
||||
Class<?> sourceClazz = source.getClass();
|
||||
// 获取源对象的所有属性
|
||||
Field[] sourceFields = sourceClazz.getDeclaredFields();
|
||||
|
||||
// 循环取到源对象的单个属性
|
||||
for (Field sourceField : sourceFields) {
|
||||
boolean sourceFieldHasAnnotation = sourceField.isAnnotationPresent(PropertyMappingTo.class);
|
||||
|
||||
String sourceFieldName = sourceField.getName();
|
||||
// 如果源对象没有添加注解则默认使用源对象属性名去寻找对于的目标对象属性名
|
||||
if (sourceFieldHasAnnotation) {
|
||||
PropertyMappingTo propertyMapping = sourceField.getAnnotation(PropertyMappingTo.class);
|
||||
String targetFieldName = propertyMapping.value();
|
||||
copyProperty(sourceFieldName, targetFieldName, source, target, sourceField.getType(), methodNameAndTypeMap);
|
||||
} else if (methodNameAndTypeMap.containsKey(sourceFieldName)) {
|
||||
// 如果源对象和目标对象的属性名相同则直接设置
|
||||
// 获取目标对象的属性名,将属性名首字母大写,拼接如:setUsername、setId的字符串
|
||||
// 判断源对象的属性名、属性类型是否和目标对象的属性名、属性类型一致
|
||||
copyProperty(sourceFieldName, sourceFieldName, source, target, sourceField.getType(), methodNameAndTypeMap);
|
||||
}
|
||||
}
|
||||
// 返回赋值得到的目标对象
|
||||
return target;
|
||||
}
|
||||
|
||||
private static <SOURCE, TARGET> void copyProperty(String sourceFieldName, String targetFieldName,
|
||||
SOURCE source, TARGET target, Class<?> sourceType,
|
||||
Map<String, Class> methodNameAndTypeMap) {
|
||||
try {
|
||||
Class<?> sourceClazz = source.getClass();
|
||||
Class<?> targetClazz = target.getClass();
|
||||
// 获取源对象的属性名,将属性名首字母大写,拼接如:getUsername、getId的字符串
|
||||
String sourceGetMethodName = getterName(sourceFieldName);
|
||||
// 获得属性的get方法
|
||||
Method sourceMethod = sourceClazz.getMethod(sourceGetMethodName);
|
||||
// 调用get方法
|
||||
Object sourceFieldValue = sourceMethod.invoke(source);
|
||||
|
||||
Class methodType = methodNameAndTypeMap.get(targetFieldName);
|
||||
// 通过字段名称得到set方法名称
|
||||
String targetSetMethodName = setterName(targetFieldName);
|
||||
if (methodType == sourceType) {
|
||||
// 调用方法,并将源对象get方法返回值作为参数传入
|
||||
Method targetSetMethod = targetClazz.getMethod(targetSetMethodName, sourceType);
|
||||
targetSetMethod.invoke(target, sourceFieldValue);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException("转换失败,请检查属性类型是否匹配");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过属性名称得到对于的get或set方法
|
||||
*
|
||||
* @param fieldName 属性名称
|
||||
* @param methodType 方法类型可选值为:get或set
|
||||
* @return 返回对象的get或set方法的名称
|
||||
*/
|
||||
private static String getMethodNameFromField(String fieldName, String methodType) {
|
||||
if (fieldName == null || fieldName.length() == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* If the second char is upper, make 'get' + field name as getter name. For example, eBlog -> getBlog
|
||||
*/
|
||||
if (fieldName.length() > 2) {
|
||||
String second = fieldName.substring(1, 2);
|
||||
if (second.equals(second.toUpperCase())) {
|
||||
return methodType + fieldName;
|
||||
}
|
||||
}
|
||||
|
||||
// Common situation
|
||||
fieldName = methodType + fieldName.substring(0, 1).toUpperCase() +
|
||||
fieldName.substring(1);
|
||||
return fieldName;
|
||||
}
|
||||
|
||||
private static String getterName(String fieldName) {
|
||||
return getMethodNameFromField(fieldName, "get");
|
||||
}
|
||||
|
||||
private static String setterName(String fieldName) {
|
||||
return getMethodNameFromField(fieldName, "set");
|
||||
}
|
||||
}
|
|
@ -9,13 +9,10 @@ import java.io.FileInputStream;
|
|||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 解析 WordPress 导出的 XML 数据为 Map 结果集
|
||||
*
|
||||
* @author guqing
|
||||
* @date 2019-10-29 14:49
|
||||
* @date 2020-01-18 14:06
|
||||
*/
|
||||
public class WordPressMigrateUtils {
|
||||
|
||||
/**
|
||||
* 存储在此集合中的节点名称都会被解析为一个List存储
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
package run.halo.app.utils;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.json.XML;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* <p> 将xml解析为json </p>
|
||||
*
|
||||
* @author guqing
|
||||
* @date 2019-11-17 13:33
|
||||
*/
|
||||
public class XmlMigrateUtils {
|
||||
public static String xml2jsonString(FileInputStream in) throws JSONException, IOException {
|
||||
String xml = IOUtils.toString(in, "UTF-8");
|
||||
JSONObject jsonObject = XML.toJSONObject(xml);
|
||||
return jsonObject.toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package run.halo.app.utils;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.util.ResourceUtils;
|
||||
import run.halo.app.handler.migrate.converter.Converter;
|
||||
import run.halo.app.handler.migrate.converter.WordPressConverter;
|
||||
import run.halo.app.handler.migrate.support.vo.PostVO;
|
||||
import run.halo.app.handler.migrate.support.wordpress.Rss;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author guqing
|
||||
* @date 2020-01-18 14:02
|
||||
*/
|
||||
public class XmlMigrateUtilsTest {
|
||||
|
||||
@Test
|
||||
void testXmlMigrateUtils() throws IOException, URISyntaxException {
|
||||
JSONObject json = readXml();
|
||||
System.out.println("WordPress blog json data:" + json);
|
||||
Rss rss = json.getObject("rss", Rss.class);
|
||||
// System.out.println(rss);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testWordPressConvert() throws IOException, URISyntaxException {
|
||||
JSONObject json = readXml();
|
||||
Rss rss = json.getObject("rss", Rss.class);
|
||||
// System.out.println("WordPress blog rss data:" + rss);
|
||||
Converter<Rss, List<PostVO>> converter = new WordPressConverter();
|
||||
List<PostVO> postVoList = converter.convertFrom(rss);
|
||||
|
||||
System.out.println(postVoList);
|
||||
}
|
||||
|
||||
private JSONObject readXml() throws IOException, URISyntaxException {
|
||||
URL migrationUrl = ResourceUtils.getURL(ResourceUtils.CLASSPATH_URL_PREFIX + "wordpressdemo.xml");
|
||||
URI wordpressUrl = migrationUrl.toURI();
|
||||
File file = new File(wordpressUrl);
|
||||
FileInputStream inputStream = new FileInputStream(file);
|
||||
String s = XmlMigrateUtils.xml2jsonString(inputStream);
|
||||
return JSONObject.parseObject(s);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<rss version="2.0"
|
||||
xmlns:excerpt="http://wordpress.org/export/1.2/excerpt/"
|
||||
xmlns:content="http://purl.org/rss/1.0/modules/content/"
|
||||
xmlns:wfw="http://wellformedweb.org/CommentAPI/"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:wp="http://wordpress.org/export/1.2/"
|
||||
>
|
||||
<channel>
|
||||
<title>WordPress Demo</title>
|
||||
<link>http://148.70.10.235</link>
|
||||
<description>又一个WordPress站点</description>
|
||||
<pubDate>Mon, 08 Jul 2019 07:27:10 +0000</pubDate>
|
||||
<language>zh-CN</language>
|
||||
<wp:wxr_version>1.2</wp:wxr_version>
|
||||
<wp:base_site_url>http://148.70.10.235</wp:base_site_url>
|
||||
<wp:base_bloge_url>http://148.70.10.235</wp:base_bloge_url>
|
||||
|
||||
<wp:author>
|
||||
<wp:author_id>1</wp:author_id>
|
||||
<wp:author_login><![CDATA[admin]]></wp:author_login>
|
||||
<wp:author_email><![CDATA[i@ryanc.cc]]></wp:author_email>
|
||||
<wp:author_display_name><![CDATA[admin]]></wp:author_display_name>
|
||||
<wp:author_first_name><![CDATA[]]></wp:author_first_name>
|
||||
<wp:author_last_name><![CDATA[]]></wp:author_last_name>
|
||||
</wp:author>
|
||||
|
||||
|
||||
<generator>https://wordpress.org/?v=4.8</generator>
|
||||
|
||||
<item>
|
||||
<title>世界,您好!</title>
|
||||
<link>http://148.70.10.235/index.php/2019/07/08/hello-world/</link>
|
||||
<pubDate>Mon, 08 Jul 2019 03:10:55 +0000</pubDate>
|
||||
<dc:creator><![CDATA[admin]]></dc:creator>
|
||||
<guid isPermaLink="false">http://148.70.10.235/?p=1</guid>
|
||||
<description>123123131</description>
|
||||
<content:encoded><![CDATA[欢迎使用WordPress。这是您的第一篇文章。编辑或删除它,然后开始写作吧!
|
||||
<h2>hello二级标题</h2>
|
||||
<ul>
|
||||
<li>list1</li>
|
||||
<li>list2</li>
|
||||
<li>list3</li>
|
||||
</ul>
|
||||
<h3>hello 三级标题</h3>
|
||||
|
||||
<blockquote>daidjoadoiajdoijo这是引用
|
||||
|
||||
</blockquote>]]></content:encoded>
|
||||
<excerpt:encoded><![CDATA[]]></excerpt:encoded>
|
||||
<wp:post_id>1</wp:post_id>
|
||||
<wp:post_date><![CDATA[2019-07-08 11:10:55]]></wp:post_date>
|
||||
<wp:post_date_gmt><![CDATA[2019-07-08 03:10:55]]></wp:post_date_gmt>
|
||||
<wp:comment_status><![CDATA[open]]></wp:comment_status>
|
||||
<wp:ping_status><![CDATA[open]]></wp:ping_status>
|
||||
<wp:post_name><![CDATA[hello-world]]></wp:post_name>
|
||||
<wp:status><![CDATA[publish]]></wp:status>
|
||||
<wp:post_parent>0</wp:post_parent>
|
||||
<wp:menu_order>0</wp:menu_order>
|
||||
<wp:post_type><![CDATA[post]]></wp:post_type>
|
||||
<wp:post_password><![CDATA[]]></wp:post_password>
|
||||
<wp:is_sticky>0</wp:is_sticky>
|
||||
<category domain="post_format" nicename="post-format-aside"><![CDATA[日志]]></category>
|
||||
<category domain="post_tag" nicename="%e6%a0%87%e7%ad%be1"><![CDATA[标签1]]></category>
|
||||
<category domain="post_tag" nicename="%e6%a0%87%e7%ad%be2"><![CDATA[标签2]]></category>
|
||||
<category domain="category" nicename="%e8%bf%99%e6%98%af%e5%88%86%e7%b1%bb"><![CDATA[这是分类]]></category>
|
||||
<wp:postmeta>
|
||||
<wp:meta_key><![CDATA[_edit_last]]></wp:meta_key>
|
||||
<wp:meta_value><![CDATA[1]]></wp:meta_value>
|
||||
</wp:postmeta>
|
||||
<wp:postmeta>
|
||||
<wp:meta_key><![CDATA[_thumbnail_id]]></wp:meta_key>
|
||||
<wp:meta_value><![CDATA[9]]></wp:meta_value>
|
||||
</wp:postmeta>
|
||||
<wp:comment>
|
||||
<wp:comment_id>1</wp:comment_id>
|
||||
<wp:comment_author><![CDATA[一位WordPress评论者]]></wp:comment_author>
|
||||
<wp:comment_author_email><![CDATA[wapuu@wordpress.example]]></wp:comment_author_email>
|
||||
<wp:comment_author_url>https://wordpress.org/</wp:comment_author_url>
|
||||
<wp:comment_author_IP><![CDATA[]]></wp:comment_author_IP>
|
||||
<wp:comment_date><![CDATA[2019-07-08 11:10:55]]></wp:comment_date>
|
||||
<wp:comment_date_gmt><![CDATA[2019-07-08 03:10:55]]></wp:comment_date_gmt>
|
||||
<wp:comment_content><![CDATA[嗨,这是一条评论。
|
||||
要开始审核、编辑及删除评论,请访问仪表盘的“评论”页面。
|
||||
评论者头像来自<a href="https://gravatar.com">Gravatar</a>。]]></wp:comment_content>
|
||||
<wp:comment_approved><![CDATA[1]]></wp:comment_approved>
|
||||
<wp:comment_type><![CDATA[]]></wp:comment_type>
|
||||
<wp:comment_parent>0</wp:comment_parent>
|
||||
<wp:comment_user_id>0</wp:comment_user_id>
|
||||
</wp:comment>
|
||||
<wp:comment>
|
||||
<wp:comment_id>2</wp:comment_id>
|
||||
<wp:comment_author><![CDATA[admin]]></wp:comment_author>
|
||||
<wp:comment_author_email><![CDATA[i@ryanc.cc]]></wp:comment_author_email>
|
||||
<wp:comment_author_url></wp:comment_author_url>
|
||||
<wp:comment_author_IP><![CDATA[60.160.204.183]]></wp:comment_author_IP>
|
||||
<wp:comment_date><![CDATA[2019-07-08 15:26:57]]></wp:comment_date>
|
||||
<wp:comment_date_gmt><![CDATA[2019-07-08 07:26:57]]></wp:comment_date_gmt>
|
||||
<wp:comment_content><![CDATA[这是评论回复]]></wp:comment_content>
|
||||
<wp:comment_approved><![CDATA[1]]></wp:comment_approved>
|
||||
<wp:comment_type><![CDATA[]]></wp:comment_type>
|
||||
<wp:comment_parent>1</wp:comment_parent>
|
||||
<wp:comment_user_id>1</wp:comment_user_id>
|
||||
</wp:comment>
|
||||
</item>
|
||||
<item>
|
||||
<title>这是文章2222</title>
|
||||
<link>http://148.70.10.235/index.php/2019/07/08/%e8%bf%99%e6%98%af%e6%96%87%e7%ab%a02222/</link>
|
||||
<pubDate>Mon, 08 Jul 2019 07:20:18 +0000</pubDate>
|
||||
<dc:creator><![CDATA[admin]]></dc:creator>
|
||||
<guid isPermaLink="false">http://148.70.10.235/?p=6</guid>
|
||||
<description>dadadadada</description>
|
||||
<content:encoded><![CDATA[<h3>三级标题</h3>
|
||||
<ul>
|
||||
<li>列表</li>
|
||||
<li>list2</li>
|
||||
<li>list3</li>
|
||||
</ul>
|
||||
]]></content:encoded>
|
||||
<excerpt:encoded><![CDATA[]]></excerpt:encoded>
|
||||
<wp:post_id>6</wp:post_id>
|
||||
<wp:post_date><![CDATA[2019-07-08 15:20:18]]></wp:post_date><!-- 要这个日期 -->
|
||||
<wp:post_date_gmt><![CDATA[2019-07-08 07:20:18]]></wp:post_date_gmt>
|
||||
<wp:comment_status><![CDATA[open]]></wp:comment_status>
|
||||
<wp:ping_status><![CDATA[open]]></wp:ping_status>
|
||||
<wp:post_name><![CDATA[%e8%bf%99%e6%98%af%e6%96%87%e7%ab%a02222]]></wp:post_name>
|
||||
<wp:status><![CDATA[publish]]></wp:status>
|
||||
<wp:post_parent>0</wp:post_parent>
|
||||
<wp:menu_order>0</wp:menu_order>
|
||||
<wp:post_type><![CDATA[post]]></wp:post_type>
|
||||
<wp:post_password><![CDATA[]]></wp:post_password>
|
||||
<wp:is_sticky>0</wp:is_sticky>
|
||||
<category domain="post_tag" nicename="hello"><![CDATA[Hello]]></category>
|
||||
<category domain="category" nicename="hello%e6%96%87%e7%ab%a0"><![CDATA[Hello文章]]></category>
|
||||
<category domain="post_tag" nicename="java"><![CDATA[Java]]></category>
|
||||
<category domain="post_tag" nicename="test"><![CDATA[Test]]></category>
|
||||
<category domain="category" nicename="test%e6%96%87%e7%ab%a0"><![CDATA[Test文章]]></category>
|
||||
<wp:postmeta>
|
||||
<wp:meta_key><![CDATA[_edit_last]]></wp:meta_key>
|
||||
<wp:meta_value><![CDATA[1]]></wp:meta_value>
|
||||
</wp:postmeta>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
Loading…
Reference in New Issue