Commit c1bc259c authored by liaozan's avatar liaozan 🏀

feat: support preview

parent 17da53a4
package com.schbrain.archetype.initializer.controller;
import com.schbrain.archetype.initializer.maven.MavenUtils;
import com.schbrain.archetype.initializer.param.ArchetypeGenerateParam;
import com.schbrain.common.util.ServletUtils;
import org.springframework.http.ContentDisposition;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.util.StreamUtils;
import com.schbrain.archetype.initializer.response.PreviewFileTree;
import com.schbrain.archetype.initializer.service.ArchetypeService;
import com.schbrain.common.support.result.ResponseDTO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.List;
/**
* @author liaozan
......@@ -24,25 +19,22 @@ import java.io.IOException;
@RestController
public class InitializerController {
@PostMapping("/archetype/generate")
public void generateArchetype(@RequestBody @Validated ArchetypeGenerateParam param) throws IOException {
File generatedProject = MavenUtils.generate(param);
FileInputStream inputStream = new FileInputStream(generatedProject);
@Autowired
private ArchetypeService archetypeService;
HttpServletResponse response = ServletUtils.getResponse();
response.addHeader(HttpHeaders.CONTENT_DISPOSITION, contentDisposition(generatedProject));
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
@PostMapping("/archetype/generate")
public ResponseDTO<String> generateArchetype(@RequestBody @Validated ArchetypeGenerateParam param) {
return ResponseDTO.success(archetypeService.generate(param));
}
StreamUtils.copy(inputStream, response.getOutputStream());
@GetMapping("/archetype/preview/{id}")
public ResponseDTO<List<PreviewFileTree>> previewArchetype(@PathVariable String id) throws FileNotFoundException {
return ResponseDTO.success(archetypeService.preview(id));
}
private String contentDisposition(File file) {
return ContentDisposition
.attachment()
.filename(file.getName())
.build()
.toString();
@GetMapping("/archetype/download/{id}")
public void downloadArchetype(@PathVariable String id) throws IOException {
archetypeService.download(id);
}
}
\ No newline at end of file
package com.schbrain.archetype.initializer.maven;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ZipUtil;
import com.schbrain.archetype.initializer.param.ArchetypeGenerateParam;
import com.schbrain.common.util.IdWorker;
import com.schbrain.common.util.JacksonUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.maven.cli.MavenCli;
......@@ -10,6 +10,7 @@ import org.apache.maven.settings.Mirror;
import org.apache.maven.settings.Server;
import org.apache.maven.settings.Settings;
import org.apache.maven.settings.io.DefaultSettingsWriter;
import org.springframework.util.StringUtils;
import java.io.File;
import java.io.IOException;
......@@ -38,20 +39,21 @@ public class MavenUtils {
log.info("Success install archive of : {}", workDirectory);
}
public static File generate(ArchetypeGenerateParam param) {
public static String generate(ArchetypeGenerateParam param) {
log.info("Prepare to generate archetype project: {}", JacksonUtils.toJsonString(param, true));
MavenCli mavenCli = new MavenCli();
File archetype = getArchetypeDirectory(param.getArtifactId());
String id = IdWorker.getIdStr();
File archetype = getArchetypeDirectory(id);
String outputDirectory = archetype.getAbsolutePath();
String[] args = getArchetypeGenerateArgs(param, getSettingsFile().getAbsolutePath(), outputDirectory);
mavenCli.doMain(args, null, System.out, System.err);
log.info("Generate archetype project at {}", outputDirectory);
return ZipUtil.zip(archetype);
return id;
}
public static File getArchetypeDirectory(String id) {
String tmpDirPath = FileUtil.getTmpDirPath();
return new File(tmpDirPath, id);
}
private static File getSettingsFile() {
......@@ -88,14 +90,6 @@ public class MavenUtils {
return SETTINGS_FILE;
}
private static File getArchetypeDirectory(String artifactId) {
String tmpDirPath = FileUtil.getTmpDirPath();
File archetype = new File(tmpDirPath, artifactId);
// clean cache
FileUtil.del(archetype);
return archetype;
}
private static String[] getInstallArgs(String settingsFile) {
return new String[]{
"-B",
......@@ -107,6 +101,10 @@ public class MavenUtils {
@SuppressWarnings("SpellCheckingInspection")
private static String[] getArchetypeGenerateArgs(ArchetypeGenerateParam param, String settingsFile, String outputDirectory) {
String subModuleNamePrefix = param.getSubModuleNamePrefix();
if (!StringUtils.hasText(subModuleNamePrefix)) {
subModuleNamePrefix = param.getArtifactId();
}
return new String[]{
"-B",
"archetype:generate",
......@@ -117,6 +115,7 @@ public class MavenUtils {
String.format("-DoutputDirectory=%s", outputDirectory),
String.format("-DgroupId=%s", param.getGroupId()),
String.format("-DartifactId=%s", param.getArtifactId()),
String.format("-DsubModuleNamePrefix=%s", subModuleNamePrefix),
};
}
......
......@@ -11,9 +11,19 @@ import javax.validation.constraints.NotBlank;
@Data
public class ArchetypeGenerateParam {
/**
* groupId
*/
@NotBlank
private String groupId;
/**
* artifactId
*/
@NotBlank
private String artifactId;
/**
* 子模块前缀
*/
private String subModuleNamePrefix;
}
\ No newline at end of file
package com.schbrain.archetype.initializer.response;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.util.CollectionUtils;
import java.util.List;
/**
* @author liaozan
* @since 2022/3/20
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PreviewFileTree {
private String fileName;
private List<PreviewFileTree> children;
public boolean isFile() {
return CollectionUtils.isEmpty(children);
}
}
\ No newline at end of file
package com.schbrain.archetype.initializer.service;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ZipUtil;
import com.schbrain.archetype.initializer.maven.MavenUtils;
import com.schbrain.archetype.initializer.param.ArchetypeGenerateParam;
import com.schbrain.archetype.initializer.response.PreviewFileTree;
import com.schbrain.common.util.ServletUtils;
import org.springframework.http.ContentDisposition;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.util.StreamUtils;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
/**
* @author liaozan
* @since 2022/3/20
*/
@Service
public class ArchetypeService {
private final Map<String, String> archetypeNameCache = new ConcurrentHashMap<>();
public String generate(ArchetypeGenerateParam param) {
String generateId = MavenUtils.generate(param);
archetypeNameCache.put(generateId, param.getArtifactId());
return generateId;
}
public List<PreviewFileTree> preview(String id) throws FileNotFoundException {
File generatedFiles = getGeneratedFiles(id);
return List.of(buildFileTree(generatedFiles));
}
public void download(String id) throws IOException {
File generatedFiles = ZipUtil.zip(getGeneratedFiles(id));
FileInputStream inputStream = new FileInputStream(generatedFiles);
HttpServletResponse response = ServletUtils.getResponse();
response.addHeader(HttpHeaders.CONTENT_DISPOSITION, contentDisposition(generatedFiles));
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
StreamUtils.copy(inputStream, response.getOutputStream());
}
private PreviewFileTree buildFileTree(File root) {
File[] fileItems = root.listFiles();
if (ArrayUtil.isEmpty(fileItems)) {
return new PreviewFileTree(root.getName(), Collections.emptyList());
}
List<PreviewFileTree> children = Arrays.stream(fileItems)
.map(fileItem -> {
PreviewFileTree childrenFileTree;
if (fileItem.isDirectory()) {
childrenFileTree = buildFileTree(fileItem);
} else {
childrenFileTree = new PreviewFileTree(fileItem.getName(), Collections.emptyList());
}
return childrenFileTree;
})
.sorted(Comparator.comparing(PreviewFileTree::isFile))
.collect(Collectors.toList());
PreviewFileTree fileTree = new PreviewFileTree();
fileTree.setFileName(root.getName());
fileTree.setChildren(children);
return fileTree;
}
private File getGeneratedFiles(String id) throws FileNotFoundException {
File archetypeDirectory = MavenUtils.getArchetypeDirectory(id);
if (!archetypeDirectory.exists()) {
throw new FileNotFoundException(archetypeDirectory.getAbsolutePath());
}
String artifactId = archetypeNameCache.get(id);
return new File(archetypeDirectory, artifactId);
}
private String contentDisposition(File file) {
return ContentDisposition
.attachment()
.filename(file.getName())
.build()
.toString();
}
}
\ No newline at end of file
......@@ -15,7 +15,18 @@
<input id="input-artifact" v-model="form.artifactId" class="form-input" type="text">
</div>
<input class="form-submit" type="button" value="下载" @click="submit">
<input class="form-submit" type="button" value="生成" @click="submit">
<input :disabled="this.id === null" class="form-submit" type="button" value="预览" @click="preview">
<input :disabled="this.id === null" class="form-submit" type="button" value="下载" @click="download">
<el-dialog v-model="dialogVisible" title="预览" width="30%">
<el-tree ref="fileTree" :data="treeData" :props="defaultProps" @node-click="handleNodeClick"/>
<template #footer>
<span class="dialog-footer">
<el-button type="primary" @click="dialogVisible = false">关闭</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
......@@ -28,6 +39,13 @@ export default {
name: "BackendStarter",
data() {
return {
id: null,
dialogVisible: false,
defaultProps: {
children: 'children',
label: 'fileName',
},
treeData: [],
form: {
groupId: '',
artifactId: '',
......@@ -36,20 +54,27 @@ export default {
},
methods: {
submit: function () {
axios.post('/archetype/generate', this.form, {
axios.post('/archetype/generate', this.form).then(res => {
this.id = res.data.result
}, error => {
ElMessageBox.alert(error.data.message)
})
},
preview: function () {
axios.get(`/archetype/preview/${this.id}`, this.form).then(res => {
this.treeData = res.data.result
this.dialogVisible = true
}, error => {
ElMessageBox.alert(error.data.message)
})
},
download: function () {
axios.get(`/archetype/download/${this.id}`, {
responseType: 'blob'
}).then(res => {
const {headers, data} = res
const fileName = headers['content-disposition'].replace(/\w+;\sfilename="(.*)"/, '$1')
const blob = new Blob([data])
const link = document.createElement('a');
link.style.display = "none";
link.href = URL.createObjectURL(blob);
link.download = fileName;
document.body.appendChild(link);
link.click();
URL.revokeObjectURL(link.href);
document.body.removeChild(link);
this.downloadGeneratedProject(data, fileName);
}, error => {
const reader = new FileReader();
reader.readAsText(error.data, 'utf-8');
......@@ -60,6 +85,20 @@ export default {
)
}
})
},
handleNodeClick: function (data) {
console.log(data)
},
downloadGeneratedProject: function (data, fileName) {
const blob = new Blob([data])
const link = document.createElement('a');
link.style.display = "none";
link.href = URL.createObjectURL(blob);
link.download = fileName;
document.body.appendChild(link);
link.click();
URL.revokeObjectURL(link.href);
document.body.removeChild(link);
}
}
}
......@@ -113,7 +152,7 @@ export default {
}
.form-submit {
margin-top: 10px;
margin: 10px 5px 0 5px;
background: -webkit-gradient(linear, left top, left bottom, color-stop(0.05, #ff5bb0), color-stop(1, #ef027d));
border-radius: 9px;
-webkit-border-radius: 9px;
......@@ -123,11 +162,18 @@ export default {
font-size: 15px;
font-weight: bold;
font-style: normal;
height: 40px;
height: 35px;
line-height: 30px;
width: 100px;
width: 60px;
text-decoration: none;
text-shadow: 1px 1px 0 #c70067;
cursor: pointer;
}
.form-submit:disabled {
background: -webkit-gradient(linear, left top, left bottom, color-stop(0.05, #000000), color-stop(1, #000000));
border: 1px solid #000000;
text-shadow: 1px 1px 0 #000000;
cursor: not-allowed;
}
</style>
\ No newline at end of file
......@@ -18,14 +18,15 @@ service.interceptors.request.use(config => {
service.interceptors.response.use(response => {
hideLoading()
if (response.data.type === 'application/json') {
if (response.data.code !== 200) {
if (response.headers['content-type'] === 'application/json') {
if (response.data.code !== 0) {
return Promise.reject(response);
}
}
return response
}, error => {
hideLoading()
return Promise.reject(error)
})
export default service
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment