diff --git a/src/main/java/run/halo/app/model/params/BaseMetaParam.java b/src/main/java/run/halo/app/model/params/BaseMetaParam.java
new file mode 100644
index 000000000..e0ffb6401
--- /dev/null
+++ b/src/main/java/run/halo/app/model/params/BaseMetaParam.java
@@ -0,0 +1,36 @@
+package run.halo.app.model.params;
+
+import lombok.Data;
+import run.halo.app.model.dto.base.InputConverter;
+import run.halo.app.utils.ReflectionUtils;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Size;
+import java.lang.reflect.ParameterizedType;
+
+/**
+ * Base meta param.
+ *
+ * @author ryanwang
+ * @author ikaisec
+ * @date 2019-08-04
+ */
+@Data
+public abstract class BaseMetaParam implements InputConverter {
+
+ @NotBlank(message = "文章id不能为空")
+ private Integer postId;
+
+ @NotBlank(message = "Meta key 不能为空")
+ @Size(max = 1023, message = "Meta key 的字符长度不能超过 {max}")
+ private String key;
+
+ @NotBlank(message = "Meta value 不能为空")
+ @Size(max = 1023, message = "Meta value 的字符长度不能超过 {max}")
+ private String value;
+
+ @Override
+ public ParameterizedType parameterizedType() {
+ return ReflectionUtils.getParameterizedTypeBySuperClass(BaseMetaParam.class, this.getClass());
+ }
+}
diff --git a/src/main/java/run/halo/app/model/params/PostMetaParam.java b/src/main/java/run/halo/app/model/params/PostMetaParam.java
new file mode 100644
index 000000000..49d3d9c2b
--- /dev/null
+++ b/src/main/java/run/halo/app/model/params/PostMetaParam.java
@@ -0,0 +1,19 @@
+package run.halo.app.model.params;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import run.halo.app.model.entity.PostMeta;
+
+/**
+ * Post meta param.
+ *
+ * @author ryanwang
+ * @author ikaisec
+ * @date 2019-08-04
+ */
+@Data
+@ToString(callSuper = true)
+@EqualsAndHashCode(callSuper = true)
+public class PostMetaParam extends BaseMetaParam {
+}
diff --git a/src/main/java/run/halo/app/model/params/SheetMetaParam.java b/src/main/java/run/halo/app/model/params/SheetMetaParam.java
new file mode 100644
index 000000000..b32b1879b
--- /dev/null
+++ b/src/main/java/run/halo/app/model/params/SheetMetaParam.java
@@ -0,0 +1,19 @@
+package run.halo.app.model.params;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import run.halo.app.model.entity.SheetMeta;
+
+/**
+ * Sheet meta param.
+ *
+ * @author ryanwang
+ * @author ikaisec
+ * @date 2019-08-04
+ */
+@Data
+@ToString(callSuper = true)
+@EqualsAndHashCode(callSuper = true)
+public class SheetMetaParam extends BaseMetaParam {
+}
diff --git a/src/main/java/run/halo/app/repository/PostMetaRepository.java b/src/main/java/run/halo/app/repository/PostMetaRepository.java
new file mode 100644
index 000000000..e2bc2acbe
--- /dev/null
+++ b/src/main/java/run/halo/app/repository/PostMetaRepository.java
@@ -0,0 +1,14 @@
+package run.halo.app.repository;
+
+import run.halo.app.model.entity.PostMeta;
+import run.halo.app.repository.base.BaseMetaRepository;
+
+/**
+ * PostMeta repository.
+ *
+ * @author ryanwang
+ * @author ikaisec
+ * @date 2019-08-04
+ */
+public interface PostMetaRepository extends BaseMetaRepository {
+}
diff --git a/src/main/java/run/halo/app/repository/SheetMetaRepository.java b/src/main/java/run/halo/app/repository/SheetMetaRepository.java
new file mode 100644
index 000000000..4a6b7d98b
--- /dev/null
+++ b/src/main/java/run/halo/app/repository/SheetMetaRepository.java
@@ -0,0 +1,14 @@
+package run.halo.app.repository;
+
+import run.halo.app.model.entity.SheetMeta;
+import run.halo.app.repository.base.BaseMetaRepository;
+
+/**
+ * SheetMeta repository.
+ *
+ * @author ryanwang
+ * @author ikaisec
+ * @date 2019-08-04
+ */
+public interface SheetMetaRepository extends BaseMetaRepository {
+}
diff --git a/src/main/java/run/halo/app/repository/base/BaseMetaRepository.java b/src/main/java/run/halo/app/repository/base/BaseMetaRepository.java
new file mode 100644
index 000000000..297ad38f7
--- /dev/null
+++ b/src/main/java/run/halo/app/repository/base/BaseMetaRepository.java
@@ -0,0 +1,28 @@
+package run.halo.app.repository.base;
+
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.repository.NoRepositoryBean;
+import org.springframework.lang.NonNull;
+import run.halo.app.model.entity.BaseMeta;
+
+import java.util.List;
+
+/**
+ * Base meta repository.
+ *
+ * @author ryanwang
+ * @author ikaisec
+ * @date 2019-08-04
+ */
+@NoRepositoryBean
+public interface BaseMetaRepository extends BaseRepository, JpaSpecificationExecutor {
+
+ /**
+ * Finds all metas by post id.
+ *
+ * @param postId post id must not be null
+ * @return a list of meta
+ */
+ @NonNull
+ List findAllByPostId(@NonNull Integer postId);
+}
diff --git a/src/main/java/run/halo/app/service/PostMetaService.java b/src/main/java/run/halo/app/service/PostMetaService.java
new file mode 100644
index 000000000..5f1930c01
--- /dev/null
+++ b/src/main/java/run/halo/app/service/PostMetaService.java
@@ -0,0 +1,14 @@
+package run.halo.app.service;
+
+import run.halo.app.model.entity.PostMeta;
+import run.halo.app.service.base.BaseMetaService;
+
+/**
+ * Post meta service interface.
+ *
+ * @author ryanwang
+ * @author ikaisec
+ * @date 2019-08-04
+ */
+public interface PostMetaService extends BaseMetaService {
+}
diff --git a/src/main/java/run/halo/app/service/SheetMetaService.java b/src/main/java/run/halo/app/service/SheetMetaService.java
new file mode 100644
index 000000000..e78ca6f94
--- /dev/null
+++ b/src/main/java/run/halo/app/service/SheetMetaService.java
@@ -0,0 +1,14 @@
+package run.halo.app.service;
+
+import run.halo.app.model.entity.SheetMeta;
+import run.halo.app.service.base.BaseMetaService;
+
+/**
+ * Sheet meta service interface.
+ *
+ * @author ryanwang
+ * @author ikaisec
+ * @date 2019-08-04
+ */
+public interface SheetMetaService extends BaseMetaService {
+}
diff --git a/src/main/java/run/halo/app/service/base/BaseMetaService.java b/src/main/java/run/halo/app/service/base/BaseMetaService.java
new file mode 100644
index 000000000..a368f2b5a
--- /dev/null
+++ b/src/main/java/run/halo/app/service/base/BaseMetaService.java
@@ -0,0 +1,62 @@
+package run.halo.app.service.base;
+
+import org.springframework.lang.NonNull;
+import run.halo.app.model.entity.BaseMeta;
+import run.halo.app.model.params.BaseMetaParam;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Base meta service interface.
+ *
+ * @author ryanwang
+ * @author ikaisec
+ * @date 2019-08-04
+ */
+public interface BaseMetaService extends CrudService {
+
+ /**
+ * Lists metas by post id.
+ *
+ * @param postId post id must not be null
+ * @return a list of meta
+ */
+ @NonNull
+ List listBy(@NonNull Integer postId);
+
+
+ /**
+ * Creates a meta by meta.
+ *
+ * @param meta meta must not be null
+ * @return created meta
+ */
+ @NonNull
+ @Override
+ META create(@NonNull META meta);
+
+ /**
+ * Creates a meta by meta param.
+ *
+ * @param metaParam meta param must not be null
+ * @return created meta
+ */
+ @NonNull
+ META createBy(@NonNull BaseMetaParam metaParam);
+
+ /**
+ * Target validation.
+ *
+ * @param targetId target id must not be null (post id, sheet id)
+ */
+ void validateTarget(@NonNull Integer targetId);
+
+ /**
+ * Convert to map.
+ *
+ * @param metas a list of metas
+ * @return a map of metas
+ */
+ Map convertToMap(List metas);
+}
diff --git a/src/main/java/run/halo/app/service/impl/BaseMetaServiceImpl.java b/src/main/java/run/halo/app/service/impl/BaseMetaServiceImpl.java
new file mode 100644
index 000000000..9ea1c9b7e
--- /dev/null
+++ b/src/main/java/run/halo/app/service/impl/BaseMetaServiceImpl.java
@@ -0,0 +1,72 @@
+package run.halo.app.service.impl;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.util.Assert;
+import org.springframework.util.CollectionUtils;
+import run.halo.app.model.entity.BaseMeta;
+import run.halo.app.model.params.BaseMetaParam;
+import run.halo.app.repository.base.BaseMetaRepository;
+import run.halo.app.service.base.AbstractCrudService;
+import run.halo.app.service.base.BaseMetaService;
+import run.halo.app.utils.ServiceUtils;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Base meta service implementation.
+ *
+ * @author ryanwang
+ * @author ikaisec
+ * @date 2019-08-04
+ */
+@Slf4j
+public abstract class BaseMetaServiceImpl extends AbstractCrudService implements BaseMetaService {
+
+ private final BaseMetaRepository baseMetaRepository;
+
+
+ public BaseMetaServiceImpl(BaseMetaRepository baseMetaRepository) {
+ super(baseMetaRepository);
+ this.baseMetaRepository = baseMetaRepository;
+ }
+
+ @Override
+ public List listBy(Integer postId) {
+ Assert.notNull(postId, "Post id must not be null");
+ return baseMetaRepository.findAllByPostId(postId);
+ }
+
+ @Override
+ public META create(META meta) {
+ Assert.notNull(meta, "Domain must not be null");
+
+ // Check post id
+ if (!ServiceUtils.isEmptyId(meta.getPostId())) {
+ validateTarget(meta.getPostId());
+ }
+
+ // Create meta
+ return super.create(meta);
+ }
+
+ @Override
+ public META createBy(BaseMetaParam metaParam) {
+ Assert.notNull(metaParam, "Meta param must not be null");
+ return create(metaParam.convertTo());
+ }
+
+ @Override
+ public Map convertToMap(List metas) {
+ if (CollectionUtils.isEmpty(metas)) {
+ return Collections.emptyMap();
+ }
+
+ Map result = new HashMap<>();
+ metas.forEach(meta -> result.put(meta.getKey(), meta.getValue()));
+
+ return result;
+ }
+}
diff --git a/src/main/java/run/halo/app/service/impl/PostMetaServiceImpl.java b/src/main/java/run/halo/app/service/impl/PostMetaServiceImpl.java
new file mode 100644
index 000000000..7c8c8d8e2
--- /dev/null
+++ b/src/main/java/run/halo/app/service/impl/PostMetaServiceImpl.java
@@ -0,0 +1,38 @@
+package run.halo.app.service.impl;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import run.halo.app.exception.NotFoundException;
+import run.halo.app.model.entity.PostMeta;
+import run.halo.app.repository.PostMetaRepository;
+import run.halo.app.repository.PostRepository;
+import run.halo.app.service.PostMetaService;
+
+/**
+ * Post meta service implementation class.
+ *
+ * @author ryanwang
+ * @author ikaisec
+ * @date 2019-08-04
+ */
+@Slf4j
+@Service
+public class PostMetaServiceImpl extends BaseMetaServiceImpl implements PostMetaService {
+
+ private final PostMetaRepository postMetaRepository;
+
+ private final PostRepository postRepository;
+
+ public PostMetaServiceImpl(PostMetaRepository postMetaRepository,
+ PostRepository postRepository) {
+ super(postMetaRepository);
+ this.postMetaRepository = postMetaRepository;
+ this.postRepository = postRepository;
+ }
+
+ @Override
+ public void validateTarget(Integer postId) {
+ postRepository.findById(postId)
+ .orElseThrow(() -> new NotFoundException("该文章不存在或已删除").setErrorData(postId));
+ }
+}
diff --git a/src/main/java/run/halo/app/service/impl/SheetMetaServiceImpl.java b/src/main/java/run/halo/app/service/impl/SheetMetaServiceImpl.java
new file mode 100644
index 000000000..4b0f52d70
--- /dev/null
+++ b/src/main/java/run/halo/app/service/impl/SheetMetaServiceImpl.java
@@ -0,0 +1,38 @@
+package run.halo.app.service.impl;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import run.halo.app.exception.NotFoundException;
+import run.halo.app.model.entity.SheetMeta;
+import run.halo.app.repository.SheetMetaRepository;
+import run.halo.app.repository.SheetRepository;
+import run.halo.app.service.SheetMetaService;
+
+/**
+ * Sheet meta service implementation class.
+ *
+ * @author ryanwang
+ * @author ikaisec
+ * @date 2019-08-04
+ */
+@Slf4j
+@Service
+public class SheetMetaServiceImpl extends BaseMetaServiceImpl implements SheetMetaService {
+
+ private final SheetMetaRepository sheetMetaRepository;
+
+ private final SheetRepository sheetRepository;
+
+ public SheetMetaServiceImpl(SheetMetaRepository sheetMetaRepository,
+ SheetRepository sheetRepository) {
+ super(sheetMetaRepository);
+ this.sheetMetaRepository = sheetMetaRepository;
+ this.sheetRepository = sheetRepository;
+ }
+
+ @Override
+ public void validateTarget(Integer sheetId) {
+ sheetRepository.findById(sheetId)
+ .orElseThrow(() -> new NotFoundException("该页面不存在或已删除").setErrorData(sheetId));
+ }
+}