feat: support deploy static page in netlify.

pull/471/head^2
ruibaby 2019-12-26 20:17:03 +08:00
parent 83e87553b7
commit c37f36f61e
14 changed files with 185 additions and 14 deletions

View File

@ -35,6 +35,7 @@ public class Application extends SpringBootServletInitializer {
// Run application // Run application
context = SpringApplication.run(Application.class, args); context = SpringApplication.run(Application.class, args);
} }
/** /**

View File

@ -1,26 +1,55 @@
package run.halo.app.controller.admin.api; package run.halo.app.controller.admin.api;
import cn.hutool.core.io.FileUtil;
import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiOperation;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import run.halo.app.model.properties.NetlifyStaticDeployProperties;
import run.halo.app.model.support.StaticPageFile; import run.halo.app.model.support.StaticPageFile;
import run.halo.app.service.OptionService;
import run.halo.app.service.StaticPageService; import run.halo.app.service.StaticPageService;
import java.io.FileNotFoundException;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List; import java.util.List;
/** /**
* @author ryan0up * Static page controller.
* @date 2019/12/25 *
* @author ryanwang
* @date 2019-12-25
*/ */
@RestController @RestController
@RequestMapping("/api/admin/static_page") @RequestMapping("/api/admin/static_page")
public class StaticPageController { public class StaticPageController {
private final static String DEPLOY_API = "https://api.netlify.com/api/v1/sites/%s/deploys";
private final OptionService optionService;
private final RestTemplate httpsRestTemplate;
private final StaticPageService staticPageService; private final StaticPageService staticPageService;
public StaticPageController(StaticPageService staticPageService) { public StaticPageController(StaticPageService staticPageService,
OptionService optionService,
RestTemplate httpsRestTemplate) {
this.staticPageService = staticPageService; this.staticPageService = staticPageService;
this.optionService = optionService;
this.httpsRestTemplate = httpsRestTemplate;
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
mappingJackson2HttpMessageConverter.setSupportedMediaTypes(Collections.singletonList(MediaType.ALL));
this.httpsRestTemplate.getMessageConverters().add(mappingJackson2HttpMessageConverter);
} }
@GetMapping @GetMapping
@ -35,9 +64,29 @@ public class StaticPageController {
staticPageService.generate(); staticPageService.generate();
} }
@GetMapping("deploy") @PostMapping("deploy")
@ApiOperation("Deploy static page to remove platform") @ApiOperation("Deploy static page to remove platform")
public void deploy() { public void deploy() {
staticPageService.deploy(); staticPageService.deploy();
} }
@GetMapping("netlify")
public void testNetlify() throws FileNotFoundException {
String domain = optionService.getByPropertyOfNonNull(NetlifyStaticDeployProperties.NETLIFY_DOMAIN).toString();
String siteId = optionService.getByPropertyOfNonNull(NetlifyStaticDeployProperties.NETLIFY_SITE_ID).toString();
String token = optionService.getByPropertyOfNonNull(NetlifyStaticDeployProperties.NETLIFY_TOKEN).toString();
HttpHeaders headers = new HttpHeaders();
headers.set("Content-Type", "application/zip");
headers.set(HttpHeaders.AUTHORIZATION, "Bearer " + token);
Path path = staticPageService.zipStaticPagesDirectory();
byte[] bytes = FileUtil.readBytes(path.toFile());
HttpEntity<byte[]> httpEntity = new HttpEntity<>(bytes, headers);
ResponseEntity<Object> responseEntity = httpsRestTemplate.postForEntity(String.format(DEPLOY_API, siteId), httpEntity, Object.class);
}
} }

View File

@ -1,5 +1,7 @@
package run.halo.app.handler.migrate; package run.halo.app.handler.migrate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import run.halo.app.model.enums.MigrateType; import run.halo.app.model.enums.MigrateType;
@ -9,6 +11,8 @@ import run.halo.app.model.enums.MigrateType;
* @author ryanwang * @author ryanwang
* @date 2019-10-30 * @date 2019-10-30
*/ */
@Slf4j
@Component
public class CnBlogsMigrateHandler implements MigrateHandler { public class CnBlogsMigrateHandler implements MigrateHandler {
@Override @Override

View File

@ -31,7 +31,7 @@ public class MigrateHandlers {
public MigrateHandlers(ApplicationContext applicationContext) { public MigrateHandlers(ApplicationContext applicationContext) {
// Add all migrate handler // Add all migrate handler
addFileHandlers(applicationContext.getBeansOfType(MigrateHandler.class).values()); addMigrateHandlers(applicationContext.getBeansOfType(MigrateHandler.class).values());
} }
@NonNull @NonNull
@ -56,7 +56,7 @@ public class MigrateHandlers {
* @return current migrate handlers * @return current migrate handlers
*/ */
@NonNull @NonNull
private MigrateHandlers addFileHandlers(@Nullable Collection<MigrateHandler> migrateHandlers) { private MigrateHandlers addMigrateHandlers(@Nullable Collection<MigrateHandler> migrateHandlers) {
if (!CollectionUtils.isEmpty(migrateHandlers)) { if (!CollectionUtils.isEmpty(migrateHandlers)) {
this.migrateHandlers.addAll(migrateHandlers); this.migrateHandlers.addAll(migrateHandlers);
} }

View File

@ -1,9 +1,18 @@
package run.halo.app.handler.staticdeploy; package run.halo.app.handler.staticdeploy;
import cn.hutool.core.io.FileUtil;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import run.halo.app.model.enums.StaticDeployType; import run.halo.app.model.enums.StaticDeployType;
import run.halo.app.model.properties.NetlifyStaticDeployProperties;
import run.halo.app.service.OptionService; import run.halo.app.service.OptionService;
import run.halo.app.service.StaticPageService;
import java.nio.file.Path;
/** /**
* Netlify deploy handler. * Netlify deploy handler.
@ -15,15 +24,40 @@ import run.halo.app.service.OptionService;
@Component @Component
public class NetlifyStaticDeployHandler implements StaticDeployHandler { public class NetlifyStaticDeployHandler implements StaticDeployHandler {
private final static String DEPLOY_API = "https://api.netlify.com/api/v1/sites/%s/deploys";
private final OptionService optionService; private final OptionService optionService;
public NetlifyStaticDeployHandler(OptionService optionService) { private final RestTemplate httpsRestTemplate;
private final StaticPageService staticPageService;
public NetlifyStaticDeployHandler(OptionService optionService,
RestTemplate httpsRestTemplate,
StaticPageService staticPageService) {
this.optionService = optionService; this.optionService = optionService;
this.httpsRestTemplate = httpsRestTemplate;
this.staticPageService = staticPageService;
} }
@Override @Override
public void deploy() { public void deploy() {
String domain = optionService.getByPropertyOfNonNull(NetlifyStaticDeployProperties.NETLIFY_DOMAIN).toString();
String siteId = optionService.getByPropertyOfNonNull(NetlifyStaticDeployProperties.NETLIFY_SITE_ID).toString();
String token = optionService.getByPropertyOfNonNull(NetlifyStaticDeployProperties.NETLIFY_TOKEN).toString();
HttpHeaders headers = new HttpHeaders();
headers.set("Content-Type", "application/zip");
headers.set(HttpHeaders.AUTHORIZATION, "Bearer " + token);
Path path = staticPageService.zipStaticPagesDirectory();
byte[] bytes = FileUtil.readBytes(path.toFile());
HttpEntity<byte[]> httpEntity = new HttpEntity<>(bytes, headers);
ResponseEntity<Object> responseEntity = httpsRestTemplate.postForEntity(String.format(DEPLOY_API, siteId), httpEntity, Object.class);
} }
@Override @Override

View File

@ -26,8 +26,8 @@ public class StaticDeployHandlers {
private final Collection<StaticDeployHandler> staticDeployHandlers = new LinkedList<>(); private final Collection<StaticDeployHandler> staticDeployHandlers = new LinkedList<>();
public StaticDeployHandlers(ApplicationContext applicationContext) { public StaticDeployHandlers(ApplicationContext applicationContext) {
// Add all file handler // Add all static deploy handler
addFileHandlers(applicationContext.getBeansOfType(StaticDeployHandler.class).values()); addStaticDeployHandlers(applicationContext.getBeansOfType(StaticDeployHandler.class).values());
} }
@ -42,6 +42,7 @@ public class StaticDeployHandlers {
for (StaticDeployHandler staticDeployHandler : staticDeployHandlers) { for (StaticDeployHandler staticDeployHandler : staticDeployHandlers) {
if (staticDeployHandler.supportType(staticDeployType)) { if (staticDeployHandler.supportType(staticDeployType)) {
staticDeployHandler.deploy(); staticDeployHandler.deploy();
return;
} }
} }
@ -55,7 +56,7 @@ public class StaticDeployHandlers {
* @return current file handlers * @return current file handlers
*/ */
@NonNull @NonNull
public StaticDeployHandlers addFileHandlers(@Nullable Collection<StaticDeployHandler> staticDeployHandlers) { public StaticDeployHandlers addStaticDeployHandlers(@Nullable Collection<StaticDeployHandler> staticDeployHandlers) {
if (!CollectionUtils.isEmpty(staticDeployHandlers)) { if (!CollectionUtils.isEmpty(staticDeployHandlers)) {
this.staticDeployHandlers.addAll(staticDeployHandlers); this.staticDeployHandlers.addAll(staticDeployHandlers);
} }

View File

@ -4,7 +4,7 @@ package run.halo.app.model.enums;
* Migrate type. * Migrate type.
* *
* @author ryanwang * @author ryanwang
* @date : 2019-03-12 * @date 2019-03-12
*/ */
public enum MigrateType implements ValueEnum<Integer> { public enum MigrateType implements ValueEnum<Integer> {

View File

@ -11,12 +11,12 @@ public enum StaticDeployType implements ValueEnum<Integer> {
/** /**
* Deploy static pages in remote git repository, such as github pages,gitee pages,coding pages.etc. * Deploy static pages in remote git repository, such as github pages,gitee pages,coding pages.etc.
*/ */
GIT(1), GIT(0),
/** /**
* Deploy static pages in netlify. * Deploy static pages in netlify.
*/ */
NETLIFY(2); NETLIFY(1);
private Integer value; private Integer value;

View File

@ -30,6 +30,11 @@ public class HaloConst {
*/ */
public final static String HALO_BACKUP_PREFIX = "halo-backup-"; public final static String HALO_BACKUP_PREFIX = "halo-backup-";
/**
* Static pages pack prefix.
*/
public final static String STATIC_PAGE_PACK_PREFIX = "static-pages-";
/** /**
* Default theme name. * Default theme name.
*/ */

View File

@ -16,6 +16,8 @@ import java.util.List;
@ToString @ToString
public class StaticFile implements Comparator<StaticFile> { public class StaticFile implements Comparator<StaticFile> {
private String id;
private String name; private String name;
private String path; private String path;

View File

@ -14,6 +14,8 @@ import java.util.List;
@Data @Data
public class StaticPageFile implements Comparator<StaticPageFile> { public class StaticPageFile implements Comparator<StaticPageFile> {
private String id;
private String name; private String name;
private Boolean isFile; private Boolean isFile;

View File

@ -2,8 +2,13 @@ package run.halo.app.service;
import run.halo.app.model.support.StaticPageFile; import run.halo.app.model.support.StaticPageFile;
import java.nio.file.Path;
import java.util.List; import java.util.List;
import static run.halo.app.model.support.HaloConst.FILE_SEPARATOR;
import static run.halo.app.model.support.HaloConst.TEMP_DIR;
import static run.halo.app.utils.HaloUtils.ensureSuffix;
/** /**
* Static Page service interface. * Static Page service interface.
* *
@ -17,6 +22,11 @@ public interface StaticPageService {
*/ */
String PAGES_FOLDER = "static_pages"; String PAGES_FOLDER = "static_pages";
String STATIC_PAGE_PACK_DIR = ensureSuffix(TEMP_DIR, FILE_SEPARATOR) + "static-pages-pack" + FILE_SEPARATOR;
String[] USELESS_FILE_SUFFIX = {"ftl", "md", "yaml", "yml", "gitignore"};
/** /**
* Generate pages. * Generate pages.
*/ */
@ -27,6 +37,13 @@ public interface StaticPageService {
*/ */
void deploy(); void deploy();
/**
* Zip static pages directory.
*
* @return zip path
*/
Path zipStaticPagesDirectory();
/** /**
* List file of generated static page. * List file of generated static page.
* *

View File

@ -1,6 +1,8 @@
package run.halo.app.service.impl; package run.halo.app.service.impl;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.file.FileWriter; import cn.hutool.core.io.file.FileWriter;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.PageUtil; import cn.hutool.core.util.PageUtil;
import freemarker.template.Template; import freemarker.template.Template;
import freemarker.template.TemplateException; import freemarker.template.TemplateException;
@ -42,6 +44,8 @@ import java.nio.charset.StandardCharsets;
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.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -123,6 +127,7 @@ public class StaticPageServiceImpl implements StaticPageService {
pagesDir = Paths.get(haloProperties.getWorkDir(), PAGES_FOLDER); pagesDir = Paths.get(haloProperties.getWorkDir(), PAGES_FOLDER);
FileUtils.createIfAbsent(pagesDir); FileUtils.createIfAbsent(pagesDir);
Files.createDirectories(Paths.get(STATIC_PAGE_PACK_DIR));
} }
@Override @Override
@ -158,6 +163,22 @@ public class StaticPageServiceImpl implements StaticPageService {
staticDeployHandlers.deploy(type); staticDeployHandlers.deploy(type);
} }
@Override
public Path zipStaticPagesDirectory() {
try {
String staticPagePackName = HaloConst.STATIC_PAGE_PACK_PREFIX +
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss-")) +
IdUtil.simpleUUID().hashCode() + ".zip";
Path staticPageZipPath = Files.createFile(Paths.get(STATIC_PAGE_PACK_DIR, staticPagePackName));
FileUtils.zip(pagesDir, staticPageZipPath);
return staticPageZipPath;
} catch (IOException e) {
throw new ServiceException("Failed to zip static pages directory", e);
}
}
@Override @Override
public List<StaticPageFile> listFile() { public List<StaticPageFile> listFile() {
return listStaticPageFileTree(pagesDir); return listStaticPageFileTree(pagesDir);
@ -176,6 +197,7 @@ public class StaticPageServiceImpl implements StaticPageService {
pathStream.forEach(path -> { pathStream.forEach(path -> {
StaticPageFile staticPageFile = new StaticPageFile(); StaticPageFile staticPageFile = new StaticPageFile();
staticPageFile.setId(IdUtil.fastSimpleUUID());
staticPageFile.setName(path.getFileName().toString()); staticPageFile.setName(path.getFileName().toString());
staticPageFile.setIsFile(Files.isRegularFile(path)); staticPageFile.setIsFile(Files.isRegularFile(path));
if (Files.isDirectory(path)) { if (Files.isDirectory(path)) {
@ -708,9 +730,41 @@ public class StaticPageServiceImpl implements StaticPageService {
*/ */
private void copyThemeFolder() throws IOException { private void copyThemeFolder() throws IOException {
ThemeProperty activatedTheme = themeService.getActivatedTheme(); ThemeProperty activatedTheme = themeService.getActivatedTheme();
Path path = Paths.get(pagesDir.toString(), activatedTheme.getId()); Path path = Paths.get(pagesDir.toString(), activatedTheme.getFolderName());
FileUtils.createIfAbsent(path); FileUtils.createIfAbsent(path);
FileUtils.copyFolder(Paths.get(activatedTheme.getThemePath()), path); FileUtils.copyFolder(Paths.get(activatedTheme.getThemePath()), path);
cleanThemeFolder(Paths.get(pagesDir.toString(), activatedTheme.getFolderName()));
}
private void cleanThemeFolder(Path themePath) {
if (!Files.isDirectory(themePath)) {
return;
}
try (Stream<Path> pathStream = Files.list(themePath)) {
pathStream.forEach(path -> {
if (!Files.isDirectory(path)) {
for (String suffix : USELESS_FILE_SUFFIX) {
if (suffix.contains(FileUtil.extName(path.toFile()))) {
try {
Files.delete(path);
} catch (IOException e) {
e.printStackTrace();
}
}
}
} else {
if (path.getFileName().toString().contains(".git")) {
FileUtils.deleteFolderQuietly(path);
} else {
cleanThemeFolder(path);
}
}
});
} catch (IOException e) {
throw new ServiceException("Failed to list sub files", e);
}
} }
/** /**

View File

@ -1,5 +1,6 @@
package run.halo.app.service.impl; package run.halo.app.service.impl;
import cn.hutool.core.util.IdUtil;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.lang.NonNull; import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
@ -59,6 +60,7 @@ public class StaticStorageServiceImpl implements StaticStorageService {
pathStream.forEach(path -> { pathStream.forEach(path -> {
StaticFile staticFile = new StaticFile(); StaticFile staticFile = new StaticFile();
staticFile.setId(IdUtil.fastSimpleUUID());
staticFile.setName(path.getFileName().toString()); staticFile.setName(path.getFileName().toString());
staticFile.setPath(path.toString()); staticFile.setPath(path.toString());
staticFile.setRelativePath(StringUtils.removeStart(path.toString(), staticDir.toString())); staticFile.setRelativePath(StringUtils.removeStart(path.toString(), staticDir.toString()));