项目场景:
公司产品后台服务版本升级之增量资源包打包功能开发:每一次版本升级的增量sql(SpringBoot项目的resources下)和增量sql相对应的资源(在资源云服务器上:Centos7系统)打包下载接口。
涉及知识点:
- 浏览器访问API,资源包直接下载本地磁盘
- 访问resources下指定目录的文件
- Java删除Linux文件
- Zip文件打包(提供ZipUtil)
业务:运维人员登录后台系统点击资源打包功能,下载压缩的资源zip包上传到另一个云服务器(专门用于版本升级的),用户的本地服务器可以定时检测同步是否有新版本,从而下载对应的资源包,话不多说,上代码:
Controller层:
@Api(tags = "版本与license Controller")
@RequestMapping("/version")
@RestController
public class VersionController extends BaseController {
@Resource
private VersionService versionService;
@ApiOperation(value = "生成资源压缩包")
@GetMapping("/admin/generateResourceZip")
@SysLog(value = "生成压缩资源包", isEdit = false)
public void generateResourceZip(HttpServletResponse response) throws Exception {
String resourcePath = versionService.generateResourceZip();
String filename = URLEncoder.encode(resourcePath.substring(resourcePath.lastIndexOf("/")+1), "UTF-8");
response.setContentType("application/x-download");
response.setHeader("Content-Disposition", "attachment;filename=" + filename);//浏览器上提示下载时默认的文件名
try (ServletOutputStream out = response.getOutputStream();
InputStream stream = new FileInputStream(resourcePath)){//读取服务器上的文件
byte buff[] = new byte[1024];
int length = 0;
while ((length = stream.read(buff)) > 0) {
out.write(buff,0,length);
}
stream.close();
out.close();
out.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Service层:
/**
* @Author: yz
* @Date 14:42 2023/1/7
* @Param []
* @return 版本资源打包功能
*/
@Override
public String generateResourceZip() throws Exception {
//找到对应的增量sql文件
//加载资源文件
String path = Constants.RESOURCE_SQL_PATH;
final Resource[] resources = new PathMatchingResourcePatternResolver().getResources(ResourceUtils.CLASSPATH_URL_PREFIX + path);
log.info("开始扫描增量sql");
//存储在fastdfs上的路径集合
List<String> fastdfsList = new ArrayList<>();
//存储在nginx下xxa目录下的路径集合
List<String> nginxXxaList = new ArrayList<>();
//存储在nginx下xxb目录下的路径集合
List<String> nginxXxbList = new ArrayList<>();
//存储在nginx下xxc目录下的路径集合
List<String> nginxXxcList = new ArrayList<>();
//存储在nginx下xxd目录下的路径集合
List<String> nginxXxdList = new ArrayList<>();
//读取增量sql文件的资源path内容
for (int i = 0; i < resources.length; i++) {
Resource resource = resources[i];
// 遍历文件内容
StringBuffer script = new StringBuffer();
try(InputStreamReader isr = new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8);
BufferedReader bufferReader = new BufferedReader(isr)) {
String tempString;
while ((tempString = bufferReader.readLine()) != null) {
script.append(tempString).append("\n");
}
} catch (IOException e) {
e.printStackTrace();
}
String content = script.toString();
//判断资源存储于fastdfs、nginx下xxd、xxb、xxa、xxc下
String[] fsatdfsSplit = content.split(Constants.FASTDFS_PATH);
String[] xxaSplit = content.split(Constants.SPECIAL_INTERACTION_PATH);
String[] xxbSplit = content.split(Constants.EXAM_INTERACTION_PATH);
String[] xxcSplit = content.split(Constants.INTERACTION_UPLOAD_PATH);
String[] xxdSplit = content.split(Constants.INTERACTION_ASSESS_PATH);
if(fsatdfsSplit.length>1){
fastdfsPackage(content,fastdfsList);
}
if(xxaSplit.length>1){
for (int j = 0; j < xxaSplit.length; j++) {
if(j>0){
nginxXxaList.add(Constants.SPECIAL_INTERACTION_PATH+xxaSplit[j].substring(0,xxaSplit[j].indexOf("\'")));
}
}
}
if(xxbSplit.length>1){
for (int j = 0; j < xxbSplit.length; j++) {
if(j>0){
nginxXxbList.add(Constants.EXAM_INTERACTION_PATH+xxbSplit[j].substring(0,xxbSplit[j].indexOf("\'")));
}
}
}
if(xxcSplit.length>1){
for (int j = 0; j < xxcSplit.length; j++) {
if(j>0){
nginxXxcList.add(Constants.INTERACTION_UPLOAD_PATH+xxcSplit[j].substring(0,xxcSplit[j].indexOf("\'")-1));
}
}
}
if(xxdSplit.length>1){
for (int j = 0; j < xxdSplit.length; j++) {
if(j>0){
nginxXxdList.add(Constants.INTERACTION_ASSESS_PATH+xxdSplit[j].substring(0,xxdSplit[j].indexOf("\'")-1));
}
}
}
}
//根据path内容cp资源到服务器的mnt目录下(条件判断必须是linux环境下)
//获取当前环境
String osName = System.getProperty("os.name");
String fileUploadPath;
if (!osName.startsWith("Windows")) {
//linux,创建文件夹汇总所有版本资源
fileUploadPath= Constants.VERSION_PATH+"/";
File uploadFile = new File(fileUploadPath);
if (uploadFile.exists()) {
//需要删除原先的resources资源文件夹
String rmDirCommond = "rm -rf "+Constants.VERSION_PATH;
Runtime.getRuntime().exec(rmDirCommond);
log.info("删除原先的resources资源文件夹成功");
}
uploadFile.mkdirs();
//复制fastdfs的新增资源
if(fastdfsList.size()>0){
FastDFSClientUtil fastDFSClientUtil = new FastDFSClientUtil();
for (int i = 0; i < fastdfsList.size(); i++) {
String targetPath = fileUploadPath+"data"+fastdfsList.get(i);
String fastDirPath = targetPath.substring(0,targetPath.lastIndexOf("/"));
log.info("先创建文件目录:"+fastDirPath);
log.info("目标文件目录:"+targetPath);
File fastDir = new File(fastDirPath);
fastDir.mkdirs();
File fastFile = new File(targetPath);
fastFile.createNewFile();
int downFlag = fastDFSClientUtil.download_file(Constants.FASTDFS_PATH+fastdfsList.get(i),
new BufferedOutputStream(new FileOutputStream(targetPath)));
log.info("第"+(i+1)+"个文件的下载结果为:" + (downFlag == 0 ? "下载文件成功" : "下载文件失败"));
}
}
//复制nginx下xxd、xxb、xxa、xxc下版本新增的交互动画资源(文件夹的资源形式)
copyNginxFolder(nginxXxdList, fileUploadPath);
copyNginxFolder(nginxXxbList, fileUploadPath);
copyNginxFolder(nginxXxaList, fileUploadPath);
copyNginxFolder(nginxXxcList, fileUploadPath);
} else {
throw new BusinessException("请发布linux系统后打包版本资源");
}
//打包shell脚本
final Resource[] shellResources = new PathMatchingResourcePatternResolver().getResources(ResourceUtils.CLASSPATH_URL_PREFIX + Constants.RESOURCE_SHELL_PATH);
String shellFolderPath;
//linux,创建文件夹汇总所有版本资源
shellFolderPath = Constants.VERSION_PATH + "/shell/";
File shellFolderFile = new File(shellFolderPath);
shellFolderFile.mkdirs();
//复制shell文件
log.info("复制shell文件...");
for (int n = 0; n < shellResources.length; n++) {
final Resource shellResource = shellResources[n];
// 遍历文件内容
StringBuffer shellScript = new StringBuffer();
try(InputStreamReader isr = new InputStreamReader(shellResource.getInputStream(), StandardCharsets.UTF_8);
BufferedReader bufferReader = new BufferedReader(isr)) {
String tempString;
while ((tempString = bufferReader.readLine()) != null) {
shellScript.append(tempString).append("\n");
}
} catch (IOException e) {
e.printStackTrace();
}
String shellContent = shellScript.toString();
String name = shellResource.getFilename();
File dest = new File(shellFolderPath + name);
dest.createNewFile();
FileWriter fw=new FileWriter(dest,true);
fw.write(shellContent);
fw.close();
}
//压缩命令zip FileName.zip DirName压缩资源目录
log.info("开始资源压缩打包...");
FileOutputStream fos1 = new FileOutputStream(Constants.VERSION_NAME);
ZipUtil.zip(Constants.VERSION_PATH, fos1);
log.info("开始资源压缩打包成功");
return Constants.VERSION_NAME;
}
/**
* @Author: yz
* @Date 19:14 2023/1/5
* @Param [list, fileUploadPath]
* @return 复制nginx目录下的资源文件夹
*/
private static void copyNginxFolder(List<String> list, String fileUploadPath) throws Exception {
if(list.size()>0){
for (int i = 0; i < list.size(); i++) {
String targetPath = fileUploadPath+"nginx/html/"+list.get(i);
String sourcePath = Constants.NGINX_DOWNLOAD_PATH+list.get(i);
log.info("nginx原资源文件路径:"+sourcePath);
copyFolder(sourcePath,targetPath);
}
}
}
/**
* @Author: yz
* @Date 13:26 2023/1/5
* @Param [file, list]
* @return fastdfs解析文件封装函数
*/
public static void fastdfsPackage(String content,List<String> list) throws IOException {
String[] split = content.split("XXXXXX");
for (int j = 0; j < split.length; j++) {
if(j>0){
list.add(split[j].substring(0,split[j].indexOf("\'")));
}
}
}
/**
* @Author: yz
* @Date 13:26 2023/1/5
* 复制文件夹(使用缓冲字节流)
* @param sourcePath 源文件夹路径
* @param targetPath 目标文件夹路径
*/
public static void copyFolder(String sourcePath,String targetPath) throws Exception{
//源文件夹路径
File sourceFile = new File(sourcePath);
//目标文件夹路径
File targetFile = new File(targetPath);
if(!sourceFile.exists()){
log.info("源文件夹不存在:"+sourcePath);
return;
//throw new Exception("文件夹不存在");
}
if(!sourceFile.isDirectory()){
throw new Exception("源文件夹不是目录");
}
if(!targetFile.exists()){
targetFile.mkdirs();
}
if(!targetFile.isDirectory()){
throw new Exception("目标文件夹不是目录");
}
File[] files = sourceFile.listFiles();
if(files == null || files.length == 0){
return;
}
for(File file : files){
//文件要移动的路径
String movePath = targetFile+File.separator+file.getName();
if(file.isDirectory()){
//如果是目录则递归调用
copyFolder(file.getAbsolutePath(),movePath);
}else {
//如果是文件则复制文件
BufferedInputStream in = new BufferedInputStream(new FileInputStream(file));
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(movePath));
byte[] b = new byte[1024];
int temp = 0;
while((temp = in.read(b)) != -1){
out.write(b,0,temp);
}
out.close();
in.close();
}
}
}
ZipUtil类:
@Slf4j
@RestController
public class ZipUtil {
/**
* 压缩文件夹到指定输出流中,可以是本地文件输出流,也可以是web响应下载流
* @auther yz
* @param srcDir 源文件夹
* @param outputStream 压缩后文件的输出流
* @throws IOException IO异常,抛出给调用者处理
*/
public static void zip(String srcDir, OutputStream outputStream) throws IOException {
try (
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream);
ArchiveOutputStream out = new ZipArchiveOutputStream(bufferedOutputStream);
) {
Path start = Paths.get(srcDir);
Files.walkFileTree(start, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
ArchiveEntry entry = new ZipArchiveEntry(dir.toFile(), start.relativize(dir).toString());
out.putArchiveEntry(entry);
out.closeArchiveEntry();
return super.preVisitDirectory(dir, attrs);
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
try (
InputStream input = new FileInputStream(file.toFile())
) {
ArchiveEntry entry = new ZipArchiveEntry(file.toFile(), start.relativize(file).toString());
out.putArchiveEntry(entry);
IOUtils.copy(input, out);
out.closeArchiveEntry();
}
return super.visitFile(file, attrs);
}
});
}
}
/**
* 解压zip文件到指定文件夹
* @auther yz
* @param zipFileName 源zip文件路径
* @param destDir 解压后输出路径
* @throws IOException IO异常,抛出给调用者处理
*/
public static void unzip(String zipFileName, String destDir) throws IOException {
try (
InputStream inputStream = new FileInputStream(zipFileName);
) {
unzip(inputStream, destDir);
}
}
/**
* 从输入流中获取zip文件,并解压到指定文件夹
* @auther yz
* @param inputStream zip文件输入流,可以是本地文件输入流,也可以是web请求上传流
* @param destDir 解压后输出路径
* @throws IOException IO异常,抛出给调用者处理
*/
public static void unzip(InputStream inputStream, String destDir) throws IOException {
try (
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
ArchiveInputStream in = new ZipArchiveInputStream(bufferedInputStream);
) {
ArchiveEntry entry;
while (Objects.nonNull(entry = in.getNextEntry())) {
if (in.canReadEntryData(entry)) {
File file = Paths.get(destDir, entry.getName()).toFile();
if (entry.isDirectory()) {
if (!file.exists()) {
file.mkdirs();
}
} else {
try (
OutputStream out = new FileOutputStream(file);
) {
IOUtils.copy(in, out);
}
}
} else {
log.info(entry.getName());
}
}
}
}
}
结果:
总结:
增量sql截取存储资源对应的path,找到资源服务器下对应的资源下载到服务器指定一个目录下,最后web端访问接口下载到本地
结束语:
-----忧伤没有成本意识,激动没有判断能力,疲惫让人失去主见(旅游乱买,半夜买东西),结论:重要决策一定要平静的时候。