Newer
Older
mcms / ms-basic / src / main / java / net / mingsoft / basic / action / TemplateAction.java
StephanieGitHub on 16 Dec 2020 19 KB first commit
/**
 * The MIT License (MIT) * Copyright (c) 2020 铭软科技(mingsoft.net)

 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:

 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.

 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

package net.mingsoft.basic.action;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.file.FileReader;
import cn.hutool.core.io.file.FileWriter;
import cn.hutool.core.util.CharUtil;
import cn.hutool.core.util.StrUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import net.mingsoft.base.entity.ResultData;
import net.mingsoft.basic.annotation.LogAnn;
import net.mingsoft.basic.biz.IAppBiz;
import net.mingsoft.basic.constant.e.BusinessTypeEnum;
import net.mingsoft.basic.constant.e.CookieConstEnum;
import net.mingsoft.basic.entity.AppEntity;
import net.mingsoft.basic.entity.ManagerSessionEntity;
import net.mingsoft.basic.util.BasicUtil;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
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.ResponseBody;
import springfox.documentation.annotations.ApiIgnore;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;

/**
 * @ClassName: TemplateAction
 * @Description: TODO(模板控制层)
 * @author 铭飞开源团队
 * @date 2020年7月2日
 *
 */
@Api("获取有关模版文件夹或模版文件信息")
@Controller("/basicTemplate")
@RequestMapping("/${ms.manager.path}/template")
public class TemplateAction extends BaseAction {

	/**
	 * 站点业务层
	 */
	@Autowired
	private IAppBiz appBiz;

	@Value("${ms.upload.template}")
	private String uploadTemplatePath;

	@Value("${ms.upload.denied}")
	private String uploadFileDenied;


	/**
	 * 查询模版风格供站点选择
	 *
	 * @param request
	 *            请求
	 * @return 模版文件集合
	 */
	@ApiOperation(value = "查询模版风格供站点选择")
	@GetMapping("/queryAppTemplateSkin")
	@ResponseBody
	public ResultData queryAppTemplateSkin(HttpServletRequest request) {
		List<String> folderNameList = this.queryTemplateFile();
		Map map = new HashMap();
		if (folderNameList != null) {
			map.put("fileName", folderNameList);
		}
		return ResultData.build().success(map);
	}

	/**
	 * 返回主界面index
	 */
	@GetMapping("/index")
	public String index(HttpServletResponse response,HttpServletRequest request){
		return "/basic/template/index";
	}

	/**
	 * 返回模板list
	 */
	@GetMapping("/list")
	public String list(HttpServletResponse response,HttpServletRequest request){
		return "/basic/template/list";
	}

	/**
	 * 返回模板编辑页面
	 */
	@GetMapping("/edit")
	public String edit(HttpServletResponse response,HttpServletRequest request){
		return "/basic/template/edit";
	}

	/**
	 * 查询模版文件供栏目选择
	 *
	 * @param request
	 *            请求
	 * @return 返回列表集合
	 */
	@ApiOperation(value = "查询模版文件供栏目选择")
	@GetMapping("/queryTemplateFileForColumn")
	@ResponseBody
	public ResultData queryTemplateFileForColumn(HttpServletRequest request) {
		ManagerSessionEntity managerSession = getManagerBySession();
		String path = BasicUtil.getRealTemplatePath( uploadTemplatePath + File.separator
				+ managerSession.getBasicId() + File.separator);
		int websiteId = managerSession.getBasicId();
		AppEntity website = (AppEntity) appBiz.getEntity(websiteId);
		//如果没有选择风格,则返回空
		if(StringUtils.isBlank(website.getAppStyle())){
			return ResultData.build().success(new ArrayList<>());
		}
		path = path + File.separator + website.getAppStyle();
		LOG.debug("tempPath:" + path);
		List<String> listName = new ArrayList<String>();
		files(listName, new File(path), website.getAppStyle());
		return ResultData.build().success(listName);
	}

	private void files(List list, File fileDir, String style) {
		if (fileDir.isDirectory()) {
			File files[] = fileDir.listFiles();
			for (int i = 0; i < files.length; i++) {
				File currFile = files[i];
				if (currFile.isFile()) {
					String ex = currFile.getName();
					if (ex.endsWith("htm") || ex.endsWith("html")) {
						String _pathName = new String();
						_pathName = files(currFile, style, _pathName);
						list.add(_pathName + currFile.getName());
					}
				} else if (currFile.isDirectory()) {
					files(list, currFile, style);
				}
			}
		}
	}

	private String files(File file, String style, String pathName) {
		if (!file.getParentFile().getName().equals(style)) {
			pathName = file.getParentFile().getName() + "/" + pathName;
			pathName = files(file.getParentFile(), style, pathName);
		}
		return pathName;
	}

	/**
	 * 点击模版管理,获取所有的模版文件名
	 *
	 * @param response
	 *            响应
	 * @param request
	 *            请求
	 * @return 返回模版文件名集合
	 */
	@ApiOperation(value = "点击模版管理,获取所有的模版文件名")
	@ApiImplicitParam(name = "pageNo", value = "pageNo", required = true,paramType="query")
	@GetMapping("/queryTemplateSkin")
	@ResponseBody
	protected ResultData queryTemplateSkin(HttpServletResponse response, HttpServletRequest request) {
		String pageNo = request.getParameter("pageNo");
		if (!NumberUtils.isNumber(pageNo)) {
			pageNo = "1";
		}
		ManagerSessionEntity managerSession = getManagerBySession();
		List<String> folderNameList = this.queryTemplateFile();
		Map<String,Object> map = new HashMap<>(3);
		map.put("folderNameList", folderNameList);
		map.put("websiteId", managerSession.getBasicId());
		int recordCount = 0;
		if (folderNameList!=null) {
			recordCount = folderNameList.size();
		}
		map.put("pageNo",pageNo);
		BasicUtil.setCookie( response, CookieConstEnum.PAGENO_COOKIE, pageNo);
		return ResultData.build().success(map);
	}
	/**
	 * 解压zip模版文件
	 *
	 *            文件路径
	 * @throws ZipException
	 * @throws IOException
	 */
	@ApiOperation(value ="解压zip模版文件")
	@ApiImplicitParam(name = "fileUrl", value = "文件路径", required = true,paramType="query")
	@LogAnn(title = "解压zip模版文件",businessType= BusinessTypeEnum.OTHER)
	@GetMapping("/unZip")
	@ResponseBody
	@RequiresPermissions("template:upload")
	public ResultData unZip(@ApiIgnore ModelMap model, HttpServletRequest request) throws  IOException {
		String entryName = "";
		String fileUrl = request.getParameter("fileUrl");
		//校验路径
		if (fileUrl!=null&&(fileUrl.contains("../")||fileUrl.contains("..\\"))){
			ResultData.build().error();
		}

		// 创建文件对象
		File file = new File(BasicUtil.getRealTemplatePath(fileUrl));
		// 创建zip文件对象
		unZip(file, new File(BasicUtil.getRealTemplatePath(fileUrl.substring(0, fileUrl.length() - file.getName().length()))), Charset.forName("GBK"));

		FileUtils.forceDelete(file);
		return ResultData.build().success(entryName);
	}

	/**
	 * 删除模版
	 *
	 *            模版名称
	 * @param request
	 *            响应
	 */
	@ApiOperation(value = "删除模版")
	@ApiImplicitParam(name = "fileName", value = "模版名称", required = true,paramType="query")
	@LogAnn(title = "删除模版",businessType= BusinessTypeEnum.DELETE)
	@PostMapping("/delete")
	@ResponseBody
	@RequiresPermissions("template:del")
	public ResultData delete(HttpServletRequest request) {
		String fileName = request.getParameter("fileName");
		String path = BasicUtil.getRealTemplatePath( uploadTemplatePath + File.separator
				+ BasicUtil.getAppId() + File.separator + fileName);
		try {
			FileUtils.deleteQuietly(new File(path));
			return ResultData.build().success();
		} catch (Exception e) {
			return ResultData.build().error();
		}
	}

	/**
	 * 获取模版路径
	 *
	 * @param request
	 *            请求
	 * @param fileNameUrl
	 *            文件路径
	 * @return 返回路径
	 */
	private String getTemplateUrl(HttpServletRequest request, String fileNameUrl) {
		ManagerSessionEntity managerSession = getManagerBySession();
		String templetsUrl = BasicUtil.getRealTemplatePath( uploadTemplatePath + File.separator
				+ managerSession.getBasicId() + File.separator + fileNameUrl);
		return templetsUrl;
	}

	/**
	 * 显示子文件和子文件夹
	 *
	 * @param response
	 *            响应
	 * @param request
	 *            请求
	 * @return 返回文件名集合
	 */
	@ApiOperation(value = "显示子文件和子文件夹")
	@ApiImplicitParam(name = "skinFolderName", value = "skinFolderName", required = true,paramType="query")
	@GetMapping("/showChildFileAndFolder")
	@ResponseBody
	public ResultData showChildFileAndFolder(HttpServletResponse response, HttpServletRequest request) {
		ManagerSessionEntity managerSession = getManagerBySession();
		List<String> folderNameList = null;
		String skinFolderName = request.getParameter("skinFolderName");

		//非法路径过滤
		if(skinFolderName!=null&&(skinFolderName.contains("../")||skinFolderName.contains("..\\"))){
			return ResultData.build().error("非法路径");
		}

		skinFolderName=skinFolderName.substring(skinFolderName.lastIndexOf("templets"));
		File files[] = new File(BasicUtil.getRealTemplatePath( skinFolderName)).listFiles();
		Map<String,Object> map = new HashMap<>();
		if (files != null) {
			folderNameList = new ArrayList<String>();
			List<String> fileNameList = new ArrayList<String>();
			for (int i = 0; i < files.length; i++) {
				File currFile = files[i];
				String filter = BasicUtil.getRealTemplatePath(
						uploadTemplatePath + File.separator + BasicUtil.getAppId());
				LOG.debug("过滤路径" + filter);
				String temp = currFile.getPath();
				//以当前系统分隔符作判断,将不是当前系统的分隔符替换为当前系统的

				temp=temp.replace(File.separator.equals("\\")?"/":"\\", File.separator).replace(filter, "");
				if (currFile.isDirectory()) {
					folderNameList.add(temp);
				} else {
					fileNameList.add(temp);
				}
			}
			folderNameList.addAll(fileNameList);
			map.put("fileNameList", folderNameList);
		}
		String uploadFileUrl = skinFolderName;
		map.put("uploadFileUrl", uploadFileUrl);
		map.put("websiteId", managerSession.getBasicId());
		return ResultData.build().success(map);
	}

	/**
	 * 读取模版文件内容
	 *
	 * @param model
	 * @param request
	 *            请求
	 * @return 返回文件内容
	 */
	@ApiOperation(value = "读取模版文件内容")
	@ApiImplicitParam(name = "fileName", value = "文件名称", required = true,paramType="query")
	@GetMapping("/readFileContent")
	@ResponseBody
	@RequiresPermissions("template:update")
	public ResultData readFileContent(@ApiIgnore ModelMap model, HttpServletRequest request) {
		String fileName = request.getParameter("fileName");
		fileName=fileName.substring(fileName.lastIndexOf("templets"));

		//非法路径过滤
		if(fileName!=null&&(fileName.contains("../")||fileName.contains("..\\"))){
			return ResultData.build().error("非法路径");
		}

		Map<String,Object> map = new HashMap<>();
		if (!StringUtils.isEmpty(fileName)) {
			map.put("fileContent", FileReader.create(new File(BasicUtil.getRealTemplatePath( fileName))).readString());
		}

		map.put("name", new File(BasicUtil.getRealTemplatePath(fileName)).getName());
		map.put("fileName", fileName);
		map.put("fileNamePrefix", fileName.substring(0, fileName.lastIndexOf(File.separator) + 1));
		return ResultData.build().success(map);
	}

	/**
	 * 删除模版文件
	 *
	 *            文件名称
	 * @param request
	 *            请求
	 */
	@ApiOperation(value = "删除模版文件")
	@ApiImplicitParam(name = "fileName", value = "文件名称", required = true,paramType="query")
	@LogAnn(title = "删除模版文件",businessType= BusinessTypeEnum.DELETE)
	@PostMapping("/deleteTemplateFile")
	@ResponseBody
	public ResultData deleteTemplateFile(HttpServletRequest request) {
		ManagerSessionEntity managerSession = getManagerBySession();
		String fileName = request.getParameter("fileName");
		fileName=uploadTemplatePath + File.separator
				+ managerSession.getBasicId() + File.separator + fileName;
		fileName=fileName.substring(fileName.lastIndexOf("templets"));
		FileUtil.del(BasicUtil.getRealTemplatePath(fileName ));
		return ResultData.build().success();
	}

	/**
	 * 写入模版文件内容
	 *
	 * @param model
	 * @param request
	 *            请求
	 * @param response
	 *            响应
	 * @throws IOException
	 */
	@ApiOperation(value = "写入模版文件内容")
	@ApiImplicitParams({
		@ApiImplicitParam(name = "fileName", value = "文件名称", required = true,paramType="query"),
		@ApiImplicitParam(name = "oldFileName", value = "旧文件名称", required = true,paramType="query"),
		@ApiImplicitParam(name = "fileContent", value = "文件内容", required = true,paramType="query"),
	})
	@LogAnn(title = "写入模版文件内容",businessType= BusinessTypeEnum.UPDATE)
	@PostMapping("/writeFileContent")
	@ResponseBody
	public ResultData writeFileContent(@ApiIgnore ModelMap model, HttpServletRequest request, HttpServletResponse response)
			throws IOException {
		String fileName = request.getParameter("fileName");
		//校验后缀文件名
		if (!checkFileType(fileName)){
			return ResultData.build().error();
		}

		String oldFileName = request.getParameter("oldFileName");
		String fileContent = request.getParameter("fileContent");
		if (!StringUtils.isEmpty(fileName)) {
			// 文件路径
			String templets = BasicUtil.getRealTemplatePath(fileName);
			LOG.debug(templets);
			FileWriter.create(new File(templets)).write(fileContent);
			if (!fileName.equals(oldFileName)) {
				// 得到一个待命名文件对象
				File newName = new File(templets);
				// 获取新名称文件的文件对象
				File oldName = new File(BasicUtil.getRealTemplatePath( oldFileName));
				// 进行重命名
				oldName.renameTo(newName);
				FileUtil.del(BasicUtil.getRealTemplatePath( oldFileName));
			}
			return ResultData.build().success();
		}
		return ResultData.build().error();
	}

	/**
	 * 查询模版文件集合
	 *
	 * @return 模版文件集合
	 */
	private List<String> queryTemplateFile() {
		ManagerSessionEntity managerSession = getManagerBySession();
		List<String> folderNameList = null;
		if (!isSystemManager()) {
			String templets = BasicUtil.getRealTemplatePath( uploadTemplatePath + File.separator
					+ managerSession.getBasicId() + File.separator);
			File file = new File(templets);
			String[] str = file.list();
			if (str!=null) {
				folderNameList = new ArrayList<String>();
				for (int i = 0; i < str.length; i++) {
					// 避免不为文件夹的文件显示
					if (str[i].indexOf(".") < 0) {
						folderNameList.add(str[i]);
					}
				}
			}
		}
		return folderNameList;
	}

	/**
	 * 解压zip文件,并过滤文件后缀名不符合要求的文件
	 * @param file  	被解压文件
	 * @param outFile	输入文件目录
	 * @param charset	字符编码集
	 * @return
	 */
	protected File unZip(File file, File outFile, Charset charset){
		ZipFile zipFile;
		try {
			zipFile = new ZipFile(file, charset);
		} catch (IOException e) {
			throw new IORuntimeException(e);
		}
		try {
			final Enumeration<ZipEntry> em = (Enumeration<ZipEntry>) zipFile.entries();
			ZipEntry zipEntry;
			File outItemFile;
			while (em.hasMoreElements()) {
				zipEntry = em.nextElement();
				String name = zipEntry.getName();
				// FileUtil.file会检查slip漏洞,漏洞说明见http://blog.nsfocus.net/zip-slip-2/
				outItemFile = buildFile(outFile, name);
				if (zipEntry.isDirectory()) {
					// 创建对应目录
					//noinspection ResultOfMethodCallIgnored
					outItemFile.mkdirs();
				} else {
					// 写出文件
					write(zipFile, zipEntry, outItemFile);
				}
			}
			return outFile;
		}finally {
			IoUtil.close(zipFile);
		}
	}

	/**
	 * 构建文件,文件目录
	 * @param outFile	输出文件
	 * @param fileName	文件名
	 * @return
	 */
	private File buildFile(File outFile, String fileName) {
		if (!FileUtil.isWindows()
			// 检查文件名中是否包含"/",不考虑以"/"结尾的情况
				&& fileName.lastIndexOf(CharUtil.SLASH, fileName.length() - 2) > 0) {
			// 在Linux下多层目录创建存在问题,/会被当成文件名的一部分,此处做处理
			// 使用/拆分路径(zip中无\),级联创建父目录
			final String[] pathParts = StrUtil.splitToArray(fileName, CharUtil.SLASH);
			for (int i = 0; i < pathParts.length - 1; i++) {
				//由于路径拆分,slip不检查,在最后一步检查
				outFile = new File(outFile, pathParts[i]);
			}
			//noinspection ResultOfMethodCallIgnored
			outFile.mkdirs();
			// 最后一个部分如果非空,作为文件名
			fileName = pathParts[pathParts.length - 1];
		}
		return FileUtil.file(outFile, fileName);
	}

	private void write(ZipFile zipFile, ZipEntry zipEntry, File outItemFile) throws IORuntimeException {
		InputStream in = null;
		try {
			//校验文件后缀名
			if (!checkFileType(zipEntry.getName())){
				return;
			}
			in = zipFile.getInputStream(zipEntry);
			FileUtil.writeFromStream(in, outItemFile);
		} catch (IOException e) {
			throw new IORuntimeException(e);
		} finally {
			IoUtil.close(in);
		}
	}

	/**
	 * 校验文件后缀名是否符合要求
	 * @param fileName	文件名
	 * @return false 不合法 true 符合
	 */
	protected boolean checkFileType(String fileName){
		//校验后缀文件名
		String[] errorType = uploadFileDenied.split(",");
		String fileType=fileName.substring(fileName.lastIndexOf("."));
		for (String type : errorType) {
			//校验禁止上传的文件后缀名(忽略大小写)
			if((fileType).equalsIgnoreCase(type)){
				LOG.info("文件类型被拒绝:{}",fileName);
				return false;
			}
		}
		return true;
	}
}