From 4191cdf16e3376bf43f63ef42a26712db931b13d Mon Sep 17 00:00:00 2001 From: ruibaby Date: Sat, 16 Mar 2019 17:22:13 +0800 Subject: [PATCH] v1.0 --- .../java/cc/ryanc/halo/utils/HaloUtils.java | 125 -------- .../java/cc/ryanc/halo/utils/ThemeUtils.java | 151 ++++++++++ .../web/controller/admin/ThemeController.java | 280 +++++++++++++++++- .../controller/admin/base/BaseController.java | 48 +++ ...ntroller.java => BaseFrontController.java} | 2 +- 5 files changed, 464 insertions(+), 142 deletions(-) create mode 100644 src/main/java/cc/ryanc/halo/utils/ThemeUtils.java create mode 100644 src/main/java/cc/ryanc/halo/web/controller/admin/base/BaseController.java rename src/main/java/cc/ryanc/halo/web/controller/core/{BaseController.java => BaseFrontController.java} (95%) mode change 100755 => 100644 diff --git a/src/main/java/cc/ryanc/halo/utils/HaloUtils.java b/src/main/java/cc/ryanc/halo/utils/HaloUtils.java index be54b7c4f..b6709a6f0 100755 --- a/src/main/java/cc/ryanc/halo/utils/HaloUtils.java +++ b/src/main/java/cc/ryanc/halo/utils/HaloUtils.java @@ -205,131 +205,6 @@ public class HaloUtils { return ""; } - /** - * Scan internal themes and user's themes - * - * @return List - */ - public static List getThemes() { - final List 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 - */ - private static List getThemesByPath(File themesPath) { - final List 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 getTplName(String theme) { - final List 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 getCustomTpl(String theme) { - final List 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; - } - /** * 导出为文件 * diff --git a/src/main/java/cc/ryanc/halo/utils/ThemeUtils.java b/src/main/java/cc/ryanc/halo/utils/ThemeUtils.java new file mode 100644 index 000000000..da3ecb7e4 --- /dev/null +++ b/src/main/java/cc/ryanc/halo/utils/ThemeUtils.java @@ -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 getThemes() { + final List 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 + */ + private static List getThemesByPath(File themesPath) { + final List 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 + */ + public static List getTplName(String theme) { + final List 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 getCustomTpl(String theme) { + final List 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; + } +} diff --git a/src/main/java/cc/ryanc/halo/web/controller/admin/ThemeController.java b/src/main/java/cc/ryanc/halo/web/controller/admin/ThemeController.java index de3c3856f..0fdf23c75 100644 --- a/src/main/java/cc/ryanc/halo/web/controller/admin/ThemeController.java +++ b/src/main/java/cc/ryanc/halo/web/controller/admin/ThemeController.java @@ -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 options = optionService.listOptions(); - model.addAttribute("options",options); - - List 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 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")); + } } diff --git a/src/main/java/cc/ryanc/halo/web/controller/admin/base/BaseController.java b/src/main/java/cc/ryanc/halo/web/controller/admin/base/BaseController.java new file mode 100644 index 000000000..69f120058 --- /dev/null +++ b/src/main/java/cc/ryanc/halo/web/controller/admin/base/BaseController.java @@ -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(); + } + } +} diff --git a/src/main/java/cc/ryanc/halo/web/controller/core/BaseController.java b/src/main/java/cc/ryanc/halo/web/controller/core/BaseFrontController.java old mode 100755 new mode 100644 similarity index 95% rename from src/main/java/cc/ryanc/halo/web/controller/core/BaseController.java rename to src/main/java/cc/ryanc/halo/web/controller/core/BaseFrontController.java index 43240e43d..baaa22074 --- a/src/main/java/cc/ryanc/halo/web/controller/core/BaseController.java +++ b/src/main/java/cc/ryanc/halo/web/controller/core/BaseFrontController.java @@ -11,7 +11,7 @@ import cn.hutool.core.text.StrBuilder; * @author : RYAN0UP * @date : 2017/12/15 */ -public abstract class BaseController { +public abstract class BaseFrontController { /** * 定义默认主题