Refactor and beauty codes

pull/146/head
johnniang 2019-04-09 01:18:52 +08:00
parent f87dc413e3
commit 468a0323b4
14 changed files with 256 additions and 196 deletions

View File

@ -17,7 +17,6 @@ import run.halo.app.model.params.UserParam;
import run.halo.app.model.properties.BlogProperties; import run.halo.app.model.properties.BlogProperties;
import run.halo.app.model.properties.PrimaryProperties; import run.halo.app.model.properties.PrimaryProperties;
import run.halo.app.model.support.HaloConst; import run.halo.app.model.support.HaloConst;
import run.halo.app.model.support.Theme;
import run.halo.app.service.OptionService; import run.halo.app.service.OptionService;
import run.halo.app.service.ThemeService; import run.halo.app.service.ThemeService;
import run.halo.app.service.UserService; import run.halo.app.service.UserService;
@ -63,7 +62,6 @@ public class StartedListener implements ApplicationListener<ApplicationStartedEv
@Override @Override
public void onApplicationEvent(ApplicationStartedEvent event) { public void onApplicationEvent(ApplicationStartedEvent event) {
// save halo version to database // save halo version to database
this.cacheThemes();
this.cacheOwo(); this.cacheOwo();
this.cacheActiveTheme(); this.cacheActiveTheme();
this.printStartInfo(); this.printStartInfo();
@ -96,22 +94,12 @@ public class StartedListener implements ApplicationListener<ApplicationStartedEv
} }
} }
/**
* Cache themes to map
*/
private void cacheThemes() {
final List<Theme> themes = themeService.getThemes();
if (null != themes) {
HaloConst.THEMES = themes;
}
}
/** /**
* Get active theme * Get active theme
*/ */
private void cacheActiveTheme() { private void cacheActiveTheme() {
try { try {
configuration.setSharedVariable("themeName", themeService.getTheme()); configuration.setSharedVariable("themeName", themeService.getActivatedTheme());
} catch (TemplateModelException e) { } catch (TemplateModelException e) {
log.error("", e); log.error("", e);
} }

View File

@ -23,16 +23,23 @@ import javax.persistence.*;
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class ThemeSetting extends BaseEntity { public class ThemeSetting extends BaseEntity {
/**
* Theme id as id.
*/
@Id @Id
@Column(name = "id", columnDefinition = "varchar(255) not null")
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id; private String id;
@Column(name = "theme", columnDefinition = "varchar(255) not null")
private String theme;
/**
* Setting key.
*/
@Column(name = "setting_key", columnDefinition = "varchar(255) not null") @Column(name = "setting_key", columnDefinition = "varchar(255) not null")
private String key; private String key;
/**
* Setting value
*/
@Column(name = "setting_value", columnDefinition = "varchar(10239) not null") @Column(name = "setting_value", columnDefinition = "varchar(10239) not null")
private String value; private String value;

View File

@ -1,25 +0,0 @@
package run.halo.app.model.enums;
/**
* Option source.
*
* @author johnniang
* @date 4/1/19
*/
@Deprecated
public enum OptionSource implements ValueEnum<Integer> {
SYSTEM(0),
THEME(1);
private final int value;
OptionSource(int value) {
this.value = value;
}
@Override
public Integer getValue() {
return null;
}
}

View File

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

View File

@ -11,6 +11,7 @@ import java.io.Serializable;
* @date : 2018/1/3 * @date : 2018/1/3
*/ */
@Data @Data
@Deprecated
public class Theme implements Serializable { public class Theme implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@ -28,5 +29,5 @@ public class Theme implements Serializable {
/** /**
* Theme properties * Theme properties
*/ */
private ThemeProperties properties; private ThemeProperty properties;
} }

View File

@ -1,27 +0,0 @@
package run.halo.app.model.support;
import lombok.Data;
/**
* @author : RYAN0UP
* @date : 2019-03-22
*/
@Data
public class ThemeProperties {
private String id;
private String name;
private String website;
private String description;
private String logo;
private String version;
private String author;
private String authorWebsite;
}

View File

@ -0,0 +1,61 @@
package run.halo.app.model.support;
import lombok.Data;
/**
* @author : RYAN0UP
* @date : 2019-03-22
*/
@Data
public class ThemeProperty {
/**
* Theme id.
*/
private String id;
/**
* Theme name.
*/
private String name;
/**
* Theme website.
*/
private String website;
/**
* Theme description.
*/
private String description;
/**
* Theme logo.
*/
private String logo;
/**
* Theme version.
*/
private String version;
/**
* Theme author.
*/
private String author;
/**
* Theme author website.
*/
private String authorWebsite;
/**
* Folder name.
*/
private String folderName;
/**
* Has options.
*/
private Boolean hasOptions;
}

View File

@ -2,9 +2,8 @@ package run.halo.app.service;
import org.springframework.lang.NonNull; import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import run.halo.app.model.support.Theme;
import run.halo.app.model.support.ThemeFile; import run.halo.app.model.support.ThemeFile;
import run.halo.app.model.support.ThemeProperties; import run.halo.app.model.support.ThemeProperty;
import java.io.File; import java.io.File;
import java.nio.file.Path; import java.nio.file.Path;
@ -21,7 +20,7 @@ public interface ThemeService {
* *
* @return list of themes * @return list of themes
*/ */
List<Theme> getThemes(); List<ThemeProperty> getThemes();
/** /**
* Lists theme folder by absolute path. * Lists theme folder by absolute path.
@ -78,14 +77,6 @@ public interface ThemeService {
*/ */
Path getBasePath(); Path getBasePath();
/**
* Get theme Properties.
*
* @param path path
* @return ThemeProperties
*/
ThemeProperties getProperties(@NonNull File path);
/** /**
* Get template content by template absolute path. * Get template content by template absolute path.
* *
@ -130,11 +121,17 @@ public interface ThemeService {
String render(@NonNull String pageName); String render(@NonNull String pageName);
/** /**
* Gets current theme name. * Gets current theme id.
* *
* @return current theme name * @return current theme id
*/ */
@NonNull @NonNull
String getTheme(); String getActivatedTheme();
/**
* Actives a theme.
*
* @param themeId theme id must not be blank
*/
void activeTheme(@NonNull String themeId);
} }

View File

@ -9,27 +9,30 @@ import cn.hutool.setting.dialect.Props;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import run.halo.app.cache.StringCacheStore;
import run.halo.app.config.properties.HaloProperties; import run.halo.app.config.properties.HaloProperties;
import run.halo.app.exception.NotFoundException; import run.halo.app.exception.NotFoundException;
import run.halo.app.exception.ServiceException;
import run.halo.app.model.properties.PrimaryProperties; import run.halo.app.model.properties.PrimaryProperties;
import run.halo.app.model.support.HaloConst; import run.halo.app.model.support.HaloConst;
import run.halo.app.model.support.Theme;
import run.halo.app.model.support.ThemeFile; import run.halo.app.model.support.ThemeFile;
import run.halo.app.model.support.ThemeProperties; import run.halo.app.model.support.ThemeProperty;
import run.halo.app.service.OptionService; import run.halo.app.service.OptionService;
import run.halo.app.service.ThemeService; import run.halo.app.service.ThemeService;
import run.halo.app.utils.FilenameUtils; import run.halo.app.utils.FilenameUtils;
import run.halo.app.utils.JsonUtils;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.ArrayList; import java.util.*;
import java.util.Comparator; import java.util.stream.Collectors;
import java.util.List;
import static run.halo.app.model.support.HaloConst.DEFAULT_THEME_NAME; import static run.halo.app.model.support.HaloConst.DEFAULT_THEME_NAME;
@ -42,39 +45,52 @@ import static run.halo.app.model.support.HaloConst.DEFAULT_THEME_NAME;
public class ThemeServiceImpl implements ThemeService { public class ThemeServiceImpl implements ThemeService {
/** /**
* The type of file that can be modified. * Theme property file name.
*/ */
private static String[] CAN_EDIT_SUFFIX = {"ftl", "css", "js"}; private final static String THEME_PROPERTY_FILE_NAME = "theme.properties";
/**
* These file names cannot be displayed.
*/
private static String[] FILTER_FILES = {".git", ".DS_Store", "theme.properties"};
/**
* Theme folder location.
*/
private final static String THEME_FOLDER = "templates/themes";
/** /**
* Configuration file name. * Configuration file name.
*/ */
private final static String[] OPTIONS_NAMES = {"options.yaml", "options.yml"}; private final static String[] OPTIONS_NAMES = {"options.yaml", "options.yml"};
/**
* The type of file that can be modified.
*/
private static String[] CAN_EDIT_SUFFIX = {"ftl", "css", "js"};
/**
* These file names cannot be displayed.
*/
private static String[] FILTER_FILES = {".git", ".DS_Store", THEME_PROPERTY_FILE_NAME, "options.yaml", "option.yml"};
/**
* Theme folder location.
*/
private final static String THEME_FOLDER = "templates/themes";
/** /**
* Render template. * Render template.
*/ */
private final static String RENDER_TEMPLATE = "themes/%s/%s"; private final static String RENDER_TEMPLATE = "themes/%s/%s";
private final static String THEMES_CACHE_KEY = "themes";
private final Path workDir; private final Path workDir;
private final ObjectMapper yamlMapper; private final ObjectMapper yamlMapper;
private final OptionService optionService; private final OptionService optionService;
private final StringCacheStore cacheStore;
public ThemeServiceImpl(HaloProperties haloProperties, public ThemeServiceImpl(HaloProperties haloProperties,
OptionService optionService) { OptionService optionService,
StringCacheStore cacheStore) {
this.optionService = optionService; this.optionService = optionService;
this.cacheStore = cacheStore;
yamlMapper = new ObjectMapper(new YAMLFactory()); yamlMapper = new ObjectMapper(new YAMLFactory());
workDir = Paths.get(haloProperties.getWorkDir(), THEME_FOLDER); workDir = Paths.get(haloProperties.getWorkDir(), THEME_FOLDER);
} }
@ -85,36 +101,37 @@ public class ThemeServiceImpl implements ThemeService {
* @return list of themes * @return list of themes
*/ */
@Override @Override
public List<Theme> getThemes() { public List<ThemeProperty> getThemes() {
final List<Theme> themes = new ArrayList<>(); // Fetch themes from cache
final File[] files = getThemeBasePath().listFiles(); return cacheStore.get(THEMES_CACHE_KEY).map(themesCache -> {
try { try {
if (null != files) { @SuppressWarnings("unchecked")
Theme theme; List<ThemeProperty> themes = JsonUtils.jsonToObject(themesCache, LinkedList.class);
for (File file : files) { return themes;
if (!file.isDirectory()) { } catch (IOException e) {
continue; throw new ServiceException("Failed to parse json", e);
}
theme = new Theme();
theme.setKey(file.getName());
theme.setHasOptions(false);
for (String optionsName : OPTIONS_NAMES) {
// Resolve the options path
Path optionsPath = workDir.resolve(file.getName()).resolve(optionsName);
if (!Files.exists(optionsPath)) {
continue;
}
theme.setHasOptions(true);
}
theme.setProperties(getProperties(new File(getThemeBasePath(), file.getName())));
themes.add(theme);
}
} }
} catch (Exception e) { }).orElseGet(() -> {
throw new RuntimeException("Themes scan failed", e); try {
}
return themes; // List and filter sub folders
List<Path> themePaths = Files.list(getBasePath()).filter(path -> Files.isDirectory(path)).collect(Collectors.toList());
if (CollectionUtils.isEmpty(themePaths)) {
return Collections.emptyList();
}
// Get theme properties
List<ThemeProperty> themes = themePaths.stream().map(this::getProperty).collect(Collectors.toList());
// Cache the themes
cacheStore.put(THEMES_CACHE_KEY, JsonUtils.objectToJson(themes));
return themes;
} catch (Exception e) {
throw new ServiceException("Themes scan failed", e);
}
});
} }
/** /**
@ -208,7 +225,7 @@ public class ThemeServiceImpl implements ThemeService {
*/ */
@Override @Override
public boolean isTemplateExist(String template) { public boolean isTemplateExist(String template) {
StrBuilder templatePath = new StrBuilder(getTheme()); StrBuilder templatePath = new StrBuilder(getActivatedTheme());
templatePath.append("/"); templatePath.append("/");
templatePath.append(template); templatePath.append(template);
File file = new File(getThemeBasePath(), templatePath.toString()); File file = new File(getThemeBasePath(), templatePath.toString());
@ -242,30 +259,6 @@ public class ThemeServiceImpl implements ThemeService {
return workDir; return workDir;
} }
/**
* Get theme Properties.
*
* @param path path
* @return ThemeProperties
*/
@Override
public ThemeProperties getProperties(File path) {
File propertiesFile = new File(path, "theme.properties");
ThemeProperties properties = new ThemeProperties();
if (propertiesFile.exists()) {
Props props = new Props(propertiesFile);
properties.setId(props.getStr("theme.id"));
properties.setName(props.getStr("theme.name"));
properties.setWebsite(props.getStr("theme.website"));
properties.setDescription(props.getStr("theme.description"));
properties.setLogo(props.getStr("theme.logo"));
properties.setVersion(props.getStr("theme.version"));
properties.setAuthor(props.getStr("theme.author"));
properties.setAuthorWebsite(props.getStr("theme.author.website"));
}
return properties;
}
/** /**
* Get template content by template absolute path. * Get template content by template absolute path.
* *
@ -297,12 +290,13 @@ public class ThemeServiceImpl implements ThemeService {
*/ */
@Override @Override
public void deleteTheme(String key) { public void deleteTheme(String key) {
if (!this.isThemeExist(key)) { if (!isThemeExist(key)) {
throw new NotFoundException("该主题不存在!").setErrorData(key); throw new NotFoundException("该主题不存在!").setErrorData(key);
} }
File file = new File(this.getThemeBasePath(), key); File file = new File(this.getThemeBasePath(), key);
FileUtil.del(file); FileUtil.del(file);
HaloConst.THEMES = this.getThemes();
cacheStore.delete(THEMES_CACHE_KEY);
} }
@Override @Override
@ -334,12 +328,99 @@ public class ThemeServiceImpl implements ThemeService {
@Override @Override
public String render(String pageName) { public String render(String pageName) {
return String.format(RENDER_TEMPLATE, getTheme(), pageName); return String.format(RENDER_TEMPLATE, getActivatedTheme(), pageName);
} }
@Override @Override
public String getTheme() { public String getActivatedTheme() {
return optionService.getByProperty(PrimaryProperties.THEME).orElse(DEFAULT_THEME_NAME); return optionService.getByProperty(PrimaryProperties.THEME).orElse(DEFAULT_THEME_NAME);
} }
@Override
public void activeTheme(String themeId) {
// TODO Check existence of the theme
}
/**
* Gets theme property.
*
* @param themePath must not be null
* @return theme property
*/
private ThemeProperty getProperty(@NonNull Path themePath) {
Assert.notNull(themePath, "Theme path must not be null");
Path propertyPath = themePath.resolve(THEME_PROPERTY_FILE_NAME);
if (!Files.exists(propertyPath)) {
return null;
}
File propertyFile = propertyPath.toFile();
try {
Properties properties = new Properties();
properties.load(new java.io.FileReader(propertyFile));
ThemeProperty themeProperty = new ThemeProperty();
themeProperty.setId(properties.getProperty("theme.id"));
themeProperty.setName(properties.getProperty("theme.name"));
themeProperty.setWebsite(properties.getProperty("theme.website"));
themeProperty.setDescription(properties.getProperty("theme.description"));
themeProperty.setLogo(properties.getProperty("theme.logo"));
themeProperty.setVersion(properties.getProperty("theme.version"));
themeProperty.setAuthor(properties.getProperty("theme.author"));
themeProperty.setAuthorWebsite(properties.getProperty("theme.author.website"));
themeProperty.setFolderName(propertyFile.getName());
themeProperty.setHasOptions(hasOptions(propertyPath));
return themeProperty;
} catch (IOException e) {
// TODO Consider Ignore this error, then return null
throw new ServiceException("Failed to load: " + themePath.toString(), e);
}
}
/**
* Gets theme Properties.
*
* @param path path
* @return ThemeProperty
*/
private ThemeProperty getProperties(File path) {
File propertiesFile = new File(path, "theme.properties");
ThemeProperty properties = new ThemeProperty();
if (propertiesFile.exists()) {
Props props = new Props(propertiesFile);
properties.setId(props.getStr("theme.id"));
properties.setName(props.getStr("theme.name"));
properties.setWebsite(props.getStr("theme.website"));
properties.setDescription(props.getStr("theme.description"));
properties.setLogo(props.getStr("theme.logo"));
properties.setVersion(props.getStr("theme.version"));
properties.setAuthor(props.getStr("theme.author"));
properties.setAuthorWebsite(props.getStr("theme.author.website"));
properties.setFolderName(path.getName());
}
return properties;
}
/**
* Check existence of the options.
*
* @param themePath theme path must not be null
* @return true if it has options; false otherwise
*/
private boolean hasOptions(@NonNull Path themePath) {
Assert.notNull(themePath, "Path must not be null");
for (String optionsName : OPTIONS_NAMES) {
// Resolve the options path
Path optionsPath = themePath.resolve(optionsName);
if (Files.exists(optionsPath)) {
return true;
}
}
return false;
}
} }

View File

@ -22,5 +22,4 @@ public class ThemeSettingServiceImpl extends AbstractCrudService<ThemeSetting, I
this.themeSettingRepository = themeSettingRepository; this.themeSettingRepository = themeSettingRepository;
} }
} }

View File

@ -1,14 +1,10 @@
package run.halo.app.web.controller.admin.api; package run.halo.app.web.controller.admin.api;
import run.halo.app.model.dto.OptionOutputDTO; import run.halo.app.model.dto.OptionOutputDTO;
import run.halo.app.model.enums.OptionSource;
import run.halo.app.model.params.OptionParam; import run.halo.app.model.params.OptionParam;
import run.halo.app.service.OptionService; import run.halo.app.service.OptionService;
import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import run.halo.app.model.dto.OptionOutputDTO;
import run.halo.app.model.enums.OptionSource;
import run.halo.app.model.params.OptionParam;
import javax.validation.Valid; import javax.validation.Valid;
import java.util.List; import java.util.List;

View File

@ -6,8 +6,8 @@ import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import run.halo.app.model.properties.PrimaryProperties; import run.halo.app.model.properties.PrimaryProperties;
import run.halo.app.model.support.BaseResponse; import run.halo.app.model.support.BaseResponse;
import run.halo.app.model.support.Theme;
import run.halo.app.model.support.ThemeFile; import run.halo.app.model.support.ThemeFile;
import run.halo.app.model.support.ThemeProperty;
import run.halo.app.service.OptionService; import run.halo.app.service.OptionService;
import run.halo.app.service.ThemeService; import run.halo.app.service.ThemeService;
@ -44,7 +44,7 @@ public class ThemeController {
*/ */
@GetMapping @GetMapping
@ApiOperation("List all themes") @ApiOperation("List all themes")
public List<Theme> listAll() { public List<ThemeProperty> listAll() {
return themeService.getThemes(); return themeService.getThemes();
} }
@ -55,7 +55,7 @@ public class ThemeController {
*/ */
@GetMapping("files") @GetMapping("files")
public List<ThemeFile> listFiles() { public List<ThemeFile> listFiles() {
return themeService.listThemeFolderBy(themeService.getTheme()); return themeService.listThemeFolderBy(themeService.getActivatedTheme());
} }
@GetMapping("files/content") @GetMapping("files/content")
@ -71,19 +71,21 @@ public class ThemeController {
@GetMapping("files/custom") @GetMapping("files/custom")
public List<String> customTemplate() { public List<String> customTemplate() {
return themeService.getCustomTpl(themeService.getTheme()); return themeService.getCustomTpl(themeService.getActivatedTheme());
} }
@PostMapping("active") @PostMapping("{themeId}/activate")
@ApiOperation("Active a theme") @ApiOperation("Active a theme")
public void active(String theme) throws TemplateModelException { public void active(@RequestParam("themeId") String themeId) throws TemplateModelException {
themeService.activeTheme(themeId);
// TODO Check existence of the theme // TODO Check existence of the theme
optionService.saveProperty(PrimaryProperties.THEME, theme); optionService.saveProperty(PrimaryProperties.THEME, themeId);
configuration.setSharedVariable("themeName", theme); configuration.setSharedVariable("themeName", themeId);
configuration.setSharedVariable("options", optionService.listOptions()); configuration.setSharedVariable("options", optionService.listOptions());
} }
@DeleteMapping("{key}") @DeleteMapping("key/{key}")
@ApiOperation("Deletes a theme") @ApiOperation("Deletes a theme")
public void deleteBy(@PathVariable("key") String key) { public void deleteBy(@PathVariable("key") String key) {
themeService.deleteTheme(key); themeService.deleteTheme(key);
@ -92,6 +94,6 @@ public class ThemeController {
@GetMapping("configurations") @GetMapping("configurations")
@ApiOperation("Fetches theme configuration") @ApiOperation("Fetches theme configuration")
public BaseResponse<Object> fetchConfig() { public BaseResponse<Object> fetchConfig() {
return BaseResponse.ok(themeService.fetchConfig(themeService.getTheme())); return BaseResponse.ok(themeService.fetchConfig(themeService.getActivatedTheme()));
} }
} }

View File

@ -114,7 +114,7 @@ public class CommonController implements ErrorController {
return "common/error/404"; return "common/error/404";
} }
StrBuilder path = new StrBuilder("themes/"); StrBuilder path = new StrBuilder("themes/");
path.append(themeService.getTheme()); path.append(themeService.getActivatedTheme());
path.append("/404"); path.append("/404");
return path.toString(); return path.toString();
} }
@ -130,7 +130,7 @@ public class CommonController implements ErrorController {
return "common/error/500"; return "common/error/500";
} }
StrBuilder path = new StrBuilder("themes/"); StrBuilder path = new StrBuilder("themes/");
path.append(themeService.getTheme()); path.append(themeService.getActivatedTheme());
path.append("/500"); path.append("/500");
return path.toString(); return path.toString();
} }

View File

@ -12,7 +12,6 @@ import org.springframework.web.bind.annotation.ResponseBody;
import run.halo.app.exception.BadRequestException; import run.halo.app.exception.BadRequestException;
import run.halo.app.model.entity.*; import run.halo.app.model.entity.*;
import run.halo.app.model.enums.AttachmentType; import run.halo.app.model.enums.AttachmentType;
import run.halo.app.model.enums.OptionSource;
import run.halo.app.model.params.InstallParam; import run.halo.app.model.params.InstallParam;
import run.halo.app.model.properties.*; import run.halo.app.model.properties.*;
import run.halo.app.model.support.BaseResponse; import run.halo.app.model.support.BaseResponse;