还是三层架构,首先考虑数据访问层,这一层没有什么处理,因为是把头像存到硬盘里没有存到数据库里;业务层需要就是上传完头像后更新headurl,服务端就需要提供一个改变头像路径的功能;上传文件这个事就在controller表现层里实现就好了。因为啊MultipartFile是属于表现层的一个对象,要是传给了service就产生了耦合了,不太合适。
首先上传头像得有一个存储头像的路径,这个路径不能是固定的,因为在本地开发和部署到服务器肯定不一样,目前存到本地,后期也会存到云服务器上。在application.properties里配上头像上传路径。
1. 大体思路
①用户点击首页面【账号设置】按钮,getSettingPage()会返回静态资源【/site/setting】,进入到设置页面,执行UserController的控制器方法;
②我们点击【上传头像】按钮后,发送【/user/upload】,执行UserController的控制器方法uploadHeader():首先对空值进行处理,然后用substring分割出文件后缀,png或者jpg等等。如果没有后缀就提示文件格式不正确。对用户上传的图片重命名,用之前写的生成uuid的方法加上分割出来的文件后缀。将文件存储到我们在配置文件中设置的路径下,并更新当前user的headerUrl属性为指定格式的路径;
③当我们需要访问头像(即user的headerUrl属性)时,控制器方法getHeader()会根据headerUrl,将对应目录下的头像复制出来。
2. 用户控制器-上传头像
这里用到的是Spring MVC的multipartFile,头像的存储和获取直接在controller层操作。新建一个UserController。将类的访问路径设为/user。
转载:初步理解MultipartFile - 简书 (jianshu.com)
MultipartFile是SpringMVC提供简化上传操作的工具类。
在不使用框架之前,都是使用原生的HttpServletRequest来接收上传的数据,文件是以二进制流传递到后端的,然后需要我们自己转换为File类。使用了MultipartFile工具类之后,我们对文件上传的操作就简便许多了。
MultipartFile主要是用表单的形式进行文件上传,在接收到文件时,可以获取文件的相关属性,比如文件名、文件大小、文件类型等等。
- 第一个方法,用于返回个人设置页面,直接返回模板路径。
- 然后上传头像的方法,这里从容器获取两个对象,一个是MultiparFile,也就是从浏览器传过来的头像文件,一个是model,用于模型返回信息。
- 首先对空值进行处理,然后用substring分割出文件后缀,png或者jpg等等。如果没有后缀就提示文件格式不正确。对用户上传的图片重命名,用之前写的生成uuid的方法加上分割出来的文件后缀。
- 再在我们指定的文件存放位置新建一个文件,文件名使用生成的名字,并记录异常,将异常向上抛出,用于之后的处理。然后从hostHolder里获取当前用户,更新头像路径。
package com.nowcoder.mycommunity.controller;@Controller
@RequestMapping("/user")
public class UserController {private static final Logger logger = LoggerFactory.getLogger(UserController.class);//注入文件的上传路径@Value("${community.path.upload}")private String uploadPath;//注入域名@Value("${mycommunity.path.domin}")private String domain;//访问路径@Value("${server.servlet.context-path}")private String contextPath;@Autowiredprivate UserService userService;@Autowiredprivate HostHolder hostHolder;//进入账号设置@RequestMapping(path="/setting",method = RequestMethod.GET)public String getSettingPage(){return "/site/setting";}//上传头像@RequestMapping(path = "/upload", method = RequestMethod.POST)public String uploadHeader(MultipartFile headerImage, Model model){//如果没上传头像,就点击上传头像按钮if(headerImage == null){model.addAttribute("error","您还没有选择图片!");return "/site/setting";}//获取图片后缀名String fileName = headerImage.getOriginalFilename();String suffix = fileName.substring(fileName.lastIndexOf("."));if(StringUtils.isBlank(suffix)){ //如果后缀名为空model.addAttribute("error","文件格式不正确!");return "/site/setting";}//生成随机的文件名fileName = CommunityUtil.generateUUID() + suffix;File dest = new File(uploadPath + "/" + fileName);try {headerImage.transferTo(dest);} catch (IOException e) {logger.error("上传文件失败:" + e.getMessage());throw new RuntimeException("上传文件失败,服务器发生异常!", e);}//到此就可以正常进行了//更新当前用户头像的路径(Web访问路径)//格式:http://localhost:8080/community/user/header/xxx.pngUser user = hostHolder.getUser();String headerUrl = domain + contextPath + "/user/header/" + fileName;userService.updateHeader(user.getId(), headerUrl);return "redirect:/index";}//获取头像(和上面头像设置时的路径格式保持一致)@RequestMapping(path = "/header/{fileName}", method = RequestMethod.GET)public void getHeader(@PathVariable("fileName") String fileName, HttpServletResponse response){//服务器存放路径fileName = uploadPath + "/" + fileName;//文件后缀String suffix = fileName.substring(fileName.lastIndexOf("."));//响应图片(浏览器响应图片时,为此写法)response.setContentType("image/" + suffix);//获取字节流try (//此为java7写法,这里面声明的变量会自动加finally,在里面自动关闭//而输出流会被SpringMVC自动关闭FileInputStream fis = new FileInputStream(fileName);OutputStream os = response.getOutputStream();){byte[] buffer = new byte[1024];int b = 0;while((b = fis.read(buffer)) != -1){os.write(buffer, 0, b);}} catch (IOException e) {e.printStackTrace();}}
}
3. 更新用户头像方法
在UserService类中添加方法
还是从dao层向controller开发,由于头像直接存在本地,没有存到数据库,这里不涉及dao层。
service层主要处理user表里的headUrl,这个方法在userMapper里写过,直接调用就可以。
//更新用户头像public int updateHeader(int userId, String headurl){return userMapper.updateHeader(userId, headurl);}
4. 配置文件
添加文件的上传存储路径。
# 上传资源存放的位置
community.path.upload=e:/nowcoder/data/upload
5. html
①首页index.html:进入【账号设置】。
<div class="dropdown-menu" aria-labelledby="navbarDropdown"><a class="dropdown-item text-center" href="site/profile.html">个人主页</a><a class="dropdown-item text-center" th:href="@{/user/setting}">账号设置</a><a class="dropdown-item text-center" th:href="@{/logout}">退出登录</a><div class="dropdown-divider"></div><span class="dropdown-item text-center text-secondary" th:utext="${loginUser.username}">nowcoder</span>
</div>
②账号设置页面setting.html:上传头像
<!-- 上传头像 --><h6 class="text-left text-info border-bottom pb-2">上传头像</h6><form class="mt-5" method="post" enctype="multipart/form-data" th:action="@{/user/upload}"><div class="form-group row mt-4"><label for="head-image" class="col-sm-2 col-form-label text-right">选择头像:</label><div class="col-sm-10"><div class="custom-file"><!-- name需要与控制器方法中形参名相同 --><input type="file" th:class="|custom-file-input ${error!=null?'is-invalid':''}|"id="head-image" name="headerImage" lang="es" required=""><label class="custom-file-label" for="head-image" data-browse="文件">选择一张图片</label><div class="invalid-feedback" th:text="${error}">该账号不存在!</div></div></div></div><div class="form-group row mt-4"><div class="col-sm-2"></div><div class="col-sm-10 text-center"><button type="submit" class="btn btn-info text-white form-control">立即上传</button></div></div></form