Commit c1bc259c authored by liaozan's avatar liaozan 🏀

feat: support preview

parent 17da53a4
package com.schbrain.archetype.initializer.controller; package com.schbrain.archetype.initializer.controller;
import com.schbrain.archetype.initializer.maven.MavenUtils;
import com.schbrain.archetype.initializer.param.ArchetypeGenerateParam; import com.schbrain.archetype.initializer.param.ArchetypeGenerateParam;
import com.schbrain.common.util.ServletUtils; import com.schbrain.archetype.initializer.response.PreviewFileTree;
import org.springframework.http.ContentDisposition; import com.schbrain.archetype.initializer.service.ArchetypeService;
import org.springframework.http.HttpHeaders; import com.schbrain.common.support.result.ResponseDTO;
import org.springframework.http.MediaType; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StreamUtils;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse; import java.io.FileNotFoundException;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.util.List;
/** /**
* @author liaozan * @author liaozan
...@@ -24,25 +19,22 @@ import java.io.IOException; ...@@ -24,25 +19,22 @@ import java.io.IOException;
@RestController @RestController
public class InitializerController { public class InitializerController {
@PostMapping("/archetype/generate") @Autowired
public void generateArchetype(@RequestBody @Validated ArchetypeGenerateParam param) throws IOException { private ArchetypeService archetypeService;
File generatedProject = MavenUtils.generate(param);
FileInputStream inputStream = new FileInputStream(generatedProject);
HttpServletResponse response = ServletUtils.getResponse(); @PostMapping("/archetype/generate")
response.addHeader(HttpHeaders.CONTENT_DISPOSITION, contentDisposition(generatedProject)); public ResponseDTO<String> generateArchetype(@RequestBody @Validated ArchetypeGenerateParam param) {
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); 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) { @GetMapping("/archetype/download/{id}")
return ContentDisposition public void downloadArchetype(@PathVariable String id) throws IOException {
.attachment() archetypeService.download(id);
.filename(file.getName())
.build()
.toString();
} }
} }
\ No newline at end of file
package com.schbrain.archetype.initializer.maven; package com.schbrain.archetype.initializer.maven;
import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ZipUtil;
import com.schbrain.archetype.initializer.param.ArchetypeGenerateParam; import com.schbrain.archetype.initializer.param.ArchetypeGenerateParam;
import com.schbrain.common.util.IdWorker;
import com.schbrain.common.util.JacksonUtils; import com.schbrain.common.util.JacksonUtils;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.maven.cli.MavenCli; import org.apache.maven.cli.MavenCli;
...@@ -10,6 +10,7 @@ import org.apache.maven.settings.Mirror; ...@@ -10,6 +10,7 @@ import org.apache.maven.settings.Mirror;
import org.apache.maven.settings.Server; import org.apache.maven.settings.Server;
import org.apache.maven.settings.Settings; import org.apache.maven.settings.Settings;
import org.apache.maven.settings.io.DefaultSettingsWriter; import org.apache.maven.settings.io.DefaultSettingsWriter;
import org.springframework.util.StringUtils;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
...@@ -38,20 +39,21 @@ public class MavenUtils { ...@@ -38,20 +39,21 @@ public class MavenUtils {
log.info("Success install archive of : {}", workDirectory); 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)); log.info("Prepare to generate archetype project: {}", JacksonUtils.toJsonString(param, true));
MavenCli mavenCli = new MavenCli(); MavenCli mavenCli = new MavenCli();
String id = IdWorker.getIdStr();
File archetype = getArchetypeDirectory(param.getArtifactId()); File archetype = getArchetypeDirectory(id);
String outputDirectory = archetype.getAbsolutePath(); String outputDirectory = archetype.getAbsolutePath();
String[] args = getArchetypeGenerateArgs(param, getSettingsFile().getAbsolutePath(), outputDirectory); String[] args = getArchetypeGenerateArgs(param, getSettingsFile().getAbsolutePath(), outputDirectory);
mavenCli.doMain(args, null, System.out, System.err); mavenCli.doMain(args, null, System.out, System.err);
log.info("Generate archetype project at {}", outputDirectory); 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() { private static File getSettingsFile() {
...@@ -88,14 +90,6 @@ public class MavenUtils { ...@@ -88,14 +90,6 @@ public class MavenUtils {
return SETTINGS_FILE; 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) { private static String[] getInstallArgs(String settingsFile) {
return new String[]{ return new String[]{
"-B", "-B",
...@@ -107,6 +101,10 @@ public class MavenUtils { ...@@ -107,6 +101,10 @@ public class MavenUtils {
@SuppressWarnings("SpellCheckingInspection") @SuppressWarnings("SpellCheckingInspection")
private static String[] getArchetypeGenerateArgs(ArchetypeGenerateParam param, String settingsFile, String outputDirectory) { private static String[] getArchetypeGenerateArgs(ArchetypeGenerateParam param, String settingsFile, String outputDirectory) {
String subModuleNamePrefix = param.getSubModuleNamePrefix();
if (!StringUtils.hasText(subModuleNamePrefix)) {
subModuleNamePrefix = param.getArtifactId();
}
return new String[]{ return new String[]{
"-B", "-B",
"archetype:generate", "archetype:generate",
...@@ -117,6 +115,7 @@ public class MavenUtils { ...@@ -117,6 +115,7 @@ public class MavenUtils {
String.format("-DoutputDirectory=%s", outputDirectory), String.format("-DoutputDirectory=%s", outputDirectory),
String.format("-DgroupId=%s", param.getGroupId()), String.format("-DgroupId=%s", param.getGroupId()),
String.format("-DartifactId=%s", param.getArtifactId()), String.format("-DartifactId=%s", param.getArtifactId()),
String.format("-DsubModuleNamePrefix=%s", subModuleNamePrefix),
}; };
} }
......
...@@ -11,9 +11,19 @@ import javax.validation.constraints.NotBlank; ...@@ -11,9 +11,19 @@ import javax.validation.constraints.NotBlank;
@Data @Data
public class ArchetypeGenerateParam { public class ArchetypeGenerateParam {
/**
* groupId
*/
@NotBlank @NotBlank
private String groupId; private String groupId;
/**
* artifactId
*/
@NotBlank @NotBlank
private String artifactId; 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 @@ ...@@ -15,7 +15,18 @@
<input id="input-artifact" v-model="form.artifactId" class="form-input" type="text"> <input id="input-artifact" v-model="form.artifactId" class="form-input" type="text">
</div> </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> </div>
</template> </template>
...@@ -28,6 +39,13 @@ export default { ...@@ -28,6 +39,13 @@ export default {
name: "BackendStarter", name: "BackendStarter",
data() { data() {
return { return {
id: null,
dialogVisible: false,
defaultProps: {
children: 'children',
label: 'fileName',
},
treeData: [],
form: { form: {
groupId: '', groupId: '',
artifactId: '', artifactId: '',
...@@ -36,20 +54,27 @@ export default { ...@@ -36,20 +54,27 @@ export default {
}, },
methods: { methods: {
submit: function () { 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' responseType: 'blob'
}).then(res => { }).then(res => {
const {headers, data} = res const {headers, data} = res
const fileName = headers['content-disposition'].replace(/\w+;\sfilename="(.*)"/, '$1') const fileName = headers['content-disposition'].replace(/\w+;\sfilename="(.*)"/, '$1')
const blob = new Blob([data]) this.downloadGeneratedProject(data, fileName);
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);
}, error => { }, error => {
const reader = new FileReader(); const reader = new FileReader();
reader.readAsText(error.data, 'utf-8'); reader.readAsText(error.data, 'utf-8');
...@@ -60,6 +85,20 @@ export default { ...@@ -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 { ...@@ -113,7 +152,7 @@ export default {
} }
.form-submit { .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)); background: -webkit-gradient(linear, left top, left bottom, color-stop(0.05, #ff5bb0), color-stop(1, #ef027d));
border-radius: 9px; border-radius: 9px;
-webkit-border-radius: 9px; -webkit-border-radius: 9px;
...@@ -123,11 +162,18 @@ export default { ...@@ -123,11 +162,18 @@ export default {
font-size: 15px; font-size: 15px;
font-weight: bold; font-weight: bold;
font-style: normal; font-style: normal;
height: 40px; height: 35px;
line-height: 30px; line-height: 30px;
width: 100px; width: 60px;
text-decoration: none; text-decoration: none;
text-shadow: 1px 1px 0 #c70067; text-shadow: 1px 1px 0 #c70067;
cursor: pointer; 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> </style>
\ No newline at end of file
...@@ -18,14 +18,15 @@ service.interceptors.request.use(config => { ...@@ -18,14 +18,15 @@ service.interceptors.request.use(config => {
service.interceptors.response.use(response => { service.interceptors.response.use(response => {
hideLoading() hideLoading()
if (response.data.type === 'application/json') { if (response.headers['content-type'] === 'application/json') {
if (response.data.code !== 200) { if (response.data.code !== 0) {
return Promise.reject(response); return Promise.reject(response);
} }
} }
return response return response
}, error => { }, error => {
hideLoading() hideLoading()
return Promise.reject(error)
}) })
export default service 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