1,需求分析:新增数据的同时可选择附件,附件上传到部署服务器
2,原型
3,代码
3.1) Contrller
/**
* <pre>
* 内购方案维护
* </pre>
*
* @author shenke
* @version $Id: AddressController.java, v 0.1 2019年5月09日 下午16:51:39 shenke Exp $
*/
@RestController
@RequestMapping("/csms/sales/plan")
public class PlanController {
private Logger logger = LoggerFactory.getLogger(PlanController.class);
@Autowired
private PlanService planService;
@RequestMapping(value = "/insert/upload", method = RequestMethod.POST)
@LoggerManage(description = "新增内购方案")
public BaseResponseModel<Object> insertPlan(@RequestPart("files") MultipartFile[] files, PlanVO vo, @RequestParam("token") String token) {
BaseResponseModel<Object> response = new BaseResponseModel<>();
try {
response = planService.insertPlan(files, vo, token);
} catch (Exception e) {
e.printStackTrace();
response.setRepCode(RespCode.ACT_EXCEPTION);
response.setRepMsg(RespMsg.ACT_EXCEPTION_MSG);
}
return response;
}
3.2) serviceImp
上传文件本质就是读取文件,转化字节数据,通过输出流写入到服务器文件。而服务器文件,即在服务器某路径下创建某个文件(不存在即创建)文件名与导入相同,或者为防止重复导入相同文件,文件名用日期区分。
// 服务器相对路径
public static final String SERVICE_PATH = "/data/nfs/upload/";
@Override
public BaseResponseModel<Object> uploadFile(MultipartFile[] files, String token, Long id) throws Exception {
logger.info(files);
BaseResponseModel<Object> response = new BaseResponseModel<>();
logger.info("开始执行上传文件......................................");
logger.info("上传文件个数:" + files.length);
if (files != null && files.length > 0) {
//循环获取file数组中得文件
for (int i = 0; i < files.length; i++) {
MultipartFile mltfile = files[i];
FileOutputStream fos = null;
if (!mltfile.isEmpty()) {
try {
byte[] b = mltfile.getBytes();
String fileName = new String(mltfile.getOriginalFilename().getBytes(), "UTF-8");
// 生成文件全路经
String detailPath = getIdPath(token);
String fileNameService = DateUtil.getNowTimeNoMinus() + "_" + fileName;
String realPath = SERVICE_PATH + detailPath + "/" + fileNameService;
String uploadPath = detailPath + "/" + fileNameService;
// String realPath = "D:/data/file/" + detailPath+"/" + fileNameService;
/*logger.info("上传文件名:" + fileName);
logger.info("详细路径:" + detailPath);
logger.info("服务文件名:" + fileNameService);
logger.info("全路径:" + realPath);*/
// 写入文件
File file = new File(realPath);
if (!file.exists()) {
File dir = new File(file.getParent());
dir.mkdirs();
file.createNewFile();
}
fos = new FileOutputStream(file);
fos.write(b);
// 保存附件
saveFile(token, fileName, uploadPath, id);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fos != null) {
fos.close();
fos = null;
}
}
}
}
}
response.setRepCode(RespCode.SUCCESS);
response.setRepMsg(RespMsg.SUCCESS_MSG);
return response;
}
4,文件校验
包括大小,文件名是否含有非法字符,文件后缀是否合法
/**
* 校验文件
* @param token
* @param mltfile
* @param response
* @return
*/
private Boolean checkFile(String token, MultipartFile mltfile, BaseResponseModel<Object> response) throws IOException {
Boolean flag = false;
// token
if (token.isEmpty() || token.split("_").length != 5) {
response.setRepCode(RespCode.TOKEN_ISEMPTY);
response.setRepMsg(RespMsg.TOKEN_ISEMPTY_MSG);
flag = true;
return flag;
}
// 大小
long fileSize = mltfile.getSize();
logger.info("文件大小:" + fileSize);
if (fileSize > FILE_SIZE) {
response.setRepCode(RespCode.FILE_TOOBIG);
response.setRepMsg(RespMsg.FILE_TOOBIG_MSG);
flag = true;
return flag;
}
// 校验文件名是否包含特殊字符
String fileName = mltfile.getOriginalFilename();
String fileNameSub = fileName.substring(0, fileName.lastIndexOf("."));
// String regEx1 = "<script[\s\s]+</script *>";
String regEx = "^[a-zA-Z0-9\u4E00-\u9FA5 .。_-]+$";
Pattern p = Pattern.compile(regEx);
Matcher m = p.matcher(fileNameSub);
logger.info("校验文件名返回:" + m.matches());
if (!m.matches()) {
response.setRepCode(RespCode.FILE_NAME);
response.setRepMsg(RespMsg.FILE_NAME_MSG);
flag = true;
return flag;
}
// 头文件校验后缀
String type = FileTools.getCorrectType(mltfile);
if(type == null){
response.setRepCode(RespCode.FILE_FORMAT);
response.setRepMsg(RespMsg.FILE_FORMAT_MSG);
flag = true;
return flag;
}
String fileEnd = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
List<String> endList = new ArrayList<>();
endList.add("doc");
endList.add("docx");
endList.add("docm");
endList.add("dotx");
endList.add("dotm");
endList.add("xls");
endList.add("xlsx");
endList.add("xltx");
endList.add("xltm");
endList.add("xlsb");
endList.add("xlam");
endList.add("ppt");
endList.add("pptx");
endList.add("ppsx");
endList.add("potx");
endList.add("potm");
endList.add("ppam");
endList.add("ppsm");
endList.add("pdf");
if (!endList.contains(fileEnd)) {
response.setRepCode(RespCode.FILE_FORMAT);
response.setRepMsg(RespMsg.FILE_FORMAT_MSG);
flag = true;
return flag;
}
return flag;
}
说明:
4.1) 校验文件名,防止含有脚本语言,不足之处要用正则表达式过滤,但是不会写,是通过正向匹配,允许传什么字符,来过滤非法字符。这里只允许英文大小写,数字,空格 . 。_ -等形式输入。如果想扩大可输入访问可参考ASCLL码。注意-只能放最后,因为中划线-是匹配字符,代表起始可输入字符,详细参考ascll码表
String regEx = "^[a-zA-Z0-9\u4E00-\u9FA5 .。_-]+$";
// 校验文件名是否包含特殊字符
String fileName = mltfile.getOriginalFilename();
String fileNameSub = fileName.substring(0, fileName.lastIndexOf("."));
// String regEx1 = "<script[\s\s]+</script *>";
String regEx = "^[a-zA-Z0-9\u4E00-\u9FA5 .。_-]+$";
Pattern p = Pattern.compile(regEx);
Matcher m = p.matcher(fileNameSub);
logger.info("校验文件名返回:" + m.matches());
if (!m.matches()) {
response.setRepCode(RespCode.FILE_NAME);
response.setRepMsg(RespMsg.FILE_NAME_MSG);
flag = true;
return flag;
}
4.2) 文件后缀校验
上面代码提供了两种校验方式
1,直接截取文件后缀,但会存在安全问题,如果恶意对文件后缀进行修改,.DAT修改为.pdf,无法校验出来。
2,通过头文件校验,上述就算对文件后缀恶意修改,但是头文件还是修改之前的,所有可以通过获取头文件,校验文件格式
// 头文件校验后缀
String type = FileTools.getCorrectType(mltfile);
if(type == null){
response.setRepCode(RespCode.FILE_FORMAT);
response.setRepMsg(RespMsg.FILE_FORMAT_MSG);
flag = true;
return flag;
}
头文件工具类
**
* @program wh-parent
* @className FileTools
* @author: shenke
* @create: 2019/05/22 17:09
*/
public class FileTools {
private static final Logger logger = LoggerFactory.getLogger(FileTools.class);
private final static Map<String, String> FILE_TYPE_MAP = new HashMap<>();
static {
FILE_TYPE_MAP.put("D0CF11", ".doc"); // d0cf11e0a1b11ae10000000000000000
FILE_TYPE_MAP.put("504b03", ".docm"); // 504b03040a0000000000874ee2400000
FILE_TYPE_MAP.put("504b03", ".docx"); // 504b03040a0000000000874ee2400000
FILE_TYPE_MAP.put("504b03", ".dotx"); // 504b03040a0000000000874ee2400000
FILE_TYPE_MAP.put("504b03", ".dotm"); // 504b03040a0000000000874ee2400000
FILE_TYPE_MAP.put("D0CF11", ".xls"); // d0cf11e0a1b11ae10000000000000000
FILE_TYPE_MAP.put("504b03", ".xlsx"); // 504b030414000600080000002100ca84
FILE_TYPE_MAP.put("504b03", ".xltm"); // 504b03040a0000000000874ee2400000
FILE_TYPE_MAP.put("504b03", ".xltx"); // 504b03040a0000000000874ee2400000
FILE_TYPE_MAP.put("D0CF11", ".xlsb"); //
FILE_TYPE_MAP.put("D0CF11", ".xlam"); //
FILE_TYPE_MAP.put("d0cf11e0a1b11ae10000000000000000", ".ppt"); // d0cf11e0a1b11ae10000000000000000
FILE_TYPE_MAP.put("504b030414000600080000002100a287", ".pptx"); // 504b030414000600080000002100a287
FILE_TYPE_MAP.put("504b03", ".ppsx"); // 504b03040a0000000000874ee2400000
FILE_TYPE_MAP.put("504b03", ".potx"); // 504b03040a0000000000874ee2400000
FILE_TYPE_MAP.put("504b03", ".potm"); // 504b03040a0000000000874ee2400000
FILE_TYPE_MAP.put("D0CF11", ".ppam"); //
FILE_TYPE_MAP.put("504b03", ".ppsm"); // 504b03040a0000000000874ee2400000
FILE_TYPE_MAP.put("255044", ".pdf"); // 255044462d312e350d0a25b5b5b5b50d xls 另存为 255044462d312e370a25c2b3c7d80d0a
// jpg ffd8ffe000104a464946000101010078(ppt 另存)
}
/**
* 校验文件真实格式
*
* @param multipartFile
* @return
* @throws IOException
*/
public static String getCorrectType(MultipartFile multipartFile) throws IOException {
InputStream inputStream = null;
try {
inputStream = multipartFile.getInputStream();
byte[] b = new byte[16];
inputStream.read(b, 0, b.length);
String headerHex = String.valueOf(bytesToHexString(b));
// logger.info("16进制编码:" + headerHex);
headerHex = headerHex.substring(0, 6).toUpperCase();
String value = FILE_TYPE_MAP.get(headerHex);
// logger.info("当前文件 headerHex == " + headerHex + ", 类型 type == " + value);
return value;
} catch (Exception e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
inputStream.close();
}
}
return "";
}
/**
* 将文件头转换成16进制字符串
*
* @param src
* @return 16进制字符串
*/
private static String bytesToHexString(byte[] src) {
StringBuilder stringBuilder = new StringBuilder();
if (src == null || src.length <= 0) {
return null;
}
for (int i = 0; i < src.length; i++) {
int v = src[i] & 0xFF;
String hv = Integer.toHexString(v);
if (hv.length() < 2) {
stringBuilder.append(0);
}
stringBuilder.append(hv);
}
return stringBuilder.toString();
}
}