pull/137/head
ruibaby 2019-03-16 17:22:13 +08:00
parent 450f473fcf
commit 4191cdf16e
5 changed files with 464 additions and 142 deletions

View File

@ -205,131 +205,6 @@ public class HaloUtils {
return "";
}
/**
* Scan internal themes and user's themes
*
* @return List
*/
public static List<Theme> getThemes() {
final List<Theme> themes = new ArrayList<>();
try {
final File classPath = new File(ResourceUtils.getURL("classpath:").getPath());
final File internalThemesPath = new File(classPath.getAbsolutePath(), "templates/themes");
themes.addAll(getThemesByPath(internalThemesPath));
final File userPath = new File(System.getProperties().getProperty("user.home"));
final File userThemesPath = new File(userPath.getAbsolutePath(),"halo/templates/themes");
themes.addAll(getThemesByPath(userThemesPath));
} catch (Exception e) {
log.error("Themes scan failed", e);
}
return themes;
}
/**
* Scan themes by directory
*
* @param file file
* @return List<Theme>
*/
private static List<Theme> getThemesByPath(File themesPath) {
final List<Theme> themes = new ArrayList<>();
try {
final File[] files = themesPath.listFiles();
if (null != files) {
Theme theme;
for (File file : files) {
if (file.isDirectory()) {
if (StrUtil.equals("__MACOSX", file.getName())) {
continue;
}
theme = new Theme();
theme.setThemeName(file.getName());
File optionsPath = new File(themesPath.getAbsolutePath(),
file.getName() + "/module/options.ftl");
if (optionsPath.exists()) {
theme.setHasOptions(true);
} else {
theme.setHasOptions(false);
}
File gitPath = new File(themesPath.getAbsolutePath(), file.getName() + "/.git");
if (gitPath.exists()) {
theme.setHasUpdate(true);
} else {
theme.setHasUpdate(false);
}
themes.add(theme);
}
}
}
} catch (Exception e) {
log.error("Themes scan failed", e);
}
return themes;
}
/**
*
*
* @param theme theme
* @return List
*/
public static List<String> getTplName(String theme) {
final List<String> tpls = new ArrayList<>();
try {
// 获取项目根路径
final File basePath = new File(ResourceUtils.getURL("classpath:").getPath());
// 获取主题路径
final File themesPath = new File(basePath.getAbsolutePath(), "templates/themes/" + theme);
final File modulePath = new File(themesPath.getAbsolutePath(), "module");
final File[] baseFiles = themesPath.listFiles();
final File[] moduleFiles = modulePath.listFiles();
if (null != moduleFiles) {
for (File file : moduleFiles) {
if (file.isFile() && file.getName().endsWith(".ftl")) {
tpls.add("module/" + file.getName());
}
}
}
if (null != baseFiles) {
for (File file : baseFiles) {
if (file.isFile() && file.getName().endsWith(".ftl")) {
tpls.add(file.getName());
}
}
}
} catch (Exception e) {
log.error("Failed to get theme template", e);
}
return tpls;
}
/**
* page_xxx
*
* @return List
*/
public static List<String> getCustomTpl(String theme) {
final List<String> tpls = new ArrayList<>();
try {
final File basePath = new File(ResourceUtils.getURL("classpath:").getPath());
// 获取主题路径
final File themePath = new File(basePath.getAbsolutePath(), "templates/themes/" + theme);
final File[] themeFiles = themePath.listFiles();
if (null != themeFiles && themeFiles.length > 0) {
for (File file : themeFiles) {
String[] split = StrUtil.removeSuffix(file.getName(), ".ftl").split("_");
if (split.length == 2 && "page".equals(split[0])) {
tpls.add(StrUtil.removeSuffix(file.getName(), ".ftl"));
}
}
}
} catch (FileNotFoundException e) {
log.error("File not found", e);
}
return tpls;
}
/**
*
*

View File

@ -0,0 +1,151 @@
package cc.ryanc.halo.utils;
import cc.ryanc.halo.model.support.Theme;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.ResourceUtils;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.List;
/**
* Theme utils
*
* @author : RYAN0UP
* @date : 2019/3/16
*/
@Slf4j
public class ThemeUtils {
/**
* Scan internal themes and user's themes
*
* @return List
*/
public static List<Theme> getThemes() {
final List<Theme> themes = new ArrayList<>();
try {
themes.addAll(getThemesByPath(getInternalThemesPath()));
themes.addAll(getThemesByPath(getUsersThemesPath()));
} catch (Exception e) {
log.error("Themes scan failed", e);
}
return themes;
}
/**
* Scan themes by directory
*
* @param file file
* @return List<Theme>
*/
private static List<Theme> getThemesByPath(File themesPath) {
final List<Theme> themes = new ArrayList<>();
try {
final File[] files = themesPath.listFiles();
if (null != files) {
Theme theme;
for (File file : files) {
if (file.isDirectory()) {
if (StrUtil.equals("__MACOSX", file.getName())) {
continue;
}
theme = new Theme();
theme.setThemeName(file.getName());
File optionsPath = new File(themesPath.getAbsolutePath(),
file.getName() + "/module/options.ftl");
if (optionsPath.exists()) {
theme.setHasOptions(true);
} else {
theme.setHasOptions(false);
}
File gitPath = new File(themesPath.getAbsolutePath(), file.getName() + "/.git");
if (gitPath.exists()) {
theme.setHasUpdate(true);
} else {
theme.setHasUpdate(false);
}
themes.add(theme);
}
}
}
} catch (Exception e) {
log.error("Themes scan failed", e);
}
return themes;
}
/**
* Get internal themes
*
* @return File
* @throws FileNotFoundException FileNotFoundException
*/
public static File getInternalThemesPath() throws FileNotFoundException {
return new File(ResourceUtils.getURL("classpath:").getPath(), "templates/themes");
}
/**
* Get user's themes
*
* @return File
*/
public static File getUsersThemesPath() {
return new File(System.getProperties().getProperty("user.home"), "halo/templates/themes");
}
/**
* Get theme templates
*
* @param theme theme
* @return List<String>
*/
public static List<String> getTplName(String theme) {
final List<String> templates = new ArrayList<>();
try {
final File themesPath = new File(getUsersThemesPath(), "templates/themes/" + theme);
final File modulePath = new File(themesPath.getAbsolutePath(), "module");
final File[] baseFiles = themesPath.listFiles();
final File[] moduleFiles = modulePath.listFiles();
if (null != moduleFiles) {
for (File file : moduleFiles) {
if (file.isFile() && file.getName().endsWith(".ftl")) {
templates.add("module/" + file.getName());
}
}
}
if (null != baseFiles) {
for (File file : baseFiles) {
if (file.isFile() && file.getName().endsWith(".ftl")) {
templates.add(file.getName());
}
}
}
} catch (Exception e) {
log.error("Failed to get theme template", e);
}
return templates;
}
/**
* Get custom template, such as page_xxx.ftl, and xxx will be template name
*
* @return List
*/
public static List<String> getCustomTpl(String theme) {
final List<String> templates = new ArrayList<>();
final File themePath = new File(getUsersThemesPath(), "templates/themes/" + theme);
final File[] themeFiles = themePath.listFiles();
if (null != themeFiles && themeFiles.length > 0) {
for (File file : themeFiles) {
String[] split = StrUtil.removeSuffix(file.getName(), ".ftl").split("_");
if (split.length == 2 && "page".equals(split[0])) {
templates.add(StrUtil.removeSuffix(file.getName(), ".ftl"));
}
}
}
return templates;
}
}

View File

@ -1,15 +1,33 @@
package cc.ryanc.halo.web.controller.admin;
import cc.ryanc.halo.model.support.Theme;
import cc.ryanc.halo.logging.Logger;
import cc.ryanc.halo.model.support.JsonResult;
import cc.ryanc.halo.service.LogService;
import cc.ryanc.halo.service.OptionService;
import cc.ryanc.halo.utils.HaloUtils;
import cc.ryanc.halo.utils.LocaleMessageUtil;
import cc.ryanc.halo.utils.ThemeUtils;
import cc.ryanc.halo.web.controller.admin.base.BaseController;
import cc.ryanc.halo.web.controller.core.BaseFrontController;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.file.FileReader;
import cn.hutool.core.io.file.FileWriter;
import cn.hutool.core.text.StrBuilder;
import cn.hutool.core.util.RuntimeUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.ZipUtil;
import freemarker.template.Configuration;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.List;
import java.util.Map;
import static cc.ryanc.halo.model.support.HaloConst.THEMES;
/**
* Themes controller
@ -19,23 +37,253 @@ import java.util.Map;
*/
@Controller
@RequestMapping(value = "/admin/themes")
public class ThemeController {
public class ThemeController extends BaseController {
private OptionService optionService;
private final Logger log = Logger.getLogger(getClass());
public ThemeController(OptionService optionService) {
this.optionService = optionService;
private LogService logsService;
public ThemeController(Configuration configuration,
OptionService optionService,
LocaleMessageUtil localeMessageUtil,
LogService logsService) {
super(configuration, optionService, localeMessageUtil);
this.logsService = logsService;
}
/**
* Render theme manage
*
* @param model model
* @return template path: admin/admin_theme.ftl
*/
@GetMapping
public String themes(Model model) {
Map<String, String> options = optionService.listOptions();
model.addAttribute("options",options);
List<Theme> themes = HaloUtils.getThemes();
model.addAttribute("themes", themes);
model.addAttribute("activeTheme", BaseFrontController.THEME);
model.addAttribute("themes", THEMES);
return "admin/admin_theme";
}
/**
* Active theme
*
* @param themeName theme name
* @param request request
* @return JsonResult
*/
@GetMapping(value = "/set")
@ResponseBody
@CacheEvict(value = "posts", allEntries = true, beforeInvocation = true)
public JsonResult activeTheme(@RequestParam("themeName") String themeName,
HttpServletRequest request) {
try {
optionService.saveOption("theme", themeName);
BaseFrontController.THEME = themeName;
configuration.setSharedVariable("themeName", themeName);
log.info("Changed theme to {}", themeName);
return new JsonResult(ResultCodeEnum.SUCCESS.getCode(), localeMessageUtil.getMessage("code.admin.theme.change-success", new Object[]{themeName}));
} catch (Exception e) {
return new JsonResult(ResultCodeEnum.FAIL.getCode(), localeMessageUtil.getMessage("code.admin.theme.change-failed"));
} finally {
refreshCache();
}
}
/**
* Upload theme
*
* @param file file
* @return JsonResult
*/
@RequestMapping(value = "/upload", method = RequestMethod.POST)
@ResponseBody
public JsonResult uploadTheme(@RequestParam("file") MultipartFile file,
HttpServletRequest request) {
try {
if (!file.isEmpty()) {
final File themePath = new File(ThemeUtils.getUsersThemesPath().getAbsolutePath(), new StrBuilder("templates/themes/").append(file.getOriginalFilename()).toString());
file.transferTo(themePath);
log.info("Upload topic success, path is " + themePath.getAbsolutePath());
ZipUtil.unzip(themePath, new File(basePath.getAbsolutePath(), "templates/themes/"));
FileUtil.del(themePath);
logsService.save(LogsRecord.UPLOAD_THEME, file.getOriginalFilename(), request);
} else {
log.error("Upload theme failed, no file selected");
return new JsonResult(ResultCodeEnum.FAIL.getCode(), localeMessageUtil.getMessage("code.admin.theme.upload-no-file"));
}
} catch (Exception e) {
log.error("Upload theme failed: {}", e.getMessage());
return new JsonResult(ResultCodeEnum.FAIL.getCode(), localeMessageUtil.getMessage("code.admin.theme.upload-failed"));
}finally {
refreshCache();
}
return new JsonResult(ResultCodeEnum.SUCCESS.getCode(), localeMessageUtil.getMessage("code.admin.theme.upload-success"));
}
/**
* Delete theme
*
* @param themeName theme name
* @return redirect to admin/themes
*/
@GetMapping(value = "/remove")
public String removeTheme(@RequestParam("themeName") String themeName) {
try {
final File themePath = new File(ThemeUtils.getUsersThemesPath(), themeName);
FileUtil.del(themePath);
} catch (Exception e) {
log.error("Delete theme failed: {}", e.getMessage());
} finally {
refreshCache();
}
return "redirect:/admin/themes";
}
/**
* Install theme page
*
* @return template path: admin/widget/_theme-install.ftl
*/
@GetMapping(value = "/install")
public String install() {
return "admin/widget/_theme-install";
}
/**
* Clone theme by git
*
* @param remoteAddr theme remote address
* @param themeName theme name
* @return JsonResult
*/
@PostMapping(value = "/clone")
@ResponseBody
public JsonResult cloneFromRemote(@RequestParam(value = "remoteAddr") String remoteAddr,
@RequestParam(value = "themeName") String themeName) {
if (StrUtil.isBlank(remoteAddr) || StrUtil.isBlank(themeName)) {
return new JsonResult(ResultCodeEnum.FAIL.getCode(), localeMessageUtil.getMessage("code.admin.common.info-no-complete"));
}
try {
final String cmdResult = RuntimeUtil.execForStr("git clone " + remoteAddr + " " + ThemeUtils.getUsersThemesPath().getAbsolutePath() + "/" + themeName);
if (NOT_FOUND_GIT.equals(cmdResult)) {
return new JsonResult(ResultCodeEnum.FAIL.getCode(), localeMessageUtil.getMessage("code.admin.theme.no-git"));
}
} catch (FileNotFoundException e) {
log.error("Cloning theme failed: {}", e.getMessage());
return new JsonResult(ResultCodeEnum.FAIL.getCode(), localeMessageUtil.getMessage("code.admin.theme.clone-theme-failed") + e.getMessage());
}finally {
refreshCache();
}
return new JsonResult(ResultCodeEnum.SUCCESS.getCode(), localeMessageUtil.getMessage("code.admin.common.install-success"));
}
/**
* Update theme
*
* @param themeName theme name
* @return JsonResult
*/
@GetMapping(value = "/pull")
@ResponseBody
public JsonResult pullFromRemote(@RequestParam(value = "themeName") String themeName) {
try {
final String cmdResult = RuntimeUtil.execForStr("cd " + ThemeUtils.getUsersThemesPath().getAbsolutePath() + "/" + themeName + " && git pull");
if (NOT_FOUND_GIT.equals(cmdResult)) {
return new JsonResult(ResultCodeEnum.FAIL.getCode(), localeMessageUtil.getMessage("code.admin.theme.no-git"));
}
} catch (Exception e) {
log.error("Update theme failed: {}", e.getMessage());
return new JsonResult(ResultCodeEnum.FAIL.getCode(), localeMessageUtil.getMessage("code.admin.theme.update-theme-failed") + e.getMessage());
} finally {
refreshCache();
}
return new JsonResult(ResultCodeEnum.SUCCESS.getCode(), localeMessageUtil.getMessage("code.admin.common.update-success"));
}
/**
* Redirect to theme option page
*
* @param model model
* @param theme theme name
* @param hasUpdate hasUpdate
*/
@GetMapping(value = "/options")
public String setting(Model model,
@RequestParam("theme") String theme,
@RequestParam("hasUpdate") String hasUpdate) {
model.addAttribute("themeDir", theme);
if (StrUtil.equals(hasUpdate, TrueFalseEnum.TRUE.getDesc())) {
model.addAttribute("hasUpdate", true);
} else {
model.addAttribute("hasUpdate", false);
}
return "themes/" + theme + "/module/options";
}
/**
* Edit theme template
*
* @param model model
* @return template path: admin/admin_theme-editor.ftl
*/
@GetMapping(value = "/editor")
public String editor(Model model) {
final List<String> templates = ThemeUtils.getTplName(BaseFrontController.THEME);
model.addAttribute("tpls", templates);
return "admin/admin_theme-editor";
}
/**
* Get template content
*
* @param tplName template name
* @return template content
*/
@GetMapping(value = "/getTpl", produces = "text/text;charset=UTF-8")
@ResponseBody
public String getTplContent(@RequestParam("tplName") String tplName) {
String tplContent = "";
try {
final StrBuilder themePath = new StrBuilder(ThemeUtils.getUsersThemesPath().getAbsolutePath());
themePath.append(BaseFrontController.THEME);
themePath.append("/");
themePath.append(tplName);
final File themesPath = new File(themePath.toString());
final FileReader fileReader = new FileReader(themesPath);
tplContent = fileReader.readString();
} catch (Exception e) {
log.error("Get template file error: {}", e.getMessage());
}
return tplContent;
}
/**
* Save template file
*
* @param tplName template name
* @param tplContent template content
* @return JsonResult
*/
@PostMapping(value = "/editor/save")
@ResponseBody
public JsonResult saveTpl(@RequestParam("tplName") String tplName,
@RequestParam("tplContent") String tplContent) {
if (StrUtil.isBlank(tplContent)) {
return new JsonResult(ResultCodeEnum.FAIL.getCode(), localeMessageUtil.getMessage("code.admin.theme.edit.no-content"));
}
try {
final StrBuilder themePath = new StrBuilder(ThemeUtils.getUsersThemesPath().getAbsolutePath());
themePath.append(BaseFrontController.THEME);
themePath.append("/");
themePath.append(tplName);
final File tplPath = new File(themePath);
final FileWriter fileWriter = new FileWriter(tplPath);
fileWriter.write(tplContent);
} catch (Exception e) {
log.error("Template save failed: {}", e.getMessage());
return new JsonResult(ResultCodeEnum.FAIL.getCode(), localeMessageUtil.getMessage("code.admin.common.save-failed"));
}
return new JsonResult(ResultCodeEnum.SUCCESS.getCode(), localeMessageUtil.getMessage("code.admin.common.save-success"));
}
}

View File

@ -0,0 +1,48 @@
package cc.ryanc.halo.web.controller.admin.base;
import cc.ryanc.halo.service.OptionService;
import cc.ryanc.halo.utils.LocaleMessageUtil;
import cc.ryanc.halo.utils.ThemeUtils;
import freemarker.template.Configuration;
import freemarker.template.TemplateModelException;
import static cc.ryanc.halo.model.support.HaloConst.OPTIONS;
import static cc.ryanc.halo.model.support.HaloConst.THEMES;
/**
* Admin base Controller
*
* @author : RYAN0UP
* @date : 2019/3/16
*/
public abstract class BaseController {
private final Configuration configuration;
private final OptionService optionService;
private final LocaleMessageUtil localeMessageUtil;
public BaseController(Configuration configuration,
OptionService optionService,
LocaleMessageUtil localeMessageUtil) {
this.configuration = configuration;
this.optionService = optionService;
this.localeMessageUtil = localeMessageUtil;
}
/**
* Clear all caches
*/
public void refreshCache() {
try {
OPTIONS.clear();
THEMES.clear();
OPTIONS = optionService.listOptions();
THEMES = ThemeUtils.getThemes();
configuration.setSharedVariable("options", OPTIONS);
} catch (TemplateModelException e) {
e.printStackTrace();
}
}
}

View File

@ -11,7 +11,7 @@ import cn.hutool.core.text.StrBuilder;
* @author : RYAN0UP
* @date : 2017/12/15
*/
public abstract class BaseController {
public abstract class BaseFrontController {
/**
*