序言
现在,绝大部分的应用程序在很多的情况下都需要使用到文件上传与下载的功能,在本文中结合hap利用spirng mvc实现文件的上传和下载,包括上传下载图片、上传下载文档。前端所使用的技术不限,本文重点在于后端代码的实现。希望可以跟随我的一步步实践,最终轻松掌握在hap中的文件上传和下载的具体实现。
案例
1. 数据库设计
表结构
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for tb_fruit
-- ----------------------------
DROP TABLE IF EXISTS `tb_fruit`;
CREATE TABLE `tb_fruit` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`fruitName` varchar(255) DEFAULT NULL,
`picturePath` varchar(255) DEFAULT NULL,
`filePath` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
字段描述
Id 主键自增
fruitName 水果名称
picturePath 图片路径
filePath 附件2. 先使用代码生成工具根据表结构生成对应的测试
dto层:
2 3 package hbi.core.test.dto; 4 5 /**Auto Generated By Hap Code Generator**/ 6 7 import com.hand.hap.mybatis.annotation.ExtensionAttribute; 8 9 import org.hibernate.validator.constraints.Length; 10 11 import javax.persistence.GeneratedValue; 12 13 import javax.persistence.Id; 14 15 import javax.persistence.Table; 16 17 @Table(name = "tb_fruit") 18 19 public class Fruit{ 20 21 22 23 public static final String FIELD_ID = "id"; 24 25 public static final String FIELD_FRUITNAME = "fruitname"; 26 27 public static final String FIELD_PICTUREPATH = "picturepath"; 28 29 public static final String FIELD_FILEPATH = "filepath"; 30 31 32 33 @Id 34 35 @GeneratedValue 36 37 private Long id; 38 39 40 41 @Length(max = 255) 42 43 private String fruitname; 44 45 46 47 @Length(max = 255) 48 49 private String picturepath; 50 51 52 53 @Length(max = 255) 54 55 private String filepath; 56 57 //省略get/set方法... 58 59 } 60
controller层: 69 package hbi.core.test.controllers; 70 71 import com.hand.hap.attachment.exception.FileReadIOException; 72 73 import com.hand.hap.core.IRequest; 74 75 import com.hand.hap.core.exception.TokenException; 76 77 import com.hand.hap.system.controllers.BaseController; 78 79 import com.hand.hap.system.dto.ResponseData; 80 81 import hbi.core.test.dto.Fruit; 82 83 import hbi.core.test.service.IFruitService; 84 85 import net.sf.json.JSONObject; 86 87 import org.apache.commons.lang.StringUtils; 88 89 import org.springframework.beans.factory.annotation.Autowired; 90 91 import org.springframework.stereotype.Controller; 92 93 import org.springframework.validation.BindingResult; 94 95 import org.springframework.web.bind.annotation.*; 96 97 import org.springframework.web.multipart.MultipartFile; 98 99 import javax.servlet.http.HttpServletRequest; 100 101 import javax.servlet.http.HttpServletResponse; 102 103 import java.io.*; 104 105 import java.net.URLEncoder; 106 107 import java.text.SimpleDateFormat; 108 109 import java.util.*; 110 111 112 113 @Controller 114 115 public class FruitController extends BaseController { 116 117 @Autowired 118 119 private IFruitService service; 120 121 /** 122 123 * word文件路径 124 125 */ 126 127 private String wordFilePath="/u01/document/userfile"; 128 129 /** 130 131 * 图片文件路径 132 133 */ 134 135 private String userPhotoPath = "/u01/document/userphoto"; 136 137 /** 138 139 * 文件最大 5M 140 141 */ 142 143 public static final Long FILE_MAX_SIZE = 5*1024*1024L; 144 145 /** 146 147 * 允许上传的图片格式 148 149 */ 150 151 public static final List<String> IMG_TYPE = Arrays.asList("jpg", "jpeg", "png", "bmp"); 152 153 /** 154 155 * 允许上传的文档格式 156 157 */ 158 159 public static final List<String> DOC_TYPE = Arrays.asList("pdf", "doc", "docx"); 160 161 /** 162 163 * ContentType 164 165 */ 166 167 public static final Map<String, String> EXT_MAPS = new HashMap<>(); 168 169 static { 170 171 // image 172 173 EXT_MAPS.put("jpg", "image/jpeg"); 174 175 EXT_MAPS.put("jpeg", "image/jpeg"); 176 177 EXT_MAPS.put("png", "image/png"); 178 179 EXT_MAPS.put("bmp", "image/bmp"); 180 181 // doc 182 183 EXT_MAPS.put("pdf", "application/pdf"); 184 185 EXT_MAPS.put("ppt", "application/vnd.ms-powerpoint"); 186 187 EXT_MAPS.put("doc", "application/msword"); 188 189 EXT_MAPS.put("doc", "application/wps-office.doc"); 190 191 EXT_MAPS.put("docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"); 192 193 } 194 195 196 197 /** 198 199 * @author jiaqing.xu@hand-china.com 200 201 * @date 2017/9/4 17:23 202 203 * @param wordFile photoFile fruitName 204 205 * @return List<Fruit> 206 207 * @description 保存水果信息 208 209 */ 210 211 @RequestMapping(value = {"/api/public/upload/fruit/submit"}, method = RequestMethod.POST) 212 213 @ResponseBody 214 215 public List<Fruit> fruitSubmit(@RequestParam(value = "wordFile", required = true) MultipartFile wordFile,@RequestParam(value = "photoFile", required = true) MultipartFile photoFile, @RequestParam(value="fruitName",required = true) String fruitName,HttpServletRequest request){ 216 217 IRequest requestContext = createRequestContext(request); 218 219 //上传word文件到磁盘 220 221 Map<String,Object> result1 = uploadFile(wordFile,wordFilePath,DOC_TYPE,null); 222 223 //上传图片文件到磁盘 224 225 Map<String, Object> result2 = uploadFile(photoFile,userPhotoPath, IMG_TYPE, 200); 226 227 228 229 List<Fruit> list = new ArrayList<>(); 230 231 //如果创建成功则保存相应路径到数据库 232 233 if((boolean)result1.get("success")&&(boolean)result2.get("success")){ 234 235 Fruit fruit = new Fruit(); 236 237 fruit.setFruitname(fruitName); 238 239 //设置图片路径 240 241 fruit.setPicturepath((String) result2.get("path")); 242 243 //设置附件路径 244 245 fruit.setFilepath((String) result1.get("path")); 246 247 //插入 249 fruit = service.insertSelective(requestContext,fruit); 251 list.add(fruit); 253 } 255 return list; 256 257 } 258 259 260 261 /** 262 263 * @author jiaqing.xu@hand-china.com 264 265 * @date 2017/9/4 16:18 266 267 * @param 268 269 * @return 270 271 * @description 【通用】 上传文件到磁盘的某一个目录 272 273 */ 274 275 public Map<String, Object> uploadFile(MultipartFile file, String path, List<String> fileTypes, Integer ratio){ 276 277 Map<String, Object> results = new HashMap<>(); 278 279 results.put("success", false); 280 281 if(file == null || file.getSize() < 0 || StringUtils.isBlank(file.getOriginalFilename())){ 282 283 results.put("message", "文件为空"); 284 285 return results; 286 287 } 288 289 if(file.getSize() > FILE_MAX_SIZE){ 290 291 results.put("message", "文件最大不超过" + FILE_MAX_SIZE/1024/1024 + "M"); 292 293 return results; 294 295 } 296 297 String originalFilename = file.getOriginalFilename(); 298 299 if(!fileTypes.contains(originalFilename.substring(originalFilename.lastIndexOf(".")+1))){ 300 301 results.put("message", "文件格式不正确"); 302 303 return results; 304 305 } 306 307 SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMddHHmmssSSS"); 308 309 String datetime = formatter.format(new Date()); 310 311 String yearmonth = datetime.substring(0, 6); 312 313 // 在基础路径上加上年月路径 314 315 String fileDir = path + "/" + yearmonth; 316 317 // 文件名以 [_时间戳_随机数#原文件名]的形式 随机数防止重复文件名 318 319 String randomNumber = String.valueOf((Math.random() * 90000) + 10000).substring(0, 5); 320 321 String fileName = "_" + datetime + randomNumber + "=" + originalFilename; 322 323 // 文件路径 324 325 String filePath = fileDir + "/" + fileName; 326 329 try { 330 331 // 创建目录 332 333 File dir = new File(fileDir); 334 335 if(!dir.exists() && !dir.isDirectory()){ 336 337 dir.mkdirs(); 338 339 } 340 341 // 文件输入流 342 343 InputStream is = file.getInputStream(); 344 345 // 输出流 346 347 OutputStream os = new FileOutputStream(filePath); 348 349 // 输出文件到磁盘 350 351 int len = 0; 352 353 byte[] buffer = new byte[2048]; 354 355 while((len = is.read(buffer, 0, 2048)) != -1){ 356 357 os.write(buffer, 0, len); 358 359 } 360 361 os.flush(); 362 363 os.close(); 364 365 is.close(); 366 368 369 //向结果中保存状态消息和存储路径 370 371 results.put("success", true); 372 373 results.put("message", "SUCCESS"); 374 375 results.put("path", filePath); 376 377 378 379 // 压缩图片 380 381 if(ratio != null){ 382 383 //ImgExif.ExifInfoToRotate(filePath); 384 385 ImgCompress imgCompress = new ImgCompress(filePath); 386 387 imgCompress.setFileName(filePath); 388 389 imgCompress.resizeByWidth(ratio); 390 391 } 392 393 } catch (Exception e) { 394 395 e.printStackTrace(); 396 397 results.put("message", "ERROR"); 398 399 return results; 400 401 } 402 403 404 405 return results; 406 407 } 408 413 /** 414 415 * @author jiaqing.xu@hand-china.com 416 417 * @date 2017/9/4 17:23 418 419 * @param 420 421 * @return 422 423 * @description 【通用】 读取上传的文件 将文件以流的形式输出 424 425 */ 426 427 @RequestMapping(value = "/api/public/read/file") 428 429 public void readFile(@RequestParam String path, HttpServletResponse response) throws FileReadIOException, TokenException { 430 431 Map<String, Object> results = new HashMap<>(); 432 433 try { 434 435 File file = new File(path); 436 437 if(!file.exists()){ 438 439 results.put("success", false); 440 441 results.put("message", "文件不存在"); 442 443 JSONObject jsonObject = JSONObject.fromObject(results); 444 445 response.getWriter().write(jsonObject.toString()); 446 447 return; 448 449 } 450 451 // 类型 452 453 String contentType = EXT_MAPS.get(path.substring(path.lastIndexOf(".") + 1)); 454 455 // 设置头 456 457 response.addHeader("Content-Disposition", "attachment;filename="" + URLEncoder.encode(path.substring(path.indexOf("=") + 1), "UTF-8") + """); 458 459 response.setContentType(contentType + ";charset=UTF-8"); 460 461 response.setHeader("Accept-Ranges", "bytes"); 462 463 // 输出文件 464 465 InputStream is = new FileInputStream(file); 466 467 OutputStream os = response.getOutputStream(); 468 469 int len = 0; 470 471 byte[] buffer = new byte[2048]; 472 473 while ((len = is.read(buffer, 0, 2048)) != -1){ 474 475 os.write(buffer, 0, len); 476 477 } 478 479 os.flush(); 480 481 os.close(); 482 483 is.close(); 484 485 } catch (IOException e) { 487 e.printStackTrace(); 489 } 491 } 493 }
说明:
(1)文件保存路径wordFilePath、 水果照片保存路径userPhotoPath可以通过配置文件进行配置。
(2)其中保存的文件的路径在项目的根路径下,比如我的项目在D盘的某一个文件夹下,则生成的word文件的位置为D盘/u01/document/userfile路径下.
3. postman测试
注意点:在使用postman测试时,使用post请求,参数中body为form-data,依次将需要的参数填写到对应的位置。
返回后的数据为插入数据库中的记录。
4. 运行结果
数据库中插入的新的记录:
磁盘中的word文件:
磁盘中的图片:
浏览器进行下载测试: