feat: support meta_keywords and meta_description in every page. (#612)

pull/619/head
Ryan Wang 2020-03-04 20:03:08 +08:00 committed by GitHub
parent a9fedef8b2
commit d82f3847c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 186 additions and 94 deletions

View File

@ -45,6 +45,8 @@ public class ContentContentController {
private final PhotoModel photoModel; private final PhotoModel photoModel;
private final LinkModel linkModel;
private final OptionService optionService; private final OptionService optionService;
private final PostService postService; private final PostService postService;
@ -61,6 +63,7 @@ public class ContentContentController {
TagModel tagModel, TagModel tagModel,
JournalModel journalModel, JournalModel journalModel,
PhotoModel photoModel, PhotoModel photoModel,
LinkModel linkModel,
OptionService optionService, OptionService optionService,
PostService postService, PostService postService,
SheetService sheetService, SheetService sheetService,
@ -72,6 +75,7 @@ public class ContentContentController {
this.tagModel = tagModel; this.tagModel = tagModel;
this.journalModel = journalModel; this.journalModel = journalModel;
this.photoModel = photoModel; this.photoModel = photoModel;
this.linkModel = linkModel;
this.optionService = optionService; this.optionService = optionService;
this.postService = postService; this.postService = postService;
this.sheetService = sheetService; this.sheetService = sheetService;
@ -93,8 +97,7 @@ public class ContentContentController {
} else if (optionService.getPhotosPrefix().equals(prefix)) { } else if (optionService.getPhotosPrefix().equals(prefix)) {
return photoModel.list(1, model); return photoModel.list(1, model);
} else if (optionService.getLinksPrefix().equals(prefix)) { } else if (optionService.getLinksPrefix().equals(prefix)) {
model.addAttribute("is_links", true); return linkModel.list(model);
return themeService.render("links");
} else { } else {
throw new NotFoundException("Not Found"); throw new NotFoundException("Not Found");
} }

View File

@ -116,6 +116,8 @@ public class ContentSearchController {
model.addAttribute("rainbow", rainbow); model.addAttribute("rainbow", rainbow);
model.addAttribute("nextPageFullPath", nextPageFullPath.toString()); model.addAttribute("nextPageFullPath", nextPageFullPath.toString());
model.addAttribute("prePageFullPath", prePageFullPath.toString()); model.addAttribute("prePageFullPath", prePageFullPath.toString());
model.addAttribute("meta_keywords", optionService.getSeoKeywords());
model.addAttribute("meta_description", optionService.getSeoDescription());
return themeService.render("search"); return themeService.render("search");
} }
} }

View File

@ -43,11 +43,27 @@ public class CategoryModel {
this.optionService = optionService; this.optionService = optionService;
} }
/**
* List categories.
*
* @param model model
* @return template name
*/
public String list(Model model) { public String list(Model model) {
model.addAttribute("is_categories", true); model.addAttribute("is_categories", true);
model.addAttribute("meta_keywords", optionService.getSeoKeywords());
model.addAttribute("meta_description", optionService.getSeoDescription());
return themeService.render("categories"); return themeService.render("categories");
} }
/**
* List category posts.
*
* @param model model
* @param slug slug
* @param page current page
* @return template name
*/
public String listPost(Model model, String slug, Integer page) { public String listPost(Model model, String slug, Integer page) {
// Get category by slug // Get category by slug
final Category category = categoryService.getBySlugOfNonNull(slug); final Category category = categoryService.getBySlugOfNonNull(slug);
@ -82,6 +98,8 @@ public class CategoryModel {
model.addAttribute("category", categoryDTO); model.addAttribute("category", categoryDTO);
model.addAttribute("nextPageFullPath", nextPageFullPath.toString()); model.addAttribute("nextPageFullPath", nextPageFullPath.toString());
model.addAttribute("prePageFullPath", prePageFullPath.toString()); model.addAttribute("prePageFullPath", prePageFullPath.toString());
model.addAttribute("meta_keywords", optionService.getSeoKeywords());
model.addAttribute("meta_description", optionService.getSeoDescription());
return themeService.render("category"); return themeService.render("category");
} }
} }

View File

@ -82,6 +82,8 @@ public class JournalModel {
model.addAttribute("rainbow", rainbow); model.addAttribute("rainbow", rainbow);
model.addAttribute("nextPageFullPath", nextPageFullPath.toString()); model.addAttribute("nextPageFullPath", nextPageFullPath.toString());
model.addAttribute("prePageFullPath", prePageFullPath.toString()); model.addAttribute("prePageFullPath", prePageFullPath.toString());
model.addAttribute("meta_keywords", optionService.getSeoKeywords());
model.addAttribute("meta_description", optionService.getSeoDescription());
return themeService.render("journals"); return themeService.render("journals");
} }
} }

View File

@ -0,0 +1,31 @@
package run.halo.app.controller.content.model;
import org.springframework.stereotype.Component;
import org.springframework.ui.Model;
import run.halo.app.service.OptionService;
import run.halo.app.service.ThemeService;
/**
* @author ryanwang
* @date 2020-03-04
*/
@Component
public class LinkModel {
private final ThemeService themeService;
private final OptionService optionService;
public LinkModel(ThemeService themeService,
OptionService optionService) {
this.themeService = themeService;
this.optionService = optionService;
}
public String list(Model model) {
model.addAttribute("is_links", true);
model.addAttribute("meta_keywords", optionService.getSeoKeywords());
model.addAttribute("meta_description", optionService.getSeoDescription());
return themeService.render("links");
}
}

View File

@ -76,6 +76,8 @@ public class PhotoModel {
model.addAttribute("photos", photos); model.addAttribute("photos", photos);
model.addAttribute("nextPageFullPath", nextPageFullPath.toString()); model.addAttribute("nextPageFullPath", nextPageFullPath.toString());
model.addAttribute("prePageFullPath", prePageFullPath.toString()); model.addAttribute("prePageFullPath", prePageFullPath.toString());
model.addAttribute("meta_keywords", optionService.getSeoKeywords());
model.addAttribute("meta_description", optionService.getSeoDescription());
return themeService.render("photos"); return themeService.render("photos");
} }
} }

View File

@ -24,6 +24,7 @@ import run.halo.app.service.*;
import run.halo.app.utils.MarkdownUtils; import run.halo.app.utils.MarkdownUtils;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
/** /**
* Post Model * Post Model
@ -104,6 +105,20 @@ public class PostModel {
List<Tag> tags = postTagService.listTagsBy(post.getId()); List<Tag> tags = postTagService.listTagsBy(post.getId());
List<PostMeta> metas = postMetaService.listBy(post.getId()); List<PostMeta> metas = postMetaService.listBy(post.getId());
// Generate meta keywords.
if (StringUtils.isNotEmpty(post.getMetaKeywords())) {
model.addAttribute("meta_keywords", post.getMetaKeywords());
} else {
model.addAttribute("meta_keywords", tags.stream().map(Tag::getName).collect(Collectors.joining(",")));
}
// Generate meta description.
if (StringUtils.isNotEmpty(post.getMetaDescription())) {
model.addAttribute("meta_description", post.getMetaDescription());
} else {
model.addAttribute("meta_description", postService.generateDescription(post.getFormatContent()));
}
model.addAttribute("is_post", true); model.addAttribute("is_post", true);
model.addAttribute("post", postService.convertToDetailVo(post)); model.addAttribute("post", postService.convertToDetailVo(post));
model.addAttribute("categories", categoryService.convertTo(categories)); model.addAttribute("categories", categoryService.convertTo(categories));
@ -159,6 +174,8 @@ public class PostModel {
model.addAttribute("pageRainbow", rainbow); model.addAttribute("pageRainbow", rainbow);
model.addAttribute("nextPageFullPath", nextPageFullPath.toString()); model.addAttribute("nextPageFullPath", nextPageFullPath.toString());
model.addAttribute("prePageFullPath", prePageFullPath.toString()); model.addAttribute("prePageFullPath", prePageFullPath.toString());
model.addAttribute("meta_keywords", optionService.getSeoKeywords());
model.addAttribute("meta_description", optionService.getSeoDescription());
return themeService.render("index"); return themeService.render("index");
} }
@ -209,6 +226,8 @@ public class PostModel {
model.addAttribute("pageRainbow", rainbow); model.addAttribute("pageRainbow", rainbow);
model.addAttribute("nextPageFullPath", nextPageFullPath.toString()); model.addAttribute("nextPageFullPath", nextPageFullPath.toString());
model.addAttribute("prePageFullPath", prePageFullPath.toString()); model.addAttribute("prePageFullPath", prePageFullPath.toString());
model.addAttribute("meta_keywords", optionService.getSeoKeywords());
model.addAttribute("meta_description", optionService.getSeoDescription());
return themeService.render("archives"); return themeService.render("archives");
} }
} }

View File

@ -11,6 +11,7 @@ import run.halo.app.model.enums.PostEditorType;
import run.halo.app.model.enums.PostStatus; import run.halo.app.model.enums.PostStatus;
import run.halo.app.model.support.HaloConst; import run.halo.app.model.support.HaloConst;
import run.halo.app.model.vo.SheetDetailVO; import run.halo.app.model.vo.SheetDetailVO;
import run.halo.app.service.OptionService;
import run.halo.app.service.SheetService; import run.halo.app.service.SheetService;
import run.halo.app.service.ThemeService; import run.halo.app.service.ThemeService;
import run.halo.app.utils.MarkdownUtils; import run.halo.app.utils.MarkdownUtils;
@ -30,12 +31,23 @@ public class SheetModel {
private final ThemeService themeService; private final ThemeService themeService;
public SheetModel(SheetService sheetService, StringCacheStore cacheStore, ThemeService themeService) { private final OptionService optionService;
public SheetModel(SheetService sheetService, StringCacheStore cacheStore, ThemeService themeService, OptionService optionService) {
this.sheetService = sheetService; this.sheetService = sheetService;
this.cacheStore = cacheStore; this.cacheStore = cacheStore;
this.themeService = themeService; this.themeService = themeService;
this.optionService = optionService;
} }
/**
* Sheet content.
*
* @param sheet sheet
* @param token token
* @param model model
* @return template name
*/
public String content(Sheet sheet, String token, Model model) { public String content(Sheet sheet, String token, Model model) {
if (StringUtils.isEmpty(token)) { if (StringUtils.isEmpty(token)) {
@ -58,6 +70,20 @@ public class SheetModel {
SheetDetailVO sheetDetailVO = sheetService.convertToDetailVo(sheet); SheetDetailVO sheetDetailVO = sheetService.convertToDetailVo(sheet);
// Generate meta keywords.
if (StringUtils.isNotEmpty(sheet.getMetaKeywords())) {
model.addAttribute("meta_keywords", sheet.getMetaKeywords());
} else {
model.addAttribute("meta_keywords", optionService.getSeoKeywords());
}
// Generate meta description.
if (StringUtils.isNotEmpty(sheet.getMetaDescription())) {
model.addAttribute("meta_description", sheet.getMetaDescription());
} else {
model.addAttribute("meta_description", sheetService.generateDescription(sheet.getFormatContent()));
}
// sheet and post all can use // sheet and post all can use
model.addAttribute("sheet", sheetDetailVO); model.addAttribute("sheet", sheetDetailVO);
model.addAttribute("post", sheetDetailVO); model.addAttribute("post", sheetDetailVO);

View File

@ -45,6 +45,8 @@ public class TagModel {
public String list(Model model) { public String list(Model model) {
model.addAttribute("is_tags", true); model.addAttribute("is_tags", true);
model.addAttribute("meta_keywords", optionService.getSeoKeywords());
model.addAttribute("meta_description", optionService.getSeoDescription());
return themeService.render("tags"); return themeService.render("tags");
} }
@ -82,6 +84,8 @@ public class TagModel {
model.addAttribute("tag", tagDTO); model.addAttribute("tag", tagDTO);
model.addAttribute("nextPageFullPath", nextPageFullPath.toString()); model.addAttribute("nextPageFullPath", nextPageFullPath.toString());
model.addAttribute("prePageFullPath", prePageFullPath.toString()); model.addAttribute("prePageFullPath", prePageFullPath.toString());
model.addAttribute("meta_keywords", optionService.getSeoKeywords());
model.addAttribute("meta_description", optionService.getSeoDescription());
return themeService.render("tag"); return themeService.render("tag");
} }
} }

View File

@ -24,9 +24,6 @@ public class CategoryDTO implements OutputConverter<CategoryDTO, Category> {
private String name; private String name;
@Deprecated
private String slugName;
private String slug; private String slug;
private String description; private String description;

View File

@ -20,9 +20,6 @@ public class TagDTO implements OutputConverter<TagDTO, Tag> {
private String name; private String name;
@Deprecated
private String slugName;
private String slug; private String slug;
private String thumbnail; private String thumbnail;

View File

@ -28,9 +28,6 @@ public class BasePostMinimalDTO implements OutputConverter<BasePostMinimalDTO, B
private PostStatus status; private PostStatus status;
@Deprecated
private String url;
private String slug; private String slug;
private PostEditorType editorType; private PostEditorType editorType;
@ -41,5 +38,9 @@ public class BasePostMinimalDTO implements OutputConverter<BasePostMinimalDTO, B
private Date editTime; private Date editTime;
private String metaKeywords;
private String metaDescription;
private String fullPath; private String fullPath;
} }

View File

@ -3,7 +3,6 @@ package run.halo.app.model.dto.post;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.ToString; import lombok.ToString;
import run.halo.app.model.enums.PostCreateFrom;
/** /**
* Base page simple output dto. * Base page simple output dto.
@ -29,7 +28,5 @@ public class BasePostSimpleDTO extends BasePostMinimalDTO {
private Integer topPriority = 0; private Integer topPriority = 0;
private PostCreateFrom createFrom;
private Long likes = 0L; private Long likes = 0L;
} }

View File

@ -3,7 +3,6 @@ package run.halo.app.model.entity;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.ToString; import lombok.ToString;
import run.halo.app.model.enums.PostCreateFrom;
import run.halo.app.model.enums.PostEditorType; import run.halo.app.model.enums.PostEditorType;
import run.halo.app.model.enums.PostStatus; import run.halo.app.model.enums.PostStatus;
@ -44,7 +43,7 @@ public class BasePost extends BaseEntity {
* Post url. * Post url.
*/ */
@Deprecated @Deprecated
@Column(name = "url", columnDefinition = "varchar(255) not null", unique = true) @Column(name = "url", columnDefinition = "varchar(255) not null")
private String url; private String url;
/** /**
@ -76,7 +75,7 @@ public class BasePost extends BaseEntity {
/** /**
* Post summary. * Post summary.
*/ */
@Column(name = "summary", columnDefinition = "varchar(500) default ''") @Column(name = "summary", columnDefinition = "longtext default ''")
private String summary; private String summary;
/** /**
@ -115,12 +114,6 @@ public class BasePost extends BaseEntity {
@Column(name = "top_priority", columnDefinition = "int default 0") @Column(name = "top_priority", columnDefinition = "int default 0")
private Integer topPriority; private Integer topPriority;
/**
* Create from,server or WeChat.
*/
@Column(name = "create_from", columnDefinition = "int default 0")
private PostCreateFrom createFrom;
/** /**
* Likes * Likes
*/ */
@ -134,6 +127,18 @@ public class BasePost extends BaseEntity {
@Temporal(TemporalType.TIMESTAMP) @Temporal(TemporalType.TIMESTAMP)
private Date editTime; private Date editTime;
/**
* Meta keywords.
*/
@Column(name = "meta_keywords", columnDefinition = "varchar(500) default ''")
private String metaKeywords;
/**
* Meta description.
*/
@Column(name = "meta_description", columnDefinition = "varchar(1023) default ''")
private String metaDescription;
@Override @Override
public void prePersist() { public void prePersist() {
super.prePersist(); super.prePersist();
@ -172,10 +177,6 @@ public class BasePost extends BaseEntity {
topPriority = 0; topPriority = 0;
} }
if (createFrom == null) {
createFrom = PostCreateFrom.ADMIN;
}
if (visits == null || visits < 0) { if (visits == null || visits < 0) {
visits = 0L; visits = 0L;
} }

View File

@ -1,30 +0,0 @@
package run.halo.app.model.enums;
/**
* Post create from type.
*
* @author johnniang
*/
public enum PostCreateFrom implements ValueEnum<Integer> {
/**
*
*/
ADMIN(0),
/**
*
*/
WECHAT(1);
private final Integer value;
PostCreateFrom(Integer value) {
this.value = value;
}
@Override
public Integer getValue() {
return value;
}
}

View File

@ -1,19 +0,0 @@
package run.halo.app.model.enums.converter;
import run.halo.app.model.enums.PostCreateFrom;
import javax.persistence.Converter;
/**
* Post create from converter.
*
* @author johnniang
* @date 3/27/19
*/
@Converter(autoApply = true)
public class PostCreateFromConverter extends AbstractConverter<PostCreateFrom, Integer> {
public PostCreateFromConverter() {
super(PostCreateFrom.class);
}
}

View File

@ -23,9 +23,6 @@ public class CategoryParam implements InputConverter<Category> {
@Size(max = 255, message = "分类名称的字符长度不能超过 {max}") @Size(max = 255, message = "分类名称的字符长度不能超过 {max}")
private String name; private String name;
@Deprecated
private String slugName;
@Size(max = 255, message = "分类别名的字符长度不能超过 {max}") @Size(max = 255, message = "分类别名的字符长度不能超过 {max}")
private String slug; private String slug;

View File

@ -6,7 +6,6 @@ import org.springframework.util.CollectionUtils;
import run.halo.app.model.dto.base.InputConverter; import run.halo.app.model.dto.base.InputConverter;
import run.halo.app.model.entity.Post; import run.halo.app.model.entity.Post;
import run.halo.app.model.entity.PostMeta; import run.halo.app.model.entity.PostMeta;
import run.halo.app.model.enums.PostCreateFrom;
import run.halo.app.model.enums.PostEditorType; import run.halo.app.model.enums.PostEditorType;
import run.halo.app.model.enums.PostStatus; import run.halo.app.model.enums.PostStatus;
import run.halo.app.utils.SlugUtils; import run.halo.app.utils.SlugUtils;
@ -35,9 +34,6 @@ public class PostParam implements InputConverter<Post> {
private PostStatus status = PostStatus.DRAFT; private PostStatus status = PostStatus.DRAFT;
@Deprecated
private String url;
@Size(max = 255, message = "文章别名的字符长度不能超过 {max}") @Size(max = 255, message = "文章别名的字符长度不能超过 {max}")
private String slug; private String slug;
@ -63,7 +59,9 @@ public class PostParam implements InputConverter<Post> {
private Date createTime; private Date createTime;
private PostCreateFrom createFrom = PostCreateFrom.ADMIN; private String metaKeywords;
private String metaDescription;
private Set<Integer> tagIds; private Set<Integer> tagIds;

View File

@ -33,9 +33,6 @@ public class SheetParam implements InputConverter<Sheet> {
private PostStatus status = PostStatus.DRAFT; private PostStatus status = PostStatus.DRAFT;
@Deprecated
private String url;
@Size(max = 255, message = "页面别名的字符长度不能超过 {max}") @Size(max = 255, message = "页面别名的字符长度不能超过 {max}")
private String slug; private String slug;
@ -50,8 +47,6 @@ public class SheetParam implements InputConverter<Sheet> {
private Boolean disallowComment = false; private Boolean disallowComment = false;
private Date createTime;
@Size(max = 255, message = "页面密码的字符长度不能超过 {max}") @Size(max = 255, message = "页面密码的字符长度不能超过 {max}")
private String password; private String password;
@ -61,6 +56,12 @@ public class SheetParam implements InputConverter<Sheet> {
@Min(value = 0, message = "Post top priority must not be less than {value}") @Min(value = 0, message = "Post top priority must not be less than {value}")
private Integer topPriority = 0; private Integer topPriority = 0;
private Date createTime;
private String metaKeywords;
private String metaDescription;
private Set<SheetMetaParam> sheetMetas; private Set<SheetMetaParam> sheetMetas;
@Override @Override

View File

@ -23,9 +23,6 @@ public class TagParam implements InputConverter<Tag> {
@Size(max = 255, message = "标签名称的字符长度不能超过 {max}") @Size(max = 255, message = "标签名称的字符长度不能超过 {max}")
private String name; private String name;
@Deprecated
private String slugName;
@Size(max = 255, message = "标签别名的字符长度不能超过 {max}") @Size(max = 255, message = "标签别名的字符长度不能超过 {max}")
private String slug; private String slug;

View File

@ -354,6 +354,20 @@ public interface OptionService extends CrudService<Option, Integer> {
@NonNull @NonNull
String getBlogTitle(); String getBlogTitle();
/**
* Gets global seo keywords.
*
* @return keywords
*/
String getSeoKeywords();
/**
* Get global seo description.
*
* @return description
*/
String getSeoDescription();
/** /**
* Gets blog birthday. * Gets blog birthday.
* *

View File

@ -308,4 +308,12 @@ public interface BasePostService<POST extends BasePost> extends CrudService<POST
*/ */
@NonNull @NonNull
List<BasePostDetailDTO> replaceUrl(@NonNull String oldUrl, @NonNull String newUrl); List<BasePostDetailDTO> replaceUrl(@NonNull String oldUrl, @NonNull String newUrl);
/**
* Generate description.
*
* @param content html content must not be null.
* @return description
*/
String generateDescription(@NonNull String content);
} }

View File

@ -425,6 +425,21 @@ public abstract class BasePostServiceImpl<POST extends BasePost> extends Abstrac
return updated.stream().map(this::convertToDetail).collect(Collectors.toList()); return updated.stream().map(this::convertToDetail).collect(Collectors.toList());
} }
@Override
public String generateDescription(String content) {
Assert.notNull(content, "html content must not be null");
String text = HaloUtils.cleanHtmlTag(content);
Matcher matcher = summaryPattern.matcher(text);
text = matcher.replaceAll("");
// Get summary length
Integer summaryLength = optionService.getByPropertyOrDefault(PostProperties.SUMMARY_LENGTH, Integer.class, 150);
return StringUtils.substring(text, 0, summaryLength);
}
@Override @Override
public POST create(POST post) { public POST create(POST post) {
// Check title // Check title

View File

@ -469,6 +469,16 @@ public class OptionServiceImpl extends AbstractCrudService<Option, Integer> impl
return getByProperty(BlogProperties.BLOG_TITLE).orElse("").toString(); return getByProperty(BlogProperties.BLOG_TITLE).orElse("").toString();
} }
@Override
public String getSeoKeywords() {
return getByProperty(SeoProperties.KEYWORDS).orElse("").toString();
}
@Override
public String getSeoDescription() {
return getByProperty(SeoProperties.DESCRIPTION).orElse("").toString();
}
@Override @Override
public long getBirthday() { public long getBirthday() {
return getByProperty(PrimaryProperties.BIRTHDAY, Long.class).orElseGet(() -> { return getByProperty(PrimaryProperties.BIRTHDAY, Long.class).orElseGet(() -> {

View File

@ -4,6 +4,7 @@
update posts set `slug`=`url`; update posts set `slug`=`url`;
alter table posts modify slug varchar(255) not null; alter table posts modify slug varchar(255) not null;
alter table posts modify url varchar(255) null; alter table posts modify url varchar(255) null;
alter table posts modify summary longtext default '';
-- Migrate categories Table -- Migrate categories Table
update categories set `slug`=`slug_name`; update categories set `slug`=`slug_name`;

@ -1 +1 @@
Subproject commit b50e52376237f47b9da289d8feafe891332e0943 Subproject commit a25940ca70146600bbf371386f9e9a87948f3e6f